From 4916f1ad4ae8148ef77ef3af2d09dd8786aa2951 Mon Sep 17 00:00:00 2001 From: "omegent-app[bot]" <306514130+omegent-app[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:13:38 +0000 Subject: [PATCH 1/8] chore: pin effect packages to Effect main (addeaea) npm rc is still 4.0.0-rc.112. Consume unpublished Effect main via pkg.pr.new and allow nested URL tarball deps. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com> --- package.json | 6 +- packages/cli/package.json | 4 +- packages/e2e/package.json | 8 +- packages/effect-app/package.json | 1 + packages/infra/package.json | 4 +- packages/vue-components/package.json | 1 + packages/vue/package.json | 5 +- pnpm-lock.yaml | 179 ++++++++++++++------------- pnpm-workspace.yaml | 2 + 9 files changed, 110 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index 443358225..f7286af54 100644 --- a/package.json +++ b/package.json @@ -50,15 +50,15 @@ "@effect-app/infra": "workspace:*", "@effect/language-service": "0.86.2", "@effect/tsgo": "^0.31.0", - "@effect/platform-node": "4.0.0-rc.112", - "@effect/vitest": "4.0.0-rc.112", + "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "@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.112", + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "effect-app": "workspace:*", "eslint": "^10.4.1", "json5": "^2.2.3", diff --git a/packages/cli/package.json b/packages/cli/package.json index e0b8cbcc5..78b76d2a0 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.112", - "effect": "4.0.0-rc.112", + "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "js-yaml": "4.2.0" }, "devDependencies": { diff --git a/packages/e2e/package.json b/packages/e2e/package.json index 46ac8c208..994354740 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.112", - "@effect/platform-node": "4.0.0-rc.112", - "@effect/vitest": "4.0.0-rc.112", + "@effect/atom-vue": "https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "@tanstack/vue-query": "5.96.2", "@types/node": "25.9.1", "@vitejs/plugin-vue": "^6.0.7", - "effect": "^4.0.0-rc.112", + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "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 741edcb05..5a288a692 100644 --- a/packages/effect-app/package.json +++ b/packages/effect-app/package.json @@ -18,6 +18,7 @@ "validator": "^13.15.35" }, "devDependencies": { + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "@faker-js/faker": "^8.4.1", "@types/node": "25.9.1", "@types/validator": "^13.15.10", diff --git a/packages/infra/package.json b/packages/infra/package.json index c529c0c0f..08dfe5a42 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.112", + "@effect/sql-sqlite-node": "https://pkg.pr.new/Effect-TS/effect/@effect/sql-sqlite-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "@sentry/opentelemetry": "10.55.0", "@types/better-sqlite3": "^7.6.13", "@types/node": "25.9.1", @@ -33,6 +33,8 @@ "mongodb": "7.2.0", "redis": "^3.1.2", "redlock": "^4.2.0", + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "typescript": "~6.0.3", "vitest": "^4.1.7" }, diff --git a/packages/vue-components/package.json b/packages/vue-components/package.json index 63a2be3c5..4bdf4de36 100644 --- a/packages/vue-components/package.json +++ b/packages/vue-components/package.json @@ -36,6 +36,7 @@ "vuetify": "^4.0.8" }, "devDependencies": { + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "@effect-app/eslint-shared-config": "workspace:*", "@storybook/vue3": "^10.4.1", "@storybook/vue3-vite": "^10.4.1", diff --git a/packages/vue/package.json b/packages/vue/package.json index 5548fc4d7..097176169 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -25,7 +25,10 @@ "vue": "^3.5.35" }, "devDependencies": { - "@effect/vitest": "4.0.0-rc.112", + "effect": "https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/atom-vue": "https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/platform-browser": "https://pkg.pr.new/Effect-TS/effect/@effect/platform-browser@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", + "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70", "@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 ff9359f43..4d1d2d5da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,14 +45,14 @@ importers: specifier: 0.86.2 version: 0.86.2 "@effect/platform-node": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(redis@6.2.1(@opentelemetry/api@1.9.1)) + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(redis@6.2.1(@opentelemetry/api@1.9.1)) "@effect/tsgo": specifier: ^0.31.0 version: 0.31.0 "@effect/vitest": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(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: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(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 @@ -72,8 +72,8 @@ importers: specifier: ^0.54.0 version: 0.54.0 effect: - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112 + specifier: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 effect-app: specifier: workspace:* version: link:packages/effect-app @@ -117,11 +117,11 @@ importers: packages/cli: dependencies: "@effect/platform-node": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(redis@6.2.1(@opentelemetry/api@1.9.1)) + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(redis@6.2.1(@opentelemetry/api@1.9.1)) effect: - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112 + specifier: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 js-yaml: specifier: 4.2.0 version: 4.2.0 @@ -153,14 +153,14 @@ importers: version: link:../effect-app devDependencies: "@effect/atom-vue": - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) "@effect/platform-node": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(redis@6.2.1(@opentelemetry/api@1.9.1)) + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(redis@6.2.1(@opentelemetry/api@1.9.1)) "@effect/vitest": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(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: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(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))) @@ -171,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: ^4.0.0-rc.112 - version: 4.0.0-rc.112 + specifier: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 typescript: specifier: ~6.0.3 version: 6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e) @@ -191,9 +191,6 @@ importers: date-fns: specifier: ^4.4.0 version: 4.4.0 - effect: - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112 nanoid: specifier: ^5.1.11 version: 5.1.11 @@ -219,6 +216,9 @@ importers: "@types/validator": specifier: ^13.15.10 version: 13.15.10 + effect: + specifier: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 fast-check: specifier: ^4.9.0 version: 4.9.0 @@ -330,9 +330,6 @@ importers: packages/infra: dependencies: - "@effect/vitest": - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(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 @@ -345,9 +342,6 @@ importers: "@sentry/node": specifier: 10.55.0 version: 10.55.0(supports-color@8.1.1) - effect: - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112 effect-app: specifier: workspace:* version: link:../effect-app @@ -374,8 +368,11 @@ importers: specifier: ^7.9.5 version: 7.9.5(supports-color@8.1.1) "@effect/sql-sqlite-node": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112) + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/sql-sqlite-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/sql-sqlite-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70) + "@effect/vitest": + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(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))) "@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) @@ -397,6 +394,9 @@ importers: better-sqlite3: specifier: ^12.10.0 version: 12.10.0 + effect: + specifier: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 jwt-decode: specifier: ^4.0.0 version: 4.0.0 @@ -419,12 +419,6 @@ importers: packages/vue: dependencies: - "@effect/atom-vue": - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) - "@effect/platform-browser": - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112) "@formatjs/intl": specifier: ^4.1.12 version: 4.1.12 @@ -440,9 +434,6 @@ importers: change-case: specifier: ^5.4.4 version: 5.4.4 - effect: - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112 effect-app: specifier: workspace:* version: link:../effect-app @@ -453,9 +444,15 @@ importers: specifier: ^3.5.35 version: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) devDependencies: + "@effect/atom-vue": + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + "@effect/platform-browser": + specifier: https://pkg.pr.new/Effect-TS/effect/@effect/platform-browser@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/platform-browser@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70) "@effect/vitest": - specifier: 4.0.0-rc.112 - version: 4.0.0-rc.112(effect@4.0.0-rc.112)(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: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(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 @@ -465,6 +462,9 @@ importers: "@vitejs/plugin-vue": 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@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 intl-messageformat: specifier: ^11.2.7 version: 11.2.7 @@ -493,9 +493,6 @@ importers: "@tanstack/vue-form": specifier: ^1.32.0 version: 1.32.0(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) - effect: - specifier: ^4.0.0-rc.112 - version: 4.0.0-rc.112 effect-app: specifier: workspace:* version: link:../effect-app @@ -548,6 +545,9 @@ importers: "@vueuse/core": specifier: ^14.3.0 version: 14.3.0(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e))) + effect: + specifier: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 + version: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 jsdom: specifier: ^29.1.1 version: 29.1.1 @@ -1188,10 +1188,12 @@ packages: } hasBin: true - "@effect/atom-vue@4.0.0-rc.112": + "@effect/atom-vue@https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70": resolution: { - integrity: sha512-2sOESDblJGtJMxObqAw9UHHVrqhLHr2ha7JqvXdt2yhf4h57rK+rY3eEjclSgNsshABdcClP/Z0jNvgUD+A42g==, + integrity: sha512-hygZTEoDFGQ5q2+ao3m1yQemfh8CTxAOS/ylRMKkbuFXmClRC91oCt5i/8kvH/a4dXdRUdHmVhkJ8QD8N2cDXA==, + tarball: https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70, } + version: 4.0.0-rc.112 peerDependencies: effect: ^4.0.0-rc.112 vue: ^3.5.35 @@ -1202,10 +1204,12 @@ packages: } hasBin: true - "@effect/platform-browser@4.0.0-rc.112": + "@effect/platform-browser@https://pkg.pr.new/Effect-TS/effect/@effect/platform-browser@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70": resolution: { - integrity: sha512-GSlqNDnjILz2EqOFPhVdMEHxlPq6SGb8+KOpNnLfRvVJjXRTMawEO+v1PCuwt2CHAqESbJAa2Nk+qcQvTrv4MQ==, + integrity: sha512-/6H2ldq7LKktAbk34w2UUOfqlD9M2wCRWvm/O0QzkInjOVGPqAE52qc1MoLD8kTIDV+ZcuDeqM9Xtq5ajywWlw==, + tarball: https://pkg.pr.new/Effect-TS/effect/@effect/platform-browser@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70, } + version: 4.0.0-rc.112 peerDependencies: effect: ^4.0.0-rc.112 @@ -1217,10 +1221,12 @@ packages: peerDependencies: effect: ^4.0.0-beta.86 - "@effect/platform-node-shared@4.0.0-rc.112": + "@effect/platform-node-shared@https://pkg.pr.new/Effect-TS/effect/@effect/platform-node-shared@addeaea": resolution: { - integrity: sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ==, + integrity: sha512-XlXy4u1F/UE7JviJ2t17P7RB3uzxKEwPa6nXwWywE98gBQDcKEybg4rTb7E9QET2IgHyrVcbnwkf6deb4Mmuyw==, + tarball: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node-shared@addeaea, } + version: 4.0.0-rc.112 engines: { node: ">=18.0.0" } peerDependencies: effect: ^4.0.0-rc.112 @@ -1234,19 +1240,23 @@ packages: effect: ^4.0.0-beta.86 ioredis: ^5.7.0 - "@effect/platform-node@4.0.0-rc.112": + "@effect/platform-node@https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70": resolution: { - integrity: sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ==, + integrity: sha512-cTGBpi7ryIFc06wVrEGQmPGyrW5kC2DgaSe3yuPBm7YYlZENVS8uuFTuRoFTgkgU8DWHPz2RrWs03frJLUIqVQ==, + tarball: https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70, } + version: 4.0.0-rc.112 engines: { node: ">=18.0.0" } peerDependencies: effect: ^4.0.0-rc.112 redis: ">=5.0.0 <7.0.0" - "@effect/sql-sqlite-node@4.0.0-rc.112": + "@effect/sql-sqlite-node@https://pkg.pr.new/Effect-TS/effect/@effect/sql-sqlite-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70": resolution: { - integrity: sha512-jGRtbJsn5z7DqsMEPSbXq2Q9mYEvMy1v9DTeziTtDEnDzlxhxfrXkSeupZbYqAP5pukIM1JBa10zfAn6KnHG2Q==, + integrity: sha512-XGbD0nN4fMe/LlklpLBT6tl/fR9oItVQ5MeFu1usTIuX9vKl3PhKrxbmdBCLsXcaivlgXEb9FpCzsyvnQiB8Dw==, + tarball: https://pkg.pr.new/Effect-TS/effect/@effect/sql-sqlite-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70, } + version: 4.0.0-rc.112 peerDependencies: effect: ^4.0.0-rc.112 @@ -1305,13 +1315,15 @@ packages: } hasBin: true - "@effect/vitest@4.0.0-rc.112": + "@effect/vitest@https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70": resolution: { - integrity: sha512-mEKh/FI64mt8JK1/v9mpOrJYdnp+UFZdRUBMEdZMiKz7klg6NPqVgg/oeAGH6wOOQc2iAPcfc2H9BbAv1KyzMQ==, + integrity: sha512-gGXcxkIcb/PpRaAei3oKZfQDOyy35szoM5kD7MEnK0hZlPUOjq6rGFol0C5qd9e6hr37m5HxC8ePmC8xpx57ZA==, + tarball: https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70, } + version: 4.0.0-rc.112 peerDependencies: effect: ^4.0.0-rc.112 - vitest: ">=4.1.0 <5.0.0" + vitest: ">=5.0.0 <6.0.0" "@emnapi/core@1.10.0": resolution: { @@ -5739,10 +5751,12 @@ packages: integrity: sha512-+Y16hy3LhAju/FM3dz3Xp+aGi8dw9AtEzswcvwxREpeEPq+rL8TFS1PgWAO1+/3kKKH4bq9NdBGtfhHzM2K7Ow==, } - effect@4.0.0-rc.112: + effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70: resolution: { - integrity: sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A==, + integrity: sha512-FlnhpW5G20GdwEaH8fSnZF/gvi6BFIeMdz6hjRmQBqwbz2WlrY9qSTuOCQMZHytGPHkiHiQm+GPBBB1cBgWe0Q==, + tarball: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70, } + version: 4.0.0-rc.112 electron-to-chromium@1.5.267: resolution: { @@ -7495,11 +7509,6 @@ packages: integrity: sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==, } - msgpackr@2.0.5: - resolution: { - integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==, - } - muggle-string@0.4.1: resolution: { integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==, @@ -9114,9 +9123,9 @@ packages: } engines: { node: ">=20.18.1" } - undici@8.10.0: + undici@8.10.2: resolution: { - integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==, + integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==, } engines: { node: ">=22.19.0" } @@ -10324,16 +10333,16 @@ snapshots: - ioredis - utf-8-validate - "@effect/atom-vue@4.0.0-rc.112(effect@4.0.0-rc.112)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)))": + "@effect/atom-vue@https://pkg.pr.new/Effect-TS/effect/@effect/atom-vue@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(vue@3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)))": dependencies: - effect: 4.0.0-rc.112 + effect: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 vue: 3.5.35(typescript@6.0.3(patch_hash=6a20b8df080eb8a9c34008e1802ecfd839f4e90e07f1ee97f78694dbd95a520e)) "@effect/language-service@0.86.2": {} - "@effect/platform-browser@4.0.0-rc.112(effect@4.0.0-rc.112)": + "@effect/platform-browser@https://pkg.pr.new/Effect-TS/effect/@effect/platform-browser@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)": dependencies: - effect: 4.0.0-rc.112 + effect: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 "@effect/platform-node-shared@4.0.0-beta.86(effect@4.0.0-beta.86)": dependencies: @@ -10344,10 +10353,10 @@ snapshots: - bufferutil - utf-8-validate - "@effect/platform-node-shared@4.0.0-rc.112(effect@4.0.0-rc.112)": + "@effect/platform-node-shared@https://pkg.pr.new/Effect-TS/effect/@effect/platform-node-shared@addeaea(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)": dependencies: "@types/ws": 8.18.1 - effect: 4.0.0-rc.112 + effect: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 ws: 8.21.3 transitivePeerDependencies: - bufferutil @@ -10364,20 +10373,19 @@ snapshots: - bufferutil - utf-8-validate - "@effect/platform-node@4.0.0-rc.112(effect@4.0.0-rc.112)(redis@6.2.1(@opentelemetry/api@1.9.1))": + "@effect/platform-node@https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(redis@6.2.1(@opentelemetry/api@1.9.1))": dependencies: - "@effect/platform-node-shared": 4.0.0-rc.112(effect@4.0.0-rc.112) - effect: 4.0.0-rc.112 - mime: 4.1.0 + "@effect/platform-node-shared": https://pkg.pr.new/Effect-TS/effect/@effect/platform-node-shared@addeaea(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70) + effect: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 redis: 6.2.1(@opentelemetry/api@1.9.1) - undici: 8.10.0 + undici: 8.10.2 transitivePeerDependencies: - bufferutil - utf-8-validate - "@effect/sql-sqlite-node@4.0.0-rc.112(effect@4.0.0-rc.112)": + "@effect/sql-sqlite-node@https://pkg.pr.new/Effect-TS/effect/@effect/sql-sqlite-node@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)": dependencies: - effect: 4.0.0-rc.112 + effect: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 "@effect/tsgo-darwin-arm64@0.31.0": optional: true @@ -10410,9 +10418,9 @@ snapshots: "@effect/tsgo-win32-arm64": 0.31.0 "@effect/tsgo-win32-x64": 0.31.0 - "@effect/vitest@4.0.0-rc.112(effect@4.0.0-rc.112)(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@https://pkg.pr.new/Effect-TS/effect/@effect/vitest@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70(effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70)(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: 4.0.0-rc.112 + effect: https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 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": @@ -13058,10 +13066,7 @@ snapshots: uuid: 14.0.0 yaml: 2.9.0 - effect@4.0.0-rc.112: - dependencies: - fast-check: 4.9.0 - msgpackr: 2.0.5 + effect@https://pkg.pr.new/Effect-TS/effect/effect@addeaea0b0abe0dc24ca1d4d0fd194a0e458be70: {} electron-to-chromium@1.5.267: optional: true @@ -14387,10 +14392,6 @@ snapshots: optionalDependencies: msgpackr-extract: 3.0.4 - msgpackr@2.0.5: - optionalDependencies: - msgpackr-extract: 3.0.4 - muggle-string@0.4.1: {} multipasta@0.2.7: {} @@ -15701,7 +15702,7 @@ snapshots: undici@7.25.0: {} - undici@8.10.0: {} + undici@8.10.2: {} undici@8.3.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index af3462948..2df6c3501 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,7 @@ packages: - packages/* +# pkg.pr.new Effect tarballs nest URL deps; required to consume unpublished RC main. +blockExoticSubdeps: false allowBuilds: "@parcel/watcher": false better-sqlite3: true From b1c9b0ba30a6c1a5402181bdf75d6b4cfadb61e3 Mon Sep 17 00:00:00 2001 From: "omegent-app[bot]" <306514130+omegent-app[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:14:25 +0000 Subject: [PATCH 2/8] chore: adapt to Effect main Config/CLI and Schema APIs PascalCase constructors, Config.Record as a Config, native Arbitrary, Fiber.cache.span, and inet server addresses. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com> --- .changeset/effect-main-rc-apis.md | 14 + packages/cli/src/gist.ts | 8 +- packages/cli/src/index.ts | 40 +- .../e2e/test/repoInvalidation.e2e.test.ts | 7 +- packages/effect-app/src/Config/SecretURL.ts | 2 +- .../src/Model/query/new-kid-interpreter.ts | 388 +++++++++--------- packages/effect-app/src/Schema.ts | 28 +- packages/effect-app/src/Schema/brand.ts | 9 +- packages/effect-app/src/Schema/email.ts | 3 - packages/effect-app/src/Schema/ext.ts | 4 +- packages/effect-app/src/Schema/moreStrings.ts | 19 +- packages/effect-app/src/Schema/phoneNumber.ts | 4 - .../effect-app/src/client/apiClientFactory.ts | 9 +- packages/effect-app/test/moreStrings.test.ts | 14 +- packages/infra/src/CUPS.ts | 2 +- packages/infra/src/arbs.ts | 15 +- packages/infra/src/logger/shared.ts | 2 +- packages/infra/src/routing.ts | 2 +- .../src/routing/middleware/middleware.ts | 2 +- packages/infra/src/test.ts | 6 +- packages/infra/test/rawQuery.test.ts | 2 +- .../test/rpc-context-map-streaming.test.ts | 7 +- .../infra/test/rpc-e2e-invalidation.test.ts | 7 +- .../infra/test/rpc-stream-fullstack.test.ts | 7 +- 24 files changed, 298 insertions(+), 303 deletions(-) create mode 100644 .changeset/effect-main-rc-apis.md diff --git a/.changeset/effect-main-rc-apis.md b/.changeset/effect-main-rc-apis.md new file mode 100644 index 000000000..c526da852 --- /dev/null +++ b/.changeset/effect-main-rc-apis.md @@ -0,0 +1,14 @@ +--- +"effect-app": patch +"@effect-app/cli": patch +--- + +Adapt to unpublished Effect main (pkg.pr.new `addeaea`, still versioned `4.0.0-rc.112`). + +npm `rc` remains `4.0.0-rc.112`; this pin consumes Effect main until the next RC publishes. Breaking API updates: + +- PascalCase Config/CLI constructors (`Config.String`, `Flag.File`, `Config.NonEmptyString`, `Config.Redacted`, `Config.Literal`) +- `Config.Record` now returns a Config +- Schema `toArbitrary` (fast-check) replaced by native `effect/unstable/arbitrary` +- `Fiber.currentSpan` moved to `fiber.cache.span` +- HTTP server addresses are `InetAddressV4`/`InetAddressV6` instead of `TcpAddress` diff --git a/packages/cli/src/gist.ts b/packages/cli/src/gist.ts index c437ad960..272e6fd75 100644 --- a/packages/cli/src/gist.ts +++ b/packages/cli/src/gist.ts @@ -547,9 +547,9 @@ export class GistHandler extends Context.Service()("GistHandler", { handler: Effect.fn("effa-cli.gist.GistHandler")(function*({ YAMLPath }: { YAMLPath: string }) { // load company and environment from environment variables const CONFIG = yield* Config.all({ - company: Config.string("COMPANY"), - env: Config.string("ENV").pipe(Config.withDefault("local-dev")), - gistCacheId: Config.nonEmptyString("EFFA_GIST_CACHE_ID") + company: Config.String("COMPANY"), + env: Config.String("ENV").pipe(Config.withDefault("local-dev")), + gistCacheId: Config.NonEmptyString("EFFA_GIST_CACHE_ID") }) yield* Effect.logInfo(`Company: ${CONFIG.company}, ENV: ${CONFIG.env}`) @@ -576,7 +576,7 @@ export class GistHandler extends Context.Service()("GistHandler", { ) // load GitHub token securely from environment variable - const redactedToken = yield* Config.redacted(configFromYaml.settings.token_env) + const redactedToken = yield* Config.Redacted(configFromYaml.settings.token_env) yield* Effect.logInfo(`Using GitHub token from environment variable: ${configFromYaml.settings.token_env}`) yield* Effect.logInfo(`Token loaded: ${redactedToken}`) // this will show in logs diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a36611456..ff0b07779 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -403,7 +403,7 @@ NodeRuntime.runMain( * CLI */ - const WrapAsOption = Flag.string("wrap").pipe( + const WrapAsOption = Flag.String("wrap").pipe( Flag.withAlias("w"), Flag.optional, Flag.withDescription( @@ -413,7 +413,7 @@ NodeRuntime.runMain( // has prio over WrapAsOption const WrapAsArg = Argument - .string("wrap") + .String("wrap") .pipe( Argument.atLeast(1), Argument.optional, @@ -473,7 +473,7 @@ NodeRuntime.runMain( ) const EffectAppLibsPath = Argument - .directory("effect-app-libs-path", { mustExist: true }) + .Directory("effect-app-libs-path", { mustExist: true }) .pipe( Argument.withDefault("../../effect-app/libs"), Argument.withDescription("Path to the effect-app-libs directory") @@ -506,7 +506,7 @@ NodeRuntime.runMain( Effect.fn("effa-cli.ue")(function*({}) { yield* Effect.logInfo("Update effect-app and/or effect packages") - const prompted = yield* Prompt.select({ + const prompted = yield* Prompt.Select({ choices: [ { title: "effect-app", @@ -665,7 +665,7 @@ NodeRuntime.runMain( .make( "gist", { - config: Flag.file("config").pipe( + config: Flag.File("config").pipe( Flag.withDefault("gists.yaml"), Flag.withDescription("Path to YAML configuration file") ) @@ -683,10 +683,10 @@ NodeRuntime.runMain( .make( "nuke", { - dryRun: Flag.boolean("dry-run").pipe( + dryRun: Flag.Boolean("dry-run").pipe( Flag.withDescription("Show what would be done without making changes") ), - storePrune: Flag.boolean("store-prune").pipe( + storePrune: Flag.Boolean("store-prune").pipe( Flag.withDescription("Prune the package manager store") ) }, @@ -718,18 +718,18 @@ NodeRuntime.runMain( .make( "sync-effect", { - manifests: Flag.string("manifests").pipe( + manifests: Flag.String("manifests").pipe( Flag.withAlias("m"), Flag.optional, Flag.withDescription( "Comma-separated list of package.json paths to scan (default: package.json)" ) ), - prefix: Flag.string("prefix").pipe( + prefix: Flag.String("prefix").pipe( Flag.optional, Flag.withDescription("Subtree prefix (default: repos/effect)") ), - url: Flag.string("url").pipe( + url: Flag.String("url").pipe( Flag.optional, Flag.withDescription( "Git repository URL (default: https://github.com/Effect-TS/effect.git)" @@ -753,24 +753,24 @@ NodeRuntime.runMain( .make( "sync-effect-app", { - manifests: Flag.string("manifests").pipe( + manifests: Flag.String("manifests").pipe( Flag.withAlias("m"), Flag.optional, Flag.withDescription( "Comma-separated list of package.json paths to scan (default: package.json)" ) ), - prefix: Flag.string("prefix").pipe( + prefix: Flag.String("prefix").pipe( Flag.optional, Flag.withDescription("Subtree prefix (default: repos/libs)") ), - url: Flag.string("url").pipe( + url: Flag.String("url").pipe( Flag.optional, Flag.withDescription( "Git repository URL (default: https://github.com/effect-app/libs.git)" ) ), - ref: Flag.string("ref").pipe( + ref: Flag.String("ref").pipe( Flag.optional, Flag.withDescription("Ref escape hatch (branch/tag/sha/latest); latest means main") ) @@ -792,7 +792,7 @@ NodeRuntime.runMain( ) .pipe(Command.withDescription("Sync the Effect App libs subtree to the version pinned in package.json")) - const SharedLockfileFlag = Flag.file("lockfile").pipe( + const SharedLockfileFlag = Flag.File("lockfile").pipe( Flag.optional, Flag.withDescription("Path to lockfile (default: .shared.json)") ) @@ -802,10 +802,10 @@ NodeRuntime.runMain( "sync", { lockfile: SharedLockfileFlag, - update: Flag.boolean("update").pipe( + update: Flag.Boolean("update").pipe( Flag.withDescription("Bump the pinned ref to the latest sha before syncing") ), - ref: Flag.string("ref").pipe( + ref: Flag.String("ref").pipe( Flag.optional, Flag.withDescription("Ref (branch/tag/sha) to update to; default: remote default branch HEAD") ) @@ -839,16 +839,16 @@ NodeRuntime.runMain( "sync-push", { lockfile: SharedLockfileFlag, - message: Flag.string("message").pipe( + message: Flag.String("message").pipe( Flag.withAlias("m"), Flag.optional, Flag.withDescription("Commit message for the push") ), - branch: Flag.string("branch").pipe( + branch: Flag.String("branch").pipe( Flag.optional, Flag.withDescription("Branch name in shared repo (default: auto-generated)") ), - pr: Flag.boolean("pr").pipe( + pr: Flag.Boolean("pr").pipe( Flag.withDescription("Open a PR via `gh pr create` after pushing") ) }, diff --git a/packages/e2e/test/repoInvalidation.e2e.test.ts b/packages/e2e/test/repoInvalidation.e2e.test.ts index 7247e2280..6091c9dbb 100644 --- a/packages/e2e/test/repoInvalidation.e2e.test.ts +++ b/packages/e2e/test/repoInvalidation.e2e.test.ts @@ -19,6 +19,7 @@ import * as ManagedRuntime from "effect/ManagedRuntime" import * as Option from "effect/Option" import * as Scope from "effect/Scope" import { FetchHttpClient } from "effect/unstable/http" +import * as NetAddress from "effect/unstable/net/NetAddress" import * as Reactivity from "effect/unstable/reactivity/Reactivity" import { RpcSerialization } from "effect/unstable/rpc" import { createServer } from "http" @@ -143,8 +144,10 @@ const ClientLayer = Layer Effect.gen(function*() { const server = yield* HttpServer.HttpServer const addr = server.address - if (addr._tag !== "TcpAddress") return yield* Effect.die(new Error("expected TcpAddress")) - const host = addr.hostname === "0.0.0.0" ? "127.0.0.1" : addr.hostname + if (NetAddress.isUnixPathAddress(addr)) { + return yield* Effect.die(new Error("expected inet address")) + } + const host = NetAddress.isUnspecified(addr.address) ? "127.0.0.1" : NetAddress.formatUrlHost(addr.address) return ApiClientFactory .layer({ url: `http://${host}:${addr.port}`, headers: Option.none() }) .pipe(Layer.provide(FetchHttpClient.layer)) diff --git a/packages/effect-app/src/Config/SecretURL.ts b/packages/effect-app/src/Config/SecretURL.ts index 044f4bc7e..684db9396 100644 --- a/packages/effect-app/src/Config/SecretURL.ts +++ b/packages/effect-app/src/Config/SecretURL.ts @@ -76,5 +76,5 @@ export const value: (self: SecretURL) => string = internal.value export const unsafeWipe: (self: SecretURL) => void = internal.unsafeWipe export const secretURL = (name?: string): Config.Config => { - return Config.map(Config.nonEmptyString(name), fromString) + return Config.map(Config.NonEmptyString(name), fromString) } diff --git a/packages/effect-app/src/Model/query/new-kid-interpreter.ts b/packages/effect-app/src/Model/query/new-kid-interpreter.ts index 8c55a1938..e3c680444 100644 --- a/packages/effect-app/src/Model/query/new-kid-interpreter.ts +++ b/packages/effect-app/src/Model/query/new-kid-interpreter.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { identity, pipe } from "effect/Function" -import * as Match from "effect/Match" +import { identity } from "effect/Function" import * as Array from "../../Array.ts" import { toNonEmptyArray } from "../../Array.ts" import * as Option from "../../Option.ts" @@ -192,197 +191,204 @@ const interpret = < ? { ..._, path: `${path}.-1.${_.path}` } : { ..._, result: _.result.map(applyPath(path)) } - pipe( - a, - Match.valueTags({ - value: () => { - // data.filter.push(value) - }, - where: ({ current, operation, relation, subPath }) => { - upd(interpret(current)) - if (typeof operation === "function") { - data.filter.push( - { - t: "where-scope", - result: interpret(operation(make())).filter.map(subPath ? applyPath(subPath) : identity), - relation - } - ) - } else { - data.filter.push( - { - t: "where", - path: operation[0], - op: operation.length === 2 ? "eq" : operation[1], - value: operation.length === 2 ? operation[1] : operation[2] - } - ) - } - }, - and: ({ current, operation, relation }) => { - upd(interpret(current)) - if (typeof operation === "function") { - data.filter.push( - { t: "and-scope", result: interpret(operation(make())).filter, relation } - ) - } else { - data.filter.push( - { - t: "and", - path: operation[0], - op: operation.length === 2 ? "eq" : operation[1], - value: operation.length === 2 ? operation[1] : operation[2] - } - ) - } - }, - or: ({ current, operation, relation }) => { - upd(interpret(current)) - if (typeof operation === "function") { - data.filter.push( - { t: "or-scope", result: interpret(operation(make())).filter, relation } - ) - } else { - data.filter.push( - { - t: "or", - path: operation[0], - op: operation.length === 2 ? "eq" : operation[1], - value: operation.length === 2 ? operation[1] : operation[2] - } - ) - } - }, - one: ({ current }) => { - upd(interpret(current)) - data.limit = 1 - data.ttype = "one" - }, - count: ({ current }) => { - upd(interpret(current)) - data.ttype = "count" - data.schema = S.Struct({ id: S.String }) as any - }, - order: ({ current, direction, field }) => { - upd(interpret(current)) - data.order.push({ key: field, direction }) - }, - page: (v) => { - upd(interpret(v.current)) - data.limit = v.take - data.skip = v.skip - }, - project: (v) => { - upd(interpret(v.current)) - if (v.mode === "aggregate" && v.aggregateMap) { - data.schema = v.schema - data.mode = "aggregate" - data.aggregateMap = Object.fromEntries( - Object.entries(v.aggregateMap).map(([key, expression]) => { - switch (expression._tag) { - case "agg-field": - return [key, { _tag: "agg-field" as const, path: expression.path }] - case "agg-count": - return [key, { _tag: "agg-count" as const }] - case "agg-count-when": { - const filter = interpret(expression.operation(make())).filter - return [key, { _tag: "agg-count-when" as const, filter }] - } - case "agg-sum": - return [key, { _tag: "agg-sum" as const, field: expression.field }] - case "agg-min": - return [key, { _tag: "agg-min" as const, field: expression.field }] - case "agg-max": - return [key, { _tag: "agg-max" as const, field: expression.field }] - } - }) - ) - return - } - if (v.computed && v.mode === "transform") { - throw new Error("Computed projections require mode 'project' or 'collect', not 'transform'") - } - data.schema = v.schema - data.mode = v.computed - ? v.mode === "collect" ? "collect" : "project" - : v.mode - data.computed = v.computed - ? Object.fromEntries( - Object.entries(v.computed).map(([key, expression]) => { - const e = expression - const op = "operation" in e ? e.operation : undefined - const filter = op ? interpret(op(make())).filter.map(applyPath(e.path)) : [] - switch (e._tag) { - case "relation-count": - case "relation-any": - case "relation-every": - return [key, { _tag: e._tag, path: e.path, filter }] - case "relation-distinct-count": - case "relation-sum": - return [ - key, - { _tag: e._tag, path: e.path, field: e.field, filter } - ] - case "relation-sum-expr": - return [ - key, - { _tag: e._tag, path: e.path, expression: e.expression, filter } - ] - case "relation-sum-expr-by": - return [ - key, - { - _tag: e._tag, - path: e.path, - expression: e.expression, - unit: e.unit, - filter - } - ] - case "relation-sum-expr-normalized": - return [ - key, - { - _tag: e._tag, - path: e.path, - expression: e.expression, - unit: e.unit, - toBase: e.toBase, - factors: e.factors, - filter - } - ] - case "relation-collect": - return [ - key, - { - _tag: e._tag, - path: e.path, - field: e.field, - distinct: e.distinct, - filter - } - ] - case "relation-collect-fields": - return [ - key, - { - _tag: e._tag, - path: e.path, - fields: e.fields, - distinct: e.distinct, - filter - } - ] - case "relation-length": - return [key, { _tag: e._tag, path: e.path }] + switch (a._tag) { + case "value": + break + case "where": { + const { current, operation, relation, subPath } = a + upd(interpret(current)) + if (typeof operation === "function") { + data.filter.push( + { + t: "where-scope", + result: interpret(operation(make())).filter.map(subPath ? applyPath(subPath) : identity), + relation + } + ) + } else { + data.filter.push( + { + t: "where", + path: operation[0], + op: operation.length === 2 ? "eq" : operation[1], + value: operation.length === 2 ? operation[1] : operation[2] + } + ) + } + break + } + case "and": { + const { current, operation, relation } = a + upd(interpret(current)) + if (typeof operation === "function") { + data.filter.push( + { t: "and-scope", result: interpret(operation(make())).filter, relation } + ) + } else { + data.filter.push( + { + t: "and", + path: operation[0], + op: operation.length === 2 ? "eq" : operation[1], + value: operation.length === 2 ? operation[1] : operation[2] + } + ) + } + break + } + case "or": { + const { current, operation, relation } = a + upd(interpret(current)) + if (typeof operation === "function") { + data.filter.push( + { t: "or-scope", result: interpret(operation(make())).filter, relation } + ) + } else { + data.filter.push( + { + t: "or", + path: operation[0], + op: operation.length === 2 ? "eq" : operation[1], + value: operation.length === 2 ? operation[1] : operation[2] + } + ) + } + break + } + case "one": { + upd(interpret(a.current)) + data.limit = 1 + data.ttype = "one" + break + } + case "count": { + upd(interpret(a.current)) + data.ttype = "count" + data.schema = S.Struct({ id: S.String }) as any + break + } + case "order": { + upd(interpret(a.current)) + data.order.push({ key: a.field, direction: a.direction }) + break + } + case "page": { + upd(interpret(a.current)) + data.limit = a.take + data.skip = a.skip + break + } + case "project": { + upd(interpret(a.current)) + if (a.mode === "aggregate" && a.aggregateMap) { + data.schema = a.schema + data.mode = "aggregate" + data.aggregateMap = Object.fromEntries( + Object.entries(a.aggregateMap).map(([key, expression]) => { + switch (expression._tag) { + case "agg-field": + return [key, { _tag: "agg-field" as const, path: expression.path }] + case "agg-count": + return [key, { _tag: "agg-count" as const }] + case "agg-count-when": { + const filter = interpret(expression.operation(make())).filter + return [key, { _tag: "agg-count-when" as const, filter }] } - }) - ) - : undefined + case "agg-sum": + return [key, { _tag: "agg-sum" as const, field: expression.field }] + case "agg-min": + return [key, { _tag: "agg-min" as const, field: expression.field }] + case "agg-max": + return [key, { _tag: "agg-max" as const, field: expression.field }] + } + }) + ) + break } - }) - ) + if (a.computed && a.mode === "transform") { + throw new Error("Computed projections require mode 'project' or 'collect', not 'transform'") + } + data.schema = a.schema + data.mode = a.computed + ? a.mode === "collect" ? "collect" : "project" + : a.mode + data.computed = a.computed + ? Object.fromEntries( + Object.entries(a.computed).map(([key, expression]) => { + const e = expression + const op = "operation" in e ? e.operation : undefined + const filter = op ? interpret(op(make())).filter.map(applyPath(e.path)) : [] + switch (e._tag) { + case "relation-count": + case "relation-any": + case "relation-every": + return [key, { _tag: e._tag, path: e.path, filter }] + case "relation-distinct-count": + case "relation-sum": + return [ + key, + { _tag: e._tag, path: e.path, field: e.field, filter } + ] + case "relation-sum-expr": + return [ + key, + { _tag: e._tag, path: e.path, expression: e.expression, filter } + ] + case "relation-sum-expr-by": + return [ + key, + { + _tag: e._tag, + path: e.path, + expression: e.expression, + unit: e.unit, + filter + } + ] + case "relation-sum-expr-normalized": + return [ + key, + { + _tag: e._tag, + path: e.path, + expression: e.expression, + unit: e.unit, + toBase: e.toBase, + factors: e.factors, + filter + } + ] + case "relation-collect": + return [ + key, + { + _tag: e._tag, + path: e.path, + field: e.field, + distinct: e.distinct, + filter + } + ] + case "relation-collect-fields": + return [ + key, + { + _tag: e._tag, + path: e.path, + fields: e.fields, + distinct: e.distinct, + filter + } + ] + case "relation-length": + return [key, { _tag: e._tag, path: e.path }] + } + }) + ) + : undefined + break + } + } return data } diff --git a/packages/effect-app/src/Schema.ts b/packages/effect-app/src/Schema.ts index ebd79f40d..a20a48b0d 100644 --- a/packages/effect-app/src/Schema.ts +++ b/packages/effect-app/src/Schema.ts @@ -2,10 +2,11 @@ import * as S from "effect/Schema" import { type Simplify } from "effect/Struct" import type * as Tracer from "effect/Tracer" import type { RequiredKeys } from "effect/Types" +import type { Arbitrary as FastCheckArbitrary } from "fast-check" import type { NonEmptyReadonlyArray } from "./Array.ts" -import { fakerArb } from "./faker.ts" import { Email as EmailT, type Email as EmailType } from "./Schema/email.ts" import { concurrencyUnbounded, withDefaultMake, withDefaultParseOptions } from "./Schema/ext.ts" +import type { FC } from "./Schema/FastCheck.ts" import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.ts" import { type AST } from "./Schema/schema.ts" import * as SchemaAST from "./SchemaAST.ts" @@ -129,6 +130,9 @@ export { NonEmptyString } from "./Schema/strings.ts" export * as SchemaIssue from "effect/SchemaIssue" +/** Fast-check generator factory previously exported as `Schema.Arbitrary`. */ +export type Arbitrary = (fc: FC) => FastCheckArbitrary + export const decodeEffectConcurrently: typeof S.decodeEffect = withDefaultParseOptions(S.decodeEffect) export const decodeUnknownEffectConcurrently: typeof S.decodeUnknownEffect = withDefaultParseOptions( S.decodeUnknownEffect @@ -347,29 +351,11 @@ export interface WithOptionalSpan { [SpanId]?: Tracer.Span } -const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType -const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType - -export const Email = EmailT - .pipe( - S.annotate({ - // eslint-disable-next-line @typescript-eslint/unbound-method - toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail) - }), - withDefaultMake - ) +export const Email = EmailT.pipe(withDefaultMake) export type Email = EmailType -export const PhoneNumber = PhoneNumberT - .pipe( - S.annotate({ - toArbitrary: () => (fc) => - // eslint-disable-next-line @typescript-eslint/unbound-method - fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber) - }), - withDefaultMake - ) +export const PhoneNumber = PhoneNumberT.pipe(withDefaultMake) export type PhoneNumber = PhoneNumberType diff --git a/packages/effect-app/src/Schema/brand.ts b/packages/effect-app/src/Schema/brand.ts index 5f522daad..73497ee74 100644 --- a/packages/effect-app/src/Schema/brand.ts +++ b/packages/effect-app/src/Schema/brand.ts @@ -28,12 +28,7 @@ export interface Constructor> { is(a: Unbranded): a is Unbranded & A } -type BrandAnnotations> = - & S.Annotations.Filter - & ( - C extends string ? { readonly toArbitrary?: S.Annotations.ToArbitrary.Declaration } - : {} - ) +type BrandAnnotations = S.Annotations.Filter export interface BrandedSchema> extends S.Bottom< @@ -57,7 +52,7 @@ export interface BrandedSchema> extends export const fromBrand = >( constructor: Constructor, - options?: BrandAnnotations + options?: BrandAnnotations ) => (self: Self): BrandedSchema => { const branded = S.fromBrand(options?.identifier ?? "Brand", constructor as any)(self as any) diff --git a/packages/effect-app/src/Schema/email.ts b/packages/effect-app/src/Schema/email.ts index 229c202b7..dc1f93330 100644 --- a/packages/effect-app/src/Schema/email.ts +++ b/packages/effect-app/src/Schema/email.ts @@ -22,8 +22,5 @@ export const Email = S identifier: "Email", description: "an email according to RFC 5322", jsonSchema: { format: "email", minLength: 3, maxLength: 998 } - }), - S.annotate({ - toArbitrary: () => (fc) => fc.emailAddress().map((_) => _ as Email) }) ) diff --git a/packages/effect-app/src/Schema/ext.ts b/packages/effect-app/src/Schema/ext.ts index 232dbf2b0..4f3e63603 100644 --- a/packages/effect-app/src/Schema/ext.ts +++ b/packages/effect-app/src/Schema/ext.ts @@ -54,8 +54,8 @@ type ProvidedCodec = S.Codec< const concurrencySetting = Effect.runSync( Config - .literal("unbounded", "SCHEMA_CONCURRENCY") - .pipe(Config.orElse(() => Config.number("SCHEMA_CONCURRENCY")), Config.option) + .Literal("unbounded", "SCHEMA_CONCURRENCY") + .pipe(Config.orElse(() => Config.Number("SCHEMA_CONCURRENCY")), Config.option) ) export const DefaultParseOptions: SchemaAST.ParseOptions = { diff --git a/packages/effect-app/src/Schema/moreStrings.ts b/packages/effect-app/src/Schema/moreStrings.ts index 22f3ef8ad..d8ef9902e 100644 --- a/packages/effect-app/src/Schema/moreStrings.ts +++ b/packages/effect-app/src/Schema/moreStrings.ts @@ -15,7 +15,7 @@ import * as Effect from "effect/Effect" import { pipe } from "effect/Function" import * as S from "effect/Schema" import type { Simplify } from "effect/Types" -import { customRandom, nanoid, urlAlphabet } from "nanoid" +import { nanoid } from "nanoid" import validator from "validator" import type * as SchemaAST from "../SchemaAST.ts" import { type BrandedSchema, fromBrand, nominal } from "./brand.ts" @@ -164,23 +164,16 @@ export type StringId = string & StringIdBrand const minLength = 6 const maxLength = 50 -const size = 21 -const length = 10 * size const StringIdSchemaBase = pipe( S.String, S.check(S.isMinLength(minLength), S.isMaxLength(maxLength)), fromBrand(nominal(), { identifier: "StringId", - toArbitrary: () => (fc) => StringIdArb()(fc), jsonSchema: {} }) ) const makeStringId = (s?: string): StringId => s !== undefined ? S.decodeSync(StringIdSchemaBase)(s) : nanoid() as unknown as StringId -const StringIdArb = (): S.Arbitrary => (fc) => - fc - .uint8Array({ minLength: length, maxLength: length }) - .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId) /** * A string that is at least 6 characters long and a maximum of 50. * @@ -226,18 +219,11 @@ export function prefixedStringId() { ) => { type FullPrefix = `${Prefix}${Separator}` const pref = `${prefix}${separator ?? "-"}` as FullPrefix - const arb = (): S.Arbitrary => (fc) => - StringIdArb()(fc).map( - (x) => (pref + x.substring(0, 50 - pref.length)) as Type - ) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const s = StringIdSchemaBase .pipe( S.refine((x: string): x is Type => x.startsWith(pref), { identifier: name - }), - S.annotate({ - toArbitrary: () => (fc) => arb()(fc) }) ) const schema = s.pipe(withDefaultMake) @@ -331,8 +317,5 @@ export const Url: UrlSchema = S identifier: "Url", jsonSchema: { format: "uri" } }), - S.annotate({ - toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url) - }), withDefaultMake ) diff --git a/packages/effect-app/src/Schema/phoneNumber.ts b/packages/effect-app/src/Schema/phoneNumber.ts index 1134ed11e..2ddc9f6e6 100644 --- a/packages/effect-app/src/Schema/phoneNumber.ts +++ b/packages/effect-app/src/Schema/phoneNumber.ts @@ -3,7 +3,6 @@ import { isValidPhone } from "effect-app/validation" import * as S from "effect/Schema" import type { Simplify } from "effect/Types" import { withDefaultMake } from "./ext.ts" -import { Numbers } from "./FastCheck.ts" import type { B } from "./schema.ts" import type { NonEmptyStringBrand } from "./strings.ts" @@ -23,8 +22,5 @@ export const PhoneNumber = S description: "a phone number with at least 7 digits", jsonSchema: { format: "phone" } }), - S.annotate({ - toArbitrary: () => (fc) => Numbers(7, 10)(fc).map((_) => _ as PhoneNumber) - }), withDefaultMake ) diff --git a/packages/effect-app/src/client/apiClientFactory.ts b/packages/effect-app/src/client/apiClientFactory.ts index dfab8d5f5..b13436285 100644 --- a/packages/effect-app/src/client/apiClientFactory.ts +++ b/packages/effect-app/src/client/apiClientFactory.ts @@ -25,13 +25,8 @@ export interface ApiConfig { } export const DefaultApiConfig = Config.all({ - url: Config.string("apiUrl").pipe(Config.withDefault("/api")), - headers: Config - .schema( - Config.Record(Schema.String, Schema.String), - "headers" - ) - .pipe(Config.option) + url: Config.String("apiUrl").pipe(Config.withDefault("/api")), + headers: Config.Record(Schema.String, Schema.String, "headers").pipe(Config.option) }) export type Req = S.Top & { diff --git a/packages/effect-app/test/moreStrings.test.ts b/packages/effect-app/test/moreStrings.test.ts index a09f028a5..2189c15c4 100644 --- a/packages/effect-app/test/moreStrings.test.ts +++ b/packages/effect-app/test/moreStrings.test.ts @@ -1,5 +1,4 @@ import * as S from "effect-app/Schema" -import * as fc from "fast-check" import { urlAlphabet } from "nanoid" import { test } from "vitest" @@ -7,11 +6,10 @@ const nanoidAlphabet = new Set(urlAlphabet) const isNanoId = (value: string) => value.length === 21 && Array.from(value).every((char) => nanoidAlphabet.has(char)) -test("StringId arbitrary generates nanoid-shaped values", () => { - fc.assert( - fc.property(S.toArbitrary(S.StringId)(fc), (value) => { - expect(isNanoId(value)).toBe(true) - expect(S.is(S.StringId)(value)).toBe(true) - }) - ) +test("StringId make generates nanoid-shaped values", () => { + for (let i = 0; i < 20; i++) { + const value = S.StringId.make() + expect(isNanoId(value)).toBe(true) + expect(S.is(S.StringId)(value)).toBe(true) + } }) diff --git a/packages/infra/src/CUPS.ts b/packages/infra/src/CUPS.ts index cfa52c3f0..49c938311 100644 --- a/packages/infra/src/CUPS.ts +++ b/packages/infra/src/CUPS.ts @@ -131,7 +131,7 @@ function* buildListArgs(config?: { host?: string | undefined }) { export const CUPSConfig = Config.all({ server: Config - .string("server") + .String("server") .pipe( Config.map((s) => new URL(s)), Config.option, diff --git a/packages/infra/src/arbs.ts b/packages/infra/src/arbs.ts index e3051ece9..0cd545b32 100644 --- a/packages/infra/src/arbs.ts +++ b/packages/infra/src/arbs.ts @@ -3,7 +3,9 @@ import { faker } from "@faker-js/faker" import { setFaker } from "effect-app/faker" import type * as S from "effect-app/Schema" -import * as FastCheck from "effect/testing/FastCheck" +import * as Effect from "effect/Effect" +import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary" +import * as FastCheck from "fast-check" import { Random } from "fast-check" import { congruential32 } from "pure-rand/generator/congruential32" @@ -20,3 +22,14 @@ export function generate(arb: FastCheck.Arbitrary) { export function generateFromArbitrary(arb: S.Arbitrary) { return generate(arb(FastCheck)) } + +export function generateFromSchema(schema: S) { + const samples = Effect.runSync( + Arbitrary.sampleEffect(Arbitrary.schema(schema), { count: 1, seed }) + ) + const value = samples[0] + if (value === undefined) { + throw new Error("failed to sample schema") + } + return { value } +} diff --git a/packages/infra/src/logger/shared.ts b/packages/infra/src/logger/shared.ts index c79da9a4d..60e8387cf 100644 --- a/packages/infra/src/logger/shared.ts +++ b/packages/infra/src/logger/shared.ts @@ -5,7 +5,7 @@ import { storeId } from "effect-app/Store" import type * as Fiber from "effect/Fiber" export function getRequestContextFromFiber(fiber: Fiber.Fiber) { - const span = Option.fromNullishOr(fiber.currentSpan) + const span = Option.fromNullishOr(fiber.cache.span) const locale = fiber.getRef(LocaleRef) const namespace = fiber.getRef(storeId) return RequestContext.make({ diff --git a/packages/infra/src/routing.ts b/packages/infra/src/routing.ts index 438efbae8..edce4156c 100644 --- a/packages/infra/src/routing.ts +++ b/packages/infra/src/routing.ts @@ -182,7 +182,7 @@ export type RouteMatcher< export const skipOnProd = Effect .gen(function*() { - const env = yield* Config.string("env") + const env = yield* Config.String("env") return env !== "prod" }) .pipe(Effect.orDie) diff --git a/packages/infra/src/routing/middleware/middleware.ts b/packages/infra/src/routing/middleware/middleware.ts index a549fb64a..1b84d226d 100644 --- a/packages/infra/src/routing/middleware/middleware.ts +++ b/packages/infra/src/routing/middleware/middleware.ts @@ -61,7 +61,7 @@ const summarizePayload = (payload: unknown): unknown => export const DevModeLive = Layer.effect( DevMode, Effect.gen(function*() { - const env = yield* Config.string("env").pipe(Config.withDefault("local-dev")) + const env = yield* Config.String("env").pipe(Config.withDefault("local-dev")) return env !== "prod" }) ) diff --git a/packages/infra/src/test.ts b/packages/infra/src/test.ts index 8d8e6d313..ba59a3227 100644 --- a/packages/infra/src/test.ts +++ b/packages/infra/src/test.ts @@ -1,12 +1,12 @@ import * as S from "effect-app/Schema" import { copy } from "effect-app/utils" -import { generateFromArbitrary } from "./arbs.ts" +import { generateFromSchema } from "./arbs.ts" /** * Given the schema for an object-like structure, creates a function that generates random instances of that object with some values provided. */ export const createRandomInstance = (s: S.Codec & { fields: S.Struct.Fields }) => { - const gen = generateFromArbitrary(S.toArbitrary(s)) + const gen = generateFromSchema(s) return (overrides?: Partial) => { const v = gen.value return overrides ? copy(v, overrides) : v @@ -17,7 +17,7 @@ export const createRandomInstance = (s: S.Codec * Like `createRandomInstance`, but takes encoded values rather than decoded ones. */ export const createRandomInstanceI = (s: S.Codec & { fields: S.Struct.Fields }) => { - const gen = generateFromArbitrary(S.toArbitrary(s)) + const gen = generateFromSchema(s) const encode = S.encodeSync(s) const decode = S.decodeSync(s) return (overrides?: Partial) => { diff --git a/packages/infra/test/rawQuery.test.ts b/packages/infra/test/rawQuery.test.ts index 8d2007ea8..f94900a6c 100644 --- a/packages/infra/test/rawQuery.test.ts +++ b/packages/infra/test/rawQuery.test.ts @@ -99,7 +99,7 @@ class SomethingRepo extends Context.Service()( Layer.provide( Effect .gen(function*() { - const url = yield* Config.redacted("STORAGE_URL").pipe( + const url = yield* Config.Redacted("STORAGE_URL").pipe( Config.withDefault( Redacted.make( // the emulator doesn't implement array projections :/ so you need an actual cloud instance! diff --git a/packages/infra/test/rpc-context-map-streaming.test.ts b/packages/infra/test/rpc-context-map-streaming.test.ts index d0a332345..462b483f9 100644 --- a/packages/infra/test/rpc-context-map-streaming.test.ts +++ b/packages/infra/test/rpc-context-map-streaming.test.ts @@ -48,6 +48,7 @@ import * as Layer from "effect/Layer" import * as Option from "effect/Option" import * as Stream from "effect/Stream" import { FetchHttpClient } from "effect/unstable/http" +import * as NetAddress from "effect/unstable/net/NetAddress" import { RpcSerialization } from "effect/unstable/rpc" import { createServer } from "http" import { RequestContextMiddleware } from "../src/internal/RequestContextMiddleware.js" @@ -193,8 +194,10 @@ const ClientLayer = Layer Effect.gen(function*() { const server = yield* HttpServer.HttpServer const addr = server.address - if (addr._tag !== "TcpAddress") return yield* Effect.die(new Error("expected TcpAddress")) - const host = addr.hostname === "0.0.0.0" ? "127.0.0.1" : addr.hostname + if (NetAddress.isUnixPathAddress(addr)) { + return yield* Effect.die(new Error("expected inet address")) + } + const host = NetAddress.isUnspecified(addr.address) ? "127.0.0.1" : NetAddress.formatUrlHost(addr.address) const url = `http://${host}:${addr.port}` return ApiClientFactory .layer({ url, headers: Option.none() }) diff --git a/packages/infra/test/rpc-e2e-invalidation.test.ts b/packages/infra/test/rpc-e2e-invalidation.test.ts index 1f2010d1e..88689c9b8 100644 --- a/packages/infra/test/rpc-e2e-invalidation.test.ts +++ b/packages/infra/test/rpc-e2e-invalidation.test.ts @@ -30,6 +30,7 @@ import * as Option from "effect/Option" import * as Ref from "effect/Ref" import * as Stream from "effect/Stream" import { FetchHttpClient } from "effect/unstable/http" +import * as NetAddress from "effect/unstable/net/NetAddress" import { RpcSerialization } from "effect/unstable/rpc" import { createServer } from "http" import { RequestContextMiddleware } from "../src/internal/RequestContextMiddleware.js" @@ -230,8 +231,10 @@ const ClientLayer = Layer Effect.gen(function*() { const server = yield* HttpServer.HttpServer const addr = server.address - if (addr._tag !== "TcpAddress") return yield* Effect.die(new Error("expected TcpAddress")) - const host = addr.hostname === "0.0.0.0" ? "127.0.0.1" : addr.hostname + if (NetAddress.isUnixPathAddress(addr)) { + return yield* Effect.die(new Error("expected inet address")) + } + const host = NetAddress.isUnspecified(addr.address) ? "127.0.0.1" : NetAddress.formatUrlHost(addr.address) const url = `http://${host}:${addr.port}` return ApiClientFactory .layer({ url, headers: Option.none() }) diff --git a/packages/infra/test/rpc-stream-fullstack.test.ts b/packages/infra/test/rpc-stream-fullstack.test.ts index e73aa53ac..f2513da5c 100644 --- a/packages/infra/test/rpc-stream-fullstack.test.ts +++ b/packages/infra/test/rpc-stream-fullstack.test.ts @@ -23,6 +23,7 @@ import * as Layer from "effect/Layer" import * as Option from "effect/Option" import * as Stream from "effect/Stream" import { FetchHttpClient } from "effect/unstable/http" +import * as NetAddress from "effect/unstable/net/NetAddress" import { RpcSerialization } from "effect/unstable/rpc" import { createServer } from "http" import { makeRouter } from "../src/routing.js" @@ -176,8 +177,10 @@ const ClientLayer = Layer Effect.gen(function*() { const server = yield* HttpServer.HttpServer const addr = server.address - if (addr._tag !== "TcpAddress") return yield* Effect.die(new Error("expected TcpAddress")) - const host = addr.hostname === "0.0.0.0" ? "127.0.0.1" : addr.hostname + if (NetAddress.isUnixPathAddress(addr)) { + return yield* Effect.die(new Error("expected inet address")) + } + const host = NetAddress.isUnspecified(addr.address) ? "127.0.0.1" : NetAddress.formatUrlHost(addr.address) const url = `http://${host}:${addr.port}` return ApiClientFactory .layer({ url, headers: Option.none() }) From aac629e5f71d59fbadc329ebbe540499168cae7e Mon Sep 17 00:00:00 2001 From: "omegent-app[bot]" <306514130+omegent-app[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:16:41 +0000 Subject: [PATCH 3/8] Squashed 'repos/effect/' changes from 011ce634f8..addeaea0b0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit addeaea0b0 dispatch websocket events directly (#8127) b43bfd644a Align CLI constructor names with Schema and Config (#8121) e7a4e0cb98 Remove changesets for private tool packages (#8122) 7105c732f3 Revert "Version Packages (rc) (#7446)" (#8124) 26e0085d09 Report MCP toolkit failures before recovery (#7520) 54adcab052 Version Packages (rc) (#7446) 3715d9b015 chore(docs): Made all code examples in MCP.md typecheck successfully (#8079) 0a08ae0626 Add shared network address values (#7524) 6d00dcca64 Update migration guidance for recent public API changes (#8118) 5a80204398 Fix arbitrary generation and shrinking for index signatures (#8095) 291d616282 fix(cli): split key=value pairs at the first separator (#8086) d5c7cd2c47 fix(logger): Removed unused stderr option from consolePretty signature (#8074) c5eca6539f Fix file body content-length precision and range errors (#8115) 4907e9b247 Skip optional stack capture when stackTraceLimit is zero (#8114) 87654c5eed Fix inconsistency between CookiesError name and tag (#8064) 4ab4e838a4 Fix filesystem and HTTP file range precision loss (#8110) 66e2becda4 fix(platform-bun): preserve readable stream failures (#8036) a6ceb98e89 Fix invalid File.readAlloc sizes on Node and Deno (#8112) 39b9738727 Fix tool result serialization to respect failure status (#8106) c85fc0b8f2 Reject file seeks before the start of the file (#8108) f7f1d78e52 Clamp HttpPlatform file responses to the available bytes (#8111) fc9fedf59f Fix oversized byte ranges in HttpStaticServer (#8109) e9915d5d7a Upgrade Vitest integrations to version 5 (#8104) 93f32c93ca Migrate filesystem byte counts to ByteSize (#7552) c80faab766 Refactor ByteSize exports to use explicit type annotations (#8107) 72cfa24209 feat(logger): Exposed platform specific console loggers separately (#8083) cda12840a4 Fix JSDoc section formatting in multipart and process APIs (#8105) ca6f0dc4fc Expose final URL on HttpClientResponse (#7496) 3d203b702e add Effectable.Mixin (#7907) 44f44cae65 Fix token bucket refill timing and fractional costs (#8103) 9f37e58d00 fix(cli): preserve prompt frames while rendering (#8097) 98592a2373 Fix strictJsonSchema leaking into OpenRouter requests (#8102) 8f3ea0d005 Fix Anthropic non-native structured output generation (#8100) ad8e9ad7af Fix strictJsonSchema leaking into Anthropic Messages requests (#8098) cce221ebf4 Update compatible workspace dependencies (#8096) db995df19b Separate template literal validation from codec parsing (#8094) 2a30248b6e Reduce the basic Effect bundle size (#8090) ef165814ea Control SQL span propagation with a context reference (#7493) 77f85fe161 use new instantiation for streams (#8087) 7e24e3a5ef fix(atom-solid): forward default idle TTL (#7533) e7eebf4685 fix(docgen): make module page paths relative to the source root (#8051) 1747d842c8 fix(docgen): keep colliding module examples in distinct files (#8043) 8efa56986d fix(doctest): execute TypeScript snippets from Markdown and MDX (#8049) 9f75b5ea51 fix(api-diff): preserve migration example fence framing (#8047) d681c2eb1b fix(cli): reset date input buffer on Tab navigation (#8057) 32f9c60189 fix(docgen): validate parsed class property examples (#8041) 9bbe1a5940 fix(cli): preserve option-looking flag values in wizard mode (#8059) f9235832c4 fix(api-diff): preserve CRLF import-map replacement guidance (#8045) ad07200712 Fix transformClient for generated streaming requests (#8081) 79ae49f19c fix(AtomRpc): match RPC requirements in query return type (#8067) fc668b61e9 Expose SSE decode options per HttpApiClient call (#7954) 2f106a5245 fix(platform): propagate upstream NodeStream duplex failures (#8071) 6550a074c5 Port HttpApiBuilder.handler from v3 (#8078) 74dd9b3be8 fix(Schema): return compose from encodeTo without transformation, closes #8073 (#8077) e2ae72481e fix(Graph): respect impassable edges during cycle propagation (#7920) a29b8f4de5 fix(Effect): type effectify mapper caller inputs (#8009) 0847c41e24 fix(Effect): restore scoped NaN log annotations (#8026) d6422f4104 fix(Effect): retain union-record errors and services (#8014) 0d98213464 fix(Types): retain required named keys on indexed records (#8024) c020987a06 fix(Effect): skip restoration of expired scoped services (#8005) ee336d80b4 fix(Effect): retain direct constructor error types (#8016) 14526354f3 fix(Effect): install cleanup before invoking use callbacks (#8020) 8426e5f7ad fix(Effect): type repeatOrElse fallback metadata correctly (#8022) b28ab48be7 Fix durable suspension for child workflow fan-out inside activities (#8070) 50ef80e251 fix(Effect): constrain mapped track source errors to the mapper domain (#8030) 1c2afc15bb fix(Effect): complete source cleanup before timeout fallback (#8032) abe95d1234 fix(Effect): preserve causes for unmatched reasons (#8007) e255f0893e fix(ai-openai): tolerate missing response sequence numbers (#8037) bd393d63c1 fix(Effect): return an Effect type from withErrorReporting (#8028) 6f6bd84fe0 fix(platform-bun): preserve routes across handler reloads (#8035) dd6ce2d6e3 Defer Undici loading in platform-node (#7528) 7b2c5bd3c3 fix(Effect): preserve errors in saved tapDefect operators (#8012) 4950a914a6 fix(Effect): forward original arguments to eager transforms (#8001) 408eaa3b69 Disable npm audit during pnpm setup (#8033) ba2fd822e9 Wait for the process group in NodeChildProcessSpawner release and kill (#8018) b2cc832e8e Tighten SocketRunner readiness checks (#7999) d1ceffab6b Stabilize SocketRunner test synchronization (#7997) 96f99b325d Fix HTTP Router prefix order when nested (#7913) 414dc9056a fix(AtomRpc): preserve client middleware error types (#7963) d12f922853 fix(Tuple): retain optional evolve transform results (#7924) fe4fed1b01 fix(AtomHttpApi): expose generated client stream success types (#7965) be6b6af60a fix(bundle): decode module URLs before discovering fixtures (#7971) 8cf12030e8 fix(Context): infer services for saved curried getters (#7933) 2c63f1e07e fix(RequestResolver): consume tagged iterable results (#7981) 1579d6fd06 fix(Metric): scope cached hooks to the selected registry (#7991) bea7d4c3a8 fix(ai-codegen): recognize whitespace-leading inline patches (#7894) a3ebb7fcb2 fix(EventLogRemote): retry wrapped authentication Forbidden errors (#7862) 1e6e206f63 fix(HttpRunner): preserve configured leading path separators (#7898) 4b7e6c74d2 fix(sql-sqlite-wasm): retain statement-specific result columns (#7953) 53e6c7311b fix(Metric): canonicalize attribute order in series keys (#7993) 05b1e80cde fix(reactivity): honor explicit zero query TTL values (#7961) 027ceb91e0 fix(Effectable): evaluate Class with asEffect (#7915) 50640f9ec5 fix(api-diff): preserve literal type categories in snapshots (#7850) 4a59c6a559 fix(Multipart): add isStreamPart guard (#7987) a71140fbe7 fix(HttpClient): constrain catch recovery to HTTP responses (#7945) 7738b48e48 Stabilize two-step workflow branch test (#7995) d425c8c131 Skip unencodable atoms during dehydration (#7994) 248201f784 fix(Layer): forward span stack-capture options (#7983) f1b291027c fix(RateLimiter): report exact fixed-window reset lifetime (#7985) d39041ea2b fix(oxc): escape rewritten import specifiers (#7975) b17bcf6f55 fix(bundle): propagate requested output write failures (#7969) 3e2a8adf67 fix(oxc): preserve bigint precision in literal fixes (#7973) bc582c9431 fix(RequestResolver): preserve tagged handler failure causes (#7979) 84ad49a357 fix(FiberHandle): keep same-fiber registrations idempotent (#7967) 4ffcaf4c4a fix(ChildProcess): preserve astral escapes in templates (#7989) d150a643c4 fix(AtomHttpApi): dispatch top-level group endpoints (#7959) 8766475023 fix(DurableDeferred): require encoding services when recording exits (#7941) 22cd7fa32a fix(sql-d1): preserve native raw result envelopes (#7882) 7bb878176b fix(Sharding): preserve explicit registration service overrides (#7906) 832c1d8e21 fix(platform-deno): decode Redis URL authority credentials once (#7833) 5fe42ce443 fix(sql-sqlite-bun): allow database export within transactions (#7831) b4d81832be fix(sql-libsql): isolate transaction contexts per client (#7829) d8ff9605bb fix(DurableClock): preserve explicit zero in-memory thresholds (#7840) 91e9af0d5f fix(SqlMessageStorage): preserve reply IDs in by-ID reads (#7837) 473bd814a9 fix(ChunkedMessage): return a chunk for empty framing data (#7842) e80d3972fc fix(LayerMap): retain errors for preloaded service reacquisition (#7937) f4981cf9a5 fix(sql-sqlite-wasm): start native message ports (#7951) 862ba71de0 fix(platform-deno): apply writeFile modes only during creation (#7943) 88593e4abc fix(sql-sqlite-wasm): retain native worker error codes (#7947) 8f397ed4e7 fix(Reply): align codec service requirements with their direction (#7939) ce120f474b fix(Layer): require error taps to accept the source error (#7935) 53eb88875d fix(platform-browser): invoke custom runtime teardown (#7866) f1a941d52c fix(Logger): complete file log writes after partial progress (#7874) f984ee82d2 fix(Random, Crypto): prevent rounding to exclusive upper bounds (#7864) 436f5eb44a fix(ConfigProvider): substitute dotenv references as literal data (#7884) 1df933d1de fix(HashRing): include the final exclusion search radius (#7886) 6b8128e0bd fix(platform-browser): recognize IndexedDB keys containing bytes (#7868) e11be41dfc fix(SqlModel): retain insert result decoding services (#7904) 0f0a614dcb Stabilize shard release active-teardown test (#7957) df3fc47609 fix(SqlRunnerStorage): bound PostgreSQL acquisition results to requested shards (#7860) a8ea80799a fix(Entity): forward test-client fatal-defect options (#7889) ce4aa65409 fix(EntityProxyServer): retain client codec service requirements (#7902) 07ffd25bd0 fix(Number): preserve negative-zero remainder dividends (#7910) d51aa36a73 move a couple more jobs back to github d473bd3ee9 fix(Formatter): preserve defined falsy Error causes (#7872) 0276a27ddb fix(HttpClient): keep preprocessing inside redirect recovery wrappers (#7922) f43b9d6d99 fix(Tokenizer): measure complete candidate prompts when truncating (#7912) 3853b112d4 fix(api-diff): retain optional class members in snapshots (#7852) 37eca35cea fix(jsdocs): refresh input caches for each extraction (#7848) f8ab8e0603 fix(utils): preserve absolute codegen discovery matches (#7854) e8912473bd fix(MessageStorage): clear queued control envelopes with their address (#7835) 5b03c4861a Update v3-to-v4 guidance for recent API changes (#7955) 4eb0fa7230 Reduce HTTP server overhead in response headers, routing, HttpApi errors, and Effect.cached (#7956) fa6027b16e feat(http): reduce web handler cold start on Cloudflare (#7927) a9d1ee3d4d Fix disabled OTLP batch flushing (#7931) 42fd969b45 fix(Scheduler): fall back to a microtask when timers cannot be set (#7930) a31adbefd6 Optimize OtlpTracer span creation and export (#7929) d7c66d0349 ci: run Static and Build jobs on GitHub-hosted runners (#7928) 7e74c58bed Fix flaky browser RPC stream test (#7900) 2ec12ad1cd Configure test matrix (#7899) 93422fc5a7 Merge Deno and Node type checks (#7892) af0ccdd13a fix(TestSchema): compare every own struct field by AST (#7870) 2f474730d1 Right-size Namespace CI runners (#7880) a2c1ce62f0 fix(SqlEventJournal): preserve callback error identity (#7844) 9cc3e6218a Stabilize durable clock routing test (#7858) 4372c79a32 fix(effect): centralize JavaScript array index validation (#7855) a62f4838f0 Use GitHub runners for releases (#7856) 2122e07b86 Configure namespace.so runners (#7845) 180b635aea fix(Schema): centralize JSON Pointer URI fragment handling (#7823) 500c60b3ab Remove ignored NodeStream bufferSize option (#7838) 56e72b38c0 fix(effect): encode tool results using their known result branch (#7748) d84f865a48 fix(platform-browser): return typed errors when subtle crypto is unavailable (#7808) 9252a00ba3 fix(platform-node): preserve unsafe worker reply payloads (#7810) 73bc3a13b6 fix(McpServer): resolve HTTP resource templates without losing origins (#7814) 072005e64b fix(IndexedDb): preserve out-of-line primary keys in query results (#7818) 99f57af401 fix(IndexedDb): keep select streams within their query limits (#7820) b628bb1240 fix(McpServer): type prompt callbacks with decoded parameters (#7816) 8364ddda23 fix(Socket): resume paused WebSockets after reader handoff (#7825) ad67d8c5a1 fix(Socket): count UTF-8 text bytes toward WebSocket watermarks (#7827) a3f2b31934 fix(cli): retain subcommand alias completion contexts (#7802) ff0f00627e fix(openapi-generator): convert multipart record payloads (#7770) b845b18637 Add defect recovery operators to Stream and Channel (#7822) ec0c087526 fix(Channel): emit CR-terminated lines before pulling more input (#7798) f74282cb08 fix(MutableList): preserve bulk-prepended values when appending (#7776) 8479f4e91f fix(ai-anthropic): preserve base64 image string payloads (#7758) ab07e66144 fix(opentelemetry): use collection interval starts for delta metrics (#7762) d6f9eba7e8 fix(ExecutionPlan): capture while predicate requirements (#7784) 412bc408a1 fix(sql-sqlite-do): classify streaming storage failures as SqlError (#7732) b64f406de1 fix(effect): clear stale dynamic tool schemas when replacing parameters (#7750) 1e92dbd26f Improve HTTP server and HttpRouter request throughput (#7772) 72b85820f1 fix(NodeHttpClient): parse Undici response forms from cached bytes (#7718) 3a0828b058 fix(PersistedCache): persist synchronous lookup defects (#7766) 615c41e193 fix(NodeSink): invalidate drain callbacks on interruption (#7714) f01b8ad919 fix(docgen): preserve alias names and declared type parameters (#7752) 0beaf69e3c fix(NodeChildProcessSpawner): expose the first pipeline stage stdin (#7728) 474860b462 fix(atom-react): preserve notifications after switching refs (#7744) fe606666a7 fix(sql-mssql): adapt byte arrays for automatic VarBinary binding (#7726) 84d2a470ec fix(effect): tolerate repeated reactivity keys during query cleanup (#7742) 3f51acdf5e fix(effect): forward fallback atom writes to the primary atom (#7740) 49e39010ff fix(effect): retain completed non-streaming tool approval results (#7738) 736c882a45 fix(BrowserHttpClient): support readers in ArrayBuffer mode (#7736) e406217f8c fix(NodeHttpServer): forward custom and empty status text (#7730) fe3830add4 fix(sql-pg): honor explicit SSL overrides for URL modes (#7734) c8072b6c4a fix(openapi-generator): encode declared form-urlencoded requests (#7768) b505c0db03 fix(opentelemetry): preserve negative counter deltas in both exporters (#7760) 95c2581e0f fix(FileSystem): retain the sink write default for undefined flags (#7788) ccc2e02def fix(Optic): splice canonical string indices in optionalKey (#7780) e42516a9c5 fix(sql-mssql): forward the configured NTLM domain (#7724) fc3b718711 fix(Trie): preserve valued nodes during removal (#7774) 53511efcd1 fix(Schema): preserve ArrayEnsure element branches and encoding cardinality (#7796) 604b1c109d fix(Optic): preserve deletions through pick and omit (#7778) 14d810a726 fix(Order): retain finite criteria across comparisons (#7782) d8776058ba fix(platform-bun): report Unix addresses for Unix socket servers (#7800) 310f8d3424 fix(cli): include inherited shared flags in descendant completions (#7804) 4daa260526 fix(sql-sqlite-node): capture unprepared statement preparation errors (#7722) d68ff05506 fix(HttpMiddleware): preserve existing CORS Vary dimensions (#7693) 163cebdc19 fix(NodeFileSystem): classify watch events relative to their target (#7710) 2ce57b35a6 Isolate MySQL single-table persistence tests (#7812) 3b6d3a4105 test: serialize MySQL integration suites on Node (#7811) 668082885f fix(SynchronizedRef): correct callback-only modifySomeEffect currying (#7790) 2b3a9a3fbb fix(sql-pglite): preserve string values passed to sql.json (#7720) f93d051c33 fix(platform-node): preserve incoming bytes when selecting text readers (#7716) 48dbbb22bb fix(cli): allow optional wrappers around alternative flags (#7687) f7490d49c7 Add manual Queue taker flushing APIs (#7806) 45ffa72212 fix(HttpApiTest): run registered pre-response handlers (#7705) 37cdd692ad fix(NodeFileSystem): accept empty writeAll buffers (#7708) d0a6c5eee0 fix(NodeSink): capture writable finalization errors (#7712) d8cc9ed80e fix(RpcSerialization): retain falsy control IDs when decoding JSON-RPC (#7683) 7d455f51ac fix(OpenApi): populate schemas before endpoint transforms (#7703) 62326505a7 fix(HttpApiClient): preserve base paths in URL builders (#7701) 47b358a4b8 fix(HttpApiClient): decode form-urlencoded responses through UrlParams (#7699) 7750dbe0d5 fix(HttpApiBuilder): honor status annotations on stream wrappers (#7697) 6525771eef fix(HttpApiSchema): clear stale metadata in encodeToWithHeaders (#7695) d386979fed fix(HttpStaticServer): ignore Range on non-GET requests (#7691) 2a3a4783bc fix(HttpRouter): normalize prefix-removal metadata (#7689) 975f7586f3 fix(HttpServerResponse): preserve Web response content lengths (#7681) 4b73e1bd55 fix(Schema): preserve percent-encoded JSON Schema references (#7661) 0b93584808 fix(ai): preserve string tool results (#7805) c86c99939e fix(HttpEffect): close request scopes for streaming HEAD responses (#7679) 1c89c78f9f fix(cli): classify absent required variadic arguments as missing (#7685) 74ff5c3138 fix(atom-vue): publish the selected ref's current value (#7746) d902f1fd92 fix(doctest): preserve statement boundaries after generated assertions (#7754) 62945c1baf fix(ai-openrouter): retain normalized tool-call finish reasons (#7764) 685275058a fix(ai-openai): preserve image strings in OpenAI and compatible requests (#7756) d14c4631ac fix(Yaml): preserve folded scalar paragraph and indentation breaks (#7669) 381b79464f refactor(Channel): remove runDone (#7657) d3c6b73cc4 fix(Model): preserve omitted variants in FieldOption (#7665) 2e39e8bafe fix(Stream): preserve large source chunks when rechunking (#7655) fc91af659d fix(Toml): allow child tables in distinct array entries (#7667) 9960708052 fix(FetchHttpClient): set duplex for raw Web stream bodies (#7677) 284050c2aa fix(Mime): normalize parameters in all-extension lookups (#7675) ed74b18b95 fix(Sink): preserve unconsumed leftovers in flatMap (#7659) d592c1492a fix(SchemaBinary): preserve leading U+FEFF in strings (#7673) 2245997ad2 fix(Sse): preserve events with mixed line endings (#7671) 6f090d4c20 fix(VariantSchema): preserve classes in default extraction (#7663) 243c72f001 Fix SchemaAST declaration parser contract (#7651) 5c7eed0c5a Reduce HTTP server allocations per request (#7650) 183c2ea65e Reduce RPC server allocations per request (#7649) a1177caf27 Refactor ByteSize as branded bigint (#7648) 9956f0e6f4 Reduce allocations in Effect primitives and fibers (#7643) 7616f73c16 fix(httpapi): transform middleware-declared errors like endpoint errors (#7561) a2c9e7c17a Simplify Graph protocol and runtime guard (#7644) 464d0b3e52 fix(platform-bun): load Bun's RedisClient lazily so the barrel imports under Node (#7564) de7774f359 test(sql-mysql2): reuse Model test container (#7642) f9d0dec12c fix(UrlParams): preserve immutable setAll overrides (#7631) 629870d461 fix(SchemaGetter): preserve duplicate array-valued leaves (#7641) 02be94ce78 fix(Chunk): preserve sliced elements during concatenation (#7617) 8efc70ea16 fix(Struct): honor numeric property selections (#7619) aa0aba3833 fix(Headers): match stateful redaction patterns independently (#7627) 4446451011 fix(Prompt): preserve text parts during serialization (#7637) 58be97238e fix(Prompt): retain JSON-restorable response attachments (#7639) 7bd3f34385 fix(sql): renumber cached returning fragments (#7635) f921ed305c fix(HashMap): isolate extracted collision entries (#7625) f05ae0b3fd fix(DateTime): apply calendar parts without intermediate overflow (#7621) 829aff97d7 fix(Headers): compare redacted names case-insensitively (#7629) 59812fd221 fix(UrlParams): coerce scalar null inputs (#7623) 1693a87732 fix(sql): compile identifiers in returning helpers (#7633) 0856631a89 fix(RequestResolver): preserve persisted batch failure results (#7613) 78cc9c0d0f fix(Cache): avoid caching synchronous interruptions (#7614) 06c6307d3d fix(ScopedCache): capture refresh lookup callback defects (#7612) 0af0985d5c fix(RequestResolver): preserve cached race winners (#7594) 8ac53b6ae6 fix(FiberMap): retain reentrant replacement ownership (#7615) 5f8ad105c0 fix(PubSub): preserve empty sentinel values in batched polling (#7607) cd83544dd5 fix(Pool): preserve reservations during borrower release (#7604) cec6c2d7be unstable/ai: route tool param validation through failureMode and drop ToolParameterValidationError.toolParams (#7588) 1320075fd4 fix(PubSub): advance sliding subscribers past dropped messages (#7603) bb99734ef8 fix(RcRef): reject acquisitions completed after owner shutdown (#7598) 78a42697f6 fix(ScopedCache): detach invalidated entries before finalization (#7606) 1aa1d8bd0f fix(RcMap): handle synchronous lookup defects (#7605) f17eb0a6f6 fix(Cache): enforce capacity when publishing refreshed entries (#7596) f30cbfe539 fix(Cache): preserve replacement values after zero-TTL refresh (#7595) 222e7caa4b fix(RcRef): preserve replacement resources during borrower cleanup (#7586) debe8fdc15 fix(Cache): preserve replacements during conditional invalidation (#7587) 1a86166f10 fix(RequestResolver): evict cancelled pending cache entries (#7583) 7245f87ace fix(PartitionedSemaphore): preserve new waiters during stale cleanup (#7584) a8588f99a4 fix(Cache): preserve newer entries when refresh is interrupted (#7585) 46d83101e8 Move unstable HTTP schemas to Schema (#7553) c8ea60231a fix(Queue): prevent duplicate messages from resumed producers (#7576) d812596a10 Fix CLI formatter NO_COLOR handling (#7573) 4697aaa1d3 Fix CLI help alignment for Unicode graphemes (#7572) 0a3862349e Expose named random service types (#7571) acc1e5384a Normalize core service key identities (#7568) 797c9e3c1f fix(effect): type Cause.Reason#annotate as Context-only (#7565) 0c95c04b41 Fix Worker.run hanging when a worker dies before the ready handshake (#7570) c34edcb6d7 Remove invalid SynchronizedRef Ref inheritance (#7569) ccbdbd51e7 Fix custom HTTP server span header redaction (#7563) 9642776614 Hide SchemaAST and SchemaIssue class implementations (#7560) 5641ad333a Refactor Schema internals and representation APIs (#7558) 81485ef028 fix(cluster): run the empty shard-lock liveness probe on the shared pool (#7551) 9b517ad28a PersistedQueue hardening and optimization pass (#7547) 11c5ee7202 Fix Content-Length parsing (#7538) 40466bf397 Fix JSDoc validation violations (#7549) 3623e3550b Preserve omitted additionalProperties in OpenAPI schemas (#7546) a267ff08fe Use Effect YAML parser in OpenAPI generator (#7545) 06e0c5ad23 Update v3-to-v4 Socket and typeclass guidance (#7541) 53843f6490 Add exact ByteSize value module (#7525) a29e05a907 fix(RcMap): preserve replacement entries during invalidated resource cleanup (#7516) b76a1cf32b Add in-place TLS socket upgrade (#7514) 75e80bfd26 Fix Atom idleTTL cleanup for stale derived nodes (#7535) c1693ffda8 Update repository dependencies (#7534) 1742d2f484 Fix Set-Cookie name validation (#7522) 145d8e1013 Fix Schema.mutable with encoded arrays, closes #7517 (#7519) 6e3ae7b635 fix(ai): keep non-object tool results out of structuredContent (#7495) 5664c941b5 Add NodeSocketServer.makeTls (#7512) a181b8be07 Close pending Node sockets immediately (#7513) a4bb2aa226 Add NodeSocket.makeTls (#7511) d60c5d40b5 Normalize collection count handling (#7510) c405edc991 Set sql-pg multiplex concurrency default to 32 (#7509) 09a3c9667b Fix SchemaBinary strict encoding and field collisions (#7508) 9ad2a43d71 Fix SchemaBinary decoder limits (#7507) 20bd53d4a9 Fix SchemaBinary dictionary and number array handling (#7506) 9adb9f2c82 Reject overlong PostgreSQL channel names (#7504) 96ea948655 Fix zero-TTL PostgreSQL pool churn (#7503) e203638f6e Allow configuring PostgreSQL backend message size (#7502) 1d0ff4ff67 Reject head-side IPv4 tails in the PostgreSQL inet codec (#7501) 4d288b18e0 Update v3-to-v4 migration docs for MessagePack and Arbitrary removals (#7499) ba53b646e9 Pull-based Socket API with backpressure (#7487) 3787c46b19 Fix React Native SQLite value queries (#7469) 62d82f4919 Decode ArrayBuffer BLOBs in SqlEventJournal (#7498) 97dd022fe7 Treat NaN as a non-positive count in Stream.take (#7497) f356bc2da6 Recommend Effect.fnUntraced in AI guide (#7492) 078cdbf6a3 Refine repository skill guidance (#7491) dd99ab007e fix(cluster): bound teardown interrupt classification (#7489) e72b12fc30 Execute streaming tool handlers eagerly with interruption and synthesized results (#7486) d7ae6b6491 fix(cluster): never surface transient routing states for persisted messages (#7485) be0f8221e3 Support WebSocket clients across runtimes (#7477) 561296283c Fix readonly SQLite transactions (#7484) 310dd9ce96 Restore Effect.timeout error message (#7481) b945ded23a Align type IDs with module paths (#7483) 186dd49140 Normalize collection count handling (#7478) d74dc67469 Optimize jsdocs skill guidance (#7479) 8f5290a807 Add maintenance skills (#7476) dc0645bec5 Ignore file cleanup (#7475) d4ebda795f Remove unused Docker Compose configuration (#7474) 6a2a9174a0 Ignore generated bundle stats (#7473) a90ef9da69 Remove Temporal polyfill from scratchpad (#7472) 5adc4699d1 Streamline repository instructions (#7471) 0613ecd593 Expand Arbitrary generation and shrinking (#7466) 0d083ba26b Remove the MIME dependency from @effect/platform-node (#7463) 9ea0c511fe Stabilize SQL runner blackhole recovery test (#7462) 8d1e97adbf Fix tagsExhaustive Effect.fn handler inference (#7460) b4d5398598 Remove MessagePack support (#7461) 1a2ccee2bb Default TCP cluster serialization to SchemaBinary (#7427) 770403411b Preserve response part assignability after narrowing (#7448) 505dc05dd6 fix(platform-node): prevent writing the HTTP response onto an upgraded WebSocket connection (#7459) 2cf7564166 Fix chunked multipart parsing after file parts (#7458) 8fb2d0b3a4 Align PGlite listen with PostgreSQL (#7456) 115d8c2259 refactor(effect): standardize Config constructor naming (#7453) a63dcbf04e Native Arbitraries (#7254) 534b8b9dba Replace pg with a native PgProtocol client (#7426) 84864bc30c Fix Schema class equivalence derivation, closes #7450 (#7451) fa6a56b862 Terminate `Stream.fromEventListener` after one item if `once: true` (#7443) 3c81fbe5c6 Stabilize SQL runner release recovery test (#7444) 30b9b33b21 Stabilize Deno SocketRunner isolation test (#7445) 2600f62f45 Version Packages (rc) (#7381) 118124d913 Redact values in CLI wizard command output (#7442) c7e2723c18 Fix CLI help spacing for long names (#7441) 7d8535af82 Handle cleared sockets in Node HTTP remoteAddress (#7440) 0fb12d5c4d Extract shared AI provider error description formatter (#7439) 4148e21eb5 Relax atom-react React peer dependency (#7435) 44675cbce3 Surface the provider's explanation on AuthenticationError (#7437) 925b82a81f Fix MCP initialize rejected over the protocol version header (#7388) 5c4b7a0b17 Return workflow execution IDs from proxy discard endpoints (#7428) e1fb57ffbf Fix Bun per-message websocket compression (#7408) b3f268db22 Update cluster migration guidance for RPC codecs (#7438) 20cb4f260e Add optional acquisition APIs to RcMap and LayerMap (#7411) 811d579c43 Fix JSON Schema type applicability (#7436) 0b9f780ff2 Fix EventLog remote write retries (#7401) 145c3b5d09 Fix IndexedDB equals typing without key paths (#7433) d9d2cfcb73 Reject unsupported JSON Schema import keywords, closes #7410 (#7429) 0dd7825e4d Add schema-derived binary codec (#7366) 02a5146d69 Skip empty remote journal write callbacks (#7424) 1f686d99a7 Update repository dependencies (#7421) f77ec19cff Defer OpenAPI documentation generation until first request (#7417) 480fb15659 Generalize JSON Schema dialect converters (#7420) 7455246f35 `ai/openai`: Minimal patch to add cache breakpoint support for openai-gpt-5.6+ prompts (#7403) 4d89bb8ffb Fix JSON Schema reference resolution, closes #7409 (#7415) eb9bd86404 chore: upgrade TSTyche (#7414) 1144032ced Add a Postgres wire protocol codec to sql-pg (#7391) 058fb15647 Fix groupBy key inference (#7406) c76524e1d9 Await Node worker termination (#7405) b722eca6d2 Add vendored Standard Schema module (#7404) be75d5ea6e Improve Pool performance (#7402) 993f4be999 Add Pool operation benchmarks (#7399) 436f10d1ef Fix Prompt.file swallowing j and k while filtering b6bf5e1449 Fix `Prompt.autoComplete` swallowing `j` and `k` while filtering (#7393) a5f78d3fcb Implement schema-aware RPC serialization (#7387) d57bba1486 Skip stack capture for SchemaError (#7389) 15272a66ad Fix completion of choice values containing quotes, colons or spaces (#7312) 6740db247e Add partial matching to Schema.TaggedUnion (#7386) 150e92c416 Optimize synchronous Schema parsing (#7384) 043b587e6e Add context-based CLI prompt themes (#7382) 85837274fa fix(effect): drop unreachable concurrency guard in iteratorEagerImpl (#7373) f1f8c9a3ae Revert "Add pg pipeline configuration" (#7383) c04c7f8b7a Add pg pipeline configuration (#7380) 1683d1f88f Document Match and RPC migration changes (#7378) 648f566dd2 Version Packages (rc) (#7297) e03ea90772 fix(Deferred): clear resumes before resuming waiters on completion (#7364) a47cbf1fea Add Match.fn selector matchers (#7363) 39b55f850a Preserve encoded AI tool parameters for manual resolution (#7362) 7f870225bb Merge resource usage and finalizer failures (#7361) d84633171b Preserve Context accessor with loose object spread (#7352) ffc823537a Add RPC HTTP stream backpressure (#7358) d5689682e9 Add configurable schema reference policies, closes #7357 (#7360) 4f6ae041a2 Add standalone dual Optic functions, closes #7299 (#7359) b44636ff72 Schema: avoid projecting checks and annotations through artificial JSON encodings, closes #7192 (#7349) b3913895c1 Harden SqlRunnerStorage shard-lock integration test (#7355) 0be2303dd3 Make server notifications first-class RPC messages (#7354) 92922ee7de Preserve unsafe workflow interrupts across replay (#7351) 1e83ca110d Align in-memory workflow interrupt finalization (#7350) b6b63e1515 fix(openapi): preserve JSON.rawJSON in cached specs (#7250) 5f37f351b0 Add Redis subscribe API (#7348) 9b10fc85dd Fix ordered channel effects queue finalizer (#7347) 7425bcb7f7 Fix fiber observer cancellation during exit (#7344) bf23ba7033 fix(rpc): forward every worker-runner disconnect to the server (#7342) 5f962c8003 Pin fixed next release action (#7339) b19ccc7325 feat(schema): add JsonObject schema (#7337) 44d66f8d7c test: cover BigInt comparison, ordering, and clamping functions (#7333) 06f73fe9ae test: cover Ordering.reverse and Ordering.match (#7334) 16bf1efdf4 Compact JSON Schema checks (#7336) 770c6d0f0d Fix Effect.fn self transform binding (#7335) ff98f0b0e2 Fix JSON Schema object scope intersections (#7330) cbe1850420 Deduplicate OpenAPI JSON Pointer decoding (#7331) 859c02fe90 Fix Graph runtime guards and bulk removal caching (#7328) 581e184a93 Fix escaped OpenAPI reference tokens (#7323) 249a302d6d Drop invalid OpenAPI schema examples (#7325) 425457c1e9 Fix open object type generation (#7326) 008c423e41 Allow path-level parameters in OpenAPI generator inputs (#7324) 16899dd655 Optimize core Graph operations (#7327) 3d895bc6b7 Fix OpenAPI HttpClient response matching (#7321) aac8584fd9 Resolve Match terminal combinators for Match.value on generic inputs (#7317) 550a41a254 Update dependencies and resolve compatibility issues (#7316) ae82ebb3e2 test: cover Tuple.appendElement, appendElements, makeEquivalence, makeOrder (#7307) 865c42d724 Additional graph performance improvements (#7314) 0ce3b00753 Guard graph traversal callbacks from mutation (#7311) 6140ebd358 Refactor graph tests (#7310) 66cec47101 Remove graph library audit scope docs (#7309) 196ddab008 Improve Graph API documentation (#7308) 45d79c7cbd Guard graph transformations and add bulk removals (#7306) c6c49c9699 Fix cache edge cases (#7305) bc062920ac Add connectivity, matching, and flow algorithms to Graph (#7304) 7157bbc4fd Unify graph algorithms on CSR traversal (#7302) 563815a250 Fix bounded Graph DFS ordering and A* validation (#7301) f93616f31f Fix graph index exhaustion and other minor issues (#7300) 41af92d95b Update Graph, CLI, and MCP migration guidance (#7298) b7559505c8 fix(ai-openai): decode nested Responses API error stream events (#7262) 66114151c2 Version Packages (rc) (#7245) baa99fc176 Make CLI boolean flags required when omitted (#7296) 7e3f07cf3e fix(cli): generate Zsh completions for arguments and subcommands (#7199) aaad961400 Optimize Graph queries and traversals (#7295) 7e9923b1a0 Add custom revivers to HTTP JSON parsers (#7294) f4fbe9ca02 Support standalone Effect.forEach data-last usage (#7293) b2796f35fd Fix BrowserRuntime page lifecycle handling (#7292) d10ceb06d5 Add edge indexes to graph paths (#7291) 397bf1ebd9 Graph: deduplicate directed neighbor queries (#7276) a894fe1df8 Graph: ignore allocator history in equality (#7274) e491deb1cd Graph: preserve scoped mutation errors (#7272) a82ffc03df Graph: stabilize traversal configuration (#7273) 6233c4c109 Graph: correct hash-law coverage (#7271) a371754b1e Optimize graph traversal (#7263) ee06c9c1ee Fix dropped text for nullable streamed tool calls (#7269) 0a127b8847 Allow customizing CLI prompt prefixes (#7267) f99c508a09 Fix SQLite persisted queue delivery (#7266) 189b003a23 Add `Schema.Graph` and `Graph.fromSnapshot` (#7261) 2b6a6f5dbf Support max reasoning and compatible provider quota errors (#7258) e811353e3b Fix edge mutability and A* heuristics (#7259) 6eebd0a618 feat: add v2025-11-25 protocol adapter (#7234) 01c69399cf Document public random hex contract and tracer regressions (#7252) 9761c3c478 Share tracer hex ID generation (#7251) 4026e2dde3 Improve tracing performance in span creation and HTTP tracer middleware (#7248) 7fd79b2cc9 Restore Effect.head (#7246) f0641210f9 Support refinements in Schedule.while (#7200) b660bf01d9 Normalize unbounded PubSub replay capacities (#7244) 3bf8d52529 Respect zero Node socket open timeouts (#7243) b5946ece2b Version Packages (rc) (#7211) 17892e75a8 Use Context mapUnsafe in less call sites (#7241) 4d8a2306df Fix Effect.fromOption inline inference (#7240) 2ae5a96266 Fix zero maxBytes handling in NodeStream (#7233) ccae60e5ed fix(sql): propagate a failed transaction begin as a typed error (#7236) 6dc80329b3 Update MCP and cluster migration guidance (#7238) f4ba735bc4 upgrade pnpm (#7237) 18270ddeaf feat: add mcp core for protocol versions (#6829) d8d3fdcb53 Migrate NodeRedis from ioredis to redis (node-redis) (#7221) 7323e874ad Add entity registration failure coverage (#7231) be175a4a17 Stabilize held-message storage timing test (#7230) 89dd5b87fd Add malformed cluster message coverage (#7228) 12133eae04 Add directional cluster transport failure coverage (#7227) 149067cf9f Add cluster lock storage failure coverage (#7224) 8081362eb7 test(cluster): cover persistence failure races (#7226) 3fba454816 Use handleAll in HttpApi examples (#7225) 2cabcb7247 Add workflow owner-loss integration coverage (#7222) c372b80e4a Add entity shutdown and replay integration coverage (#7223) 7018f96684 Stabilize DurableDeferred race replay test (#7220) a0743f2b9f Add SQL, HttpApi testing, and CLI schema coverage to ai-docs (#7219) 26db404a32 Use migrations for SQL persisted queues (#7218) f21f9c9f73 Add HttpStatus module with fromLiteral and centralize status mappings (#7216) 6ff5396813 feat(cluster): bound runner entity residency and storage reads (#7206) 5f1f828521 Fix flaky workflow suspension race test (#7215) ce39159f14 Add Inspectable.format migration guidance (#7214) 03031395d3 Add the Claude 5 family to the Anthropic model capability table (#7208) b57aaa5119 Rename Live layer conventions (#7212) 3702bedd8f Remove kubernetes-types dependency (#7205) d848411826 docs: consume streaming requests with .handleRaw in HTTPAPI.md (#7209) 2670398261 Preserve TestClock nanosecond precision (#7210) 18d15576d5 docs(HTTPAPI): update streaming responses section to use stream schemas (#7207) 85c9694698 Remove the uuid dependency (#7204) d240dd3868 Update README installation guidance for V4 RC (#7197) b284ec72c5 docs: remove obsolete TaggedError migration mapping, closes #7201 (#7202) bef7bf38ae Version Packages (rc) (#7167) 06b2aba8d7 Bump prerelease tag to rc (#7198) da76e58b03 Improve package READMEs (#7196) 31b27e4990 fix(cluster): discard volatile messages eagerly (#7195) 22b579f6c5 fix(Deferred): guard waiter cleanup against post-completion runs (#7193) 3e19539205 Fix DurableDeferred race wake-up (#7179) 84589518c3 Fix HttpRouter middleware handled errors (#7191) 08a3c74133 fix(httpapi): decode singleton array query parameters, closes #7186 (#7189) 97b544d8b6 fix(schema): strip internal sentinel annotation, closes #7187 (#7188) eb0bae08d5 fix(openapi): return fresh cached specs (#6550) accf447451 refactor(schema): move SchemaError into Schema module (#7181) c82c53228d fix(formatter): align diagnostic formatting (#7180) dfb173efff fix(formatter): stringify bigint values in JSON diagnostics (#6546) fad4b7c513 Use Promise microtasks in Scheduler (#7178) 2e1ddbebd9 Fix arbitrary migration annotations (#7175) 005e0902ca Fix Queue.await Done completion (#7174) afa8630592 Remove pullfrog (#7172) e15fa96239 feat: add support for `mdx` in `@effect/doctest` (#7151) c7fa11044c Move platform packages (#7169) 1629cbcea0 Optimize SchemaIssue default formatter bundle size (#7168) 4f6d131e85 Improve Union candidate selection for nested unions and contradicted sentinels (#7158) 3c495ae7c9 Version Packages (beta) (#7150) bc80d146c0 Improve Migrator file URL docs and test 8b91605485 Import migrations through a file URL so Windows paths work in Migrator.fromFileSystem aedb67242c Share MySQL persistence container across suites (#7164) e74c302afe Stop child process spawns and kills from flashing console windows on Windows (#7154) 48f75ee251 Expose SQL runner recovery test failures (#7163) c30386df4b Fix SQLite client locking defaults (#7162) 9611ed42d1 Fix Duration Hash.symbol violating the Hash/Equal contract (#7153) 123df5df56 Restore NodeHttpClient early-close coverage (#7161) 8dba98881e Clarify multipart collection release notes (#7160) d901928efa Make multipart content collection linear (#7157) 65a4efd859 Make Node HTTP client tests deterministic (#7159) 596f3f92d7 Fix multipart file limit hangs (#7156) df431ae722 Render CLI UserError through the output formatter (#7102) b32bdef0d1 Require explicit JSON Schema pattern handling (#7149) fb75264aa7 Version Packages (beta) (#7103) 0bcf6ed57c Fix multipart parser continuing after maxParts (#7144) 9f151906eb Fix ClickHouse KILL QUERY parameterization (#7138) 089313ec2a Fix NodeStream.toString registers duplicate error event listener (#7143) c25b84c09b Fix PgClient makeClient missing error event handler (#7142) 1416ccd474 Simplify Schema arbitrary derivation API (#7148) 548908a71d Improve Schema representation reference identity (#7146) 6c60375e68 Fix cluster shutdown hang from abandoned request (#7134) 5f1775cb06 Keep NodeTerminal readline alive briefly between prompts (#7106) 3f01731a09 Fix openRouter emits cumulative tool-parameter prefixes as deltas (#7132) 3a867573dd Fix unknown MCP cancellation suppresses a later reused request ID (#7131) d6a4a9cef4 Fix openRouter reasoning-end metadata uses an undeclared casing (#7133) deed5fbdc9 Fix batched encrypted event records reuse per-record randomization metadata (#7124) 615d1d5d02 Fix SQL resolvers executing empty batches (#7120) d0baed9d72 Correct max output tokens for Claude Opus 4.6, 4.7, 4.8 and Sonnet 4.6 (#7129) 69756a2290 Fix openRouter streamed citation end offsets overwrite start offsets (#7128) 16b94c7024 Fix openAI telemetry response attributes use the request namespace type (#7126) b9875ea453 Fix closing an older Bun serve scope restores an obsolete handler (#7113) 14278e7d62 Fix node tracer shutdown exceeds shutdownTimeout (#7112) b588640b4f Fix openAI-compatible telemetry response attributes use the request namespace type (#7127) 42c810dd37 Release RPC worker pool entries after worker failure (#7147) 4bca71d491 Fix deno fileWebResponse ignores range options (#7123) 08d0d39a22 Fix rcRef leaks resources acquired before acquisition failure (#7109) ba9cb63b87 Fix defecting execution-plan observers replace outcomes and leave events unpaired (#7121) 7aff81a9ce Fix sQL findById resolvers invoke NonEmpty callbacks with an empty batch (#7119) c2071b1647 Fix txPubSub subscriber release interrupts after hub shutdown (#7117) aea89d0c42 Fix disabled tracer timing still records span end times (#7116) 6310a8c68c Fix buffered worker send failures escape the WorkerError channel (#7114) abf77b0400 Fix fiber.joinAll erases input fiber error types (#7111) f8b8dd3637 Fix scopedRef.set orphans a replacement when old cleanup defects (#7115) c1a13f3157 Fix bun fileWebResponse ignores range options (#7122) ac71ede168 Fix log annotations overwrite active span correlation identifiers (#7118) 22f4897bba Fix client-server-client FormData conversion loses multipart representation (#7107) f4a9762bb9 feat: add idempotent memoization for AST transforms (#7104) 269516851b Fix concurrent first RcRef borrowers acquire different generations (#7110) a1d4057711 Add explicit ConfigProvider environment record constructor (#7105) f617b7f7d3 Remove docgen configuration and output (#7095) d17059615c Fix JSON Schema prefixItems import semantics (#7060) a80d47f17c Preserve provider metadata in Prompt.fromResponseParts (#7100) 5b6febd5f0 Version Packages (beta) (#7081) 31efc5c4ea Update peer dependency ranges (#7094) 8525f05d1e Add Cron.format for cron expression conversion (#7097) f6c6ec966c Ship AI documentation with published packages (#7098) b206fa5d76 Expose terminal state through Stdio (#7090) a7bcc6363f Refresh v3 to v4 migration guidance (#7096) b938c8ad28 Add opt-in schema issue input reporting, closes #7021 (#7093) 4c28b0fb5f Deploy website after package releases (#7091) 04185644da Use tagged error guards across package copies (#7087) c91f4015f8 Fix Deno filesystem error context (#7086) f0be8554da Remove Context service definition stack capture (#7084) 9591f00993 Scope Atom runtime memoization to registries (#7082) d334a8593a Add retry and response header options to HttpClient.withRateLimiter (#6827) c1ed0ac97c Version Packages (beta) (#6928) c2c966d70b Honor writeFile flags in Deno (#7080) 64dc7c76dc Fix failed ResourceRef rebuilds permanently block waiters (#7039) 3437e21a56 Fix memory journal skips the first conflict candidate (#7033) 41a550d1fe Fix memory journal returns the last remote sequence instead of the next (#7034) 7bdb470711 Wait for MySQL test container readiness (#7079) 5f3fb814d1 Fix runner streams do not terminate after terminal replies (#7041) 931f573749 Update scoped test guidance for v4 (#7078) 8f63cce636 Fix failed OTLP exports consume delta checkpoints (#7037) abc73555ea Fix tracer provider shutdown after flush failure (#7064) 39f1297acc Fix tuple.make loses element literal types (#7063) e50858905f Fix pending sockets prevent scoped server shutdown (#7067) 24e22d23a7 Fix failed ResourceMap lookups leak acquisition scopes (#7066) 781022acdd Fix map and Set equality permits many-to-one matches (#7056) 3454cdb528 Fix getAndUpdateSome targets the wrapper instead of its backing ref (#7040) 647d14e572 Fix scoped lock finalizers release under the wrong fiber owner (#7036) e4d589e0ea Fix clearAddress leaves primary-key deduplication state (#7038) dce8219d40 Fix response.failed loses its provider error (#7043) 0cdadd75bc Fix slidingSize depends on upstream chunk boundaries (#7055) 17b5d50219 Fix memory journal does not relay imported entries (#7042) 3159d15040 Fix logger shutdown is skipped after flush failure (#7046) 993ba60ee6 Fix sQL journal commits entries before callback success (#7044) af14e752ed Fix tuple.pick types reorder and deduplicate requested indexes (#7047) a1b5df2064 Fix schedule omits the schedule error from its return type (#7050) 7963ce1cd9 Fix optional tuple arbitrary generation shifts later elements into gaps (#7053) 545d876764 Fix a failing request-body stream leaves the Node client hanging (#7049) 59f5e99819 Fix sSE accepts malformed retry directives (#7048) 96e5e9576b Fix memory runner storage ignores health state (#7074) 433fb81ca4 Fix runCount discards its numeric result in the public type (#7054) d56dfcf54c Fix curried catch overloads expose the handled error type (#7057) 79512bb207 Fix wrapped spans treat every OpenTelemetry status as an error (#7069) 2f9016d23c Fix xHR formData decoding always defects (#7075) a5404d4f43 Fix byte-backed text attachments are base64-encoded as plaintext (#7070) e2ec1311be Fix killing a pipeline leaves its root process running (#7073) 92a9ac5ac0 Fix fiberSet serializes itself as FiberMap (#7062) a5278b1824 Fix mCP sampling metadata has the wrong optionality and shape (#7071) e443403cf0 Fix void MCP tools are reported as internal failures (#7072) 88c7632c2b Rename Schedule sequencing APIs to concat (#7077) 936b135839 Fix cluster single-runner shutdown deadlock (#7032) 49fc969429 Remove redundant Effect.scoped test wrappers (#7058) 0f721d406d Return the new offset from File.seek (#7076) 85d2b446e3 Report transient RPC socket open failures (#7068) d795ee7717 Fix DevTools span request snapshots (#7065) d44cead7e0 ExecutionPlan: lifecycle events for retries and execution observability (#7026) eaa7e71b88 Fix conflict scanning indexes the wrong history array (#7029) 7f4c095b62 Rename RateLimiter makeSleep to sleep (#7023) 45379d6179 Fix replacing a trie key mutates the original and increments size (#7028) 130b28df55 Prevent Effect.updateService and Effect.updateServiceScoped supertype widening (#7027) 7a41f5aa72 Prevent Effect.provideServiceEffect supertype widening (#7025) b8cc53d221 Keep release PR type checks unstripped (#7022) e9308045be Hold persisted messages during entity registration (#7018) 17f0b91a24 Fix Schema.make nested class construction, closes #6890 (#7020) 0a45ef3bb4 Round Redis persistence TTLs (#7019) 0a82d88b7d Fix fractional durable clock wake-ups (#7016) 1001bccb9e httpapi: WithHeaders response header schemas (#6934) 7f31fb0350 Remove external network from HttpClient tests (#7017) 8459cdbae8 Vendor multipasta as MultipartParser (#7012) 306014a1ce Vendor find-my-way router (#7013) 99dd6b5804 Inline INI, YAML, and TOML parsers (#7014) 5a6a5738e5 Recognize and ignore a leading UTF-8 BOM in SSE streams (#6976) e184f7367e Cancel in-flight Tedious requests when their Effects are interrupted (#6999) 181c9ef5e5 Default empty SSE event types to the message type (#6977) 1949439175 Separate the default variant cache key from named variants (#6973) dd9f891e23 Rename Command.withHidden to Command.unlisted (#7010) abcbb2abe1 Encode empty-data SSE events as dispatchable events (#6975) eb9ee83b38 Persist permanent entries in KVS setMany operations (#7003) 6e7f2e2d1d Return MSSQL procedure values through the output property (#6995) 18aa69aecc Share persistence layers across cache tests (#7009) 3c5e429878 Preserve nanosecond precision in TestClock wall time (#6950) ae4cf7b5e2 Generate valid MSSQL upserts for multi-table persistence (#7005) b32f4cb7b2 Support empty records and iterables in Prompt.all (#6962) 6143de21ee Fix Bash subcommand dispatch for flag values (#7008) 722ea484c9 Exclude disabled choices from multi-select prompts (#6963) d517692ef7 Reject NDJSON values without a JSON representation (#6982) 1434eecbd3 Apply byte-range options to default Web-file responses (#6983) 7f12d4b4e7 Map socket writer exceptions and rejections to SocketError (#6987) aa0580497e Synchronize response metadata when replacing bodies (#6986) ba2c3aa05e Generate unique paths for duplicate multipart filenames (#6990) 39b57d7857 Retain the last SSE event ID across dispatched events (#6978) b78acdf422 Generate even and odd safe integers in Crypto random APIs (#6941) 4158562097 Handle accepted undefined fields during variant extraction (#6974) 01bd9546f1 Preserve file and directory semantics in completion descriptors (#6961) db4c2ccdec Preserve lexical ordering in streaming template interpolation (#6988) 6bde7f27f3 Match Fish completions against the full command path (#6959) 32e4a69b31 Scope custom queue ID deduplication to each named queue (#7006) 6704bb84c3 Emit valid terminal CSI sequences from cursorTo (#6956) 67faacd467 Select Bash completions for the active positional argument (#6957) 6af04a50bd Defer memoized Layer state installation until Effect execution (#6946) 03af7e8555 Close suspended workflow scopes after resumed completion (#7000) 4a17049157 Hold the shared PostgreSQL client permit through transactions (#6991) caf84b6600 Isolate compiled SQL fragment caches by compiler instance (#6997) 3dd1ddce89 Settle pending SQLite WASM requests before replacing workers (#6994) 6ef5f1a041 Decode split UTF-8 sequences correctly in NDJSON streams (#6998) 5d6fe775b7 Enforce read-only mode when opening Bun SQLite databases (#6993) 3058fd594f Keep ordered SQL resolver results aligned after encoding failures (#7001) b4463f46fc Register alternate flags used by Param.orElse (#6952) 2e6f760dcb Support raw BodyInit values in converted server requests (#6989) fd652ce2bd Return selected rows from React Native SQLite value queries (#6992) 32a59e8058 Cache original response bytes before decoding text (#6985) 90ffb083b3 Preserve fractional leading zeros while editing float prompts (#6966) ce067f799e Mark omittable CLI inputs as optional in structured help (#6960) 02b02651ed Allow MCP tool calls to omit optional arguments (#6968) 22f150a093 Correct year, ordinal, and meridiem date-mask formatting (#6964) a7121310db Use the supplied hash for all HashMap.modifyHash writes (#6951) fbb9ce5e62 Correct the UnknownSubcommand runtime tag spelling (#6965) 20ddc63058 Preserve hidden metadata across command combinators (#6958) 6124ab39eb Reject truncated MessagePack frames at end of stream (#6953) cb6c8376b2 Reject zero execution attempts in ExecutionPlan steps (#6943) c987a12a01 Honor numeric zero TTL values in ScopedCache.make (#6948) 2141e28903 Preserve CRLF state across SSE input chunk boundaries (#6954) 2db266b1bf Include plain variant structs in the default union (#6955) 1bbae84f88 Omit removed services from Context.addOrOmit result types (#6940) a98cda9422 Check symbol-keyed properties in Match object patterns (#6947) 729a663275 Keep TestClock nanosecond access total after infinite adjustments (#6949) badd3bf65f Make Iterable.flatten stack safe across empty iterables (#6944) 9215bc5da7 Preserve integral precision when parsing decimal Duration inputs (#6945) 01af079c18 Validate DateTime object instants before construction (#6942) 841b3ea6ae Preserve sibling input evidence when Config.all fails (#6939) 13c5872ed3 Honor populated variables before dotenv defaults in ConfigProvider (#6938) 62d057566c Expose the encoded type from TestSchema.Encoding.encodeUnknownEffect (#6937) 2235a29502 Fix persisted cluster reply serialization hangs (#6972) c2436feef1 Use local server for NodeHttpClient redirect test (#7007) 84dc8ab7ac Serialize concurrent nested SQL transactions (#6971) c3c7647bdf Pin fixed next release action (#6981) b19e3cba42 Update next release action (#6979) add1a26628 Disable Changesets changelog formatting (#6970) 3dacca4976 Fix Changesets v3 release workflow (#6969) a0a0830994 Upgrade Changesets and restore release queues (#6799) 5d2529a113 Stabilize blackholed query recovery test (#6933) 592dd36164 Rename Schema error constructors (#6732) 5cdd3e94fe Pin GitHub Actions to commit hashes (#6926) d1133e8f2b Fix OpenAI tool result test fixtures (#6931) 82a3fbfce8 Route provider-executed tool results into the assistant message in Prompt.fromResponseParts (#6836) 5b3ab384c4 Use local server in NodeHttpClient tests (#6929) 2b9b2105eb Use local server in HttpClient tests (#6930) 721b9f0d32 Attach an error listener to the upgrade socket in NodeHttpServer (#6927) dff25449df Version Packages (beta) (#6658) 1c40b28095 Add DateTime.toEpochSeconds and fromEpochSeconds (#6909) c0a1534944 Add HttpServer response compression middleware (#6898) e02fbb66f5 Fix Semaphore.withPermits leaking permits when interrupted (#6910) 0b4a3c4b1b Fix NodePath file URL conversion to follow the layer's platform flavor (#6914) 2bfa4bfa6b Update pnpm/action-setup and upload-artifact to their Node 24 releases (#6916) ab0859b974 Preserve local OpenTelemetry parent context (#6902) 52494be9e8 Prevent Metric attribute serialization key collisions (#6901) 985de097d7 Keep Chunk.take and Chunk.drop valid for fractional counts (#6899) 4686265406 Emit specialized OpenAI tool results only once (#6921) b650832870 Keep Stream.range advancing with a zero-sized chunk (#6900) c8fd57e07e Make ClickHouse startup timeouts interruptible and close the client (#6907) 0d83916b66 Isolate delta baselines for each metric reader (#6904) a9131368c1 Fix malformed JSON in Anthropic code-execution deltas (#6918) f3f6c1e02c Preserve equals signs in inline CLI option values (#6903) da10211673 Terminate OpenAI streams when response.failed arrives (#6920) 5ed5692f1a Propagate ClickHouse result decoding failures as SqlError (#6906) b74333d83e Replace existing HashRing nodes when primary keys match (#6911) 34ced6990e Close OPFS access handles when the SQLite worker shuts down (#6923) c53591f2b0 Preserve fractional numbers and Unicode in MSSQL parameters (#6922) 8a45ec3117 Bind inferred ClickHouse numbers with fraction-preserving types (#6908) 3859896551 Refresh v3 to v4 migration guidance (#6925) a94cbed84e Ignore `uniqueItems` when set to `false` while importing JSON Schema documents (#6924) 9344742c6b Fix OpenAI stable web search action parameters (#6722) 5c029f1092 Simplify required Struct make input type (#6919) 8f9499f562 Remove actual values from schema issues (#6912) b75884413b Fix flaky arbitrary candidate property assertion (#6897) 52262be2ed Bind event log RPCs to authenticated identities (#6896) 48f22a7d16 Use layered storage for Context (#6828) fbbcfe7bb9 Stabilize child process and libSQL tests (#6895) 50c9459dac Stabilize Deno MySQL integration tests (#6894) 4b3460daa4 Add reproduction for Trie issue (#6862) 4a394bd298 Add reproduction for vitest/utils issue (#6843) 79db8e5e42 Add reproduction for platform-browser/BrowserKeyValueStore issue (#6857) fb3ab9b0b4 Stabilize SuspendOnFailure workflow test (#6892) 9fb59a5bab Add reproduction for libsql/LibsqlClient issue (#6858) 3895b9cf17 Add reproduction for Cause issue (#6879) ef6eca71dc Add reproduction for platform-browser/IndexedDbQueryBuilder issue (#6852) adf6c6cd38 Add reproduction for String issue (#6860) 52b2d7b5bd Add reproduction for TxQueue issue (#6851) 1519406fed Add reproduction for OtlpResource issue (#6865) f398149c13 Honor HTTP-date Retry-After values in OtlpExporter (#6864) b3901d29c5 Add reproduction for Equal issue (#6874) 4a0984af62 Add reproduction for Persistence issue (#6869) 979ce3985d Add reproduction for internal/protobuf issue (#6875) 7314d60528 Add reproduction for HttpBody issue (#6866) f5cf96548a Add reproduction for HttpClientRequest issue (#6867) eec57445df Add reproduction for TxQueue issue (#6853) fffd88b313 Add reproduction for Persistence issue (#6868) 39ed94a075 Add reproduction for platform-browser/IndexedDbDatabase issue (#6854) 6301fd710b Add reproduction for Trie issue (#6856) ef07642dfe Add reproduction for Sink issue (#6876) f1e3a378c1 Fix empty snake case conversions (#6859) 446fdb9210 Remove API documentation CI (#6841) ecd9993611 Add reproduction for FiberSet issue (#6873) 608fdec4f4 Add reproduction for Trie issue (#6861) dbe91f6961 Fix stream execution plan retry limits (#6889) 13d31cfc2d Add reproduction for internal/otlpEnv issue (#6863) aebc5c6166 Add reproduction for TxPubSub issue (#6850) b46c92f3b3 Fix SubscriptionRef getAndUpdateSome None result (#6849) 48155c8ccf Add reproduction for Schedule issue (#6855) 0ac99b44ec Stabilize SQL runner connection recovery test (#6886) f4151e1937 Add reproduction for ScopedRef issue (#6848) 5ab9c08463 Fix Formatter output contracts (#6872) cd122b9030 Fix BigInt gcd and lcm edge cases (#6870) b4f1ee238d Fix Array index handling (#6878) f77c120d8e Add support for converting JSON Schema documents to Draft-04, closes #6846 (#6883) 25a029ccf2 Decode OpenAI-compatible tool parameters (#6882) 72475b470b Fix SQL cleanup test timing race (#6881) 9160ad7d14 Add reproduction for LayerMap issue (#6871) 8ef72577d1 Add reproduction for MutableList issue (#6847) 5de588b247 Add reproduction for Cache issue (#6844) 724ce09650 Fix aggregateWithin continuation growth (#6877) 0a0f2f0fb9 Standardize JSDoc categories (#6835) 5441c8e656 Fix Metric.isMetric runtime brand (#6822) 89ce5f3e16 Fix ChannelSchema.decodeUnknown input type (#6820) c9b56ab507 Fix Metric linear boundary spacing (#6821) ace903e09c Skip HTTP span attributes when unsampled (#6824) 705d1f13a1 Optimize Node HTTP response handling (#6813) 4c008d28b3 Fix Stream.mapAccumArrayEffect data-first usage (#6823) 1ed7c17eeb Avoid redundant HttpRouter context clone (#6812) e8eb62b3d0 Refactor Config schema loading (#6814) bcf2d4b10c More jsdoc improvements (#6817) c9aa7d0fa8 Jsdoc improvements (#6816) d14df510f2 Fix flaky test (#6815) 5335797003 Overhaul example snippets (#6808) d0f1a22951 Split wall and monotonic clock semantics (#6807) 97169902ee Fix PubSub replay value retention (#6805) c144b26ab0 Fix SQL cleanup test clock race (#6806) c5bf174cb3 Add D1 batch support (#6524) f1bc8274a6 Cap RPC streaming decoder buffers (#6802) 4dc35f6464 Add cluster integration test suite (#6800) 04fd44a42a Run SQL persistence cleanup in bounded batches (#6564) 8037a9810c ci: post pullfrog commit-status checks on PR runs (#6801) e56cd8f90c Add HttpClient tracer header filter (#6697) a959a8bf21 Namespace Postgres advisory locks by table prefix (#6798) d3ba732005 Add console output assertions (#6797) 8e89cc60a6 Add doctest conventional assertions (#6794) b2f95a9c2f Add Semaphore takeIfAvailable (#6793) 98de280384 Refresh v3 to v4 migration guidance (#6792) 24e0e93dc3 Propagate trace context for persisted workflow requests (#6783) 15de65eb21 Add docs for doctest (#6791) 20103b21f3 Add doctest (#6789) 346b09e178 Simplify vitest configuration (#6786) 5287b24f5f Refine ConfigProvider interface (#6788) 9263ba30c4 Fix Changesets GitHub query batching (#6784) 65aa881c0b Remove JSDoc Oxlint rule (#6785) d767b65a76 fix(schema): resolve representation identifiers by AST identity (#6782) aeba0c8c9f fix: mcp conformance and add test suite (#6693) acee26944b fix(schema): deduplicate JSON Schema fallback definitions (#6781) c96b7f6359 Emit MCP tool output schemas (#6780) 8e7c706b0a Bound pending SSE decoder state (#6777) 5f63adbe75 Harden file-backed key-value store keys (#6772) 053bc42e2a Escape control characters in CLI errors (#6773) 660875b432 Strip credentials on cross-origin HTTP redirects (#6771) f0c8151688 Fix generated SSE event decoding (#6770) f9ba41282e Secure MSSQL transport defaults (#6766) 4a57af2401 Harden cookie attribute validation (#6763) 1acbd8b44c Harden JSON-RPC message classification (#6759) a4757f1c47 Fix Atom dependencies during batch rebuilds (#6751) 060e3468c8 chore: add Pullfrog workflow file (#6750) 94bb1ba3b4 Fix Wrangler installation for v4 docs deployment (#6747) b9981593e4 Deploy v4 API docs to Cloudflare Pages (#6739) cedb01a025 Prefer explicit OTLP resource configuration (#6746) 501a1a1f91 Render bundle size comments as tables (#6744) cccd029ae0 Harden pull request workflows (#6736) 1284aa1834 Simplify Optic composition and add regression coverage (#6735) 6d2a942ed7 Optimize Schema.Class decoding (#6733) 7bde6ccb2b Restore FileSystem.watch recursive control (#6705) a3fabe25ad Add Deno cluster HTTP support (#6731) 60863090af Add native Deno HTTP server (#6730) e0426da103 Add Deno cluster socket support (#6728) 955eb69b79 Construct empty Bun multipart streams per request (#6726) 507506cc38 Update Deno CI to fix native transform flake (#6727) d435467e63 Add Deno multipart support (#6725) 7870557987 Add Deno socket server support (#6724) 20b9660d42 Fix OpenAI compat parallel tool calls (#6719) 081f4d8cd0 add platform literal to HttpPlatform (#6723) 35141f1a02 Add Deno socket support (#6721) 87ca79ec8f Add Deno HTTP platform (#6720) 5002d95d83 Fix error names in JSDocs (#6718) 897b35ca37 Add Deno services aggregate (#6716) 31170c19b2 Document ChildProcess environment behavior (#6717) 3eeea73cfc Fix CLI end-of-options operands for subcommands (#6692) 75f340ef3d Add Deno terminal support (#6715) 6eba7ec51c Fix bundle fixture feature retention (#6714) 733f75b712 Preserve reactive query metadata (#6711) 26ccf093a7 Refactor JsonPatch internals (#6713) 951d06b83d Fix stateful Schema.isPattern validation (#6712) 1a7ce8150e Add options to Schema.fromJsonString (#6707) 9867b9fc69 Improve configuration (#6701) 5d52d9d148 Fix Schema Union candidate pruning (#6704) d48506d975 Effect Schema Runtime Performance (#6649) 6a5e86f896 Allow enabling websocket compression in NodeHttpServer and BunHttpServer (#6691) d51329b433 Add native Deno stdio support (#6700) cd9862247f feat: DenoFileSystem (#6682) 504343b0cd Add DenoChildProcessSpawner (#6688) b6d3e67c7c remove file descriptor type (#6696) a149f89295 Add Deno Redis integration (#6686) 827945679e Add guidelines for examples (#6695) 45e7810882 feat(platform-deno): add DenoHttpClient (#6685) dbd6ea4ef6 Add Deno Crypto implementation (#6684) 5f87bc7a66 Fix BrowserCrypto random bytes above Web Crypto limit (#6683) ec656dccf1 feat: deno platform (#6412) ee29ddf862 Add core Web Stream interoperability (#6681) 23e176a4f0 Fix worker runner cleanup (#6680) 9f02491ae3 Preserve buffered NodeTerminal readLine input (#6679) 381c14141b Signal Terminal end-of-input at stdin EOF instead of hanging (#6676) 9800e3acc8 Scope cluster reply serialization failures and peer-delivered defects to their own request (#6579) 1747440de9 Expose runtime schemas for prompt parts (#6677) 4cc95ae4a8 fix: accept image generation lifecycle statuses (#6675) 83d571c950 Fix useAtomSuspense registry isolation (#6672) 83c74972f6 Avoid direct @vitest/runner import (#6668) 5985277187 Document MCP protocol migration requirement (#6674) bcf6db0da5 Remove version.ts (#6670) 0a532e503f feat: add MCP protocol adapters (#6625) 52838418db Surface unknown OpenAI-compatible stream events (#6667) a3fd084821 Fix HttpRouter web handler context (#6665) 96ced895a0 Deduplicate module migration notes (#6662) 797111ae76 Suppress redundant removed API migration entries (#6660) ed0ebf8e5c Fix reactivity for hydrated atoms (#6661) cc27b194b9 Preserve accessors with loose object spread (#6659) 205ebc7760 Avoid timers in synchronous scheduler dispatch (#6657) b49e8d8a0a Add v3 to v4 API migration reference (#6656) 3fccb56a59 fix(ai-openai): remove stale web search patch (#6655) 9b16adb8f9 Add API diff tooling for v3-to-v4 migration (#6547) de2a9a6909 Version Packages (beta) (#6560) 028bbb391e Remove Effect.withConcurrency and "inherit" concurrency (#6653) 64c6ab1951 fix(ai): encode system messages as input text (#6549) acd385ebb3 Fix AI provider header redaction (#6650) 5e9221652f Align schema runtime brand guards (#6647) 7271a7faf1 Optimize schema union derivation (#6646) af1dd350f8 Optimize template literal parser segmentation (#6645) 475fe5c12c Prevent SQL runner lock refreshes from hanging (#6516) 6079fda7b0 Fix telemetry loss during OTLP exporter shutdown (#6634) 078e1f5636 Optimize equality-based array operations (#6644) 7a03c893ce Hash SQL message deduplication keys to prevent message_id overflow (#6618) 3e3dda09ef docs(agents): run only affected tests, leave the full suite to CI (#6642) 45762bd78d Add manual flush registry to OtlpExporter (#6641) ddd60ea31f Make schema tree validation stack safe (#6640) 4f1e3183f7 Fix Latch stale flush resuming waiters registered after close (#6637) 57367d54de Fix PartitionedSemaphore permit leak on interruption (#6638) 2ab05c3a8f Clarify Semaphore take documentation (#6636) 0c2db1564d Fix empty struct union candidate selection (#6635) 30effdbb32 Fix Schema JSON stringification of undefined (#6633) c8d73fd21f Fix Schema StringTree DAG validation (#6631) dd2f1a96c6 Fix Schema numeric index key matching (#6629) 9df77b78c5 Fix Schema enum codegen type (#6628) 1596afee04 Remove the labeling workflow (#6626) 2321ea1fa8 Handle conflicting Schema bracket paths (#6627) 1725927504 Fix literal string check regex metadata (#6624) 6956bc0e6c Fix MCP Streamable HTTP transport compliance (#6610) e9fee0ddd6 Fix Schema template literal type codegen (#6623) 0444004198 Remove experimental SchemaUtils module (#6622) b08653e76a Validate ordered check bounds with canonical codecs (#6621) c917bb94a4 Fix CLI positional argument overflow (#6561) ff5d6e278a Make Schema.Date reject invalid dates (#6620) c323d8b30d Prevent MCP tool errors from exposing internals (#6607) 9d8d85c1bb Fix Metric max for negative-only observations (#6614) 35c445ff18 Expose tool call IDs to AI handlers (#6615) a6e8391cd3 Add Tool.setNeedsApproval combinator (#6616) bc1f3583e6 Fix duplicate chunk handling in ChunkedMessage.join (#6613) 0e50ec7dbb Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches (#6608) 97bafeab46 Make embedding usage input tokens optional (#6609) 71b3e9a044 test(ai): make LanguageModel tests actually execute (#6601) 80e9256036 Fix Chat persistence tests (#6602) aab3514130 Use it.effect for KeyValueStore tests (#6603) 475bdaec90 Fix no-op CLI argument tests (#6605) 4724238e88 Use Effect test helpers consistently (#6604) fab0ab8f7a Make AI response fields optional (#6606) 9fcdade4a8 Interrupt Stream async iterable pulls on iterator close (#6599) 73d40aacd8 Fix streaming tool call concurrency (#6598) 4cd40f5692 Fix mergeAll outer failure handling (#6593) 44e8512991 EFF-22: test Deferred waiter cleanup on interrupt (#6587) aa853a581b Add interruption test: Effect.callback register cleanup runs on interrupt (#6591) d0b3265c32 Fix Stream.haltWhen for synchronous streams (#6592) 7bf8fb0352 Add Latch await interruption coverage (#6588) c827e28071 test: assert Effect.race / Effect.raceFirst interrupt the losing side (#6590) 89a962b5b4 Add test: onExit callback observes interrupt exit (#6589) 6e146907e4 EFF-24: Test blocked Semaphore.withPermits interruption (#6585) 8f126b22ed Add direct tests for Effect.uninterruptibleMask (#6586) 1156d0d486 Add Queue.take interruption test (#6584) b94f6e64d5 test: assert acquireUseRelease release runs when use phase is interrupted (#6583) 157cf8742d test: Effect.all / zip / zipWith with concurrency interrupt in-flight siblings on failure (#6582) b6b17b63f7 test: assert Effect.timeout interrupts the underlying effect (#6581) 4ac7e8b136 Add Effect.updateServiceScoped (#6562) cea1d9c926 ManagedRuntime: add Symbol.asyncDispose (#6577) 69663534d6 Record: make fromIterableBy dual (#6576) 1bfce93e6d Make schemas directly extendable as classes (#6575) c0f9fc9fda Fix OpenRouter generation usage schemas (#6572) 7ed945044e Add HttpClientRequest.updateHeaders and removeHeader (#6574) 5101e92c9c Fix dynamic record property assignment (#6567) 980d220970 document the Schema.Date migration (#6568) b49284193f Label pull requests by target branch (#6565) 0e0c9d7922 Fix FiberHandle.clear race (#6552) b6392e1197 Add custom equality detection for atoms (#6563) 7ce815cd5a Refactor SchemaRepresentation (#6424) 40a9c60d0a Use Node 26 for Corepack (#6559) 19eeda68ba Migrate docgen (#6556) ed2afb3424 Symlink agent instructions during installation (#6554) 4e0be584fb Version Packages (beta) (#6515) 731bea1941 Audit (#6545) 31a8250428 Clean up Fiber.joinAll observers on interruption (#6536) 2bae1accce Fix HttpRouter middleware context inference (#6526) b35ed2904f Simplify the displayed `Type`, `Encoded`, and `Iso` types of required readonly `Schema.Struct` fields, closes #6521 (#6523) b82248bef7 Move pnpm config (#6520) 017f384b1e Fix OpenRouter dynamic tools sending an empty parameters schema (#6518) 0e116a139c Preserve OpenAI Responses cache write token usage (#6519) 06eb3bc770 Restrict nightly AI codegen to Effect-Ts repositories (#6517) a80866655f @effect/sql-mysql2: add disablePreparedStatements (#6510) dd44624573 Fix MutableList filter empty-state handling (#6514) ab8390d4c1 chore: upgrade TSTyche (#6511) 67114e90c3 Version Packages (beta) (#6478) 0f37a01d91 fix changeset eb9b10256c Don’t create a table when it’s not needed (#6498) b0f1a50dac Handle aborted HEAD responses during NodeHttpServer disposal (#6506) c1288dd1a5 Add discriminants to Schema.toTaggedUnion (#6501) 688d46afd0 Add `Effect.reduce` (#6496) 8b155da06e only interrupt cache lookup when all awaiters are gone (#6494) 3a8733564c clean up more references on fiber exit (#6495) 875e618c37 fix(cli): remove doubled Expected prefix from InvalidValue messages (#6487) 31d3fc4327 Fix published pre-response handler types (#6433) 06e7e8c660 Route multipart errors to HTTP responses (#6493) 6dc83f26dd Preserve nested class construction when applying constructor defaults, closes #6491 (#6492) ce95d88603 fix(openapi-generator): handle recursive schema forward references (#6483) eb3ea7a75e Preserve chained vitest helpers like it.describe.each through the it proxy (#6444) c0333e7f75 Fix fiber self-interuption from inside a running operation (#6484) e000f80fd5 Fix Cron.prev month day rollover (#6472) 2b58a3dab6 Fix Cron alias normalization (#6475) 6ff502363b Fix Cron documentation accuracy (#6480) f4ee7655ee Fix Cron.prev weekday wrapping (#6471) 97e2ce8770 Fix Cron weekday JSDoc examples (#6479) c1e2fe0cf9 Add `Cron` day and weekday intersection semantics in inspection repre… (#6476) f3fbae8d7b Validate Cron.make field restrictions (#6474) 510b55f3e2 Fix Cron timezone equality (#6477) 6184a7dc53 Version Packages (beta) (#6386) bfb203e95a Distribute `HttpApiBuilder` handler requirements per service so request middleware layers can provide them, closes #6464 (#6468) 8df19f4fe8 Fix isInt32 filter group annotations (#6465) bddb010eac Fix Graph allocator equality (#6462) 9e6e12d75c Fix mutable Graph hashing (#6461) e7aca894bb Fix Graph async mutation callbacks (#6459) 55d75609b8 Fix undirected Graph equality (#6458) febeabc3f7 Fix Graph.topo directed graph types (#6460) e6e6dba6e9 Optimize Graph pathfinding queues (#6457) 5560d05aa6 Fix Graph curried getter inference (#6454) 54161c98f6 Fix Graph Walker repeatability (#6455) 3394b93d97 Fix mutable graph topological walkers (#6456) 0e135bd242 Fix PgClient.makeClient connection acquisition (#6451) 212493b9a1 Retry failed Redis script loads (#6390) 0ebdbe7446 Fix multipart limit violations being silently swallowed (#6395) 7eea4d0b73 Fix one-shot iterable handling (#6438) 80ea8cb922 Fix Graph lazy traversal stale node skips (#6430) 46997fa604 Fix Graph shortest-path edge weight validation (#6426) b3302645fc Fix child process force-kill escalation (#6411) a79f5ec4f2 fix(vitest): preserve schema arbitraries in records (#6416) a328835e50 Fix Graph.bellmanFord self cycle detection (#6425) 9ca7f9a693 Fix Graph.isGraph predicate narrowing (#6423) 533c9f4819 Clean up stale package references (#6422) 44b9cf3d24 Fix Graph.floydWarshall null edge costs (#6421) 0a8aa6acb9 Fix Graph.isAcyclic for parallel undirected edges (#6420) 7517d09f12 Make Graph interfaces opaque (#6419) 8f6e3adb18 Fix Graph edge transforms (#6418) c8d9fcf7b0 Fix Graph mutation finalization (#6417) f809189ddf Fix Graph.Walker iterator receiver (#6415) 385f7a4ee4 Fix Graph.toGraphViz DOT escaping (#6414) 80b539f8ab Reintroduce CLI wizard mode (#6409) b690d47794 Document graph set operation gotchas (#6408) 88a54cc341 feat: add graph set operators (#6394) caafeed714 Remove legacy LSP and use effect/tsgo (#6401) 8ce4795ccb Add CLI config for built-in flags (#6397) 01d5a9c069 Fix pkg.pr.new README badge (#6391) 2e9a34ac2b Fix missing CLI flag value handling (#6389) 55d4eb34f2 fix(cli): make `Command.withSubcommands` not lose context type inference (#6359) 7543afea6f fix: repair provider-defined tools (Tool cloning + Anthropic client tools) (#6371) 0fa51411b4 Cleanup workflows and markdown files (#6325) f9fc49b152 Cut over Effect main to V4 (#6324) dadbb37f74 Adapt Effect V4 for the canonical repository 5fcc13f054 Merge frozen Effect V4 history into Effect e670e0f6be Version Packages (#6323) 3e4abbcb0d Version Packages (beta) (#2577) fffdee0cc5 Release queue: minor (#6286) 703792d9de Pin to npm 11 to fix provenance bug in cli (#6322) 11e0169475 Version Packages (#6282) 0082f4f74f Fix JSON Schema tuple allOf intersections (#2605) 3f0f9e0a73 upgrade `tstyche` to latest version (#6320) 72ac585884 Add HttpApiError for status 422 (#2613)A b24d248c8d Fix durable race result replay (#2612) 884905232d Ensure `PersistedQueue` decoding failures are processing attempts (#2611) c15e16ad13 fix(PersistedQueue): Fix Redis-backed PersistedQueue script handling (#2500) 87bea7e162 Handle large millis passed to sleep (#2583) 2b7ce2b513 Fix SQL-backed persisted queues to refresh locks for actively acquired elements. (#2609) 5f142a2136 Preserve autocomplete for known OpenAI-compatible config (#2608) 5b2a0bceea ensure WithTransaction wraps entire rpc handler (#2607) 6e08428d98 Fix Schema tuple post-rest indexing (#2604) 388dcf953f Fix Schema union candidate dispatch ordering (#2603) 01d00a3abf Prevent prototype pollution in Schema bracket-path decoding (#2602) 3c62b7b6dc Compile HttpApi SSE client decoders once (#2600) a0372736ac Preserve HttpApi __proto__ identifiers (#2598) 0f9c07841b Reject duplicate OpenAPI operations and operation identifiers, and reject incompatible security schemes that reuse a name (#2595) 5e8c1b82bf Reject unknown and duplicate HttpApi handler registrations with descriptive errors (#2594) 214c458084 Apply `transformClient` when building an individual HttpApi endpoint client (#2592) a807cd1703 Keep HttpApi composition immutable (#2591) 4ae0c5ffcb CLI cleanups (#2590) 8bd458975a fix(schema): reject non-record objects in isJson (#2588) 989603b60a Expose SchemaError module, closes #2586 (#2587) 5946da3804 Reuse HttpApi response schemas (#2585) fd8a356f06 Normalize HttpApi payload media types (#2584) ce38dc33bd Harden HttpApi documentation HTML rendering (#2582) eec85ddba0 Fix HttpApi client error decoding (#2581) 19c222cac2 Fix HttpApi authorization decoding (#2580) c2a5edc3ab Improve unstable `HttpApi` type-level performance (#2476) 97fdaa9c1f Fix Atom.kvs async writes (#2578) 0c2ed5ee0e Normalize empty assistant message content to an empty string (#2576) f643dbb265 Pin to NPM 11 to fix provenance bug in cli (#2572) 8e6f66adf1 Version Packages (beta) (#2571) 97a4138f9e fix(ai-anthropic): preserve the string branch of the open `Model` enum (#2568) 3f0ccc0471 Document interdependent HTTP API middleware (#2569) 2711e39af7 move schema ai-docs d6184fd6ce Version Packages (beta) (#2565) 57fe79316f change rpc ids to string | number (#2566) 1503f45cb5 update dependencies (#2563) 5434f2e88c Version Packages (beta) (#2548) 97f29df457 use Sets to track atom relationships (#2562) 0c2f78f695 Schedule simplification (#2561) 266cb90bb2 Treat empty config strings as missing (#2560) bdca35449d Fix Memory_20250818 missing requiresHandler flag (#2531) d24511fee9 More closely adhere to vixie semantics for cron expressions (#6305) fbefa850fa fix activity retry policy (#2559) 912f095a34 Consolidate Schedule limiting APIs (#2554) a6718f9e00 More closely adhere to vixie semantics for cron expressions (#2556) 18a49e1786 Fix `Schedule.cron` when the test clock is adjusted to infinity (#2557) fb50f14fc3 Add Schedule.min api (#2558) bef51540a2 Add Schedule.max api (#2551) 18e0564bd0 Remove some Schedule APIs (#2553) d95868a1c9 Fix `Schedule.cron` when the test clock is adjusted to infinity (#6303) a482442abd feat: add `Schema.DateFromMillis` (#2542) 307d54a54b Allow cron fields like 5/15 (#6302) 0b4a32f426 Allow cron fields like `5/15` to expand from the starting value through the field maximum. (#2547) 82951e58d3 Version Packages (beta) (#2525) ff30b6e7c2 Cache clients by entity reference (#2546) a0a3490bbc Isolate cluster entity handler context from the fiber that first wakes it (#2545) 51f6d197b7 Replace better-sqlite3 with node:sqlite in sql-sqlite-node (#2487) aa80c4775a add LayerRef module (#2541) c2ae4fce2f Schema: add `Schema.Decoder` and `Schema.Encoder`, and accept simpler schema types in APIs that only decode, only encode, or only need the basic schema shape, closes #2536 (#2539) 1caab3cc30 feat(effect): Add glob to file system (#2523) 95a0e9bb62 fork memo map on nested builds (#2538) 6d2c614fab optimize bun stream reading (#2537) 4b6856f914 Improve schema passthrough strict: false mode example (#2532) f11ce73af6 Fix HttpApi runtime shape (#2524) 455ecc9ac7 Add Predicate ai-docs introduction (#2522) 117ee96ba1 Add Schema introduction to AI docs (#2521) 13df1ebb11 rearrange schedule ai docs c74babd0ad Version Packages (beta) (#2506) 2bc541501a Fix StructuralProto equality (#2519) ba7e77e046 Move UrlParams.makeUrl to Url.make (#2518) 5713ee7edb accept UrlParams.Input in some UrlParams apis (#2505) ceaffb8e53 render nested error causes in Tracer exception events (#2511) 00652fe95c Preserve content schema identifiers when emitting JSON Schema for `Schema.fromJsonString`, closes #2495 (#2512) e11cccc7d5 ensure handler errors don't cause httpapi security middleware to fallback (#2507) 6c5816746e fix(httpapi): return 400 for malformed JSON request body (#2492) 3e59443be0 fix(@effect/ai-openai): map max_output_tokens finish reason to length (#6297) a445fe5268 Version Packages (beta) (#2502) affdc13904 Fix excess property handling in schema-backed class constructors, closes #2499 (#2501) 04cda03099 Version Packages (beta) (#2486) b135b2517f Fix `Schedule.andThenResult` to emit `self` outputs as `Failure` and `other` outputs as `Success`, closes #2497 (#2498) aaa21a369a Fix string case conversion for numeric word segments (#2488) 3475ee6c2b fix RequestResolver interruption (#2485) cfa2bbf7f2 fix(ai-anthropic): populate cachedInputTokens in streaming responses (#6288) 95c7d2ed43 fix(Cron): do not skip earlier days when the upcoming day is missing from the month (#6285) 99d5575a14 fix(platform): scope HttpLayerRouter.addHttpApi error encoder to its own routes (#6244) 4c5a0e384a Version Packages (#6269) 2e01a9e8cd fix (ai-openai): Ignore unrecognized events rather than cancelling the event stream. Closes #6277 (#6281) 7b57f41f85 fix release-queue 9fb7f7d076 try add approval gate to release-queue (#6280) 18762540d7 fix(ai-amazon-bedrock): emit streaming "finish" after metadata arrives (#6272) 7e00169ae0 Backport content-length header removal (#6273) 8222963e76 Fix undirected graph traversal (#6267) 05d72eab7b Version Packages (#6259) 02ae8fb158 fix(ai): support empty tool parameters (#6260) e2126bc14c fix(Data): preserve generic A in TaggedEnum $match arms (#6250) f7e836ea9b Emit `additionalProperties: false` for records with string keys and `Schema.Never` values (#6257) 626c61b3ef Version Packages (#6227) 26e19228e1 Backport cluster shard group fixes (#6255) e5998a45f6 fix(workflow): forward parent pointer when spawning a child with discard:true (#6241) 13c82074fd fix(ai-amazon-bedrock): strip thinking from additionalModelRequestFields when using generateObject (#6206) e71ba68273 fix(@effect/cli): use Ansi.blackBright for Weak spans so --help is readable on dark terminals (#6208) 1a63ec87cd fix(cli): replace all hyphens in shell completion command names (#6213) 3585f25110 Version Packages (#6218) 54e61b3e08 Support null K8s lastTransitionTime values (#6217) 70ce155cd7 Version Packages (#6197) ec042e71ad Backport workflow suspension failure fixes (#6196) 5f468d857f clone full v4 history 39c934c147 Version Packages (#6195) 518d0e3f48 preserve fiber context in HttpLayerRouter.addHttpApi so API-level middleware is applied (#6147) 0fac630b27 correct typos in source code (receive, separate) (#6110) d1f7672155 docs(@effect/vitest): fix return type in fails example (#6163) e2374c20ce make RpcSerialization.msgPack options configurable (#6161) c0166426f8 update msgpackr to 1.11.10 for Cloudflare Workers compatibility (#6191) 74f3267a6c fix: floor TestClock nanoseconds before BigInt conversion (#6194) 39474662a7 chore: disable TypeScript's nightly workflow (#6193) b56a6ec056 Version Packages (#6189) 49c5acd593 (fix: ai-openai) Remove strict param from the OpenAI request body (#6188) cc0c40a2fe Version Packages (#6142) 47f04399d3 fix(ai-openai): deduplicate response.output items to prevent invalid JSON concatenation (#6178) b63fdb8783 Change 'in-memory' to 'in_memory' in prompt cache enums (#6187) 4708bb8e32 backport cluster serialization fix for notify path (#6183) 739f077d1c [ISSUE-6009] (fix: ai-openai) Make strict mode configuable for tool definitions (#6174) ec5c50507b fix(cli): prevent --log-level=value from swallowing next argument (#6144) 6c39a34c61 fix(ai-openrouter): correct HTTP-Referrer header to HTTP-Referer (#6145) f99048e9f4 fix: resolve batched request resolver defects hanging consumer fibers (#6139) d4ed885bb9 Version Packages (#6141) 4767f860a8 EFF-749 Backport sql-pg dedicated listen connection (#6140) 6e3782af7a Version Packages (#6135) f7bb09b022 Add Cron.prev reverse iteration support (#5786) bd7552a19c Add type-level utils to asserting layer types (#5920) ad1a7eb7f6 RcMap: support dynamic idleTimeToLive values per key (#5859) 0d32048f98 Add logs to first propagated span (#5710) 37a8af53e3 Version Packages (#6134) 041dd22de0 fix(sql-kysely): proxy get invariant violation (#6123) a03b6a29ed fix(logger): use source maps for error stack traces in browser pretty logger (#6093) 7d8fc1ef2e docs(Layer): clarify scopedDiscard JSDoc to match effectDiscard (#5790) add06f4521 fix(Equal): guard against null in structuralRegion comparison (#6133) 5c80e578bd fix(ai-openrouter): allow partial tool_call deltas in streaming (closes #6128) (#6131) aa473938a5 Version Packages (#6089) 9245bc59eb fix(effect): correct scheduler changeset level (#6125) 8798a84321 fix(effect): isolate scheduler runners per fiber (#6124) 7103e2473d fix(ai-openrouter): handle usage-only terminal stream chunks (#6117) fc82e81448 Backport `Types.VoidIfEmpty` to 3.x (#6107) cf74940521 Prevent schema validation from stripping headers prototype (#6101) f6b0960bf3 Backport: Fix TupleWithRest post-rest index drift validation bug (#6097) 88a5260788 remove defensive check from tool call json schema generation (#6094) 82996bce8d fix(Schema): handle Transformation in getIndexSignatures for correct omit behavior (#6088) 4d97a61a15 Schema: fix getPropertySignatures crash on Struct with optionalWith({ default }) (#6086) ab3b64c20a Version Packages (#6080) 2d2bb1364a fix semaphore race condition where permits could be leaked (#6081) 4eb5c008df build ManagedRuntime synchronously if possible (#6079) dd4cf4b119 Version Packages (#6074) a91364d0ef fix(ai-openrouter): optional name in streaming tool calls (#6071) 4f2107548f Version Packages (#6063) 94b00c8e4c feat(rpc): add defect schema option to Rpc.make (#6065) 12b1f1eadf prevent Stream.changes from writing empty chunks (#6062) ed45318177 Version Packages (#6045) c3e706ff4d fix(ai-openrouter): deduplicate reasoning parts when streaming (#6060) 52cd53115f fix(cluster): HttpLayerRouter variants for route registration in HttpRunner (#6056) 598ff7642f fix(rpc): encode defects with Schema.Defect in sendRequestDefect and sendDefect (#6055) d67c7089ba Backport HttpBody/HttpServerResponse contentType improvements (#6050) a8c436f700 Fix `Stream.decodeText` to correctly handle multi-byte UTF-8 characters split across chunk boundaries. (#6040) 904e055143 Version Packages (#6034) 708b8ba2dc sqlite-bun query prep fail in Effect, not defect (#6044) 8e2286271a upgrade better-sqlite3 to v12 (#6042) 22d9d27bc0 Backport client interrupt error handling (#6035) 740a912142 Backport PgClient.fromPool (#6033) 7e35a4164b Version Packages (#6032) 17812444c6 Backport MessageStorage encoded NonEmptyArray (#6031) 33712058e2 tweak AGENTS.md (#6030) 5487cfb65a update docgen (#6029) 5b9ba01ad9 add agents.md (#6028) cc2e49ac8d Version Packages (#5999) 38241dee23 Fix up the OpenRouter AI Provider schemas (#6026) c1aaefb7ad Add SafeIntegers support to @effect/sql-sqlite-bun client (#6016) 9a96b87a33 fix(HttpClient): backport retryTransient fixes from v4 (#6019) 5df4da10de fix(cli): fix Prompt.text clear when input wraps terminal lines (#6022) e71889f35b fix(Match): handle null/undefined in Match.tag and Match.tagStartsWith (#6018) 0023c19c63 fix(platform): properly join URL paths in HttpClientRequest.appendUrl (#6021) 7b8165f457 fix(@effect/sql-pg): readded stream to PgClientConfig (#5998) 3134f12ba8 Version Packages (#5985) 4860d1e09b Fix prototype comparison in Equal.ts (#5996) 118e7a4af5 Add `rows` and `isTTY` to `Terminal` (#5977) 4725a7ecea fix(opentelemetry): make sdk-trace-node and sdk-trace-web required peer deps (#5952) f4972eda6c feat(opentelemetry): add protobuf protocol support for OTLP exporters (#5927) 7e925eae4a Fix Array.flatten type inference loss (#5963) (#5981) abdab5cc4e fix(opentelemetry): correct Span.addEvent 2-arg overload (#5929) d7e75d6d15 fix `Config.orElseIf` signature (#5970) 0d1a44fa14 fix(cli): allow options after positional arguments (#5983) a339a87dde Fix @effect/workflow README: Use provideMerge instead of provide (#5975) c9dc711464 doc: Clean up ZIO refs in Stream and Channel docs. (#5961) f183370ffb Version Packages (#5959) c0a43bd145 Regenerate @effect/ai-google from latest OpenAPI spec (#5958) f28f353d5c Version Packages (#5950) 577f839e00 Ensure OpenAI headers are redacted (#5949) 856ec0a271 chore: update TSTyche to v6.0.0-beta.4 (#5498) 72c1f534b0 Version Packages (#5947) c5a1e332e0 Ensure client is defined when catching error (#5943) e88f289c1d Fix HttpRunner double-slash routing (#5946) 245c3e60aa Version Packages (#5945) f21f03477e Fix `Prompt.fromResponseParts` when input contains a provider executed tool (#5944) 9f74ba1404 Version Packages (#5925) ee69cd796f Fix HttpServerResponse.fromWeb losing Content-Type header (#5940) 72f61be280 Fix openai streaming decoding error when using provider webSearch tool (#5938) 65e9e35157 Document subtle CORS middleware allowedHeaders behavior (#5936) ba9e7908a8 Fix accumulation of response parts in `Chat.streamText` (#5931) 34fbbb18e3 Regenerate Open Router Schemas (#5928) 488d6e870e fix(effect): respect times: 0 in Effect.retry options (#5924) 431c9971c1 Version Packages (#5909) ff7053f6d8 support non-errors in HttpClient.retryTransient (#5917) 77eeb86ddf Add test for ensuring typeConstructor is attached (#5911) 287c32c9f1 Add typeConstructor annotation for Schema (#5910) 1b23741a3d handle executable paths with spaces in CLI arguments, close #5845 (#5853) fb78c4061c Version Packages (#5903) 03355c1470 Fix Tracer.currentOtelSpan to work with OTLP module (#5890) a0a84d8df0 add HttpApp.fromWebHandler (#5902) a6dfca93b6 fix: do not use `performance.now` if it's not available (#5897) e97675698c Version Packages (#5892) 65bff451fc add basic apis for converting to web Request/Response (#5896) 59760c1d10 fix: Fixed "oneOf" toolChoice for google language model (#5834) 5d7c9d8bb8 fix(ai-openrouter): Add support for google-gemini-v1 reasoning format (#5799) b1ffd223ab add cache point support for user and tool messages in bedrock (#5891) 590dbfc942 Version Packages (#5886) 38abd67998 filter non-JSON values from schema examples and defaults, closes #5884 (#5888) 44e0b04448 JSONSchema: add missing options for target JSON Schema version in make function, closes #5883 (#5885) 64b4dc40aa docs(platform): clarify FileSystem.watch recursive option behavior (#5792) b234af9973 Version Packages (#5877) 6c5c2ba50c fix(hash/equal): prevent defects with invalid date (#5878) d739bee38b Fix HttpApp stream flaky test (#5879) cc4d2c3821 ensure no more Activites are attempted before suspending (#5880) bd080284fe Fix NoSuchElementException instantiation in fastPath in FiberRuntime (#5874) 18783c9855 Version Packages (#5869) 3f9bbfe9ef Fix the arbitrary generator for BigDecimal to allow negative scales (#5875) 5be3b6add4 Widen otel logs peer dep range (#5863) ebe2e52784 Ensure Devtools Tracer does not cut off span options (#5864) cfdd411f2d Version Packages (#5848) 1ddd4e477a Update generated OpenAI schema definitions (#5850) 2dcbf98b0b Update generated OpenRouter definitions and fix logprob schema (#5849) 7c7d2e0491 add Workflow.scope, a seperate Scope that only closes on completion (#5846) 96c9537f73 ensure PersistedQueue memory driver removes items (#5847) 92f4840142 Version Packages (#5838) 811852a618 support idempotent offers to PersistedQueue (#5837) d7197889e0 Version Packages (#5836) 25d1cb60aa consider clean http interrupts (already responded) as successful responses (#5835) 0800e15c04 Version Packages (#5830) 7b2cd374cb reset redis staging area on a schedule for PersistedQueue (#5829) 40beb57ba6 Version Packages (#5828) 7bd4e827bc add TestRunner & SingleRunner modules (#5827) 1ebd493520 Version Packages (#5825) 019bf24f62 improve ordering guarantees for SqlPersistedQueue (#5824) 38f12729d3 Version Packages (#5822) ebfbbd62e1 support `HttpApiError` unification (#5818) 726ad2c90e propagate tracing info with DurableQueue (#5823) 2519056cb3 add DurableQueue module (#5821) dd4bb1bb35 Version Packages (#5819) c379c45777 ensure redis PersistedQueue removes stale pending items on reset (#5820) f03b8e55f1 fix:Arr.intersectionWith,differenceWith that Iterable [#5812](https://github.com/Effect-TS/effect/issues/5812) (#5815) 1074ba5ab2 Version Packages (#5817) a9206ceee6 add dynamic batch size to PersistedQueue based on pending takers (#5816) 49d15de7e0 Version Packages (#5814) 7ef13d3014 fix SqlPersistedQueue batch size (#5813) 678cb9189c Version Packages (#5810) 7b23d9a812 persisted queue (#5808) 4937bd8ce0 Version Packages (#5794) 0cf9e0bc78 Setup trusted publishing to NPM (#5802) df61f10013 Ensure new system prompt is created if none exists when appending / prepending (#5803) 8ebd29ec10 use original status code if headers have already been sent (#5797) a2d965d2a2 allow advisory locks to be disabled (#5793) 16e219bf46 Version Packages (#5788) 47bf3d2324 immediately set entity keep alive in EntityResource (#5785) cc54fa25f4 Version Packages (#5784) 8b879fb3b8 add EntityResource.makeK8sPod (#5783) 597eac0563 Version Packages (#5782) df69cb5ed5 provide seperate close scope to EntityResource via context (#5781) 0a1c842d29 Version Packages (#5779) af7916a3f0 add EntityResource module (#5778) bf883f4bf0 Version Packages (#5776) b92632ded7 ensure ClusterCron's can be resumed if re-added (#5775) 69e1389b3c Version Packages (#5774) 9283499a1c add RateLimiter helpers `makeWithRateLimiter` and `makeSleep` (#5773) 2367179499 Version Packages (#5770) 794c790d73 add in-memory WorkflowEngine layer (#5771) 079975c69d backport Effect.gen optimization (#5772) 62f76361ee make RpcClient socket retry schedule configurable (#5769) 26d1773ff7 Version Packages (#5766) f38c215f99 add entityRegistrationTimeout to ShardingConfig (#5765) 52753e2420 Version Packages (#5764) 1df657e468 fix RateLimiter fixed window alogrithm (#5763) 51bd337add Version Packages (#5762) e144f02c93 fix: scalar configuration and types (#5759) fe2792a670 Fix query result streaming in @effect/sql-pg (#5758) 1deeb6a58c fix partial window calulation for fixed window rate limiter (#5761) e95695057b Version Packages (#5753) d2b68ac9e1 add PartitionedSemaphore module (#5739) f445b87bab Fix Types.DeepMutable mapping over functions (#5752) 0fbd1c2c92 Version Packages (#5749) b93fc638da don't resume parent workflow when suspending on failure (#5748) 6a6cccdd0f Version Packages (#5745) 7f3c781c11 remove auto-resumption of child workflows (#5744) e92f67b09f Version Packages (#5743) 8c49696f37 don't try resume a child workflow if it has a defect (#5742) 89370e0ee5 increase HttpClient test timeouts (#5741) fcaeb2b842 make http client tests as flaky (#5740) f50e25bafc Version Packages (#5738) 2bb8242cb0 ensure HttpApiScalar source is tree-shakable (#5737) 60bba1c54d Version Packages (#5736) 973c90af48 handle undefined pg client (#5735) 23530f79cb convert bigints to string for otel (#5734) b44935d9dc Version Packages (#5732) 796a3b5aa3 add persisted RateLimiter to @effect/experimental (#5731) aae1dd4800 Version Packages (#5729) 1961185e50 Fix UrlParams.setAll overwrite semantics (#5728) c959a77edc Version Packages (#5727) 06b49b9b6a PgClient: fix regression on undefined config values (#5725) d34476eebf support and test against vitess for SqlRunnerStorage (#5726) 6c1aa11e6a Version Packages (#5718) 3689a183c5 re-use resume parent messages for child workflows (#5719) d277a32b6c improve pg client error handling (#5717) 7ce05ed4d9 Version Packages (#5713) 629b98d3b6 optimize Sharding shard release (#5716) b689450fc7 @effect/ai: link to website docs (#5711) fb533705e0 Fix Response.Part, AllParts, StreamPart not inferring Schema properly if toolkit is WithHandler (#5685) f8273c9de1 fix log level cli arg infinite loop (#5677) 7d28a908f9 Use standard formatting function in Config error messages, closes #5709 (#5712) 0d78615000 Version Packages (#5707) 84d22eb759 add timeout to pg query cancellation (#5705) 3b30752d89 Version Packages (#5704) 374f58c107 preserve Layer.mergeAll context order (#5703) f3c750f659 Version Packages (#5702) c00268d6ae add interruption support to sql-pg (#5701) dff094e2a5 Version Packages (#5700) 19e14ec890 add preemptiveShutdown option to ShardingConfig (#5698) db2ce87fab Version Packages (#5696) 63f2bf393e tie Entity lifetime to layer scope (#5695) da545cd6b9 Version Packages (#5694) c7e572c350 change workflow re-registration defect to a warning (#5693) 615fc6d2c7 Version Packages (#5687) c2133fad3a prevent interrupt suspends from propagating (#5691) 106abe0426 fix handling of multi-statement queries (#5690) 0d779286ee add Sharding shutdown log (#5689) 15100f6ed1 retry interrupted workflow activities (#5684) d43577be59 fix RpcServer.toLayer type (#5682) 571025ceaf allow any type to be used as sql parameters (#5686) 7c6dac4ad4 Version Packages (#5681) 3c15d5f99f `Array.window` signature has been improved (#5679) 24a1685c70 backport effect 4 cluster (#5646) 2a03c76c27 backport graph module changes (#5660) 3863fa89f6 Add Effect.fn.Return to allow typing returns on Effect.fn (#5631) 27863abed9 Add workflow type utils (#5490) 38e08c6f16 Version Packages (#5647) 2603745bad Expose disableFatalDefects option in RPC server public API (#5664) ca03e10265 README.md for HttpApiEndpoint: clarify header schema usage and add note on header normalization (#5668) 463345d734 add jsonSchema2020-12 target and fix tuple output (#5671) a5374696bd fix `Graph.neighbors` for undirected graphs (#5669) 979d1c10cd Support config.url in MysqlMigrator (#5632) 775e37e8ca Remove some leftover gen adapters (#5653) 52d59635f3 Make sure AsEffect is computed (#5628) 98270ac8bc Version Packages (#5645) 7de0bfc57f Ensure model field accepts a string in Anthropic schemas (#5644) f49bbc567a Version Packages (#5643) b8e3c6d510 fix ReferenceError in NodeSocket.fromNet (#5642) 8e71b72c40 Version Packages (#5641) 85ea731c83 use external interruption for workflow suspend (#5640) 810f35e3cc Version Packages (#5639) c11019ddff don't use removeAllListeners in NodeSocket.fromNet (#5638) 11e211b028 Version Packages (#5635) a5d0f2bd54 Ensure tool call parts are emitted immediately when streaming (#5634) b4487926ae Version Packages (#5623) 9ce78f4be8 Change AI packages to minor release (#5624) 4c3bdfbcbc remove either from tool call results (#5621) 92a4f3f4f2 Version Packages (#5619) d2140c1604 don't restart an entity during shutdown (#5618) bcef21f568 Version Packages (#5616) 6ae2f5da45 JSONSchema: Fix issue where invalid `default`s were included in the output (#5617) c63e658224 Allow user to control how tool call errors are handled (#5614) 1d2e92de9a Remove accidental commit of debug console.dir in ai-anthropic (#5615) c8fef45201 Version Packages (#5613) 25fab8147c Fix JSON Schema generation with `topLevelReferenceStrategy: "skip"`, closes #5611 (#5612) 29c6e03600 Version Packages (#5609) 12c3ce129f Fix node stat (#5595) 5c9f88f313 Version Packages (#5607) 215ed4642b Fix either detection for anthropic provider-defined tool results (#5608) 8ba47576c7 Fix Array Do documentation (#5598) e54f2dc612 Version Packages (#5605) 8048b44317 fix consistent fallthrough to error case (#5601) 57d4bcbafb Version Packages (#5604) 64b764b320 guard against race conditions in NodeSocketServer (#5602) cf6b56b631 Version Packages (#5600) d7eba97728 Ensure provider-defined tool results are properly added to input messages (#5599) 238c042cc0 Version Packages (#5591) f6987c04eb add additional predicate typings for HttpMiddleware.cors allowOrigins (#5588) f4c4702ab0 add openTimeout options to NodeSocket.makeNet (#5590) bc573e4570 Version Packages (#5589) d628c15278 Fix response schema when tool call resolution is disabled (#5587) 7e0d94c6a9 Version Packages (#5586) 07802f78fd Enable `console.group` in `Logger.prettyLogger` when using Bun (#5584) cf17f2f031 keep socket error listener attached in NodeSocket (#5585) 7b0047e697 Version Packages (#5583) f8b93ac644 feat: added allowed tools (#5377) 70fe803469 Automatically set otel parent when present as external span (#5433) 1c6ab74b31 feat: add Graph module with comprehensive graph data structure support (#5372) c60956e18f Adjust `xForwardedHeaders` middleware to always use `x-forwarded-for` (#5311) a098ddfc55 introduce ReadonlyTag as a Tag that can only be used to read a service from requirements (#5305) c296e32554 add Effect.Semaphore.resize (#5295) ba29c0ddd6 Version Packages (#5582) dd7b4591b7 persist activity interrupts as "Suspended" (#5581) 2dfe5e2ff9 Version Packages (#5578) c9e1e4064c ignore non-client interrupts in workflow activities (#5577) da96e7e0cf Version Packages (#5576) d0e97d74a9 fix SqlMessageStorage last reply for sqlite (#5575) 7508c6d277 Version Packages (#5572) 122aa53058 Filter to response status 200 in AI client stream requests (#5571) c6fae646b3 Version Packages (#5569) bc83015e25 Fix return type of `Response.AllParts` and redact headers in AiError (#5570) 6c2d586ae4 Support expiration of persisted chats via chat `timeToLive` (#5568) 27fb44cf61 Version Packages (#5567) 5b134824c1 Ensure decoded response metadata is always set (#5540) ab57b7af53 Add message identifiers to persisted chat options and fix Prompt construction from response parts (#5566) d67885cb94 Version Packages (#5564) fc7e32af79 prevent EntityNotAssigned when sending to local runner (#5563) 89a90cb969 Version Packages (#5562) 1a9e60163d optimize SqlShardStorage queries (#5561) 2e5cddf973 Version Packages (#5559) 800ab2e6d9 Enhance the prompt module and improve AI response errors (#5554) 761be65d24 Version Packages (#5558) 978b6ffc0b allow NodeSocket.makeNet open to be interrupted (#5557) de80e54403 Version Packages (#5556) aacd472a9b Add support for `Persistence` to the Effect AI SDK `Chat` module (#5555) cb8e9b0590 Version Packages (#5553) ffa494cbc3 allow predicates for HttpMiddleware.cors allowOrigins (#5552) 085f076bdc Version Packages (#5551) d4d86a81f0 remove msgpackr re-exports (#5549) d418ad0016 Version Packages (#5550) ae3854d06c optimize non-streaming response for http rpc protocol (#5548) e8bd5b86fe Version Packages (#5546) f71a7319c5 Hotfix/OpenAI tool result (#5545) 08ff12fb4b Version Packages (#5544) 2b9db9e7f9 propagate workflow interruption to unfinished children (#5543) 972f638ec8 Version Packages (#5534) 4f07ee06e5 backport MessageStorage.unregisterReplyHandler (#5541) 0457546129 Fix the instructions for calling an RPC in the RPC readme (#5536) ea95998de2 Preserve precision of histogram metric boundary values (#5533) 0c351bfe64 Version Packages (#5530) d9c8bce027 catch interruptions in Sharding storage loop (#5529) c8c7ecb873 Version Packages (#5528) e3dac9527a attempt to prevent InterruptedException leaks into entity replies (#5527) 3e6ebe9bce Version Packages (#5526) 7edc074f67 backport ClusterWorkflowEngine partial entity clients (#5525) 68815b86b4 Version Packages (#5522) 3128917009 Fix conversion of anyOf json schema to openapi schema (#5523) fa49bc86b1 Add an Effect AI SDK provider integration package for OpenRouter (#5521) d2e03fdf61 Version Packages (#5520) 92c533fb52 improve SqlMessageStorage insert queries (#5519) 59d52a3ea5 Version Packages (#5518) de07e58054 add onOpen option to Socket.run (#5517) 77f803eb24 Version Packages (#5514) 42b914a0e8 Refactor the Effect AI SDKs (#5469) faf6314e5b Version Packages (#5513) 934c1d9517 backport active shard latch changes from effect 4 (#5512) 780c4f086b Version Packages (#5511) c2fed22a13 re-add two phase runner health check (#5510) 9cf74ed893 Version Packages (#5506) 26d12678b0 ensure runner detects when it has been externally unregistered (#5508) 1c4b6ae81d ensure ShardManager and Runner get fresh versions of the rpc protocol (#5505) 4f79e585fe Rethrow original cause from makeRemoteCallback (#5503) aa0b4c8acd Version Packages (#5502) a874567c59 don't remove entities from map on shutdown (#5501) ec8a769c12 Version Packages (#5493) 0421c8ce2e provide http span to global middleware (#5492) cb3d996a56 Version Packages (#5489) 92fd3caac5 return executionId from Workflow.execute + discard (#5488) 0b472a3575 Version Packages (#5487) 85a60a0f06 add Workflow.poll api (#5486) 1a396024f7 Version Packages (#5483) f8b8d3db1a fix traceFlags propagation when set to 0 (#5485) 0a9ec23dca fix multiple persisted requests subscribing to the same id (#5484) 333be046b5 Add `suffix` option to `FileSystem.makeTempFile` (#5481) f60dce6b5f docs: fix JSDoc content for Predicate.isNumber to match the implementation (#5478) b07a6e99ef Version Packages (#5477) 18ec39853b fix: Fixes the total token count (#5476) ade0d8ff60 Version Packages (#5475) 5f5ae17305 Ensure finish part is emitted by anthropic when streaming (#5474) fc1f01f28d Version Packages (#5473) fb00cd6cf2 ensure defects don't cause loops in ClusterCron (#5472) 44bd4c5de2 Version Packages (#5471) e0d79c63c9 suspend workflow execution to ensure defects don't escape (#5470) 662b495f9f Version Packages (#5467) 75dffc877b ensure HttpApiClient adds encoding contentType to headers (#5466) cd6bc4540c Version Packages (#5463) 51bfc78a70 ensure tracerLogger does not drop message items (#5462) 00f4557b68 Version Packages (#5461) b359bdca4f add preload options to LayerMap (#5456) 5fa7404a37 Invalidate prepared-statement cache after loadExtension (Node) (#5459) 3ede18a20d Version Packages (#5454) d6887d54a6 wait for client to initialize before sending notifications (#5453) 4527ded2f2 Version Packages (#5452) fb5e414943 Simplify Effect.raceAll implementation, ensure children fibers are awaited (#5449) 018363b9cb Fix Predicate.isIterable to allow strings (#5451) d3910827aa Version Packages (#5448) e7aa8c2bf9 Ensure verbose transcription schema is tried before standard transcription schema (#5450) 8f8c4011f4 Propagate spanId and traceId to otel logs (#5444) b3cd3b9751 Version Packages (#5428) ccd655d218 Align `Either` type parameter naming with `Effect` (#5445) 0065a12bb8 Fix `InferenceConfiguration` schema in the Amazon Bedrock AI provider package (#5438) a33e49153d ensure Effect.promise captures span on defect (#5437) 5f796d96d4 add type level test for PR #5360 (#5435) 3b2609409a Refactor JSONSchema generator d0146151bc Version Packages (#5423) 3a8ba9b5e8 Ensure system content blocks are properly formatted for Amazon Bedrock AiLanguageModel (#5424) 0271f1450c backport `formatUnknown` from v4 (#5422) bddb3e98ce chore(test): remove `assertType` (#5420) 288cdd5c97 Version Packages (#5419) 7ad7b3c7de exclude layer services from HttpLayerRouter.toWebHandler request context (#5418) 169f06136a Version Packages (#5416) 4cb3af5aea fix rpc msgpack serialization when chunk contains partial frames (#5415) 15b14d7849 refactor: remove gen adapter `function* (_)` from remaining tests (#5411) 47e46ea504 Version Packages (#5408) 2965aa7cff Remove `@opentelemetry/semantic-conventions` from Effect-native OTLP modules (#5413) fef9771eab refactor: move `isQuitException` function to @effect/platform/Terminal (#5410) 031074df7b refactor: remove gen adapter `function* (_)` from sql-* tests (#5409) 8a3dc9ed7e refactor: remove gen adapter `function* (_)` from cli tests (#5406) 84bc3003b4 Fix Schema.Defect when seeing a null-prototype object (#5407) 1a6279c9b4 refactor: remove gen adapter `function* (_)` from platform-node-shared tests (#5405) 05d7f7313b Version Packages (#5403) 8c7bb52dc7 abort HttpClientResponse.stream regardless of how stream ends (#5402) 0e46e24c24 Remove dependency on `@opentelemetry/semantic-conventions` (#5397) ce9210f123 Version Packages (#5401) 0e296f532a CLI: Support default selection in Prompt.multiSelect (#5400) 1e09570ea6 Version Packages (#5395) 59547d94dc add DurableDeferred.withActivityAttempt, for scoping it to the current activity run (#5394) 6bc3f8090c Version Packages (#5392) 786867b1a4 support multiple HttpLayerRouter.addHttpApi (#5391) 1a21c6fa01 Version Packages (#5390) fa0fa16a19 fix: Correct error message for when the test was interrupted (#5386) 3e163b24cc expose clientId to rpc request handlers (#5389) 9ed188c79c Version Packages (#5384) 995199396e add setMany to ResultPersistence (#5385) 5c67e29945 Ensure user input is always offered to the terminal mailbox (#5383) f4a180470e Version Packages (#5381) 168987408d temporarily disable otlp exporter if endpoint can't be reached (#5380) 15f6e901d4 Version Packages (#5376) 43fc52eb85 improve: add flatten option to RpcTest.makeClient (#5375) 55d48a6264 add cors to HttpLayerRouter rpc example (#5370) 5a1d5757b6 Version Packages (#5369) 99302f4233 Add `HttpServerResponse.expireCookie` (#5357) e4914ff7dc Version Packages (#5366) b254747d47 only trigger Reactivity invalidation on successful mutation (#5365) 92a144b347 Version Packages (#5363) 4bcf799275 fix: Tool calls with no argumets get ignored (#5352) d484f21402 Version Packages (#5362) 292a7c5405 Add support for OpenAi GPT-5 options and `AiLanguageModel.embedMany` (#5361) 0e41608aa2 Version Packages (#5359) 0ff713fb2a Version Packages (#5353) 27a4e02852 `HttpServerResponse.redirect` defaults to 302 Found status (#5355) a94953971c improve RunnerHealth by detecting if Runner is connected to the host (#5358) fd86f93217 Respect `disableToolCallResolution` flag in stream call. (#5351) a0a38e5111 Version Packages (#5348) d0b5fd1f7a add Migrator.fromRecord changeset (#5350) 20f0d6978e update Cluster layer conditional storage types (#5347) b7b8a8ffec Version Packages (#5346) 58d56d549f increase size of entity_type on cluster_messages table (#5345) a8c314eba3 Version Packages (#5343) 1765ca8ddd add Reactivity.unsafeRegister (#5344) a9554ea53a add Reactivity accessor apis (#5342) f1ad6c5ffc add `stdin`, `stderr` and `stdout` to both `NodeSink` and `NodeStream` (#5339) 695bc1ab86 Improve child process cleanup with platform-specific termination (#5327) e14b4af15c Version Packages (#5337) 1628026081 remove info log from RpcServer stdio protocol (#5338) 767e49e384 fix: rpc group prefix (#5335) bc584dd2c7 Version Packages (#5332) 6df2620863 improve baseUrl handling in Otlp constructor (#5334) a656185b96 Update OpenAi client to work with "openai-compatible" providers (#5314) e6aef1a925 Version Packages (#5324) aed804e396 use Dispatcher.destroy() in undici finalizers (#5325) f187941946 non-greedy matching for Schema.String in Schema.TemplateLiteralParser (#5322) afd4ef9d48 Version Packages (#5316) 792e4cf049 fix Effect.gen compatibility with RpcMiddleware wrap option (#5319) 5f98388175 Improve provide/merge supporting readonly Layer arrays. (#5315) 1a6f4be013 Version Packages (#5310) 6e4d658bfa add http headers to rpc headers for http protocols (#5309) 58e5e9dd22 Version Packages (#5308) 7d7c55dade Update RcMap.ts (#5306) 1f028e5d28 locally provide EventLog service where possible (#5307) e2cb111370 Version Packages (#5300) 0138aa5fdf use base64 for EventLog.Identity strings (#5299) cfbcbacdba Version Packages (#5298) eedbab5a02 Remove retry from ClusterCron in favour of user supplied retry (#5297) bc27e42ff1 remove test cases covered by test matrix (#5296) 217fcc1c93 Version Packages (#5288) 35045558e7 Adds DST disambiguation support to the DateTime library and fixes an existing production bug with timezone conversion near DST transitions. (#5275) f6c7ca752f Improve inference of Metric.mapInput for use in Effect.pipe(...) (#5282) dfd80aa55f Version Packages (#5284) 491c7e614a fixed parentheses in `scripts/clean.mjs` (#5281) f359d11104 docs: fix typo in struct evolve (#5287) ffc17c6d12 add EventLog.layerIdentityKvs (#5286) 12abcbcca3 docs: fix cluster docgen links pointing to platform (#5285) de92b1c46a disable tracing for ClusterCron client to disconnect parent span (#5283) b9e475de2f Version Packages (#5278) 6309e0abe1 Fix Layer.mock dual detection (#5277) 6d7bfcafb0 Version Packages (#5272) c887902e29 feat: add resume endpoint to WorkflowProxy (#5265) 88e219f8a8 track the Cause for workflow SuspendOnFailure (#5271) 0ffbb40a6c Version Packages (#5263) e9cbd26734 add RpcClientError (#5264) 5a0f4f1766 remove recursion from Sink fold loop (#5262) 6c0af4cd81 Version Packages (#5261) 0b17343e55 don't capture non-suspended interrupts in Workflow.intoResult (#5260) 6950e0ed34 Version Packages (#5259) d070f2e7d5 fix: properly form otlp urls (#5257) 7813640279 Fix problematic prompt pasting (#5255) 9455bfbbe7 Version Packages (#5253) eb197b2cca Add "scale" option to OpenAI service tier (#5251) 4ed9603124 Version Packages (#5252) 7e1041599a Array: add findFirstWithIndex function (#5063) ed2c74ae8f feat: Add Struct.entries (#5237) 073a1b8be5 feat: backport Layer.mock from effect-smol (#5151) 40c3c875f7 add `Random` implementation with fixed values (#5134) 8d95eb0356 add Effect.ensure{Success,Error,Requirements}Type (#5109) e8c7ba5fd3 Remove global state index by version, make version mismatch a waning message (#5064) f382e99e40 Schedule output has been added into `CurrentIterationMetadata` (#5025) e9bdececdc Add HashMap.countBy (#4935) 7a0ae7b8a5 Version Packages (#5247) 5b74ea5e58 Clarify Tuple length requirements (#5158) aaa6ad0673 Copy over apply, bind, call into service proxy (#5246) 3f6f1f2084 Version Packages (#5245) 9dc9269261 Add error cause to the migration phase failure (#5238) bd184df039 fix: service test "promises" use case (#5236) f3c24d9274 Version Packages (#5241) e43b1fd9c4 expose the AiChat history Ref (#5240) 4e497e735d Version Packages (#5235) de513d9abb ensure duplicate paths are a defect in HttpApi (#5234) 0277d1eec2 Version Packages (#5233) 9ee1ad4450 Embed database config directly in mysqldump command (#5226) 4ffbd674af retry RunnerHealth ping to relax down conditions (#5232) cec0cebd47 Version Packages (#5228) c639456919 Update the generated OpenAPI specification for OpenAI (#5227) f9a41412d4 Version Packages (#5225) 127e602ee6 prevent fiber leak when Stream.toAsyncIterable returns early (#5224) 0e96d10490 Version Packages (#5223) 15df9bf0c7 Schema.attachPropertySignature: simplify signature and fix parameter type to use Schema instead of SchemaClass (#5222) 6e7a9f1161 Version Packages (#5220) 0dfc7246bd ensure rpc stdio protocol interrupts parent fiber when stdin ends (#5218) d720f10727 optimize experimental dataLoader (#5219) 6c88eb6895 Version Packages (#5214) 17a5ea8fa2 Updated deprecated OTel span attributes names and values (#5192) d0c20594f5 feat(Predicate): updated doc comments for clarity and examples in the Predicate module (#5094) d25f22be75 Removed some unnecessary single-arg pipe calls (#5211) f5dfabf51b Fix incorrect schema ID annotation in `Schema.lessThanOrEqualToDate`, closes #5212 (#5213) d0108cfa63 Version Packages (#5210) 3deaa66e02 fix ai layerConfig regression, to allow for conditional Config variables (#5209) 90ec865373 feat(@effect/vitest): modernize Vitest integration with AbortSignal support (#5205) e0176548ba Version Packages (#5207) dbabf5e76f consolidate Http web handler layer apis (#5208) 2297a8a114 use default execution in drizzle (#5125) 27206d7f05 lazily build HttpLayerRouter web handlers (#5206) f8542e29c5 Version Packages (#5198) 5b7cd923e7 ensure RpcMiddleware failures are reflected in RpcClient (#5204) 81fe4a2c81 Clarify Array rotate example (#5157) c1c05a8242 remove completion helper overload from Effect.catchTag, to fix Effect.fn inference (#5097) 8c878e3f0b Version Packages (#5197) 56b33c357c add discard methods to cluster EntityProxy's (#5196) 56f9639631 Version Packages (#5187) a28efb8913 add workflow SuspendOnFailure annotation (#5195) ad6e9688d7 suspend parent workflows when child workflow suspends (#5191) d5e25b237f feat(platform): add recursive option to FileSystem.watch (#5174) 5df2392721 fix handling of failed streams in NodeHttpServer (#5194) 32ba77ae30 fix(platform): fix KeyValueStore.make type mismatch (#5177) e5692ab2be Do not use config wrap helper for AI provider `layerConfig` (#5186) 6e39157fe2 Version Packages (#5182) 001392ba8b ensure HttpApiClient schemas are composed correctly (#5184) 79a1947359 allow RpcMessage spans to be missing (#5183) 7bfb099cb5 update find-my-way-ts (#5181) 993e488890 add npm version badge to README (#5178) 29f1f277e6 Version Packages (#5176) 678318d2e8 propagate headers to HttpServerResponse.raw(Response) (#5175) 85373f3578 Fix broken package links in README table (#5173) 08712dc429 Version Packages (#5171) 7c6e01daee add HttpLayerRouter.toWebHandler example to README (#5172) 54514a2f53 add HttpLayerRouter.toWebHandler (#5170) 6bcc9e005b Version Packages (#5167) 2baddac347 make cluster shard manager options consistent between platform (#5168) 4ce4f824f6 add global middleware to HttpLayerRouter (#5166) 358dcb83fe Version Packages (#5162) 25ca0cf141 Ensure tool call parts are properly merged when combining `AiResponse`s (#5165) d92d12acb6 Add Google Generative AI Provider (#5029) 03f4e8cdb2 load otel attributes from Config in Otlp modules (#5161) a6f7e47fe4 Version Packages (#5160) a9b617f125 Fix UrlParams.toRecord when there's a __proto__ key (#5153) 7e26e86524 add HttpLayerRouter.add & addAll apis (#5159) 6fab763b52 Version Packages (#5150) aaae9b1034 Support extracting tool call results from `AiResponse.WithToolCallResults` (#5133) 030ac217ea Support disabling tool call resolution in AiLanguageModel (#5154) 905da996aa Fix: disallow invalid _tag keys in TaggedEnum.$match handlers (#5149) e7cedb4a68 Version Packages (#5145) 96c1292628 simplify VariantSchema Class types (#5144) 2c15579efe Version Packages (#5143) 2fd8676c80 improve type safety of HttpLayerRouter.middleware error handling (#5142) 6fb7d921cf Version Packages (#5141) b01d2e0d59 remove dynamic cluster metrics that could cause memory leaks (#5140) 2a8a7a6a8f Version Packages (#5138) 7fdc16bd88 only register new runners if they are not present in the ShardManager state (#5139) e82a4fd60f move HttpLayerRouter request errors to Layer error channel (#5137) a597acfe02 fix race condition in HttpApp test (#5131) 16cd6da494 Version Packages (#5130) 1b6e396d69 attach http request scope to stream lifetime for stream responses (#5128) 46c321657d relax SqlSchema/Resolver.void return type constraint to unknown (#5129) b49d23ad43 Version Packages (#5124) b9279543cf add HttpLayerRouter module (#5117) 4fea68ca7a `HttpRouter.mountApp` prefix matching fixed (#5111) 99590a6ca9 fix DateTime zone check to includes zones without ":" (#5127) 6c3e24c230 Schema.equivalence: handle non-array and non-record inputs (#5123) aef71b3aab Version Packages (#5122) 8e3c565aad Fix generated OpenAPI types for Anthropic AI provider (#5121) a79f6a8bd6 Version Packages (#5119) 530aa6561b Add Amazon Bedrock AI provider (#5020) 3fe93c56f6 update @effect/language-service (#5116) 0e4693f84f chore: upgrade TSTyche (#5106) 25441ec40b Version Packages (#5103) c1a9c9d0c3 add option to exclude log spans from OtlpLogger (#5107) faad30ec87 relax Predicate.compose constraint on second refinement (#5100) 23495453a7 Version Packages (#5099) 112a93a9ba add flatten options to RpcClient constructors (#5098) 94bc85d4d8 docs: remove unused import in HttpServerRequest example (#5092) a9d17be31f Version Packages (#5090) d5fd2c1526 fix unwrapping of DurableDeferred results (#5091) 9d189d744a propagate fiber refs to workflow activities (#5089) f667373da3 expose system option in AiChat constructors (#5088) 262c04a42e Version Packages (#5085) 867919c8be allow for namespacing rpcs using a `group.method` convention (#5086) b5bac9ac29 add HttpApiClient.makeWith, for supporting passing in HttpClient with errors and requirements (#5087) f90813f757 correctly propagate fs.watch errors when the stat fails (#5084) 6d1aef52e3 Version Packages (#5083) 5137c70346 allow Multipart configuration in HttpApiSchema.Multipart (#5081) c23d25c3e7 fix Effect.filterOrFail return type inference (#5082) bb77c2eab1 increase timeout for jekyll build (#5073) a8453a9e6b Version Packages (#5071) ff90206fc5 use FIFO queue for shard assignment, to reduce churn (#5070) f325caa3e0 fix cron tests (#5065) 79f49fd3a5 Version Packages (#5057) a8d99b2ec2 add support for mcp 2025-06-18 (#5056) 0ec20120da Version Packages (#5054) 914a191e7c fix retrieval of HttpApiSchema.param annotations (#5053) 67dc27031d Version Packages (#5052) 0945c0d0a2 Fix the generated Anthropic OpenAPI schemas (#5051) 9500339c32 Version Packages (#5050) ddfd1e43db fix RpcClient payload for tagged requests (#5049) f2981c0520 Version Packages (#5048) 8cb98d53e6 ensure Stream.toReadableStream ignores empty chunks (#5047) db2dd3c3a8 ignore ReadableStream defect in bun due to controller bug (#5046) 6b13a4953f Version Packages (#5043) b7cc5c7b6b simplify and increase rebalance rate for ShardManager (#5044) 93687ddbb2 HttpApiBuilder handler improvements (#5042) 2be881b78b Version Packages (#5041) daed158f2c allow undefined mcp payloads (#5040) b8bdb89621 Version Packages (#5039) c315989cad remove McpServer requirement from McpServer.resource (#5038) f3523c65c2 Version Packages (#5034) dd4d3802f7 eliminate McpServer requirement from resource layers (#5037) cbac1ac61a add .of helpers to RpcGroup, Entity and AiToolkit (#5036) 1bb0d8ab96 ensure DateTime.make interprets strings without zone as UTC (#5033) 92704eb954 Version Packages (#5028) bf369b2902 allow property signatures in HttpApiSchema.param (#5032) a02470c755 fix `Context.add` `Context.make` fns (#5031) a5f75956ef add missing type variances in `Match.ValueMatcher` (#5026) aa3a819707 Introduce a native McpServer implementation for Effect (#4961) f891d45adf ensure __proto__ as name argument in do notation is preserved on lexical scope (#5003) efed2381a1 Version Packages (#5027) ee3a1973f5 add jsonrpc serialization to rpc (#4623) a07dab258e Version Packages (#5024) e0d3d424d8 add spanAttributes option to RpcServer constructors (#5023) 8de8070917 Version Packages (#5022) dca92fd8cf add disableFatalDefects option to cluster entities (#4750) ba63604445 Version Packages (#5019) d3501768d4 prevent shadowing of Workflow context (#5018) cc283b9682 add more spans to ClusterWorkflowEngine (#5017) b0f77d628b Version Packages (#5015) 6e2e886f06 fix ShardManager metrics (#5016) bf418ef14a Minor updates to `Record.findFirst` (#5008) 0e545f8bf4 Version Packages (#5012) 7bf6cb9438 rpc error middleware & ping timeouts (#5014) 2dc5f932f8 Disallow excess options in `AiLanguageModel` methods (#5011) b04d025e23 Version Packages (#5010) 2a9a0ef118 remove Workflow.Registration type (#5009) 56b090e7e4 Version Packages (#5000) 22166f80c6 make registerEntity a no-op on clientOnly cluster (#4999) db892c3a33 Version Packages (#4997) f8ff7dccfe expose MessageStorage in cluster clientOnly layers (#4998) 8b9db77428 allow literals in HttpApiSchema.param (#4996) c9f5cd047f Version Packages (#4993) 34333ab08d add Activity.raceAll (#4995) 74ab9a0a9e don't inherit interruption flag in Effect.addFinalizer (#4994) 770008eca3 ensure Cause.YieldableError extends Error (#4986) bda9ebd07a Version Packages (#4985) 7e59d0e2e0 expose Sharding.pollStorage api (#4984) e69b860c36 Version Packages (#4979) 4ccfe44391 Update LSP to 0.18.0 (#4980) 59575c5bf1 do not resume already running workflows (#4983) a2d57c9ac5 Fix Machine initialize R argument (#4978) c2529ff791 Version Packages (#4976) d244b6345e add EntityProxy, EntityProxyServer, WorkflowProxy, WorkflowProxyServer (#4977) ceea77a130 allow wrapping a web Response with HttpServerResponse.raw on some platforms (#4975) b97928d40a Version Packages (#4974) 612c73979a ignore runners that cannot be parsed (#4973) 9290e6943a Version Packages (#4965) ccead2cf65 Enable flaky Metrics histogram test (#4970) d0067caef0 optimize for requests with a WithExit reply (#4972) 3086405630 add shard groups, to allow entities to target specific runners (#4969) 85f54ed1ec Fix the type of `AiToolkit.Any` (#4968) 71e1e6c535 fix RpcClient error types when using discard (#4967) 8c79abeb47 add ClusterCron module (#4966) 2f39f7631a chore: upgrade TSTyche (#4925) ec52c6a221 ensure HttpApi security middleware cache is not shared (#4964) da5ecf594a Version Packages (#4962) 4ddb28d230 improve @effect/ai llm schema compatibility (#4959) dd2b909fed Version Packages (#4956) 6dfbae946e ensure Workflow.interrupt triggers compensation (#4958) 700162d293 Support typed cause for Workflow withCompensation (#4957) b8aec45288 add Workflow.withCompensation (#4955) 8de094a625 Version Packages (#4954) fd60c73ea6 add Activity.onError (#4953) 177373f254 Version Packages (#4951) 87722fce69 improve Effect.catchTag auto-complete (#4952) a116aeade9 add @effect/workflow (#4945) 36217eeb13 remove `this` type propagation from Effect.fn (#4950) 7ef53aad26 Version Packages (#4946) 5b3c6dbe51 fix array buffer slice in HttpApiClient (#4948) 52c88c4b7d fix AiToolkit handlers type extraction (#4947) ab7684f1c2 decode HttpApiClient response from ArrayBuffer (#4941) af51043a8c Version Packages (#4940) 0ddf148a24 relax `Schema.brand` constraint, closes #4942 (#4943) a77afb1f71 allow Rpc to be extended to create opaque types (#4939) ca5674a5f6 Version Packages (#4938) d615e6e5b9 adjust ExecutionPlan `provides` & `requirements` types (#4937) 71174d0969 Escape JSON Schema $id for empty struct (#4936) 6866230b31 Version Packages (#4934) 51898004e1 Adds `HashMap.hasBy` (#4915) 055267461a Refactor `AiModel` and remove `AiPlan` (#4918) 5c806af1cd fix auto-completion for ExecutionPlan.make (#4930) 194d748694 add ExecutionPlan module (#4910) 58bfeaa64d Add `round` and `sumAll` functions to `BigDecimal` (#4920) 9198e6fcc1 feat: Function Parameters for Effect.Service (#4911) 58ccb91328 `Config.port` and `Config.branded` functions have been added (#4892) 918c9ea1a5 Add Array.removeOption and Chunk.removeOption (#4861) 2a370bf625 Add `Array.countBy` (#4864) fd47834820 return a proxy Layer from LayerMap service (#4898) ee0bd5d248 Add `Schedule.CurrentIterationMetadata` Reference (#4828) 60ddff06c3 Version Packages (#4933) cc5bb2b918 `Effect.fn`: fix type inference for `Effect` subtypes (#4924) 55225206ab refactor PlatformError and make it a schema (#4932) 7ae795d9d7 Version Packages (#4928) 58c5fd3dd3 disable tracing for internal sql queries (#4929) 0617b9dc36 fix package internal imports (#4927) 8a8cbcc49a Version Packages (#4919) c20b95a99f update /platform dependencies (#4921) 78047e8dfc Fix `Effect.fn` inference in case of use with pipe functions (#4917) 94ada43092 Fix missing encoding of path parameters in HttpApiClient (#4916) f570554595 fix(Metric): summary min max values when no samples (#4869) 45bd8af519 Version Packages (#4913) eef605278e Fix issue with parsing file input parts in OpenAI provider (#4914) 618903ba9a Secure Swagger UI with safe serialization and escaping (#4855) 502735889a Version Packages (#4908) 7764a07d96 add HttpClient.withScope, for tying the lifetime of the request to a Scope (#4912) 30a0d9cb51 add HttpClientRequest.toUrl (#4909) 4577f548d6 Escape JSON-pointers in JSONSchema.make (#4907) 877ed37cf2 Version Packages (#4900) 89657ac2fb Fix sql transforms when executing multiple statements (#4903) d13b68e3a9 `Url.setPassword` supports `Redacted` values (#4890) 1627a0299a add Entity.makeTestClient, for testing entity handlers (#4906) 0a8c0e762a fix: anthropic tag name (#4899) d45e8a8ac8 Handle `Respondable` defects in `toResponseOrElseDefect` (#4896) d3b201f0b5 chore: upgrade TSTyche (#4877) 1424a34209 Version Packages (#4884) 93a911ce19 update dev dependencies (#4885) a328f4b4fe add HttpBody.formDataRecord (#4889) eaf8405ab9 add EntityNotAssignedToRunner error (#4883) b8722b817e More type-safe`HashMap.has/get` (#4659) 34cde2980d Version Packages (#4879) 739a3d4a45 remove content headers for FormData bodies (#4882) b86c1fc63b Ensure that multiple tool calls are processed properly for OpenAi (#4880) 8b32eb5f12 Ensure that the finish reason is set properly (#4878) ce26a81646 Version Packages (#4871) 1269641a99 preserve name & `this` in Effect.fn apis (#4857) 787ce7042e ensure generic refinements work with Effect.filterOr* (#4870) f833ea353b Version Packages (#4867) cb3c30f540 preserve tool call results in AiResponse.merge (#4866) 64954405eb ensure socket close events are not errors in RpcServer (#4860) ba7d7e0f56 upgrade TSTyche (v4 beta) (#4745) bef1d199fd Version Packages (#4859) fa10f56b96 Support multiple values in Function.apply (#4705) 59d24d09a4 Support Drizzle Config (#4850) 8421e6e493 Expose `isTimeoutException` (#4846) c5e76b833a Update sql-drizzle (#4840) 49aa7236a1 Add Either.transposeMapOption (#4741) 1dcfd41ff9 improve Effect.filter* types to exclude candidates in fallback functions (#4814) b21ab16b6f Add `Pipeable.Class` api (#4669) 0061dd1407 allow catching multiple different tags in Effect.catchTag (#4796) d9f5deae0f `Brand.unbranded` getter (#4788) e4f49b6685 Default never type for `MutableHasMap.empty & MutableList.empty` (#4768) f2dcdb37ab Add configuration options to the PgDrizzle layer (#4759) fcf1822f98 Add property `message: string` to `ConfigError.And` & `Or` (#4743) c65459587b Add Layer.setRandom, for over-riding the default Random service (#4694) a9b3fb78ab Allow removing multiple Headers (#4627) 6f02224b3f add Stream.toAsyncIterableRuntime api (#4680) 74c14d01d0 feat: add Record.findFirst (#4500) b8cdaaa286 Version Packages (#4849) cd6cd0eacd add apis for implementing and accessing single rpc handlers (#4858) a4d42c5566 Refactor the AI packages (#4766) a15d95cb17 Version Packages (#4848) 24a9ebbb5a Schema: TaggedError no longer crashes when the `message` field is explicitly defined (#4847) 85fd4454f0 Version Packages (#4843) 672920f85d allow overriding http span names (#4842) dae3c78961 Version Packages (#4838) 2f3b7d4e1f Fix Mailbox.fromStream (#4837) 4be592b679 Version Packages (#4833) 17e2f30914 JSONSchema: respect annotations on declarations (#4832) 6ecad8a809 Version Packages (#4830) 114dad9a93 Fix `StreamAdapter` error forwarding (#4827) 2d55bc52c5 sending persisted messages without MessageStorage is now a defect (#4829) 6e918f416b Version Packages (#4826) 1b30f616e7 ensure clientOnly mode without storage falls back to network messaging (#4825) b93c54c28a Version Packages (#4817) 056a910d0a Fix `Layer.discard` jsdoc (#4822) 146af39d8d fix sqlite SqlMessageStorage.repliesFor (#4823) e25e7bbc17 Fix `causeResponse` to always return `internalServerError` for defects (#4821) 3273d572c2 Fix captureStackTrace (#4816) 3cb5627de5 fix link to `FiberRef.currentMinimumLogLevel` (#4811) b1f30b43d9 Version Packages (#4810) b1164d49a1 fix refinement narrowing in Match (#4809) e3a207cbd4 Version Packages (#4804) b5b7da3199 add a shutdownTimeout option to the @effect/opentelemetry exporters (#4808) 0b54681cd8 Match the JS API for locale arguments (#4806) c0ed246656 ignore `scratchpad` in changeset config (#4807) 41a59d5916 implement stack cleaning for bun (#4805) 578e953e9d kill path aliases, use src exports (#4778) 5eaf79e1ae Version Packages (#4802) f1c8583f8c expose uninteruptible option to HttpApiBuilder .handle apis (#4803) ee14444102 improve Match refinement resolution (#4800) 1092dde02e Version Packages (#4795) 239cc995ce Schema: respect custom constructors in `make` for `Schema.Class`, closes #4797 (#4798) 8b6c947eaa Make `Effect.liftPredicate` and `Either.liftPredicate` reusable (#4687) c50a63bbec fix(Metric): summary quantile value when overshoots (#4794) 054f5b71ab Version Packages (#4792) a7ef0e6579 add excludeTestServices option to it.layer (#4791) e2b06d881b Update eslint-plugin and enable no-import-from-barrel rule (#4787) 72d6970a53 chore: tweak `HttpApiEndpoint` type test (#4723) 1785cd1fa6 Version Packages (#4780) 6ed8d1589b drop use of performance.timeOrigin in clock (#4786) 664293f975 fix module import (#4779) 36b33cd4a9 Version Packages (#4776) 5fce6ba19c Fix `JSONSchema.make` for `Exit` schemas (#4701) ee77788747 JSONSchema: apply `encodeOption` to each example and retain successful results (#4777) 570e45f8cb JSONSchema: preserve original key name when using `fromKey` followed by `annotations`, closes #4774 (#4775) 632eec8d04 Version Packages (#4773) d6e115617f Allow rpc middleware to wrap execution (#4764) 082e615749 include error cause in otel exception.stacktrace (#4772) 2c66c16375 add option to RpcClient socket protocol to retry transient errors (#4771) deb8d736a7 Version Packages (#4765) c2ad9ee9f3 Fixes a bug where non existing properties were allowed in the `make` constructor of a `Schema.Class`, closes #4767 (#4770) 9c686542b6 Add documentation to `Number` module. (#4735) e1b8f7efbb Version Packages (#4757) b5ad11e511 add child_process workers to platform-node (#4758) 43b22df02f disable tracer for Otlp http client (#4760) e536127c1e allow Pool to acquire multiple items at once (#4756) c63d57eb9a Version Packages (#4755) d3df84e8af fix RpcClient worker protocol scopes (#4754) 1f705234b8 Version Packages (#4749) bc7efa3b03 preserve refinement types in Match.when (#4748) b74b84fcb2 Version Packages (#4738) e857e9ad5d Fix/bug platform permission (#4747) b7a64c90bb add Effect native OtlpMetrics & OtlpLogger (#4740) a676ddcdd3 fix catchAllCause jsdoc example (#4744) d78249f0b6 Allow Match.typeTags to specify a return type (#4734) ea8f0b5efa make OtlpTracer example more portable (don't use platform-node) (#4737) b6068a4b5e Version Packages (#4733) 049f0ec78e include cause in otel exceptions (#4732) d8b50bcb06 Version Packages (#4731) d36ccc0c3c adjust otel export when max batch size is reached (#4730) fc0c0b1d8b Version Packages (#4728) b7b4c9f31c remove requirement of @opentelemetry libraries in OtlpTracer (#4729) b8b070382b add AiModels service to AnthropicClient.layerConfig (#4726) 58eaca9ef1 Update RPC README.md (#4725) 46a64bd30b add effect native otlp exporter (#4727) b70724993a Version Packages (#4720) a79b732bdd ensure more persisted messages are eligible for the fast path (#4719) 43815686c5 Version Packages (#4715) 6966708a30 reset shard last_read on acquistion (#4714) ff2ecbef82 Version Packages (#4713) da21953a38 attempt to use network for persisted messages where possible (#4712) f28cfdb92b Version Packages (#4711) b3a2d32772 Make Match.valueTags dual (#4708) 896fbbf6ed tighten cluster health checks to improve worst case recovery time (#4710) 9da76f2f0f Version Packages (#4707) f3674777fc update otel peer dependencies to ^2.0.0 (#4696) b542a4bf19 fix(Metric): summary quantile values (#4706) 4edb176995 Version Packages (#4704) a1d4673a42 improve RpcServer schema defects (#4703) 4a687e8dbe Version Packages (#4698) 60778824a4 Fix JSONSchema generation for record values that include `undefined`, closes #4697 (#4699) 47618c1ad8 Add docs to `effect/MutableHashSet` module (#4674) 3985abe3d5 fix rpc README server example (#4693) 90483fbb69 Re-export `default` from Undici (#4688) 123adebccc Version Packages (#4691) 44140423a2 fix assignability in RpcGroup.merge (#4690) 8453857bb6 Version Packages (#4684) 868d5c247b fix(platform-node): Handle empty chunks in streams (#4653) d46d2c6f40 Add manual `gpt-4.5-preview` fix for `CreateChatCompletionResponse` schema (#4638) e1dfdf123e fix(sql-kysely): prevent non-object proxy creation error (#4661) 85fba815ac Pass options to HttpServerResponse.raw (#4642) 3ed0b56ae6 Handle arbitrary length StreamChunkPart (#4630) 5a5ebdddfa ensure Equal considers URL by value (#4682) 40dbfeff23 allow Effect.fnUntraced to return non-effects (#4676) 8883c83b5e Version Packages (#4673) 26c060c659 remove trailing slash from rpc http requests (#4678) e3e5873f30 allow RpcGroup.merge to accept multiple groups (#4677) e4ba2c66a8 docs(effect/HashSet): fix `HashSet.md` api docs. (#4667) a10d396740 Version Packages (#4665) 0ec5e0353a ensure RpcClient does not miss chunks (#4672) 05c4d772ac ensure RpcClient fiber is defined before polling (#4671) 34f03d6687 fix Data.TaggedEnum with generics regression (#4670) 37aa8e1377 Add JSDocs to `effect/HashSet` module (#4664) cd1f034823 chore: remove unused expression from Either tests (#4657) b886c6cc31 docs: fix layer referenced in `RpcSerialization` jsdocs (#4652) f293e97ab2 Enhance documentation for `dual` function, closes #4654 (#4655) 76b4b43225 fixed typo in jsdocs (#4649) f0c5a2405a Version Packages (#4647) 0a3e3e18ee Schema: `standardSchemaV1` now includes the schema, closes #4494 (#4648) f87991b6d8 Arbitrary: fix bug where annotations were ignored (#4646) 6e69dcb60c Version Packages (#4640) d2f11e557d ensure rpc does not flatten Effect sub-types (#4639) d70b39cb5f Version Packages (#4635) 18a7936832 fix clientOnly mode for SocketRunner (#4637) 168e388825 ensure upgrade socket is closed on http response finish (#4634) 95717ce825 check for codegen changes (#4633) a443f70477 re-run codegen (#4632) 3603051678 fix tsconfig.test.json (#4631) 4c1bf892b1 Update README.md (#4628) 9a88bb9404 Version Packages (#4625) 3a99a2dbaa add Sharding.activeEntityCount (#4626) 9517601586 Update version of `gpt-tokenizer` used by `@effect/ai-openai` to latest (#4624) d31f4099f2 Version Packages (#4622) 4a274fe9f6 remove Context.ValidTagsById usage (#4620) 814733fe62 remove Sharding.make export (#4621) 312f4891eb Version Packages (#4619) f67c2faeae ensure persisted messages are sent when no assignments (#4618) b40af943a3 remove old examples from rpc README 666364e965 remove merge markers from rpc README 3131f8fd12 refactor of @effect/cluster packages (#4497) ab957c1fee Make TestClock.setTime accept a DateTime.Input (#4512) aba2d1d831 @effect/rpc 3rd revision (#4362) 26dd75f276 add LayerMap module (#4441) 35db9ce228 Add Effect.transposeMapOption (#4597) 1f47e4e125 Add DateTime.nowAsDate (#4511) c7fac0cd7e Add `Hashmap.every` (#4513) 04dff2d01a add Tracer Span.addLinks, for dynamically linking spans (#4465) 4191918a27 fix assert in Array.window example cf77ea9ab4 feat: add Array.window (#4477) ffaa3f3969 Add `Either.transposeOption` (#4466) baaab60b73 Make runtime run sync dual (#4374) 27e56d8353 Version Packages (#4612) 5f1fd15308 Url improvements (#4615) 8bb1460c82 HttpApiEndpoint: add missing `head` and `options` constructors, closes #4613 (#4614) 566236361e update ai Generated schemas (#4592) 596e051b0c Version Packages (#4608) 0c4803fcc6 Preserve specific annotations when using `Schema.typeSchema`, closes #4609 (#4610) 6f65ac4eac Add support for `jsonSchema` annotations on `SymbolFromSelf` index si… (#4607) 36cf6f3fd0 Version Packages (#4588) 4296293049 Fix issue with generic filters when generating arbitraries, closes #4605 (#4606) be12983bc7 Add support for refinements to `Schema.omit`, closes #4603 (#4604) fad8cca9bb Schema: enhance the internal `formatUnknown` function to handle vario… (#4601) 2e4277e0c8 Refactor jsdocs (Array, Effect modules + typeclass package) (#4599) 46f847993c Update docgen to latest version (#4598) 389dca2c59 Update README examples using @effect/rpc-http/HttpRpcRouter (#4594) de88127a5a Schema: export `Field` type (#4593) 082b0c1b9f Improve clarity of `TimeoutException` error message (#4591) 1b41820deb Schema: export `IndexSignature.Type` type (#4590) 9c241abe47 Schema: simplify `Struct` and `Record` return types (#4587) edd9ba7629 fix: unnecessary `as` type cohercion in `Either.fromNullable` (#4586) cfbf1b59b0 Version Packages (#4584) e5b16fd5c4 Skip flaky test for mutually suspended schemas (#4585) 527c964522 Allow `toString` Method to Be Overridden in Schema Classes, closes #4577 (#4578) ba50b5c964 Version Packages (#4581) d630249426 support Layer.launch when using WorkerRunner (#4583) 88fe129237 remove Scope from HttpClient requirements (#4573) 4d4781efde adjust worker runner code formatting (#4582) 2976e52538 fix: `Match.tags` throwing on `undefined` input (#4579) bbdc2795a4 prevent worker handler interrupts from shutting down runner (#4580) fe7da20315 Update @effect/docgen and fix code examples (#4575) a2361c6632 fix: unnecessary `as` in `all` function in the Effect `Either` module (#4574) 8ae7827c7e fix(schema): `isEncodeException` jsdoc (#4571) d047181485 Update @effect/docgen to latest experimental version (#4570) 4e0efa1fa3 Update docgen to latest experimental version (#4569) 031f9a8ad6 Version Packages (#4566) c65d3362d0 Add ParseOptions argument to standardSchemaV1 (#4567) 22d2ebb4b1 ParseResult.ArrayFormatter: correct `_tag` fields for `Refinement` an… (#4565) a55835a5c5 Version Packages (#4561) c5bcf53b7c expose ParseError in HttpApiClient (#4562) 6cd4a102f5 ensure empty Chunk's don't cause NodeStream.toReadable to hang (#4560) f7eae9a6c9 Version Packages (#4553) f910880690 add additional properties options to HttpApiBuilder.middlewareOpenApi (#4559) 05306d5cc5 refactor NodeStream.toReadable (#4558) 5ac4278091 Update platform README to link runMain docs to website (#4557) 840cc73299 Add `additionalPropertiesStrategy` option to `OpenApi.fromApi`, close… (#4540) 87ba23c41c ConfigProvider: `fromEnv`: add missing `Partial` modifier (#4554) 99fcbf712d Bug fix: change interruptible order in platform shutdown flow (#4539) 0d014803e4 Fetch http client merge headers (#4493) a95108acac Avoid inclusion of `_tag` in tool call parameters (#4545) 9bf8a74b96 disallow excess properties for various function options (#4541) fcc9e3be44 Version Packages (#4552) 3154ce4692 Arbitrary: `make` called on `Schema.Class` now respects property anno… (#4551) 3676392852 Version Packages (#4535) 3d6d323c2a Ensure `AiPlan` builder returns the correct shape (#4549) 532a8f7bf2 Update README.md (#4547) a0acec851f Schema: add support for Transformation + Struct in `extend`, closes #4536 (#4546) 6cf11c3a75 Preserve branded primitive types in `DeepMutable` transformation, clo… (#4543) 975c20e446 Allow defects to pass through checks in `AiPlan` (#4537) 367bb35f4c Match.tag + Match.withReturnType can use literals without as const (#4530) 0c0d163660 Version Packages (#4529) 17d9e89f98 Schema: Export `MakeOptions` type, closes #4532 (#4533) e5fbfd49f0 chore: notify on failed TypeScript's nightly type test runs (#4528) e0746f9aa3 Fix w3c traceparent header parsing (#4525) 5db2b3772d Version Packages (#4474) a67a8a1a49 Introduce `AiModel` and `AiModel.Plan` (#4504) ecedb3d64e chore: test types against TypeScript’s nightly releases (#4515) 2251b15288 Schema: More Accurate Return Type for `parseNumber` (#4524) ce059f2747 Schema: Cleanup `as any` usage and add `Tuple2` API interface (#4523) 62ad0b09d8 Schema: fix Datetime transformations (#4521) c12b1860e7 Schema: fix wrong `EitherFromUnion` return type (#4520) 10932cbf58 Schema: Use `additionalProperties` instead of `patternProperties` for… (#4519) 8b6a49e082 Schema: improve type definitions for TaggedClass, TaggedError, and Ta… (#4517) 5c2ad19792 Schema: improve `ArrayEnsure` and `NonEmptyArrayEnsure` return types (#4516) a4979db021 Schema: add missing `from` property to `brand` interface (#4514) 623c8cd053 Schema: More Accurate Return Type for `compose` (#4510) fb798eb906 Schema: More Accurate Return Types (#4509) f2aee989b0 Schema: More Accurate Return Types for `ArrayEnsure` and `NonEmptyArr… (#4508) 477b488284 Schema: More Accurate Return Type for `parseJson(schema)` (#4507) 3c4ac4f93a Schema: Fix Constraint Definitions for `DataFromSelf` and `Data` (#4506) cc5588df07 Schema: More Accurate Return Types for `DataFromSelf` and `Data` (#4502) 9f6c78468b Schema: Add Missing `declare` API Interface to Expose Type Parameters (#4501) f1408dc983 chore: simplify `test-types` script (#4498) 494266202b Schema: Remove Redundant `annotations` Typings from `Union` Interface (#4499) b74255a304 ensure fibers can't be added to Fiber{Handle,Set,Map} during closing (#4496) 9dd8979e94 Simplify signatures in `Array` module (#4495) 886aaa81e0 Schema: Improve `Literal` return type — now returns `SchemaClass` ins… (#4492) 00b4eb1ece Schema: more precise return types when filters are involved (#4488) 2c639ecee3 Schema: more precise return types when transformations are involved (#4487) 8366ced746 chore: add `references` for type test projects (#4485) 2e15c1e336 fix nested batching (#4483) 7828638432 chore: add missing type assertions (#4481) d3f3abd887 Mark `Option.filterMap` as an alias of `flatMap` (#4475) 7e523187c6 Switch from `@effect/dtslint` to `tstyche` for type-level testing (#4467) d7f6a5c7d2 Fix `Context.Tag` unification (#4419) 780dec2c18 suppress warning for experimental features (#4470) d63a5c40e3 remove duplicate code in stm (#4459) 58c55a090e Version Packages (#4457) 31be72ada1 Add support for `Schema.Enums` in `HttpApiBuilder.isSingleStringType`, closes #4471 (#4472) d7ad83da85 fix vitest v3 deprecation warnings (#4468) aa39549a09 updated vitest to v3 (#4376) ba60870497 fix `DateTime` utc tests (#4460) 0638b8cc68 fix jsdoc codemod (#4463) 2a4b5720b8 fix import statement (#4464) 10641cd082 update pnpm and node version (#4462) 9b9699fe61 fix `Duration.unsafeFormatIso` docs (#4461) 3e7ce97f8a ensure key for header security is lower case (#4456) c8279316b9 Version Packages (#4455) 9375c28ca8 Add initial anthropic ai provider integration package (#4446) b56a211056 Fix Option `filterMap` example (#4454) a585aa56a9 Version Packages (#4450) 546a492e60 Add HashMap.toValues and HashSet.toValues (#4317) bf865e5833 allow accessing args in Effect.fn pipe (#4417) de8ce92492 Add Layer.updateService mirroring Effect.updateService (#4421) 5fc8a90ba4 Add missing `Either.void` constructor (#4413) 9760fdc37b Schema: Add `standardSchemaV1` API to Generate a Standard Schema (#4359) 5b471e7d43 add ISO8601 duration formatting (#4343) c874a2e4b1 Add `HashMap.some` (#4347) 8baef83e7f add Promise based apis to Fiber{Handle,Set,Map} modules (#4401) 6862444094 Make it easy to convert a DateTime.Zoned to a DateTime.Utc (#4375) cf8b2dd112 Relax `Trie` variance (#4338) 9c9654e281 fix some tests using old assert d90cbc274e Add `Effect.whenLogLevel` (#4342) db426a5fb4 `Differ` implements `Pipeable` (#4239) 65c47966ce add {FiberHandle,FiberSet,FiberMap}.awaitEmpty apis (#4337) 4f810cc277 add Effect.filterEffect* apis (#4335) 655bfe29e4 Add `Effect.transposeOption`, closes #3142 (#4284) 6806c0e19f reduce churn of RcMap idle timeout fiber (#4282) 75632bd44b add RcMap.touch, for reseting the idle timeout for an item (#4281) f98b2b7592 add RcMap.invalidate api, for removing a resource from an RcMap (#4278) 9324b18be1 Version Packages (#4434) f70a65ac80 Preserve function `length` property in `Effect.fn` / `Effect.fnUntrac… (#4447) c407726f79 HttpApiBuilder: URL parameters are now automatically converted to arr… (#4444) ba409f69c4 Fix Context.Tag inference using explicit generics (#4422) 9dc15d8789 Refactor README files (#4427) 4018eaed27 Schema: add missing support for tuple annotations in `TaggedRequest` (#4440) 543d36d1a1 Schedule: fix unsafe `tapOutput` signature (#4439) 5808fbc70b Schedule: add JSDocs (#4438) 3d2e3565e8 use Map for Scope finalizers, to ensure they are always added (#4432) a2e27e718f Match module: add jsdocs, type-level tests, 100% coverage (#4423) 7b5a76e96c Version Packages (#4424) 3ffe06df87 ensure last port in browser worker closes naturally with the outer scope (#4433) b6a032f07b ensure Channel executor catches defects in doneHalt (#4430) 2473ad5cf2 run platform workers in a Scope, send errors or termination to a CloseLatch (#4429) 42ddd5f144 Schema: add missing `description` annotation to `BooleanFromString` (#4426) 2fe447c635 Update `forEach` function in `Chunk` to include missing index paramet… (#4404) 03c048ced2 Version Packages (#4420) cdc82e4ae8 Support per-request `HttpClient` transformations in the OpenAI AI integration package (#4418) b8ff1e69e8 Version Packages (#4399) 7d57ecdaf5 add HttpServerResponse.mergeCookies (#4416) 74d9365340 fix fast-check imports (#4415) f76abdc55e internal: simplify formatters implementation (#4414) 87f5f2842e Enhance `TagClass` and `ReferenceClass` to enforce `key` type narrowi… (#4411) 3e56745421 Fix CLI -h to display default values (#4400) (#4402) e30f132c33 Fix STM unification (#4412) 58a6f9d7b4 Send `SELECT 1` as unprepared statements (#4410) 2b37d8efff remove unnecessary type assertion in `Effect.forEach` definition (#4407) 82518c13ad internal: replace `eitherOrUndefined` API usage with `isEither` in `P… (#4406) 33fa667c26 Duration: fix `format` output when the input is zero (#4403) 57d1623d78 Add `LoggerProvider` support to `@effect/opentelemetry` (#4371) 4dbd170538 Make Array.makeBy dual (#4397) fa8a02941e Schema: add additional tests for effectful transformations (#4398) 81dd6a5d01 mark Micro test as flaky (with retry option) (#4396) e18685d815 update schema benchmarks (#4395) 238d638b30 Version Packages (#4393) 1b4a4e904e Fix internal import in Schema.ts, closes #4391 (#4392) 096d254bfe Version Packages (#4322) f0894708e9 Add GenAI telemetry annotations to AI chat completions (#4389) f269122508 A more precise signature has been applied for `Effect.schedule` (#4387) fcf3b7cc07 Rename `correlationId` to `toolCallId` for AI structured outputs (#4388) f5e3b1bcdf Support non-identified schemas in `Completions.structured` and `AiChat.structured` (#4378) a0c85e6601 Add `Embeddings` service to the AI integration packages (#4368) 5a9d78542d [internal] improve assertions (#4386) 0275faa38f Schema: improve `handleForbidden` implementation with `SyncScheduler` (#4385) 5426b2db32 test: add assertions for Option and Cause using node:assert, vitest, … (#4384) c9175aef41 Platform: add `sync` (fsync) (#4345) e374d94778 fix effect vitest layer (#4382) fd92e694ca Add missing `listen` example for Bun (#4383) dedadf1659 tests: use `assertIncludes` instead of `assertTrue` where applicable (#4381) c45b5592b5 fix imports (#4380) 8c23f5ad60 rename to `vitest.setup.ts` (#4379) ae8518c980 refactor platform tests (#4377) d1a5b43aca Tests: remove deprecated adapter, closes #3072 (#4372) aa26d9b446 Simplify `internal/cause.ts` by using the internal `Option.mergeWith` (#4369) 72f09dc58b Refactor spanSymbol usage to ensure consistent internal access (#4367) c180a67ea6 internal: add a few 4.0 todos (#4366) 207491b208 internal: add SchemaTest utils (#4365) 766113c0ea Fix handling of hrtime in `Duration.decode` (#4341) fe0e4e8b3d remove regex index reset in `Duration.decode` (#4348) 6f1c3d7b45 Enhance test reliability by using `node:assert` (#4350) bb05fb8345 Ensure nested configs can be loaded from env file (#4360) 7437058771 Refactor zod comparison and add URLs section, closes #4346 (#4358) 7b03057507 Expose `Schedule.isSchedule` (#4344) 62934fc61a optimize streaming response for NodeHttpServer (#4354) 8f6006a610 fix HttpServerRequest.arrayBuffer for bun & web handlers (#4353) 9172efba98 optimize Stream.toReadableStream (#4352) 430c846cba fix Layer.scope types to correctly use the Scope tag identifier (#4351) 502223adf1 Cause: add jsdocs and tests (#4340) 59b3cfbbd5 Cookies: `unsafeMakeCookie` and `unsafeSetAll` now throw a more infor… (#4334) 6811009944 Fix the RPC readme (#4336) ff529da0a7 Semaphore take fifo comment (#4325) 712277f949 Cron: `unsafeParse` now throws a more informative error instead of a … (#4333) e59a3546c3 Enhance JSDoc comments in the Option module (#4332) 107e6f0557 Schema: Improve encoding in `Defect` and add test for array-based def… (#4331) e386d2f1b3 Add missing overload for `Option.as` (#4330) 65c11b9266 Schema: Update `itemsCount` to allow `0` as a valid argument, closes … (#4329) 55147c5ad1 Add HRTime schema for backward compatibility with Duration (#4327) 602939adbb Simplify gen implementations in Either and Option (#4324) 00f9a476da Mark "mutually suspended schemas" test as flaky with retry option (#4323) a9c94c8077 Schema: Fix `Duration` Encoding to Support All Duration Types (#4313) f523f1a752 Version Packages (#4321) 8dff1d1bff [Fix] Cannot find name 'MissingSelfGeneric'. (#4320) aba2e3a81a Version Packages (#4288) fc5e0f0d35 improve UnknownException error messages (#4292) 5c5612fa94 Allow configuring OpenAI API URL and API key (#4316) 8b4e75d35d Added type-level validation for the `Effect.Service` (#4298) b2a31be85c Duration: make `DurationValue` properties readonly (#4314) 4136318eaa add missing Duration tests (#4318) a1fd4d345d chore: remove numRuns option from Arbitrary test (#4315) 3b19bcfd3a fix "ParseError" conflict between `Cron` & `Schema` (#4228) c662fa33d8 Schema: fix `DurationFromMillis` jsdoc (#4310) 004fd2bbd1 Schema: Enforce Finite Durations in `DurationFromNanos` (#4309) 3191fa37cb Schema: Simplify DurationFromSelf Arbitrary by removing redundant dur… (#4308) 289c13b38e Schema: Add Support for Infinity in Duration (#4307) 5e43ce50ba eliminate Scope by default in some layer apis (#4306) 76eb7d0fbc ensure toWebHandler context argument is a Context before using it (#4304) bf5f0ae9da Schema: Fix `BigIntFromNumber` to enforce upper and lower bounds (#4301) f474678bf1 Schema: update `pluck` type signature to respect optional fields (#4300) 5b9bec656a Schema: refactor tests (#4299) ee187d0980 fix: update `Cause.isCause` type from 'never' to 'unknown' (#4296) bec5ea459a docs: improve documentation for Effect functions to clarify error han… (#4295) eb264ed8a6 Fix following relative locations (#4286) b064b3b293 ensure cause is rendered in FiberFailure (#4294) 68b1c70123 docs: enhance documentation for LogLevel.locally function with detail… (#4293) 7b228c05d7 Update CODEOWNERS to include schema-related modules and add any missi… (#4289) 5514d05b5c Array: Fix `Either` import and correct `partition` example. (#4287) ce0e614e43 internal: remove deprecated adapter in src folders (#4285) 8704e55043 Version Packages (#4258) dd7724a394 refactor Chunk.toArray tests for clarity and coverage (#4283) d9a63d9d38 improve performance of Effect.forkIn (#4279) 86530720d7 simplify HttpApi path regex for parameters (#4277) bd0d4892b0 add experimental EventLog modules (#3978) 84a0911816 fix formatting of time zone offsets that round to 60 minutes (#4276) 86457544ed Ensure `ConfigError` is surfaced by fallback `Config` (#4275) a8b0ddb847 Schema: Extend Support for Array filters, closes #4269 (#4273) fcf8edc003 Update pages workflow to use non-deprecated actions (#4274) 3179a9f65d ensure DateTime.Zoned produces valid dates (#4267) 1fcbe55345 Schema: Replace the `TimeZoneFromSelf` interface with a class definit… (#4266) 6cb9b76639 Add support for symbols in the `Issue` definition within `platform/HttpApiError` (#4264) a4a5b87baf Schema: update Symbol encoding to use Symbol.keyFor (#4262) fb0ad5ab09 Schema: fix Symbol identifier annotation (#4261) 24f21c0a09 Schema: update error messages and annotations for JSON transformations (#4260) 507d546bd4 Schema: improve error messages for invalid transformations (#4259) 8db239b9c8 Schema: Correct `BigInt` and `BigIntFromNumber` identifier annotation… (#4257) 662e9fef40 Version Packages (#4256) 5b50ea4a10 [bugfix] `Layer.retry` and `MetricPolling.retry` signatures (#4231) c9e5e1be17 Add isHttpMethod refinement (#4250) bcc600b5e3 ensure vitest .layer api uses TestServices for provided layer (#4254) c170a68b62 Use non-enumerable properties for mutable fields of `DateTime` objects (#4253) a66c2eb473 Improve DateTime type preservation (#4255) 7b3d58d7ae Add HttpClient.tapError (#4225) ff7c07d2fc Version Packages (#4238) 1d7fd2b7ee Allow `Schema.TemplateLiteral` to handle strings with linebreaks, clo… (#4252) c110032322 Update `HttpApi` to remove wildcard support for better OpenAPI compat… (#4245) ca07bd6cbb Add runMain docs (#4249) d0a9da261c docs: add OpenApiJsonSchema module documentation and examples (#4247) 765d41b62b Url module: add tests and docs (#4246) d7dac48a47 Improve pattern handling by merging multiple patterns into a union, c… (#4244) b514200998 fix it.prop type signature (#4227) d05d663e6f feat: add it.*.fails method to @effect/vitest (#3973) 8cd7319b65 `Url` module (#4177) c1a0339034 fix parcel watcher exports (#4242) b9210c3802 Refactor `dtslint/Unify.ts` to utilize `hole` (#4241) 23ac740c7d Make OpenApiSpec mutable to make handling it more convenient (#4237) 766e8526b1 Version Packages (#4213) 0def0887cf fix color option for Logger.prettyLogger (#4236) b78880619f chore: centralize subroutines in `fromApi` (#4235) f852cb0204 Deduplicate errors in `OpenApi.fromApi` (#4234) 7276ae2106 Ensure the encoding kind of error responses is respected in the OpenA… (#4233) ea77cca4cc Use `fast-check` re-export (#4232) 212e78475f Platform: add OpenAPI.fromApi tests + docs refactoring (#4226) 734af82138 fix inference for contravariant type-parameters (#4220) c640d77b33 fix: ManagedRuntime.Context to work when Context is of type never, cl… (#4219) 3adf5bca9a update babel-plugin-annotate-pure-calls (#4217) da58a6d9cf Schema: move type-level fields (e.g., Type, Encoded) in the Schema im… (#4216) 17b515844a Fixed type-error in @effect/cli README.md (#4209) 90fc632f46 fix BigDecimal.unsafeFromNumber jsdoc (#4214) b63c780108 Refine `Effect.validateAll` return type to use `NonEmptyArray` for errors (#4212) 60620418eb Version Packages (#4191) 5351dee0bb Refactor platform README (HttpApi docs) and fix examples code (#4211) fda8ef777a Logger: eliminate duplicate code in stringLogger and logFmtLogger imp… (#4199) e61f38c5d2 Effect jsdocs: minor adjustments to interruption APIs (#4208) d5008a99b9 update internal namespace import convention from `_` to `_` (#4207) d4a0ee8b21 update docgen to latest (#4206) cf81b00528 chore: simplify OptionFromNonEmptyTrimmedString implementation (#4205) 914e396442 Effect module: more jsdocs (#4204) 62cfcd8dcf fix(docs): sql insert resolver (#4203) 0988083d45 Remove internal EffectError make sure errors are raised with Effect.fail in Effect.try (#4202) 8b46be6a3b incorrect type declaration in LibsqlClient.layer (#4185) 43267412c1 add missing internal tag (#4198) 39db211414 Avoid putting symbols in global to fix incompatibility with Temporal Sandbox. (#4196) 302b57d2cb [bugfix] `concurrentFinalizers` in `Effect.all` implementation (#4194) 16dd657033 extend IterableIterator instead of Generator (#4190) bfe802734b ensure Effect.timeoutTo sleep is interrupted (#4189) df82e92d08 Version Packages (#4184) c9926402a4 fix vitest .prop with objects ef64c6fec0 remove generics from HttpClient tag service (#4169) e1eeb2d706 Carry both call-site and definition site in Effect.fn, auto-trace to anon (#4155) 73f9c6f2ff add `DateTimeUtcFromDate` schema (#3956) c11f3a60a0 Add Context.mergeAll (#4135) 618f7e092a add span annotation to disable propagation to the tracer (#4123) abb22a429b add UriComponent schemas and encoding (#3982) d80182060c added `Cron.unsafeParse` and allow passing the `tz` parameter as `string` (#4106) 17cb451459 add second granularity to Cron (#4088) 3d2b7a7e94 Allow passing fast-check parameters to prop function (#4099) c0ba834d19 add Schema.headNonEmpty on Schema.NonEmptyArray (#3983) 642376c63f add non-traced overload to Effect.fn (#4077) f369a89e98 feat: add runtime type extractor (#4020) fb44b251b1 Version Packages (#4178) 62f12d8244 Support native arbitraries in @effect/vitest (#4173) a1ca621a2c Schema: chore, remove duplicate code (#4183) b3c160d7a1 Replace absolute imports with relative ones (#4182) a475cc25fd Schema: Fix `withDecodingDefault` implementation to align with its si… (#4181) d8151f7233 Schema: Fix bug where calling `withDecodingDefault` after `withConstr… (#4180) 199214e21c Schema: refactor annotations (#4175) 39457d4897 Fix Stream.scoped example (#4176) 0c91dd6d30 Version Packages (#4164) 1ce703b041 Add support for `TemplateLiteral` parameters in `TemplateLiteral`, cl… (#4174) 8f81334332 remove release-queue concurrency (#4172) c23a6d1e74 change release-queue git_user (#4171) 36028580ea add concurrency cancel-in-progress to release-queue job (#4170) 28de34a9f8 Use Redacted values with libSQL (#4112) 1b72560828 Fix dtslint clean command (#4167) 396b1ae284 Effect module: add more jsdocs (#4165) 1c08a0b850 Support template literals in Schema.Config (#4113) e151ad37c7 Version Packages (#4158) 4686ed7e20 use PAT for release-queue checkout (#4162) 763832b6c2 use Effect Bot as release-queue git user (#4161) 228cffcaf6 use PAT for pr checkout (#4160) 28b15ee6dc use PAT for release-queue jobs (#4159) 05d71f8562 Support URL objects in HttpServerResponse.redirect (#4154) e66b9205f2 ensure WebSocket's are always closed with an explicit code (#4157) 9b8e49c4cb Effect module: add more jsdocs (#4156) 9ce1aae8c6 Version Packages (#4151) 011e2b604e upgrade undici to 7.x and expose re-exports in Undici module (#4149) 909181a9ce add HttpServerResponse.redirect api (#4152) 1a6b52dcf0 Arbitrary: optimize date-based refinements (#4150) 34b23b612b Version Packages (#4146) 6984508c87 ensure HttpApi union schemas don't transfer non-api related annotations (#4147) 883639cc8c ensure HttpApi preserves referential equality of error schemas (#4145) 9e40ecebee Version Packages (#4144) f7f2dd6794 add timeout to sql connection attempts (#4143) 889b57520b Version Packages (#4142) b7a0a87827 add @effect/sql-sqlite-do for cloudflare durable object sqlite (#4141) dcfe1304be Version Packages (#4140) 1237ae847f fix json schema output for Ai completions (#4139) 42940f3d53 Version Packages (#4134) cec0b4d152 JSONSchema: handle the `nullable` keyword for OpenAPI target, closes #4075 (#4138) 24086163b6 Arbitrary: fix bug related to refinements of declarations raising a w… (#4137) 8d978c53f6 add `type` for homogeneous enum schemas, closes #4127 (#4128) 0159e95e71 Version Packages (#4133) 1d3df5bc43 add context argument to HttpApp web handlers (#4132) f8b1112368 Version Packages (#4119) 11fc401f43 add predefined empty errors to HttpApiError (#4130) c99a0f376d replace HttpApi.empty with HttpApi.make(identifier) (#4129) 31c62d83cb Rewrite the Arbitrary compiler from scratch, closes #2312 (#4126) 662d1ce6fb Allow the transformation created by the Class API to be annotated on … (#4118) c70cf4ed03 Version Packages (#4100) 03bb00faa7 avoid using non-namespaced "async" internally (#4115) ef70ffc417 ensure Reactivity events aren't missed (#4114) 619b6d925d Refactor `Clipboard` module with `TypeId` and custom error (#4105) 1e59e4fd77 remove FnEffect type to improve return type of Effect.fn (#4107) 22905cf5ad JSONSchema: merge refinement fragments instead of just overwriting them (#4111) 8adcd7e767 JSONSchema: improve internal `pruneUndefinedFromPropertySignature` to… (#4109) 8d914e504e JSONSchema: represent `never` as `{ enum: [] }` (#4108) c2e9a88a51 JSONSchema: fix since tag of fromAST API (#4102) 14e1149f1a Schema: align the `make` constructor of structs with the behavior of … (#4101) 9f5a6f701b Fix issue where `identifier` is ignored, closes #4012 (#4019) 05a258ee56 Version Packages (#4098) 9a5b8e36d1 handle WebSocket's that emit ArrayBuffer instead of Uint8Array (#4097) b5ef12cfd5 Version Packages (#4086) 9cd92b06bd fix remoteAddress changeset (#4096) 733f8be16d Allow use of the URL object with libSQL (#4082) e9936278d9 Add the missing dependency on @effect/experimental to @effect/sql-X (#4090) 518b258a8a remove socket write indirection (#4087) 76b59960a2 Implement `remoteAddress` for `@effect/platform-bun` (#4095) 415f4c9832 http api param inherits description from schema #4046 (#4091) 6e323a36fa Optimize cron (#4010) b1a7685127 Update absurd function documentation (#4085) e93109a162 Version Packages (#4078) e23d77156e Fix `TemplateLiteral` and `TemplateLiteralParser` encoded type infere… (#4084) 343b6aa6ac Preserve `MissingMessageAnnotation`s on property signature declaratio… (#4083) afba339adc Fix the behavior of `Schema.TemplateLiteralParser` when the arguments… (#4081) 90906f7f15 Fix the `Schema.TemplateLiteral` output type when the arguments inclu… (#4080) 3862cd3c7f SchemaAST: fix `TemplateLiteral` model (#4076) 0015724b86 Version Packages (#4069) da3a607bea use OpenAi response_format for structured completions (#4071) ac662f4464 ensure opentelemetry tags have unique identifiers (#4070) 70447306be ensure the uniqueness of the parameters at the type level (#4065) ec6cb5daa8 Add the missing TypeScript fences to the recent examples. (#4066) 5a701a3547 Version Packages (#4061) c2249ea13f HttpApi OpenApi adjustments (#4064) 01cee560b5 Micro adjustments (#4063) 1de3fe7d1c simplify HttpApiClient param regex (#4062) 1358aa5326 Allow creating a route for all methods (#4055) 6e8e48572f align Micro jsdocs with latest implementation (#4060) 0f581e2320 Version Packages (#4053) a71bfef46f Ensure current time zone tag type is properly exported (#4059) 77ae93849b fix Stream.partitionEither example (#4057) dd8a2d8e80 ensure pool.get is interrupted on shutdown (#4052) 87ab6a131d Version Packages (#4050) f47d935722 update HttpApiEndpoint.ValidateUrlParams to allow arrays (#4049) e0b9b09e70 Implement Effect.fn to define traced functions (#3938) 5eff3f6fa3 fix multipart support for bun http server (#4028) 672bde5bec support array of values in /platform url param schemas (#4015) 9264162a82 Inherit child fibers created by merged streams (#3997) 147434b03d Preserve scopes in stream operations (#3989) d9fe79bb5a remove Env, EnvRef & FiberFlags from Micro (#3969) 6e69493004 add Context.Reference - a Tag with a default value (#3961) 1e2747c63a "format" property in `JSONSchema` and `OpenApiSpec` (#3945) b4ce4ea7fd New methods `extractAll` and `extractSchema` to `UrlParams` (added `Schema.BooleanFromString`) (#3900) 15fcc5a0ea Add support for timezones in Cron (#3943) aadb8a48d2 Add scientific notation for BigDecimal formatting and improve handling of numbers (#3911) 9bc9a47680 `URL` and `URLFromSelf` schemas (#3889) 5a259f3711 switch from CPS to fiber runtime for Micro (#3860) c36f3b95df made `Config.redacted` more general (#3873) 251d189420 `Config.url` constructor (#3834) 312fb7e948 Version Packages (#4043) 6de9ff01b9 add error handling to Inspectable.toJSON (#4048) 3069614927 catch logger defects from calling .toJSON on data types (#4042) 09a5e522fd fix docs for Stream.partition (#4041) 9d7ae53925 Version Packages (#4036) e6d4a37c1d add template literal api for defining HttpApiEndpoint path schema (#4035) c907ac048d Version Packages (#4030) 5e1740017c Avoid "Cannot access 'ParentSpan' before initialization" error during… (#4029) f7986460d7 Version Packages (#4026) 270f199b31 update OpenApi version to 3.1.0 (#4025) cfb5ccc5f4 Version Packages (#4023) 24cc35e26d improve HttpApi handling of payload encoding types (#4024) e9dfea3f39 add .reactive method to SqlClient interface (#4021) 1b1ba099bc allow cloning a SqlClient with transforms disabled (#4022) 753d120544 Version Packages (#4008) edd72be57b try encode defects that match an Error schema in HttpApi (#4017) a9e00e43f0 allow using HttpApiSchema.Multipart in a union (#4016) a3e277170a consider TimeoutException a transient error in HttpClient.retryTransient (#4014) 54addee438 Remove reference to non-existent function (#4013) b8eb7b3fd0 remove @types/node resolutions (#4009) 944025bc5c Wrap JSDoc @example tags with a TypeScript fence, closes #4002 (#4007) 24b4dba413 Version Packages (#4006) beaccae2d1 fix HttpApiBuilder.middleware when used multiple times (#4005) af409cf1d2 fix behaviour of Stream.partition to match the types (#4004) 9139df943f Version Packages (#3993) c963886d58 `HttpApiClient.group` & `HttpApiClient.endpoint` (#3977) 6c858a6509 Add JSDocs to Effect module (#3979) 42c4ce6f8d ensure fiber observers are cleared after exit to prevent memory leaks (#3998) a4bc0859f7 Add utility for generating JSDoc @example blocks (#3994) d85a42f3d8 switch to @types/bun (#3996) d5e1afd0cb upgrade to typescript 5.7 (and latest dtslint) (#3990) a1cf616768 Fix multi-select prompt not displaying descriptions (#3992) 3686ff644f Layer.ts jsdoc - grammar fix (#3985) 340b7a8e79 Version Packages (#3960) 1d998671be Apply stable filters only on `Composite` failures, closes #3980 (#3981) d8356aad42 Refactor JSON Schema Generation to Include Transformation Annotations… (#3971) a36a25069f Ensure command-line arguments are validated for prompt-based commands (#3976) 6dae414799 add support for 0 capacity to Mailbox (#3972) 4dca30cfcd Use `AutoTitleAnnotationId` to add automatic titles to avoid interfering with user-defined titles (#3918) 320557ab18 `OpenApi.Transform` annotation (#3968) 71f7d4ecb9 Prompt multi select (#3929) 7b93dd622e Fix `HttpApiGroup.addError` signature (#3962) 6b0d737078 Remove `Omit` from `Class` to align type signatures with runtime beha… (#3959) 1b5219cbe2 docs: fix incorrect usage of PgClient.layer in sql README (#3954) 6da6284ae5 Version Packages (#3948) 52c4b89da6 add transformClient option to OpenAiClient layer (#3949) 3cc6514d2d `HttpApi` annotation with additional schemas (#3939) c138fa7ec6 Version Packages (#3940) 3aff4d3883 `OpenApi.Summary` & `OpenApi.Deprecated` annotations (#3944) 7807f502a6 use wa-sqlite for sqlite-wasm package (#3934) 7484ca1718 fix vitest skipping example (#3946) c4e2bfd3eb remove @effect/platform from Permissions changeset (#3941) 18bdbacfcd Added `Permissions` module to `@effect/platform-browser` (#3902) 0184a996cb Version Packages (#3926) 8398b32082 allow DateTime.makeZoned to default to the local time zone (#3936) 4e9e256e9f add Reactivity module to experimental (#3937) c8d20b0827 Remove `eslint-plugin-deprecation` (#3933) 72e55b7c61 allow Stream.split to use refinement for better type inference (#3917) 1eb63d4e8d Version Packages (#3921) 2d8a75081e ensure a ManagedRuntime can be built synchronously (#3924) f9839467b4 remove redundant check in JSONNumber declaration (#3920) b27f3c2531 Version Packages (#3910) 72b02726d6 use effect/JSONSchema for effect/ai & allow http client transforms (#3916) 995bbdffea Schema: BigDecimal Arbitrary restricted to the range of 0-18 (#3907) 7868f31584 Version Packages (#3909) eb8c52d8b4 use plain js data structures for HttpApi properties (#3908) 5392047b33 Version Packages (#3905) a0584ece92 ensure Socket send queue is not ended (#3906) dd14efe0ac improve platform/Worker shutdown and logging (#3904) a9ac08fde2 Version Packages (#3899) 5eef4998b6 add HttpApi.addHttpApi method, for merging two HttpApi instances (#3903) 8240b1c10d Fix broken basic auth base64 decoding (#3898) 6c0ff9d214 chore: fix NumberFromString category (#3901) a2bd4dfa3d fix: NodeWorker not responding to interruption. (#3897) 63dee92689 Version Packages (#3895) 12b32753af simplify DevTool/Server module (#3896) 3ff8e5b413 remove debug logging from NodeSocket (#3894) cbd1bc5a2b Version Packages (#3891) cd720aedf7 support "dropping" & "sliding" strategies in Mailbox (#3893) 7d8965036c simplify HttpApiClient implementation (#3892) b631f40abb Avoid creation of subarray view in Base64.decode (#3886) 8a435ff048 Version Packages (#3887) 8a30e1dfa3 simplify HttpApiBuilder handler logic (#3885) f7e4f21e4e Version Packages (#3877) a123e80f11 add FromIterator primitive to improve Effect.gen performance (#3883) 07c493a598 add withResponse option to HttpApiClient methods (#3884) 257ab1b539 simplify Socket internal code (#3882) 0289d3b639 implement Effect.suspend using OP_COMMIT (#3881) bd5fcd3e6b refactor Effect.gen to improve performance (#3880) 4211a2355b Return sequential cause from `Effect.acquireUseRelease` (#3879) 7386b710e5 fix the type signature of `use` in Effect.Service (#3862) a503f8c7d5 Version Packages (#3871) 10ac592ce0 set clickhouse changeset to patch (#3876) 98ab8cd489 Adds `withClickhouseSettings` to `@effect/sql-clickhouse` (#3859) 68b5c9e44f move _op check out of the fiber hot path (#3868) 6f8682184a fix DevTools error logging (#3875) c348b4abf0 Fix WebSdk.layer not infering properly due to R and A type args being swapped (#3874) 6306e66560 fix `Config.integer` & `Config.number` (#3872) 361c7f39a2 Add jsdoc-examples for class-based APIs (#3869) 9c9928dfea R/use literal key (#3849) 2f569a68e3 Version Packages (#3866) 33f5b9ffae ensure Channel.mergeWith fibers can be interrupted (#3867) 50f0281b0d fix memory leak in Stream.retry (#3865) 62d365d93d Version Packages (#3857) ce1c21ffc1 fix `Tag.Proxy` types (#3858) 81ddd45fc0 Integration with Scalar (#3856) 3db5a97b31 Version Packages (#3846) 54284ea895 increase timeout for libsql tests (#3855) 3a6d757bad Support union of parameters in functions in `Effect.Tag.Proxy` type (#3841) b521577501 update OpenAI schemas (#3854) 70dd4d7cd2 use `layer` / `layerConfig` convention for sql-* packages (#3852) 68e7aa2450 add "Zod enums" example/comparison, closes #3847 (#3850) 59d813aa49 ensure fiber refs are not inherited by ManagedRuntime (#3845) c24466605c Version Packages (#3843) 0de2ebff5a fix since tags (#3844) 2367708be4 add support for `Schema.OptionFromUndefinedOr` in JSON Schema generat… (#3842) 29fc33f33c Add getSemigroupUnion, getMonoidUnion and getSemigroupIntersection for Record (#3840) 4c965f3496 Version Packages (#3838) 522f7c518a eliminate HttpApiEndpoint context in .handle (#3837) d58bd18bf0 Version Packages (#3832) 690d6c54d2 add HttpApiBuilder.handler, for defining a single handler (#3836) b9423d8bf8 Ensure undefined JSON values are not coerced to empty string (#3833) 279fe3a716 ensure parent annotations take precedence over surrogate annotations (#3831) 02b09e3c8b Version Packages (#3821) 42cd72a44c ensure headers are redacted when inspecting http requests & responses (#3826) c5d537fa9c add more type level tests for Types.MergeRight (#3828) c1afd55c54 Make `MergeRight`, `MergeLeft` and `MergeRecord` homomorphic (#3825) 11ec8965d1 add JSDocs to GlobalValue module (#3827) 3f797813b7 chore: remove "@effect/schema" leftovers (#3822) 714e11945e simplify Match fail keys types (#3820) d2fc79a474 Version Packages (#3819) 9604d6b616 wait for connection in DevTools client with 1s timeout (#3818) 251509d62b Version Packages (#3817) c86b1d7cd4 allow Request.signal to be missing in .toWebHandler apis (#3816) afb4e43bc5 Version Packages (#3812) e0e504ce40 fix race condition in Micro tests (#3815) 02d413e7b6 OpenApi spec securitySchema keys fix (#3811) 1eb2c30ba0 Add missing bearer format field (#3813) d5fba6391e cache OpenApi schema generation (#3814) a619704fd4 Fix cli options repeated order (#3773) a8e8e8d0e0 Version Packages (#3809) e7afc47ce8 ensure HttpMiddleware is only initialized once (#3808) 6bfab7eefb Version Packages (#3807) a4aa34a0c3 fix HttpServer.layerContext access before initialization (#3806) cdca95e7ed Version Packages (#3804) 8b976697ed fix @effect/vitest .prop test (#3803) 7564f56bb2 add HttpServer.layerContext to platform-node/bun (#3802) 6734a03534 Version Packages (#3801) 5821ce3455 add Redactable to Inspectable module (#3777) 685a4609b0 Vitest prop testing (#3703) 6d9de6b871 HttpApi second revision (#3794) ace95f46e2 porting of #3792 (#3793) 905a045836 Schema.equivalence: fix discriminated tuples (#3789) c79c4c1783 Merge Schema into Effect (#3772) 38d30f08b8 add option to .releaseLock a ReadableStream on finalization (#3771) 4a01828b66 add TSubscriptionRef (#2725) 736897a5f8 Version Packages (#3800) dacbf7db59 add sql-clickhouse & clickhouse dialect (#3760) b836fc429a Add JSDoc comments for effect creation and execution APIs (#3797) fdfa50146e Vitest README: add Logging section (#3795) a3ebc5af56 Version Packages (#3791) 382556f893 resolve parse error when using `pick` with union of class schemas, cl… (#3792) 97cb014511 Equivalence: Fixed a bug related to discriminated tuples (#3790) 0bb8b0175d Version Packages (#3786) 20364020b8 fix HttpMiddleware circular import (#3784) 9e2cc4eafa Version Packages (#3781) 1b1ef29ae3 ensure cors middleware also affects error responses (#3780) 2465abd571 Version Packages (#3770) 3bcdfb3b64 remove Scope requirement from NodeHttpServer.makeHandler (#3779) 73947b5dbd dx: add watch task to expose all compile errors to vscode, including not open files. (#3722) 8c33087942 add support for WebSocket protocols option (#3769) 61a99b2bf9 allow tacit usage with do notation apis (.bind / .let) (#3768) e99a9c3966 ncrease timeout for release action (#3765) 3d6bcd09e2 Version Packages (#3762) bd160a4f71 add ai packages (#3631) 360ec14dd4 Allow Schema.Either to support Never without type errors, closes #3755 (#3761) 518ef7bb9e Version Packages (#3758) 09e863648f feat: add support for spanPrefix to HttpRpcRouter (#3747) d200f38af7 `@effect/sql-libsql` allow either a pre-built client or configuration options (#3759) b75ac5d090 add dotenv support to PlatformConfigProvider (#3743) 8ee30d3a70 Version Packages (#3754) 8a6975492c update HttpClient accessor changeset (#3757) 90ceeab3a0 HttpClient refactor & simplification (#3746) e0a5dadd78 Fix Transactions in `sql-libsql` (#3720) 44583d4e41 platform: add documentation on Scope requirement for HTTP requests (#3748) f02b354ab5 Enhanced Error Reporting for Discriminated Union Tuple Schemas (#3753) 597b301811 Version Packages (#3745) 9fef88016a test sql connection as part of client construction (#3744) 6225e562fb Version Packages (#3741) 3b2ad1d58a Revert deno support to Inspectable (#3727) (#3740) b06f17d2b0 Version Packages (#3738) d75140c7a6 Implement Effect.Service and allow multiple layers to be provided in Effect.provide (#3690) 0ba66f2451 add deno support to Inspectable (#3727) c716adb250 Add `Array.pad` (#3660) 5b364942e9 Add `Tuple.map` (#3658) 7b3150fa3a revert RcRef Unify change (#3693) 07796813f0 `Effect.mapAccum` & `Array.mapAccum` preserve non-emptiness (#3675) 9237ac69bc move ManagedRuntime.TypeId to fix circular imports (#3682) 534129f811 `Pool` is subtype of `Effect` (#3674) 016f9ad931 fix Unify for RcRef & Deferred (#3678) be0451c149 `Effect.provide(managedRuntime)` and `ManagedRuntime` is subtype of `Effect, E, never>` (#3677) bf77f51b32 `Latch` implements `Effect` with `.await` semantics (#3638) 49863919cd Add `isRegExp` type guard (#3647) d1387aebd1 `Resource` and `ScopedRed` is subtype of `Effect`. (#3626) ff3d1aab29 feat: add HashMap.HashMap.Entry utility type (#3618) 6fc1e02062 add Tracer.withSpanContext (#3737) 4c23a73c29 Version Packages (#3716) f40da15fbe add HttpClientResponse.filterStatus apis (#3736) 8e94585abe fix: handle blob message data from websocket (#3732) 88e85db34b Ensure random numbers are correctly distributed (#3734) 5266b6cd86 Improve DX of type errors from inside `pipe` and `flow` (#3731) b00f7a0a73 Accept TLS options for SSL configuration of PgClient (#3704) 83887ca1b1 Consider async operation in runSync as a defect, add span based stack (#3717) cdead5c9cf added Stream.mergeWithTag (#3699) 766a8af307 changeset 89bc646425 make `BigDecimal.scale` dual 9131e6ddba Version Packages (#3712) 534abce3c9 feat(opentelemetry): add withActiveSpan function to attach Effect to current Span (#3705) 823e04ee75 Version Packages (#3711) dc14e96fe7 ensure rpc chunks are always delimited on write (#3710) 16fe968745 Add codemod to propagate JSDoc comments to dual signatures (#3697) 3b5b3329d4 Version Packages (#3692) 4d91f41798 Add vitest layer api (#3689) ce8b8108d2 Version Packages (#3684) f100e20871 use decoded ids in sql resolvers (#3683) 0f262fca0f Version Packages (#3680) 660cd0f936 add support for watch mode in .runMain (#3679) 46efdc6f90 Version Packages (#3668) 6aa5edcbba Add libSQL package (#3649) 0a68746c89 Add VariantSchema fieldFromKey utility (#3676) 45096569d5 `Micro.EnvRef` and `Micro.Handle` is subtype of `Micro` (#3661) ad7e1de948 add more description annotations (#3673) 090e41c636 JSON Schema: handle refinements where the 'from' part includes a tran… (#3672) 2fcb54292a chore: fix open api json schema test (#3671) c57ee0c2c1 chore: fix json schema test (#3670) 734eae654f add description annotation to the encoded part of NumberFromString (#3669) fd83d0e548 remove default json schema annotations from string, number and boolean (#3667) ec37566475 Version Packages (#3665) 0e0af6d659 Revert "only call process.exit in runMain if signal has been intercepted (#3656)" (#3666) 3812788d79 add OpenApiJsonSchema module (#3664) fa00ffa84f Version Packages (#3663) d5c8e7e473 fix Model findById data loader (#3657) 4479c3e7af fix `addFinalizerExit` jsdocs, closes #3651 (#3659) cc3b5bab66 Version Packages (#3655) 6d4d861db6 only call process.exit in runMain if signal has been intercepted (#3656) de48aa54e9 Remove internal sorting of property signatures, closes #3652 (#3654) 8af86cb477 platform README: add Persisting Cookies example (#3648) 6fa730c0cb Version Packages (#3646) 7720db6bcb ensure sqlite-bun returns array values (#3645) a7c3c85c51 Version Packages (#3641) bb5ec6b4b6 fix encoding of logs to tracer span events (#3644) c642abc46a add .env to .gitignore (#3643) 321b201adc use HttpClientRequest.originalUrl for search params parser (#3640) 6f836a6dc2 Version Packages (#3636) e6440a74fb Stable filters now generate multiple errors when 'errors = all', clos… (#3635) 1742945dca Version Packages (#3634) b86b47d57c fix assignability of ChannelSchema apis (#3632) 6a32efb98f fix: Typo in rpc readme.md (#3630) 818ab95911 platform README: add RequestInit section (#3629) e5fdfd315b platform README: expand HTTP Client section (#3625) d790dc18f3 Version Packages (#3628) f0d8ef1ce9 revert cron schedule regression (#3627) 5ff64851d0 Version Packages (#3617) e38b3dcacd Return raw SQL query results in sql-sqlite-node (#3607) 10bf6213f3 fix double firing of schedules at initial interval (#3624) ae36fa68f7 allow crlf characters in base64 encoded strings (#3623) f56ab785cb add Date filters (less than, greater than, between), closes #3606 (#3619) cd756584c3 use Mailbox for NodeStream module (#3616) e3eae02c8e Version Packages (#3615) e44c5f2282 accept Redacted in HttpClientRequest.basicAuth/bearerToken (#3614) 798a6d5506 Version Packages (#3604) 6a128f63f9 ensure FetchHttpClient always attempts to send a request body (#3613) 01f0591911 changeset adjustments (#3612) 02f6b0660e add additional duration conversion apis (#3605) eebfd29633 add `Duration.parts` api (#3603) 569a8017ef `Dequeue` and `Queue` is subtype of `Effect` (#3591) f1b5b3c362 make sure DevTools sends final messsages (#3611) a7098bf7db ensure Mailbox.await waits for the mailbox to be empty (#3609) adf7d7a7df add Mailbox module, a queue which can have done or failure signals (#3580) 273565e790 add Effect.makeLatch (#3571) fcfa6ee30f add Logger.withLeveledConsole (#3540) 5798f76195 add Semaphore.withPermitsIfAvailable (#3593) e0d21a54c8 add filter refinement overloads to http client (#3595) 12b893e63c make Fiber subtype of Effect (#3590) 040703d0e1 `FiberRef` subtype of `Effect` (#3577) bbad27ec0a add Stream.share api (#3080) 5f0bfa1720 `Deferred` is subtype of `Effect` (#3572) bb9931b62e Made `Ref`, `SynchronizedRef`,`RcRef, and `SubscriptionRef` a subtype of `Effect` (#3511) 42a8f99740 Add `RcMap.keys` and `MutableHashMap.keys` lookup functions (#3559) 812a4e86e2 add Logger.prettyLoggerDefault, to prevent duplicate pretty loggers (#3552) aa1fa5301e Add number round (#3533) 7041393cff refactor /platform HttpClient (#3537) 007289a52d FiberRefs perf work (#3518) 362604ae01 don't reconstruct either in ParseResult.go (#3608) 7fdf9d9aa1 relax constraints for members within a union from `Schema.Any` to `Sc… (#3589) 8ec4ff456f update pnpm with corepack (#3602) 291370bd04 Version Packages (#3601) ccd67df6b4 remove network roundtrip from mysql update in Model repository (#3600) 4a848b0010 Version Packages (#3585) d8aff79d4c fix mysql support for Model.makeRepository (#3599) 8632f7dd8c focus the platform README on unstable modules (#3596) a880d860e6 Add webpack ignore to dynamic import to address module resolution issues (#3597) 35a0f81314 TestClock yield with setTimeout(0) (#3592) 95bf1ee8d0 enable eslint no-console rule (#3586) 8261c5ae6f add missing `encoding` argument to `Command.streamLines` (#3582) ac9bf4662d update dtslint (#3583) 4d96d03548 Update to TS 5.6 (#3579) 192682c492 update platform-* READMEs (#3581) be0b14870e Version Packages (#3575) ce86193546 fix VariantSchema variant validation (#3574) b00e615968 Version Packages (#3570) 06989e9969 Ensure `QuitException` terminates command-line processing (#3569) 1352d69b6b Version Packages (#3568) 2df49c44ee Add `Options.withFallbackPrompt` to CLI (#3561) dfc644c41c Version Packages (#3564) 3791e24163 add Socket.toChannelString api (#3567) 2f96465b31 fix HttpPlatform dependency cycle (#3566) 90ac8f6f60 add HttpApiBuilder.toWebHandler api (#3565) f6acb71b17 Enhanced the generation of recursive Arbitraries to prevent infinite nesting, closes #3512 (#3560) 82015d687d Version Packages (#3558) c969f74f61 add ChannelSchema module to /experimental (#3557) dc8baa5a18 allow manual trigger of snapshots (#3556) 67765a0597 Version Packages (#3554) 64c22927aa leave node stream listeners attached to prevent unhandled errors (#3555) 4a701c406d ensure Socket.toChannel fiber is attached to Scope (#3550) b63ce8f721 chore: migrate eslint to flat config (#3528) 07169692e1 Version Packages (#3547) 8a601d7a1f remove console.log statements from Micro (#3548) 353ba19f9b fix exported Stream types (#3546) c1e95aab47 Version Packages (#3524) 79aa6b136e fix Micro.forEach for empty iterables (#3545) dcb9ec0db4 fix(Stream): align .empty to behave like .make with no arguments (#3543) f8e7d26e44 update docgen version (#3539) f842e1d98b Add Zod link to schema README (#3538) 7cc6076754 infra: add python to dev shell (#3531) 79859e7104 Optimize Array.sortWith to avoid calling the map function excesively (#3536) ab8729dc1d Condense schema README (#3534) 0ba81cb61d add `onnotice` option to `PgClient` (#3529) f6a469c190 Support tacit usage of `Effect.tapErrorTag` and `Effect.catchTag` (#3516) 19a7c962cf Version Packages (#3519) 6bf28f7e3b add HttpApi modules to /platform (#3495) 200e9d3153 rename Config.stringNonEmpty to nonEmptyString (#3517) e55783886b Add a Config constructor : `Config.stringNonEmpty ` (#3514) 8356321598 Introduce `bindAll` (#3486) db89601ee9 feat(`effect/Micro)`: add isMicroCause (#3501) 718cb70038 feat(Stream): race (#3305) 2f456cce50 Preserve `Array.modify` and `Array.modifyOption` non emptiness (#3496) 7906d54e70 fix Array.replaceOption signature (#3497) d6e7e40b1e preserve `Array.replace` `Array.replaceOption` non emptiness (#3491) e9d03107ac Avoid automatic propagation of finalizer concurrency, closes #3440 (#3441) 8745e41ed9 return Exit values from `Fiber.awaitAll` (#3147) 192f2ebb2c add `propagateInterruption` option to Fiber{Handle,Set,Map} (#3407) 0c6948479f Version Packages (#3513) e809286590 Detect environment in Logger.pretty using process.stdout (#3510) 8e64b1a6ed Renders the default for all `Prompt` types that accepts `TextOptions`. (#3508) dd3512c0d2 Version Packages (#3507) 50ec8897a4 use Array.partitionMap for Effect.partitionMap (#3504) e7a65e3c6a use Logger.pretty for runMain, and support dual usage (#3506) cd154fe259 Version Packages (#3498) 46a575f48a fix nested Config.array (#3499) f960bf4523 refactor(Stream): left/right parameters and generic names (#3306) ef3cc95556 Version Packages (#3475) 413994c979 add binary support to KeyValueStore (#3494) f2c8dbb77e add VariantSchema.Union constructor (#3493) ae53062da4 enable multiple statements for sql-mysql2 (#3492) 0c0984173b fix type error when .pipe() has no arguments (#3490) f2a851f289 use DurationInput for Model makeDataLoaders window (#3489) 8dd3959e96 move VariantSchema.extract to factory, and copy type level behaviour (#3488) 00670d0f8d ensure VariantSchema preserves opaque types (#3487) 35be739a41 infer Model repository id schema from id column name (#3483) dba570a8e9 make VariantSchema constructor apis internal (#3482) c35c8148d2 typeclass: add let example (#3481) f8326cc109 add Model.makeDataLoaders, for deriving CRUD data loaders (#3480) 83a108a254 add Model.makeRepository, for deriving a simple CRUD repo (#3479) da52556cfe Add VariantSchema.fieldEvolve api (#3478) 2cb6ebbf78 update Model doc categories (#3477) 5e9f51e4a1 add Model.FieldOption & Model.Date (#3476) 14a47a8c1f Add support for incrementing / decrementing a gauge (#3474) f43450522a Lock down workflow permissions (#3464) 72dd53566a fix: respect the default for a text prompt (#3466) c6b46007f8 Version Packages (#3470) c3446d3e57 guard against stale values in Model.DateTime fields (#3471) 9efe0e5b57 respond with 404 for NoSuchElementException in HttpServerRespondable (#3469) 41d4d72182 Version Packages (#3468) cfcfbdfe58 add VariantSchema.fields for accessing the fields (#3467) 650a53d58b Remove unnecessary workflow steps (#3463) 6dff3f3d6b Remove unnecessary checkout step (#3462) 6209c85b15 Version Packages (#3461) e9da5396bb add VariantSchema & Model modules (#3455) 4fabf75b44 support partial objects in sql helpers (#3460) a2aea87199 Version Packages (#3447) 00b6c6d400 ensure defects are caught in Effect.tryPromise (#3459) a07990de97 Add support for obtaining raw SQL query results from the underlying SQL client (#3457) f8d95a61ad ensure makeZonedFromString supports 00:00 offset (#3458) c940df6380 ensure Scheduler tasks are added to a matching priority bucket (#3452) 5dcb401bfc add HttpRouter.currentRouterConfig fiber ref (#3454) a9345f17b3 fix schema readme (#3451) 0e42a8f045 update workspace dependencies (#3450) 1ceed149dc add Schema.ArrayEnsure & Schema.NonEmptyArrayEnsure (#3448) c1987e25c8 Make json schema output more compatible with Open AI structured outpu… (#3433) 8295281ae9 Fix `Cache<_, Value, _>` type parameter variance (covariant -> invariant) (#3404) d9e4e12d98 Version Packages (#3445) 335111ddf5 sync /cluster* package.json dependencies with source code (#3446) 04adcace91 ensure Stream.toReadableStream pulls always result in a enqueue (#3444) 332b0c6ad3 Version Packages (#3438) 8ac351e26c use en-US locale in DateTime tests (#3443) 739834efd9 add Rpc prefix to rpc module names (#3442) 630d40eaa7 add `HttpRouter.concatAll` + fix `HttpRouter.concat` mounts (#3439) 17245a4e78 add Cookies.get/getValue apis (#3437) 60fbdfb3af Version Packages (#3431) d829b57635 remove host from HttpServerRequest url's (#3436) fd4b2f6516 ensure fiber is properly cleared in FiberHandle.unsafeSet (#3435) db8e85ff82 Update README.md (#3434) 99ad841529 Fix return types for `attachPropertySignature` function (#3430) 70c3d67af7 Version Packages (#3429) 76b0496ff9 fix for missing global undici dispatcher (#3428) 826d4fa178 Version Packages (#3423) 23a51df368 ensure /rpc streams on bun aren't interrupted early (#3426) 8cc151795d interrupt effect when test finishes (#3416) 056b710897 add `{Bun,Node}HttpServer.layerTest` for testing http servers (#3409) 818ea47b47 Version Packages (#3421) fb18738e7f properly interrupt DevTools client during finalization (#3420) d4970e3506 Representing Optional Fields with never Type (#3419) 0a2f659dd0 Version Packages (#3418) 5b0a98de79 remove TimeToLive trait in favour of `timeToLive` options (#3417) 58cababf23 Version Packages (#3415) e7cb109d07 ensure broken HttpMiddleware that doesn't fail responds (#3414) 9f3f19aca7 Version Packages (#3399) fb9f7867f0 ensure failure in HttpMiddleware results in a response (#3408) 510a34d4cc Fix `Effect.repeat` with times option returning wrong value (#3405) 216ac82b07 Update README.md (#3401) 252f26940b update version of node used by ci (#3402) 404700304a add userland dtslint tests (#3400) 45dbb9ffea add `listen` / `notify` to `PgClient` (#3398) 3f8e3d4d05 expand HTTP Client docs (#3396) b6e357d897 Add instructions for adding annotations to a Schema.Class usage (#3395) 2177fe8e21 Version Packages (#3389) 5c3a4d005c ensure DevTools client is shutdown (#3394) 3dce357efe Enhanced Parsing with TemplateLiteralParser, closes #3307 (#3347) 657fc48bb3 Implement `DecodingFallbackAnnotation` to manage decoding errors (#3346) 7411eaafb3 chore: fix DateTime since tags (#3388) 4dccc1cb71 Version Packages (#3385) 42d07067e9 allow sql Statement transformers to be effectful (#3387) 250717ed79 use type alias for HttpApp (#3386) 11223bf9cb add HttpServerRequest to HttpApp.Default (#3384) 8ea4f44c43 add Concurrency Annotation docs (#3381) 85a374a332 Version Packages (#3378) 4ddbff0bb4 Support `Refinement` in `Preficate.tuple` and `Predicate.struct` (#3366) 8135294b59 add DateTime module (#3296) 4bce5a0274 feat: add onlyEffect option to Effect.tap (#3292) 2d09078c59 add `Random.choice` (#3314) 7d02174af3 adjusted various function signatures to return `Array` (#3340) 1e0fe802b3 make List.Cons extend NonEmptyIterable (#3350) e74cc38cb4 feat(Stream): onEnd (#3303) cd255a4887 add Stream.asyncPush api (#3277) bb069b49ef add Stream.onStart api (#3302) 3845646828 Implement Struct.keys as a typed alternative to Object.keys (#3282) 9da883f0da fix(kysely): whitelist compile mehod from proxy 9f11d6d3b4 sql-kysely: fix error handling in internal executeCommit (#3271) db1dbc694b Feature: add @effect/sql-kysely package (#3017) 3eb8c4789c Revert "Release queue: minor" (#3379) 9c058c9833 Release queue: minor (#3248) d12ec6582e Version Packages (#3358) 6359644463 add MicroScheduler to Micro module (#3377) 9f197e8620 add vitest `runIf` (#3376) f566fd1d7e Added a test covering the `Hash.number` bug for fractional numbers (#3373) 5f01eb91be update to vitest v2 (#3367) 5d34223f75 Fix typo about do notation documentation (#3372) 1234fcf701 Make the usage of annotations to customize the output of TreeFormatte… (#3374) 647b5df763 increase max-old-space-size manually for tests (#3371) 727ce89191 schema: add userland tests (#3370) 64f7f446fd allow snapshot action on all PRs (#3369) f0d1824b17 add `socket` option to `PgClient` (#3365) 16b2202f9a add `applicationName` option to `PgClient` (#3364) 2fc0ff4c59 export `isPropertySignature` guard (#3363) 7f41e42883 Add `Service` and `Identifier` type helpers to `Tag` (#3362) 6a7e16544c upgrade typescript (#3360) 9bbe7a6814 add `NonEmptyTrimmedString`, `OptionFromNonEmptyTrimmedString`, close… (#3343) 7c0da5050d Add `Context` field to `Schema` interface, closes #3356 (#3359) a57d6cc0af docs: Bring e/sql docs up to date after recent module reorg. (#3354) f262665c27 Improve annotation retrieval from `Class` APIs, closes #3348 (#3357) c7081cfbaa Version Packages (#3341) a26ce581ca fix Hash.number not returning unique values (#3355) fc20f73c69 wait for worker ready latch before sending initial message (#3353) 1dbd0a1712 make access to `self` lazy in platform-browser WorkerRunner (#3352) c8c71bd20e ensure all Data.Error arguments are preserved in .toJSON (#3349) 1ba640c702 Fix typo propety to property (#3345) 39597ec78e remove snapshot comment (#3351) 210c374e06 remove push trigger from snapshot workflow (#3342) 068e322ee1 add vitest `skipIf` (#3319) eb4d014c55 fixed search params related function signatures (#3339) 1c4176cc03 remove link to non existing API (#3334) d1018b056d Version Packages (#3330) c8f8690585 Improve the JSDoc for `Prompt.all` (#3332) f241154add Expose success and failure schemas on `TaggedRequestClass` interface,… (#3333) b8e3ab6739 Support record argument in `Prompt.all` (#3329) 9800255c47 fix schema benchmarks (#3328) e11922115b Version Packages (#3326) b2b02cbd02 Ensure the DevTools client flushes requests on interrupt (#3325) 0ddb79f982 Only allow snapshots to run on pull requests (#3324) a61979ef34 Fix the snapshot action (#3321) 199df952be Version Packages (#3323) 20807a45ed schema next minor (#3227) 0af945cb8b Version Packages (#3301) 755c45cb09 Publish snapshots on every pull request (#3320) dc56044c6b fix /snapshot comment output (#3317) 796c9c83ac change /snapshot output (#3316) 4f727301fb update JSON Schema guide (#3313) fa3d372c3c update eslint plugin (#3312) 6921c4fb8c Remove incorrect static override type annotations in class definitions (#3311) 99bddcfb3d add patch for babel-plugin-annotate-pure-calls (#3310) 3afcc93413 Forbid usage of property "name" in Effect.Tag (#3288) 29a5389915 move Arbitrary / Pretty / JSON Schema / Equivalences to top level sections (#3300) 215800db07 add "Naming Conventions" doc section (#3298) f34f6a768a Version Packages (#3285) 2b14d18146 fix YieldableError rendering on bun (#3295) cc327a1bcc correctly exclude symbols from Record.keys (#3294) 4bfe4fb5c8 Changed `Stream.groupByKey` JSDoc category from `utils` to `grouping` (#3289) 8ec49555ed remove type-level error message from `optional` signature, closes #3290 (#3291) f0285d3af6 JSON Schema: change default behavior for property signatures containi… (#3287) 3ac2d76048 Fix: Correct Handling of JSON Schema Annotations in Refinements (#3284) a91a8e5a26 Version Packages (#3275) adbf75340a remove cjs /platform dependencies (#3281) fcecff7f7e ensure /platform HttpApp.toWebHandler runs Stream's with the current runtime (#3278) 51e9c5c87f feat: allow equals sign in aliased parameter values. (#2755) a9d7800f6a use "unbounded" buffer for Stream.fromEventListener (#3266) 402ba652e8 Make sure snapshot URLs are split on the last `@` character (#3274) 42f76546b4 do not use compact mode as it is broken (#3273) c527a03e87 Use `pkg.pr.new` for snapshots (#3269) b37a8c7b14 Version Packages (#3268) 1d51d1b627 allow creating browser WorkerRunner from a MessagePort (#3263) 07db4ac8da add NodeHttpServerRequest.toServerResponse for accessing the raw node response (#3265) b6f09d6b1c disable bun test suite (#3264) 07c541dff0 align schema readme `Schema.message` -> `Schema.annotations` (#3261) 3271ca1ff6 Version Packages (#3245) 5be9cc0440 Allow ignoring empty lines when unpacking Ndjson (#3259) 53c0db0687 replace /platform RefailError with use of the "cause" property (#3260) ada68b3e61 refactor & simplify /platform backing workers (#3255) 1b4523699f force flush otel provider before calling shutdown (#3254) ed0dde4888 update dependencies (#3253) e38f3014fa chore: improve transformation accuracy in `Router.toHandler` (#3251) 203658f800 add support for `Refinement`s to `Predicate.or`, closes #3243 (#3250) ca775cec53 if performance.timeOrigin is 0, use performance.now() directly in Clock (#3247) eb1c4d44e5 render nested causes in Cause.pretty (#3246) 42aec0e4c5 fix: typo in DurableExecutionJournal.ts (#3174) 68f9cc9c33 Version Packages (#3237) 786b2ab29d Support heterogenous argument in `Option.firstSomeOf` (#3230) e85733e02a add issue_comment to release-queue workflow (#3242) fc573547d4 Align Stream.run public function signatures (#3238) a1db40a650 ensure interrupts are handled in WorkerRunner (#3241) dabd028dec fix process .isTTY detection (#3240) edb0da3837 ensure Schema.TaggedError renders a .cause correctly (#3234) c8d3fb0fe2 set Logger.pretty message color to deepskyblue on browsers (#3236) 98a88f7af6 Version Packages (#3232) 6684b4c27d improve safari support for Logger.pretty (#3235) cb9f8a1ef6 revert PrimaryKey fallback in /experimental Persistence (#3233) 639208eeb8 Render a more helpful error message in Effect.timeout (#3228) d4336d8e32 Version Packages (#3224) 0623fca416 improve /platform/WorkerError messages (#3223) a16e9bea41 Version Packages (#3222) 0ad8bd82e6 Add JSDocs for logger-related functions (#3221) 55fdd761ee fix Logger.pretty on bun (#3220) 6e0a107518 Add example to Random.make (#3219) cf8c52bddc Version Packages (#3218) 5ab348f265 allowing customizing Stream pubsub strategy (#3216) 9f66825f1f allow customizing the output buffer for the Stream.async* apis (#3212) 4626de59c2 export Random.make taking hashable values as seed (#3203) a1f5b831a1 include Error.cause stack in log output (#3194) 60bc3d0867 add RcMap & RcRef modules (#3179) f01e7db317 add PubSub `replay` option (#3135) ac71f378f2 fix(Stream|Channel): exclude `Scope` from the resulting effect env of all the non-scoped `.run*` methods (#3190) 13cb861a5e refactor(Stream): use more common Types.TupleOf instead of Stream.DynamicTuple (#2958) e7fc45f0c7 add Logger.prettyLogger & Logger.pretty (#3146) 79d2d91464 ensure "cause" is rendered in Data.Error output (#3188) e4bf1bf2b4 feat(Stream): raceAll implementation (#3131) 8432360ce6 refactor(Stream): rename argument names of mergeLeft and mergeRight from self/that to left/right for clarity (#3160) 7f52592745 feature: add cloudflare @effect/sql-d1 package (#3045) 3e04bf8a71 feat: add `Config.Config.Success` (#3047) 464c5a867f Version Packages (#3205) 660b83ef27 disable lmdb tests in experimental (#3217) cf96477060 add basic docs to rpc (#3214) 1aa7424acd reduce chance of race condition in Micro withConcurrency test (#3211) 7af137c943 prevent reclaim of manually invalidated pool items (#3210) 366f2ee3fb simplify /platform http response handling (#3209) 89270589d8 Improve babel build post-processing (#3206) 097d25cb5d allow invalidated Pool items to be reclaimed with usage strategy (#3202) f22586f60f Remove unnecessary build config (#3207) ee4b3dc5f6 Updated the JSDocs for the `Stream` module by adding examples to key … (#3204) 3429c8fb61 Version Packages (#3172) f7534b94cb Add `toString` to `AST.PropertySignature` and `AST.IndexSignature` an… (#3198) a435e0fc53 refactor `TrimEnd` & `TrimStart` (#3181) 24566bb5af Add example of customizing the type output of TreeFormatter using ann… (#3193) 5d5cc6cfd7 Support `Capitalize` `Uncapitalize` filters and schemas (#3192) f89e03fb90 Zod comparison: Add some links and fix typo (#3183) e821a797bf Add reference to `filterEffect` in the Zod comparison and fix faq sec… (#3182) bab158ac9e README: add FAQ section (#3178) dc20a10ef6 Internal: simplify product implementation (#3177) b5554db36c allow Stream run fiber to close before trying to interrupt it (#3176) a9c4fb3bf3 ensure fibers are interrupted in Stream.mergeWith (#3175) 359ff8aa2e add Serializable docs (#3148) 499b15e510 Version Packages (#3170) 34151d1c86 Add `Effect` and `Micro` basic instances (#3169) d29e821b25 Version Packages (#3168) 749b90345f Ensure file selector prompts can traverse parent directories (#3167) ad51cbe7d9 Version Packages (#3162) 15967cf189 Add `filterEffect` API, closes #3165 (#3166) 2328e17577 Add `pick` and `omit` members to `Struct` interface, closes #3152 (#3163) a5737d6db2 ensure PubSub.publishAll does not increase size while there are no subscribers (#3161) 75a6c97746 Version Packages (#3159) c3e3ed6491 drop requirement of PrimaryKey in /experimental Persistence (#3158) 542baf77b4 Version Packages (#3154) 271ec23adb remove console.log from Redis persistence (#3150) 51bb7d5677 Add `Prompt.file` to support path-based options in the CLI's `--wizard` mode (#3153) 64e1a16266 suppress ajv warnings in tests (#3151) 48e614e59f Version Packages (#3140) cb22726568 add PersistedCache module to /experimental (#3149) 253078047d Add an example of a transformation where we need to use `Schema.typeS… (#3145) d006cec022 Enhance JSON Schema Support for Refinements in Record Parameters (#3143) e911cfdc79 Optimize JSON Schema output for homogeneous tuples (such as non empty… (#3139) 53038f11c8 Version Packages (#3137) 422a2ee597 log DevTool's errors at debug level (#3138) 7f8900a1de support undefined in http request schema apis (#3136) a4982f6288 Version Packages (#3128) 139d4b39fb use Channel.acquireUseRelease for Channel.withSpan (#3134) 33735b16b4 Support for the tacit usage of external handlers for `Match.tag` and `Match.tagStartsWith` functions (#3121) 5c0ceb0082 Micro: renaming to align with `Effect` module (#3096) 8513d7edba README: Recommend using the `strict: true` option to get better error… (#3132) 34faeb6305 Add `ReadonlyMapFromRecord` and `MapFromRecord`, closes #3119 (#3130) 489d20a366 refactor vitest (#3122) 07be551362 Version Packages (#3126) 61e59640fd Add support for Union, Suspend, and Refinement as the second argument… (#3125) c004e1177d Version Packages (#3118) baa90df966 add HttpClient.followRedirects api (#3123) fcb7411be7 Adds a `--log-level` built-in option to all commands (#3107) cb76bcb2f8 Modified `JSONSchema.make` to selectively ignore the `title` and `des… (#3117) eb2494a2fe Version Packages (#3111) 296a9e24b5 fix deadlock in @effect/experimental dataLoader (#3110) f7688bfb49 Version Packages (#3105) 6d2280e949 fix assignability of HttpMiddleware in HttpRouter.use (#3109) 0cb3e9de39 Ensure help documentation always respects the cli configuration (#3106) 52a87c7a0b remove the stack from HttpServerError.RouteNotFound (#3104) c1874935d6 Version Packages (#3100) dbd53ea363 filter undefined from UrlParams Input (#3102) d9905444b9 Generate JSON Schemas correctly for a schema created by extending two… (#3101) a047af9944 fix using unions with Match.withReturnType (#3099) e86211abac Version Packages (#3093) d7dde2b4af remove global AbortController from Micro (#3095) 72638e3d99 Micro: update `fromOption` to change the failure type to `NoSuchElementException` (#3083) 74e0ad23b4 add mount apis to HttpRouter.Service (#3094) d71c192b89 Special case `S.parseJson` to generate JSON Schemas by targeting the … (#3087) 9b2fc3b9df Micro: add `zipWith` (#3085) 829fb19d9d Version Packages (#3092) a5b95b5482 add some common services to HttpRouter.Default (#3091) 5e29579187 add HttpServerRequest.toURL api (#3090) 6b3bd8729c Version Packages (#3081) ab3180f827 add HttpClientResponse.matchStatus* apis (#3089) a48ee845ac add HttpServerRespondable trait (#3088) 8898e5e238 Cleanup signal "abort" event handler in `Micro.runFork` (#3067) 81027a1a5d docs: remove adapter examples (#3041) c342739622 Support `this` argument for `Micro.gen` (#3065) c86bd4e134 Support unification for `Micro` module (#3078) ff786367c5 Align the `Micro.catchIf` signature with `Effect.catchIf` (#3082) bbdd365677 update to typescript 5.5 (#3079) 91595d5145 Unify CI and Nix to Nodejs `v20.12.2` (#3076) 6f8035d5af Version Packages (#3075) 0b47fdfe44 Revert the 0.67.22 patch as it is causing issues with other array fil… (#3074) a70705c7f8 Version Packages (#3071) 3da1497b5c Reuse centralized do-notation code in `Micro` module (#3062) 192261b2ae Add `refineTypeId` unique symbol to the `refine` interface to ensure … (#3070) 6ddabaaafd Version Packages (#3066) 027004a897 add HttpRouter.Tag.unwrap api (#3064) c2c3672e77 (chore) Platform README: Minor formatting changes (#3061) 7ba763c456 Version Packages (#3060) 66a19109ff add missing `TypeLambda` to `Micro` module (#3056) 2e8e2520ca add Layer based api for creating HttpRouter's (#3059) 428edf1289 Version Packages (#3055) 87e8a99349 Enhance JSDocs for error tapping APIs with examples (#3054) 37a07a2d8d coerce primitive types in UrlParams input (#3053) 38b406bd7c Version Packages (#3052) b77fb0a811 add HttpMiddleware.cors (#3051) 75e718ee0a chore: Update README.md with examples of extending schemas (#3050) a5e923e6a0 Version Packages (#3049) 530fa9e36b Fix error message display for composite errors when `overwrite = false` (#3046) f5a4175b7e Version Packages (#3044) 1d62815a50 Add `make` constructor to `Class`-based APIs, closes #3042 (#3043) 442ef9e121 Schema Readme: update Troubleshooting When Working With Generic Schemas sect… (#3038) 74b87f19aa Version Packages (#3037) 5a248aa5ab remove dependency on /platform-node from DevTools (#3036) 01075fb396 Version Packages (#3034) d33d8b050b restructure sql modules to have flat imports (#3035) 815cb48de2 remove ignoreLogged from Micro tests 984d516ccd add Micro module (#2884) fc11ae17e0 update Injecting fetch docs (#3033) 2f619e6cef update cluster examples for latest /platform (#3032) 61707b6ffc Add `Effect.liftPredicate` (#2956) 9c1b5b39e6 add EventListener type to Stream to avoid use of dom lib (#3020) 63dd0c3af4 restructure platform http to use flattened modules (#3014) c6a4a26660 add Array.ensure api (#2812) 8c3b8a2ce2 add `ManagedRuntime` type utils (`Context` and `Error`) (#3005) 91bf8a2e9d Implement `at` method for tuples (#2976) c0ce180861 Make `Option.liftPredicate` dual (#2957) ff73c0cacd feat(Stream): implement Success, Error, Context type accessors (#2959) a35faf8d11 Add `lastNonEmpty` function to `Chunk` module, closes #2946 (#2951) 017e2f9b37 Add `Either.liftPredicate` (#2919) 1d6210ecef Version Packages (#3031) 3ba7ea1c3c update find-my-way-ts & multipasta (#3030) 1bc3bf5ee7 Version Packages (#3029) d47380012c Introducing Customizable Parsing Behavior at the Schema Level, closes… (#3028) c1aa202200 Fix typos in Schedule.ts (#3026) dbe962e070 Version Packages (#3025) eb341b3eb3 Replace `Types.Simplify` with a custom `Simplify` to restore nice typ… (#3024) 6eac3d2f1e add `@effect/cluster-*` packages (#2927) 6bba557930 platform: update README with example on mocking fetch for HTTP reques… (#3007) 467db0e640 Version Packages (#3022) b8ea6aa479 update find-my-way-ts to fix vercel edge support (#3021) ab3f0552cc (JSDoc) add descriptions and examples to Effect.zip and Effect.zipWith (#3019) 276b394f1a Version Packages (#3013) b51e26662b Fix handling of `exact` option overrides in `AST.ParseOptions` and im… (#3015) 6c89408cd7 ensure Config.Wrap only destructures plain objects (#3012) 804413b15a Fix typo in Effect.ts (#3011) 6f014bff63 platform: Add documentation for toWebHandler function in README, incl… (#3009) c94ebf4bb4 JSDocs: add examples to caching functions (#3008) ebed9627ae Version Packages (#3003) f6c79772e6 Schema: version 0.68.0 (#2906) a67b8fe2ac use Math.random for Hash.random (#3001) 368d735fba Version Packages (#2996) 7204ca5761 fix support for Predicates in Predicate.compose (#3000) 06ede85d6e Chunk tests (#2999) 0ff11d7199 fix typo (`isExact` -> `exact`) and make internal `toASTAnnotations` more general (#2998) 3b15e1b505 Improve error handling (type-level) for improper usage of `optional`,… (#2997) 3a750b25b1 Expose `exact` option for strict decoding on missing properties, clos… (#2994) 01171bc24e add internal mergeSchemaAnnotations (#2992) 50fc28962f Version Packages (#2973) 2ee4f2be7f Remove `Simplify` from `extend`, `pick`, `omit`, `pluck` APIs, closes… (#2991) 07e12ecdb0 refactor Socket to use do notation (#2988) e32b5ebd52 Enhance README with guidelines on schema transformations and arbitrar… (#2987) 13e3dcf9f8 Add instructions for handling unsupported types in JSON Schema genera… (#2984) b38d456e07 Typo in Example Source Code in README.md (#2983) 3572646d5e ensure multiline error messages are preserved in cause rendering (#2981) 7f987a30f1 improve dataLoader concurrency when max batch size is hit (#2980) df4bf4b62e fix discard option in Effect.all (#2977) f085f92dfa Fix Unify for Stream (#2917) 9b3b4ac639 Add `Schema.NonEmptyChunkFromSelf` and `Schema.NonEmptyChunk`, closes… (#2971) 1aed347a12 Updated `Chunk.toArray` and `Chunk.toReadonlyArray`. Improved functio… (#2970) d31b874e21 Remove stray bit of text in Italian (#2969) ca78f589ba Version Packages (#2949) 001e08d05a update example fix for workspaces (#2963) 4d3fbe82e8 fix KeyValueStore for react native by making constructors lazy (#2966) db68ca5ebd chore: Update `propertyOrder` option in README to use "original" inst… (#2964) 6068e073d4 Fix runtime error in `Struct.evolve` by enhancing compile-time checks, closes #2953 (#2954) cd09d0274a catchTag: add more type-level tests (#2962) d79ca17d9f The `minItems` filter now checks for an invalid argument (`n < 1`) an… (#2955) 5ece97a9b8 Clarify usage and behavior of Schema.is in documentation (#2960) eb98c5b79a Change `Config.array` to return `Array` instead of `ReadonlyArray` (#2952) 184fed83ac Ensure `Chunk.reverse` preserves `NonEmpty` status, closes #2947 (#2950) 3a77e20978 Remove unnecessary `===` comparison in `getEquivalence` functions (#2948) 38bda05f77 docs(Schema): add more examples for renaming properties in the README (#2945) 02f8ed232d Redacted: add JSDocs (#2944) b64b6ed152 Schema: fix Redacted since tag (#2941) 2002b6baa6 Schema: add Redacted docs (#2939) 615493a441 Add documentation for `Predicate.implies` (#2937) 856024ca36 Version Packages (#2936) 071a46c572 use timeToLiveStrategy of "creation" for mssql pool (#2933) 188f0a5c57 parse URL instances when creating client requests (#2924) 2b9ddfcbac support new Pool options in /platform WorkerPool (#2876) 0f40d989da add concurrency control to Pool options (#2865) 1f4ac00a91 feat(Stream): zipLatestAll (#2832) 64565db9b8 ensure isTupleOf works with ReadonlyArray (#2883) 67f160a213 Introduce `Redacted` module (#2856) b53f69bff1 add `TupleOf` and `TupleOfAtLeast` types, `isTupleOf` and `isTupleOfAtLeast` type guards (#2830) 9305b764cc Add queuing strategy option for Stream.toReadableStream (#2864) 5bd549e4bd Gen context (#2857) b761ef00ea add Layer.annotateLogs & Layer.annotateSpans (#2836) c727592f68 README: update pull requests guidelines (#2935) 8bc3822ab4 Version Packages (#2928) 7c4d500af6 ensure rpc client properly detects Stream requests (#2934) fd8d860432 update @effect/sql dependencies (#2932) e50e01db54 add @effect/sql-drizzle integration package (#2860) a67d60276f ensure pre-response handler is checked after running the user-provided http app (#2931) 7fcdb92427 propertyOrder: do not add missing optional values (#2930) 61e8edbf95 fix print to stdout of cli (#1812) (#2920) 4c6bc7f190 Add `propertyOrder` option to `ParseOptions` to control the order of … (#2926) ae55d07add Version Packages (#2911) 3f28bf2743 only wrap objects with string keys in Config.Wrap (#2923) 8c5d280c04 remove usage of performance.timeOrigin (#2921) 6ba6d269f5 Remove toJSON from PrettyError and fix message generation (#2912) cd7496ba21 Add support for `AST.Literal` in `Schema.TemplateLiteral`, closes #2913 (#2916) 349a036ffb Schema: align constructors arguments (#2915) 58178204a7 Fix id extraction in Context.Tag.Identifier (#2914) 799aa20b4f Make baseUrl() more defensive in @effect/platform (#2903) df87aa2438 Version Packages (#2909) a0dd1c1ede TemplateLiteral: fix bug related to ${number} span, closes #2907 (#2908) 69fca515a2 Version Packages (#2898) 7d6d875007 add support for unions to `rename`, closes #2904 (#2905) c6ab27e46d encodedBoundSchema: preserve array stable filters (#2902) 3c080f74b2 add support for string literals to `Schema.TemplateLiteral` and `Temp… (#2901) d9d22e7c4d Schema: expose property signature schemas (#2892) fb91f17098 ensure Equal considers Date by value (#2894) bf806bf04c chore: Update @since version in schema files to 0.67.0 (#2899) 70cda704e8 Add `encodedBoundSchema` API (#2897) 1490aa00e0 update and simplify flake.nix (#2895) 5ef9982fe6 Schema: Add documentation for handling equality in Schema.Class (#2893) 22755e268f Version Packages (#2889) ac58169e34 fix changeset (#2891) 5745886985 Fix constructor type inference for classes with all optional fields, … (#2890) 6801fca443 ensure provide is additive on context (#2887) bc86a0b5da Version Packages (#2872) 84d331725b DeepMutable: remove redundant branch (#2885) 2c2280b98a feat: added KeyValueStore.layerStorage (#2737) e2740fc4e2 add `requiredToOptional` function to `Schema` module, closes #2881 (#2882) 60fe3d5fb2 add missing `makePropertySignature` constructor (#2880) cc8ac5080d fix: DeepMutable tuples (#2879) e83e4f52ed vitest: add basic docs (#2868) 73485053f9 Update `timeout` parameter type in `flakyTest` function to accept `Du… (#2869) c268928405 Version Packages (#2871) 3e86cedfa2 fix(sql): mysql migrator (#2867) 2b9035032d chore: Update platform README with example for streaming stdout (#2870) c12d7ff193 remove unused internal APIs (#2866) 37acb4a346 schema: readme: fix typo (#2863) 04ea5a7934 Update outdated `RequestResolver` description and remove redundant as… (#2862) a2036a745b Version Packages (#2848) c5846e9913 Add `tag` and `TaggedStruct` constructors (#2851) 2b0ceb63a7 refactor Formatting Errors section (#2861) a0f989d382 ensure sqlite wasm type id is unique (#2858) c5c94edf1d remove temp directory for FileSystem makeTempFileScoped (#2859) ab605312ba CLI: README: Include Basic Usage and revise the Tutorial section (#2844) 5dec35f41c schema: readme: refactor Transformations section (#2852) c23b142729 docs: Add example for getting raw request in platform README (#2850) 40f7e9b553 schema: remove outdated md file (#2849) 70001732f4 display a nicer message in the cli wizard when using Option (#2847) a7d5c61818 platform: readme: add KeyValueStore section (#2845) 5d981b4f05 Version Packages (#2839) bf662893dc allow additional span attributes to be passed to sql clients (#2838) a4beb698e6 Version Packages (#2824) 031c7122a2 attach Stream.toReadableStream fibers to scope (#2834) 5133ca9dc4 remove pool resizing in platform workers to enable concurrent access (#2835) af0543cbe6 add sql.reactive api to /sql-sqlite-react-native (#2833) a44e532cf3 make `Array.separate`, `Array.getRights`, `Array.getLefts`, `Array.getSomes` heterogeneous (#2744) 1262e7b182 platform: add "Applying Middleware in Your Application" section (#2826) 39f715ed80 add dtslint tests for separate, getRights, getLefts, getSomes (#2825) 608b01fc34 Array: simplify signatures (`ReadonlyArray | Iterable = Ite… (#2823) e22427c5e9 schema: readme: add warning on Secret schema composition (#2822) 29871554b2 schema: add Config documentation (#2821) 40a6dcba41 Version Packages (#2809) 1af94df6b7 improve worker pool error reporting and finalization (#2819) c07e0cea8c Revise the ordering of type parameters within the `SchemaStore` inter… (#2792) e3766411b6 Add support for `Config` module, closes #2346 (#2816) f8038cadd5 Add support for Enums in Record, closes #2811 (#2814) e313a01b7e Array: fix `flatMapNullable` implementation and add descriptions / ex… (#2808) c3fddb3c78 Version Packages (#2806) 5af633eb5f update dependencies (#2803) 45578e8faa fix internal cutpoint name preservation (#2805) dfc87e230c ensure effect_internal_* function names are preserved with esm (#2804) 02c87ec0fd Version Packages (#2791) 9f8122e788 Improve internalization of functions to clean stack traces (#2797) 61c584869c use commit author for changelog attribution (#2802) 6a6f6706b8 Avoid eager read of the stack when captured by a span (#2798) 5d9266e8c7 Prohibit name clashes in Effect.Tag (#2787) 5fb0b338e0 Replace Postgres.js parsed options in `PgClient.config` (#2783) 7cc8020183 wait for callback in node http server responses (#2796) 78ffc27ee3 add `optional` and `optionalWithOptions` API interfaces (#2794) 559bc6a28b add Getting Started docs for HTTP Server (#2771) c1eaef9104 Update the definition of `Handler` to utilize `App.Default` (#2784) 577e540333 add note about `Schema.Struct({})` (#2790) b04c868fcb add documentation for `Schema.OptionFromUndefinedOr` (#2786) 674b13234b Version Packages (#2781) 72a172ccba add test for Trimmed pattern (#2782) 5432fff7c9 Change trimmed json-schema pattern (#2775) 19da8d06a7 Version Packages (#2780) c1e991dd5b fix Config.Wrap for optional properties (#2779) fe2a5bcf4e Version Packages (#2777) 64c9414e96 Improve Cause Rendering (Vitest) (#2747) 963b4e7ac8 Chunk difference methods (#2658) 713574813a capture stack trace for tracing spans (#2673) 146cadd9d0 fix(platform): http client body stream runtime (#2694) 870c5fa52c support $is & $match for Data.TaggedEnum with generics (#2702) 2cbb76bb52 Add do notation for Array (#2678) 4e98821979 Revert "Release queue: minor (#2690)" 76efa4769b Release queue: minor (#2690) ed965315f3 Version Packages (#2764) 54d8580615 Test build output before publishing (#2759) 17da864e4a Add onNoneEncoding to schema.optional (#2772) dc24efe7cb schema: add recap section to README.md (#2774) 596aaea022 fix for Infinity delays in Schedule (#2773) 9196fff545 Added a note about the usage of attachPropertySignature (#2770) ff0efa0a14 feat: allow point-free usage of Schema.make constructors (#2769) ba246e40d4 schema: add documentation for the Forbidden parsing error (#2766) 17fc22e132 Add `{ once: true }` to all `"abort"` event listeners for `AbortContr… (#2761) 810f222268 fix Config.Wrap incorrectly wrapping functions & arrays (#2762) e63057ca4d add examples to repeat, repeatN, repeatOrElse (#2760) 0b2629c246 Version Packages (#2758) 9c514de281 Re-publishing schema due to empty root entrypoint (#2757) f2ce49849d Version Packages (#2753) ee0859398e Improving Predicate Usability of `Schema.is` (#2756) dc06f273fd Browser Geolocation API (#2691) 01b4553df4 sql: use constants from otel semantic-conventions instead of hardcoded values (#2712) da6d7d8452 `pick`: do not return a `ComposeTransformation` if none of the picked… (#2746) 4a82356ddc fix snapshot workflow permissions (#2752) 9991f4da96 Version Packages (#2751) b8579425e9 Version Packages (#2748) fdb7203ac0 add backup api to sqlite-node client (#2749) 6ac4847944 fix memory leak in Socket's (#2750) 40c2b1d923 update otel deps (#2736) fb8f1b0958 Version Packages (#2739) 992c8e2153 `Schema.optional`: the `default` option now allows setting a default … (#2741) 89a3afbe19 add `cause` in errors thrown by `asserts`, closes #2729 (#2738) 0d1f513335 Schema(readme): improve `suspend` examples (#2735) 799f469512 Schema(internal): remove temporary usage of jest/expect (#2734) c1b9a8ff42 Version Packages (#2733) e41e91122f fix: vitest custom tester with toMatchObject (rebase) (#2732) 50b8bd566d Schema / Document how to introduce `jsonSchema` annotation type constraints in user-space. (#2726) 423dd38c1c Version Packages (#2724) d7e49971fe Schema 0.67 (#2634) ac97186b52 Version Packages (#2723) d4fb55dc04 run .close() when browser worker shuts down (#2722) 8c35b2fde6 Version Packages (#2716) 1f6dc96f51 fix(platform-node): EADDRINUSE error reporting (#2706) 266767cb58 chore: fix `Rpc.Rpc.Proto["schema"]` type (#2714) 55ac93ad7e Version Packages (#2711) 121d6d9375 Class API: avoid access before initialization when creating a class w… (#2710) 48a543e0e4 Version Packages (#2704) d57fbbbd6c replace isows with WebSocketConstructor service in @effect/platform/Socket (#2703) bfd5a7935f Version Packages (#2699) 5866c621d7 fix http ServerResponse cookie apis (#2698) 25616b85b9 update sql readme (#2697) 7e60860011 Version Packages (#2695) 07242746ed make @effect/sql dialect agnostic (#2693) e4c9c13f00 Version Packages (#2680) e4b82d239b ensure http response middleware is interruptible (#2689) 56ec8ddc34 Fix placeholder count in `@effect/sql` helpers (#2684) d3cf2d8003 Fix `sql.update` default arguments (#2685) 0f88814f4d Add `prepare` and `types` to `PgClientConfig` (#2683) 3b3098a7ec standardize the imports across examples, closes #2659 (#2686) 915ebec404 internal: centralize code related to do notation (#2681) 2e1cdf67d1 ensure all type ids are annotated with `unique symbol` (#2679) 74f07b819a Version Packages (#2675) e5e56d138d Allow structural regions in equality for testing & update vitest to 1.5.3 (#2670) 81c8bff313 Version Packages (#2671) 7719b8a735 allow http client trace propagation to be controlled (#2672) 9deab0aec9 move http search params apis to ServerRequest module (#2669) ebd865639d Version Packages (#2657) a79c003d21 fix FiberRefs test c3c12c6625 add SortedMap.lastOption & partition apis (#2652) ba64ea6757 add `Types.DeepMutable` (#2628) 92d56dbb3f add $is & $match helpers to Data.TaggedEnum constructors (#2620) b5de2d2ce5 add Effect.annotateLogsScoped (#2618) 0ec93cb4f1 rename and export platform file schemas (#2610) a023f28336 add SpanKind to Tracer spans (#2597) 1c9454d532 add Effect.timeoutOption (#2541) a1c7ab8ffe added Stream.fromEventListener (#2513) 5cd63d2262 Version Packages (#2641) 557707bc9e update dependencies (#2656) 5ac124fe31 fix typo in Effect.never documentation (#2655) f4ed3068a7 Actually fix Cause equality (#2654) e79cb83d3b Consider type of failure in Cause equality (#2653) 46b1327ff0 fix issue where "self" can be undefined in /platform workers (#2651) 16039a08f0 improve error messages for Http.client.filterStatus* (#2650) d1d33e10b2 fix: type import from "effect" peer (#2648) 661004f4bf fix: Effect.forEach overload (#2640) 69d974b2d7 Version Packages (#2638) 18de56b4a6 Avoid treating completed requests as interrupted when race conditions occur (#2637) 0517e3e3bd Version Packages (#2616) 8206529d6a add support for data-last subtype overloads in `compose` (#2631) ffe4f4e95d Make sure GenKind utils are backward compatible & don't generate loops (#2625) 027418edaa Revert removal of custom `NoInfer` (#2626) 8492f5b944 allow for multiple otel span processors & metric readers (#2623) e7d1607dba add db.name span attribute to mysql & pg (#2622) f363af08df sql tx spans and semantic conventions (#2621) ac1898eb7b feat: BatchedRequestResolver works with NonEmptyArray (#2609) 7d9950e634 Fix snapshot publishing with provenance (#2615) 2b307497fe Version Packages (#2612) 868ed2a8fe feat: foreach preserve non emptyness. (#2608) 6222404467 simplify EffectGenerator type (#2611) ba67a5bb7e Version Packages (#2596) 9a24667200 allow use of Effect.gen without the adapter (#2602) dd41c6c725 Remove excessive constraint from the pipeable overload of `attachProp… (#2595) 0983c8b758 Version Packages (#2584) b3b51a2ea0 redact some common sensitive http header names in traces (#2589) 9dfc156dc1 Schema: remove non-tree-shakable compiler dependencies from the Schema module (#2586) e4ba97d060 Schema: JSONSchema - rearrange handling of surrogate annotations to o… (#2585) 80271bdc64 Fix JSDoc typos in `BooleanFromUnknown` and `split` functions for `Schema` package (#2583) 68a8b5f22e Version Packages (#2580) b3fe829e8b Schema: add default title annotations to both sides of Struct transfo… (#2582) a58b7deb8b Fix formatting for Class and brands AST (#2581) d90e8c3090 Schema: JSONSchema should support make(Class) (#2579) c2df604122 Version Packages (#2578) 773b8e0152 Schema: partial / required: add support for renaming property keys in… (#2577) 3f69580940 Bump `pnpm` version (#2574) e839f7191a Version Packages (#2576) d2ebe0e48a add otel Resource.layerFromEnv, for constructing a resource from env variables (#2575) c8c798a46a Update `@opentelemetry/*` peer dependencies to ensure allowed versions include used imports (#2572) 7c37aa1215 Update README for the typeclass package (#2571) 51fe09a173 Version Packages (#2570) faf6abe7ec use op-sqlite for sqlite react native (#2569) 8ae40ac704 Version Packages (#2566) a7b4b84bd5 add Match.withReturnType api (#2568) 6f38dff41f add more span attributes to http traces (#2567) a3b0e6c490 add Http.response.void helper, for creating a http request that returns void (#2565) 0059a3196d Version Packages (#2564) 2cecdbd1cf Re-enable provenance publishing (#2562) ce8440d57a Schema: expand the Zod comparison section to include a list of code c… (#2563) 265ffb6eb4 Schema: refactor test filenames to align with new naming conventions (#2560) bb64b55092 Version Packages (#2559) 8d39d6554a add no-op FileSystem constructor for testing (#2558) 186766b290 Version Packages (#2557) 5ef0a1ae9b fix Command stdin being closed too early (#2556) 34b4b39a01 Version Packages (#2540) e9875da373 Use JSONC for code example language (#2554) 8edacca37f prevent use of `Array` as import name to solve bundler issues (#2555) 76277cb3b5 Use tsconfig.build.json between packages (#2535) 570e8d87e7 Improve the typings of Array.isArray (#2552) b2b5d6626b Fix `transformOrFail` and `transformOrFailFrom` signatures in the exp… (#2550) 55c7f346b4 Update intro video (#2549) 7fc41102e2 update sql.in examples in README (#2545) f838c15ca8 add sqlfx migration notes to /sql (#2544) 87c5687de0 allow fs.watch backend to be customized (#2542) 3da0cfa12c skip running effects in FiberHandle/Map if not required (#2539) cbaf2cdf85 Remove leftovers (#2536) 1713d63096 Version Packages (#2525) e81b17c69d update devDependencies (#2534) 769eafe7ae Fix typo in `AsyncFiberException` (#2533) e0474e085f refine package descriptions (#2532) e542371981 Schema: add `compose` overloads to enable a subtyping relationship in both di… (#2531) 78b767c2b1 Rename `ReadonlyArray` and `ReadonlyRecord` modules (#2529) 56483396d5 clean up sql Migrator modules (#2530) 189d4cae80 disable provenance for sql packages (#2528) 0a6088b734 add sql to 3.0 changeset c55bcf981d replace unit with void in @effect/sql 2fb7d9ca15 Add Effect 3.0 changeset (#2526) 1b5f0c77e7 add FiberHandle module, for holding a reference to a running fiber (#2524) 2eff431132 fix platform dual predicate cf69f46690 make Http.middleware.withTracerDisabledWhen a Layer api (#2491) ae09bfcf07 fix PlatformConfigProvider test 5a2314b70e replace use of `unit` terminology with `void` (#2476) 6460414351 properly handle multiple ports in SharedWorker (#2468) 6c6087a4a8 move fetch options to a FiberRef (#2467) be9d025e42 add unique identifier to Tracer.ParentSpan tag (#2459) bf9f71f3b6 use ts NoInfer in Iterable module fd55365e5c fix rpc HttpRouterNoStream return types a18f5948f1 support variadic arguments in Effect.log (#2452) 53d1c2a775 use LazyArg for Effect.if branches (#2451) 9aeae461fd Schema: remove the custom `Simplify` type that was previously introd… (#2391) e7e1bbe684 Replace custom `NoInfer` type with the native `NoInfer` type from Typ… (#2390) 9a3bd47ebd Bump TypeScript min requirement to version 5.4 d50a652479 add preregisteredWords options to frequency metric key type (#2377) aa4a3b550d swap type parameters for /platform data types (#2376) 271b79fc0b Either: fix `getEquivalence` parameter order from `Either.getEquivale… (#2243) 10c169eadc `Cache`, `ScopedCache` swap type parameters [another try] (#2239) 5c2b5614f5 Changing the `HaltStrategy.match` function signature (#2202) 2f96d938b9 Fix ConfigError `_tag` (#2206) 14999741d2 Move `sqlfx/sql` into the `@effect/sql` package (#2104) 25d74f8c4d Fix UrlParams.makeUrl when globalThis.location is set to `undefined` (#2514) 64241815fe change return type of Fiber.joinAll to return an array (#2521) e2aaaba9b1 Version Packages (#2523) b79cc59dbe move uninterruptible to route option (#2517) cd3bb6999f Add instructions for running the codemod (#2519) 0dba8044d5 Version Packages (#2490) bded4f023e Schema: consolidate schema names (#2497) 646b5da114 ensure context is available in rpc http stream (#2516) d590094348 allow http handlers to be uninterruptible (#2515) 0d3231a195 make `AST.pick` correctly handle key renames (#2511) c22b019e5e Introduce LazyArbitrary and new make (#2508) 0ca835cbac ensure correct value is passed to mapping function in `mapAccum` loop (#2507) 0aee906f03 Re-export FastCheck (#2505) 776ef2bb66 add a `RegExp` module to `packages/effect`, closes #2488 (#2493) 41c810228b Centralize error messages for bugs (#2503) da22adc650 use a FiberSet to run http server fibers (#2504) 90776ec8e8 make tracing spans cheaper to construct (#2502) e3e09247d4 Add Duration.divide (#2494) 232c353c2e simplify scope internals (#2500) 217147ea67 ensure FIFO ordering when a Deferred is resolved (#2499) e983740114 Added {Readable,Subscribable}.unwrap (#2498) 4c37001341 Schema: fix `exitFromSelf` description annotation (#2496) 8a69b4ef6a Schema: fix `eitherFromSelf` description annotation (#2495) b3acf47f9c Schema: next minor (#2440) 9881f26ee6 platform: readme: add "Obtaining Information About the Running Proces… (#2492) 870985694a add Readable & Subscribable modules (#2472) 43c5d936ce Version Packages (#2480) 75da860b4a add string to platform ServerRequest.upgradeChannel (#2487) 672f13747d more support for strings in platform Socket (#2486) 42b36519f3 Schema: add `ParseIssueTitle` annotation, closes #2482 (#2483) a4032fac1d Schema: clean up: remove useless internal modules (#2481) c6dd3c6909 Make the file tree provider the fallback in PlatformConfigProvider.layerFileTreeAdd (#2479) 759c5bbda0 Version Packages (#2478) 365a4865de add PlatformConfigProvider module (#2477) 2fc20b3b02 Version Packages (#2470) cd54fe935b change changeset target to @effect/platform-browser (#2475) 4c302f83ac fix typos in Logger.withConsoleLog changeset (#2474) 89413286d3 make Logger.structuredLogger default for browser (#2466) dadc690612 add Logger.withConsoleLog/withConsoleError apis (#2473) 58f66fecd4 Schema: Class API: Added default `title` annotation to the encoded side (#2471) d2091714a7 replace isomorphic-ws with isows (#2469) 3cad21daa5 Schema: `length` now allows expressing a range (#2465) 7e3e438d8b Version Packages (#2457) 35ad0ba9f3 fix exact optional properties type errors (#2463) 7ddd65415b Support Heterogeneous Effects in Effect Iterable apis (#2438) 607b2e7a7f remove handled errors from Effect.retryOrElse (#2462) 8fdfda6618 improve formatting of Runtime failures (#2461) f456ba273b use const type parameter for Config.withDefault (#2460) 8206caf7c2 add Iterable module (#2415) e066ae2df3 move non-streaming rpc apis to seperate module (#2455) f993857d5b use node fs/promises for readdir, to fix recursive option (#2458) 9c6a5001b4 feat: add force option to FileSystem.remove (#2427) 04e096d171 Compare with zod (#2430) 82e96a6ad0 Version Packages (#2447) 8b46fdebf2 Schema: add `set`, `setFromSelf`, `map`, `mapFromSelf` (#2456) 7cc2b41d6c Add Schema.BooleanFromUnknown (#2453) ee10a50da8 Opentelemetry: export an empty NodeSDK layer (#2433) 5170ce708c Add support for W3C Trace Context propagation (#2445) 63a1df2e4d add support for binary data with XHR client (#2454) 74a5daed0e Platform: Auto-instrument HTTP client (#2450) 71838ca7e0 fixed typo (#2448) 62a7f23937 Schema: ParseResult: add `output` field to `TupleType` and `TypeLiteral` (#2446) 30da2d15c5 Version Packages (#2429) 4cd2bedf97 preserve jsdoc and cmd-click (#2436) 6cc6267026 Schema: Enums are now exposed under an `enums` property of the schema (#2442) f2bb332926 Schema: fix ts 5.4 build errors (#2439) a31917aa4b Schema: export `null` from the AST module (#2428) ed7624cd5c Version Packages (#2418) 5ded019970 Schema: corrected the `optional` signature to exclude invalid options (#2424) dc7e97974a Schema: readme: add guidelines for messages (#2423) 9b664a72d0 Schema: readme: pattern to simplify recursive schema definition (#2420) bfc7f0837e Update schema readme (#2422) d7688c0c72 Add Config.duration (#2407) c34eb3ecae don't persist items with a TimeToLive of zero (#2421) b3a4face2a Collect exits on forEach interrupt of residual requests (#2416) 0dd62a7019 Remove the `Render` module from printer (#2378) 3981b0ab14 Schema: use bigint.{fromString,fromNumber} for bigint schemas (#2409) e3865eb97d Version Packages (#2414) 4789083283 make /platform ClientRequest implement Effect (#2413) 0fd6ebfbec Version Packages (#2412) fb7285e8d6 fix broken imports in /platform (#2411) 95ef720ee7 Version Packages (#2408) 26435ecfa0 add undici http client to @effect/platform-node (#2410) 6180c0cc51 fix interruption of parked requests (#2404) a76e5e131a Schema: extend should un-nest sub-unions (#2398) 7899e29504 platform: readme: add HTTP Client section (#2405) 126133b106 Version Packages (#2401) 54b7c0077f add BigInt.fromString and BigInt.fromNumber (#2399) 8c9abe2b35 add set-cookie headers in Http.response.toWeb (#2403) 3336287ff5 add Duration.subtract api (#2402) 47a8f1b644 expose Schema ParseOptions in /platform schema apis (#2400) 0a92b7f678 Version Packages (#2388) 75a8d16247 add Cookies module to /platform http (#2387) d17a427c44 fix: Schema.extend with nested union (#2396) b6ee13b947 Fix helper signatures (#2394) 9eb53d3725 platform: README: add some basic docs for Terminal, Command and FileS… (#2393) 3307729de1 update typescript to 5.4 (#2385) 9392de6baa preserve defect information & TaggedError message (#2359) 706b64d98f Version Packages (#2382) 970f55fc86 add name to no-op spans (#2386) 2f488c436d update dependencies (#2384) 317b5b8e8c add Redis & TimeToLive to Persistence module (#2383) 37ca592a41 add fiber ref for disabling the tracer (#2381) 6fbac51020 Schema: docs: `optionalToOptional` and `optionalToRequired` (#2380) dbcf48c144 Schema: internal: enhance error messages by eliminating the internal … (#2379) d76a50f9ee Version Packages (#2374) 9bab1f9fa5 Make @effect/opentelemetry metrics conform to the spec (#2375) 9bbde5be9a fix: default effect counters should be right type. (#2373) 64a250d375 expose vitest helpers as @effect/vitest (#2369) 1131ea4c31 Version Packages (#2367) dc7e49720d Schema template literal regex escape special characters (#2370) 1879f629d0 fallback to node fs.watch if @parcel/watcher fails to import (#2368) ffaf7c3651 Schema: Improve `All` types to encompass additional `any` / `never` c… (#2366) ea79d0406b Schema: update README (#2365) 97c68cb5f5 Version Packages (#2364) e0af20ec5f Schema: make defensive copies of exports (#2363) 4ca62287be Version Packages (#2361) 0f6c7b426e add support for watching single files (#2360) d46550c105 Version Packages (#2358) 151b7850fa Add clear API on ResultPersistence (#2355) 71fd528750 make more data types in /platform implement Inspectable (#2357) 1b3180cc64 Version Packages (#2338) 7a45ad0a5f Schema: make `partial` dual (#2356) 5f5fcd969a Schema: make `optional` dual (#2353) a45a525e7c Schema: refactor unions: remove sorting, flattening, and unification (#2350) bb0b69e519 add overload to Effect.filterOrFail that fails with NoSuchElementException (#2354) 63f83722b1 remove use of `__proto__` (#2352) 4e64e9b987 fix metrics not using labels from fiber ref (#2351) 9bfa5ce26e Schema: internal: fix typeAST, encodedAST impl (#2349) cb20824416 Schema: feedback: expose Class API `identifier`s (#2348) 595140a13b Schema: feedback: add back `BrandSchema` (#2347) 814e5b828f Prevent Effect.if from crashing when first argument is not an Effect (#2299) 6b20badebb added Predicate.isTruthy (#2336) abf5548566 Fix test on TS 5.5 that is able to infer guards (#2344) 3851a022c4 proposal for Effect.Tag conundrum (#2266) 6f7dfc9637 Schema: improve pick/omit and add support for Class (#2343) dd2765fb35 Schema: use `negate` from effect/BigDecimal (#2341) 2d013c80b7 Schema: use `not` from effect/Boolean (#2340) 5c3b1ccba1 use `Simplify` from `effect/Types` in `TaggedClass`, `TaggedError` an… (#2339) 88b85838e0 JSONSchema: allow overrides through annotations, closes #1823 (#2337) 80f72bb3bf Version Packages (#2333) 69d27bb633 add .watch method to /platform FileSystem (#2334) d0f56c68e6 Schema: add missing `Date` api interfaces (#2332) 31a33e4c41 Version Packages (#2331) f908948fd0 use Deferred.unsafeDone for websocket onclose + onerror (#2330) d76a89c7f2 Version Packages (#2329) eb93283985 set unhandled log level to none for fibers in FiberSet/Map (#2328) 9fba3f1813 Version Packages (#2326) 77bc27b925 ensure websocket events are not missed (#2327) e006e4a538 ensure Socket fibers are interruptible (#2325) 02b6dee923 Version Packages (#2324) d6d3f4ea4c remove tag argument for Machine serializable procedures (#2323) 0c0b98d0c3 Version Packages (#2322) 326b65ae8f internal: fix changeMap impl (#2321) cfef6ecd1f feedback: remove `array` overload to restore pipeability to the API (#2320) f9aa524dc3 chore: refactor Arbitrary.ts internals (#2318) 9538120cd1 Version Packages (#2317) 7b64c662ed support Infinity in DevTools metrics schema (#2316) 5e98f245d1 Version Packages (#2315) 83ddd6f410 Don't log an empty message (#2262) c362e06655 prevent unhandled fiber errors in Sockets (#2314) 52fb1b0343 Version Packages (#2313) 89748c90b3 merge Parser module into ParseResult (#2310) 4f35a7e7c4 Remove function renaming from internals, introduce new cutpoint strategy (#2290) 9971186272 add Ndjson module to experimental (#2311) 28dc61bf0f Version Packages (#2302) a75bc48e0e fix: expose missing `ParseResult` types (#2308) 7f89c8295a chore: add more toString methods (#2307) 6dfadd1371 WHATSNEW: fix propertySignature example (#2306) d10f876cd9 chore: improves the display of `Struct.Type` and `Struct.Encoded` whe… (#2305) 743ae6d12b chore: remove static `struct` from `makeClass` internal constructor (#2304) c7d303630b ReadonlyArray: fix `sort` signature, closes #2301 (#2303) bce21c5ded ReadonlyArray: fix `intersperse` signature (#2300) 1d5eaa8b42 Version Packages (#2278) 90ec9bf480 rename `headOr` to `headOrElse` to align with the standard naming con… (#2298) b320df3b12 add mutable API interface (#2297) 6479cc4ec5 Remove `propertySignatureAnnotations` (#2295) 59212123ba Refactor `pluck` (#2294) d90a99d03d add AbortSignal support to ManagedRuntime (#2293) dd05faa621 performance improvements for RequestResolver (#2288) 817a04cb2d add support for AbortSignal's to runPromise (#2285) 802674b379 added support for PromiseLike (#2280) 6ebda05601 AST: expose the `getTemplateLiteralRegExp` API (#2292) fe12bbac67 move PropertySignatureAST to PropertySignature namespace (#2291) 3e2038f1a9 Schema annotations interfaces have been refactored into a namespace `Annotations` (#2289) a1f44cb511 add option to /platform runMain to disable error reporting (#2287) d910dd2ca1 allow optional fields in http form schemas (#2286) 1cb7f9cff7 use Schema.declare for http multipart PersistedFile schema (#2284) fb943edcc9 refactor `propertySignature` and add `propertySignatureAnnotations` (#2282) 509be1a081 add SocketCloseError with additional metadata (#2283) e7ca973c54 add OpenTimeout & CloseEvent to Sockets (#2281) bdff193365 chore: better changesets (#2279) 5d47ee0855 schema: next minor (#2172) ef3a254a0e Version Packages (#2275) 068054540f improve /platform error messages (#2276) 20e63fb920 add ManagedRuntime module, to make incremental adoption easier (#2211) 8b552a28a3 improve Persistence error messages (#2274) 9be042e418 fix /platform docgen (#2273) 9fd5db60b6 remove log.txt (#2272) de0069b276 Version Packages (#2235) 4064ea04e0 add PlatformLogger module (#2269) f373529999 add Logger batching and structured logging (#2270) 0f3d99c275 propogate Socket handler errors to .run Effect (#2267) e03811e80c fix: tap and andThen fallthrough function (#2264) fa9663cb85 add WebSocket support to /platform http server (#2261) 507ba4060f Allow duration inputs to be singular (#2263) d535e4304c uncurry sortedSet combinators (#2265) d8e694040f Added Schema.sortedSet (#2241) f82875f4e2 add Machine module to experimental (#2256) 98b921a1cf send a final metrics snapshot on DevTools shutdown (#2254) 1bf9f31f07 Make sure Effect.Tag works with primitives (#2257) e466afe32f Added Either and Option type extractors (#2255) 89b5d1c4bb CLI: Print unknown argument errors (#2252) e3ff789226 add FiberMap/FiberSet.join api (#2244) 5f7925be12 chore(ci): update all CI jobs in order to not have Node 20 warnings (#2249) 613753300c Request : swap Error and Success type params + make Effect.request dual (#2238) 781dc18e23 update description (#2245) 5234a9aa26 add support for SpanEvent's to DevTools protocol (#2246) ac41d84776 Add Effect.Tag to simplify access to service (#2225) 0c2da75f49 Remove duplicate word in wizard mode prompt (#2240) de74eb80a7 Struct: make `pick` / `omit` dual (#2233) 465be7926a Schema: add `BatchingAnnotation` (#2234) aa66d87a31 Version Packages (#2220) bd1d7ac75e use less aggressive type exclusion in http router apis (#2232) 39f583eaeb TaggedClass: ensure constructor parameters don't overwrite the tag (#2222) 7535080f2e add option to include prefix when mounting an http app to a router (#2231) f428198725 Schema: add `ConcurrencyAnnotation` (#2227) fa5d3d1586 fix typo in jsdoc (#2226) 0a37676aa0 [effect] document zipLeft and zipRight (#2223) 6f503b774d Adds isSet, isMap to Predicate module (#2224) c035972dfa ensure Schema.Class is compatible without strictNullCheck (#2221) a4a0006c7f Fix documentation for `Predicate.isNull` `Predicate.isNotNull` (#2219) 4bf878df19 remove flake utils (#2204) 94229d48af Version Packages (#2218) 5d308534ca Schema: add pickLiteral to Schema literal (#2209) 6e350ed611 JSONSchema: prune `UndefinedKeyword` if the property signature is mar… (#2217) 0ba3a09c3d fix: madge false positive on `import "lmdb"` on case insensitive file systems (#2215) 12345bf795 update http examples (#2205) 505162f414 Version Packages (#2197) 0c58b32dcf add convenience client constructors to /rpc-http (#2198) aa6556f007 handle defects in worker runner (#2196) 7356dd8e6b Version Packages (#2195) 6100b5c04c fix parseResult.fromOption 55b26a6342 Either: fix `fromOption` overloads order (#2194) 136ef40fe4 Equivalence: return `Equal.equals` instead of `Equivalence.strict()` … (#2185) f24ac9f0c2 add support for `Equivalence` to class APIs (#2184) 93b412d4a9 ReadonlyArray.groupBy: allow for grouping by symbols, closes #2180 (#2181) 983892ff99 ReadonlyRecord.IsFiniteString: handle `""` literal (#2179) 40ff2fc1b3 Updated the `MessageAnnotation` type to accept a `ParseIssue` (#2171) 20977393d2 feat(either): add do notation to Either (#2157) 54ddbb720a Schema: change the `MessageAnnotation` type to be non-parametric; now… (#2170) d8d278b2ef swap `GroupBy` type parameters from `GroupBy signature (#2148) b1163b2bd6 add RequestResolver.aroundRequests api (#2145) c5686451c8 generate a random span id for the built-in tracer (#2147) de1b226282 Relax FiberRefs, make None behave like Runtime (#2139) 44c3b43653 Add a way to redact HTTP headers (#2122) 88835e575a Fix withRandom and withClock types (#2144) ff88f808c4 Fix Cause.pretty when toString is invalid (#2143) 11be07bf65 Add functional analogue of `satisfies` operator (#2080) dbff62c302 Schema: add `{ exact: true }` optional argument to the `partial` API,… (#2141) e572b076e9 allow passing structs when encoding schema classes (#2128) b415577f6c Fix internals of TestAnnotationsMap making it respect equality (#2138) a66339090a Expose Random Tag and functions to use a specific random service implementation (#2137) e787a5772e Schema: refactor the `declare` signature to ensure that the decoding … (#2123) 4e350894b2 Version Packages (#2134) 65895ab982 use Schema.TaggedError for worker errors (#2133) e70fc5e359 Version Packages (#2132) 9ee4de01bb ignore errors when attempting to shutdown otel (#2131) 025597240e Version Packages (#2129) 2b62548458 add Scope to Http client (#2119) c4bf9fd0d5 fix: update README.md (#2125) e25666cb68 Version Packages (#2120) df98f81055 revert PR 2113 (#2126) b881365035 Fix the return order of batched requests (#2114) 7eecb1c6ce ParseResult: add missing `decode` / `encode` exports (#2124) aef2b8bb63 Schema: use the `identity` function in the transformation defining a … (#2121) 855ff0b043 disable instanceof check for encoding schema classes (#2113) 4b3cd44716 Version Packages (#2118) bb09ea3d1e add XMLHttpRequest client to @effect/platform-browser (#2117) 135d53c9cd Version Packages (#2116) ec78c9566b fix Persistence KeyValueStore adapter (#2115) 1efab54323 Version Packages (#2112) 17bda66431 ensure large semaphore takes don't block smaller ones (#2107) 7fe6aaf736 Version Packages (#2109) a344b42086 Schema: add support for unions to `keyof` (#2111) 13d3266f33 Schema: make `headOr` dual (#2110) 1c6d18b422 Schema: add `transformation: false` option to `pluck` (#2108) 6c0b1b14a8 Version Packages (#2105) 0f83515a9c Ensure `RateLimiter` utilizes the token-bucket algorithm (#2097) b7d9a55ebb make Rpc.Request compatible with Persistence (#2106) efd41d8131 `RateLimiter.withCost` combinator + Rate Limiter composition tests (#2090) e1e0b953c9 Version Packages (#2095) 56c09bd369 Expand Either and Option `andThen` to support the `map` case like Effects' `andThen` (#2103) 71aa5b1c18 remove no longer necessary './internal/timeout' (#2098) 1700af8af1 optimize Effect.zip{Left,Right} (#2099) ce0c2efb46 add non-streaming handlers and resolvers to rpc (#2100) 6654f5f0f6 default to `never` for Runtime returning functions (#2096) 2eb11b4775 revert some type param adjustments in FiberSet (#2094) de59e28bed Version Packages (#2093) 29739dde8e improve type extraction for Router.fromIterable (#2091) b974658d41 add rpc Router.provideService and .provideServiceEffect (#2092) 1414c80376 Version Packages (#2086) b5a8215ee2 Fix `Schedule` typings (some APIs didn't have `Effect` parameters swa… (#2085) 5a8d379598 Version Packages (#2082) 4a5d01a409 simplify RateLimiter implementation using semaphore (#2084) be19ce0b8b Direct Port `RateLimiter` from Rezilience (#2083) e94130ae4b fix changesets for rpc (#2081) 2131a8cfd2 rpc second revision (#1966) 9dc04c88a2 Introducing Optional Annotations in `optional` API (#2079) d3f9f4d403 Schema: switch from positional arguments to a … (#2074) af47aa3719 move where platform worker spawn function is provided (#2078) fb112a41fc remove more platform re-exports (#2077) 56b86916bf Fix usage of Schema.TaggedError in combination with Unify (#2072) b2e8b9118f fix lint issues 6727474974 change `Sink` type parameters order from `Sink` to `Sink` (#2073) 5e33765156 Internal: change `HandoffSignal` type parameters order from `HandoffS… (#2070) 96bcee2102 change `Runtime.RunCallbackOptions` type parameters order from `RunCa… (#2069) 4cd6e14494 Rename schema transform params (#2065) 8dd83e854b change `Channel` type parameters order from `Channel` to `TExit` to `Res… (#2061) 52e5d20775 change `Request` type parameters order from `Request` to `Reque… (#2060) 78f47abfe3 change `Pool` type parameters order from `Pool` to `Pool` to `Fib… (#2056) c6137ec62c change `RuntimeFiber` type parameters order from `RuntimeFiber`… (#2055) 8b0ded9f10 change `Fiber` type parameters order from `Fiber` to `Fiber` to `Take` to `T… (#2052) 0e56e998ab change `Deferred` type parameters order from `Deferred` to `Deferred` (#2050) 8ee293159b enhance DX by swapping type parameters and adding defaults to async* … (#2046) 81b7425320 change `Streamable.Class` and `Effectable.Class` type parameters order from `Class` to `Class` (#2045) 60686f5c38 change `Layer` type parameters order from `Layer` to `Layer` (#2044) 7356e5cc16 change `STM` type parameters order from `STM` to `STM` (#2043) 86f665d7bd use Context for collecting transferables (#2041) 3077cde08a change `Stream` type parameters order from `Stream` to `Stream` (#2036) c77f635f8a change `Exit` type parameter order from `Exit` to `Exit` (#2035) 1a77f72cda change Effect type parameters order (#2034) 9a2d1c1468 feat(breaking): Add ClassTag, improve Effect.serviceConstants, require string identifier for all tags (#2028) 02c34615d0 feat: Make Equal an implicit trait, namely remove the type Data.Data (#2023) 6361ee2e83 fix for encoding of Transferable schemas (#2027) b1e2086ea8 remove re-exports for platform packages (#2025) 5127afec1c rename some ReadonlyRecord apis (#2024) a34dbdc155 Schema: change type parameters order from `Schema` to `Schema` (#2026) f5ae08195e use TimeoutException for timeout (#1995) e2f1ffbf90 Version Packages (#2076) 3ddfdbf914 add apis for manipulating a Runtime (#2075) b57db1cccc Version Packages (#2066) 6928a2b0ba Fix for possible stack overflow issues due to Array.push in combination with spread operator (#2057) d0b911c75f add releaseAll api to Semaphore (#2067) 330e1a4e2c add Option.orElseSome (#2071) 296bc1c9d2 Enhancement: Add toJSON for Secret (#2033) a47800bde5 Version Packages (#2064) eb1f7878c9 make array types in cli more permissive (#2047) 2e9bb98d84 fix snapshot workflow (#2051) 0400503300 Version Packages (#2040) 1b841a91fe fix ClientRequest.make signature (generic was unused) (#2039) 72e87b646e Version Packages (#2038) 32bf796c3e remove overloads from ClientRequest.make (#2037) 946492c499 chore(ci): add bun to test matrix (#2030) 10bcbc64c6 add provenance publishing (#2019) 9f89d5d4ff Version Packages (#2021) cde08f354e use Proxy for platform schema Transferable (#2020) a715073648 Version Packages (#2018) c96bb17043 Support URL objects in client requests (#2016) bda312fccb Version Packages (#2012) 13785cf4a5 Schema: Equivalence: handle transformations (#2017) f1ff44b58c Schema: add option to preserve excess properties, closes #2008 (#2011) 02d84f7322 Version Packages (#2000) f73e6c033f optimize fiber id hashing (#2007) 22794e0ba0 Add documentation to `Effect.intoDeferred` (#2004) 6bf02c70fe Schema: optional: rearrange overloads to enhance DX, closes #2002 (#2005) 0098dc76ab Fix CLI readme (#2003) aab2e4e156 Ensure single invalid variadic option is reported as an error (#2001) 78f5921150 ensure forked fibers are interruptible (#1999) 8b3cc9605a Schema: add Understanding Schema Declaration for New Data Types > Add… (#1996) 673968567d Version Packages (#1994) 003bb691f2 Fix stack overflow exception when nesting cli options / args in a `Command` config (#1990) 8be6c3e579 Version Packages (#1993) 9863e2fb35 Schema: enhance error messages (#1992) a7e7411a84 Version Packages (#1989) 64f710aa49 Schema: Class constructor: avoid overwriting `props` with `additional… (#1988) 8a76e9d176 Version Packages (#1982) eaab2e81be Default `Options.repeated` to an empty array if option not provided (#1984) 634af60a2f add lmdb implementation of persistence (#1985) fa205d09d7 Fix jsdoc for `Effect.provideService*` methods (#1979) 9cf3782a17 Fix CLI argument parsing to properly handle when a repeated option is not provided (#1980) 9ce786e585 Version Packages (#1972) 1a84dee0e9 Schema: add hash (#1977) ac30bf4cd5 Schema: Allow non-async Effects to be used with *Sync combinators, cl… (#1978) 29b23a095b Schema: update benchmarks code (#1981) c7550f96e1 Schema: fix templateLiteral signature (R should be never) (#1975) 8d1f6e4bb1 Schema: causeFromSelf: add missing type parameter (#1974) 7b84a3c7e4 Race interruptibly in `Channel.mergeAllWith` (#1971) d404561e47 Execute acquire in `ScopedRef` uninterruptibly (#1970) 229d0579c4 Version Packages (#1969) fdf7b0e664 Ensure data-loader worker fiber can be interrupted (#1968) 41b32c8018 Version Packages (#1965) 62b40e8479 add context tracking to Schema, closes #1873, #1954, #1956 (#1922) 645bea2551 Fix signatures related to predicates, closes #1916 (#1958) de4cb049a3 Fix the parsed letter case for variadic and key/value CLI flags (#1963) 84da31f064 ReadonlyArray: fix `sortWith` sig, closes #1961 (#1964) 7e16222dc4 Version Packages (#1953) ee4ff8a943 add Fiber{Set,Map}.runtime api (#1941) fe05ad7bcb fix ClientRequest stream bodies (#1959) 202befc2ec Effect: cache the `FiberId` hash in the constructor (#1957) 10df798639 avoid sleep for zero duration in schedule (#1952) e574992de2 Version Packages (#1950) 21b9edde46 fix: runFork with Scope should not run OOM (#1949) 7a786d46ec Version Packages (#1946) 3bf67cf64f Schema: fix `getNumberIndexedAccess` signature and implementation (#1945) 9c5986cc64 Version Packages (#1943) d21e028fe2 fix Options.mapEffect export (#1942) 4a4195c9bd Version Packages (#1940) 9a0d61f674 Rename mapOrFail to mapEffect (#1938) 7287fbe9ec Version Packages (#1936) 0d1af1e38c fix `head` and `headOr` signatures (#1939) a2225247e9 ReadonlyRecord: add `mapKeys` / `mapEntries` (#1934) 86180cc961 Introduce the `executable` argument to `CliApp.make` (#1925) d04d9f55cf Version Packages (#1929) f493929ab8 Schema: re-add getPropertySignatures (#1933) 5911fa9c94 Schema: add pluck (#1932) b88096aa1a Schema: add fallback arg to headOrFail and rename to headOr (#1931) d5432213e9 Schema: add optionFromOrUndefined (#1930) 2530d470b0 Schema: add head / headOrFail (#1928) 112e5041d5 Version Packages (#1927) 169bc3011e use sliding queue for DevTools client (#1926) 2bcd781649 Version Packages (#1923) edb722df37 allow undefined in rpc schemas (#1924) 05c44b3066 Release queue: minor (#1919) 1167f68bec Version Packages (#1921) f7f19f66a5 add FiberMap.remove (#1920) 2d6f2e9332 Version Packages (#1893) 540b2941dd Add `Trie` data structure (#1871) 536c1dfb78 add FiberMap & FiberSet modules (#1897) a3f96d615b convert value bag classes to object literals (#1903) ba483caf1c chore: use Schema.Variance in PropertySignatureImpl (#1915) 60f09c93d7 remove duplicated `SchemaImpl` class definition (#1914) b557a10b77 ParseResult refactoring (#1913) ec2bdfae2d Schema: refactor `eitherFromUnion` as union of transformations (#1910) 2a40f53f81 Schema: Chore - Re-added the benchmark folder from the archived repo (#1912) 74b90940e5 fix formatting of suspend before initialization (#1909) d1c7cf54fd add ConfigFile module to cli (#1907) 4863253bfc add file parsing apis to cli (#1899) 71ed54c3fb fix(platform-node): change MAX_SAFE_INTEGER to a valid number (#1905) 0c397e7620 Schema: show error message when throw ParseError (#1906) 337e80f69b Chore/schema more annotations (#1904) 1283a32bab remove `console.log` in test (#1901) 4ef1e6f4e0 add Schema apis to cli Options & Args (#1898) 48a3d40aed make worker initial message type safe (#1895) 25adce7ae7 allow pre-validated cron expressions for Schedule.cron (#1894) 8eec87e311 Struct: add `get`, closes #1890 (#1891) 687e02e7d8 Schema: fix ParseIssue.actual on transformation (#1892) 2198fa46bf Version Packages (#1886) 1d3a06bb58 Avoid killing all fibers on interrupt (#1885) 5b46e996d3 Schema: treat missing properties as `undefined` (excluding `is` and `… (#1888) 210d27e999 Schema: add eitherFromUnion (#1794) 87f7ef28a3 Add `Cron` module and `Schedule.cron` constructor. (#1884) d389a7801f Version Packages (#1881) c4b84f724a remove `from`/`to` APIs (#1878) d626c28bcb Schema: update arbitraries section (#1880) fc444d780a Version Packages (#1876) 92c0322a58 add http Multiplex module (#1879) a904a73945 Schema: Arbitrary remove `from`/`to` API in favour of `make` (#1877) 7b2f8743d9 Schema: refactor `ParseResult` module (#1842) 6318090b7c adjust pull-request contribution instructions (#1872) 55a9d68f0b Version Packages (#1868) d3d3bda74c don't add Transferable to schema types (#1869) 4c90c54d87 support context propogation in platform workers (#1870) d5a19499aa add index argument to many functions in ReadonlyArray (#1850) 6978c0789e Schema README: Update outdated Parse JSON section, closes #1866 (#1867) 0e619913e5 docs: add Url example to schema readme (#1856) 109aa6adb6 fix various api reference links (#1862) 2493bf357c fix api documentation links for `/schema` (#1861) 496db532b4 Version Packages (#1860) 16bd87d326 fix: expose `LiteralValue` type (#1859) 6269299faf cli: Tutorial in README missing arg/options for clone subcommand (#1857) b635058b9d Version Packages (#1791) 072421149c Add Option-returning overloads for findFirst and findLast in Readonly… (#1854) 78fec17bf1 fix Args.optional returning Option (#1853) 04fb8b428b Avoid default param initialization (#1848) 389a8b1c7f Add `/experimental` package (#1849) a23a068337 add email example (#1844) 684ee3baf8 add per-project scripts for convenience (#1845) bcf0900b58 Micro optimization to avoid inline creation & spreading of objects / arrays (#1847) 693b8f3a3d Enable `exactOptionalPropertyTypes` accross the board (#1846) 9f2bc5a19e add Config.literal (#1795) 7b5eaa3838 make serviceFunctions accept an Effect (#1797) 6299b84c11 Uncommented linesIterator string function (#1798) c0aeb5e302 add Route to RouteContext (#1799) 99d22cbee1 Node HttpClient treat just upgrade response as successful response (#1796) 14e4393ebe only deploy docs on main organization (#1792) 0535c1acd3 run workflow fa3d68d31c fix docs d987daafad switch to caret for effect d0471ca7b5 fix publish script c5c987d7a5 apply new linting rules 196b09e611 updated package.json files d74f723cf4 adding package files 40462f4325 Merge remote-tracking branch 'rpc/main' 2ee0966002 Merge remote-tracking branch 'printer/main' 83028ca582 Merge remote-tracking branch 'platform/main' 8a6b4d6bd6 Merge remote-tracking branch 'typeclass/main' 1e60713a39 Merge remote-tracking branch 'cli/main' 4a41381c39 Merge remote-tracking branch 'opentelemetry/main' 757a2299a7 Merge remote-tracking branch 'schema/main' 8e51bf4463 Merge remote-tracking branch 'effect/main' ba9e404e9f workspace skeleton e546bc30fa Version Packages (#685) a20996ea90 Schema: add annotations argument to `attachPropertySignature` (#679) a704f4f784 improve: Actually use Arbitrary interface in to/from/unsafe signature (#684) 5f6cbb7c78 fix examples typechecking (#108) 3c666360bf Version Packages (#433) 33f035c084 update effect (#432) 5e48223461 Version Packages (#164) e74816ffa7 update dependencies (#163) 794df2bbd2 Version Packages (#457) 36f24988e2 update effect (#456) 73cbdc0911 Version Packages (#370) 677d87eda0 update effect (#373) 5a280a67af Version Packages (#107) 79ca9aa11c update effect (#106) 19e30a08cb Version Packages (#674) b5f25fa449 Version Packages (#69) ae81ec5bae update effect (#673) f7072309bf update effect (#70) 7befa92b38 Version Packages (next) (#1783) 50459adba9 use NoSuchElementException for more optional apis (#1780) 87145c93c7 update dependencies (#372) 4dc5e5380f rename schemaBodyMultipartJson to schemaBodyFormJson & support url forms (#371) e32cef0b89 rename server FormData module to Multipart (#369) 31c1b8e3e9 Version Packages (#668) dd4c1eccb1 Schema: Inconsistent Naming and Simplifying Schemas (#669) df0bb415e0 Schema: refactor property signature APIs, closes #648 (#649) f3d0da3a09 only add one predicate in Match.discriminators (#1786) 2856eedb66 simplify Match extraction types (#1785) 329e589017 Version Packages (#666) f96d6cb210 JSONSchema: Ensure proper handling of identifier annotations when gen… (#665) 373840b0e1 add Layer.empty (#1782) baccf15356 Version Packages (#431) 191dc7dfca Version Packages (#162) 7a51f7dded update effect (#430) 5f8d15463f update effect (#161) ff4d661b37 Version Packages (#455) f76bfb992e update effect (#454) 326c882d17 Version Packages (#368) 9ef1193802 update effect (#367) 8ccdd98673 Version Packages (#105) 4b1eb960ce update effect (#104) 7960a17256 Version Packages (#663) 151ac99e51 Version Packages (#68) 16ad627d8c update effect (#662) a6beb6d131 update effect (#67) 152be12c28 Version Packages (next) (#1769) 995decb2a0 Version Packages (#364) b52c6c28de Add constructor for creating a config provider from a json object (#1778) 3b6dd0ba3b self-assign normalized `BigDecimal` (#1776) 6f0a1d8526 run test blocks concurrently by default (#1773) a8a23c56a3 add Scope for every http request (#366) ecf5b58a0d respond with 503 on server induced interrupt (#365) 338591f1b4 fix schemaNoBody (#363) 02fb55efa5 Version Packages (#362) 6bd8b5b068 fix headers type for schemaJson (#361) d02d66f0a8 Version Packages (#360) fc7fd500ed use branded type for Headers (#359) 710d0e389b Version Packages (#358) 5bc8dd726e respond with 499 on interrupt (#357) faf2d80c50 run tests concurrently by default (#356) 450635824a Version Packages (#355) e18cd6987a add Layer support to SerializedWorker (#354) 51a69f9458 use relative imports (#64) fd69c2b5cd fix example style (#65) 53fcb19318 Version Packages (#353) 0cab0eb013 interrupt workers on all failures (#352) dabb9022e6 Version Packages (#351) 62c318d7d5 add decode option to worker runner (#350) 4212f28ece Version Packages (#349) 07d78c0760 refactor worker runner apis (#348) 30b0bfe58f Version Packages (#347) 1bc541a619 ensure node client doesn't throw on interruption (#346) 7cebbfd4b0 ReadonlyRecord: add `fromIterableBy` (#1772) 921b813a7a adjust metric boundaries for timer histograms (#1770) badf0fb483 Version Packages (#345) e4c729ee8c fix type import (#343) a93803153d SerializedWorker improvements (#344) 28bf592abb Version Packages (#658) 64196fd5e0 add Secret (#654) fb1be2a597 Version Packages (#429) 30e2b1f476 Version Packages (#342) 0609addbae replace http router with find-my-way-ts (#341) 6a2c5af6ae Version Packages (#657) f5bef5df1f Schema: fix `DocAnnotations` definition (#656) 000f412b41 Version Packages (#339) 25604c6e96 change http serve apis (#338) 0211407294 Effect.mergeAll should work when Z is an iterable, closes #1765 (#1768) 73e54e9f9a fix for multiple handler transforms (#428) 10fe22cc03 Version Packages (#427) cf38149edf add Command.provideSync/provideEffect (#426) 4f9bf2f197 Version Packages (#160) c4de391b15 Version Packages (#425) a29e186100 update /platform (#159) 19e37550d1 update /platform (#424) 43d796fe4b Version Packages (#336) 48eb4b4dad optimization for Effect handlers (#337) 0c78249896 add SerializedWorker (#335) da63e3e801 Version Packages (#653) e522c78e8b fix for never error types in TaggedRequest.Any (#655) 5d8d0ac846 Version Packages (#423) 997da481cf add apis for manipulating handlers (#422) 3680ad639c feat: added toString for Schema classes (#605) 702646aedf feat: added Duration filters (#604) 0baa202b13 Version Packages (#63) cbd39fe209 fix peerDependencies (#62) 8e39d49701 Version Packages (#413) 8ca5383d84 update effect (#417) 423c48d31f Version Packages (#158) efc2fc5397 update effect (#157) e90c0bc2cf Version Packages (#453) f5f30de0b4 update effect (#452) a2f6fd7562 Version Packages (#332) b0187a0e30 Version Packages (#61) d3e7250d54 update effect (#331) 893a2f96de update effect (#60) c239ee0c1a Version Packages (#103) cf15acc9b2 update effect (#102) 1eb8adb90d Version Packages (#639) a4a6d7386c fix peerDependencies effect version (#651) 28b577d247 update effect (#650) 7b1f925fff Version Packages (next) (#1750) 442b73a390 generate proxy packages (#1747) ebd1fd6d44 ensure that JSON Schema annotations can be exclusively applied to ref… (#645) f688585a94 Duration: add tuple labels to DurationInput (#1762) aafcc562e6 update language service (#1760) 3ad7a57aba filterOrFail: add example with user-defined type guard (#1761) 94b7c4a363 Version Packages (#330) ff22e53d86 Platform-node http client add error event listener in waitForResponse to handle ECONNRESET socket errors (#329) 90aceff19a Make help documentation print built-in options by default (#416) 22eb0a855f Version Packages (#327) 6439ab8d10 add Router.schemaSearchParams/schemaPathParams (#326) fb1cc2dc23 fix stack filtering for first throw point (#1756) c23942569e Fix provideSomeRuntime internals, restore context and flags properly (#1758) e99e2da9a9 Add primitive for holding sensitive / confidential values (#404) 3f4275c88a Default `CliConfig.finalCheckBuiltIn` to `false` (#411) 4e9c642752 update docgen (#643) f7b0d3b32d Effect > andThen: add examples and missing dtslint tests (#1757) 7be6c05aca chore: switch to tinybench (#642) 084771443f chore: Schema: refactor Arbitrary definitions (#641) 49d771b3cd Effect: remove `config` API (since `Config` now implements `Effect`) (#1755) 86736917af ParseResult: rename `ParseErrors` to `ParseIssue` (#640) cf05e9473d Schema: rename lazy to suspend (to align with Effect.suspend), closes… (#636) 3785d38b7d JSONSchema: rename `JsonSchema7Top` to `JsonSchema7Root`, closes #634 (#635) c1f15ede1c ParseResult: do not redefine `try` (#638) 7b5285f063 chore: add record('a' | `prefix-${string}`, number) test (#637) c0bc6a4c21 Version Packages (#325) 89fd07562c improve serve api (#324) 0bf888ede3 Remove `orElse` variants from `Command` (#409) 4c9041c8ee make Config implement Effect (#1754) a1a3044bbf Expose `Console` context tag (#1753) 50451ec2b5 Fix the jsdoc for Effect.withConsoleScoped (#1749) 9829199624 chore: simplify declarations code (#633) ce1b7fa43c Version Packages (#408) c64903ddc5 update effect (#410) a13df7de08 Version Packages (#156) fa8abd6a51 update effect (#155) 8dbae29997 Version Packages (#451) 2e34c21533 update effect (#450) dd42bc6c95 Version Packages (#320) 3479fd97a1 update effect (#321) c2437a385b Version Packages (#59) 5263717cd9 update effect (#58) e0293ac73d Version Packages (#101) f2af8b194a Version Packages (#631) d098ea2322 update effect (#100) 2c220780b1 update effect (#632) 9463b6c3bb add Serializable module (#609) ddeeb610c2 Version Packages (next) (#1725) 952a2827d2 Rename `ConfigSecret` to `Secret` (#1745) 57b0456e6a remove fork example (#1744) 9e29f76c7c improve performance of fiber forking (#1743) c18a40541f Add `readLine` to `Terminal` and make `columns` property an Effect (#319) 4fd8f6dbc2 Use Nix to manage all dependencies in CI (#1728) e0526e69b6 ReaonlyRecord: rename `fromIterable` to `fromIterableWith` and add st… (#1738) e3d9f50347 Version Packages (#630) d3af6ce4f2 Schema: add filter overloading returning Option (#629) deb40ac143 Chunk: add splitNonEmptyAt (#1737) b8349e514a ReadonlyArray: add splitWhere and split (alignment with Chunk) (#1736) ecea784d26 expose Layer MemoMap apis (#1735) c48bc4b17b updated BigDecimal.toString (#1729) ab0cd7ba0c Add `Console` combinators to `Effect` module (#1733) 116c04cd27 Version Packages (#627) 8f45996566 S.rename: handle field transformations, closes #625 (#626) 15e5da41e9 fix: expose `JsonSchema7Top` and `JsonSchema7` dts (#623) 72acd2cf0a Version Packages (#622) 203ffee440 Schema: add `fromJson` combinator (#621) 2975a8313f fix Subcommand type extraction (#407) e5bd625868 Version Packages (#317) 10a3cd560b Fix number of retries in retryN (#1727) b955441c98 Version Packages (#620) 3b507a2215 add Schema.mapLiterals (#617) 74425b3a7b Chore/refactor internals (#618) 70eed81206 Version Packages (#406) 73fdd9a42c add Args/Options.withFallbackConfig (#403) 6ca94d7a0a add logging to runMain (#316) 5080184aca Version Packages (#374) 4af3ee64f1 Ensure error on excess args (#402) 8bdc4ae216 Add tutorial to README (#401) 319a4b0800 fix the tacit use of some NonEmpty unfied APIs (#1726) dedc899265 Version Packages (#315) 0c9026229c refactor node command executor (#314) e20ccf2081 fix the tacit use of flatten (#1724) 53ddac2e32 Version Packages (#313) 29cacb8a8c scope commands to prevent process leaks (#312) 774a9cb78c Version Packages (#99) 8bd89eae87 update effect (#400) 915105e3bb Version Packages (#154) ba07622167 update effect (#153) 1fcf795e7b Version Packages (#311) b1d7d053df update effect (#310) 5faa11aff8 Version Packages (#449) 21ad10fe7e update effect (#448) 165d3aca8f Version Packages (#616) 8d4f717b82 update effect (#615) ad74dbe84d update effect (#98) 20b3bac5ea Version Packages (#57) 1de437a180 update effect (#56) 31f1eb4474 Version Packages (next) (#1721) 6e5c7dda4a update build setup (#1722) 1c6d5775e5 Allow `--wizard` for subcommands (#399) 2f7589d210 Version Packages (#309) 3df39ed213 fix mime version to preserve cjs support (#308) 21a0dc34f8 update effect (#398) 43b505a496 Version Packages (#152) 0fc696b340 update effect (#151) a0f30e2c7c Version Packages (#305) 564c92e43b update effect (#306) 1ce1f62c9a http client api updates (#304) 4d4a043b4a Version Packages (#447) 18eb6de0a6 update effect (#446) e4a5f1285a add Http/App test (#303) 3559642fd1 Version Packages (#614) 41a260b15e fix jsdoc for Inspectable.format (#1720) 52171682b6 Version Packages (#55) a45714ad3f update effect (#613) 50d135756e Version Packages (#97) f5257b93ff update effect (#54) 1ca6b4cc0e update effect (#96) 045274674a Version Packages (next) (#1682) 3dcf42777d add Request.failCause (#1719) 037d5d5157 fix withDescription for mapped commands (#397) 8dee44bec7 Fix prompt styles (#396) 7587eeecfd Add final confirmation to run command during wizard mode (#395) 0ff94e01a1 Refactor the internals of prompt rendering (#394) c628452c07 ReadonlyArray: merge NonEmpty APIs into base ones (#1717) 4fb9fa71fd simplify async constructors (#1715) cab5e2d57d Version Packages (#445) a0536d21fb Refactor and enhance the external api of printer-ansi (#444) 92b4955779 Consistency between batching and fiber state (#1706) 8f0158e882 Get rid `absorb` mention in Effect module (#1718) a15673d4cc List: merge NonEmpty APIs into base ones (#1716) 1231b06feb localized handlers for commands (#390) 3a55169684 improved Duration.toString (#1703) e5763b73c6 Version Packages (#299) 6577426dbe add .toWebHandler* to Http/App (#298) 26ef716f69 Version Packages (#599) 92bf23d319 improve JSON Schema output (#585) 54aa5f9f97 Refactor ParseResult APIs (#593) 66db8833bb Remove redundant `ValidDate` schema; it is simply an alias of `Date`. (#589) 29e4a8c704 Bug Fix: align index signature behaviour to TypeScript (#607) 64f9fdb570 BigDecimal: rename `toString` to `format` (#1713) 2617125d2f add `.getLefts` and `.getRights` and rename `.compact` to `.getSomes` (#1704) aef82ea3fa Add Either.filterOrLeft (#1694) 09e1324efc Chunk: merge NonEmpty APIs into base ones (#1711) 9624b2d3e3 Fix ReadonlyArray.difference description (#1712) c87830fbae Layer(docs): fix merge / mergeAll categories and add dtslint tests (#1710) fc32857957 Layer: rename `zipWithPar` to `zipWith` (standard) (#1707) 16755265b4 replace Layer.provide* with Layer.use* (#1688) 922aad5121 allow custom description for `timer` metrics (#1701) ef0059bf74 Fix shell completions (#387) b5cd64ac18 TreeFormatter: enhance `formatActual` for data types with a custom `t… (#603) 3adc1118fc Config: standardize error messages (#1699) a36733eb31 chore > docs: standardize mapping / zipping / sequencing categories (#1698) 3392607970 expose currentConcurrency fiber ref (#1697) ee960bf747 feat: added Option.andThen (#1695) b5f18815fb Version Packages (#296) 1093415caf expose node channel apis (#295) 24027681c8 Add BigDecimal (#597) ea14159bb0 chore: exclude variance implementations from test coverage (#1693) 536483f5d2 add PrimaryKey.value (#1692) 0f6a7e69b2 add vitest browser sharding (#1691) a31fd91580 allow omission of Scope type in R of Stream.asyncScoped (#1690) e52b18ff67 docs: improve `Pool`'s `makeWithTTL` JSDoc example (#1689) 2482da8663 Version Packages (#596) 4e87059472 fix YieldableError.toString crashing on react-native (#1687) 6cf7cc3c16 add _tag to TaggedRequest.Base (#595) 887b90bb6e Version Packages (#591) 08b195f3ad Types: add variance helpers (#1686) 73d26d145c fix typo: change "Option" to "Either" in Either.ts module documentation (#1684) 49236eb181 fix: fixed hrTime type arg order (#594) fb6f89b55a feat: added ParseResult.try (#592) 0c55d9ab3e add support for Duration (#582) 88ac6d9779 Parser: should use the original ast to generate a more informative error m… (#590) c290d21787 Version Packages (#588) 84d701711e DateFromSelf: its arbitrary should also generate "Invalid Date"s (#587) 9c049ff428 Version Packages (#294) 38e8c7746a add ability to disable http tracer with predicate (#292) 01f87c7278 ensure http methods are uppercase (#293) 0210259c40 add default type to data class props generic (#1683) ade2e8ce6c fix error value of `Effect.tryPromise` (#1679) 9b66217a9c forbid excess properties when matching tags (#1681) 7e2416646e update dtslint (typescript 5.3 support) (#53) d37d5f2d8b update dtslint (typescript 5.3 support) (#584) ddaed4aded update dtslint (typescript 5.3 support) (#1680) 4fc6873b27 Config: add more tests (#1677) 420be8a257 Version Packages (#581) 6310424cae fix missing class .struct schema (#580) ba9a251384 update effect (#388) c3562ec37d Version Packages (#150) 1f3496d40f update effect (#149) f293a832ce Version Packages (#443) ef9d1336a6 update effect (#442) 94cfc1db80 Version Packages (#95) cd5ee5f46e Version Packages (#290) f4a61e29f0 update effect (#291) 2818cb2bf6 Version Packages (#52) f5658a6d74 update effect (#51) 121b9e13ae Version Packages (#578) 53ea1b01cf update effect (#94) 8e81615a30 add TaggedRequest class (#574) c44a16861c update effect (#577) 10d8fb9d98 Version Packages (next) (#1656) 8f830d7792 Cause.YieldableError extends Inspectable (#1678) f0aa8651d9 update dependencies (#289) 7e2711126f add PrimaryKey module (#1669) d0efa36003 allow hrtime as `Duration` input (#1674) 7446fe2aac refactor built-in exceptions (#1671) 991da14c7c Config: propagate the path in validation, closes #1667 (#1668) cb528506e3 support null/undefined in PubSub, TPubSub & TQueue (#1676) bff93b2b87 Cleanup console logs (#386) d2e8a0b204 Support multi-valued arguments appearing anywhere in arguments (#385) b6d5877817 remove `size-limit` for now (#1675) 757940af26 fix size limit (#1673) daec67d322 add Metric.unsafeRegister (#1672) 50f0a9e116 Implement `Args.withDefault` (#384) d4fb9ea47d Implement support for variadic `Options` (#383) 3e052e705c add Request.Class and use it as the base for TaggedClass (#1670) 2dda70e416 Fix the type signature of `Options.keyValueMap` (#382) a495bda988 Fix completion script generation and add a quick-start guide (#381) a5c28a3257 Version Packages (#288) 511e74e861 expose ServerResponse.setStatus (#287) 946bc965e4 Version Packages (#286) 1c0e2cf7b9 Avoid mutating global state with `Terminal` service (#285) f8c587e47c Version Packages (#284) 6fccc884b7 adjust services in Node/BunContext (#283) b253e51caf Version Packages (#573) b26c96968b Effect.catchTag can be used to catch ParseError (#572) a96c033d87 Fix generation of completion script (#378) b5db508531 Version Packages (#281) 50986e61ec Add Terminal to NodeContext and BunContext (#282) a0f75cfc9f Recursive interrupt all fibers on kill (#280) 4a25944890 improve the DX of the APIs that return a tuple by assigning meaningfu… (#1666) fb09358ed1 Chunk: fix partition signature (expose the index of the element) (#1665) 75f0eb6d5b Version Packages (#571) a42c550669 Introduce Effect.andThen (#1603) ee8d93da2a make ParseError Inspectable (#570) 888f3f1673 PollingMetric: renamed to MetricPolling (standard) (#1664) 4f1e6964c8 update docgen (#50) 7a5af4a36f exported interfaces: fix variance (#1662) 9695842839 Make all modules better support tree-shaking (#375) ee60b1a4f8 Version Packages (#569) c27b4b27b9 Version Packages (#278) 74f507bd69 forkScoped (#279) 50e2319cf7 wait for ready latch in worker (#277) 6fb0da7b3b add TaggedClass to /request (#1663) 2b3219fde8 add tagged class variants (#568) 68d80cbbc4 improve type-level tests for refinement related APIs (#1660) cf2a53e63a use `sideEffects: []` in package.json (#1661) fb1ce8a3bb Version Packages (#276) 9cfda4ad57 add stack to WorkerError (#275) 41404aec26 Version Packages (#274) f72d37b3e5 use removeEventListener over signal (#273) 3db983cdb6 add WorkerError to send api (#272) d42187de63 Version Packages (#565) 87aa3544bd Schema.rename dual api and forbid excess properties (#566) a369ba5b4c Implement wizard and completions (#373) eb7b6c0154 use `sideEffects: []` to circumvent bundler issues (#564) a7fe25735f update docgen (#1659) d6e3aeb0ec Improve refinement typings for ReadonlyArray.partition (#1658) 396fa755c4 Added refinement overloads to some Sink and Stream functions (#1654) 4e705800f8 Version Packages (#148) 899b58a250 ensure setup arg is provided (#147) 7dc49bea23 Version Packages (#271) 5e87003623 update multipasta (#270) d067de70df interfaces: revert changing methods to props (RE: #1644) (#1655) 29c13a606b remove exports from package.json (#1652) c881c808c0 move test files to *.test.ts (#1653) b5cc143414 Version Packages (#269) e102b65e88 update deps (#268) 6ec32c3b2b update docgen (#563) 1a1b7151bf Version Packages (#371) 03473b2a72 update effect (#370) 1368757ad8 Version Packages (#441) ae6f701b9c update peerDependencies (#440) 7ecd33a6c5 Version Packages (#439) cd4b4a9da9 update effect (#438) 172b3c9009 Version Packages (#93) fcb07d46b8 update effect (#92) 0ba6594e68 Version Packages (#146) dae8ebdc63 update effect (#145) e5b6dd4c6b Version Packages (#49) 6d4899735c update effect (#48) 85127f4a23 Version Packages (#266) da03faab53 update effect (#267) 522dbb95c5 ensure clipboard is accessed lazily (#265) d68eea5d2b Version Packages (#562) 0db5165b81 update effect (#561) 7cdc657e9e Version Packages (next) (#1598) bdf7dd36f0 A couple of leftovers of #1644 (#1646) c0928a4981 add Logger.withSpanAnnotations (#1645) 5210632397 interfaces: replace 0-arity functions with values (#1649) c4d743587f Cause: fix exception constructors (should respect `exactOptionalPrope… (#1648) a0de4963e5 turn on exactOptionalPropertyTypes (#1647) f3abfd3ad6 interfaces: add missing readonly modifiers and remove bivariance by changing methods to props (#1644) d0f8ea5132 Version Packages (#264) 59c45cdb8a fix fieldMimeTypes fiber ref (#263) 915f28a0e5 Version Packages (#262) a8486b7f25 use multipasta for multipart parsing (#250) 9aeee3ecb7 Version Packages (#261) eb078f9281 Expose available terminal columns from the Terminal service (#260) 10caca5c71 Version Packages (#259) 051299b2af Fix terminal tag symbol identifier (#258) cc0b5e90ff chore: fix import (#1643) 35ca712215 fix variance of Ref, TRef, Handoff (#1626) bd763d4ec2 Version Packages (#354) 226744ac12 TMap / TSet: merge removeIf / removeIfDiscard, retainIf / retainIfDiscard (`{ discard: boolean }` optional argument) (#1642) b3bdab01cc Version Packages (#558) 920c786d3e Equivalence: ignore excess properties, closes #556 (#557) f2dca8aa11 Version Packages (#257) abe0e34148 fix: added missing File type export (#256) e08cde5d62 Adds a basic `Terminal` interface for prompting for user input (#255) 7a59f12ed4 Version Packages (#555) e743de77c8 add rename API, closes #549 (#552) 550b1f5c40 Version Packages (#554) 9f6e34cfa4 Fix bug in property signature transformations when used for renaming … (#553) 03c97c4120 Version Packages (#551) 60bd6ab217 attachPropertySignature: add support for symbols as values (#550) 07b2358836 Fix the example CLI scripts (#366) dc45a58f7c Match: fix record signature (remove any from the codomain) (#1639) 6ccbe0da87 fix public module exports (#365) ebbcd0401b Version Packages (#254) f5183a2917 update dependencies and remove overrides (#253) 7cc42f211e RedBlackTree: fix bug in Hash and Equal implementation (#1641) 1072443d98 Add support for auto-generating completions for a CLI program (#358) 4b039f1fa9 fix link in "please report an issue..." message (#1640) 5cee31be57 Version Packages (#252) 34f5e34135 Re-add http module (#251) 1314d66943 add more tests (#1636) ddc9307210 fix #1637 (#1638) f0b0142980 BigDecimal: align toJSON with standard (#1634) acad6a5601 Either: rename `reverse` to `flip` (to align with `Effect.flip`) (#1632) bf7eaa73c3 refactor `clamp` / `between` with an `options` argument for `minimum` and `maximum` (standard) (#1631) ed63ee88ff Tuple: rename `tuple` to `make` (standard) (#1630) afb91208d1 Version Packages (#249) 1ebd71b93e Allow for specifying that a Command should be run in a shell (#248) fbcb4036f8 add Logger.mapInputOptions (#1606) f7c0b0e2ab update build configuration and tooling (#143) 88e2203e4c Version Packages (#547) a4b6c02976 Add uppercase combinators (#546) 261ec2baa0 replace toReadonlyArray with toArray (#1628) 917b54311b TMap: module clean up (#1625) 2355782242 update build configuration and tooling (#355) 803a7c577c properly encapsulate `src` and `test` tsconfig (#1616) 6471fa86d5 Refactor library internals to fix numerous issues (#353) a93343fb9e SortedSet, RedBlackTree, SortedMap: make fromIterable dual (#1621) fe4cb0e225 remove readonly tuples from return type when possible (#1619) 6cdffc752d check size of tree shaked module imports (#1620) 24bbd7e810 update build configuration and tooling (#544) 98d55ecac2 modernize (#245) 26259e406d HashMap: add entries functions (#1617) 74b0a00e88 Types: add Mutable helper (#1613) 86aa6a2733 ensure pool acquire is interruptible when allocated dynamically (#1611) 3053ad2db8 Version Packages (#244) 2ef61c758b fix worker interruption (#243) 16ef8e342d pack build output in dist folder (#1609) 49325a90a7 Remove workaround (#1578) e6ddb21690 Fix off-by-one in Random.shuffle (#1608) 674ce4da5c Remove potentially offenive language (#1607) 579d2f0bb9 simplify build (#1582) ce9668304a remove `export` from examples (#1604) b7977723b3 run browser tests with playwright (#1602) a46a438454 docs: add example to Effect.once (#1600) 645c96e453 add Refinement overloading to Effect.loop (#1599) 87dcb16fb6 add Refinement overloading to Effect.iterate, closes #1596 (#1597) 916dbb6e09 Version Packages (#543) a1fa8e5cd9 Chore: use Chunk.getEquivalence (#542) 06c9f0a256 Version Packages (#242) 22ba8c7508 decrease bun worker close timeout (#241) 45249d923a Version Packages (#142) 7def392db9 update effect (#141) d1ebbb4c8e Version Packages (#352) d052e48d13 update effect (#351) d0e898d09d Version Packages (#240) 6f308f93d8 update effect (#239) 7cb524682b Version Packages (#437) ad928c25d5 update effect (#436) df5f3295e1 Version Packages (#47) 25639706e0 Version Packages (#90) a9a3456d72 Version Packages (#541) 5deba89142 update effect (#46) c00746791b update effect (#89) 274b4bfb94 update effect (#540) ea4bceff5b Version Packages (next) (#1595) fb45cb81ea fix regression in process.hrtime detection (#1594) b29b70b1d7 Version Packages (#140) 6fd1af8012 update effect (#139) f06de05127 Version Packages (#238) 138e4f7410 Version Packages (#350) 0d181a339c update effect (#349) 286afe3a2a update effect (#237) 82f123f5ff Version Packages (#435) d39bb049cb update effect (#434) 4695f619a2 Version Packages (#539) 58b1a0930b Version Packages (#87) 5b5a55d805 Version Packages (#45) 685bef4813 update effect (#88) ec2ccac1b5 update effect (#538) d9ccf5a4ed update effect (#44) 526fc88867 Version Packages (next) (#1569) f21d4fa833 List.Map should correctly provide index parameter to mapper function (#1586) dd35774e26 Fix fiber failure stack (#1588) 5a22ee8635 Version Packages (#236) 20ff3598ed fix for hanging worker shutdown (#235) 9611f34f6c switch to peer dependencies (#86) d1b701f6a2 add RequestResolver.fromEffectTagged (#1562) 612aca24c4 fix timeOrigin polyfill in clock (#1593) d27ca6dee4 Predicate: add hasProperty (+ internal refactoring to leverage it) (#1592) af9462d330 run gen script (#537) 5e63bb41c3 bump node & pnpm version (#1583) 1169a25316 Version Packages (#531) a9ef84e5c6 expose Equivalence compiler (#533) b0ef15e202 Version Packages (#234) b2aeb05ef9 fix worker scope hanging on close (#233) 974fec5ec4 Version Packages (#138) 2617c1727f ensure __setup is called for every worker (#137) f50097f8aa Version Packages (#232) a69f4c1696 add onCreate and broadcast to pool options (#231) 6464420932 Version Packages (#136) e0dfd75667 fix http server types (#135) f47ab09888 fix __setup service exclude (#134) 32b3df6c29 fix docgen error in GlobalValue module (#1587) 4f12041955 Version Packages (#133) 0b9867c18d fix WebWorker schema validation (#132) 34065c2d8c Version Packages (#131) 07c87ccbac fix e2e test types (#130) 12334d0b59 fix workers exported types (#129) af1b79da5a Version Packages (#128) b7f74ca1e7 fix worker server types (#127) 9fe38b0192 Version Packages (#230) faf1073834 worker improvements (#229) fec1bf48d4 Chunk: getEquivalence, resolve index out-of-bounds error when compari… (#1585) 4007bb5c79 Version Packages (#228) 6142a6d4d4 feat: cache the reading of text/formData/arrayBuffer bodies so they can be reused (#227) 97463b22ad update docgen (#43) 0db8d8858d Arbitrary: add BigIntConstraints support (#532) 23b6ace839 Arbitrary: merge ArrayConstraints (#530) e6a298ed8f add ci matrix (#1577) 4f88fcf8cb Version Packages (#528) 45a57c8f7b add default annotation (#527) cf310effae chore: updated docgen (#1580) 59489fd531 chore: updated docgen (#526) 79cfeae4f0 retry flaky `raceBoth` test (#1581) 8bb917d5a6 Version Packages (#126) e6e5625873 handle NoOutput schemas in router (#125) 5842ff12f0 dtslint/ReadonlyArray: add sort tests (#1573) 3876e2412c Version Packages (#525) be561eac40 re-add lost mit license (#1575) 09a685c766 Isolate state by version and check for version correctness (#1564) f6e6b4335c feat: updated Schema.brand BrandError (#523) bede4a1190 Version Packages (#224) a8789c64b1 add makeHandler api for node http server (#223) 744e0a9eae switch to @effect/dtslint (#42) d790cf7991 switch to @effect/dtslint (#1570) fe9bcaab41 switch to @effect/dtslint (#522) 079813eac6 Version Packages (#124) e2dc7435f2 migrate to @effect/platform (#119) 4647fa22c7 Version Packages (#222) efe87ee776 export WorkerRunner layers (#221) 1063db3c4e Version Packages (#220) a578399f7a fix encode / transfers for effect workers (#219) cc5c0459ce Version Packages (#218) b363ae1539 add encode option to Worker & WorkerRunner (#217) 5a0353600b Version Packages (#216) c54715f2eb seperate request processing in http client (#215) be2f6b33f8 add Stream.accumulateChunks (#1568) 42c2b12b19 Version Packages (#214) ad26143f2f Version Packages (#346) 55be9c7429 update effect (#347) 49ba936378 update effect (#213) 884bb6023c Version Packages (#121) 93f4d396e5 update effect (#122) cc10769925 Version Packages (#433) 8789ec8d6d update effect (#432) bef6cb6e3f Version Packages (#41) 23ee6498c6 update effect (#40) 88bee799b9 Version Packages (#520) dc933bd751 Version Packages (#85) bc7059f1c0 update effect (#519) 1d1e5232ae update effect (#84) a308eb362e Version Packages (next) (#1566) 405f87cf77 fix support for optional props in Data classes (#1565) 2b966d987b update effect (#345) a8fc28c3ba Version Packages (#431) 633b6408cd Version Packages (#212) 9214848681 update effect (#120) bf0ee066dd update effect (#211) 149ecd5b4e update effect (#430) 57421a85de fix dtslint in devDependencies (#518) 1f6f014884 Version Packages (#39) 88a9b2440e Version Packages (#517) 6d48eaa523 chore: update effect and fast-check (#515) 129a2110a1 update effect (#38) fdf642ff6a Version Packages (#80) 00a3f5929c update effect (#83) 157a275ac3 Version Packages (#516) 16ddc29500 Arbitrary: fix recursive generation (memory issues) (#514) d37e694fd6 Version Packages (next) (#1542) e95d5a271a remove instances of instanceof (#1563) 08ba92fb28 Version Packages (#513) 2087367e80 Schema: `split` should support subtypes of `string` (#512) 9d248ec4bb ReadonlyArray / List / Chunk: merge mapNonEmpty with map (#1555) 18eb56e2b2 add Resource config to sdk layers (#82) d06229639a let TracerProvide shutdown SpanProcessor (#81) 669714d20a use scoped TracerProvider (#79) 2167dd290c use Proxy for TaggedEnum constructors (#1560) a183b863ba Allow optional parameters to be used in TaggedEnum (#1557) 232922a888 Avoid relying on captureStackTrace for Data.Error (#1559) afce1ee5a9 Fix missing globalValue in Logger and Query (#1549) 8457a51261 fix: `Struct.omit` and `Struct.pick` return types (#1554) f87e8be4dc use vitest imports throughout (#1551) 0b93da796f use vitest imports (#510) 5a98bccb88 fix symbol names (#1552) 5aa9efeb56 Data: improve displayed types (#1547) 74bd84e286 Version Packages (#509) b8dd902dc9 fix encode discriminated union with transformation (#508) f11ac218e5 remove unused dependencies (#1546) 2ce9e41cb9 Add BigDecimal module (#1528) e45f1fe2e0 Version Packages (#504) 152c9a7c41 JSONSchema: use JsonNumber in tests (#503) c684cb184a Version Packages (#502) c9d3aa543a expose JSON Schema compiler (#493) d056e62509 Version Packages (#501) becdeac604 Arbitrary: remove NaN while generating numeric template literals (#500) 9bcd8ae7dc Version Packages (#499) 450762117a fix regexp for numeric template literals (#498) 891134ca5e Version Packages (#497) 948cafafb7 Arbitrary: fix issue with generating optional tuple elements (#496) de0e6316ea Version Packages (#118) 959ed3b814 update effect (#117) b8cde0a655 Version Packages (#344) 775de562e4 update effect (#343) 73bd3bda77 Version Packages (#210) e814490372 update effect (#209) d845e41771 Version Packages (#429) 7829f7601a update effect (#428) bce50f7b5b Version Packages (#492) d89ee5304c Version Packages (#37) 01c0a56f8a Version Packages (#78) 3b711bcc38 update effect (#36) 2f0e2bd4d7 update effect (#495) 8461be61a3 update effect (#77) 9d8fac2aa2 Version Packages (next) (#1532) 22253dd07a remove sampled from span options (#1539) 6f1a3c2b49 fix signature of Layer.span (#1538) 2853a498f7 Remove `Number` module `Option` variants (#1534) 8e5a5ee42b avoid bigint literals (#1536) cee9f7973e Version Packages (#207) 2cd1d01cdf add Http.middleware.withLoggerDisabled (#208) f98fa536a0 fix: Either/Option gen when no yield executes, just a plain return (#1537) d6575e910c memoize the Parser per AST (#475) 06e60324b1 Make transformations strict by default (and allow relaxing constraint… (#491) c1d1a53856 use context for tracer spans (#1535) 89cc42d072 stream improvements (#206) 8a5f82dfaa Safe division (#1530) 67652a64d9 add missing ReadonlyRecord APIs (#1526) 61647df5e8 Version Packages (#342) 70fcf11d81 Version Packages (#76) 2756b32603 update effect (#75) b495a48050 update effect (#341) 549dd207c9 Version Packages (#116) 55ef54b2fe Version Packages (#205) f68537c514 update effect (#115) ff6a8035cb update effect (#204) 9e8f9f528d Version Packages (#427) f92908a862 update effect (#426) dcabf5f478 Version Packages (#489) 049ba87045 Version Packages (#35) 6c1a8073ae update effect (#488) 1adea45b1d update effect (#34) 8291a42965 Version Packages (next) (#1512) 6e4075ff0f fix Channel.mergeAllWith unbounded concurrency (#1529) f3e5bd05fb Removes unnecessary type parameter from TaggedEnum (#1525) ef7bd1a1d8 Version Packages (#203) 50c15592d1 add prepare script (#1522) 296e59ecd4 add Worker and WorkerRunner modules (#194) 95c0d66226 Enforce that members passed to TaggedEnum do not have a `_tag` property (#1521) c8c44030b6 add dtslint-clean helper script (#487) a78fb33771 add dtslint-clean helper script (#1523) 586de6939f Version Packages (#486) 6b4858997e Arbitrary: use Math.fround when creating number constraints, closes #484 (#485) c7b0c769de clean up fromWritable (#202) d7aa4a9630 Version Packages (#201) 303f7b9586 fix hanging node Sink fromWritable (#200) 5dad116b9a Version Packages (#198) b931a1c471 enable http tracing by default (#199) a2e06b688c fix for non-compliant node steams (#197) a20acf76d2 Version Packages (#482) 4c8e172388 Schema: add jsonSchema annotation helper, closes #479 (#483) d6279a0414 feat: add `mapNonEmpty` to `Chunk` and `List` (#1489) 14bf298b20 AST/Schema: add mutable combinator, closes #480 (#481) 06dff2a7a8 Version Packages (#196) 182d87c23a add Stream fromDuplex & pipeThroughDuplex (#195) 7657e03dd0 HashMap: add Key, Value type-level helpers, closes #1514 (#1519) 43a966fb1c Metric: add JSDocs (#1518) 5f17c89cf4 Metric: add JSDocs (#1518) 38a567b541 fix off-by-one bug in Stream.fromIterable (#1517) 8490b28797 export Channel.suspend (#1516) 60d6b7c632 improve Cause toJSON output (#1511) 093bb0ac24 Version Packages (#111) 78a7578dbc wait for ready latch in workers (#110) 8731fc779a Version Packages (#109) b4e2855117 provide runtime last in server (#108) 06385b46e9 switch to relative imports (#340) 97a424fb89 fix madge setup (#1510) e8df23e73a Version Packages (#337) c4f02b46d8 fix version script (#339) 9339fb1181 use preconstruct for builds (#338) 97e8d317ce Version Packages (#425) 931e00fe59 update effect & preconstruct (#424) df1c7eb60f Add support for prompt-style commands (#304) a3d996de7a update docgen (#478) 6d3ae1370a update docgen (#1509) 3039ee7bb4 Version Packages (#107) 85156b9b77 Version Packages (#192) bababc97d4 update effect (#193) 287db2a559 Version Packages (#73) e8ab5de077 update effect (#106) aace065121 use lazy arg for node sdk layer config (#74) 117f4235de update effect (#72) 682bdfa85d Version Packages (#477) eaefc863e4 Version Packages (#33) 6cf3e4566d update effect (#476) e58ed015d1 update effect & rename Bigint to BigInt (#32) b5cb98db70 Version Packages (next) (#1499) 45fba49058 allow message property on Data.TaggedError (#1507) 15ce201059 refactor Layer set* & tracing apis (#1506) 4899a27733 feat: allow log annotations to be any object (#1500) da44cc9530 Fix Stream.groupAdjacentBy when group spans multiple chunks (#1503) 92a727c8a3 Version Packages (#105) 0b7271f619 catch defect and cast to RpcTransportError (#104) dc68566d13 feat: add `ap` method to `Effect`, `ap` and `zipWith` to `Either` ⚡️ (#1504) 4a60b8e56f README: add Pull Requests section (#1505) fc4e196308 add Match module (#1501) c30e6fcde0 Version Packages (#103) 7af9d8cc27 remove output from __setup schema (#102) 1c83dd594b update effect (#191) 397c13398b Version Packages (#71) 00f420b806 update effect (#70) 3e9d21ce63 Rename `Bigint` to `BigInt` (#1484) 0b17d67f60 Version Packages (#31) 47a2f21be7 Version Packages (#473) ef0e95de5e update effect (#30) ac886d445e update effect (#472) 3f3a8d4aaa Version Packages (next) (#1496) 7739a7cb58 Support rendezvous-like behaviour in Queue.bounded (#1495) 58bd06e586 Version Packages (#471) fac390aa8e ParseResult: add orElse (#470) 702c38b69f Version Packages (#29) c7635977a0 update effect (#28) 7b57c6a0d0 Version Packages (#190) d495cbc5b3 Version Packages (#101) 4c00c20d0d update effect (#100) 97838eaf69 update effect (#189) c8ee0655fd Version Packages (#469) cbcacf9dcb update effect (#468) 03a7d563f3 Version Packages (#69) 7e0b6994d1 update effect (#68) afcc24bc88 Version Packages (next) (#1486) b33f6a0e1a ensure more failures are annotated with spans (#1492) d113285e11 Version Packages (#67) 2e213d884f add bigint math functions for `abs`, `sqrt`, `lcm` and `gcd` (#1487) 3187f6f4ec fix root spans (#66) 2b0b6a5d70 fix Layer.withSpan optional args (#1491) 9a66abd598 copy dtslint tests from /data and /io (#1490) 91f574693b Version Packages (#466) bbb9e554ff Add support for ParseOptions and AST to transform (#465) 51779f5e94 Improve Data.Error / TaggedError (#1483) 8e5f61a11f remove references to semigroup & monoid type class instances (#1481) c0e7a5d50f run gen script (#463) 39b4719ad2 Version Packages (#462) 70a9e857db add ArrayFormatter (#459) 9a0458ed94 move fast-check to peer dependencies, closes #458 (#461) f4eb84c84e Version Packages (#188) 63eace721e fix order of pre response access (#187) 5561437e39 Version Packages (#185) 8253ab521b add http pre response handlers (#186) 14ca70f46c Version Packages (#65) 2ece8932fd update effect (#64) 1d7269437b Version Packages (#27) 6f40002b4d Version Packages (#99) 2b875a9f8a build with preconstruct (#98) 6805d8e0b8 update effect (#184) 1f4dee34cb build with preconstruct (#26) 6b58eb9b13 Version Packages (#460) 7e17223739 use preconstruct for builds (#457) 5539963d99 Version Packages (next) (#1471) 355f5bfa3c add bigint & incremental metric types (#1465) 914b1f4f1a refactor Effectable and Streamable public api (#1480) ec4fc09256 run pnpm gen (#1475) 43962330f1 support records in Effect.tagMetrics (#1473) 66a45ecd90 feat: add Streamable constructor (#1455) e5a57e099e Version Packages (#63) 908e8afcd4 remove docs-update from pnpm version (#62) 6c6789dd12 add preconstruct (#61) 2f7b063a81 switch to build-utils prepare-v1 (#1472) 7875985bcc switch to build-utils prepare-v1 (#183) f313547002 Version Packages (#182) 82cc18c274 fix error type exports (#181) 870736499f Rename Hub to PubSub, closes #1462 (#1463) 6fd1465fb3 Version Packages (next) (#1470) 2fecb5c160 Fix yield loop (#1469) 44bb415f21 Version Packages (next) (#1468) dbaf4e34fe Attempt at resolving TS issue with module discovery (#1467) 022acf600d auto install pnpm version from package.json (#1441) 38e690525b Version Packages (#180) 54dcb21a44 update effect (#179) 2e7e9426bb Version Packages (next) (#1464) 936c422ba1 fix issues with duplicate packages (#1466) 3f0067673d Version Packages (#178) 027c0599e7 Handle empty response body when parsing the response as json (#173) (#174) fd7c4a3f05 Fix timeout binding (#1459) 654df4f103 Error: rename Tagged to TaggedClass (to align with the naming convent… (#1461) 8d2bb26baf Version Packages (#176) fb14ccb04f add ClientResponse.schemaNoBody (#177) 3e40a9d8c6 make ServerResponse an Effect (#175) e10c64b0af Version Packages (#172) 28a137195d remove preserveModules patch for preconstruct (#171) afce36d7da Version Packages (#170) f45daddbf2 Version Packages (next) (#1457) afbc438717 re-add types field to exports in package.json (#1456) 8dd48cdf52 Version Packages (#168) c65d7f2bb5 add preconstruct (#167) ae2d921a43 Version Packages (next) (#1452) 610ea9b61b add Layer.withSpan (#1454) 0cf0729a08 Version Packages (#25) bac03df709 Version Packages (#455) 195ef72971 fix peer deps ranges (#24) 37f39bb958 fix peer deps version range (#454) 965d9f7e27 Version Packages (#166) 480749f64c fix peer deps version range (#165) c9b30df3d0 remove gen:package from build script (#1453) 6097cb862c move types export condition to the top (#1451) 66014758d3 Version Packages (next) (#1447) 713e29a378 hotfix exports types condition for consumers with `NodeNext` module resolution (#1450) f93ec13bf9 add preserveModules patch for preconstruct (#1449) ce431c7065 Add vscode to gen (#1448) 803e5eee16 Add sideEffects to package json (#1446) 62899bde73 Version Packages (#58) 29520d317e Version Packages (#97) 34bbdb3136 Duration: add toSeconds (#1445) 23cdde33ca update effect (#96) 4359c75663 Version Packages (#164) 5da714cf41 update effect (#163) e8b888cf1a Version Packages (#334) e08f248602 update effect (#333) bd816c59d2 Version Packages (#23) 72289bb29f Version Packages (#421) 620f6c0c51 update effect (#22) 98d0db6faf Version Packages (#453) 3d52e498ef update effect (#420) 95b0def53b update effect (#452) c2f30b7d41 update effect (#57) ae9fd95847 update docgen (#1444) c42908e9c6 Version Packages (next) (#1443) 18696e16aa add Function top level exports (#1442) b0e01b2b47 update docgen (#451) a3dd12352d Version Packages (#331) a95e751e57 Version Packages (#93) d3bbe45df1 update to effect package (#330) 0640cee793 Version Packages (#56) c5c24057bd Version Packages (#419) 426220f9bb update to effect package (#92) 1b8ddcd5bc Version Packages (#162) 138eaa098c update to effect package (#160) e3898ab8ed update to effect package (#418) 705da1fcdb Version Packages (#21) 9b268270ff update to effect package (#20) 92bd04640c Version Packages (#450) ced9fc50f1 update to use unified "effect" package (#449) 15bf012794 update to effect package (#55) 94de1559dc Version Packages (next) (#1440) ed79588f9d integrate preconstruct, unify package structure (#1434) 6232d07437 Version Packages (#448) 18a584d5b9 Schema: remove *Result APIs (#439) 09cbc2b919 int filter: use Number.isSafeInteger instead of Number.isInteger (#447) 749b3f489b Version Packages (#446) 7e970a90d5 remove internal tag from MissingSelfGeneric utility type (#445) 6412a3bcc5 Version Packages (#161) 91f2413dbb add client transform apis (#158) 7e23cb32cc Version Packages (#444) d570de8d4a update `/io` to latest version (#443) 7c83d9c193 Version Packages (next) (#1437) eab4966b46 update dependencies (#1436) 5b50ccbddf Version Packages (#91) 7556d9d496 update dependencies (#90) 42047deaf8 Version Packages (next) (#1433) 7e7a1dfb5d update dependencies (#1435) 274aa84533 Version Packages (#157) a75ded1ac6 Version Packages (#329) 3876e73103 update dependencies (#328) edf0fd7fd6 update dependencies (#156) 3f1c2efbb3 Version Packages (#417) 9ca5488c33 update dependencies (#416) 2de18f8766 Version Packages (#442) 393bad58ab update /data & /io (#441) 5fd81d6c75 Version Packages (#53) 04efc0c421 update dependencies (#54) 85459e6528 add Tracer.currentOtelSpan (#52) 0bc52c8f8d move FiberRefsPatch from FiberRefs module to its own module (#1432) 0e2a8e1d3a upgrade docgen (#1431) 87634db3d2 Version Packages (#327) 499dd15c19 Use built-in `Console` service (#326) 9519661c13 docs: remove md fences from descriptions (#1430) 298df4c434 make tests Bun compatible (`bun test`) (#438) 959e9f0f0e Version Packages (#51) 45fbe231a0 expose otel tracer with tag + layer (#50) dcb0a43d87 Version Packages (#437) 79a099c2d7 add pretty to FilterAnnotations (#436) 927c73631d Version Packages (#435) 1092a1ab94 Exclude property signatures from index signatures validations, fix #433 (#434) e200787060 Version Packages (#431) 969378de87 check for `unknown` in Class `Self` check (#432) 7a3ee3b47b Version Packages (#154) 649d8fa421 use direct deps in sibling packages (#155) 1ec4370835 Make `platform-node` an explicit dependency of `platform-bun` (#153) 8092c1b69d require explicit type parameter for dx (#430) 7e4f23ca5c Version Packages (#429) af7e209c25 fix Class transform helper types (#428) 03feaf33c6 Version Packages (next) (#1429) f7f4234e64 expose /stm THub module (#1428) cd8fb2df0f Version Packages (#152) e175e6fdee fix exitCode for already exited processes (#151) 3fddad3bf2 Version Packages (next) (#1427) cacbf499f6 expose /data GlobalValue & Types modules (#1426) b08111a9fb Version Packages (#149) c46cf2f781 add IncomingMessage.remoteAddress (#148) bfbd2c06c8 Version Packages (#146) 3e39ab048f add b3 propagation to client and server (#144) 9d5bf06d1b update dependencies (#145) 3d006ca95d Version Packages (#426) 8ab79720ea Class: add support for Arbitrary, closes #424 (#425) 5941c1d1ca update docgen (#419) 53c0e3bbc0 Version Packages (#421) af8c8be6ac tests: ensure that what is returned when an annotation is added is st… (#423) 4ab1a98d24 move ToAsserts utility type to Schema namespace (#422) 5536005012 have Schema.Class constructors implement Schema directly (#420) 3099054258 Version Packages (#143) d74c0ba546 feat: added clipboard api (#131) 2049f99e92 fix mime package import (#142) 8b60ccb26d Version Packages (next) (#1423) a4eb46426a update dependencies (#1422) f9e1f3baf9 Version Packages (#417) e0771e2bbc update dependencies (#418) 3bf2320e8e use `Predicate.isUint8Array` (#416) 5b77890e81 Version Packages (#141) cd0322c0b0 remove host from bun server urls (#140) e72c1425cd Version Packages (#139) c39a82497d remove Router.WithoutProvided (#138) b220945c34 Version Packages (next) (#1420) 416d97779e Version Packages (#89) 49525f6cee update dependencies (#1419) 199bd2a662 update dependencies (#88) e3ce1668f5 Version Packages (#325) 980130621f update dependencies (#324) 3463ddd936 Version Packages (#136) 0864083ee0 update dependencies (#135) b381e863af Version Packages (#49) f0056c6d1e update /io (#48) 4553ce6988 Version Packages (#415) e2afdb51f2 update /io (#414) 01ca77f0b0 Version Packages (#404) 9128eaecfd update /io (#413) 91b55857a9 relax transform / transformOrFail / compose constraints (#412) ee03f3e79a rename `symbol` to `symbolFromSelf` and add `symbol` which decodes/en… (#411) 2e9faee42c remove unused code (#410) d3dcb8730e Add support for `base64`, `base64url` and `hex` encoding (#407) 6547d6568e Version Packages (#18) 09756aaec6 update to latest /data (#17) 94a6875558 Version Packages (#134) 10ce8b73f1 add http Platform module (#133) 675730e051 re-enable effectify in tests (#408) 31d560aee3 Version Packages (#323) 0ef7bc3f48 Add support for Uint8Array (#405) 74aa5df9ae ocd code comment fixes (#406) 096b1ffd0e backporting from poc (#401) 2d187e3f05 Version Packages (next) (#1417) 90dbf464bb update dependencies (#1416) 3608bb326f Version Packages (#132) 52bd231654 update dependencies (#322) 4905a54350 update dependencies (#130) 9085eeed20 Version Packages (#87) 68fa406dcc update dependencies (#86) 3455b6da2a Version Packages (#413) 85f9f87ce4 update dependencies (#412) 82554f8464 Version Packages (#399) 72a30cb208 update /data & /io (#403) c9848d8816 Version Packages (#47) 01e686ceae update dependencies & remove metric labels (#46) fcb573adf3 bun docs and examples fixes (#129) 20cf0bca69 Version Packages (#128) a8a7c94cfb interrupt http app if request is aborted (#127) 5db0b2496a Version Packages (#126) 9f782121cc add /platform-bun (#125) 2c4e9a36dd Version Packages (#124) b8caf00a24 add ClientResponse.schemaJson (#123) 4db2fc7621 Version Packages (#122) a3bb17cdca export KeyValueStore in platform-node (#121) 1a634de1ef Version Packages (#119) 42dc378bca add SchemaStore and KeyValueStore.prefix (#120) bc39b6b626 feat: added platform-browser, Storage API (#111) d233ce5006 Version Packages (#118) 975b469ea8 add support for File web api to http (#117) 0ec8d68e10 Version Packages (#116) 47fc2d3d2c add Router.WithoutProvided (#115) e66b6723c1 Version Packages (#114) cbb3154f5d try to remove route context and requests from Router context (#113) f6158976d6 add some url param body tests (#112) 2fbb757c47 Version Packages (#110) d3ebbd73c4 remove Body.EffectBody (#109) 442dccf259 Version Packages (#107) 6e4382926c add count to http log span (#106) e306fbaa27 Version Packages (#100) 01f5c32881 add more form data limit config (#105) 3875b4be71 fix HttpClient doc links (#104) 18d7f17a6b docs links (#103) 294ffb113a improve ref docs for http (#102) a25459f41d feat: added lowercase combinators (#397) 3a338c8f64 add `bigIntFromNumber` transform (#382) 6449617bc3 Version Packages (#45) 706e748ea2 add /Metrics module (#9) e1bb74fd24 add ClientRequest.bearerToken (#99) ebe86e0cd8 Version Packages (#98) 3c71e0ca89 rename IncomingMessage.urlParams to urlParamsBody (#97) cb13bfde56 Version Packages (next) (#1413) a49bef385f update peer deps (#1412) e4a16508e0 Version Packages (next) (#1411) 3ba1a92d86 update @effect/match (#1410) d1b600c112 Version Packages (#95) 2691eb5a2f only use mime module in ServerResponse (#94) fb9d05a6fb Version Packages (#93) 5802fd1f9a fix mime import (#92) ce77896fd8 Version Packages (#91) 1278ac13ba add etag generation for http file responses (#89) 089d4a404c rename Router.transform to Router.use (#90) d4ae672f67 Version Packages (#16) 2abb86dba3 move effect/data to peerDependencies (#15) 7e6b6a8fb9 Version Packages (#391) dc40e956a9 Version Packages (#87) c75c953c9a remove Middleware.compose (#88) e971b6dc7d add router combinators (#86) 3ebde8e5b4 add http client example (#85) 252426f6cb simplify compose signature (#395) 549c50d0a5 Version Packages (next) (#1409) 93196d48ba update /match (#1408) eb39127486 fix parseJson impl (#394) b71aeb22bc internal: the `roundtrip` test util should account for encoding errors (#393) c712eeaa0b pattern filter: set default Arbitrary, closes #390 (#392) c018fad89c match arities in router (#84) 88e69e27a5 Version Packages (#82) 510acbaf2f update deps + perf tweaks (#83) db0ffbdc7f add ParseJson + allow forcing composition (#388) 95246f969b use ReadonlyRecord for headers (#81) ea3ebfe7f8 Version Packages (#80) 307c0bc33d Attempt to derive content-type from provided headers (#79) 7c3b38c721 Version Packages (next) (#1407) 4f0044537e Version Packages (next) (#1405) ab7f17102e expose Console module (#1404) e4710155c2 Version Packages (#78) 56483060c3 remove Console module (#77) ced1bacec8 Version Packages (#76) bb89779de3 add size helpers (#75) 85f8521928 fix version (#74) 48067a6eb1 Version Packages (#73) 5a639faa08 add http server modules (#71) ae78952b4a Version Packages (next) (#1403) b71d88e5e0 use dependencies + peerDependencies for packages (#1402) e9164a84c5 Version Packages (#70) d1d8802a2c have Command & Client implement Pipeable (#69) a781b0fb3f Expand Classes docs (#387) de9a4ee2ca Version Packages (#386) 55d31ac7b3 add `Class` to /Schema module (#372) 73593087ee Version Packages (#68) 041094d492 add node implementation of http client (#67) d175d7066e clean up docs (#66) fdd108e2ac Version Packages (#65) be8396c4c7 fix ClientRequest jsonBody types (#64) d4f6798472 Version Packages (#63) 4943400760 improve http client options type (#62) 942be29fc4 Version Packages (#61) 0c7f900914 add HttpClient module (#38) 0f660e7ca8 update docgen (#384) fe4689aff5 Version Packages (#321) 44c829c224 move /printer to peer deps and fix version (#320) c5e44a149a fix version number (#85) fb106e172a fix version number (#411) 1f5c5e31b7 fix version number (#60) ea4bed008f Version Packages (#82) d57cb6442c update dependencies (#84) 499648d5f1 Version Packages (next) (#1398) b32af61383 switch to using peerDependencies (#1395) 821b202658 Version Packages (#315) a2710afa82 update dependencies (#319) 45a1e42da3 Version Packages (#57) e7902957db update dependencies (#59) 22dccdf500 Version Packages (#42) 24b6bf9e74 update dependencies (#44) 38e3255c80 Version Packages (#408) 7a1a220830 update dependencies (#410) 28aba8d555 Version Packages (#374) 5907808880 update /io (#376) 18c0739553 update build tools (#83) 1ec33b14c9 update build tools (#375) c463c50002 switch to using peerDependencies (#81) 16cb1a9405 use peer deps for /data and /io (#373) 8c6cf42378 update build tools (#409) aa5d1b8c37 update build tools (#58) 3f71d56cea switch to peerDependencies (#407) f271c8b443 switch to peerDependencies (#56) 43e6bb633c update build tools (#43) 9d848412ee use peer deps for /data, /io and @opentelemetry/api (#41) 42e4fff693 use peer deps for /data and /io (#318) b0b202211d update build tools (#317) c3e3a5127b make `Command`, `Option`, `Args` and `Primitive` pipeable (#316) 797ab8357b fix `withDefault` types (#314) 507d403432 switch to @effect/build-utils and @effect/eslint-plugin (#1397) 8d362203b8 Version Packages (#80) 0ea1825eb2 upgrade deps (#78) 0f111d4a7b Version Packages (#53) 1282374679 tag refactor (#55) d580ac3cd4 add writeFileString (#54) 4b61f57238 adding offset tracking and `seek` with semaphore (#46) 33ff262e6f Version Packages (#52) 4c4f95ecef revert exists change (#51) 339a21c6ee Version Packages (#50) df2e6537d2 fix exists catching wrong error (#49) 6305a92b4f Version Packages (#48) 5eeafe4529 add exists and readFileString to FileSystem (#47) caf3b50d60 Version Packages (#14) 56b9c0a149 upgrade to latest @effect/data (#13) a270ffd6df Version Packages (#40) 4133de1308 Version Packages (#45) 104bafd72c set status.interrupted attribute on interruption (#39) e34ce5c633 Version Packages (#38) deb60ecd53 fix `truncate` to support `undefined` as size (#43) 135f2d082b add additional info to interrupted spans (#37) 8ab47cc402 Version Packages (next) (#1394) 682277e6ca Version Packages (#313) 1edd74da7d update dependencies (#312) e299fa3490 Version Packages (#406) 0663f835d3 update dependencies (#405) ccad0376d1 Version Packages (#42) 295046f429 update packages (#1393) 86ccf693d1 update /data, /io and /stream (#41) 33d4889c5a Version Packages (#36) 7576be1af7 update /io (#34) ea2690b02a Version Packages (#371) 60cde35daa update /data and /io (#370) 1729780424 Use `corepack` to install `pnpm` (#311) 4d29cf3bf5 Use `corepack` to install `pnpm` (#368) e6b5530ac6 Version Packages (#12) b153ed6b30 Product: fix tuple and struct covariant params (account for empty dat… (#11) 5c31c6b4b0 Version Packages (#33) 57a4e9a138 remove name from makeExternalSpan (#32) efad40f4a5 Version Packages (#310) 70b7b2d08e Version Packages (next) (#1390) 814497e547 update dependencies (#309) 959bd14c75 update packages (#1389) ca9ebe6720 Version Packages (#404) 25bda16ad6 update /data and /io (#403) 61093e18ff Version Packages (#40) 24ef992290 update dependencies (#39) 575305afba Version Packages (#367) 5c8f8dfc13 Version Packages (#31) dc92b72019 udpate /data and /io (#30) 8d8f096d71 update /data and /io (#366) 7d550c663d Version Packages (#360) 08f7e25066 Add `compose` (#361) b1efe69e8b Add combinator that splits a string into an array of strings (#359) 009c104c4d Add schema for BigintFromString (#350) 7bf5226afe Version Packages (next) (#1388) 2dfb20f796 update /stream (#1387) db96f1affc Version Packages (#37) 5aa48e8e70 update dependencies (#36) f36f9ff70e Version Packages (#358) 89cd6c4c71 feat: add `isSchema` guard (#357) 94e7617046 Version Packages (#35) e1ee35a429 update /stream (#34) c8a71e5b74 Version Packages (next) (#1386) b51eccfeda add /stm, /stream and /match (#1385) 8b548d4c71 Version Packages (#308) ca46809e67 Upgrade to latest effect packages (#307) b32f977cdb Version Packages (#356) dd07e96773 Upgrade effect packages (#355) ce78bbcc73 Version Packages (#402) 78001b34c1 Upgrade to latest effect packages (#401) 0661b81006 README: add Interop with `@effect/data/Data` (#354) e958d95791 Version Packages (#9) ec8141b3ae move instances related to a data type into its own specific module an… (#8) 549a3c7bee Version Packages (#347) 27b6babb09 Add changeset to Contributing Guidelines (#346) 743ceaa2b1 Fix instanceOf annotations (#345) 2c50ca6bd4 Version Packages (#344) dcdaf0884c remove json schema and related types (#343) 38873263c3 Version Packages (#342) 17029a6187 fix trimmed definition, closes #340 (#341) 48172ce6d4 Version Packages (#33) debb691a80 update /io and /data (#32) 76b22cf2a5 Version Packages (#339) 5d8107a24e Version Packages (#29) 27e203b393 update /io and /data (#28) bba87d557a update /io and /data (#338) de0746fcdd Version Packages (next) (#1384) a87fe08a47 update /io and /data (#1383) cf5a4c1d2b Arbitrary: skip heavy tests (#336) 94de982a60 Version Packages (next) (#1382) 8e7eeeeabd Version Packages (#31) dba4b6a499 Version Packages (#335) 9766d40dba update /io (#334) 7abcb04993 set fromReadable buffer to 1 (#30) 13dcb589bb Version Packages (#27) a034649add Version Packages (#29) 080f394ad7 update /io (#26) ba50ecf2c5 update /io (#28) 97bdcf1ec2 update /io (#1381) 726737c9c0 Version Packages (#333) 8c922cc360 update dependencies (#332) 08d5cde01e Version Packages (#27) b7f45aadbd update /io and /data (#26) 197ae2666e Version Packages (#25) e595a28093 update /io and /data (#24) e43b71da2a Version Packages (next) (#1380) c3e675a22c update /io and /data (#1379) a7c622f1f1 Version Packages (#25) 5ee619726f correctly set file offset in node impl (#24) 2abe500bb8 Version Packages (next) (#1377) 17cf161b82 add Config/* modules (#1376) 93e70f180d Version Packages (next) (#1375) 3187e36d36 remove fast-check from deps (#1374) c6fb238e22 Version Packages (next) (#1373) 7af4a0fa85 Framework package (#1372) 619035f5cd Version Packages (#330) 24e3f7e614 make getWeight smarter (#329) e60410b057 Version Packages (#23) b1fef768f7 move node-sdk to dependencies (#22) 1508b97dbf Version Packages (#23) 848304603d update /io and /data (#22) a3dc119f0e Version Packages (#327) 082ae63e2f add pipe method to Schema (and upgrade deps) (#324) afff211e55 Version Packages (#21) d808f1b9dc update /io and /data and add supervisor (#20) e38da7e2ac Version Packages (#306) 208227fb61 Upgrade dependencies (#305) a339160043 Version Packages (#400) 5483f4508d Upgrade dependencies (#399) 291ff84707 Version Packages (#7) 4fcd5368c3 Initial release (#5) e2245d081c Version Packages (#21) b631d9193e Upgrade dependencies (#20) 2b0196373a Version Packages (#323) 371db31ac9 rename *Effect parser methods (#322) bd592de726 Version Packages (#319) 605de1da2c upgrade to latest versions (#321) f447f77512 Version Packages (#75) 770ab0f075 upgrade rpc deps (#73) (#74) 841171405c UUID: add title annotation (#320) 3f2a1a9387 Add ulid schema (#318) f5d1c4c98a add Process module (#19) a7c4dde6b5 Version Packages (#19) 363ed0c68d update /io - add more time precision and `makeExternalSpan` (#18) 647be221fc Version Packages (#317) a05c8aab6b update effect/io (#316) e9052069ff Version Packages (#315) c36d3b7fd1 upgrade to latest deps (#314) 6dc5584d43 Version Packages (#313) 270dbc0df9 struct should allow a "constructor" field name (#312) 1311cec924 finally fix docs (#4) a573efa706 Version Packages (#17) 631f7c51b5 add helper for constructing NodeSdk config (#16) 5868c086b6 Version Packages (#15) 6b512507a9 make NodeSdk config construction an Effect (#14) 5d83409bcf initial commit (#1) e35d7f9a6c Version Packages (#13) d6b5a17e73 add /Resource module (#12) b3aca2a5ae Version Packages (#2) 26b00dd0e7 rearrange example (#11) 24679eb56d add /NodeSdk module (#10) a807e7aac6 implement /io/Tracer (#8) 84712e895e Version Packages (#311) 355710f9cc chore: update /data and /io (#310) ca6ef559ea Version Packages (#308) c6ac3a366e extend should support transformations as both operands (#307) 9d3b2f5a95 Version Packages (#18) 84dc5fa00f allow unsetting fromReadable chunkSize (#17) 3459f624f2 Version Packages (#16) ea92575556 update dependencies (#15) e9d73b1601 package.json metadata (#14) 61e38ab2db add Console accessors (#12) eeccb972f7 Version Packages (#68) 060a293046 deps (#70) fa2b07226a Version Packages (#304) 22eea35784 chore: update @effect/data and fast-check (#303) dd3dbb4dd3 chore: move path-browserify types to dev (#11) 8531ed03b7 chore: add Path module (#10) cf3d224c6d feat: copy api (#8) 9d2e4545c4 chore: lift File into FileSystem module (#7) e4964a82b1 chore: add node tests (#6) 9d8f216fd5 refactor: node fs impl improvements (#5) 58b1c0eca3 docs (#4) 8d35668eaa chore: api updates (#3) 89fe8f8e8b Implement common system services (#1) 7a53c946ca switch to definitelytyped/dtslint (#299) b6a02d75e2 Version Packages (#298) 14f8f2d121 numberFromString should use `Number` instead of `parseFloat`, closes … (#297) 0a8bc4963b chore: update dependencies (#69) 3b39b8313f Version Packages (#293) ff5777aa90 upgrade to latest /io (#292) 930b50b026 Version Packages (#286) fed75b49ec memoize createLazy and fix Arbitrary implementation (#285) 44ae1de871 chore: remove makeSchema constructor from internals (#284) 5da27fc797 Version Packages (#283) ae2409bf99 handle excess properties for records (#282) 08b65dff29 Version Packages (#281) b4ad4ee9c6 Json should exclude NaN, +Infinity, -Infinity (#280) c34eed286a chore: update docs (#67) cf6385e27b Version Packages (#398) 5adef7c651 Upgrade dependencies and enable documentation (#397) a96b9c19ea Version Packages (#65) 90012658c8 chore: update dependencies (#64) bf1f7ae021 Version Packages (#278) 894898600d remove undefined from optionFromNullable (#277) 9974f918f2 Version Packages (#275) f16c6096f8 version 0.18 (#274) 20d4741e53 Version Packages (#273) 17d0c852ec pick / omit: add support for structs with property signature transfor… (#272) 92dfb8c046 readme: fix annotation example (#270) f53f06fcf4 Version Packages (#268) ff7d1532bb make extend dual (#267) 7fbfbad6f8 Version Packages (#302) 6a3c20d613 chore: update dependencies (#303) 21466292fd Version Packages (#395) c84ae12a66 chore: update dependencies (#396) 8012392753 Version Packages (#265) d35bdef353 add arbitrary to AnnotationOptions (#264) 20943012e9 Version Packages (#60) 7de1396b6f chore/e2e (#63) b79da02af1 fix: Layer in provideServiceEffect (#62) 9336bf36b1 refactor: support Layer directly in setup (#61) e6df0fa0e1 feat: __setup method on schema (#59) b67d1cff1d Version Packages (#58) 077a674473 chore: update deps (#49) a61d812e14 fix: handle interrupt in send (#48) 02f9a61be1 refactor: move requestMap cleanup to finalizer (#47) 77319ca2d4 fix: worker concurrency (#46) 4d933d1ca1 Version Packages (#259) 58706f5bb1 Version Packages (#257) 0ff92ce4d3 leverage annotations (e.g. maxLength, int, between) to improve fast-c… (#256) 62e12b41c2 Version Packages (#255) 8a50f43121 Update effect/io, make parsing fields of records and tuples parallel (#254) 56143653aa fix: SharedWorker detection on mobile (#45) aee2fb3180 refactor: add error constructors (#44) 16c9b6e6a5 feat: support shared workers (#43) bcea722e9f refactor: move RpcResolver to env by default (#41) 3786e00f81 refactor: move rpc worker queue to context (#40) cb0e382a77 feat: allow overriding web worker queue (#39) 63b33e83b7 feat: web worker server (#38) bbfbb7f3a3 chore: reexport SchemaC in transport packages (#37) 20a3368855 refactor: web worker pool construction (#36) 402fdff798 feat: SchemaC module (#35) 658b8f158f fix: deep types (#34) 09a1295ada chore: test caching (#33) 92608defb4 chore: add webworkers-example (#32) 5622eea580 refactor: better worker model (#31) 4bfd02973d fix package.json description, tags, keywords (#252) c6ab7e3d05 chore: updates (#30) 5fa616869a chore: clean up exports and handleSingleWithSchema (#29) cce5eaf9ec chore: e2e (#28) a5fa41cf97 Version Packages (#250) f3558c91c1 upgrade to latest effect/io (#249) 626b61e602 chore: add webworkers package (#27) 5d932a691b chore: add nextjs package (#26) ddbecd07c5 refactor: Schema.withServiceError (#25) 78483ca18d fix: ensure schema is provided for errors in provideServiceEffect (#24) 0f1534580b feat: node http (#23) f07b5118d8 feat: Router and provideService* (#22) 51d8fb26cd chore: update /io (#21) 919713ff15 fix: useSpan batching (#20) a33606cfb1 Version Packages (#248) 11f67ed5c0 refactor optional APIs (default values and optional fields as Options) (#247) 9b693e198e chore: add batching to http example (#19) 41a5cff57a Separate `Options.choice` and `Options.choiceWithValue` (#301) 100fb2fe53 refactor: unknown for resolver response (#18) af8976cb39 refactor: RpcResponse (#17) 676d2a2904 feat: add http transport (#16) b2d0aa1506 chore: monorepo (#14) 99be92f907 Version Packages (#246) 6d0188ba80 add `never` handling to struct API, closes #242 (#245) 8cecc6482e Version Packages (#236) 57f6f499eb add more tests (#243) cdd63fcf5a feat: added S.not (#217) 577da6609a rename date to Date (#240) c910b5bf8e refactor optional (#239) de94d65794 chore: update @effect/io (#238) ef2f4e48c6 export ValidDateFromSelf and rename validDate filter (#237) 85a5b71724 feat: isValidDate filter (#231) b47740fbeb Version Packages (#235) 998f60835f attachPropertySignature as PropertySignatureTransformation (#234) 9786b38fdc Version Packages (#233) 65885b76f3 remove optionsFromOptionals API and integrate propertySignatureTransformations into struct API (#232) cd526382ab chore: update dependencies (#394) 6f71316455 Version Packages (#230) 0d52eb97a2 add missing Forbidden handling (#229) 065150fc3b Version Packages (#228) 3ec0dbf8b6 upgrade to latest deps (#227) 50ebf2157e Version Packages (#300) f26fc4b2dc Upgrade dependencies (#299) c7c3f39888 Version Packages (#393) 13d1059c71 Upgrade dependencies (#392) 017bfcbe02 Version Packages (#225) 7cc2bc35a7 move missing keys checks to improve perfs (#224) 7701a1c462 Version Packages (#391) 5e77e3b2a1 chore: update dependencies (#390) 42d903a22c Version Packages (#216) c8de69f487 upgrade to latest /data and /io (#223) 0a9e534b2d change requirements to ts 4.9+ (#222) 5ce17f3f5d upgrade to effect/io 0.18.0 (#221) d8904f7485 Fix over checking (#219) 7e0f591433 feat: Added missing assertion functions for some AST nodes (#218) 040a51e027 fix: align dateFromString with numberFromString (#215) dc692a3be9 Version Packages (#214) 95af59952c chore(docs): updated categories (#187) 01f9be94cd Update effect/data and effect/io (#211) 7251f76a2d Version Packages (#210) 77e41bef8c fix Spread definition (#209) 88378f0fb2 Version Packages (#297) 9d6331649a chore: update /io and /data (#296) 7b0ab8615b Version Packages (#389) 54b3e026dd chore: update /io and /data (#388) 9cb647d322 Version Packages (#294) 3871a58fc5 chore: update dependencies (#295) 8953499d87 Version Packages (#387) 29181c51e2 chore: update dependencies (#386) 790644c531 Version Packages (#207) d0713acf63 Update effect/data and effect/io (#206) 29f1c8550c Use NonEmptyChunk where possible (#293) 7f968d44ba Version Packages (#205) 9d4ada15f8 remove Spread from filter return type (#204) 74256419a2 Version Packages (#290) 1fe87dfbe7 variadic Options (#289) 1770092258 chore: update /printer and devDependencies (#292) 7ea0ecf23a Version Packages (#384) c41b881e50 chore: update /data and /io (#385) 74d25ac650 chore: update dependencies (#383) 048f597588 fix: optional message if default is Option (#288) 6bf7986a45 Version Packages (#203) 9c04df6902 Schema: add support for never to From / To utility types (#202) 2026e67a32 cannot compute property signatures for refinements (#201) b17721b55b Version Packages (#200) 3f63e11309 improve filter signature (#199) a457efc976 Version Packages (#287) 7877bf4be1 chore: update dependencies (#286) b403f509c6 Version Packages (#193) 474d10ce0c update to latest deps (#197) a03942007b refactor ParseOptions, closes #163 (#196) fa52ca2040 Fix/partial (#195) a452bdc22d Fix/required (#194) afb9e3833d feat: added S.required combinator (#173) e79940a4a4 Version Packages (#191) 09ceba68c1 struct({}) should go in last position in a union (#190) 6d86cb812e Version Packages (#189) 02526d0140 upgrade to latest effect/data effect/io (#188) ec535a6d77 Version Packages (#183) e1f7a41d82 update dependencies (#185) f9237cbbd3 make optionsFromOptionals composable (#184) f2b21b7224 optionsFromOptionals: ensure non overlapping property signatures (#182) f5cf04080d fix optionsFromOptionals implementation (#181) 4e13ceae6e Allow symbols in Brand (#180) b1efcffa51 Version Packages (#179) b2c76b3924 merge transformEither and transformEffect into transformResult (#178) 758e254bbf Version Packages (#175) 3ab643ea97 add test for Transform node and fix dtslint (#177) d6ea9b524a make sure validation happens at the boundary (#176) 3dbd97583f Only run effects when allowed (#174) 9721e61890 Optimize ParseResult conditional functions (#172) 8ef8bb5197 Version Packages (#170) b45a89a88c patches from previous branch (#169) 137380401c Version Packages (#166) 49e21aa4d0 0.3.0 (#165) 7ba0ea06c7 Version Packages (#285) e7ad376ba1 Refactor command parsing (#284) 2dd437e966 Version Packages (#156) a7b8d984aa fix attachPropertySignature bug ref #153 (#155) 78b78f4054 Version Packages (#146) 4a455c86dd switch from Schema to Schema 7c55867436 Version Packages (#143) 61165ae667 chore: update /data to 0.4.1 (#145) 13d4e7acb3 add /data/Option/parseOptionals (#142) 3e1102c012 fix publishing 30aeee43ab fix publishing 91a81580d3 Version Packages 9a64823e06 upgrade to latest @effect packages 9f5998edf3 Version Packages (#133) d8731f79e5 remove invalid test (#135) 5797d0efac add BrandSchema, getOption (#134) ee20973d16 Pretty: use formatActual as default formatter (#131) 2438092ce4 update to latest effect 8388d5d6ce Version Packages (#130) 5225556ffd Optimize decoding of unions using a heuristic based on literals (#115) f63af664a1 Version Packages (#128) 05092045d7 Version Packages (#126) 44fa08e479 remove last occurrences of "fp-ts" (#122) 8854ecaa72 Version Packages (#117) b0efee0cbb general refactoring for effect/schema 0.0.1 (#120) beab0a5369 Move to the effect org (#119) a529915fc4 feat: added clamp to Number, Bigint c01fdc6a04 Wire up meter and restructure services (#7) 0b39638c61 Hide Api Layer (#5) e390393a66 Make options in withSpan optional (#4) 99e36acdfe Bootstrap integration (#1) cea256b261 add dependencies eb99cdde7c init c998d1a7b9 Version Packages 0a120181f0 Schema: add attachPropertySignature afd1f50738 Version Packages a5ecf41433 fix errata in jsonSchema annotation for Bigint.between 28dc44180e fix errata in jsonSchema annotation for Number.between 4de0e086ca fix: fixed docs dedc3ab979 Version Packages 62ac83cc5e Replace @fp-ts/core with @effect/data ebd8558256 Version Packages 1214919e66 enhance union unification for any and unknown 6add77cbc6 upgrade to latest effect/data dbce3b0434 Schema: add `brand` combinator 8e09ae34f4 fix: enhance union unification for boolean and bigint literals 79284a396c Schema: export getPropertySignatures (∼ zod's shape feature) 96cf6a34db format bigints 3c8b92a67d fix empty struct validation and reuse /core/Predicate module for all refinements 9a84a9a3a0 chore: fix multipleOf tests 551db2f6fc Version Packages c3f525ab4c move new compiler harness to AST module e679419bc5 chore: refactor Pretty compiler 3d77a6c454 feat: added data/Bigint and N.between 5f9ba34574 ensures that `lastIndex` is reset to `0` in case the user has specified the `g` flag 2be95269ae feat: added data/ReadonlyArray abfabd751b Update dev deps 3c12560b9b refactor: s/getOrElse/getOrThrowWith 8f4f26eb67 chore: update to latest ecosystem packages 5474d75aa1 feat(data/Number): added sign filters e29ca231cb refactor: created data/Number, data/String, data/Object 0a2b8823f0 Support brand errors 03e49e9237 feat: added data/Brand (#40) 52c96587fe Add support for Data ef3e36dd9c Version Packages 1546633d7c Schema: remove `json` export 791c55b372 enforce optional to be used as an outer wrapper of a Schema, closes (https://github.com/effect-ts/schema/pull/#67) 8646e8fd0b Version Packages 4caff5b250 Bug fix: - Parser + Arbitrary: favor property signatures over index signatures - Pretty: fix duplicated keys in presence of property signatures + index signatures afc2434e0a fix: type literal signatures priority 53f378b2d7 README: fix wrong example of `extend` df9b912d63 Version Packages d7eecf03c9 fix: encoder refinement 81093b8ae1 Version Packages dace24b51e upgrade to @fp-ts/data@0.1.0 964cb069b5 move ParseOptions to AST and remove the `I` type parameter from Parser de8eecb3a2 coverage: ignore /src/index.ts e7b98684a3 upgrade to @fp-ts/core@0.1.1 bd4050b873 upgrade to latest @fp-ts/data 2327830de3 remove uniq call 96216feb03 AST: remove getPropertyKeys internal function 80d33f6e1c AST: fix getPropertySignatures tuple case 48a8c61c7c update fast-check dep d99c87b8bd refactor index exports 16306c3312 Schema: 100% coverage 909b8f25b6 Arbitrary: 100% coverage be4bba3c08 AST: 100% coverage ae77d7e069 Pretty: 100% coverage ca4abc394c ParseResult: 100% coverage 4a6f299175 internal/common: 100% coverage d4c51ded75 formatter/Tree: 100% coverage 790200cac1 AST: added the `create` prefix to the APIs in order to conform to the style of TypeScript's AST 2657ba393d rename ParseError module to ParseResult 6c3596df0e revert 45 and add checks to make sure that properties or index signatures don't overlap 16058b189b AST: union should remove never from members 0b83cb37cc refactor: s/option/either in test 42d3d6f492 chore: add @since to either schema d95f668965 feat: add either dec111c3bd fix: extend union with mutually exclusive properties c85220a775 fix: use ISO format for parseDate Decoder test da851090b3 feat: add date 4440639918 feat: add `trim` combinator 96a7c865e5 chore: extended tests e3a677db73 feat: updated floatSafeRemainder f0abe4f207 chore: added tests for floats b6e7d3c585 feat: replaced mod with floatSafeRemainder 07a1f011d1 feat: added filter/multipleOf dc9444220b feat: allow extend on union types e4dca9470d Version Packages 93fa681d8b update docs 54e2fc7049 refactor annotations and refinements b718130549 AST: remove meta field from Refinement b189f99ce1 add InferAsserts utility type f12bb24b9d update deps 3c76d9d340 move annotations to /annotation/AST 0dd0f407ce refactor refinements bc3b2e069d ditch These in favour of Either f7a92718d3 AST: refactor Refinement 41b383feef refactor error messages 35acccf5aa chore 2043fa2e96 chore 25f1109c1a ParseError: rename Member to UnionMember 6f4ac7f67f ParseError: remove Transform error 6cc26578e7 ParseError: remove Refinement error c6300ecad9 ParseError: remove Equal error a7a8561fc4 AST: add identifier to TypeAlias 972d2c9208 handle Equal messages 1680e47443 handle refinement messages 528e5f8856 AST: add identifier to Lazy da33653b6b ParseError: refactor Transform error 8ddc34d5f9 simplify error messages d2441bbde2 AST: refactor Enums node (add identifier) f4f5f154a0 ParseError: refactor expected field of Type error 5d2b746239 add Meta model and refactor refinements bbe413a712 remove Meta error until I know what to do with it 545d9248cb is/asserts: add ParseOptions parameter 6c66790569 rename Decoder to Parser, DecodeError to ParseError, DecodeResult to ParseResult 76fdbf6d95 Decoder: 100% coverage 8c19ac58b9 allow warnings in refinements 6fb43e105c AST: rename Transform members 6ac13a75fb remove Encoder module 28e52dcbfe update docs b9e882dae2 remove Guard module ea29ea9956 do not export prettyFor cdb11d40c5 do not export encoderFor 87de60fd88 do not export decoderFor 60ea5cddf9 do not export arbitraryFor 9ce7f15b38 do not export guardFor ae20fa7261 refactor hook annotations 26f9c6d23d remove useless declaration 47c94d2e19 add InferInput, InferOutput 218370d710 refactor /data/UUID 78055a7dfc rename regex to pattern 10ac9837b4 more test coverage 5f3d9bf797 move options near input 50612862f2 more test coverage 0125a125d2 template literals: add support for `${number}` c6823d09f8 handle allErrors c151afe122 AST: remove isUnexpectedAllowed af89ec40a3 move options 33931215cf add options to Encoder 970d6263fd add options to Decoder b8af03ee5d /data: fix Chunk, ReadonlySet, ReadonlyMap 8c7ed0d8f0 add some missing keyof members 12fd72349a chore f6655902e9 handle sensitive schemas 1a52653742 add includes string filter a0bb62214c Arbitrary compiler: handle refinement annotations 172c8effd3 docs: Formatting errors 61c39dad8e upgrade to latest @fp-ts/data e90a8e3bc3 remove Codec module fd87409773 allow encoders to possibly fail 92d23992f1 chore 0709837109 document recursive types and transformations 4967c2e4ae upgrade to latest @fp-ts/data c66c6b1af3 refactor /data using transform: Chunk.fromArray, ReadonlyMap.fromEntries, ReadonlySet.fromArray d425c24690 Encoder: fix Transform case fa6fb4695d rename parseString to parseNumber cb0974fc4f AST: add Transform node 6c04578abf AST: rename allowUnexpected to isUnexpectedAllowed 6bb5664e57 add decodeOrThrow and an example of default error formatter 1f436a5dd6 proof that Infer works both with types and interfaces 148579a02e AST: rename Field to PropertySignature 2da28bf698 AST: rename Struct to TypeLiteral 132158adbe AST: rename LiteralType to Literal 689879f1ea record: add support for refinements 029bc51cca IndexSignature: add support for TemplateLiteral as parameter 0ede066a06 refactor annotations e71d63dc58 AST: change IndexSignature key to AST b007491d5d DecodeError: remove UnexpectedKey, UnexpectedIndex in favour of Unexpected 46c43f79eb add support for template literals cbf9a04867 Codec: remove parseOrThrow, stringify, of e931adf205 Codec: refactor from class to interface d9c9818170 unify type alias hooks e16f2fb838 regex refinement: fix pattern value 7c24a46db3 Version Packages 9efe6a0bbb upgrade to latest @fp-ts/data 2972083a82 update docs 13ef0cc76c remove arbitrary and pretty from Codec 6c0109396a chore b4ab929163 /data/Option handle undefined 8d7b4311bd do not throw on duplicate keys/index signatures d0a9ff1f57 replace records with index signatures f8e4536a51 add /data/ReadonlyMap 656946b51d test/TypeScript: refactor record handling f498ec2065 chore 57b07ee532 AST: unify unions 7940ed5be5 AST: fix record implementation 7cd7c1f5a2 refactor parser aeb7354309 refactor refinements 5d4bea8677 refactor annotations 914b942954 Schema: add null 315934908d record: add basic support for refinements 459b807a39 improve record 0f2bf2bf4d AST: rename Refinement.declaration to meta 7e5b762817 DecodeError: add actual to unexpected errors af1c4cb69b AST: add allowUnexpected to Tuple eb4ae29f93 add more tests related to warnings e576be702c upgrade to latest @fp-ts/data f02b4c917f Arbitrary: fix optional element bug 605ee2d4dd renamings 0cc5da9519 remove builders 29011961d7 Encoder: handle post rest elements 6adc213e35 A rest element cannot follow another rest element. ts(1265) 9ecf64e26d switch defaults (allowUnexpected = false) fe75dfc74c remove DecoderOutputAnnotation 9290591129 refactor annotations d099abe072 merge stringIndexSignature, symbolIndexSignature into record 2a60522b8d Schema: add condemn, exact APIs 18f66c8fe3 Schema: add transformDecodeResult, withError b1918969ac update README 141cda61a7 add InstanceOf filter 9272bf8fe9 AST: add ObjectKeyword a76d563383 add Finite filter b8450fcbcc handle custom errors 7feb5ad389 add NonNaN filter bbf3aebf3d AST: refactor Refinement dc5ce33ac2 index signatures and rest should rise warnings instead of failures 1f0a1b26d8 DecodeError: renaming and add Parse c93f951696 fix EndsWith typo 97964398e5 add NumberBuilder 1fd26ddd22 add Regex filter 40edb35158 add EndsWith filter 65e6230471 add StartsWith filter 413beb20f9 experimental: StringBuilder 1f97e47b0d bug fix: adding a post rest element makes all optional elements required but also adds `undefined` to their type 6a29bc7a50 chore 4d600296c1 Version Packages fec48deaba update docs 99ebcebae2 TypeScript: handle struct and fields jsDocs 483051474d FieldSchema -> OptionalSchema 20e6d6a128 add DocumentationAnnotation e879fb85f6 test/TypeScript: handle refinements 011334e872 add IdentifierAnnotation e1fb02ddb8 AST: add Refinement e7aa747b69 add JSONSchemaAnnotation af1a8da82d refactor annotations 300239cd58 filters are not type aliases c44016aaa2 remove Provider module 92746fa724 make Pretty annotation based c15daeb77d make Encoder annotation based 95c2addaa6 make Decoder annotation based b34e312690 make Arbitrary annotation based 7dc90a0c98 make Guard annotation based 10f9f0876c GuardAnnotation POC d93caa4431 update deps 9af9b0017a AST: add annotations field 6261b33e2f Schema: replace Optional with FieldSchema 968620c8fd AST: relax id type in TypeAliasDeclaration 056687dd8c TypeScript compiler: handle type aliases 54df3f6063 AST: add UniqueSymbol to getCardinality d10c517f75 Provider: refactoring and 100% coverage 1ce5d4718f AST: add VoidKeyword 70c9989a9d AST: add Enums 17b78174b3 AST: add UniqueSymbol e8cb50dbd4 test: add TypeScript compiler d0bf61e07f tests: more coverage 837dd8a902 Arbitrary: add support for post rest elements 2f6553f0af Pretty: add support for post rest elements 41ceaee25a Decoder: add support for post rest elements 3069765034 tuple: add support for post rest elements 868d1b5070 test optional elements 1b0b5430aa test optionals c431999323 AST: remove OptionalType node 76160cb701 AST: refactor tuple model f3a84c5af1 AST: add addElement API 9f220def04 prepare support for elements following a rest element 93440944a0 chore 4ee1faf9a5 AST: rename components to elements 6c590b2e1e Experimental: crazy struct bde4feb774 Decoder: use Validated type 1eb49ed986 Schema: add optional combinator and change struct definition a0a723b661 AST: add OptionalType 0eeae911cb AST: add missing refinements f02653c261 AST: rename Severity to Cardinality 3f889a0659 AST: refactor sorting heuristic 312c26695e AST: the weight score should give max precedence to schemas containing literals f9eaba518c AST: align tuple and struct weight ed4dfa350a AST: remove StructIntersectionSemigroup c8484569fe AST: generalise index signatures cc8bf48e33 AST: remove duplicated ASTs in unions 8c23ae68db AST: simplify keyof a4bc554d64 Chunk: more precise definition 701284590a AST: remove Declaration 16d0cb123a AST: add config field to TypeAliasDeclaration 67cf15b24f AST: add keyof field to Declaration 5ca14b0afe chore 0fe11f7ab6 Decoder: better warnings heuristic 233cce31f2 tests: add tree formatter a1855d2893 AST: rename Key to PropertyKeyType 91440283e4 AST: add pick, omit 58193bec47 add symbol to Literal 120b253289 improve decoding / encoding tactic 8edb646c31 AST: sort union members by info weight 4a4a4c4234 upgrade to latest @fp-ts/data a28bc4553b AST: first attempt to implement keyof 20bada4c2e AST: add TypeAliasDeclaration 163b6bba5a optimize Guard and Decoder tuples in case of unknown and any 38674f1b9f upgrade to latest fp-ts/data a91a02e628 fix /data/Chunk name a663b48598 remove /data/UnknownArray, /data/UnknownObject f933c414ef remove /data/JsonArray, /data/JsonObject f1fa2edc42 remove /data/Option in favour of option combinator in Schema.ts e93309d397 AST: add SymbolKeyword 705f79d697 remove chunk, list and readonlySet from Schema and Codec ad515ccd27 rename restElement to rest 186037cbc2 AST: add BigIntKeyword f68aebfcfc AST: add BooleanKeyword 703f1ebaea AST: add NumberKeyword 8c00f9267b AST: add StringKeyword 1a25721b99 AST: add AnyKeyword f5763a675e AST: add UnknownKeyword a88e9658e7 AST: add NeverKeyword 46058f3606 AST: add UndefinedKeyword 4467ce2181 AST: refactor Of to LiteralType efdc298037 DecodeError: make NotType's expected field a string 8f218230bf tests: flesh out JSONSchema compiler 6c8721e767 Version Packages (next) 23eb623743 lock versions 153304bf7c elect selected modules to main 46f873744f Version Packages (next) d678e77835 restructure package db800c647e Version Packages 25e95d9c2e improve semver ranges 45a471cfc2 Version Packages 35ce081a9e README: add schema combinator example 6d6c877a10 parseOrThrow: add support for custom formatters 82fde410c8 add support for optional tuple components 448b2e0d19 partial: add support for unions eeb8623bb6 partial: add support for arrays e0cc14d50c Schema: rename withRest to restElement 15d9cb2174 DecodeError: refactor NotType error 146e279033 AST: refactor index signatures model 3c2110d8fc Version Packages 388a1e1a8e improve dependencies semver ranges d329a9bcfe Version Packages (next) 91652fb27a update release 03fbdf1ce6 Version Packages 04de2ed77c update dependencies 3b2ca8e0b4 update docs f9ffcf9591 Version Packages 682ad39521 upgrade to latest @fp-ts/data 646f35744c fix no exported members f82d72a4c4 Version Packages 675e6cc6f4 add List data type 4ce1b2e8a9 add Symbol data type 713408ad2f add Int filter 38756e655e renaming part II 387e1e5eca renaming part I 96af3280d2 filters: add GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo d38eba4d20 remove circular dependencies e695c8133e chore 9bdf075b86 Arbitrary: add support for lazy 199c37d405 Ditch JsonDecoder, JsonEncoder, JsonCodec, UnknownDecoder, UnknownEncoder, UnknownCodec in favour of Codec feebb4e495 align UnknownCodec to JsonCodec e93bd92fae add fast-check to dependencies bde51b70ce Version Packages ff7b2696f7 Version Packages 3c2cef7744 improve JsonCodec api 3d9172ac64 UnknownCodec: re-export Infer type 333ac94d64 JsonCodec: re-export Infer type 7b17a1d381 JsonCodec: add minLength, maxLength, min, max a0cde2c5c4 add /data/Option 814f8ae3be JsonCodec: add APIs d636d50897 Schema: add extend API 0c009c8a00 throw on Lazy arbitraries 78bd0497a0 add UnknownCodec 7b5af227f0 Guard: fix index signatures bug fd38c965fd Arbitrary: add support for optional fields 1bacb0080c Guard: add support for optional fields 6bcdfe35f5 more tests 7ad7ab9756 add Pretty compiler 62416f1241 upgrade to latest @fp-ts/data 78ae1f200d Encoder: handle SymbolIndexSignature 2c4604c5ca update README 741f3a65ac Struct: add support for symbol keys 825b4b0ab4 Struct: add support for symbol keys 59e53a28b5 mutable errors aabddbe6d5 replace NonEmptyChunk with NonEmptyReadonlyArray a74f36ac51 add benchmark 1e15961034 clean up APIs 42f13faf8d handle union decoders, encoders 99c72c25ac Encoder: handle all struct cases a49729e335 Encoder: handle all tuple cases 27ded4874b handle all tuple cases 9852b18a9b handle all struct cases 523c34c455 add /data/parse 0b55a9e4d2 move filters to /data/filter folder 0ebca2dc98 add /data/filter, /data/refine bd1cc5b1cb NumberFromString -> parseFloat combinator 91821b1cba DecodeError: rename NoFinite to NotFinite b154a33118 chore ea8681a458 refactor failure tests 220bc18d1e add /data/NumberFromString f49953b51f provideJsonDecoderFor: fix index bug 9e24912192 Decoder: handle warnings in tuple 9e4347a22b add Member decode error 5f201a1851 add Key decode error 95dfa016be add Index decode error 306549552c add /data/Bigint providers 35723cfdc7 chore a0d9838feb add NoFinite error 7879ef22c7 remove direct imports from These 0ef528603a switch to These.Validated e7c888bb79 clean up tests 12409dc9c9 remove DecoderId, EncoderId f826074bda Schema: add withStringIndexSignature combinator f4934485d9 Schema: add withRest combinator 1c6bc78275 remove provideDecoderFor, provideEncoderFor 9119d8b632 add UnknownDecoder, UnknownEncoder 5ac89d3226 remove fromGuard in favour of fromRefinement 75754f3bc5 add /data/UnknownArray, /data/UnknownObject e604ac49fa renaming /data modules (Part II) 88726fbd27 renaming /data modules (Part I) aa92072d54 add /data/JsonArray, /data/JsonObject c883569ccd use @fp-ts/data/Json 682682eda4 add JsonCodec 014c70f680 move Show compiler to /test a24c9ec2ee upgrade to latest @fp-ts/data 12d8eb9864 remove unsafe prefix 87b2e45565 test refine with structs f06e995204 refine POC c8c6d861e0 upgrade deps 3ad3a70ce7 Version Packages 084dec5b33 update dependencies 6f14ce1400 Version Packages 3ffd3ef4f0 update dependencies af8c444ae2 Version Packages 213d1e79f2 update dependencies fd07caa42c Version Packages 54201c4cb6 update dependencies 5b77ccff4e fix several combinator signatures for Doc a0bdba5322 Version Packages (next) 5b81896dce update @effect/io afd1ab88fa Version Packages (next) 168bc1b885 update dependencies 230c9dae7c Version Packages 04a4f739ce update @fp-ts/data 441e68f37e update release d749b879cd Version Packages (next) 6616422a6b isolate log-level export 5c53a76145 Version Packages (next) 1400925b03 add logger sub-modues f5931db674 Version Packages (next) 787fe3c967 update @effect/io b299e2c3b4 Version Packages (next) 8871772dc4 update @effect/io e753b4f570 Version Packages (next) 306fdc1014 add optic 05a892fa6c rename fp-ts/codec to fp-ts/schema 1a54b12b25 Version Packages 1dacc94cfd update @fp-ts/data fd4717b0d0 update @effect/io 56e0fc6904 Version Packages (next) 7d7dff0a60 update docs again da001ead96 Version Packages (next) 07d48e4fd7 update docs a77139e656 update repo config 1bc5098921 remove examples dir d796a26af6 Version Packages (next) 235ac57fe9 update dependencies 3763fd21ce Version Packages (next) a905ba1742 update effect ec80221648 avoid skipLibCheck, isolate debug 66129c72ed improve example e943761ba9 add effect re-exports a73ede0d03 add fp-ts/core re-exports 10910a881d add fp-ts/data re-exports 810cc8255c bootstrap ecosystem package 874f30a06e Version Packages d7518c802e update fp-ts/data 6a30005abc README: add Features section 3e5664c1ec clean up f0b6fc3932 split JsonCodec 3fcbc5b205 add literal constructor 0f10539b54 export Arbitrary module 31bfeefb85 add dtslint 8044c67a39 export Guard module 2489627995 add /data/Chunk 5f31960ba6 Provider: pipe findHandler f5592259b7 Schema: add clone API b198a880de /data/never as empty union a36af673ad fix data/any 6e024358eb move string to /data 367bd551d0 move boolean to /data 39dd53698e move number to /data 9c3fb875cf move max to /data e19f689b48 move min to /data a937a5f9c1 move maxLength to /data 5dbdbc935e move minLength to /data fee6f1ca23 AST: add config to Declare c76e5d7bca move unknown to /data c7fd608a66 fix provider definitions 2abddb43ac remove useless types fa078a0e50 upgrade to latest @fp-ts/data f1346cb533 Provider: add make 5a3ad5333c optimise `Unknown*` guards 95315f564f AST: add Unknown c76e11bd68 add Schemable instance to Decoder b4e3475363 merge type classes e6dfb31751 add provideUnsafeDecoderFor b6cbee58bd add internal/Refinement 9fcce30c7e remove internal/Schema f8c68b9a54 readonly by default 04ea79444d remove useless typeclasses efa1c21c33 rename Support to Provider c41c50e12d rename interpreter id 193a9756b9 chore 7c8c26e38e Schema: add Infer helper 742bd3e81a chore b0765aaa03 chore e9af6e07a2 allow per data type default support 14891d880b remove annotations 7cb2c6177a AST: add symbol to Declare afcfa092b5 temporary remove memoization 50adf74066 Schema: revert maxLength / minLength handling 73b6f930af AST: re-add String 8f89f00d07 AST: re-add Boolean e4572043e9 add /data/Set 1631d42eec better Annotation model 1fd5b75c77 polish a5dea09f39 AST: move string to /data 3819d429ca move boolean to /data as a POC 191ec621a7 use /data convention to define annotations e6fa81b406 enums: test support for string enums and const enums 98d6961406 add support for native enums 2cd498a0da /data: add name annotations a3bf933867 AST: remove symbols 979b796846 add Annotation module c7641e0d80 renaming f950b45921 remove declarations d6dc4e1ac4 add annotations a8398e2201 Schema: keyof POC adb274bc1b better pick / omit management 4f0bf8e408 Meta: add comments with examples a0edd69a00 Meta: remove IndexSignature member 0966fb768f Meta: remove Array member b229a890eb rename typeclasses 435d09a696 Arbitrary: more derivations 685b257cc9 Schema: remove getDeclarations 92462dc16a Meta: remove any, never, unknown ef0d91df99 more FromSchema derivations 8fc363c0e6 memoize interpreters 30303bd607 polish ce985a400b spin up unsafeEncoderFor 5b9f1a9488 add /data/Json 457c811f4e Decoder: add lazy support b356892f81 make Decoder a Schema 16df77c258 make Show a Schema 347f15699b pick / omit experimental support for lazy 2b21176575 add symbol field to Lazy 86d239b0ed rename interpreters to `unsafe*` 0c71e4fc8c test mutually recursive schemas f22c1be88c Schema: remove declarations field 488404c169 start Lazy support e91426d76e Meta: add any 388aa5e5f9 Meta: add unknown 727d9c847c Meta: add never 5f992502d9 SchemableFunctor: add nullable, nullish b479baadf9 rename Equal to Of a76c6dc36f add some typeclasses 1f71d54c8b fix variance 669fba66de Arbitrary: remove declarations from arbitraryFor fa611a2574 make Arbitrary a Schema 0dd8ad2c1c Schema: add declarations field 8fd6298528 Guard: Guard as Schema 397469b5d9 Schema: turn into interface 951d195b0f Guard: add alias, mapSchema pocs cc62c2e949 Guard: add declarations and schema fields 4a6bce57a8 poc: crazyStruct ea2ef2f59a poc of other schema combinators (pick, omit, ...) 00d5d751ba Meta: add Boolean 5977b97c2b make declarations stateless aa0761bc89 handle config in all interpreters c6acb28ee5 Meta: remove boolean 82f9b1242c Meta: add config to Apply f4229a1dd5 add Apply 08cab222de refactor declarations b0cf316e63 Meta: rename Tag to Declare a8003764a8 rename Literal to Equal ac3084b746 Meta: rename Service to Tag 2b4264e886 Meta: rename Constructor to Service ad46cf018d Guard: add Schema member b17155b616 Meta: remove Refinement 087b2bc54e add JSONSchema tests 40fc0508bf remove error tracking 64df9955fe remove brands 78a6bfa262 Meta: add Refinement 45dd0a05e7 100% coverage f1a81ac6e6 more tests 64cee532b6 refactor services ae2d4f8176 move These module to internal fa53217d32 JsonCodec: constructor test ae373b5809 Meta: generalise Constructor af9afc751a Schema: add nonEmptyArray helper 20ecf632f5 refactor Guard a8a468ee25 rename DSL to Meta ea2412a9c5 add Arbitrary c2ea9255cc add JsonCodec 0ac41e0d99 add Codec f48a93cc64 add Show 61be62809f add Context to the mix c5182fd74a simplify Schema definition 8f4bda61e6 Guard cf7a47e809 refactor Schema / Decoder 0f4539152d tmp 3c74819faa poc 0872612997 Version Packages 0d9d8ef6aa update fp-ts a26f9e2544 initialize repository 6ee14f96ee chore: convert from yarn to pnpm ffc6796e07 Version Packages 195f9e540a refactor build setup to use pnpm 5e4003392a remove tsplus/stdlib from printer package 542f286e22 chore: update ts+ 444bca3285 feat(@effect/core): add basic cause rendering logic 1070b110a6 chore: remove tuple 91d3d4e3f1 chore: update deps e2f98d6918 Version Packages 01ffb15d21 chore(@effect/printer): upgrade dependencies 9f7a091204 chore: updates f6bd394456 chore: Align Runtime with ZIO 2.0 08d3e9fac8 feat(@effect/core): add RuntimeFlags data type 935ecee94f chore: update 2de9d2ddac chore: rollback vitest to latest known fast version (tracking https://github.com/vitest-dev/vitest/issues/1837) 4ac18fe353 chore: update ts+ 9c0ae1ecb9 chore: updates and schedule fixes f7ca8f9846 fix(@effect/core): fix bugs in Schedule 6be65a3705 feat(@effect/core): convert Deferred and FiberRef to use getters 28dbb69bc2 Version Packages 3cb164ddd0 feat(@effect/printer): use pipeable for TS+ annotations 72ed1bc864 feat(@effect/core): add Gen and Sample data types 7971b011fe chore: update ts+ e0e8d775c5 fix(SubscriptionRef): fix implementation & add missing tests (#1237) 2622d1b1ee chore: rename option to maybe 54084d2432 chore: update tsplus 3d71a0833d feat(TRandom): add implementation b979acc3ca feat: replace fluent with getter where applicable 3e9521ece9 feat(THub): add implementation 1235da7f70 fix: remove parenthesis for length and size getters c1d14e2a7b fix(@effect/core): fix Stream.peel and Stream.aggregateWithinEither 6e38992aec feat(@effect/core): Sniper Kill Has ef9bfa6d8b chore: improve scheduling 36b8322c85 Version Packages 6d18d66f15 chore: initial release 770682d96d chore(@effect/printer): remove @effect/core 254b739380 chore(@effect/core): death to semicolons 77b6bba9a5 chore: updates and revise of layer memo f23e9ec3c0 fix: fix prelude configuration 6e2135e970 fix: fix build command c758590d62 chore(@effect/printer-ansi): update deps bf017e6430 feat(@effect/printer): migrate to tsplus e827ddb50c chore: upgrade tsplus stdlib d631d5f7fb chore: update tsplus 1050a96f9e chore: update env usage 538505896e chore: update tesplus and stdlib f637cdb90f chore: improve naming to reflect order or params c79e9b2412 chore: add fluent variants 047ee293c2 chore: update tsplus 6461586a6f chore: update tsplus 3c383aca62 chore: update build-utils 5d055f64e9 chore: upgrade tsplus and improve build process e706eda282 chore(@effect/core): upgrade deps 6c4cfeee12 chore: setup publishing 8d61068019 Add compositional `FiberRef` and remove default services (#1173) ce957d72f1 chore: remove HasScope alias, upgrade dependencies 10bbac9f71 Delete `Managed` (#1152) bfa2f430d8 Add stream module (#1149) b0a30724de chore(core): cleanup metric implementation and add tests 3a76490fe2 chore: upgrade tsplus to 0.0.30 (#1115) f365801143 chore(core): update tsplus e329c44882 chore(core): convert managed tests to use tsplus ref 057ffe31d4 chore(core): upgrade dependencies a257e37f2c chore(core): upgrade dependencies 77c4d98aec chore(core): add tests for effect module (#1102) 87a6909a61 chore: fix ci 15f5f17225 chore: update tsplus c81301c1ae Add missing combinators to `Effect` module (#1089) 24fc6b225b chore(system-next): convert HashSet and HashMap to use tsplus ded6576029 chore(system-next): continue cleanup work and porting modules to `tsplus` (#1073) aae09e57e1 chore: update to latest tsplus 47ed27a993 chore: update dependency and improve exports of runtime (#1067) 605d29ac94 chore: update to fix tsserver integration a2b2630f8e chore: update, introduce temporary hkt and getters (#1065) c5ee975d11 Update Typescript Extensions: Support unions, overloads, type unification, and global pipe function (#1056) 8c8d06cb44 Introduce TypeScript Extension (for system-next) (#1054) 80165f2171 Various Fixes for 2.x Port (#1037) 0e92b25b98 chore(release): publish f0da140cf3 chore: update build a50a0e0ea0 chore(release): publish 035fff83a6 chore: update build 1d1a92bbe9 chore(release): publish be30df30cc Fix ESM build (#292) 13ab6db706 Merge pull request #137 from Effect-TS/feat/esm-build eb81e49643 chore(cli): fix esm build 87e041cb96 chore(deps-dev): upgrade dependencies c35f5e035e Merge pull request #127 from Effect-TS/fix/path-validation ccfeff8321 chore(deps-dev): upgrade dependencies 5df53754b8 Merge pull request #116 from Effect-TS/patch/publish-infrastructure da5d1ceeae chore(cli): upgrade dependencies and publish infrastructure b8b15a7f10 chore(release): publish 4ebc11d09c chore: update dependencies 06a8bc291d chore: fix package json fe3975031b chore: fix madge removal 79380b90f5 chore: fix circular deps and update f4451157a5 chore: update deps e3db682d0a chore: force ts version f0718344de chore: update deps 9fa9c329b2 Merge pull request #73 from Effect-TS/feat/completions 3683195961 Merge branch 'master' into feat/completions 07306c7636 Merge pull request #92 from Effect-TS/feat/remove-figlet 15327504c9 feat(cli): remove figlet module in favor of @effect-ts/figlet 8b2e954877 chore: update deps e42a131542 Merge pull request #83 from Effect-TS/fix/publish 79a0fe5a92 chore: fix publishing 24e218cf03 Merge pull request #82 from Effect-TS/fix/figfonts 6a97600f12 chore(deps-dev): update dependencies 51748c9808 Merge pull request #76 from Effect-TS/polish/ci-cd c456c14052 chore(deps-dev): update dependencies 81239b221a chore: minor updates to ci-cd infra 4e41c71e2f chore: update package.json and yarn.lock 43e47438b9 chore: push automatically on version 8f00118ae4 Merge branch 'master' into feat/completions 576e22be09 Merge pull request #74 from Effect-TS/fix/publish-infra 8c6d10f46c chore(deps-dev): prune dependencies 88797634c1 chore(deps-dev): upgrade dependencies 686d069e0e chore: fix spacing 4d1b6c75d6 chore(deps-dev): upgrade dependencies 81c4a0a14a chore(cli): first pass at bash completion script generation c06e5e613c Merge pull request #68 from Effect-TS/feat/upgrades 5025ada961 feat(cli): upgrade repo to yarn 3 and latest effect 91c7343d86 chore: fix commit message 8a96e5a7e0 update package.json and yarn.lock post release 905dad5281 chore: maybe fix yarn-lerna flow 981954f924 chore: fix lock ebcc1e2336 chore: add version to lifecycle f1dbd36600 chore: reverse chalk upgrade 3f4c3abbcd chore: update and fix 7afbb1363f chore: use ttypescript for jest, path-transform breaks d95a8c8082 chore: move ts-patch 5669e1d1ca chore: upgrade yarn setup 4bdfc28658 chore: update dependencies 1a412debf7 Merge pull request #56 from Effect-TS/feat/type-safe-services 650638ac27 chore(deps): upgrade dependencies to include new type-safe services in effect 414700265d fix: chainRec in Chunk, refactor Prelude d813d78eb1 Merge pull request #45 from Effect-TS/patch/prefer-pipe 82c041c680 chore(deps-dev): upgrade dev dependencies 2c04c47e19 Merge pull request #40 from Effect-TS/feat/backport-zio-hackathon 69d6e4d34d chore(deps): upgrade dependencies bcb1e53001 chore(deps-dev): bump husky from 7.0.2 to 7.0.4 (#914) 0087fea9cb chore(deps-dev): bump eslint-plugin-jest from 24.6.0 to 25.2.2 (#906) 6868696648 chore(deps-dev): bump typescript from 4.4.3 to 4.4.4 (#895) c95bb0d3a9 chore(deps): update 36dea1b06c Merge pull request #18 from Effect-TS/feature/figlet 3415b7175a Merge branch 'master' into feature/figlet 537e62d111 Merge pull request #19 from Effect-TS/feature/tree-shaking 710d42297c feat(cli): make Options more tree-shakeable c2c857621a fix(cli): add all figlet modules to package.json and fix font file copying during build 4ea93a2f47 fix(cli): repair broken build scripts 5e930b9250 chore(release): publish b175890852 chore(deps): update 9c8e9ee551 chore(deps): update 293397caee chore(deps): update d35629c013 chore(deps): update deps 84550e0bde chore(cli): update deps 610c82b80a feat(cli): initial release 9f8c7de72f chore(release): publish 4e1f57c33c chore(deps): update deps, remove unused jest extension 42d427a79e fix(printer): ensure smart documents are rendered appropriately 73f5f0288f fix(printer): flatten nested characters appropriately during optimization c90e7f4b45 fix(printer): fix issues with optimization routine cfd3f43fd4 fix(printer): export Render module from Terminal 659be7af11 fix(printer): add DocTree to the package config 4f2031633a chore(release): publish 05a8107232 chore(deps): update 17f8566646 test(printer): add DocTree tests 3c2ae607db refactor(printer): correct URI names from pretty to printer bcac4f64cf feat(printer): add initial DocTree implementation 10a549f578 feat(printer): add parser for handling conversion of DocStream to DocTree 81ed22976e feat(printer): add DocToken implementation to handle parsed DocTrees 0ffb82146a chore(deps-dev): bump eslint-plugin-prettier from 3.4.1 to 4.0.0 (#843) 613bf8a4da chore(deps-dev): bump typescript from 4.4.0-insiders.20210805 to 4.4.2 (#833) 9a2eaf2c00 chore(deps-dev): bump husky from 7.0.1 to 7.0.2 (#831) 58508f625b chore(deps-dev): bump typescript-transform-paths from 3.3.0 to 3.3.1 (#819) 77e93bcb04 chore(deps-dev): bump typescript-transform-paths from 3.1.0 to 3.3.0 (#815) 4c28671464 chore(deps-dev): bump @types/jest from 26.0.24 to 27.0.1 (#812) 4aab9f43f4 chore(deps-dev): bump typescript (#806) e20b1c29fe chore(deps): upgrade 5baf3196a7 chore(build): revise publish infrastructure d0a9a1f62f chore(release): publish 6a01275dd9 chore(deps): update c838b6d3bd chore(deps): update eb662fc679 chore(deps): update fab3aae44c refactor: fixes for ts 4.4 573e382a98 chore(deps): upgrade ts to nightly 7bef54e5c7 chore(deps): update deps 26b3ee306d chore(release): publish b109db14c8 chore(deps): update deps 3d9702347b chore(deps): update deps 3e03c8388d chore(deps): update deps 41190e1934 chore(release): publish 645d20ff81 chore(deps): update deps 5d7941f8ca chore(release): publish ee394bee3f chore(deps): update deps ea8940be9a chore(deps): update deps 2ea06d30ec chore(release): publish 121b0e4b9c chore(deps): update deps 65ee13607c chore(deps): update dependencies 556e92108c feat(schema): initial design of schema package 42923eca92 chore(release): publish 0bcf8ec053 chore(deps): update dependencies 1495409de6 chore(deps): update dependencies 52dd758b93 fix(core): remove circular dependency a23448be3a chore(deps): update dependencies d92bf19bf8 chore(deps): update dependencies 1bab9f51f3 chore(release): publish aa7fd0890a chore(printer): update dependencies 3063ba11d4 chore(release): publish d42c3f8233 refactor(printer): remove morphic dependency cc8d602e65 chore(release): publish e5cb90e30f chore(deps): update dependencies 0412d10a55 chore(deps): update dependencies 2b05005843 Publish 0cdd580780 fix(printer): update effect-ts to 0.29.0, monocle to 0.26.1 & morphic to 0.28.0 b17b78e2d4 build: fix script 1c53108b75 chore: update deps ab96a898c2 build: use commitizien, change lerna message b74c52b03f feat(streams): port some functions, reorg alphabetically 1dd042fb3a Publish c6220a2ff2 feat(printer): update build, conventional commits 3382920500 build: updates e3cde8bf64 build: use internal variants 4f62bea405 feat(build-tools): write build-tools in terms of effect bf658b3afb build: run test in parallel with build addc6485e9 build: remove the need for jest module mappers ad75718d08 build: add version commands 440d478b03 build: add patch version command 2bdfd5c307 fix: release commands c65d6fcdcd fix: release commands 47b983dee5 feat: conventional commits 2caf86672e Publish 88435d28c0 updates 4206c33c7d Complete testing of Terminal module d2661ff3ac Add tests for Terminal module 1293616797 Extract Terminal rendering logic to separate file decfbbfa90 Remove calls to pipe from Style module 40139a929b Remove calls to pipe from SGR module 90b05d4a51 Update Color module 641adfd561 Bump husky from 5.2.0 to 6.0.0 3d500cf2f4 Add tests for Render module 29df8e6637 Add tests for Layout module 7a2ebcac3b Add tests for Flatten module 10b8e17c09 Add tests for DocStream module 472311dcd8 Add tests for PageWidth module ea4872f578 Add tests for Optimize module 3c09686007 Continue testing Doc module 07c94a6fa4 Remove definition tests for Doc module cf39372390 Add initial tests for Doc module 6665bfe80d Bump husky from 5.1.3 to 5.2.0 7a114494fa Publish 7437574128 Adjust documentation in Doc module 76eee23ac3 Adjust documentation in Layout module b8bec5c834 Adjust documentation in Optimize module c7de400edb Add rendering combinators to Render module 16fc3296ee Fix implementation of renderRec in Render module bf3a83f189 Fix implementation of textSpaces in Doc module 13d147d866 Add basic renderers f8eb62b989 extract recursion breakers cef0d46ff7 Remove calls to pipe from DocStream 6d02b7de97 Remove calls to pipe from Doc 0fc6cffc11 Add pipeable dataFirst conventions to Render e2194ba230 Use dataFirst version of PageWidth.match 026f96735e Add pipeable dataFirst conventions to PageWidth b32003afa7 Add pipeable dataFirst conventions to Optimize 22de4e4bcf Add pipeable dataFirst conventions to Layout c1d5e6952c Add pipeable dataFirst conventions to Flatten c432eae65b Add pipeable dataFirst conventions to DocStream 3a62e28e32 pipeable - dataFirst convention fe23db9cfb text invariants broken on words (#1) ac544fc438 use directory modules 1f90c67fca lightweight gadt on DocStream 2f871dedc7 minor improvement 1ed44908ef improvements 0431127821 lightweight gadt encoding de7215c348 fixes and removal of fp-ts comments 7144e0ae00 refactor, fix recursion errors 29514d6a3d Initial commit 3d62ecd65d remove unused 0eba7a7930 add trace comment and revise traceCall 0fc652dea3 workaround dumb ts 9f2ddd53a5 add missing dependency 2309dd2946 Bump lerna from 3.22.1 to 4.0.0 3beea91ea1 Bump husky from 5.1.2 to 5.1.3 d3c0006675 Bump husky from 5.1.1 to 5.1.2 94473e6e7d update bd2825470c remove circular dependencies b9907a1dc3 updates 226bd8858b updates 3127636d79 Bump typescript from 4.2.0-insiders.20210210 to 4.2.2 7dc9c0d958 Bump husky from 5.1.0 to 5.1.1 117c19a289 Bump husky from 5.0.9 to 5.1.0 fe8252f02c updates c790cee210 updates a96d1e6de3 updates 4806064d34 Bump typescript from 4.2.0-dev.20210208 to 4.2.0-insiders.20210210 ae4f2ec0ee update husky config a7b0bc2a02 fix lock ea955cffe7 Bump husky from 4.3.8 to 5.0.9 5264640b55 Bump typescript from 4.2.0-dev.20210204 to 4.2.0-dev.20210208 78fec0d49d Bump typescript from 4.2.0-dev.20210203 to 4.2.0-dev.20210204 72ffe168e2 Bump typescript from 4.2.0-dev.20210202 to 4.2.0-dev.20210203 72f7906304 Bump typescript from 4.2.0-dev.20210201 to 4.2.0-dev.20210202 2c4148585b Bump typescript from 4.2.0-dev.20210128 to 4.2.0-dev.20210201 (#526) afa931083b Bump typescript from 4.2.0-dev.20210127 to 4.2.0-dev.20210128 fdcefbf805 Bump typescript from 4.2.0-dev.20210126 to 4.2.0-dev.20210127 00b62d41a1 Bump typescript from 4.2.0-dev.20210125 to 4.2.0-dev.20210126 (#521) 6b04d9101a Bump typescript from 4.2.0-dev.20210121 to 4.2.0-dev.20210125 (#518) 4d1b94ddb1 pack core repos a501bfe27e merge tracing-plugin 2fea6fc51a Merge remote-tracking branch 'tracing-plugin/master' 1aaf2eefb2 merge tracing-utils b9c22c6015 Merge remote-tracking branch 'tracing-utils/master' 2b042a3631 merge core ccb34ed8a9 Merge remote-tracking branch 'core/master' 9fccebaddf merge system e0bebd5501 Merge pull request #9 from Effect-TS/dependabot/npm_and_yarn/typescript-4.2.0-dev.20210122 7bc38e7593 Bump typescript from 4.2.0-dev.20210121 to 4.2.0-dev.20210122 ba3962afdc Merge pull request #3 from Effect-TS/dependabot/npm_and_yarn/typescript-4.2.0-dev.20210121 89c9d9e658 fix 1ea9c68b83 fix deps 5ca081e99a Bump typescript from 4.2.0-dev.20210120 to 4.2.0-dev.20210121 5a7973c144 Bump typescript from 4.2.0-dev.20210112 to 4.2.0-dev.20210121 3ed5782970 Merge pull request #3 from Effect-TS/dependabot/npm_and_yarn/typescript-4.2.0-dev.20210121 dd7e03dbe2 Bump typescript from 4.2.0-dev.20210112 to 4.2.0-dev.20210121 89fea813bf Bump typescript from 4.2.0-dev.20210112 to 4.2.0-dev.20210121 21cfabe1f1 move packages 39d78678ec fix deps 7fc7ca4f9c move package 0ab621736f move package 0b2b3b236a Merge pull request #2 from Effect-TS/dependabot/npm_and_yarn/typescript-4.2.0-dev.20210120 86c6322455 Bump typescript from 4.2.0-dev.20210112 to 4.2.0-dev.20210120 ffd0862b5d move package 268fbe8824 Publish 483a7bfd22 Publish c5a9d1701d fix bindAll variants e0dae8aa8b Publish a9e5382ac6 Publish 3f5714a290 add some traces 74ce68a107 Publish 38a6a18f8c Publish 4c3faca20c add provide managed fe21d65d36 Publish cc22a26316 Publish 818eb7d680 die trace call 297316f8db Publish c630e85483 Publish be4a74d0e5 safe recursion on node only after some levels 6049a27f9c tree safety 450477450a begin tree safety 63a26a2c94 remove task-taskeither, rename tree 96fa6a34d4 Publish 313e5fea02 remove unsupported interop 9c5451f299 restructure 56810203e4 Publish 2f477cc08f Publish 1b7f13e33c fix 8c7a35d37e Publish 04badb6bdf Publish d6ee9b5c90 fix 846d9972b5 integrate io-xpure-async as ffi 0e1dc53082 integrate io-xpure-async as ffi 99075127aa make sure tracing is enabled in bench test c3782be503 Publish 75ddd8f52a Publish 08a005947e add io and sync to bench 42eb6d68ef realign cause to zio bd67ab9bbf Publish fc80ce24ad Publish 73d0d7e7d9 fix 0c46a9a96e optimised IO c2362c3827 optimised IO 55917d50b8 Publish d0fcfbcdda Publish 9c7e203904 fix arraynode a91c9f07d6 fix arraynode 19623bd3de Publish 712c3538c6 fix identity any 32fd96e43f Publish a0a2e05fdb Publish 74bec4ff97 remove fluent apis too large in shaking 2b106921b9 Publish eef25cad55 Publish be85fc19fe add fluent api 5405a915e6 Publish 568c9ecc16 Publish c00e86d10d relax set operations a7d946ca56 fix 440deda473 continue apis cd688c6cba continue apis cee50f741b Publish f4979034b3 Publish 221e4a9951 export as_ b5d7478f2a Publish 47d56ff6bc Publish d89f18602c rename classic to common 2c7187b4ff data-first a5bdfc27cf reindex 7afad2c9f7 format 145e737936 fluent 4ba7a818cc use pipeable 0f9c645a35 map-chain-foreach cfdd8733e4 add referential default f211a36dab add comments 9b3e274ace reduce 28ffbb4733 add fluent api f089eb6052 generalise iterator 95338e3995 reindex ffe47ebc69 fix import d9a7b5e21d improve config 2b8b08b0e7 remove undefined cdeeac775c iterator e67a754fa6 continue 839669b792 remove rand and continue f3a481a6a4 move hash b8cc62126c continue 37af6fd6ea restart d1b82a2c38 fix 3eac7bd81b begin port of frptools collections 30bac5c5ad Publish e2c63da530 Publish 4188f3acd1 Publish 5a23f57802 Publish b763d8f46b derive effect identity 713fb51c4e remove accidental 339443204a continue tracing b8b2f22ea8 continue 44fb2eab86 feat: add missing dep on tracing-utils dceb4f865d fixes ebd915592e fix name 9e29f65fa6 continue tracing 01a62eb8b0 avoid no locations in execution trace 93f38992dd feat: replace hardcoded "$trace" symbol with a constant 48f72a5cf2 continue tracing a61b9a950a fix e8f019ddd9 fixes 0abff1c73a avoid non necessary trace c134e21e03 remove buggy e847a0c22d avoid no locations in execution trace a3f53da5d5 trace fail b3a459f394 work in progress 89fdc70c08 work in progress 0db727a551 fixes 97caa60384 avoid no locations in execution trace 9321a01490 work in progress 49b2706fdd Publish 48bcb599e9 Publish 4e345ee106 docs(system/layer): document Layer merging methods 7eea5409b5 Publish 713a58866e Publish 8a4e4fc5fb Publish 362ac80261 Publish f3fd1b6da6 add examples project 508412628f add examples project d6b294b4a7 add examples project 3999cdd247 add examples project d28e9ce75e Publish 0ebcc0ca1e Publish d86c529e79 add literal helper d1c1f28f08 Publish 178f8e61f0 Publish 0716ded13c backport https://github.com/zio/zio/pull/4455 983a2426a9 Publish b2bd70b312 Publish 12decf7f8e fix signature efa5478bef Publish 370a6c06d1 Publish 620b787ff7 update ts 3eac1ae9b8 update ts f68cfd3fa6 Publish d27f8393ea Publish 3088f8413f add fromNullable 550ddf3fc0 Publish 99d30ac110 Publish c1834c8217 Publish aa6c6b1894 Publish 27e000e857 remove tracing from tracing plugin package 1196d05a55 fixes for traceCall dcac66cc1a fixes for traceCall f492e81426 fixes for traceCall 6d2b0955fb Publish b3c4d2ad7d Publish da00429f30 Publish e4bfcc820e disable flow optimizer in case it contains yields 953bdc9121 disable flow optimizer in case it contains yields a8175c338d Publish c56c17790d Publish 254cd37ebd Publish 4fa003a292 Publish dd8d714c0e improve tracing f3b5a1c0c6 improve tracing c516d6d37b improve tracing a118e99c05 Publish 23e74e22da Publish e32643e260 fix 9983ac508f Publish 85c43364fa Publish 76c79cb86c locally optimise contains and reduce bfaadb62d2 locally optimized cause isEmpty a6169b95c6 Publish 2fc2ceb43b Publish 7f503e0131 fix trace of async 108a9282f5 Publish 5492170454 Publish 05c46bd319 trace promise constructors 106a486d1c Publish 5b24fcf93b Publish 182e26d1a6 fix sig 0b54c677cf fix name 00468c1807 Publish dfcd931b65 Publish 52e6dd7d45 remove mistake 33993f32b2 Publish d651f1632c Publish a0a7dc6364 Publish be846ff0cd remove tracecall - breaks generators 15c64ca0a8 remove tracecall - breaks generators d160dd152f Publish f03ce59106 Publish ff1e673afe revert to array destructuring for arguments 4c25cf822f fixed zipN/crossN 8bb2671080 improve zipN type, made flattenTuples non-recursive b9e3cd0f9e retype cross 7695c851ec autofix 275db3660a make eslint happy. Fixed flattenTuples type 279531da8f Most streams combinators implemented 5e941d03af Publish b8862af01b Publish c832d39389 Call onError on rejection. b46424d957 Call onError on rejection. b5563d8cf7 Add failing test for As.promise c71bd599ff Publish fd1c12d022 extensions 00456e367c Publish 760a282e8f Publish ef8a2a757e Make everything 'function's, add non-curried versions 815861bbcb Publish 120d4e881e Publish 443eb951cc add invariant getOrElse 6f0a50852b Publish 3388e9f0cd Publish 0424b50fdf partition: changed E to never 94ca6bd0ed copy-paste issue d9dddf81b4 update Stream/index.ts f21fd1b276 Streams until takeWhile 5be7c80e4c Publish e2f97b62a6 Publish c8fb69a569 fix 7541560849 fix ae8a243516 Publish 1e5950ed61 Publish 954386fa23 Publish cce8e0ba5d Publish f3f64fbfeb support esm bdd0a8fd9e support esm 94d47a1570 support esm c2771d82f0 support esm d0a2b497e4 all 5896583b3e all 7eae3a1a7e core b91ed665c5 improve d157651989 begin optional tracing a273481ba6 continue 66ab62a170 improve clean d63ebd3b76 continue 538712106b begin optional tracing c85a1db852 node patch to allow module path rewrite 0cea994e63 begin untraced build d8f5f12626 Publish f04fa86839 Publish 60635132ed remove trace off from core 18760ae97f trace all 4fdc3fe76a avoid tracing async & cause a01244df82 improve runtime exit and add stdin stream 6849dc1d8a Publish e389d5129d Publish a3d955d1f2 improve selective 2df3f00029 add ifF and incomplete whenF 20c720cafe improve concat to avoid deopt with empty 7456e81c3a trick for efficient concat af8ba66bff Publish 016312cab4 Publish 38b484a45d port string/boolean b1c8229429 port string/boolean be7aa39a37 Publish 54e6a2fb32 Publish b12967ade5 improve chunk c1a3dbc717 efficient encoding of chunks native buffer support 119abfaa9e efficient encoding of chunks native buffer support 20dc0c590f begin node module 04662897bb Publish 3345fa4051 Publish 28f2cd4916 apply autofix 985f94200a Publish abf4f8ef9b Publish d972e3f8b2 fix type 75fd909344 Publish 05ce4f495d Publish 97b4f0ee06 finish effect foreach fb43d2fa0d realign foreachUnitParN b5a33b472d align foreachPar 1290d9ca5a align foreachPar daa73d9729 Stream group by and others (#416) 4c1f68a8e1 Publish a7f5b5f718 Publish 8d59b224be fix tracing checks and remove comment 1adf5931cd Publish 8a8d23f1de Publish 8b9c7a4626 Publish b925c027ee Publish 5f2b3f0f77 separate node dependencies from core 7b5c2cf214 Publish 2fa40a46b4 Publish ca3937d09f add runFIber 1862eae0b9 Publish 243c897e1b Publish 1c08e208ad fixes 5b2f4edf10 customize runtime and platform 6a97ea583b add default trace renderer 6002dbfe8c trace full path file location in node 359710f8d9 improve 4abfff041a custom trace printer 38948840ed Publish 5fba4f1fff Publish e4de049937 Publish 9a654ce999 Publish 2bcc67ea83 improve file paths in traces a16123f276 Publish 7f022d60f9 Publish fb7b2617a9 render fiber id better bb7f95a0d7 allow space for breakpoints aad7e9c59d Publish 6491e2e7f3 Publish 5e375aa674 fix continuation tracign 48e4b7a70c begin realignment of refail and foreach dcda041d0f Publish 9f73656f1f Publish 63a587b0c4 improve pretty d2eccf8029 Publish 6150545bf5 Publish c70f90480f Publish b10074befb Publish e8a548fdd8 Publish 9bf7a3479a fix bundle 4168af32a9 Publish f37b915dde Publish 279423fde3 import refactor, check function signatures, added missing non-curried functions (#415) d805336c62 fix qio impl 1df4b7cc91 add qio ad a bench target 4d20dbb699 improve reversed buffer 8639c7f76b optimise ringbuffer 63105f96dc Publish 3e4579e8af Publish 6efbe621fa fix names 8931ab2a4c unbundle compiler, fix newtype comment 78a6a22ebe unbundle compiler, fix newtype comment f08d40aad3 Publish 63d527ea77 Publish 4cc700b94f Publish a18bdf50e0 figure if file is a module ad4dede99d More stream combinators (#414) 73fe489cc3 fix 006901d818 Publish ac3c66c577 Publish 6552806e80 Publish d02eff5a71 Publish 1aa1fde750 trace call 463c1e13f4 trace call 01795f3fba trace call 0b06013327 improve pretty 5fd813f303 Publish 477b2d6239 Publish a4c7680744 Publish bd2f44166c intro moduleMaps 99efec96de Publish 22d87d3d67 Publish 141bb5e295 avoid internal traces 5bddd62404 Publish 3039347fb2 Publish ef4fde4f25 Publish 937911f20b add member tracing 54176ebab6 remove tracing for class and members 77ac74f9ad Publish 7f21b3d50f Publish 19916a3427 bind instances 4bf0ad198a bind instances a7cb2b12ed Publish aa84033527 Publish b4a41fedd7 Publish 1e7f684899 Publish fd6256cc2a Publish 020aeae61f fix 81168e449f disable tracing locally 84211ac865 don't trace array 0b3e0b7b74 don't trace array e6ef479125 data first 98198a3527 avoid duplicated trace db05ae6067 always add traces 04613a9fb1 always add traces 927d78bc45 always add traces f6a91794d7 bench untraced combinator 33857ed714 add custom runtime bbaafc6cd0 improvements add4f08896 improve cause safe and fixes 9ec96768a4 improve cause safe and fixes d2a7672ef9 improve cause safe and fixes 6d981b723d foreach data-first 36462002f1 Publish f2cdc85845 Publish 8a9a762563 fix 26b8dcf3e4 fix f08f00ff2b Publish 8270588ae3 Publish 38d5d73816 Publish 995441b973 improve f43fc0b747 integrate traces in pretty print 5409f40b59 integrate traces in pretty print d3e7314771 begin trace integration e0ea305b21 Publish 6923f8edbd Publish 42e720094e Publish 3403656fab fix 98ad88b9b4 continue 98d3d72b85 relative traces 8dd7de3f11 relative traces fd8609cf14 continue 590d381229 continue dataFirst 5dccb3be74 continue selective tracing 588f5aec9c enable selective tracing 28c5e6d0d4 enable selective tracing f6405d3e21 Publish e2137ba35d Publish a0a94ae944 Publish 369148e557 continue 48b4c9fac5 continue with data first c0191650b2 continue with data first 68ad81b981 fix test ceaac4417f improve derivation of module for datafirst 04cfa0b27a improve derivation of module for datafirst 6aa4ad2af3 data first optimizer 5a545c0bd5 data first optimizer 8b79e9eafd make sure jest uses compiler b4a5818e80 add try catch 466aae2868 Publish 906b7e79c2 Publish 5ca8175e81 Publish f35fd4a5bc fix flow optimization f848de9c88 fix flow optimization f0d0bef4aa prettify lib 655cee310c prettify lib 6f63ddea77 prettify lib 1ef9509a59 Publish cb7fb575fb Publish 5717b7a370 Publish 638a565f3e Some stream combinators (#409) e24f83245f comment newtypes 2334c2c5b0 fix test 8d6a696bfc cleanup, add plugin in build 654fe70b26 cleanup, add plugin in build 678bd9a8d5 cleanup, add plugin in build 24ff9f6b82 Publish a115c34b26 Publish 8c7c3c2821 Publish 49e6201aa9 improve 9cd279ba52 improve d0a5bfab5d Publish e333055756 prevent unsupported syntax 23614e12ff Publish 2d9758ac7a remove leftover 5fb8dbb65f Publish cc75850c66 Publish 7faca0f7e2 Publish d2173e2042 introspect type for eta reduction 201e670e76 introspect type for eta reduction 9607defbab Publish be1ab7e3f5 Publish 97bdbe9fc4 add arity tags 71875fc9c4 Publish 26561b2266 Publish 36033e817b Publish c00be28e42 fix end position 5c6c6a79df eta introduction for tracing fadccaff71 eta introduction for tracing 3ceb7f8509 Publish 78bebb3367 Publish 79f1d9711c improve 5d4ea5055d extract ringbuffer 060f0b94dc Publish f4a08020a5 fix 6b9a441090 Publish 99ad531ac6 Publish 5906254d6a add global switch 91fe12ecc2 Publish cb83f2aa9b Publish 1451929675 Publish 6a3f5387aa fix child recursion 18608fe2e4 fix child recursion 093a90cb2f trace sleep 81f9f4a54b add benchmarks with tracing on 6f7c309ded remove debugging 530e10d3ed Publish 1be197ed77 Publish 8c06b6b531 fix c30b053c2a Publish 12c2725d89 Publish 780c6f62ad Publish 8b83d9bc05 improve f175a880bb improve a92841b581 trace constants db6c49b4e9 trace constants b7e2612aad fix type 0c531bee00 trace core df53a357d0 trace foldM c31cefe45a improve trace of succeed 13cea71d5a Publish 06622da494 Publish 846b68bc7a Publish ea3a26f537 improve 0371d3ba7b trace andThen via more precise replace c19399ef66 add trace via bind 74657b6273 add trace via bind 2cf934cc06 improvements 378e9b79a4 improvements e83d773652 remove old property access to services e18395c9f3 Publish 9161f97154 Publish 822d96573f Publish 4ca71f7e3c Continue with tracing (#408) acd335313e Continue with tracing (#408) 8fa95a8f16 fix d6a3c63c50 improve traceAs b15effa15a improve 3adf26a386 improve traceAs c06d18565c trace tuple 3dd2a72b59 Publish 849298e379 Publish 3caa47dae5 Publish 7d5871bbf8 trace succeed 3c810a4045 trace succeed c4f197f350 avoid loop a2dc1c7501 Publish 5149f574c7 Publish 766a34606f prepare plugin for publish 914f9b7587 prepare plugin for publish 883b5cae5d Publish 592a5fd079 Publish e832e22a8a share current file name via variable 8a5da3806e Publish c717b02b1e Publish d16d7ef14d Tracing via Compiler Plugin (#403) 250abb550e Publish da0ff0f463 Publish f8141e5650 Added mergeAllUnbounded and its dependencies (#404) 39c31084c7 Publish f88b9a1faa Publish 1913997731 Add Bench (#402) fb906b0ca7 Rollback Execution Tracing 3d1f59dd2e remove leftover default be1612433a improve introduce check and restore tracing status baffa3571e Publish babbaf3ae2 Publish 656b7675f1 Execution Tracing (#401) e5242a94f8 Publish 5760b5a797 Publish afe1ed5839 Fix a358b29732 update Stream index 5b29d4b155 distributedWith and broadcast methods f43798e48b aggregateAsync methods f5afe088f8 Transducer methods e25b331801 Fix Schedule fixed 6d282b61cf Publish 076ee07394 generic conditionals 1331751c24 Publish 0904ba53b7 Publish 1448aa9d2c improve array types d5e3b8a104 Publish 4c8b35a534 fix bind 5d3aa992d6 Publish 92e7b8e017 Publish e552a0c422 enforce lazyness on genWithHistory de75e31f9c enforce lazyness on genWithHistory 712fbf0d65 Publish ace1bb1460 Publish 7234489025 improve d2b35d6deb Publish ddc886ffb1 Publish 309ad96cb6 fix 3c5201cbe8 Publish dddd523540 Publish bdba3d0522 improvements 73a1cf9f44 Publish 621f9afa2a Publish 14ee4192b4 compose n-layers in sequence cd8c5f7a68 Publish b9e03ec73e fix missing next in genWithHistoryF 3287c9edbd Publish 88af50bde5 fix gen d3ff1f6afc Publish 30509d3c88 Publish 3b33cabef6 Bump eslint-plugin-simple-import-sort from 5.0.3 to 6.0.0 (#392) e71ea218a7 Bump eslint-plugin-simple-import-sort from 5.0.3 to 6.0.0 (#392) 07b612f5be Publish 6d8961ad46 Publish ff459c54d9 improvements cf5eede9fd continue with methods 658221637f Publish 00f427ca3a Publish 7a70c65635 continue with methods aa78d9b713 add collect variants 5426686447 rename collect to continueOrFail 242f67adea Publish 5a556c8456 Publish 82ffba1580 fix b4f4d237aa Publish 8af9ba0d35 Publish d23abdb561 improve customisability e97da183f5 Publish e0f5df3d69 Publish 1a3daf7dd6 release control on hook continuation 99392367c6 Publish d1b8003c93 Publish 1b3a8b95dd make runMain pluggable ab3429245c Publish ae25edc7e0 Publish 666a327830 fix runMain exit code 197577dbe4 improve sink c650204b0b fixes 02589ab3d1 managed zip methods 7a2d0a9246 continue with managed 67334ea80c comments 9a5da6879a continue adding methods 7749eb26bd remove fallthrough 886485ff37 Publish 6a5f2b3e8f Publish 766e1862ae listen for process exit in runMain 76645f6883 Publish c0b4cdd56c Publish 79d9053ef2 continue with methods 4812a245ac Publish 4d71e0a2fe Publish a78545eaf2 fix teardown 81255785ae ensure all fibers are interrupted before exiting process 3eb0f81e89 teardown in runMain aa055766d2 Publish 042ea2ce98 Publish e0a2d966f5 continue on managed ecf30f315c managed methods df258d023c managed someOrElse 8452c1dfbd Publish 048635b02c Publish 6280397773 fix cadee80da4 add managed methods 00611c315c Publish 99b3013f44 Publish 370cc92ef3 renames ae635f9d2b Finish sink combinators (#386) 4c5ff95d73 Publish 0db1d5036a improve array foreach b69442f2cb Publish d47321a51c improve foreach record a0b5b3b923 Publish 25675fc4ad Publish b80cd6762b fix record foreach e2d2e97588 rename array to chunk in stream 28ebadeab5 Publish 982d80feee Publish 0747a92414 fix unrefineWith 29c0d84872 Publish 1100860ff5 Publish 7aa0c96c16 minor improvements on sink zipWIthPar 617385c029 Publish ccbe7dc257 Publish ed7c1120ae add standard provide functions 8ba2f92ba5 use foreach dbdc0445a4 fix managed provide c6721c0d76 fix types 10a1d61656 more combinatoes, added provide to Managed 29d5e0d273 more sink combinators 11ead63765 absord -> absorb 1a56f4571d Publish 56dd1e3b2e avoid default import of async in effect 953040acce Publish 667ccd5de6 Publish edc79cb617 stream provide functions 1c3b08a2eb improve schedule f788fb6328 Publish 5f7416dbb0 fix 51f2d456e6 Publish e23dd15f48 fix mutable mistake f4ca310470 Publish e43819073b Publish 3b1dcebb9d move array takeUntil 2a51bbfccc Publish d27ce542a7 Publish 01fb3b1707 interface in push 70ca34a684 fixes 00b96d8917 some stream and sink combinators 3cc9e2152d add result 260285b4e7 Publish 4b2f783b1c Publish 598bb3e525 fix test f3bcc7b3da elect releasemap to module c5570c8912 elect releasemap to module f475d2f550 add methods to managed d730f008d0 Publish 871831c1e5 Publish e3105db27e fix type 76d0c50839 Publish d138d7361f Publish b07f7965d0 suspend empty 33bca7edf8 Publish be0579dfab Publish 142cf41e2c add empty layer 4de3291a68 Publish 5d04f23e69 Publish a04757de64 add use in layer f11e5ee9e5 Publish b23ee05f49 fix 06bbfdc448 remove pipe 2acc2c0ecd Publish 12f46d065c improve eitherT f4dcbbd3f8 Publish 132f8151a2 improve 7a3764b8f6 Publish 2b01a5945f improve c6f2ccb087 add optionT 72fed96035 Publish 4bb1031f7b add matchers 3307a079c2 Publish 5908291c3d Publish fc77d732e7 fix comment d4bf4c79d3 Publish 3c488aff52 Publish 687354c88c squash cause in runPromise 5858792fcc Publish 2509e838ae Publish ac343bc3fa add union functions eecc2d517a Publish adb035238f Publish 6b2f5e1322 improvements 98253ca060 improve provide and add managed methods 9d476c5dd5 add preallocate 23366b5dce Publish a65360a565 Publish 6b380bf4fc add methods 1fbe3234d5 Publish 02087ed6ed Publish ae19fa6a8b add methods bc8218bf2f improve reporter 0df182331d add failure reporter to fiber context and fork 2002a453eb add foldCause f6525dc979 add methods ecb8aeeb7c add methods 6322eb831c Publish 7deec01d6d Publish 1527ce9414 improve naming d871af2a8d Publish 58eb63f9e3 Publish 6ab7929cb2 add managed methods bffbe17400 Publish 998d127a68 Publish f405584f73 add methods 5f0e2213c6 add methods 20180c9a04 continue methods c940f1bb47 add methods to managed 07829ac740 Publish f6ca5b5946 Fix SyncLayer All cf3869e35e Publish aa708e2d1c Publish f2fcd9586f add readOption 83638cd6e9 Publish 1ebc5e5787 Publish 4cb90e50df add if variants 6b3b4317f5 Publish b00c1aba85 Publish 9bb2f42c12 add condM 548d6ed975 Publish 9adc42a3e9 Publish 0e59826fa9 safe cause printing 297c09f450 fix cause stack safety acdb7efbcd Publish 3fda1c2689 Publish 472871bbce Fix Async IAll b5e00e2ffc Fix Async IAll 5d8aff2279 Publish 5832b51cbb Publish 1f6332e329 fix c403293204 Publish d2c9a9a225 fix 78f39ad8db Publish 7940054b34 Publish 639412c8b1 fix async & safe recursion on cause/fiber 4b99b6fd93 fix async & safe recursion on cause/fiber ef7ead9201 Publish 4a28f0b2cb Publish 57b08e4fcc native sync -> async 38928f9564 native sync -> async a7439061ad remove leftovers 53650d9f8c Publish 9e252a4a0d Publish 127255611f stack safe async bf4f6dbc81 stack safe async 14f5fb8922 add stack safety notice 6f971995a0 begin stack safety of recursions 1d0e193965 remove unsafe safe a53fd8484c Publish 54534d3a13 Publish 5b6f587f53 remove leftovers 2bf9a56ae0 Fix inconsistent instruction tag 5281aa7bcb Fix ICatch interpreter in Async runInternal 445a3b74d7 Publish 55b926e384 readd run promise 7c7ea70835 Publish d74532e0ea Publish f0e3d73268 fix promise dispatch e6d336254a Publish 34f5289b4d Publish 3afc840418 demonstrate usage of dumping fiber info 504da15ad5 use parseMs to display duration 277ba2f6ae fork parse-ms npm module bde0269367 export Dump and methods a0bbdfa249 add todo b695166151 implement fiber dump fb63535675 expose scope and status on Runtime Fiber 3e07f9a962 Publish 44a9b7130d Publish 173c627e07 add never and unit fibers 93bc56a765 add missing unsafeCurrentFiber() 63cc41607e Implement Fiber toManaged e6ca28c454 move FiberRefLocals type to context.ts a10934b84b move Fiber methods from core to api 457cd1c232 add missing isDone() 069ecaa190 add docs for Fiber properties 32ab15460d finish missing update and modify methods b09278764c simplify existing methods 721226fb5c Publish a0886c700c readd tracer c1fa5dd1f4 add interrupt finalizer 10ce03c437 Initial encoding for Async (stack-safe) 0f9312c6e7 Publish 4f04b22bac Publish 73ab84d174 revert check c2f296d749 Publish 3813d751ef Publish 207de7e756 bugfix b56e6c9683 Publish 835035480d add dsls to array b4ec3030fe add array generator 7c2eae1124 Publish fd7b506c6f Publish be85e0fcaa fix 9bbde0bcea fix 85acfd715b Publish c3282b6264 Publish eab6bb9130 improve generator perf d2f6e0a512 improve generator perf 32d9657ae1 Publish 71e5fe966d Publish fe7395de2e fixes df07eb2e47 add useForever/launch a77985f7bc Publish cdc4dec853 Publish a50d26f7d4 use property key for layer memo df24e43ec3 use property key for layer memo d4513af898 Publish 3360744cdd Publish 0e4d24420e use property key for tag key 0328280bcb Publish 4aa15f2ac6 Publish d1c772fab7 finish with zio methods 626ce38c93 add methods 2f4679f781 add methods cd0782307d add source in stream f99e80af34 Publish 3c213bc482 Publish 410378b0cd add methods, rename readService 6651a861a6 Publish 6121d6bdc1 Publish 3b47867b0b fixes c12f0de0f4 Publish 6243ca89e7 Publish 4d79775344 fix nested fiber interruption 96c2b6c955 add methods 7ea4deac07 Publish 8d2b9d962c Publish 30c9eead5d Fix test, expected behaviour d856b1c4f2 decrementAndGet() wouldn't decrement 76d44de330 Publish 95d903cb24 Publish 19633508ef fix c9963b661b Publish ee3816599b Publish 0aa6142927 rollback change f0eaba841f remove test 25d5956d90 try structural variance 7ef9429a64 try structural variance 2b99e40fee improve signatures 83feae8836 remove list from effect 25f6f2e838 continue 163c0fb2e4 add missing export c55a57fc8b fix types e46c6bb4b9 continue adding 2c6c119531 add methods e0ae60d5ea add merge c351f4e150 continue 9d95e699f4 add methods 81ae29a679 Publish afc3b1e3a4 Publish 60a271f932 Fork funkia/list (#342) 9bf75d23ef Fork funkia/list (#342) 7688e4a694 fix comment ca19f3f17a Publish 3dd1247412 Publish ffae12a1d2 add fromNodeCb 3a6b50a0d9 Publish 8e08ccccd2 Publish ebc3e58319 add tools e7246acd24 Publish 2caa02e004 Publish 1e95c3aae4 add utils bb2c4cc72a Publish 9becece4f1 try-catch to prevent edge cases 39876b5271 Publish 2ab3aad95b avoid double patch 71ad4d17fb Publish d164f7cf80 export patch 9e75bad8cf Publish 0a9e458151 have operators as a module 08fff627be Publish 875e671590 fix type d684ca4470 Publish ddce13055f Publish 135a491315 rename has to tag c70412f153 rename has to tag 5d6cafac3c Publish 63920070a4 Publish 66770b82ba improve a3e1c29cd0 add additional dsl dbfec4811d add additional dsl 0845094ae9 remove unused 05eb4d7639 make sure get is in the driver 4c1371411c improve type a9d8e1f02e improve 93ce6a256c improve readabiity 5bef9a6805 improve design 387b93819a use symbol to identify 6c050d2f1e add primitives c3d0944319 add combinators fd6ddecdd4 fix package 6ecbf18117 promote async 8be2ed70a1 promote has 561f3ced3f remove redundant type 6aaccd5bc1 continue 1f60e20175 continue 38a57bbcac Begin work on sync layers ec7bcd904d fix format 836fa3df19 add gen overloads to some modules 2b3e39d7cc add gen overloads to some modules caca8c1e4e include error and env 039d672221 begin c1436c6a67 Publish 61014a8fc0 Publish b7cf39ca92 add async fromEither fromSync e41842b9eb fix format and filter 79b5329a8a filter added to system/stream (#332) 139b7d584e Publish dd48fbf083 fix run functions f95ad0e448 Publish 5711a175f4 non enumerable operators c7ccf49614 Publish 29e3e6de0b Publish 9c5b62e425 unsafe makers in queue 935fad4dde Publish d73c5c48cf Publish 6f7eb92a45 allow dynamic hash in layer 7f02f053e5 Publish ee5d14b4cc Publish 4da097bbb1 add layer operators 6244fe7ca3 Publish 42e388ca6b Publish 52fae6adf1 initial encoding of layers 0f5c10d729 initial encoding of layers 0b58349355 simplify some ReaderT transformation functions 3324095dac Publish 31e0504d2d Publish 806cc706f5 remove fake test e74db31475 improvements 7eda4785f3 improvements b16093a886 Publish b5f1045898 Publish dad2ca9f7b complete option ccd7dfb40e complete option 26093542ef style: format code 8ddda3d226 style: format code ec17154311 chore: move babel dev deps from pkg to project level c7590a3c71 chore: move babel dev deps from pkg to project level dbcaac4376 chore: remove unnecessary deps at pkg level 19e50d6da0 chore: remove unnecessary deps at pkg level 300e7e64bf rename either to or 475e5f3732 add some missing 7734cbf736 Publish 9c5e451c86 Publish c0476c87dc fix types b71c4e94a2 Publish 2aa8604465 Publish c371d9f390 add tree module 5a0eef3737 fix getFail signature a2303a6f55 Publish aa094e7ce0 add fake flow 650e8258bf Publish 8223082cf8 readd pipeline, does work 81440b46ee Publish 074d89c4c0 rollback 943225f736 Publish 37a5d1d663 try direct infer 9c7a64ed3a Publish 80effdc25a remove fake pipe, doesn't infer correctly bbcae59f31 Publish d297d00fb8 add fake pipeline operator fef8dbfc84 Publish e08aaaa6b3 Publish ddff361d81 improve 61f7c61ca1 add stricter form of gen as gen_ (lightweight) 37cb8a6107 add sync adapter to either 3e4981c3c5 add sync adapter to either 0bdc769255 add test 5818175c84 Publish f1446db829 Publish 576abea6c9 allow to yield services everywhere d189eb8e65 allow to yield services everywhere de78e14d36 Publish 26c456a4a8 check corruption in generalised gen 28e67d4392 Publish cf87e94669 improve 378e28ee14 Publish 6bfb75c3b9 Publish 9a6944a7f5 interop Has 5ce9f2e584 use data first 8d46e1ace1 inline managedNumber 9dca8663c5 fix acac422cf8 fixes de42857579 minor graphic improvement 9c6d80ef18 improve types d2e1e77b22 allow dynamic extension of interpreter in genF b50bd15a1e allow dynamic extension of interpreter in genF 9478e35e3e add optimized variant for classic types c184f0c797 add generic dsl for gen 41156ee2e1 add managed gen 55d66a4d3d improve 6e1a230bf7 improve exception 3e2129603f implement generator for Stream effect 3ac1d2cef5 add bad generator 1b4c44f216 improve types 267fd9dbcb improve types 8957873124 Publish d0817474d1 Publish a9ea2e3fc1 add onNone to gen 076d69726e add onNone to gen d950be3d2e Publish cdea3dd46d auto load async interop when using effect 3034073ad6 Publish 52223a58d2 Publish d329f44240 continue with generators 1c4343fb02 continue with generators 82aecaf20d Publish 9a39b6c14c Publish dee3b4f6a0 add async generator 7ccfaa7f72 remove fused a3760499d6 generator for option 553b34affd improve eff1f50e05 add generator for sync fc4b0c2b3c generator for either 0eb2e0b3e0 Publish 28b2e50de9 Publish e6399f0312 defaults break inference 56fb9507f0 Publish 6dd0ceb074 Publish 8e560eb8cd defaults 665691382c Publish 3d4b153798 Publish d1c4f351a9 fix cdf545c582 redundant 4f8bc52cac ensure defects are catched b4faa3c95f remove try/catch stupid perf and untyped 76df4d9880 remove useless cast 9180e07776 revert 6fd6b2baab showcase error management cdea5f7828 improve f4fb196fbb improve 48f70597fd Publish dcc63c7a74 Publish 24f7f942a8 improve example 41927482ad improve dbfbc8ed6a Publish dbcb0654d8 Publish 850b52f3b6 mix types 483755de23 improve 5d2237a56d make sure defects escape ee37bd6bed add try/catch support d280393935 Publish 9d3d505652 Publish e173970dbf fix c58a322d57 improve 15683b19ae improve 8917aa4fd3 Publish f91d07a05c Publish 6a30c3f81e generator based do 0f1f7a45ea Publish 31d9dd8269 Publish 7f8ff809cd add compute from ts-toolbelt e3c5e31ef4 Publish 7f3676c0fb Publish fc7d2de4fd improve type extractors d32c9908ed Publish 32dfb936dc Publish 0fd52575fa Publish 9a388cf716 add provideServiceM to async 703a54dfec Publish af4da590d8 improve async b29b023d36 Publish 4e714c2c1d Publish 22532a65bc resolve circular references e361215741 Publish 1f519d3454 Publish e7571ec909 fix type of forever e3dd8d70e5 add small description of cause 9fd90980e6 Publish 9f90537ad1 Publish 2406c81a04 fixes 954641bdf5 fixes 08509bc4ee add instances aa5b3e0efb add instances 30ea6d1a8f improve reduce-like allocations via free structure 95a3aa4b08 fix semantic 76b7879748 improve foreachParN by killing fibers 270b53a329 add test e8e14fef48 Publish 760e7d50f5 Publish 13de9f0cb6 make foreachParN in line with zio 29d6d9477f Publish 66e6277203 Publish 6c872eace1 fix async type 18b3f94091 mix integrations at run to minimise size in Async 8e5669c0b7 mix integrations at run to minimise size in Async 65c15e92ea interop async/effect 6fc9674879 Publish bbc91326ef Publish 0b0bbbfdf1 provide ffi integration in effect fiber 4ebf6c77dc Publish 9653a672e8 Publish 3456199f1f improve ide autocomplete 0430be4eef Publish 51a318b928 Publish 00fe2068d9 add layer restrict f5a1b9a358 Publish b8f7ff1e69 Publish c3028b4305 add fromConstructorManaged 485382033f Publish de2000b660 Publish a49284d269 add helpers for type extraction e050c54f27 Publish e5580d3b94 Publish bd3a5c688c add more variants 0a4766003b Publish f3b69a8ffc Publish 5578862d67 improve layer construction helpers c04880f581 Publish c82dd0f837 Publish 38007d5127 fix ts stupidity fea31f0cad Publish b95c7794fe Publish 265bc3f726 improve demo 7fa73ba15f remove classes from example 5e49448261 improve example 14e119a588 improve derivation 44ae1be349 eta reduction 1059bc2260 add generic support 3f184ec990 exclude generics from derivation 1c228abac0 improve derivation ae39e36d07 fix typo 60d0af365b improve derivation 4e4a474d7d add derivation a la service a7d6fb6754 add ignore 19166bfe92 effect head ad6868aae4 add get fbf9454711 continue effect functions 3c8e39f8e2 add forkWithErrorHandler ef0f7d67ca remove duplicated of sandbox-unsandbox 9503be1837 Publish 16efd5b2ab Publish 0768f0f8a5 improvements 7dcb461180 remove useless suspend 4cd908e59b Publish 08aaf7a6f4 Publish 43c4afa6fd continue with effect combinators 3b28b7bf95 reduceAll methods 6e498465d7 filter methods af979c734e add missing methods 49213c173b Publish d044bbdee5 Publish f610a7f7df remove type lambdas to avoid inconsistency 347fb44cbb multiple category instances 5102b31c99 multiple category instances f190377105 begin fused data-type b630b10c91 Publish 550501abfd Publish a9f47b706d use function in managed core 0792492128 Publish a01f6200fb Publish 620939cff9 fix stream fromArray 5e3c26cee0 Publish 591c3d3c88 Reduce FilterableWithIndex Dependencies, add Array instanced 83526c5ffc Reduce Filterable Dependencies, add Array instances bde1306e27 Fix typo 823de0fca4 Make requested changes 85fd464455 Add Filterable and FilterableWithIndex typeclasses f93cfb639c Publish 1be466b441 Show interpreter 355c89eccc Equal interpreter d51e401622 Add category specific HKT index 2b22a5b9a1 encode type lambas 0e083cbd97 fix category mapping 1c9fd1a040 add XPure category 8dd00a2cd7 add native decoder 1e92aea6df Publish 8fd174e411 add instances to Id fc3342e0ed Add monocle c2826b11f1 Publish 43f63767ba add category faee1e72bf add show instances ed5115ccef fix circularity e7f28bd6a3 Publish 478081f140 Identity & Associative ops ca2d8652aa Publish 95b09c5684 fix chainF 194cfdffb2 Publish 30837d669d Publish 6e18e0393b fix comment 74f4c8e4d0 rename a7652258a2 add Equal getTuple 03ebf971fb implement tupled 1603a57600 add comments 0d8076dffe add either instances 7751cdb7c1 add either instances 4c68e91901 add test for merge d52d2cb988 Publish ef96c6eb23 Publish 76d170b16d stream merge functions bc28da1a97 continue porting functions a511324158 fix d0c7962ca9 port light interruptible promise from workshop 2bb9a60789 add foldable array 566f789cf7 add common ops 31277e45f3 fix combineChunk and remove confusing merge 2563b43940 fix comment de509c73bd add array ops 83edd30378 revise array in system a1abb4555d fix foreach constraints 9d97162bc9 improve kind opacity 418dfe2a29 simplify base signature of sequenceF 88eab34667 add missing operations d25a85f83c add missing operations 870a0ad345 discriminate associative/closure 9d0dbc8c61 add record fromFoldable 0226761ff5 add missing branded 2cc71bfc18 add missing modules 42d1b2c899 add missing modules dad00e95fe add set module 40f8b4d7f5 Use custom parameter to distinguish HKT 01e3a73550 begin article and simplify hkt b5631092d4 Publish eca7f3321c Publish b6baf2cc2e done with fixes from comments 334960de11 update core 7060a8d40b continue with fixes 79486b35e0 interpret XPure based sync as effect primitive e013d6ca97 autofix c0a4d8634e continue 08409d2dab avoid circular dependencies with scope f8627b6ed4 addressing some of the comments 12beed3819 yield fibers after max op to increase fairness 620f382162 remove S from system packages 812dc6a736 fix 4999bdcaf0 interpret XPure based sync as effect primitive 4c498da51d fix typo 874b1eae8e xpure based sync effect 0a2ea909ca xpure based sync effect c7155f34ca add fromFunction a82392a5b1 continue with layer instances c1062b5648 continue with layer instances 75bd3880fe add modules and layer instances 53e6f86129 consolidate function syntax in array a44b27fb86 rename of to do 9aecb844d1 rename of to do ff326863f7 begin taskEither 980df579e8 begin classic task 8f6d2900f9 fix filterOr signatures e838598406 add forkAs 0d6fc8eb7f continue with methods 87fa674a75 Publish 2da3361e80 Publish b8b6e481d5 fix typo 71d8e46b2d Publish f0aa1d9f6c Publish 971a5976f8 use `function` and add some or* methods f3680e021c add toLayer, refine fromEffect naming a73322a206 add promise lift functions 6be92b3155 rename toPromise to to according to zio ec0b330204 Publish b3fc1b9308 Publish 04dcafe0b3 add Stream halt() af4acff4da add Stream fromSchedule() 65f328bfdd add Stream repeatEffectOption() 15099c2f88 add Stream unwrap() c9b4cacd4a add Stream flatten() 2a4f8c9a0a add Stream fromQueueWithShutdown() 9c8edc3865 add Stream fromQueue() 352d1fb8d8 add Stream fromArrayQueueWithShutdown() 435589c048 add Stream ensuringFirst() c1dec5a751 add Managed ensuringFirst() 27bc66fbca fix takeBetween dc13114c8a Publish 0f850c636d Publish 3b62e161b0 improve has 14124a9fa9 avoid distribution on InnerHasType ced92d0619 improve has comments 013db2f61b timeout variants f8c51960b5 port zio/pull/4171 703b37cd12 Follow fixes to zio/pull/4121 39f888f510 fix typo in AssociativeFlatten ac11ebf919 fix typo 1a661d6b11 improve Ix 987eb0c509 fix hasURI 09d1c48687 fix 727037adca attempt to fix compiler error 5663e22020 begin indexed 054aaf79bb cleanup has type, rename augmented to Tag dd0dafba30 cleanup has type, rename augmented to Tag 33672d110f allow to listen for push completion 22a5a5b7cc improve Stream's effectAsync 5878833c5f sandboxWith 40755e0b59 someOrFailException 3556f74b54 Publish bbb7cb581b Publish 5dde6d9470 someOrFail ab503d42cf split XState into separate files d5787a2d99 check memoized fn haven't been called more than expected 948bb729a5 split XReader into separate files bf046872aa split XIO into separate files 7ec64069ad simplify branchF implementation 3bd7dd76cd move Combined into their own files b33245bd69 split Array into separate files 24d56b3bbc split Associative into separate files b984ab7962 split Bounded into separate files 381ecd0160 split Closure into separate files f9e085631f split Ordering into separate files 699ff8cbcb split Commutative into separate files fd6b75bec9 split Either into separate files fff27e820f split Equal into separate files 1504fe70a1 split Identity into separate files 9b30f96128 split Inverse into separate files 35ddd3ca90 split Ord into separate files 25be5fc19a split Reader into separate files 69e1cc551f split Record into operations and instances 9eeb88004e replace iterable named reexport with export * 557b5ace83 use instance() for Effect instances creation b75bd21b9d replace contents of HKT/index.ts with autogenerated code 4d98e58482 simplify variance Strip 596ac92199 improve releaseMap design f333e58623 partially parameterize releaseMap, remove coerce a1eb959aea Publish 7375256675 Publish 7e05a710f6 avoid potential self-reference in sync cases 7e973964ab partial rollback on absurd 7f10c33f28 remove duplication wait/await, rebase 4c5cc12f5f implement Handoff class c47ebe32a6 add Promise await() b9f3baa027 fix absurd() in Function/core c3a520cf3a fix pullArray f723298af0 trying out pullArray ff11c92ee7 add doc to accessStream e4a92df296 add empty/emit/emitArray in Stream/Pull 165289aaf6 rename BufferedPull pullElements to pullElement c9500f585a add Stream accessStream() ac7966a0b6 add Stream accessM() b96fbb9c5f add Stream access() b65e459b17 add docs to methods 287d564d92 remove useless annotation 0e8acd9719 remove useless var ab24814d94 remove duplicate, fix coercion of S bdfa905d3b simplify Stream/Sink#raceBoth() 1f4a5be61e add Stream/Push halt() 4a05cff41d add Stream iterate() edea8bfdd8 add Stream fromArrayQueue() a7b71403c6 improve Stream fromArray() d7ea339076 add Stream finalizer() 69f01b406f add Stream environment() 87bf278038 add Stream empty 61daae7dad add Stream done() f8927322f2 fix: Cause/errors#isRuntime was wrong type guard 39c58c18c9 add Stream dieMessage() cf61316d81 add Effect dieMessage() c060d226c4 add Stream die() d85530cc78 add Stream concatAll() 30a7cde47f add Managed switchable() b21c8e8a25 add releaseMap accessor 6f1b9490b8 add noop Finalizer ec2b65be6b add ReleaseMap#replace 3b6107a3a3 add explicit return type on ReleaseMap#addIfOpen 13914479b3 add Stream bracketExit 9546df9d4c add Stream bracket 6f6ba7cb3f add Managed make/make_ 068ab86b9a add Stream apply 02163fbab0 add Stream absolve 5af15015eb add Stream succeed b845d9fda6 add Stream fail af837f00f3 rename single letter imports conflicting with generics 1adca91980 split Stream methods into separate files f64a4d50c1 some/someOrElse/someOrElseM 2c5efa91ba add retryWhile/Until ac8384ea65 minor improvements 47d6b3c7fe Publish 37eba0753a Publish 1451bf5484 fix typos 8d3571269a remove specialisation of SI-SO 69518f5a15 add repeatWhile 65cfcca96a add repeatUntil b15ecaf32e add repeatN ba1049c917 add reject 2731f1f87e derive selective from applicative 08d92a8f32 improve variance definitions in (X)ReaderT eefb1ff950 refineOrDie 34953a003b invert default interruption strategy on raceAll 3c54c037b5 add note 21ad35e257 remove log 23d4f3e6cc add raceAll 0dbcd0f1b2 add partition parallel variants 4d23d1eddf add partition and none 5a63cb119c add mergeAll variants e818789740 add memoize a3ade5c81d add mapN 1fe7bb3715 rename sequenceS to struct in prelude 4a13bab812 rename sequenceS to struct in prelude 083577f6db add loopUnit adb3fc03e3 add loop ba1cc5cd11 add left 500775d46b add iterate 7cdf4de900 add interruptibleMask aee02befa2 add getOrFail 78eb905d68 add forkScope and forkScopeMask 97cba89b9d add fromOption 5b68cb269a add fromFiberM f305c18820 fix comment fd03cb879f add forkAllUnit and fromFiber 4ec666f4dc add forkAll 273c5b1a22 add foldRight b04ba1fdda ass firstSuccessOf fb22bb69c2 add filterOr methods 84125af9b5 add eventually 3c35497a70 add ensuringChild and Fiber collectAll 659ed5f364 fix general pattern matcher 698bde75a2 add collect 0574150964 begin tests a6906c60c4 add catch some 644dee9cee improve exports 98a354cf86 codegen exports 132bc6b7df add unrefine f6ff87d1f5 add catch some cause a373df50f9 add comments 015b16ab72 implement cached eced5bc2c5 add bracket on error ee68f3d2e6 add uninterruptible to close a769545e24 fix imports d025bd7180 add combinators to effect 7c11e8eb18 address https://github.com/zio/zio/pull/4086 924cbdded1 address https://github.com/zio/zio/pull/4121 4806dcf1db improve clean param 212999234a pass auto if uri level config is not defined 7dc4f841c5 fixes 10b0402323 add some comments e522885f26 Clear Fix on StateT ac93adb7b2 prevent alias of statein-out 8d5779d445 progressively replace Fix at the top d2d6e5af1e avoid duplication of impl 73061b197b rename union to append, inverted union to prepend 06ce72cff7 apply orfix on local uri constrains 9f046b12cb promote KindFix to simply Kind 5d69c989a7 fix name 71a443ee46 allow structured uris to introduce layered params f391017673 fix 2f7b9f00a5 geing adding classic data types 610326752a fix test c93f6583f1 simplify ee67593ec0 rename run to either e1a174b3cd improve demo, fix branch b97dd76444 begin selective f38fc954c6 Do DSL 229c80f6c8 improve impl 13bbeb3b47 move examples into 'examples/' 96beda845a simplify InvariantT variance erasure 7a5dd142f9 wrap Array Monad and Applicative in instance() 093eb0c093 Revert "try using pipe" 74fdf9eae9 Revert "force either in scope" 65671951df Publish 879044bf06 fix access type e037be82b3 Publish 9247fe67c7 fail returns never on A 40c4111b14 use unknown to constrain A 6eb6589550 Publish a700a93c71 try common index for modules 6fd9da0378 remove not used 0652aecbac try indexing modules f530669c8b try using pipe 385c3a242c readd effect as dependency 51746e87bb force either in scope 61fdb1bf1b add IO to package json 66895a32e1 fix IO comments 285bfd8059 fix 837d2c9249 use dsl 4e3ecf5146 alias variance definition 1eae53adc1 fix mix 43df0fbd38 finish kindfix refactor 2505ad6c5f fix dsl b5cff45942 continue fixes 75a1b406ef fixes 5e4778b2d2 fix alias lookup 4e551ff18d improve demo 158b6b96d5 use kindfix fd2a58e17a improve types 02d6305cba type helpers 170448cd76 helpers for type combinations e3d5efce03 fix 3d50c61b89 adapt fx access/provide 6136dc149e Fix behaviour for E types dccb64b25f fix alias 7ae83a9f01 continue with examples and transformers 989dcfc94f improve naming 5deeac1f79 arbitrary length reindex 9386cc26ae use export * syntax b886ffcf8a rename pure to xpure d50d7e1e04 add bounded a4532c964a fix number 23cfd31ac6 add invariant transformer 40c2ba82be extend demo 11 with some functionality 53ce1350bd move all demos as tests c32cc94d23 fix imports 7cb76d330c remove usage of NonEmptyArray 2e06eb7c7b flip condition in OrFix 50e215d43e move OrNever into its own file 62c190027d move base interfaces into separate file a510116325 only reexport in index.ts 1e121cbeaf rename Kind to kind 8aa0f5f143 reorder hkt 9496783d8d move IndexForBase and IndexFor into Kind.ts 0c29471ac0 improve naming c863c54c1c improve index for c1bd143189 improve indexed 54fd794dc2 improvements d18b879286 simplify 4ba2b6e379 handle dynamic URI in getValidationF 50b8d11584 allow for custom index in eitherT 003dadf185 Publish 92923870e3 fix index 2c8765a03a Publish aa6bd7160c Publish 937e74b887 dynamic parameter index 5c9177ef40 prepare 417b4daa56 prepare c349dee829 add custom constrained parameters 674d6ebacf remove useless type trick 08cece7b4b variadic 488d780a3a add reader & readerT da7b7d3808 up to 5 layers d2aa5b8ef2 increase layers 4aee7344b5 prototype 697878e88f add dsl provideSomeF f7bd280058 fix 90560d8240 generalise Has e54f23dff1 fixes with autofix 2eb8ffcd09 fix all eslint errors 6ccd41ca7e fix all eslint errors cc397d87cb move validation to dsl a9dfecd70f use getValidation from FX c8cb44eadb rename preview to tmp f66acce933 revise uris f43a40136d revise uris 277ac3dc6f compact variance declarations d849b73dc6 validation effect 65f2d45861 correctly interpret associative either & never for identity ad7a2cc5cd interpret never differently 13ce8f7b99 fix instance 0d09fc36bc effect instances 35729db028 correct encoding of initial types a2bee0a13f fix validation and any c17d5a1a7d generic sequenceS 8fa3f655d8 encode variance generically 9fd8cd7e77 unify fix & orFix 0e04b9c826 mix all s 06ac5b2757 add mix all 3e05d80527 fix 59d2947a85 improve 84e9a38f03 comment prototype b64f142eca track composition constrains e22994205b track C, make Fix intersection friendly d440020d4b experimental encoding of dynamic variance 19a4ab869e add extend da504af868 add compactable 6ad7b45b2f improve sequenceS d045258c6c generic sequenceS e3b0ae428d add foldable with index 58b6c5f5fc use succeedF c447984a99 remove redundant param 09d32e051a improve either validation instance construction 76875c0ae4 fix signature e42053c4f9 relax 4e8dd86597 generic accessMF 05ad9450d2 begin dsl 2512029398 remove typedoc c5a9c14bfe add nojekyll d4a07f548a try auto-generated doc 785bad82c7 avoid redundant declaration 00ed39d7a6 begin environmental 879650fdb3 remove leftover 60ef68dceb add witherable with index a00d766531 add witherable 20b88ba0f6 add wiltable with index 48c5bf00b9 add covariant with index, fix traversable with index 5cea6d077f add traversable with index d5429a8639 add wiltable e543edcab1 add show 0b9a54c19b add inverse & ord 953c89cd42 add invariant 25b82fd946 eta reduction f37983ba8c add instances e790af7a39 encode variance in F 938240af17 add reduce right with index 531925b3d8 rename key to index 7233f402fe add key & reduceWithKey c07bc1b9cc foldable 3d7dde93fb continue reorg 98db0917d3 add effect data-types 8077952cab remove broken doc 424a40583e add docs to main 304be861e3 separate system and core c60b082dac separate system and core 3e4e0421e4 cleanup 6b5fefeb2e Publish cf762f352d fix associative either, begin restructure 71ac066c73 fix state & reader bd8d3b9e35 rename map value uri 07829c9656 improvements 4e403c992f defer inference of state params 8bd279c152 improve naming 1287d81f65 add strict input & output 10ceb8756b fix schedule signatures c63b3358df generic do 78066e93f1 add state fdfe8ac5c1 split instances b7f8a8280e move fx typeclasses 58c7c0bbbf comments and unpacking 4f12dc6a53 take dependencies in makers 114b8aff4a expose base makers 6d2e201b06 fix types a76b1cf897 fix zipPar 0b3dd61a52 improvements 725377c99a improvements 1e79c99211 implementation utility 9d9f122aca fix traverse 6f2fc67085 rename foreach 0a8651a447 export foreach 4126db9f2c map instances f1a6c569c5 fix array zip f7d63e0a09 uplift hkt level decdcbdb4d finalise uplifted encoding 885e9abb3c uplifted encoding 9ef730ebfe continue 8d626cc624 remove obsolete 5cb732c39e fix up following variance 7ab167c1d1 fix name 19cea74f57 remove ForeachablePar fb6cc91910 de-opt sequenceS 5d06f4852d fix yieldNow 0acd918cdd remove log 026c7ca438 fix tapCause d3731e34fc optimize sequenceS by mixin foreachable 0a9bfacd05 continue b9431ac230 add fail typeclass 70608a2527 export either sequenceS 26b8053dda export either api a395671251 generic sequenceSF 150d6963a8 small fix c3ab9a6ea7 fix 43fbf1441c Positional Variance 3ece079a41 rename 6c380b983b add intersect b4356f57d0 add environmental c878c58513 reader zip 9004f60aa7 begin reader f3fcedc5e1 continue 12bc5924ae continue 0aec777b93 continue a5e3dbd7b6 add instances 24b5889111 continue on pure 8ce6124d49 fix comment e4e3a4ffd3 begin port of zpure 005b4e3a03 standardise naming b323a377b2 avoid naming collisions 260c4c4d7f makeDerive 7744cd17d2 improve make instances 4d9baefbe8 revise instance construction 664dc5c8bf add inverse & ord a94ac2a8f8 identify building blocks, fix server use b51a8154c4 revise id 300dd2ae37 reorg c704ffd500 refine async 4df7a4f2ce avoid re'export of has d615fca648 represent compositions are intersections 6d5301be3e move things around b91794c79b fix contravariant signature d80ef70684 fix associative ce0bd17e8b improve eq array d05d0c7ddd fix names a14f20a93f improve equals 4c2f5a83d8 continue ad3182f756 add id d447974fbc improve 3945b52c28 small fix 4875ba2fb9 continue 4b9f347ac9 fix name 13e01df0cc continue 81c8f252e0 re-export effect feb02f0e76 async instances 8c0f0f7d74 continue 5d40437a7e fix naming 4e06ffbe65 equal e43bd8440a schedule applicative 055d55a359 improvements 46a549722c fix naming cb26b6f78b try out zio-prelude typeclass lattice 267b68e1f0 begin next effect refactor b6478bd98d revise foreachPar implementations 72e7b58c95 zio/pull/3992 stack corruption fix in interruption 64de9e6c7a extract loop from windowed 6228b781f9 add copy f15a47639a replace schedule c8775032da fix order 8bc3922cb2 complete new schedule e0db64b254 schedule zipWith 9da194fa39 fix type fd5fedf55f schedule while variants & zip left/right 4a2e2041f0 schedule untilInputM & untilOutputM cc52b55074 schedule untilOutput 2f16b91f23 schedule untilInput a8e0c7ee0e schedule tapOutput 0e7f7d7d61 schedule run & second & tapInput 51c4329105 continue porting schedule 742e0f0d94 schedule reconsider & reconsiderM 5b787a3de5 schedule provideSome c3acc78af7 schedule provideAll 114e22cfbb schedule modifyDelay & onDecision 8ce02ee624 schedule choose & left & right 3c89cd7c57 schedule first & identity 955cec0b33 schedule both & ensuring 074e8a662c disable broken eslint rule 61842d3792 Publish 1dd3852fc1 Port zio/commit/7a4e201c1905d6b6c124e6f2f942e01454c9db5d b8eadc890e convert const to function def 4bdb61468b either, eitherWith 64a6e2be56 delayed 4054277bc2 contramap, dimap, delayedM 561d9c0ff1 combineWith 64bfb41acc unfoldM 3171015f38 collectAll & compose a80b72d229 fold 2038fbeee0 begin porting new schedule 8b8d730f2a WIP edccc86c52 fix catchAll 41127162bd Publish 938c3f66cd add collect variants a4d2398ac2 Publish a4ec3a2013 re-add provideAll d910272323 restructure schedule a8afabcf18 Publish 505092927c rename interfaces in Overloads for consistency f41d090bc9 fix overloads in Record module 0301038f11 test compatibility with fp-ts 1a31e20180 Publish ac0291f739 Upgrade ESLint. 5a8236d482 Remove unneeded pure-calls-annotation in core edc0bf4084 Enable source maps in 'core' 4d9bece5fd Publish adf584c1db change in COf4C to make F extends URIS4 instead of MaURIS 09e22e5102 restructure queue f9fab9a6a8 fix circularity baac0449cf restructure next managed b72e39829c Publish 9234b75534 upgrade fp-ts 2b8e2a062e make on map 8ddca2562c managed sequence variants 97dfa1f9c3 begin module restructure in next 533de3fc88 remove instances and hkt deps from next aff4c5cc92 detach next effect from hkt 3ecdd49f83 remove useless pipe c33f225b31 stream zipWIth e51e745770 continue on stream ea0565b16a add original comments cf8287ed54 minor improvement 6f30842b7b fix comment 2119da6f94 continue streams 11f22b6330 continue on stream api af85c29615 add transducer then 2dac7dc9df keep separated type level identity 553ba6d76a simplify transducer encoding 39c4b19e66 add comments in stream 7b339011d4 continue with streams 787a4c4c52 stream effectAsyncInterrupt 846dd3d2e9 avoid non necessary allocation 92e1e7ef54 improve typing e98bc76eb5 stream effectAsync 3ea8ea96a1 remove global runtime 1ce05f70ff fix type 08713d5dbc improve toManaged 94116354dc continue on stream 3b6d26fcef non-failing finalizer caa8a177ba transducer and agrregate for stream b68d84afd6 implement catchAllCause for streams 3d05788140 improve chain 82be619de8 stream chain f83b2ffc0e add important note on sink semantic d172990eb5 register stream in hkt 318c323a53 Begin next/Stream c0ff18992f Publish e5c6e7ebf1 add accessM/access to streams d7428d8183 fix circularity 2ace2acd5f fix formats 58139c88a3 auto-add pure annotations 6c847a6e46 improve access c69fce1b31 derive access 14972b3e2b add more type aliases with default env 0ae6137976 add .fresh() cdc27a679d remove inner use and add .memo 63565c35a0 add comments d162be6a79 improve memo b3a8282895 include memo map in default env 4360748707 layer memoization 48e3e04214 fix 9477340be4 global runtime d1cb12e483 Allow all 2.x.x fp-ts version to be installed 44b0356331 managed fail 710ced7aff data-first refm 80187dc11d continue refM 4060638864 continue refm 48d2a15bf3 fix f9333242cc map/mapM for refM 29e5cab578 continue on refm 60bb6c0ac5 filter input ea3994439f don't export internals e4ef40984b use type defaults d59d7074ed continue f836492a22 widen xref 30bdc1a6a8 widen apis 2d01d8e438 dimapM on xrefm 64ff068490 widen api e3fa526468 continue refM b861c99fcc small fix fe459b4fd6 begin refM 4eda2f2809 continue on ref 09793857aa complete port of queue b18174ec4a continue on queue a13b0c166f improvements 727c778c67 fix 8be344f86f fix type 1763e97ff3 queue dimapM aba3d3501e fix types 7f988e5b6b continue queue api 04b26cd780 bothWithM in queue ed48db54d0 uncurried variants 00f8e38ceb comment take between 5ba8e17fa9 improve queue bbbc340496 use pipeables d6f2991202 minor reformat c4a98bf589 use pipeable matcher 3ae87395a4 improvements a285832712 fixes e0be2b480d generalise matcher 60f56aca26 introduce pattern matecher 2aaa09c2f0 add test cases from #282 3648d900e0 improvements a561540cde restrict safe coerce d4157b2521 improve ref api f41920f88a improve ref cd872e8967 add validate f8c341eb18 extract process manager from layer f6bdc0a748 Publish d288b4c5f7 avoid early process exit a313c50d2d improvements 6418763a01 improve has construction 7b6ca1f502 suspend foreach f5137e84f9 Publish 042f5b01d4 Avoid short-circuiting map in driver (bugfix) 2ac7896f30 improve ead1c9cc26 revise has f272a34d17 change the default has id 4e20029080 Publish e43502dbe4 enable incremental ts compilation c2a3980420 split build script into build:lib and build:es6 22e36e8816 split clean and build scripts 5b25b40f81 format built files only before publish a123777712 move package script 723a7cd40a move config for package script into package.json f66cfed2f6 move rewrite script da8416fc99 move rewrite-locals script 0e7b763882 Publish 92e9311293 Next: Fixes bdc206b30b Publish 372a98f0c7 fix `foldCause` type 0a46d4ebd0 Next: Improve Has API cfebc916af Publish 2f80e54ce9 Next: Improve Has 8e365934ec Next: Simplify Has, add Layer.main ac38240c10 Publish e6ab007861 Next: Fix unbound method 3a046d688d Publish c48385e117 Add Basic React Runtime 5e463f720e Publish ee4ed285f2 WIP: Move demos 28b94e248d WIP: Move Eff to next f7376e85e0 WIP: Improve API bb81835070 WIP: Use pipeable do ad02b4f893 WIP: add let to demo 8e179d2ca1 WIP: Improve do impl 1aed02afe1 WIP: Remove unused import c9eeae4de5 WIP: Use pipeable do febb917cf4 WIP: Improvements a1700d013f WIP: Pipeable do e9ab4b18af WIP: Improve using signature e675b95a84 WIP: Add router & child router 5da355313a WIP: Improve API 14401dd7dd WIP: Maximise composition remove manual passing a23aad5d37 WIP: Extract server to be able to leverage morphic b702542c39 WIP: Split usage & definition e091fd60f0 WIP: Add method & getBody 8d659c61a5 WIP: Has derivation dfdb419dcd WIP: Improvements 595cad03f3 WIP: Begin work on native http server 57606b6cc5 WIP: Improve demo 1a78161c1b WIP: Improve doc 0c44b8da42 WIP: Document sequenceT/Par/ParN 83a6dab49f WIP: Use sequenceTParN 217e9cfb96 WIP: Implement sequenceT in terms of foreach 1eb17967b8 WIP: Minor improvements d823d96775 WIP: Fix fork fiberRef behaviour 0203945be7 WIP: Improve scheduler 058e18b7df WIP: Global process registry 9a78dd8310 WIP: Fix makeInterruptible_ a0c4278a4d WIP: Improvements d8b15c7dc2 WIP: Improvements in demo d7d6463c11 WIP: Vary metric per fiber 7f9d08ae35 WIP: extract printMetrics 03ea965b82 WIP: Metrics collection via FiberRef 4b2cb6e6e0 WIP: Avoid makeInterruptible_ f6f55cb9ad WIP: Improve Layer & Has 4df75cda10 WIP: Expose fiber context for tagged processes c7439554a9 WIP: Fix refine de9d2d65fc WIP: Add refine to augumented has 171d354d3c WIP: Fix 5f866a6325 WIP: Add success interruption to demo ff2939778a WIP: Minor improvement 564efbd240 WIP: Complex failing structure 1f6f16268a WIP: Process monitor 3cc4a5232f WIP: Improve API 6b36a8bfe8 WIP: Add runtime lowering utility f2ea8da946 WIP: Improve Layer Encoding 1b93b335a4 WIP: Avoid using setImmediate in browser aeafcab61f WIP: Improve API 84346f726a WIP: Fix demo 88f7fcc464 WIP: Update demo 62a21ba79b WIP: Fix typo ed248b2a61 WIP: Improve API a3d435143b WIP: Add provideSomeLayer fb5b5c2ab0 WIP: Improve API c915ff383c WIP: Improve Layer ca8b130c2e WIP: HasURI to symbol 3dabce673f WIP: Reintroduce symbol map 6b51a2a38e WIP: Generalise has 74759d6c61 WIP: Avoid generation of string based ids dae9fa978e WIP: Generic key & reverse map 899e7852fd WIP: Fixes ed0b851391 WIP: Make key explicit 084c172dba WIP: Auto generate string keys for services 88f42be5be WIP: Improve provideSome 00507a71de WIP: Support serviceMap in provideSome e5270cf7d6 WIP: Provide is final by default c12d4e38f2 WIP: Improve Layer API 393ae4ea63 WIP: expose withClock c2131c9afe WIP: Begin Layer 168169a525 WIP: Era reduction 99c5ee5c2d WIP: Migrate to service for defaults 90f1990ff5 WIP: Has Improvements bb21d14c0a WIP: Improvements 0e678a7cc8 WIP: Use Symbol for service URI 4f0f9d9085 WIP: Refine API 7f812068d1 WIP: Refine Has 27e93c99c8 WIP: Prototype Has e49e94cb37 WIP: Minor Semaphore Type Improvement c66dda08f8 WIP: Complete Schedule 05dadea87d WIP: Document Semaphore def6aa9dbd WIP: Semaphore 77bd0eb5fc WIP: Handle Possibility that Parent Scope is Closed 214512aefb WIP: Schedule combinators 0608893886 WIP: Schedule combinators 0aa525f266 WIP: Schedule Fibonacci 6c7aa38317 WIP: Schedule Combinators bf0ec5333d WIP: Schedule unit, untilInput, doUntil 54405440e2 WIP: Schedule tapInput/Output & second 2bb7dd5db2 WIP: Schedule right & run c1db3bf9b8 WIP: Schedule repetitions & reconsider c7ac38fa41 WIP: Schedule onDecision eae8dd5bc4 WIP: Effect summarized, timedWith, timed bfc3db8316 WIP: Effect tapBoth deec1a6048 WIP: Schedule provideAll_ / provideAll 6b81911b96 WIP: Schedule jittered / noDelay / provideSome f894b6a5be WIP: Readd State in Schedule & add initialized 0602f54500 WIP: Schedule ensuring / split / first. Effect tapError fab48743ba WIP: Schedule Either/EitherWith fcba19fcbf WIP: Remove Finalizer when Fibers Terminate f5f3ad1604 WIP: Safer race behaviour 8605ca5200 WIP: Carry interruption error in race 858b9d0100 WIP: Race & Schedule Combinators 170a0bb68d WIP: Schedule foldM/fold 0d9b30b9b9 WIP: Schedule check fcae512766 WIP: Schedule bothWith 1ab193223e WIP: Schedule andThen 1798f76e5a WIP: Both variants in Schedule 3742e8360e WIP: Fix retry 086426bce2 WIP: Don't Keep State in Signature of Schedule 61c5cf6578 WIP: Overload Fixed, add Delayed 4f26465767 WIP: Fixed Schedule e5d8adb547 WIP: Functions on Schedule d06d4f81a2 WIP: Default Env, Clock Based Sleep 0c8c47ae79 WIP: Fix foreachExec overloads 246ff6f2f4 WIP: Implement Managed Foreach Variants 627adc12b7 WIP: Implment basic clock bc85c881b5 WIP: Improve Scheduler & Async e4403dcb1c WIP: Use Overloads for makeManagedReleaseMap 160029e23b WIP: Add foreachExec 4dc8a9dc78 WIP: Managed Parallel Zip & API Improvements b81acc7f19 WIP: Extract Common Type Level 623914a985 WIP: Fix demo code 87b73afdc0 WIP: Continue on Managed 11d4f4208f WIP: Begin ReleaseMap based Managed ac75bbf730 WIP: Reduce Random Env Footprint 69f260a761 WIP: Deterministic Random, Remove constraints f5b57132cf WIP: Avoid never casting in compaction 44b827beb2 WIP: Begin Schedule, constrain env for defaults 8a40336ed3 WIP: Improve run function names 99fbb577b5 WIP: Better Interrupt in foreachPar & foreachParN b63d6419d6 WIP: Fix Fiber Interruption, improve foreachParN 6a327dbada WIP: Improve foreachParN to collect errors fca3ff9ca9 WIP: foreachParN 2217057a9a WIP: Implement Queues 570cecad1d WIP: Complete foreachPar with error accumulation d80540285a WIP: Align with RC21 supervisor & scope c1aaca9a52 WIP: Line Up with Latest ZIO a0b097c556 Publish d1c86f8efd #273 Add some tests and fix issue (breaking) 9707935dbd Publish 2dffd73f3f Fix runToPromise rejection 60b4410986 Publish 4f2af83f45 Fix Mutable Bug on Exit e69a2e4b7c Add test for managed interrupt 2be0c04fc2 Publish 909d86ec44 Align fiber supervision semantic to ZIO fdbd52c40f Improve Monitor 59c8102eed Improve Express & Add waitProcessExit 6bab54b65d Publish 093ba5d4b0 Use default environment 828ef36dfb Add Monitor Layer 57fa4b3ae3 Upgrade Express & Apollo & add foldExitCode eb17b789eb Publish 0eb671326e Add causedBy to exit fold 00a0072d94 Publish e2178fe07f re-fix exitCode ff61a9b732 Publish fb319e1bf7 fix exitCode de31cc7fdf Publish 02350f046d Improve fiber supervision e644327464 Publish 6371b6c7ac add exitCode & improve zoo demo 0d6e904088 Improve Layer Inference, Use in Console 037f9152ac avoid distributive infer 6b1ff717bd add type aliases to layer 748d5ed7a1 Publish b68586b6eb Fix non exported brand f444385a68 Publish 42bcaafbbd Restrict iso & prism to newtype, add name always b03c7c2a47 improve type compaction 67813b6465 Use Layer in CQRS & ORM Config 93b659f107 Update fp-ts 2dc6b15778 Publish 3103db4abe Fix chainError & bimap c12e3d530c Publish 12c8d9b880 Fix compact, export type extractors 662ce47472 Publish 90800cebe8 Native Codec for Morphic 25135fc937 Remove wrong comment 1b57972206 Publish 40c3340b1c Remove Prelude 19857d6795 Config for Model 028bff222d Config Eq & Fix Remaining packages 64b080309e Tagged Union Config & HKT Helpers 879ba0d03a Primitives Config & Show Extensions 4bff97faff WIP 531f4b4410 Derive Eq & Fix Either SequenceT 04ec90a430 Move Pipe in Function 4b19a04ade Morphic Model Integration c2816ec038 Fixes 8a8e0c3aa8 Loop using switch 1ca62a81a4 Minimize allocations in Driver 61a82fa9e8 fix 170dac3d93 Remove unsafe optimizations & dumb benchmarks 40ebb241a3 Publish 553724cc36 Improve naming & fix signature 4e7ced397d Publish a0f342fa9e Add Comments & Erase method 2e8473eb9d Publish aaf0114392 Improve Type Names & DublyLinkedList Usage (#264) 53cb9a0b5e Add useful constructors to Managed 824dd99705 Publish 025ab24d48 Final encoding for Layer & remove unsafe allocate (#263) 27a0368303 Publish 3216e89cc0 Improve compatibility e1629e0235 Fix cause semigroup 7d000d3109 Exit as pointed tree preserving interruption cause & fixes (#262) c651f6649e Remove Modules in favour of Readonly (#261) 5d187431f3 Improve Interrupt Signature, Driver & Fiber Supervision (#260) 192c670226 Add Object prototype version of pipe 8c1a79a574 Prototype ZLayer (#256) 91866f8c99 Enstablish Compatibility With fp-ts (#257) e310b0e111 Publish 2f34ec4b72 standardise chainFirst to chainTap 54a19dd76c Publish 081f2827be refine with policy a0e31c2823 Publish 5e2a293aff add retry with 143fc4553d Publish df9b8b0b3d add missing Do/sequenceS/sequenceT 2b3c0af066 Publish 354683c538 Fix index/Index and validation 6df85cfaaa Publish 2d03b1aa12 improve opaque 31bbc119fe Publish 59f9258bae improve refinement c3a218b2c2 Publish 80e5d573de add Model to core b1e69d3d3c Publish 4cfbe18843 rewrite locals to proper aa5501adbf Publish 749757b46e fix do 7c6d552b54 Publish 01e041e682 fix broken eslint b45464effc Publish 06153ac1ea improve fold exit functions 51b6ff4d10 Publish 227e8d9508 Upgrade dependencies 3539c7cd00 Publish f278ba68a4 Publish 32074982ff Publish 6fdbfc587d Migrate packages to use core (#254) git-subtree-dir: repos/effect git-subtree-split: addeaea0b0abe0dc24ca1d4d0fd194a0e458be70 --- .agents/AGENTS.md | 46 + .agents/skills/ai-docs/SKILL.md | 16 + .agents/skills/bundle-analysis/SKILL.md | 25 + .agents/skills/changesets/SKILL.md | 58 + .agents/skills/changesets/authoring.md | 33 + .agents/skills/ci-maintenance/SKILL.md | 48 + .../ci-maintenance/privileged-workflows.md | 20 + .../skills/dependency-maintenance/SKILL.md | 58 + .../coordinated-upgrades.md | 21 + .../dependency-maintenance/manifest-roles.md | 15 + .agents/skills/effect-development/SKILL.md | 16 + .agents/skills/grill-me/SKILL.md | 43 - .agents/skills/jsdocs/SKILL.md | 224 +- .agents/skills/jsdocs/categories.md | 24 + .agents/skills/jsdocs/declarations.md | 87 + .agents/skills/jsdocs/examples.md | 26 + .agents/skills/migration-guidance/SKILL.md | 21 + .../skills/migration-guidance/annotations.md | 15 + .../skills/migration-guidance/generation.md | 14 + .agents/skills/package-development/SKILL.md | 35 + .../package-development/dependencies.md | 30 + .../skills/package-development/publishing.md | 19 + .../package-development/registration.md | 41 + .agents/skills/performance-analysis/SKILL.md | 17 + .../skills/performance-analysis/runtime.md | 19 + .agents/skills/performance-analysis/types.md | 17 + .agents/skills/scratchpad/SKILL.md | 44 - .agents/skills/scratchpad/agents/openai.yaml | 7 - .../scratchpad/scripts/extract-example.mjs | 260 - .agents/skills/test-development/SKILL.md | 13 + .../test-development/displayed-types.md | 20 + .agents/skills/test-development/runtime.md | 13 + .agents/skills/test-development/types.md | 14 + .agents/skills/vendored-assets/SKILL.md | 47 + .../skills/vendored-assets/browser-assets.md | 13 + .changeset/ai-approved-tool-results.md | 5 + .changeset/align-type-id-paths.md | 6 + .changeset/all-union-record-channels.md | 5 + .changeset/anthropic-image-strings.md | 5 + .changeset/anthropic-strict-json-schema.md | 5 + .../anthropic-structured-output-fallback.md | 5 + .changeset/arbitrary-index-constraints.md | 7 + .changeset/array-ensure-array-elements.md | 5 + .changeset/atom-rpc-query-requires.md | 5 + .changeset/atom-solid-idle-ttl.md | 5 + .changeset/bright-mimes-leave.md | 7 + .changeset/bright-sockets-connect.md | 9 + .changeset/browser-crypto-missing-subtle.md | 5 + .../browser-indexeddb-binary-key-existence.md | 5 + .changeset/browser-runtime-custom-teardown.md | 5 + .changeset/bun-http-server-unix-address.md | 5 + .changeset/bun-stream-preserve-failure.md | 5 + .../cache-invalidate-when-replacement.md | 5 + .../cache-refresh-cancellation-ownership.md | 5 + .changeset/cache-refresh-capacity.md | 5 + .../cache-refresh-zero-ttl-ownership.md | 5 + .changeset/cache-synchronous-interruption.md | 5 + .changeset/calm-ducks-fail.md | 5 + .changeset/calm-headers-hide.md | 5 + .changeset/calm-readers-transact.md | 7 + .changeset/calm-trees-generate.md | 120 + .changeset/catch-stream-channel-defects.md | 5 + .changeset/channel-rundone-completion.md | 5 + .../child-process-astral-escape-arguments.md | 5 + .changeset/childprocess-process-group-wait.md | 10 + .changeset/chunk-slice-concatenation.md | 5 + .changeset/clean-config-names.md | 7 + .changeset/clean-yaks-parse.md | 5 + .changeset/cleanup-before-use-callbacks.md | 5 + .changeset/clear-wings-relax.md | 5 + .changeset/cli-completion-command-aliases.md | 5 + .changeset/cli-completion-shared-flags.md | 5 + .../cli-key-value-pair-first-separator.md | 5 + .changeset/cli-optional-alternative-flags.md | 5 + .changeset/cli-scalar-constructor-names.md | 27 + .changeset/cli-variadic-absence-defaults.md | 5 + .../cli-wizard-option-looking-values.md | 5 + .changeset/cluster-active-teardowns.md | 10 + .changeset/cluster-reply-codec-services.md | 5 + .changeset/cold-shards-continue.md | 15 + .changeset/compatible-dependency-refresh.md | 8 + .changeset/config.json | 35 +- .changeset/context-saved-getter-inference.md | 5 + .changeset/cookies-error-tag.md | 5 + .changeset/d1-raw-native-results.md | 7 + .changeset/datetime-calendar-parts.md | 5 + .changeset/deno-redis-url-credentials.md | 5 + .changeset/deno-writefile-existing-mode.md | 5 + .changeset/docgen-alias-signature.md | 5 + .changeset/docgen-class-property-examples.md | 5 + .../docgen-source-relative-module-pages.md | 5 + .changeset/docgen-unique-example-files.md | 5 + .changeset/doctest-markdown-typescript.md | 5 + .changeset/doctest-statement-boundary.md | 5 + .changeset/dotenv-literal-substitution.md | 5 + .changeset/durable-clock-zero-threshold.md | 5 + ...durable-deferred-into-encoding-services.md | 5 + .changeset/dynamic-tool-parameter-schema.md | 5 + .changeset/eff-1004-cli-display-width.md | 5 + .../eff-1008-tool-param-failure-mode.md | 9 + .changeset/eff-1036-fiber-allocations.md | 12 + .../eff-1038-http-server-allocations.md | 6 + .changeset/eff-1039-rpc-server-allocations.md | 5 + .../eff-1042-http-server-performance.md | 10 + .changeset/eff-1201-http-overhead.md | 9 + .changeset/eff-854-pg-connection-startup.md | 19 + .changeset/eff-961-node-socket-tls.md | 11 + .changeset/eff-963-node-socket-server-tls.md | 7 + .changeset/eff-965-socket-upgrade.md | 9 + .changeset/eff-969-dependency-updates.md | 8 + .changeset/eff-972-deno-socket-server.md | 11 + .changeset/eff-997-service-keys.md | 5 + .../effect-eager-transform-arguments.md | 5 + .../effect-scoped-service-restoration.md | 5 + .../effect-unmatched-reason-preservation.md | 5 + .../effectable-class-override-delegation.md | 5 + .changeset/effectable-mixin.md | 5 + .changeset/effectify-mapper-input-tuples.md | 5 + .../entity-proxy-client-codec-services.md | 5 + .../entity-test-client-fatal-defect-option.md | 5 + ...eventlog-authentication-forbidden-retry.md | 5 + .changeset/eventlog-empty-chunk-framing.md | 5 + .changeset/exact-byte-size.md | 9 + .changeset/exact-http-file-body-length.md | 5 + .../execution-plan-captured-predicate.md | 5 + .changeset/fair-matches-infer.md | 5 + .changeset/fair-workflows-suspend.md | 5 + .changeset/fetch-raw-stream-duplex.md | 5 + .changeset/fiber-map-reentrant-replacement.md | 5 + .changeset/fiber-registration-same-fiber.md | 5 + .changeset/fifty-carrots-punch.md | 5 + .changeset/filesystem-sink-undefined-flag.md | 5 + .changeset/five-spoons-visit.md | 5 + .../fix-atom-http-stream-success-types.md | 9 + .../fix-atom-http-top-level-dispatch.md | 5 + .changeset/fix-atom-query-zero-ttl.md | 5 + .../fix-atom-rpc-client-middleware-errors.md | 5 + .changeset/fix-atom-writable-fallback.md | 5 + .changeset/fix-cors-vary.md | 5 + .changeset/fix-encoded-header-metadata.md | 5 + .../fix-filesystem-integer-precision.md | 9 + .changeset/fix-http-platform-file-ranges.md | 5 + .changeset/fix-httpapi-form-responses.md | 5 + .../fix-httpapi-test-pre-response-handlers.md | 5 + .../fix-httpapi-url-builder-base-path.md | 5 + .../fix-json-schema-percent-references.md | 5 + .../fix-mime-parameter-normalization.md | 5 + .changeset/fix-negative-file-seek.md | 7 + .../fix-openapi-client-form-url-encoding.md | 5 + .../fix-openapi-client-multipart-records.md | 5 + .../fix-openapi-endpoint-transform-order.md | 5 + .../fix-openapi-stream-transform-client.md | 5 + .changeset/fix-otel-logger-clock-skew.md | 7 - .changeset/fix-prompt-date-tab-buffer.md | 5 + .changeset/fix-react-ref-switch.md | 5 + .changeset/fix-reactivity-duplicate-keys.md | 5 + .changeset/fix-read-alloc-invalid-sizes.md | 6 + .changeset/fix-sink-flatmap-leftovers.md | 5 + .changeset/fix-sse-mixed-line-endings.md | 5 + .changeset/fix-static-head-range.md | 5 + .changeset/fix-static-oversized-ranges.md | 5 + .changeset/fix-stream-wrapper-status.md | 5 + .changeset/fix-toml-array-subtables.md | 5 + .changeset/fix-tool-result-serialization.md | 13 + .changeset/fix-vue-ref-switch.md | 5 + .changeset/fix-yaml-folded-scalars.md | 5 + .changeset/flat-pipelines-share.md | 5 + .changeset/formatter-defined-error-causes.md | 5 + .changeset/fresh-pools-rotate.md | 6 + .changeset/fuzzy-classes-compare.md | 8 + ...ph-bellman-ford-infinite-cycle-barriers.md | 5 + .changeset/hashmap-collision-entries.md | 5 + .changeset/hashring-exclusion-endpoint.md | 5 + .changeset/headers-redacted-name-case.md | 5 + .changeset/headers-stateful-patterns.md | 5 + .../http-client-catch-response-result.md | 8 + ...-client-redirect-preprocessing-recovery.md | 5 + .changeset/http-head-stream-scope.md | 5 + .changeset/http-response-content-length.md | 5 + .changeset/http-router-normalized-prefix.md | 5 + .changeset/http-runner-path-boundary.md | 7 + .changeset/http-web-handler-cold-start.md | 8 + .changeset/httpapi-middleware-error-dedupe.md | 5 + .changeset/httpapi-sse-decode-options.md | 5 + .../indexeddb-out-of-line-primary-keys.md | 5 + .changeset/indexeddb-stream-query-limits.md | 5 + .changeset/isolate-unencodable-hydration.md | 5 + .changeset/large-postgres-messages.md | 5 + .changeset/layer-error-observer-types.md | 5 + .changeset/layer-span-trace-options.md | 5 + .../layermap-preloaded-acquisition-errors.md | 7 + .changeset/lazy-bun-redis-import.md | 5 + .changeset/lazy-undici-loading.md | 5 + .../libsql-transaction-client-isolation.md | 5 + .changeset/logger-complete-file-writes.md | 5 + .../mcp-http-resource-template-origins.md | 5 + .../mcp-prompt-decoded-parameter-types.md | 5 + .changeset/mcp-structured-content-object.md | 5 + .../memory-message-storage-clear-controls.md | 5 + .changeset/metric-attribute-order-identity.md | 5 + .changeset/metric-registry-isolation.md | 5 + .changeset/model-field-option-undefined.md | 5 + .changeset/mssql-binary-parameters.md | 5 + .changeset/mssql-ntlm-domain.md | 5 + .changeset/multipart-streamed-part-guard.md | 5 + .changeset/mutable-arrays-preserve.md | 5 + .changeset/mutable-list-bulk-prepend-tail.md | 5 + .changeset/name-random-services.md | 5 + .changeset/net-address-values.md | 14 + .changeset/ninety-books-sit.md | 5 + .changeset/node-http-status-text.md | 5 + .changeset/node-response-preserve-bytes.md | 5 + .changeset/node-sink-cancel-drain.md | 5 + .changeset/node-sink-finalization-errors.md | 5 + .changeset/node-stream-buffer-size.md | 5 + .changeset/node-watch-relative.md | 5 + .../node-worker-unsafe-send-envelope.md | 5 + .changeset/node-writeall-empty.md | 5 + .changeset/normalize-collection-counts.md | 5 + ...number-remainder-negative-zero-dividend.md | 5 + .changeset/olive-queues-persist.md | 5 + .changeset/openai-image-strings.md | 6 + .changeset/openai-optional-sequence-number.md | 5 + .../openapi-omitted-additional-properties.md | 5 + .../openrouter-encrypted-tool-finish.md | 5 + .changeset/openrouter-strict-json-schema.md | 5 + .../opentelemetry-delta-interval-starts.md | 5 + .changeset/optic-projection-replacement.md | 5 + .changeset/optic-string-index-delete.md | 5 + .changeset/order-consumed-criteria.md | 5 + .changeset/otlp-disabled-batch-flush.md | 5 + .changeset/otlp-tracer-span-performance.md | 5 + .../partitioned-semaphore-stale-cleanup.md | 5 + .changeset/persisted-cache-lookup-throw.md | 5 + .changeset/pg-explicit-ssl-precedence.md | 5 + .changeset/pglite-json-string-values.md | 5 + .changeset/pipeline-root-stdin.md | 5 + .changeset/plain-tool-results.md | 7 + .changeset/pool-preserve-reservations.md | 5 + .changeset/port-http-api-builder-handler.md | 5 + .changeset/postgres-channel-names.md | 5 + .changeset/pre.json | 628 +- .../add-adaptive-rate-limiter-store.md | 0 .changeset/pre/add-atom-equality.md | 5 + .../add-bigdecimal-sumall-multiplyall.md | 0 .changeset/{ => pre}/add-chunk-schema.md | 0 .changeset/{ => pre}/add-command-hidden.md | 0 .changeset/{ => pre}/add-config-nested.md | 0 .changeset/pre/add-deno-file-system.md | 5 + .changeset/pre/add-deno-http-client.md | 7 + .changeset/pre/add-deno-multipart.md | 5 + .changeset/pre/add-deno-socket-server.md | 5 + .changeset/pre/add-deno-socket.md | 5 + .changeset/{ => pre}/add-flag-hidden.md | 0 .../{ => pre}/add-from-string-schemas.md | 0 .../{ => pre}/add-headers-remove-many.md | 0 .../add-http-client-request-update-headers.md | 5 + .../add-http-client-tracer-header-filter.md | 5 + .changeset/pre/add-httpapi-with-headers.md | 5 + .../{ => pre}/add-indexeddb-kvs-layer.md | 0 .changeset/pre/add-json-schema-draft-04.md | 5 + .changeset/{ => pre}/add-make-msgpack.md | 0 .changeset/{ => pre}/add-make-option.md | 0 .changeset/pre/add-mcp-2025-11-25-protocol.md | 7 + .changeset/pre/add-mcp-icons.md | 7 + .../{ => pre}/add-missing-tx-modules.md | 0 .changeset/{ => pre}/add-newtype-module.md | 0 .changeset/pre/add-otlp-manual-flush.md | 5 + .changeset/pre/add-platform-deno.md | 5 + .../{ => pre}/add-scalar-show-operation-id.md | 0 .changeset/{ => pre}/add-schedule-tap.md | 0 .../{ => pre}/add-schema-annotate-encoded.md | 0 .../{ => pre}/add-schema-array-ensure.md | 0 .changeset/{ => pre}/add-schema-bigdecimal.md | 0 .changeset/{ => pre}/add-schema-datetime.md | 0 .changeset/pre/add-schema-error-module.md | 9 + .../add-schema-option-from-optional-nullor.md | 0 ...dd-schema-option-from-undefined-nullish.md | 0 .../{ => pre}/add-schema-string-encoding.md | 0 .../add-schema-tagged-union-discriminants.md | 6 + .../pre/add-semaphore-take-if-available.md | 5 + .changeset/{ => pre}/add-sql-pglite.md | 0 .../{ => pre}/add-standard-jsdoc-rule.md | 0 .changeset/{ => pre}/add-stream-broadcastn.md | 0 .changeset/pre/add-tool-set-needs-approval.md | 5 + .../{ => pre}/add-unstable-encoding-export.md | 0 .changeset/pre/add-update-service-scoped.md | 5 + .changeset/{ => pre}/add-values-unprepared.md | 0 .changeset/{ => pre}/afraid-cobras-like.md | 0 .../{ => pre}/ai-openai-config-field-leak.md | 0 .../ai-openai-file-nullable-fields.md | 0 .../anthropic-4-6-structured-output.md | 0 .../pre/anthropic-claude-5-capabilities.md | 7 + .changeset/pre/anthropic-open-model-enum.md | 6 + .../pre/anthropic-stale-max-output-tokens.md | 7 + ...apply-httpapi-endpoint-client-transform.md | 5 + .../{ => pre}/asyncresult-exhaustive.md | 0 .../{ => pre}/atom-stream-error-type.md | 0 .changeset/pre/atomic-optional-rc-map.md | 6 + .../pre/authentication-error-description.md | 9 + .changeset/pre/autocomplete-jk-filter.md | 5 + .../batch-persistence-expiration-cleanup.md | 5 + .changeset/pre/beige-goats-sin.md | 5 + .changeset/{ => pre}/beige-paths-sort.md | 0 .changeset/{ => pre}/better-apples-nail.md | 0 .changeset/{ => pre}/better-rocks-arrive.md | 0 .changeset/pre/better-shrimps-follow.md | 5 + .changeset/pre/big-masks-care.md | 5 + .changeset/{ => pre}/big-pans-look.md | 0 .changeset/{ => pre}/blue-dingos-greet.md | 0 .changeset/{ => pre}/blue-ligers-cheat.md | 0 .changeset/{ => pre}/blue-onions-smile.md | 0 .changeset/pre/blue-pigs-push.md | 5 + .changeset/{ => pre}/blue-ravens-type.md | 0 .changeset/{ => pre}/blue-trams-kiss.md | 0 .changeset/{ => pre}/bold-chairs-yawn.md | 0 .changeset/{ => pre}/bold-planets-shout.md | 0 .changeset/pre/brave-keys-commit.md | 5 + .changeset/pre/brave-rings-update.md | 5 + .changeset/{ => pre}/breezy-meals-see.md | 0 .changeset/{ => pre}/bright-bugs-bow.md | 0 .changeset/{ => pre}/bright-canyons-clean.md | 0 .changeset/pre/bright-clocks-count.md | 5 + .changeset/pre/bright-docs-teach.md | 5 + .changeset/{ => pre}/bright-dogs-fail.md | 0 .changeset/{ => pre}/bright-flags-stand.md | 0 .changeset/pre/bright-graphs-calculate.md | 5 + .changeset/pre/bright-graphs-trace.md | 5 + .changeset/pre/bright-journals-commit.md | 5 + .changeset/{ => pre}/bright-laws-teach.md | 0 .changeset/{ => pre}/bright-lemons-dance.md | 0 .changeset/{ => pre}/bright-planes-smash.md | 0 .changeset/{ => pre}/bright-rats-attend.md | 0 .changeset/pre/bright-tags-recognize.md | 5 + .changeset/{ => pre}/bright-toes-rush.md | 0 .changeset/pre/brown-glasses-thank.md | 5 + .changeset/pre/brown-peas-enter.md | 5 + .changeset/{ => pre}/bumpy-boxes-teach.md | 0 .../pre/bun-websocket-outgoing-compression.md | 7 + .changeset/{ => pre}/busy-lions-sneeze.md | 0 .changeset/{ => pre}/busy-maps-attend.md | 0 .changeset/pre/calm-atoms-relax.md | 5 + .changeset/pre/calm-bash-completions.md | 5 + .changeset/{ => pre}/calm-buckets-own.md | 0 .changeset/pre/calm-buses-smile.md | 5 + .changeset/{ => pre}/calm-carrots-march.md | 0 .changeset/{ => pre}/calm-cars-rest.md | 0 .changeset/pre/calm-coins-smile.md | 5 + .changeset/pre/calm-contexts-rest.md | 5 + .changeset/pre/calm-dates-view.md | 5 + .changeset/pre/calm-dragons-command.md | 5 + .changeset/pre/calm-graphs-span.md | 5 + .changeset/pre/calm-graphs-travel.md | 5 + .changeset/pre/calm-heads-close.md | 5 + .changeset/pre/calm-hounds-smile.md | 5 + .changeset/pre/calm-keys-repeat.md | 5 + .changeset/pre/calm-masks-count.md | 5 + .changeset/pre/calm-pages-hide.md | 5 + .changeset/pre/calm-pandas-retry.md | 5 + .changeset/{ => pre}/calm-panthers-nail.md | 0 .changeset/pre/calm-pears-smile.md | 5 + .changeset/pre/calm-queues-await.md | 5 + .changeset/pre/calm-ravens-reflect.md | 5 + .changeset/pre/calm-redis-clear.md | 5 + .changeset/pre/calm-results-align.md | 5 + .changeset/pre/calm-schemas-encode.md | 5 + .changeset/{ => pre}/calm-seas-smile.md | 0 .changeset/pre/calm-servers-share.md | 5 + .changeset/pre/calm-services-rest.md | 5 + .changeset/{ => pre}/calm-squids-hug.md | 0 .changeset/pre/calm-tools-parse.md | 5 + .changeset/pre/calm-tools-remember.md | 5 + .changeset/{ => pre}/calm-tracers-sample.md | 0 .changeset/pre/calm-tuples-align.md | 5 + .changeset/pre/calm-tuples-pick.md | 5 + .changeset/pre/calm-wolves-reduce.md | 5 + .changeset/pre/cancel-tedious-requests.md | 5 + .changeset/pre/canonical-number-schemas.md | 12 + .changeset/pre/cap-rpc-streaming-buffers.md | 5 + .changeset/{ => pre}/chatty-poets-type.md | 0 .changeset/{ => pre}/chilled-mice-wash.md | 0 .changeset/{ => pre}/chilly-pumas-rule.md | 0 .changeset/{ => pre}/chubby-buckets-feel.md | 0 .changeset/{ => pre}/chubby-parents-flow.md | 0 .changeset/{ => pre}/chubby-planets-fall.md | 0 .changeset/{ => pre}/clean-balloons-tan.md | 0 .changeset/{ => pre}/clean-bulldogs-care.md | 0 .changeset/pre/clean-cats-document.md | 5 + .changeset/{ => pre}/clean-dryers-sneeze.md | 0 .changeset/pre/clean-formatters-agree.md | 5 + .changeset/{ => pre}/clean-geese-work.md | 0 .changeset/{ => pre}/clean-goats-wave.md | 0 .changeset/pre/clean-lions-cancel.md | 5 + .changeset/{ => pre}/clean-needles-shake.md | 0 .changeset/{ => pre}/clean-tires-guess.md | 0 .changeset/pre/clear-graphs-reduce.md | 5 + .changeset/pre/clear-hairs-pump.md | 5 + .changeset/{ => pre}/clear-spies-boil.md | 0 .changeset/pre/clever-maps-care.md | 8 + .changeset/pre/cli-config-built-ins.md | 24 + .changeset/{ => pre}/cli-help-choices.md | 0 .changeset/pre/cli-no-color-values.md | 5 + .changeset/pre/cli-wizard-mode.md | 5 + .../pre/close-failed-resource-map-scopes.md | 5 + .changeset/{ => pre}/cold-knives-lie.md | 0 .changeset/{ => pre}/cold-rooms-show.md | 0 .changeset/{ => pre}/cold-sloths-wave.md | 0 .changeset/pre/common-mammals-tickle.md | 5 + .changeset/pre/compact-json-schema-checks.md | 5 + .../{ => pre}/compact-json-schema-enum.md | 0 .../pre/config-provider-option-lookup.md | 15 + .../{ => pre}/config-withdefault-eager.md | 0 .../configurable-schema-reference-policy.md | 5 + .../configure-cluster-rpc-buffer-limits.md | 7 + .changeset/{ => pre}/consolidate-encoding.md | 0 .changeset/{ => pre}/consolidate-sql-error.md | 0 .changeset/pre/cozy-geese-remain.md | 5 + .changeset/{ => pre}/crisp-seas-warn.md | 0 .../pre/cron-locale-independent-aliases.md | 5 + .changeset/pre/cron-single-value-step.md | 5 + .changeset/pre/cron-testclock-infinity.md | 5 + .changeset/{ => pre}/cuddly-rooms-bet.md | 0 .changeset/pre/curly-files-range.md | 5 + .changeset/pre/curly-graphs-connect.md | 5 + .changeset/pre/curly-graphs-snapshot.md | 5 + .changeset/{ => pre}/curly-poems-talk.md | 0 .changeset/pre/curly-ravens-decode.md | 5 + .changeset/{ => pre}/curly-spies-relax.md | 0 .changeset/pre/curly-streams-stop.md | 5 + .changeset/{ => pre}/curvy-apples-float.md | 0 .changeset/{ => pre}/curvy-birds-float.md | 0 .changeset/pre/curvy-graphs-code.md | 5 + .changeset/pre/curvy-melons-stare.md | 5 + .changeset/pre/curvy-otters-sync.md | 5 + .changeset/pre/custom-cli-prompt-prefix.md | 5 + .../custom-http-security-openapi-generator.md | 0 .changeset/{ => pre}/cute-heads-thank.md | 0 .changeset/{ => pre}/cyan-loops-grow.md | 0 .changeset/{ => pre}/cyan-radios-switch.md | 0 .changeset/pre/cyan-shirts-grin.md | 5 + .changeset/pre/d1-batch-statements.md | 5 + .changeset/pre/dark-dancers-rule.md | 5 + .../pre/deduplicate-json-schema-fallbacks.md | 10 + .changeset/{ => pre}/deep-rivers-spend.md | 0 .../pre/deferred-cleanup-after-completion.md | 5 + .changeset/pre/deferred-resume-starvation.md | 5 + .changeset/pre/deno-write-copy-errors.md | 5 + .changeset/{ => pre}/dirty-lamps-trade.md | 0 .changeset/{ => pre}/dirty-laws-wear.md | 0 .changeset/pre/doctest-console-output.md | 5 + .changeset/pre/document-child-process-env.md | 5 + .changeset/pre/dry-bugs-hug.md | 15 + .../duration-temporal-object-input.md | 0 .changeset/{ => pre}/eager-coats-cheat.md | 0 .changeset/{ => pre}/early-birds-dream.md | 0 .changeset/{ => pre}/early-donuts-argue.md | 0 .changeset/pre/early-jobs-bow.md | 5 + .changeset/{ => pre}/early-peaches-check.md | 0 .../pre/eff-115-sync-scheduler-microtask.md | 5 + .../pre/eff-117-hydration-reactivity.md | 5 + .../pre/eff-121-http-router-web-handler.md | 5 + .../eff-123-openai-compat-unknown-events.md | 5 + .changeset/pre/eff-137-web-stream-interop.md | 5 + .changeset/pre/eff-140-deno-crypto.md | 5 + .changeset/pre/eff-141-deno-child-process.md | 5 + .changeset/pre/eff-142-deno-terminal.md | 6 + .changeset/pre/eff-143-deno-stdio.md | 5 + .changeset/pre/eff-145-deno-services.md | 5 + .changeset/pre/eff-148-deno-http-platform.md | 5 + .changeset/pre/eff-151-deno-redis.md | 5 + .changeset/pre/eff-153-deno-http-server.md | 5 + .../pre/eff-153-websocket-initial-frames.md | 5 + .changeset/pre/eff-154-deno-cluster-http.md | 5 + .changeset/pre/eff-155-deno-cluster-socket.md | 5 + .../pre/eff-162-browser-crypto-chunks.md | 5 + .../pre/eff-170-bun-multipart-stream.md | 5 + .changeset/pre/eff-210-cookie-validation.md | 5 + .../pre/eff-212-secure-mssql-transport.md | 7 + .../pre/eff-216-secure-http-redirects.md | 5 + .../pre/eff-218-bound-sse-pending-state.md | 10 + .../pre/eff-219-key-value-store-file-keys.md | 5 + .../pre/eff-220-cli-control-characters.md | 5 + .../pre/eff-332-http-response-compression.md | 10 + .../pre/eff-337-preserve-mssql-parameters.md | 5 + .../pre/eff-342-clickhouse-number-binding.md | 5 + .../pre/eff-389-execution-plan-attempts.md | 5 + .../pre/eff-428-pg-transaction-permit.md | 5 + .../pre/eff-467-execution-plan-events.md | 17 + .changeset/pre/eff-477-schedule-concat.md | 5 + .changeset/pre/eff-487-web-tracer-shutdown.md | 5 + ...f-51-partitioned-semaphore-interruption.md | 5 + .../eff-523-registry-scoped-atom-runtime.md | 5 + .changeset/pre/eff-532-stdio-terminal.md | 7 + .../pre/eff-537-preserve-response-metadata.md | 5 + .changeset/pre/eff-542-rc-ref-generation.md | 5 + .changeset/pre/eff-547-node-tracer-timeout.md | 5 + .changeset/pre/eff-548-bun-serve-scope.md | 5 + .changeset/pre/eff-549-worker-send-error.md | 5 + .changeset/pre/eff-552-txqueue-shutdown.md | 5 + .changeset/pre/eff-554-sql-resolver.md | 5 + .changeset/pre/eff-558-deno-file-web-range.md | 5 + .../eff-691-default-logger-ordering.md | 0 .../eff-693-rpcgroup-handler-deps.md | 0 .../eff-694-cli-completions-module.md | 0 .../{ => pre}/eff-695-layer-mock-dual-api.md | 0 ...-697-rpcserialization-json-array-decode.md | 0 ...698-rpcserialization-unreachable-branch.md | 0 .../eff-700-httpapi-middleware-errors.md | 2 +- .../eff-701-httpapierror-respondable.md | 0 .../eff-704-stream-merge-predicate.md | 0 .../{ => pre}/eff-705-layer-tap-apis.md | 0 .../{ => pre}/eff-706-servicemap-mutate.md | 0 .../eff-716-response-id-tracker-map.md | 0 .../{ => pre}/eff-717-openai-socket-cancel.md | 0 .../eff-718-embedding-model-surface.md | 0 .../{ => pre}/eff-725-fix-catch-jsdoc.md | 0 .../{ => pre}/eff-726-model-dimensions.md | 0 .../{ => pre}/eff-727-cli-help-alignment.md | 0 ...730-language-model-incremental-fallback.md | 0 .../{ => pre}/eff-736-cached-with-ttl.md | 0 .changeset/{ => pre}/eff-738-cron-prev.md | 0 .../eff-739-openai-function-call-done.md | 0 .../eff-740-missing-summary-parts.md | 0 .../eff-742-http-client-request-web.md | 0 .../{ => pre}/eff-744-sqlite-migrator-lock.md | 0 .../eff-746-fixed-iteration-catchup.md | 0 .changeset/{ => pre}/eff-747-unify-effect.md | 0 .../{ => pre}/eff-754-url-builder-any.md | 0 .../{ => pre}/eff-755-references-core.md | 0 .../eff-769-select-text-highlight.md | 0 ...774-mutable-list-append-all-empty-array.md | 0 .../{ => pre}/eff-777-schema-make-effect.md | 0 .../eff-778-http-middleware-path-logger.md | 0 .../eff-779-keyvaluestore-layer-sql.md | 0 .changeset/{ => pre}/eff-780-layer-unify.md | 0 .../eff-781-fix-stream-toqueue-types.md | 0 .../eff-782-httpapi-status-literals.md | 0 .../{ => pre}/eff-783-atom-http-api-errors.md | 0 .changeset/pre/eff-798-schema-parser.md | 5 + .../pre/eff-802-tagged-union-match-or-else.md | 5 + .changeset/pre/eff-806-schema-error-stack.md | 5 + .../eff-819-cluster-workflow-shard-groups.md | 0 .changeset/pre/eff-825-pool-performance.md | 8 + .changeset/pre/eff-825-pool-use.md | 6 + .../pre/eff-825-scope-single-finalizer.md | 7 + .changeset/pre/eff-837-dependency-updates.md | 8 + .../{ => pre}/eff-849-transpose-option.md | 0 .../pre/eff-851-skip-empty-remote-write.md | 6 + .changeset/pre/eff-861-indexeddb-equals.md | 5 + .../eff-946-concurrent-traversal-cleanup.md | 5 + .../pre/eff-952-terminal-failure-stack.md | 5 + .../pre/eff-953-interruptor-stack-trace.md | 5 + .changeset/pre/eff-955-run-sync-dispatcher.md | 5 + .changeset/pre/eff-956-await-all-children.md | 5 + .changeset/{ => pre}/eight-turkeys-own.md | 0 .changeset/{ => pre}/eighty-lies-deny.md | 0 .changeset/{ => pre}/eighty-poets-draw.md | 0 .changeset/{ => pre}/eighty-swans-scream.md | 0 .changeset/{ => pre}/eighty-teeth-sniff.md | 0 .changeset/{ => pre}/eleven-apes-share.md | 0 .changeset/{ => pre}/eleven-numbers-bake.md | 0 .changeset/pre/empty-env-values-missing.md | 9 + .changeset/pre/empty-geckos-dispatch.md | 5 + .changeset/{ => pre}/empty-gifts-beg.md | 0 .changeset/{ => pre}/empty-http-rpc-client.md | 0 .changeset/pre/empty-snakes-return.md | 5 + .../pre/escape-completion-choice-values.md | 7 + .changeset/{ => pre}/eventlog-unencrypted.md | 0 .changeset/{ => pre}/every-olives-burn.md | 0 .../{ => pre}/expand-schema-filter-output.md | 0 .changeset/pre/explicit-env-record.md | 5 + .../pre/explicit-otel-service-identity.md | 5 + .../export-schema-encode-keys-interface.md | 0 .../pre/expose-ai-prompt-part-schemas.md | 5 + .../{ => pre}/extract-semaphore-latch.md | 0 .changeset/pre/failed-otlp-checkpoints.md | 5 + .changeset/{ => pre}/fair-bees-relax.md | 0 .changeset/pre/fair-birds-limit.md | 5 + .changeset/{ => pre}/fair-buttons-share.md | 0 .changeset/pre/fair-citations-stream.md | 5 + .changeset/{ => pre}/fair-cooks-stop.md | 0 .changeset/{ => pre}/fair-cups-train.md | 0 .changeset/{ => pre}/fair-dryers-speak.md | 0 .changeset/{ => pre}/fair-forks-shake.md | 0 .changeset/pre/fair-jobs-like.md | 5 + .changeset/pre/fair-logs-correlate.md | 5 + .changeset/pre/fair-logs-listen.md | 5 + .changeset/{ => pre}/fair-pandas-prove.md | 0 .changeset/{ => pre}/fair-pants-float.md | 0 .changeset/{ => pre}/fair-poems-visit.md | 0 .changeset/pre/fair-sampling-content.md | 5 + .changeset/pre/fair-sinks-catch.md | 5 + .changeset/pre/famous-loops-flow.md | 5 + .changeset/{ => pre}/famous-wolves-lead.md | 0 .changeset/{ => pre}/fancy-glasses-grow.md | 0 .changeset/pre/fast-graph-path-queues.md | 5 + .changeset/{ => pre}/fast-times-camp.md | 0 .changeset/{ => pre}/few-birds-matter.md | 0 .changeset/{ => pre}/few-cougars-dig.md | 0 .changeset/{ => pre}/few-foxes-grin.md | 0 .changeset/{ => pre}/few-mirrors-pull.md | 0 .changeset/{ => pre}/few-socks-poke.md | 0 .changeset/pre/fiber-join-all-errors.md | 5 + .../{ => pre}/fiber-runtime-start-metrics.md | 0 .changeset/{ => pre}/fiery-jokes-care.md | 0 .changeset/{ => pre}/fiery-mammals-call.md | 0 .changeset/pre/file-prompt-jk-filter.md | 5 + .changeset/{ => pre}/fine-walls-decide.md | 0 .changeset/{ => pre}/first-success-of.md | 0 .changeset/{ => pre}/five-parents-relax.md | 0 .changeset/{ => pre}/five-worms-rhyme.md | 0 .changeset/{ => pre}/fix-1332.md | 0 .changeset/{ => pre}/fix-1917.md | 0 .changeset/{ => pre}/fix-1927.md | 0 .changeset/{ => pre}/fix-1940.md | 0 .changeset/{ => pre}/fix-1947.md | 0 .changeset/{ => pre}/fix-2002.md | 0 .changeset/{ => pre}/fix-2012.md | 0 .changeset/{ => pre}/fix-2015.md | 0 .changeset/{ => pre}/fix-2260.md | 0 .changeset/{ => pre}/fix-2268.md | 0 .changeset/{ => pre}/fix-2271.md | 0 .changeset/{ => pre}/fix-2384.md | 0 .changeset/{ => pre}/fix-2414.md | 0 .changeset/{ => pre}/fix-2419.md | 0 .changeset/pre/fix-2497.md | 5 + .changeset/pre/fix-2499.md | 5 + .changeset/pre/fix-6464.md | 5 + .changeset/pre/fix-6491.md | 5 + .changeset/pre/fix-6521.md | 5 + .../fix-ai-empty-params-structured-output.md | 0 .../{ => pre}/fix-ai-text-toolkit-typing.md | 0 .changeset/pre/fix-ai-tool-call-id.md | 5 + .changeset/pre/fix-ansi-cursor-to.md | 5 + .../{ => pre}/fix-anthropic-caller-toolid.md | 0 .../fix-anthropic-code-execution-deltas.md | 5 + .../pre/fix-anthropic-header-redaction.md | 5 + ...-anthropic-memory-tool-requires-handler.md | 5 + .changeset/pre/fix-anthropic-memory-tool.md | 11 + .../pre/fix-anthropic-plaintext-bytes.md | 5 + .../pre/fix-array-non-finite-indexes.md | 5 + .changeset/pre/fix-atom-batch-dependencies.md | 5 + .changeset/pre/fix-atom-kvs-async-write.md | 5 + .../pre/fix-atom-suspense-registry-cache.md | 5 + .../pre/fix-bash-subcommand-dispatch.md | 5 + .changeset/pre/fix-bigint-gcd-lcm.md | 5 + .changeset/pre/fix-cache-set-race.md | 5 + .../fix-catch-orelse-error-erasure.md | 0 .changeset/pre/fix-cause-map-annotations.md | 5 + .../pre/fix-channel-effects-finalizer.md | 5 + .../pre/fix-channel-schema-decode-unknown.md | 5 + .changeset/pre/fix-chunk-fractional-counts.md | 5 + .../fix-class-constructor-defaults.md | 0 .changeset/pre/fix-cli-help-table-spacing.md | 5 + .changeset/pre/fix-cli-missing-flag-values.md | 5 + .../fix-cli-mixed-global-flag-context.md | 0 .../pre/fix-cli-subcommands-requirements.md | 6 + .../pre/fix-cli-unexpected-arguments.md | 5 + .../pre/fix-clickhouse-connect-timeout.md | 5 + .../pre/fix-cluster-entity-context-bleed.md | 5 + .../pre/fix-cluster-reply-defect-isolation.md | 5 + .../pre/fix-cluster-shutdown-deadlock.md | 5 + .../fix-cluster-strand-request-shutdown.md | 5 + .changeset/pre/fix-cluster-stream-recovery.md | 5 + .../{ => pre}/fix-config-array-default.md | 0 .changeset/pre/fix-config-or-else-evidence.md | 5 + .../{ => pre}/fix-config-withDefault.md | 0 .../fix-config-withdefault-filter.md | 0 .../pre/fix-context-add-or-omit-types.md | 5 + .../pre/fix-cron-and-representations.md | 5 + .changeset/pre/fix-cron-make-validation.md | 5 + .../fix-cron-next-missing-day-overflow.md | 0 .changeset/pre/fix-cron-parser-semantics.md | 5 + .../pre/fix-cron-prev-month-rollover.md | 5 + .changeset/pre/fix-cron-prev-weekday-wrap.md | 5 + .changeset/pre/fix-cron-timezone-hash.md | 5 + .changeset/{ => pre}/fix-datetime-gmt.md | 0 .../fix-devtools-flush-on-teardown.md | 0 .changeset/pre/fix-devtools-span-snapshot.md | 5 + .../fix-durable-clock-fractional-wakeup.md | 5 + .changeset/pre/fix-durable-deferred-race.md | 5 + .changeset/pre/fix-durable-race-replay.md | 5 + .../pre/fix-duration-decimal-precision.md | 5 + .../fix-duration-symmetric-rounding.md | 0 .changeset/pre/fix-effect-fn-self.md | 5 + .changeset/pre/fix-effect-schedule-errors.md | 5 + .../fix-entity-manager-defect-replay.md | 0 .../fix-entity-proxy-rpc-handler-context.md | 0 .../fix-entity-proxy-server-path-params.md | 0 .../pre/fix-eventlog-duplicate-chunks.md | 5 + .changeset/pre/fix-fiber-exit-observers.md | 5 + .changeset/pre/fix-fiberhandle-clear-race.md | 5 + .changeset/pre/fix-fiberset-json-id.md | 5 + .../pre/fix-fiberset-runtime-interruption.md | 5 + .changeset/pre/fix-fish-command-path.md | 5 + .../pre/fix-formatter-output-contracts.md | 5 + .../pre/fix-from-json-string-identifier.md | 7 + .../pre/fix-from-option-inline-inference.md | 5 + .../fix-from-readable-stream-cancel-defect.md | 0 .../pre/fix-graph-allocator-equality.md | 5 + .../pre/fix-graph-bellman-ford-self-cycle.md | 5 + .changeset/pre/fix-graph-curried-getters.md | 5 + .../pre/fix-graph-dfs-astar-edge-cases.md | 5 + .changeset/pre/fix-graph-edge-transforms.md | 5 + .../pre/fix-graph-finite-edge-weights.md | 5 + .changeset/pre/fix-graph-mutable-hash.md | 5 + .changeset/pre/fix-graph-mutable-topo.md | 5 + .changeset/pre/fix-graph-mutation-errors.md | 5 + .changeset/pre/fix-graph-topo-types.md | 5 + .../fix-graph-undirected-traversal.md | 0 .../pre/fix-graph-walker-repeatability.md | 5 + .changeset/pre/fix-graphviz-dot-escaping.md | 5 + .changeset/pre/fix-group-by-key-types.md | 7 + .../fix-has-interrupts-only-empty.md | 0 .../{ => pre}/fix-hashmap-bit31-ordering.md | 0 .changeset/pre/fix-hashmap-modify-hash.md | 5 + .../fix-headers-proto-enumerability.md | 0 .../fix-http-client-request-content-length.md | 5 + ...fix-http-incoming-message-parse-options.md | 0 .../fix-http-pre-response-handler-types.md | 5 + .../pre/fix-http-server-request-raw-body.md | 5 + .../fix-http-server-response-body-headers.md | 5 + .../fix-http-tracer-response-cause.md | 0 .../pre/fix-httpapi-authorization-decoding.md | 11 + .../fix-httpapi-client-error-content-type.md | 11 + .../fix-httpapi-endpoint-error-inference.md | 0 .../pre/fix-httpapi-malformed-json-400.md | 5 + .changeset/pre/fix-httpapi-runtime-shape.md | 5 + .../{ => pre}/fix-httpapi-schema-types.md | 0 .../fix-httpapi-security-middleware-cache.md | 0 .../pre/fix-httpapi-single-array-query.md | 5 + .../pre/fix-invalid-openapi-examples.md | 5 + .../pre/fix-invalid-value-doubled-expected.md | 5 + .changeset/{ => pre}/fix-is-json-dag.md | 0 .../pre/fix-iterable-flatten-stack-safety.md | 5 + .../fix-json-schema-anyof-oneof-siblings.md | 0 .../{ => pre}/fix-json-schema-import-json.md | 0 .../fix-json-schema-reference-resolution.md | 5 + .../pre/fix-json-schema-unique-items-false.md | 5 + .../{ => pre}/fix-keepalive-blocked-timers.md | 0 .../fix-language-model-stream-concurrency.md | 5 + .changeset/pre/fix-latch-stale-flush.md | 11 + .changeset/pre/fix-layermap-preload.md | 5 + .changeset/pre/fix-mcp-call-tool-arguments.md | 5 + .changeset/pre/fix-mcp-completion-context.md | 5 + .../fix-mcp-param-name-resolution.md | 0 .changeset/pre/fix-mcp-request-errors.md | 5 + .../pre/fix-memory-journal-conflicts.md | 5 + .../pre/fix-memory-journal-next-sequence.md | 5 + .changeset/pre/fix-memory-journal-relay.md | 5 + .changeset/pre/fix-memory-runner-health.md | 5 + .../pre/fix-memory-workflow-interrupt.md | 5 + .../fix-mermaid-escape-special-chars.md | 0 ...fix-message-storage-clear-address-dedup.md | 5 + .../fix-metric-attribute-key-collisions.md | 5 + .changeset/pre/fix-metric-is-metric.md | 5 + .../pre/fix-metric-linear-boundaries.md | 5 + .changeset/pre/fix-metric-negative-max.md | 5 + ...fix-mssql-multitable-persistence-upsert.md | 5 + .../pre/fix-multipart-file-stream-limits.md | 5 + .changeset/pre/fix-mutable-list-bounds.md | 5 + .../pre/fix-mutable-list-empty-filter.md | 5 + .../fix-mutable-list-filter-length.md | 0 .changeset/pre/fix-ndjson-split-utf8.md | 5 + .../pre/fix-node-http-stream-failure.md | 5 + .../pre/fix-node-path-file-url-flavor.md | 5 + .changeset/pre/fix-node-pipeline-kill.md | 5 + ...ix-number-remainder-scientific-notation.md | 5 + .../fix-object-keyword-json-schema.md | 0 .changeset/pre/fix-one-shot-iterables.md | 5 + .changeset/pre/fix-openai-header-redaction.md | 6 + .../{ => pre}/fix-openai-mcp-tool-names.md | 0 .changeset/pre/fix-openai-response-failure.md | 5 + .../pre/fix-openai-specialized-tool-output.md | 5 + .../pre/fix-openai-system-input-text.md | 5 + ...fix-openai-telemetry-response-namespace.md | 6 + .../pre/fix-openai-web-search-action.md | 5 + .../pre/fix-openapi-from-api-cache-copy.md | 5 + .../fix-openapi-generator-form-urlencoded.md | 0 .../fix-openapi-generator-swagger2openapi.md | 0 ...reserve-multiple-response-content-types.md | 0 .../{ => pre}/fix-openrouter-sparse-array.md | 0 .changeset/pre/fix-otel-logger-clock-skew.md | 7 + .../fix-otel-logger-severity-number.md | 0 .changeset/pre/fix-otel-logger-shutdown.md | 5 + .changeset/pre/fix-otlp-exporter-shutdown.md | 5 + .../pre/fix-otlp-resource-attributes.md | 5 + .../pre/fix-pending-interruptible-mask.md | 5 + .../pre/fix-persisted-cluster-reply-hang.md | 5 + .../fix-persisted-queue-attempt-accounting.md | 5 + .changeset/pre/fix-prompt-all-iterables.md | 5 + .changeset/pre/fix-proto-record-assignment.md | 16 + .changeset/pre/fix-pubsub-replay-retention.md | 5 + .../fix-queue-collect-duplication.md | 0 .../{ => pre}/fix-random-string-seeds.md | 0 .changeset/pre/fix-rate-limiter-sleep.md | 5 + .../fix-ratelimiter-tokenbucket-redis-ttl.md | 0 .../pre/fix-react-native-sqlite-values.md | 5 + .changeset/pre/fix-reactive-query-metadata.md | 5 + .changeset/pre/fix-redis-persisted-queue.md | 5 + .../fix-remainder-scientific-notation.md | 0 ...x-request-resolver-pending-batches-leak.md | 0 .../fix-retry-transient-autocomplete.md | 0 .../fix-rpc-http-requestids-finalizer.md | 0 .changeset/{ => pre}/fix-rpc-json-id-edges.md | 0 .../fix-rpc-unknown-tag-isolation.md | 0 .../pre/fix-runner-stream-completion.md | 5 + .changeset/pre/fix-schedule-during.md | 5 + .../fix-schedule-fixed-double-exec.md | 0 .../fix-schedule-reduce-sync-state.md | 0 .../fix-schema-arbitrary-exclusive-bounds.md | 0 .../fix-schema-bracket-prototype-pollution.md | 5 + .../{ => pre}/fix-schema-defect-message.md | 0 .../fix-schema-encode-keys-property-keys.md | 0 .../{ => pre}/fix-schema-encodekeys-class.md | 0 .../{ => pre}/fix-schema-encodekeys-struct.md | 0 .../{ => pre}/fix-schema-encoding-checks.md | 0 .../fix-schema-identifier-expected-message.md | 0 .changeset/pre/fix-schema-is-json-records.md | 5 + .changeset/{ => pre}/fix-schema-is-uuid.md | 0 .changeset/pre/fix-schema-json-tuple-allof.md | 5 + .../pre/fix-schema-make-nested-class-union.md | 45 + .../fix-schema-option-non-schema-failures.md | 0 .../{ => pre}/fix-schema-parser-checks.md | 0 .changeset/pre/fix-schema-pattern-state.md | 5 + .../fix-schema-representation-identifiers.md | 5 + .../pre/fix-schema-sentinel-declaration.md | 5 + .../fix-schema-tuple-post-rest-indexing.md | 5 + .../pre/fix-schema-union-dispatch-order.md | 12 + .changeset/pre/fix-schema-union-pruning.md | 5 + .../pre/fix-scoped-ref-failed-replacement.md | 5 + .../fix-searchparam-initial-decode.md | 0 ...x-semaphore-with-permits-interrupt-leak.md | 5 + .../fix-serializable-wire-transfer.md | 0 .changeset/pre/fix-sliding-size-chunks.md | 5 + .../fix-sql-persisted-queue-lock-refresh.md | 5 + .changeset/pre/fix-sqlite-persisted-queue.md | 5 + .changeset/pre/fix-sse-last-event-id.md | 5 + .changeset/pre/fix-sse-leading-bom.md | 5 + .changeset/pre/fix-sse-retry-directives.md | 5 + .../pre/fix-stream-aggregate-within-idle.md | 5 + .../pre/fix-stream-execution-plan-retries.md | 5 + .../fix-stream-grouped-within-flush.md | 0 .changeset/pre/fix-stream-haltwhen.md | 5 + .../pre/fix-stream-map-accum-array-effect.md | 5 + .changeset/pre/fix-stream-range-zero-chunk.md | 5 + .../fix-stream-run-for-each-while.md | 0 .../{ => pre}/fix-stream-scan-effect.md | 0 .../{ => pre}/fix-stream-scoped-scope.md | 0 .changeset/pre/fix-string-case-digits.md | 5 + ...ix-strip-approval-artifacts-multi-round.md | 0 .../fix-struct-utility-types-simplify.md | 0 .../pre/fix-structural-proto-equality.md | 5 + .../fix-structwithrest-index-signatures.md | 0 ...ix-subscription-ref-get-and-update-some.md | 5 + .../fix-subscriptionref-getandupdateeffect.md | 5 + .../fix-tagged-union-class-sentinels.md | 0 .../{ => pre}/fix-tagged-union-match-unify.md | 0 ...fix-to-tagged-union-isanyof-custom-tags.md | 0 .../pre/fix-tool-provider-defined-clone.md | 7 + .changeset/pre/fix-trie-key-replacement.md | 5 + .changeset/pre/fix-trie-longest-prefix.md | 5 + .changeset/pre/fix-trie-undefined-values.md | 5 + ...x-tuple-with-rest-post-rest-index-drift.md | 0 ...ix-tuple-with-rest-post-rest-validation.md | 0 .../pre/fix-txpubsub-publish-all-iterables.md | 5 + .changeset/pre/fix-txqueue-closing-drain.md | 5 + .../pre/fix-txqueue-offer-all-iterables.md | 5 + .changeset/{ => pre}/fix-types-voidifempty.md | 0 .../pre/fix-variant-schema-default-cache.md | 5 + .../pre/fix-vitest-proxy-chained-helpers.md | 5 + .../pre/fix-vitest-record-schema-arbitrary.md | 5 + .changeset/pre/fix-vitest-runner-import.md | 5 + .../pre/fix-vitest-throws-assertions.md | 5 + .changeset/pre/fix-void-mcp-tool-results.md | 5 + .../{ => pre}/fix-void-response-encoding.md | 0 .changeset/pre/fix-worker-runner-cleanup.md | 7 + ...fix-workflow-defect-reply-serialization.md | 0 .../fix-workflow-entity-client-collision.md | 5 + .../fix-workflow-proxy-rpc-handler-context.md | 0 .changeset/pre/fix-workflow-trace-context.md | 5 + .changeset/pre/fix-xhr-form-data.md | 5 + .changeset/pre/fix-zero-max-bytes.md | 5 + .../pre/fix-zsh-mixed-command-completions.md | 5 + .changeset/{ => pre}/flat-chicken-remain.md | 0 .changeset/pre/flat-graphs-agree.md | 5 + .changeset/{ => pre}/floppy-cows-spend.md | 0 .changeset/pre/floppy-frogs-juggle.md | 5 + .changeset/{ => pre}/floppy-items-admire.md | 0 .changeset/{ => pre}/floppy-pigs-kiss.md | 0 .changeset/pre/floppy-rats-leave.md | 5 + .../pre/floyd-warshall-null-edge-data.md | 5 + .changeset/{ => pre}/fluffy-meals-matter.md | 0 .changeset/{ => pre}/fluffy-pumas-push.md | 0 .changeset/{ => pre}/forked-memo-maps.md | 0 .changeset/{ => pre}/forty-hounds-cheer.md | 0 .changeset/{ => pre}/forty-otters-cry.md | 0 .changeset/{ => pre}/forty-rings-film.md | 0 .changeset/{ => pre}/forty-signs-stay.md | 0 .changeset/{ => pre}/forty-swans-divide.md | 0 .changeset/{ => pre}/forty-trees-pay.md | 0 .changeset/{ => pre}/four-papayas-bow.md | 0 .changeset/{ => pre}/four-points-repeat.md | 0 .changeset/pre/frank-apes-vanish.md | 5 + .changeset/{ => pre}/fresh-cats-smash.md | 0 .changeset/pre/fresh-cycles-wait.md | 5 + .changeset/pre/fresh-deps-update.md | 5 + .changeset/{ => pre}/fresh-emus-cheat.md | 0 .changeset/pre/fresh-files-seek.md | 7 + .changeset/pre/fresh-forms-travel.md | 5 + .changeset/pre/fresh-graphs-cycle.md | 5 + .changeset/pre/fresh-images-generate.md | 5 + .changeset/pre/fresh-lines-wait.md | 5 + .changeset/{ => pre}/fresh-monkeys-smoke.md | 0 .changeset/pre/fresh-rivers-report.md | 5 + .../frozen-intrinsics-stack-trace-limit.md | 0 .changeset/{ => pre}/fruity-houses-learn.md | 0 .changeset/pre/fruity-sloths-walk.md | 5 + .changeset/{ => pre}/full-adults-double.md | 0 .changeset/{ => pre}/funny-crabs-hang.md | 0 .changeset/pre/funny-ears-beam.md | 5 + .changeset/{ => pre}/funny-forks-move.md | 0 .changeset/pre/fuzzy-batches-stop.md | 5 + .changeset/pre/fuzzy-caches-expire.md | 5 + .changeset/{ => pre}/fuzzy-camels-hunt.md | 0 .changeset/pre/fuzzy-cats-kill.md | 5 + .changeset/pre/fuzzy-cats-listen.md | 5 + .changeset/{ => pre}/fuzzy-crews-fold.md | 0 .changeset/pre/fuzzy-databases-abort.md | 5 + .changeset/{ => pre}/fuzzy-dodos-help.md | 0 .changeset/pre/fuzzy-files-slice.md | 5 + .changeset/pre/fuzzy-graphs-transform.md | 5 + .changeset/pre/fuzzy-hornets-wish.md | 5 + .changeset/{ => pre}/fuzzy-lions-perform.md | 0 .changeset/pre/fuzzy-lions-study.md | 5 + .changeset/pre/fuzzy-pandas-smile.md | 5 + .changeset/{ => pre}/fuzzy-planets-sneeze.md | 0 .changeset/pre/fuzzy-rabbits-cancel.md | 5 + .changeset/pre/fuzzy-ravens-reason.md | 5 + .changeset/pre/fuzzy-routers-smile.md | 5 + .../fuzzy-stamps-care.md} | 0 .changeset/pre/fuzzy-timers-smile.md | 5 + .../pre/general-json-schema-converters.md | 5 + .changeset/pre/giant-jeans-float.md | 5 + .changeset/{ => pre}/gold-meteors-move.md | 0 .changeset/{ => pre}/gold-readers-hug.md | 0 .changeset/{ => pre}/gold-rings-start.md | 0 .changeset/pre/good-cups-reply.md | 5 + .changeset/{ => pre}/good-tools-work.md | 0 .changeset/{ => pre}/good-trees-pull.md | 0 ...graph-acyclic-parallel-undirected-edges.md | 5 + .changeset/{ => pre}/graph-algorithm-fixes.md | 0 .../pre/graph-finalized-mutation-handle.md | 5 + .changeset/pre/graph-guard-predicates.md | 5 + .../pre/graph-sync-mutation-callbacks.md | 5 + .../pre/graph-undirected-edge-equality.md | 5 + .../pre/graph-walker-iterator-receiver.md | 5 + .changeset/{ => pre}/great-trains-mate.md | 0 .changeset/{ => pre}/great-trams-report.md | 0 .changeset/pre/green-ads-camp.md | 5 + .changeset/{ => pre}/green-beds-unref.md | 0 .changeset/pre/green-birds-close.md | 5 + .changeset/{ => pre}/green-chips-wash.md | 0 .changeset/{ => pre}/green-moons-smile.md | 0 .changeset/{ => pre}/green-pugs-play.md | 0 .changeset/{ => pre}/green-rings-prove.md | 0 .changeset/{ => pre}/happy-mirrors-dream.md | 0 .../pre/harden-httpapi-documentation-html.md | 9 + .../pre/hash-sql-message-dedupe-keys.md | 11 + .changeset/{ => pre}/heavy-loops-cut.md | 0 .changeset/{ => pre}/heavy-trams-fix.md | 0 .changeset/pre/hip-friends-kiss.md | 11 + .changeset/{ => pre}/hip-socks-travel.md | 0 .changeset/{ => pre}/honest-pens-thank.md | 0 .changeset/{ => pre}/honest-rivers-notice.md | 0 .changeset/{ => pre}/hot-taxis-fry.md | 0 .changeset/pre/hot-teeth-clean.md | 5 + .changeset/pre/http-json-reviver.md | 5 + .../pre/http-server-websocket-options.md | 22 + .changeset/pre/http-status-from-literal.md | 5 + .../httpapi-endpoint-relax-constraints.md | 0 .../{ => pre}/httpapi-schema-service-types.md | 0 .changeset/{ => pre}/huge-moons-rhyme.md | 0 .changeset/{ => pre}/humble-pigs-dig.md | 0 .changeset/pre/hungry-kings-look.md | 6 + .../icy-flies-cross.md} | 0 .changeset/pre/internal-json-string-schema.md | 5 + .changeset/pre/isolate-sql-compiler-cache.md | 5 + .changeset/{ => pre}/itchy-radios-poke.md | 0 .changeset/{ => pre}/itchy-results-bet.md | 0 .changeset/{ => pre}/itchy-shrimps-deny.md | 0 .changeset/{ => pre}/itchy-toes-promise.md | 0 .../{ => pre}/k8s-last-transition-null.md | 0 .../pre/keep-httpapi-composition-immutable.md | 7 + .changeset/{ => pre}/khaki-cats-learn.md | 0 .changeset/{ => pre}/khaki-melons-appear.md | 0 .changeset/pre/kind-flags-help.md | 5 + .changeset/{ => pre}/kind-hounds-float.md | 0 .changeset/{ => pre}/kind-windows-fall.md | 0 .changeset/{ => pre}/late-hotels-rule.md | 0 .changeset/{ => pre}/late-lamps-care.md | 0 .changeset/{ => pre}/late-rivers-applaud.md | 0 .changeset/pre/late-sockets-rest.md | 5 + .../{ => pre}/layer-map-dynamic-idle-ttl.md | 0 .changeset/pre/layered-context-storage.md | 9 + .changeset/pre/lazy-openapi-initialization.md | 5 + .changeset/{ => pre}/lazy-queens-rush.md | 0 .../{ => pre}/lazy-recursive-forward-refs.md | 0 .changeset/{ => pre}/lazy-timers-exist.md | 0 .changeset/{ => pre}/legal-pants-drop.md | 0 .changeset/{ => pre}/lemon-taxis-sin.md | 0 .changeset/{ => pre}/light-kids-sneeze.md | 0 .changeset/{ => pre}/little-dryers-allow.md | 0 .changeset/{ => pre}/long-cameras-think.md | 0 .changeset/pre/loose-wings-lie.md | 5 + .changeset/{ => pre}/lovely-cobras-change.md | 0 .changeset/{ => pre}/lovely-frogs-rescue.md | 0 .changeset/{ => pre}/lucky-buttons-jump.md | 0 .changeset/pre/lucky-dingos-smile.md | 5 + .changeset/{ => pre}/lucky-phones-listen.md | 0 .changeset/{ => pre}/lucky-worms-type.md | 0 .changeset/pre/major-chairs-design.md | 5 + .../pre/managed-runtime-async-dispose.md | 14 + .changeset/{ => pre}/many-badgers-obey.md | 0 .../pre/match-generic-value-terminals.md | 10 + .../pre/mcp-http-initialize-version-header.md | 16 + .changeset/pre/mcp-protocol-versions.md | 5 + .changeset/pre/mcp-tool-output-schema.md | 5 + .changeset/{ => pre}/mean-dingos-share.md | 0 .changeset/{ => pre}/mean-trains-smash.md | 0 .changeset/pre/memoize-idempotent-asts.md | 5 + .changeset/pre/merge-resource-failures.md | 5 + .changeset/pre/metal-nails-sneeze.md | 5 + .changeset/{ => pre}/metal-parts-yell.md | 0 .changeset/{ => pre}/mighty-games-matter.md | 0 .changeset/pre/migrate-persisted-queues.md | 5 + .changeset/pre/migrator-windows-file-url.md | 9 + .changeset/{ => pre}/modern-carrots-see.md | 0 .../{ => pre}/modern-uuid-guid-filter.md | 0 .changeset/pre/multipart-collect-linear.md | 5 + .../pre/multipart-onDone-clobbers-error.md | 5 + .changeset/pre/multipart-parser-limits.md | 5 + .../pre/mysql2-disable-prepared-statements.md | 5 + .changeset/pre/narrow-schedule-while.md | 5 + .changeset/{ => pre}/nasty-geese-grow.md | 0 .changeset/{ => pre}/neat-goats-wave.md | 0 .changeset/pre/neat-graphs-induced.md | 5 + .changeset/{ => pre}/neat-kings-chew.md | 2 +- .changeset/{ => pre}/neat-lions-rest.md | 0 .changeset/pre/neat-pandas-query.md | 5 + .changeset/{ => pre}/neat-snails-wash.md | 0 .changeset/{ => pre}/neat-taxis-notice.md | 0 .changeset/pre/neat-tuples-remember.md | 5 + .changeset/{ => pre}/neat-windows-buy.md | 0 .changeset/pre/nested-union-sentinels.md | 5 + .changeset/{ => pre}/new-dogs-swim.md | 0 .changeset/{ => pre}/new-toes-stop.md | 0 .changeset/{ => pre}/ninety-geese-exist.md | 0 .changeset/pre/node-redis-client.md | 9 + .changeset/pre/node-terminal-idle-ttl.md | 5 + .changeset/pre/node-terminal-stdin-eof.md | 5 + .../normalize-httpapi-payload-media-types.md | 11 + .../pre/normalize-unbounded-pubsub-replay.md | 5 + .../pre/o8drprcu-sqlite-node-node-sqlite.md | 5 + .changeset/{ => pre}/odd-boats-think.md | 0 .changeset/{ => pre}/odd-bulldogs-sleep.md | 0 .changeset/{ => pre}/odd-fans-glow.md | 0 .changeset/{ => pre}/odd-forks-talk.md | 0 .changeset/{ => pre}/odd-laws-draw.md | 0 .changeset/{ => pre}/odd-owls-smoke.md | 0 .changeset/{ => pre}/odd-socks-boil.md | 0 .changeset/{ => pre}/odd-suns-dance.md | 0 .changeset/{ => pre}/old-brooms-cry.md | 0 .changeset/{ => pre}/old-facts-stand.md | 0 .changeset/{ => pre}/old-mirrors-float.md | 0 .changeset/{ => pre}/olive-poems-visit.md | 0 .changeset/pre/opaque-graph-interface.md | 5 + .changeset/{ => pre}/open-hotels-remain.md | 0 .changeset/pre/open-objects-intersect.md | 7 + .../pre/openai-compat-decode-tool-params.md | 5 + .../openai-compat-empty-assistant-content.md | 5 + .../pre/openai-compat-null-tool-calls.md | 5 + .../openai-compat-nullable-tool-name.md | 0 .../pre/openai-compat-parallel-tool-calls.md | 5 + .../{ => pre}/openai-compat-reasoning.md | 0 .../pre/openai-compatible-quota-errors.md | 5 + .changeset/pre/openai-telemetry-response.md | 5 + .changeset/pre/openai-text-prompt-cache.md | 6 + .../pre/openai-tolerant-error-stream-event.md | 5 + ...penapi-generator-sse-constraint-decoder.md | 0 .changeset/pre/openapi-path-parameters.md | 6 + .changeset/pre/openapi-pointer-references.md | 5 + .changeset/pre/openapi-response-variants.md | 6 + .../pre/openrouter-generation-usage-schema.md | 11 + .../{ => pre}/openrouter-input-audio.md | 0 .../pre/openrouter-tool-parameter-deltas.md | 5 + .changeset/pre/opentelemetry-render-causes.md | 5 + .changeset/pre/optic-standalone-functions.md | 5 + .changeset/pre/optimize-array-equality.md | 5 + .../pre/optimize-httpapi-handler-types.md | 204 + .../pre/optimize-node-http-server-response.md | 5 + .../pre/optimize-schema-class-decoding.md | 5 + .changeset/pre/optional-ai-embedding-usage.md | 5 + .changeset/pre/optional-ai-response-fields.md | 5 + .../{ => pre}/otel-resource-env-precedence.md | 0 .../{ => pre}/perfect-buckets-tickle.md | 0 .changeset/{ => pre}/petite-months-allow.md | 0 .../pre/pg-client-connect-error-handler.md | 5 + .changeset/pre/plain-variant-unions.md | 5 + .../{ => pre}/platform-crypto-service.md | 0 .../{ => pre}/platform-node-shared-barrel.md | 0 .changeset/{ => pre}/plenty-moons-pull.md | 0 .changeset/{ => pre}/polite-brooms-tickle.md | 0 .changeset/pre/polite-cameras-rest.md | 6 + .changeset/pre/polite-dingos-unite.md | 5 + .changeset/{ => pre}/polite-pigs-speak.md | 0 .changeset/{ => pre}/polite-tables-kneel.md | 0 .changeset/pre/port-effect-reduce.md | 5 + .changeset/{ => pre}/port-react-hydration.md | 0 .changeset/pre/precise-clocks-rest.md | 5 + .changeset/pre/precise-test-clock-nanos.md | 5 + .../pre/preserve-command-hidden-metadata.md | 5 + .../pre/preserve-config-all-input-evidence.md | 5 + .changeset/pre/preserve-openapi-raw-json.md | 5 + .../pre/preserve-otel-parent-context.md | 5 + .../preserve-unsafe-workflow-interrupts.md | 5 + .changeset/pre/pretty-moments-clap.md | 5 + .../pre/protect-schema-issue-actuals.md | 36 + .changeset/pre/proud-ears-say.md | 5 + .../pre/provider-executed-tool-results.md | 5 + .changeset/{ => pre}/public-deer-ring.md | 0 .changeset/{ => pre}/public-jeans-stop.md | 0 .changeset/{ => pre}/pubsub-publish-false.md | 0 .changeset/{ => pre}/puny-pens-clap.md | 0 .changeset/{ => pre}/purple-bars-prove.md | 0 .changeset/{ => pre}/purple-schools-float.md | 0 .changeset/{ => pre}/purple-turtles-draw.md | 0 .changeset/{ => pre}/quick-dragons-fix.md | 0 .changeset/{ => pre}/quick-falcons-travel.md | 0 .changeset/{ => pre}/quick-geese-relax.md | 0 .changeset/pre/quick-graphs-paths.md | 5 + .changeset/pre/quick-kiwis-remember.md | 5 + .changeset/{ => pre}/quick-lamps-dig.md | 0 .changeset/{ => pre}/quick-lizards-fall.md | 0 .changeset/pre/quick-schedulers-promise.md | 5 + .changeset/{ => pre}/quick-trees-join.md | 0 .changeset/{ => pre}/quiet-carpets-grin.md | 0 .changeset/pre/quiet-clis-parse.md | 5 + .changeset/pre/quiet-crons-report.md | 5 + .changeset/pre/quiet-fibers-settle.md | 5 + .changeset/{ => pre}/quiet-files-hunt.md | 0 .changeset/pre/quiet-files-write.md | 5 + .changeset/pre/quiet-graphs-guard.md | 5 + .changeset/pre/quiet-graphs-search.md | 5 + .changeset/{ => pre}/quiet-lamps-jam.md | 0 .changeset/pre/quiet-mice-negotiate.md | 5 + .changeset/pre/quiet-observers-report.md | 5 + .changeset/pre/quiet-otters-retry.md | 5 + .changeset/pre/quiet-owls-validate.md | 5 + .changeset/pre/quiet-pandas-rebuild.md | 5 + .changeset/pre/quiet-pandas-respond.md | 5 + .changeset/{ => pre}/quiet-radios-wave.md | 0 .changeset/{ => pre}/quiet-redis-scripts.md | 0 .changeset/pre/quiet-rpcs-buffer.md | 5 + .changeset/pre/quiet-savepoints-wait.md | 6 + .changeset/pre/quiet-sockets-close.md | 5 + .changeset/pre/quiet-spans-rest.md | 5 + .changeset/{ => pre}/quiet-tigers-yell.md | 0 .changeset/pre/quiet-tools-smile.md | 5 + .changeset/pre/quiet-turtles-smile.md | 5 + .changeset/{ => pre}/random-choice.md | 0 .changeset/pre/random-hex-encoding.md | 5 + .changeset/pre/read-only-bun-sqlite.md | 5 + .changeset/{ => pre}/ready-olives-divide.md | 0 .changeset/{ => pre}/real-trains-ring.md | 0 .../pre/record-from-iterable-by-dual.md | 16 + .changeset/{ => pre}/red-pigs-repair.md | 0 .changeset/pre/redact-cli-wizard-values.md | 5 + .../redacted-representation-options.md | 0 .changeset/pre/redis-subscribe-api.md | 8 + .../{ => pre}/refactor-cli-global-flags.md | 0 .../{ => pre}/refactor-config-provider.md | 0 .../refactor-representation-references.md | 0 .changeset/pre/refine-config-absence.md | 5 + .changeset/pre/refresh-mcp-roots.md | 5 + .../pre/register-param-alternate-flags.md | 5 + .../pre/release-rpc-worker-pool-entries.md | 5 + .changeset/pre/remove-context-mutate.md | 5 + .../remove-effect-transactionwith.md | 0 .../{ => pre}/remove-http-span-counter.md | 0 .changeset/pre/remove-kubernetes-types.md | 5 + .changeset/{ => pre}/remove-nullor.md | 0 .../remove-openapi-fromapi-options.md | 0 .changeset/pre/remove-schedule-apis.md | 5 + .changeset/pre/remove-schedule-either.md | 5 + .changeset/pre/remove-schedule-elapsed.md | 5 + .changeset/pre/remove-schedule-taps.md | 5 + .../pre/remove-schema-key-value-combiner.md | 9 + ...ove-schema-stringtree-keep-declarations.md | 0 .changeset/pre/remove-schema-utils.md | 5 + .../{ => pre}/remove-types-mergerecord.md | 0 .../{ => pre}/remove-unused-utils-apis.md | 0 .changeset/pre/remove-with-concurrency.md | 5 + .changeset/{ => pre}/rename-rebuild-out.md | 0 .../pre/rename-schema-error-constructors.md | 10 + .changeset/pre/render-cli-user-errors.md | 5 + .changeset/pre/report-schema-input.md | 9 + .../pre/report-transient-rpc-socket-errors.md | 5 + .changeset/pre/required-boolean-flags.md | 5 + .changeset/pre/resource-subscriptions.md | 5 + .changeset/pre/restore-effect-head.md | 5 + .../{ => pre}/restore-schema-parse-options.md | 0 .../pre/result-map-error-success-identity.md | 6 + .changeset/pre/retry-redis-script-load.md | 5 + .../pre/reuse-httpapi-response-schemas.md | 7 + .changeset/{ => pre}/rich-dots-push.md | 0 .changeset/{ => pre}/rich-hoops-nail.md | 0 .changeset/{ => pre}/rich-sloths-draw.md | 0 .changeset/{ => pre}/ripe-lies-battle.md | 0 .../{ => pre}/rpc-client-http-early-close.md | 0 .../{ => pre}/rpc-middleware-provides-fix.md | 0 .changeset/pre/rpc-server-notifications.md | 6 + .changeset/pre/safe-json-schema-patterns.md | 5 + .changeset/{ => pre}/scalar-custom-fetch.md | 0 .changeset/pre/schema-arbitrary-factory.md | 5 + .changeset/{ => pre}/schema-as-class.md | 0 .../{ => pre}/schema-asserts-signature.md | 0 .../pre/schema-aware-rpc-serialization.md | 9 + .changeset/pre/schema-binary-codec.md | 5 + .../schema-clean-up-additionalProperties.md | 0 .changeset/pre/schema-codec-narrowing.md | 5 + .changeset/pre/schema-date-valid.md | 9 + .../schema-datetime-utc-from-string.md | 0 .../schema-decoding-defaults-services.md | 0 .../schema-defaults-issue-channel.md | 0 .../pre/schema-direct-class-extension.md | 22 + .changeset/{ => pre}/schema-dollar-prefix.md | 0 .changeset/{ => pre}/schema-lazy-bottom.md | 0 .../{ => pre}/schema-missing-self-generic.md | 0 .../schema-ordered-arbitrary-constraints.md | 0 .../{ => pre}/schema-parser-adapter-errors.md | 0 .../{ => pre}/schema-refactor-toCodecJson.md | 0 .../{ => pre}/schema-remove-annotate-in.md | 0 .../schema-rename-makeUnsafe-to-make.md | 0 .../schema-rename-parser-makeUnsafe.md | 0 .../pre/schema-representation-refactoring.md | 71 + .../{ => pre}/schema-result-combinators.md | 0 .changeset/pre/schema-runtime-performance.md | 47 + .../{ => pre}/schema-struct-simplify.md | 0 .../pre/schema-union-type-derivation.md | 5 + .changeset/pre/scope-persisted-queue-ids.md | 5 + .changeset/pre/secure-eventlog-identities.md | 5 + .changeset/{ => pre}/semantic-matching.md | 0 .changeset/{ => pre}/seven-mugs-marry.md | 0 .changeset/pre/seven-poems-divide.md | 5 + .changeset/{ => pre}/shaggy-birds-stay.md | 0 .changeset/{ => pre}/shaggy-cities-push.md | 0 .changeset/{ => pre}/shaggy-numbers-accept.md | 0 .changeset/pre/shaky-beans-throw.md | 5 + .changeset/{ => pre}/sharp-emus-applaud.md | 0 .changeset/{ => pre}/sharp-goats-wink.md | 0 .changeset/{ => pre}/sharp-pandas-care.md | 0 .changeset/{ => pre}/sharp-peas-march.md | 0 .changeset/{ => pre}/sharp-rules-draw.md | 0 .changeset/{ => pre}/sharp-singers-sort.md | 0 .changeset/{ => pre}/shiny-trains-hug.md | 0 .changeset/{ => pre}/short-cows-relate.md | 0 .changeset/{ => pre}/short-foxes-admire.md | 0 .changeset/{ => pre}/short-stamps-throw.md | 0 .changeset/{ => pre}/shy-cycles-flow.md | 0 .changeset/{ => pre}/shy-geckos-sniff.md | 0 .changeset/{ => pre}/silent-geckos-matter.md | 0 .changeset/{ => pre}/silent-needles-design.md | 0 .changeset/{ => pre}/silent-plants-matter.md | 0 .changeset/{ => pre}/silent-spoons-stare.md | 0 .changeset/pre/silly-dodos-update.md | 5 + .changeset/{ => pre}/silly-loops-tickle.md | 0 .changeset/{ => pre}/silver-bulk-indexeddb.md | 0 .changeset/{ => pre}/silver-emus-smoke.md | 0 .changeset/{ => pre}/silver-kings-poke.md | 0 .changeset/{ => pre}/silver-snails-sqlite.md | 0 .changeset/{ => pre}/silver-wings-watch.md | 0 .changeset/pre/simplify-optic-composition.md | 9 + .changeset/{ => pre}/six-cups-taste.md | 0 .changeset/pre/six-pumas-take.md | 9 + .changeset/{ => pre}/sixty-mails-shout.md | 0 .changeset/{ => pre}/sixty-socks-yell.md | 0 .changeset/{ => pre}/slick-signs-wish.md | 0 .changeset/{ => pre}/slick-toes-rush.md | 0 .changeset/pre/slimy-melons-admire.md | 33 + .changeset/{ => pre}/slimy-planets-divide.md | 0 .changeset/{ => pre}/slimy-turtles-juggle.md | 0 .changeset/{ => pre}/slow-beans-battle.md | 0 .changeset/{ => pre}/slow-berries-enjoy.md | 0 .changeset/pre/slow-entities-register.md | 6 + .changeset/pre/slow-spiders-refresh.md | 5 + .changeset/{ => pre}/small-bugs-hunt.md | 0 .changeset/{ => pre}/small-crabs-care.md | 0 .changeset/pre/small-pandas-cache.md | 5 + .changeset/{ => pre}/small-pets-sit.md | 0 .changeset/{ => pre}/smart-ducks-jump.md | 0 .changeset/{ => pre}/smart-pillows-buy.md | 0 .changeset/{ => pre}/smart-timers-fly.md | 0 .changeset/{ => pre}/smart-tips-sort.md | 0 .changeset/pre/social-hoops-knock.md | 5 + .changeset/{ => pre}/social-pumas-prove.md | 0 .changeset/{ => pre}/soft-comics-wink.md | 0 .changeset/{ => pre}/soft-delete-sqlmodel.md | 0 .changeset/{ => pre}/soft-seals-allow.md | 0 .changeset/pre/soft-sockets-write.md | 5 + .changeset/{ => pre}/solid-cougars-attack.md | 0 .changeset/{ => pre}/solid-doors-ring.md | 0 .changeset/{ => pre}/solid-items-tease.md | 0 .changeset/{ => pre}/solid-towns-smoke.md | 0 .changeset/pre/sour-bees-sleep.md | 5 + .changeset/{ => pre}/sour-canyons-rescue.md | 0 .changeset/{ => pre}/sparkly-bears-act.md | 0 .changeset/{ => pre}/sparkly-coins-sit.md | 0 .changeset/pre/spicy-doors-unlist.md | 5 + .changeset/pre/split-clock-semantics.md | 7 + .changeset/{ => pre}/spotty-comics-fry.md | 0 .changeset/pre/sql-begin-failure-typed.md | 16 + .changeset/{ => pre}/sql-migrator-mjs-mts.md | 0 .../sqlite-bun-prepare-error-channel.md | 0 .../pre/sqlite-client-locking-defaults.md | 6 + .../sqlite-do-durable-object-transactions.md | 0 .../pre/stable-graph-traversal-config.md | 5 + .changeset/{ => pre}/stale-dots-tell.md | 0 .changeset/pre/stale-graph-traversal-skips.md | 5 + .changeset/pre/stale-laws-do.md | 5 + .changeset/pre/stale-snakes-know.md | 5 + .changeset/pre/standard-schema-module.md | 5 + .changeset/{ => pre}/strict-areas-end.md | 0 .changeset/{ => pre}/strict-buckets-hug.md | 0 .../{ => pre}/strip-resolved-approvals.md | 0 .../{ => pre}/strong-balloons-tickle.md | 0 .changeset/{ => pre}/strong-bees-queue.md | 0 .changeset/pre/strong-insects-film.md | 5 + .changeset/{ => pre}/struct-record.md | 0 .changeset/{ => pre}/sunny-ads-hang.md | 0 .changeset/{ => pre}/sunny-bikes-sleep.md | 0 .changeset/{ => pre}/sunny-rooms-invent.md | 0 .changeset/{ => pre}/sweet-donuts-bet.md | 0 .changeset/{ => pre}/sweet-hotels-give.md | 0 .changeset/pre/sweet-lizards-sing.md | 5 + .changeset/pre/sweet-schedules-matter.md | 5 + .changeset/{ => pre}/sweet-views-learn.md | 0 .changeset/pre/swift-geese-count.md | 5 + .changeset/pre/swift-pandas-parse.md | 7 + .changeset/{ => pre}/swift-spiders-unpack.md | 0 .changeset/{ => pre}/swift-symbols-stand.md | 0 ...tagged-error-class-optional-empty-props.md | 0 .changeset/{ => pre}/tall-hairs-return.md | 0 .changeset/pre/tall-ideas-fix.md | 5 + .changeset/{ => pre}/tall-mails-listen.md | 0 .changeset/pre/tall-melons-shave.md | 9 + .changeset/{ => pre}/tall-queens-cheer.md | 0 .changeset/{ => pre}/tall-wombats-wave.md | 0 .../pre/tame-json-schema-intersections.md | 14 + .changeset/{ => pre}/tangy-colts-lose.md | 0 .changeset/pre/tangy-plants-run.md | 5 + .changeset/{ => pre}/tasty-comics-send.md | 0 .changeset/pre/tasty-moments-post.md | 5 + .changeset/{ => pre}/ten-kings-fry.md | 0 .changeset/pre/tender-deserts-pull.md | 5 + .changeset/pre/tender-files-complete.md | 5 + .changeset/pre/tender-points-sleep.md | 5 + .../pre/terminate-openai-failed-streams.md | 5 + .changeset/pre/theme-cli-prompts.md | 5 + .changeset/{ => pre}/thick-pandas-wait.md | 0 .changeset/{ => pre}/thin-ducks-wonder.md | 0 .changeset/{ => pre}/thirty-ducks-go.md | 0 .changeset/pre/thirty-forks-march.md | 5 + .changeset/{ => pre}/thirty-pans-love.md | 0 .changeset/{ => pre}/three-corners-sort.md | 0 .changeset/{ => pre}/three-ravens-jam.md | 0 .changeset/{ => pre}/three-tomatoes-wave.md | 0 .changeset/pre/tidy-apples-rest.md | 5 + .changeset/pre/tidy-carpets-smile.md | 5 + .changeset/pre/tidy-cats-smile.md | 5 + .changeset/pre/tidy-cats-stream.md | 5 + .changeset/pre/tidy-codecs-inherit.md | 5 + .changeset/pre/tidy-dates-smile.md | 5 + .changeset/pre/tidy-floats-edit.md | 5 + .changeset/{ => pre}/tidy-foxes-own.md | 0 .changeset/pre/tidy-geese-release.md | 5 + .changeset/pre/tidy-graphs-cycle.md | 5 + .changeset/{ => pre}/tidy-icons-glow.md | 0 .changeset/pre/tidy-int32-annotations.md | 5 + .changeset/pre/tidy-json-objects.md | 6 + .../pre/tidy-json-schema-object-scopes.md | 5 + .changeset/pre/tidy-lions-smile.md | 5 + .changeset/pre/tidy-matches-select.md | 5 + .changeset/pre/tidy-mice-grin.md | 6 + .changeset/pre/tidy-pandas-smile.md | 5 + .changeset/pre/tidy-plums-remember.md | 5 + .changeset/pre/tidy-ravens-import.md | 6 + .changeset/pre/tidy-schema-errors.md | 5 + .changeset/pre/tidy-scoped-refs-close.md | 5 + .changeset/pre/tidy-spans-rest.md | 5 + .changeset/pre/tidy-sse-events.md | 5 + .changeset/{ => pre}/tidy-stacks-encode.md | 0 .changeset/{ => pre}/tidy-stars-drive.md | 0 .changeset/pre/tidy-tools-handle.md | 6 + .changeset/pre/tidy-tools-juggle.md | 5 + .changeset/pre/tidy-tuples-rest.md | 5 + .changeset/pre/tidy-wasps-wait.md | 5 + .changeset/{ => pre}/tiny-buckets-wave.md | 0 .changeset/pre/tiny-dodos-juggle.md | 5 + .changeset/pre/tiny-files-flow.md | 5 + .changeset/{ => pre}/tiny-lilies-flash.md | 0 .changeset/pre/tiny-lizards-correct.md | 5 + .changeset/{ => pre}/tiny-rabbits-smile.md | 0 .changeset/{ => pre}/to-codec-json-schema.md | 0 .../{ => pre}/tocodecjson-return-json-type.md | 0 .../{ => pre}/tool-get-json-schema-tests.md | 0 .changeset/pre/tough-graphs-cache.md | 5 + .changeset/pre/tough-rooms-camp.md | 5 + .changeset/pre/tough-taxis-own.md | 5 + .changeset/pre/tracer-disabled-timing.md | 5 + .changeset/pre/tracer-perf.md | 5 + .changeset/{ => pre}/true-actors-battle.md | 0 .../{ => pre}/try-promise-catch-defect.md | 0 .changeset/{ => pre}/twelve-dragons-move.md | 0 .changeset/{ => pre}/twenty-buttons-cheer.md | 0 .changeset/pre/twenty-facts-laugh.md | 5 + .changeset/pre/twenty-garlics-marry.md | 6 + .changeset/{ => pre}/two-roses-double.md | 0 .../unify-error-defect-stack-options.md | 0 .changeset/pre/unique-directed-neighbors.md | 5 + .../update-schema-arbitrary-report.md | 0 .../pre/upgrade-socket-error-listener.md | 5 + .changeset/{ => pre}/upset-colts-stick.md | 0 .changeset/{ => pre}/use-url-can-parse.md | 0 .changeset/pre/valid-owls-rest.md | 5 + .../validate-httpapi-handler-registration.md | 5 + .../pre/validate-openapi-global-conflicts.md | 5 + .changeset/{ => pre}/vast-bananas-send.md | 0 .changeset/{ => pre}/vast-deserts-travel.md | 0 .changeset/{ => pre}/violet-peaches-feel.md | 0 .changeset/pre/violet-tips-open.md | 5 + .../vitest-layer-top-level-options.md | 0 .changeset/{ => pre}/wacky-grapes-poke.md | 0 .changeset/{ => pre}/wacky-rice-add.md | 0 .changeset/pre/warm-clocks-format.md | 5 + .changeset/{ => pre}/warm-dolls-brake.md | 0 .changeset/{ => pre}/warm-friends-tie.md | 0 .changeset/pre/warm-graphs-degree.md | 5 + .changeset/pre/warm-rivers-cache.md | 5 + .changeset/{ => pre}/warm-snails-shop.md | 0 .changeset/{ => pre}/wet-news-invent.md | 0 .changeset/pre/whole-pets-build.md | 5 + .changeset/pre/wild-donuts-brake.md | 31 + .changeset/{ => pre}/wild-readers-clean.md | 0 .../{ => pre}/wild-suns-bearer-space.md | 0 .../pre/windows-hide-child-process-console.md | 7 + .changeset/{ => pre}/wise-ants-wave.md | 0 .changeset/pre/wise-bats-encrypt.md | 5 + .changeset/pre/wise-files-watch.md | 7 + .changeset/{ => pre}/wise-flags-shift.md | 0 .changeset/pre/wise-graphs-connect.md | 5 + .changeset/{ => pre}/wise-oranges-stay.md | 0 .changeset/pre/wise-pandas-lock.md | 7 + .changeset/{ => pre}/witty-lobsters-share.md | 0 .../pre/worker-runner-multi-disconnect.md | 5 + .../workflow-proxy-discard-execution-id.md | 5 + .changeset/{ => pre}/yellow-adults-study.md | 0 .changeset/{ => pre}/yellow-clocks-dance.md | 0 .changeset/{ => pre}/yellow-dingos-jump.md | 0 .changeset/{ => pre}/young-doors-change.md | 0 .changeset/pre/zero-node-socket-timeout.md | 5 + .changeset/preserve-default-variant-class.md | 5 + .../preserve-negative-counter-deltas.md | 6 + .changeset/principled-collection-counts.md | 5 + .changeset/prompt-buffer-render.md | 5 + .../prompt-lossless-text-serialization.md | 5 + .changeset/prompt-response-files.md | 5 + .../pubsub-sliding-single-subscriber.md | 5 + .changeset/pull-based-socket.md | 22 + .changeset/queue-manual-flush.md | 5 + .changeset/queue-reentrant-producers.md | 5 + .changeset/quiet-buffers-decode.md | 5 + .changeset/quiet-buns-route.md | 5 + .changeset/quiet-graphs-simplify.md | 5 + .changeset/quiet-pandas-listen.md | 5 + .changeset/quiet-streams-take.md | 5 + .changeset/random-exclusive-upper-endpoint.md | 5 + .changeset/rate-limiter-reset-lifetime.md | 5 + .changeset/rcmap-invalidation-release.md | 7 + .changeset/rcmap-throwing-lookup.md | 5 + .changeset/rcref-acquisition-shutdown.md | 5 + .changeset/rcref-release-ownership.md | 5 + .changeset/reason-annotate-context-only.md | 5 + .changeset/remove-msgpack.md | 8 + .../repeat-or-else-metadata-argument.md | 5 + .changeset/report-mcp-tool-failures.md | 5 + .changeset/request-cache-cancellation.md | 5 + .../request-persisted-resolver-failures.md | 5 + .changeset/request-race-cache.md | 5 + .changeset/request-resolver-failure-causes.md | 5 + .../request-resolver-iterable-results.md | 5 + .changeset/required-keys-index-signatures.md | 5 + .changeset/resolved-client-urls.md | 8 + .changeset/rpc-falsy-control-ids.md | 5 + .changeset/scheduler-global-scope-timers.md | 5 + .changeset/schema-api-cleanup.md | 11 + .changeset/schema-array-leaf-aggregation.md | 5 + .changeset/schema-binary-preserve-bom.md | 5 + .../scoped-cache-invalidate-all-reentrancy.md | 5 + .../scoped-cache-refresh-lookup-defects.md | 5 + .changeset/scoped-log-nan-restoration.md | 5 + .changeset/shaky-terms-push.md | 5 + ...sharding-registration-context-overrides.md | 5 + .changeset/shy-seals-smile.md | 5 + .changeset/skip-disabled-stack-capture.md | 5 + .changeset/small-basic-bundle.md | 5 + .changeset/socket-paused-websocket-handoff.md | 5 + .../socket-websocket-text-byte-watermark.md | 5 + .changeset/split-lines-completed-cr.md | 5 + .changeset/spotty-masks-own.md | 5 + .../sql-event-journal-callback-errors.md | 5 + .../sql-message-storage-joined-reply-id.md | 5 + .changeset/sql-nested-placeholder-cache.md | 5 + .changeset/sql-optional-span-propagation.md | 12 + .changeset/sql-returning-identifier.md | 5 + .../sql-runner-requested-shard-results.md | 5 + .changeset/sqlite-bun-transaction-export.md | 6 + .changeset/sqlite-do-stream-errors.md | 5 + .changeset/sqlite-node-unprepared-errors.md | 5 + .changeset/sqlite-wasm-messageport-startup.md | 5 + .changeset/sqlite-wasm-statement-columns.md | 5 + .../sqlite-wasm-worker-error-metadata.md | 5 + .../sqlmodel-insert-decoding-services.md | 7 + .changeset/stream-rechunk-large-source.md | 5 + .changeset/strict-content-length.md | 5 + .changeset/strict-cookie-names.md | 5 + .changeset/struct-numeric-selection.md | 5 + .changeset/structural-schema-nodes.md | 7 + ...ronized-ref-modify-some-effect-currying.md | 5 + .changeset/synchronized-ref-not-ref.md | 5 + .../tap-defect-saved-error-inference.md | 5 + .changeset/tcp-schema-binary-default.md | 12 + ...template-literal-parts-without-encoding.md | 15 + .changeset/testschema-own-field-asts.md | 5 + .changeset/thin-ends-hug.md | 5 + .changeset/tidy-parts-narrow.md | 5 + .changeset/tidy-tools-finish.md | 5 + .changeset/timeout-error-message.md | 5 + ...timeout-or-else-cleanup-before-fallback.md | 7 + .changeset/token-bucket-elapsed-refill.md | 7 + .../tokenizer-whole-prompt-truncation.md | 5 + .changeset/tool-result-branch-encoding.md | 5 + .changeset/track-mapped-error-domain.md | 5 + .changeset/trie-remove-valued-prefix.md | 5 + .changeset/try-direct-error-types.md | 9 + .../tuple-optional-evolve-result-types.md | 7 + .changeset/undici-response-form-data.md | 5 + .changeset/unstable-http-schemas.md | 5 + ...raded-request-skips-http-response-write.md | 5 + .changeset/urlparams-null-input.md | 5 + .changeset/urlparams-setall-immutable.md | 5 + .changeset/use-canonical-array-indices.md | 5 + .changeset/violet-pugs-tickle.md | 7 + .changeset/vitest-five-migration.md | 16 + .changeset/vitest-layer-concurrency.md | 5 + .../with-error-reporting-effect-result.md | 6 + .changeset/worker-run-early-exit.md | 5 + .changeset/xhr-arraybuffer-readers.md | 5 + .github/actions/deploy-website/action.yml | 40 + .github/actions/setup/action.yaml | 18 +- .github/workflows/ai-codegen.yml | 7 +- .github/workflows/bundle-comment.yml | 49 +- .github/workflows/check.yml | 155 +- .github/workflows/cluster.yml | 24 + .github/workflows/release-queue.yml | 44 + .github/workflows/release.yml | 27 +- .github/workflows/snapshot.yml | 21 +- .gitignore | 42 +- .oxlintrc.json | 20 +- .patterns/effect.md | 102 - .patterns/jsdoc.md | 53 - .patterns/testing.md | 44 - .specs/README.md | 2 - .vscode/settings.json | 11 +- AGENTS.md | 148 - LLMS.md | 147 +- README.md | 74 +- TODOS.md | 28 - ai-docs/package.json | 6 +- .../src/01_effect/01_basics/01_effect-gen.ts | 4 +- .../src/01_effect/01_basics/02_effect-fn.ts | 27 +- .../01_basics/10_creating-effects.ts | 8 +- ai-docs/src/01_effect/01_basics/index.md | 8 +- .../01_effect/02_schema/10_schema-basics.ts | 43 + ai-docs/src/01_effect/02_schema/index.md | 7 + .../01_service.ts | 2 +- .../10_reference.ts | 0 .../20_layer-composition.ts | 4 +- .../20_layer-unwrap.ts | 6 +- .../{02_services => 03_services}/index.md | 0 .../01_error-handling.ts | 8 +- .../{03_errors => 04_errors}/10_catch-tags.ts | 6 +- .../20_reason-errors.ts | 12 +- .../{03_errors => 04_errors}/index.md | 0 .../10_acquire-release.ts | 8 +- .../20_layer-side-effects.ts | 0 .../30_layer-map.ts | 2 +- .../{04_resources => 05_resources}/index.md | 0 .../{05_running => 06_running}/10_run-main.ts | 0 .../20_layer-launch.ts | 0 .../{05_running => 06_running}/index.md | 0 .../{06_pubsub => 07_pubsub}/10_pubsub.ts | 0 .../{06_pubsub => 07_pubsub}/index.md | 0 .../10_creating-streams.ts | 4 +- .../20_consuming-streams.ts | 0 .../{02_stream => 03_stream}/30_encoding.ts | 14 +- ai-docs/src/{02_stream => 03_stream}/index.md | 0 .../10_managed-runtime.ts | 6 +- .../index.md | 0 .../src/05_batching/10_request-resolver.ts | 6 +- ai-docs/src/06_schedule/10_schedules.ts | 96 +- ai-docs/src/08_observability/10_logging.ts | 2 +- ai-docs/src/10_predicate/01_basics.ts | 14 + ai-docs/src/10_predicate/index.md | 9 + ai-docs/src/40_sql/10_basics.ts | 175 + ai-docs/src/40_sql/index.md | 6 + ai-docs/src/50_http-client/10_basics.ts | 6 +- ai-docs/src/51_http-server/10_basics.ts | 4 +- ai-docs/src/51_http-server/20_testing.ts | 100 + .../fixtures/api/Authorization.ts | 2 +- .../src/51_http-server/fixtures/api/Users.ts | 44 +- .../51_http-server/fixtures/domain/User.ts | 21 +- .../fixtures/domain/UserErrors.ts | 6 +- .../fixtures/server/Authorization.ts | 21 +- .../51_http-server/fixtures/server/Users.ts | 161 +- .../fixtures/server/Users/http.ts | 73 +- .../10_working-with-child-processes.ts | 2 +- ai-docs/src/60_child-process/index.md | 2 +- ai-docs/src/70_cli/10_basics.ts | 48 +- ai-docs/src/71_ai/10_language-model.ts | 6 +- ai-docs/src/71_ai/20_tools.ts | 10 +- ai-docs/src/71_ai/30_chat.ts | 4 +- ai-docs/src/80_cluster/10_entities.ts | 6 +- ai-docs/src/index.md | 10 +- ai-docs/tsconfig.json | 2 +- cookbooks/schedule.md | 735 - deno.json | 12 +- docker-compose.yaml | 18 - dprint.json | 19 +- flake.lock | 6 +- flake.nix | 4 +- jsdocs.config.json | 2 + migration/annotations/README.md | 14 + migration/annotations/effect__Arbitrary.yaml | 20 + migration/annotations/effect__Array.yaml | 78 + migration/annotations/effect__BigDecimal.yaml | 36 + migration/annotations/effect__BigInt.yaml | 21 + migration/annotations/effect__Brand.yaml | 33 + migration/annotations/effect__Cache.yaml | 33 + migration/annotations/effect__Cause.yaml | 213 + migration/annotations/effect__Channel.yaml | 219 + .../effect__ChildExecutorDecision.yaml | 3 + migration/annotations/effect__Chunk.yaml | 36 + migration/annotations/effect__Clock.yaml | 18 + migration/annotations/effect__Config.yaml | 114 + .../annotations/effect__ConfigError.yaml | 66 + .../annotations/effect__ConfigProvider.yaml | 69 + .../effect__ConfigProviderPathPatch.yaml | 30 + migration/annotations/effect__Console.yaml | 21 + migration/annotations/effect__Context.yaml | 54 + migration/annotations/effect__Cron.yaml | 21 + migration/annotations/effect__Data.yaml | 33 + migration/annotations/effect__DateTime.yaml | 84 + .../annotations/effect__DefaultServices.yaml | 9 + migration/annotations/effect__Deferred.yaml | 30 + migration/annotations/effect__Differ.yaml | 99 + migration/annotations/effect__Duration.yaml | 48 + migration/annotations/effect__Effect.yaml | 663 + migration/annotations/effect__Effectable.yaml | 30 + migration/annotations/effect__Either.yaml | 105 + migration/annotations/effect__Encoding.yaml | 24 + migration/annotations/effect__Equal.yaml | 3 + .../annotations/effect__Equivalence.yaml | 42 + .../annotations/effect__ExecutionPlan.yaml | 9 + .../effect__ExecutionStrategy.yaml | 33 + migration/annotations/effect__Exit.yaml | 72 + migration/annotations/effect__FastCheck.yaml | 91 + migration/annotations/effect__Fiber.yaml | 144 + .../annotations/effect__FiberHandle.yaml | 15 + migration/annotations/effect__FiberId.yaml | 63 + migration/annotations/effect__FiberMap.yaml | 15 + migration/annotations/effect__FiberRef.yaml | 144 + migration/annotations/effect__FiberRefs.yaml | 39 + .../annotations/effect__FiberRefsPatch.yaml | 24 + migration/annotations/effect__FiberSet.yaml | 9 + .../annotations/effect__FiberStatus.yaml | 33 + migration/annotations/effect__Function.yaml | 12 + .../annotations/effect__GlobalValue.yaml | 3 + migration/annotations/effect__Graph.yaml | 15 + migration/annotations/effect__GroupBy.yaml | 27 + migration/annotations/effect__Hash.yaml | 3 + migration/annotations/effect__HashMap.yaml | 15 + migration/annotations/effect__HashSet.yaml | 33 + .../annotations/effect__Inspectable.yaml | 15 + migration/annotations/effect__Iterable.yaml | 12 + migration/annotations/effect__JSONSchema.yaml | 71 + migration/annotations/effect__KeyedPool.yaml | 27 + migration/annotations/effect__Layer.yaml | 192 + migration/annotations/effect__LayerMap.yaml | 15 + migration/annotations/effect__List.yaml | 126 + migration/annotations/effect__LogLevel.yaml | 51 + migration/annotations/effect__LogSpan.yaml | 9 + migration/annotations/effect__Logger.yaml | 108 + migration/annotations/effect__Mailbox.yaml | 30 + .../annotations/effect__ManagedRuntime.yaml | 15 + migration/annotations/effect__Match.yaml | 51 + .../annotations/effect__MergeDecision.yaml | 24 + migration/annotations/effect__MergeState.yaml | 3 + .../annotations/effect__MergeStrategy.yaml | 27 + migration/annotations/effect__Metric.yaml | 105 + .../annotations/effect__MetricBoundaries.yaml | 18 + migration/annotations/effect__MetricHook.yaml | 39 + migration/annotations/effect__MetricKey.yaml | 33 + .../annotations/effect__MetricKeyType.yaml | 66 + .../annotations/effect__MetricLabel.yaml | 12 + migration/annotations/effect__MetricPair.yaml | 18 + .../annotations/effect__MetricPolling.yaml | 24 + .../annotations/effect__MetricRegistry.yaml | 9 + .../annotations/effect__MetricState.yaml | 60 + migration/annotations/effect__Micro.yaml | 500 + .../annotations/effect__ModuleVersion.yaml | 3 + .../annotations/effect__MutableHashMap.yaml | 6 + .../annotations/effect__MutableHashSet.yaml | 6 + .../annotations/effect__MutableList.yaml | 36 + .../annotations/effect__MutableQueue.yaml | 42 + migration/annotations/effect__MutableRef.yaml | 6 + migration/annotations/effect__Number.yaml | 18 + migration/annotations/effect__Option.yaml | 45 + migration/annotations/effect__Order.yaml | 63 + migration/annotations/effect__Ordering.yaml | 6 + .../annotations/effect__ParseResult.yaml | 134 + .../effect__PartitionedSemaphore.yaml | 6 + migration/annotations/effect__Pipeable.yaml | 3 + migration/annotations/effect__Pool.yaml | 15 + migration/annotations/effect__Predicate.yaml | 111 + migration/annotations/effect__Pretty.yaml | 13 + migration/annotations/effect__PubSub.yaml | 3 + migration/annotations/effect__Queue.yaml | 81 + migration/annotations/effect__Random.yaml | 18 + .../annotations/effect__RateLimiter.yaml | 3 + migration/annotations/effect__RcMap.yaml | 9 + migration/annotations/effect__RcRef.yaml | 12 + migration/annotations/effect__Readable.yaml | 21 + migration/annotations/effect__Record.yaml | 24 + .../annotations/effect__RedBlackTree.yaml | 105 + migration/annotations/effect__Redacted.yaml | 15 + migration/annotations/effect__Ref.yaml | 48 + migration/annotations/effect__Reloadable.yaml | 30 + migration/annotations/effect__Request.yaml | 30 + .../annotations/effect__RequestBlock.yaml | 30 + .../annotations/effect__RequestResolver.yaml | 36 + migration/annotations/effect__Resource.yaml | 15 + migration/annotations/effect__Runtime.yaml | 84 + .../annotations/effect__RuntimeFlags.yaml | 108 + .../effect__RuntimeFlagsPatch.yaml | 3 + migration/annotations/effect__STM.yaml | 354 + migration/annotations/effect__Schedule.yaml | 267 + .../annotations/effect__ScheduleDecision.yaml | 18 + .../annotations/effect__ScheduleInterval.yaml | 3 + .../effect__ScheduleIntervals.yaml | 3 + migration/annotations/effect__Scheduler.yaml | 42 + migration/annotations/effect__Schema.yaml | 1362 + migration/annotations/effect__SchemaAST.yaml | 444 + migration/annotations/effect__Scope.yaml | 21 + .../annotations/effect__ScopedCache.yaml | 12 + migration/annotations/effect__ScopedRef.yaml | 15 + migration/annotations/effect__Secret.yaml | 21 + .../effect__SingleProducerAsyncInput.yaml | 12 + migration/annotations/effect__Sink.yaml | 204 + migration/annotations/effect__SortedMap.yaml | 66 + migration/annotations/effect__SortedSet.yaml | 63 + migration/annotations/effect__Stream.yaml | 450 + migration/annotations/effect__StreamEmit.yaml | 16 + .../effect__StreamHaltStrategy.yaml | 44 + migration/annotations/effect__Streamable.yaml | 3 + migration/annotations/effect__Struct.yaml | 9 + .../annotations/effect__Subscribable.yaml | 21 + .../annotations/effect__SubscriptionRef.yaml | 15 + migration/annotations/effect__Supervisor.yaml | 27 + migration/annotations/effect__Symbol.yaml | 3 + .../annotations/effect__SynchronizedRef.yaml | 18 + migration/annotations/effect__TArray.yaml | 123 + migration/annotations/effect__TDeferred.yaml | 15 + migration/annotations/effect__TMap.yaml | 99 + .../annotations/effect__TPriorityQueue.yaml | 42 + migration/annotations/effect__TPubSub.yaml | 39 + migration/annotations/effect__TQueue.yaml | 87 + migration/annotations/effect__TRandom.yaml | 27 + .../annotations/effect__TReentrantLock.yaml | 21 + migration/annotations/effect__TRef.yaml | 30 + migration/annotations/effect__TSemaphore.yaml | 30 + migration/annotations/effect__TSet.yaml | 75 + .../annotations/effect__TSubscriptionRef.yaml | 36 + migration/annotations/effect__Take.yaml | 67 + .../annotations/effect__TestAnnotation.yaml | 3 + .../effect__TestAnnotationMap.yaml | 3 + .../annotations/effect__TestAnnotations.yaml | 3 + migration/annotations/effect__TestClock.yaml | 24 + migration/annotations/effect__TestConfig.yaml | 6 + .../annotations/effect__TestContext.yaml | 6 + migration/annotations/effect__TestLive.yaml | 9 + .../annotations/effect__TestServices.yaml | 96 + migration/annotations/effect__TestSized.yaml | 12 + migration/annotations/effect__Tracer.yaml | 27 + migration/annotations/effect__Trie.yaml | 6 + migration/annotations/effect__Tuple.yaml | 30 + migration/annotations/effect__Types.yaml | 24 + .../effect__UpstreamPullRequest.yaml | 3 + .../effect__UpstreamPullStrategy.yaml | 3 + migration/annotations/effect__Utils.yaml | 60 + .../effect__ai-amazon-bedrock.yaml | 3 + ...i-amazon-bedrock__AmazonBedrockClient.yaml | 3 + ...i-amazon-bedrock__AmazonBedrockConfig.yaml | 3 + ...n-bedrock__AmazonBedrockLanguageModel.yaml | 3 + ...i-amazon-bedrock__AmazonBedrockSchema.yaml | 3 + ..._ai-amazon-bedrock__AmazonBedrockTool.yaml | 3 + ...i-amazon-bedrock__EventStreamEncoding.yaml | 3 + .../effect__ai-amazon-bedrock__index.yaml | 3 + ...effect__ai-anthropic__AnthropicClient.yaml | 54 + ...effect__ai-anthropic__AnthropicConfig.yaml | 6 + ..._ai-anthropic__AnthropicLanguageModel.yaml | 21 + ...ect__ai-anthropic__AnthropicTokenizer.yaml | 6 + .../effect__ai-anthropic__AnthropicTool.yaml | 6 + .../effect__ai-anthropic__Generated.yaml | 1023 + .../effect__ai-anthropic__index.yaml | 3 + migration/annotations/effect__ai-google.yaml | 3 + .../effect__ai-google__Generated.yaml | 3 + .../effect__ai-google__GoogleClient.yaml | 3 + .../effect__ai-google__GoogleConfig.yaml | 3 + ...ffect__ai-google__GoogleLanguageModel.yaml | 3 + .../effect__ai-google__GoogleTool.yaml | 3 + .../annotations/effect__ai-google__index.yaml | 3 + .../effect__ai-openai__Generated.yaml | 3714 ++ .../effect__ai-openai__OpenAiClient.yaml | 162 + .../effect__ai-openai__OpenAiConfig.yaml | 6 + ...fect__ai-openai__OpenAiEmbeddingModel.yaml | 24 + ...ffect__ai-openai__OpenAiLanguageModel.yaml | 21 + .../effect__ai-openai__OpenAiTelemetry.yaml | 9 + .../effect__ai-openai__OpenAiTokenizer.yaml | 6 + .../annotations/effect__ai-openai__index.yaml | 3 + .../effect__ai-openrouter__Generated.yaml | 1053 + ...fect__ai-openrouter__OpenRouterClient.yaml | 15 + ...fect__ai-openrouter__OpenRouterConfig.yaml | 6 + ...i-openrouter__OpenRouterLanguageModel.yaml | 9 + .../effect__ai-openrouter__index.yaml | 3 + migration/annotations/effect__ai.yaml | 3 + .../annotations/effect__ai__AiError.yaml | 24 + .../effect__ai__EmbeddingModel.yaml | 3 + .../annotations/effect__ai__IdGenerator.yaml | 3 + .../effect__ai__LanguageModel.yaml | 6 + .../annotations/effect__ai__McpSchema.yaml | 27 + .../annotations/effect__ai__McpServer.yaml | 15 + migration/annotations/effect__ai__Model.yaml | 3 + migration/annotations/effect__ai__Prompt.yaml | 69 + .../annotations/effect__ai__Response.yaml | 72 + migration/annotations/effect__ai__Tool.yaml | 63 + .../annotations/effect__ai__Toolkit.yaml | 9 + migration/annotations/effect__ai__index.yaml | 3 + migration/annotations/effect__cli.yaml | 3 + migration/annotations/effect__cli__Args.yaml | 126 + .../annotations/effect__cli__AutoCorrect.yaml | 3 + .../effect__cli__BuiltInOptions.yaml | 48 + .../annotations/effect__cli__CliApp.yaml | 12 + .../annotations/effect__cli__CliConfig.yaml | 18 + .../annotations/effect__cli__Command.yaml | 60 + .../effect__cli__CommandDescriptor.yaml | 57 + .../effect__cli__CommandDirective.yaml | 21 + .../annotations/effect__cli__ConfigFile.yaml | 12 + .../annotations/effect__cli__HelpDoc.yaml | 78 + .../effect__cli__HelpDoc__Span.yaml | 3 + .../annotations/effect__cli__Options.yaml | 150 + .../annotations/effect__cli__Primitive.yaml | 42 + .../annotations/effect__cli__Prompt.yaml | 69 + migration/annotations/effect__cli__Usage.yaml | 3 + .../effect__cli__ValidationError.yaml | 111 + migration/annotations/effect__cli__index.yaml | 3 + migration/annotations/effect__cluster.yaml | 3 + .../effect__cluster__ClusterCron.yaml | 3 + .../effect__cluster__ClusterError.yaml | 3 + .../effect__cluster__ClusterSchema.yaml | 12 + ...ffect__cluster__ClusterWorkflowEngine.yaml | 6 + .../effect__cluster__DeliverAt.yaml | 3 + .../annotations/effect__cluster__Entity.yaml | 15 + .../effect__cluster__EntityAddress.yaml | 9 + .../effect__cluster__EntityId.yaml | 3 + .../effect__cluster__EntityProxy.yaml | 6 + .../effect__cluster__EntityProxyServer.yaml | 9 + .../effect__cluster__EntityResource.yaml | 6 + .../effect__cluster__Envelope.yaml | 45 + .../effect__cluster__HttpRunner.yaml | 12 + .../effect__cluster__K8sHttpClient.yaml | 3 + .../effect__cluster__MachineId.yaml | 3 + .../annotations/effect__cluster__Message.yaml | 3 + .../effect__cluster__MessageStorage.yaml | 15 + .../annotations/effect__cluster__Reply.yaml | 12 + .../annotations/effect__cluster__Runner.yaml | 3 + .../effect__cluster__RunnerAddress.yaml | 6 + .../effect__cluster__RunnerHealth.yaml | 3 + .../effect__cluster__RunnerServer.yaml | 6 + .../effect__cluster__RunnerStorage.yaml | 6 + .../annotations/effect__cluster__Runners.yaml | 9 + .../annotations/effect__cluster__ShardId.yaml | 9 + .../effect__cluster__Sharding.yaml | 3 + .../effect__cluster__ShardingConfig.yaml | 9 + ...t__cluster__ShardingRegistrationEvent.yaml | 3 + .../effect__cluster__SingleRunner.yaml | 3 + .../effect__cluster__Singleton.yaml | 3 + .../effect__cluster__SingletonAddress.yaml | 3 + .../effect__cluster__Snowflake.yaml | 9 + .../effect__cluster__SocketRunner.yaml | 6 + .../effect__cluster__SqlMessageStorage.yaml | 9 + .../effect__cluster__SqlRunnerStorage.yaml | 9 + .../effect__cluster__TestRunner.yaml | 3 + .../annotations/effect__cluster__index.yaml | 3 + .../annotations/effect__experimental.yaml | 3 + .../effect__experimental__DevTools.yaml | 6 + ...ffect__experimental__DevTools__Client.yaml | 15 + ...ffect__experimental__DevTools__Domain.yaml | 18 + ...ffect__experimental__DevTools__Server.yaml | 3 + .../effect__experimental__Event.yaml | 42 + .../effect__experimental__EventGroup.yaml | 15 + .../effect__experimental__EventJournal.yaml | 18 + .../effect__experimental__EventLog.yaml | 27 + .../effect__experimental__EventLogRemote.yaml | 57 + .../effect__experimental__EventLogServer.yaml | 15 + ...erimental__EventLogServer__Cloudflare.yaml | 3 + .../effect__experimental__Machine.yaml | 3 + ...ect__experimental__Machine__Procedure.yaml | 3 + ..._experimental__Machine__ProcedureList.yaml | 3 + ...l__Machine__SerializableProcedureList.yaml | 3 + .../effect__experimental__PersistedCache.yaml | 3 + .../effect__experimental__PersistedQueue.yaml | 15 + ...__experimental__PersistedQueue__Redis.yaml | 9 + .../effect__experimental__Persistence.yaml | 51 + ...fect__experimental__Persistence__Lmdb.yaml | 3 + ...ect__experimental__Persistence__Redis.yaml | 15 + .../effect__experimental__RateLimiter.yaml | 21 + ...ect__experimental__RateLimiter__Redis.yaml | 9 + .../effect__experimental__Reactivity.yaml | 15 + ...effect__experimental__RequestResolver.yaml | 12 + .../effect__experimental__Sse.yaml | 3 + .../effect__experimental__VariantSchema.yaml | 39 + .../effect__experimental__index.yaml | 3 + migration/annotations/effect__index.yaml | 12 + .../effect__opentelemetry__Logger.yaml | 9 + .../effect__opentelemetry__Metrics.yaml | 3 + .../effect__opentelemetry__NodeSdk.yaml | 3 + .../effect__opentelemetry__Otlp.yaml | 3 + .../effect__opentelemetry__OtlpLogger.yaml | 6 + .../effect__opentelemetry__OtlpMetrics.yaml | 6 + .../effect__opentelemetry__OtlpResource.yaml | 6 + .../effect__opentelemetry__OtlpTracer.yaml | 6 + .../effect__opentelemetry__Resource.yaml | 3 + .../effect__opentelemetry__Tracer.yaml | 21 + .../effect__opentelemetry__WebSdk.yaml | 3 + .../effect__opentelemetry__index.yaml | 3 + ...__platform-browser__BrowserHttpClient.yaml | 3 + ...fect__platform-browser__BrowserWorker.yaml | 6 + ...platform-browser__BrowserWorkerRunner.yaml | 3 + .../effect__platform-browser__Clipboard.yaml | 9 + ...effect__platform-browser__Geolocation.yaml | 12 + ...effect__platform-browser__Permissions.yaml | 12 + .../effect__platform-browser__index.yaml | 3 + ...ect__platform-bun__BunCommandExecutor.yaml | 3 + .../effect__platform-bun__BunContext.yaml | 6 + ...orm-bun__BunFileSystem__ParcelWatcher.yaml | 3 + ...effect__platform-bun__BunHttpPlatform.yaml | 3 + .../effect__platform-bun__BunHttpServer.yaml | 6 + ...t__platform-bun__BunHttpServerRequest.yaml | 3 + ...ffect__platform-bun__BunKeyValueStore.yaml | 3 + .../effect__platform-bun__BunSink.yaml | 9 + .../effect__platform-bun__BunSocket.yaml | 3 + ...effect__platform-bun__BunSocketServer.yaml | 3 + .../effect__platform-bun__BunStream.yaml | 15 + .../effect__platform-bun__BunWorker.yaml | 6 + .../effect__platform-bun__index.yaml | 3 + ...form-node-shared__NodeCommandExecutor.yaml | 3 + ...shared__NodeFileSystem__ParcelWatcher.yaml | 3 + ...atform-node-shared__NodeKeyValueStore.yaml | 3 + ...__platform-node-shared__NodeMultipart.yaml | 6 + ...ffect__platform-node-shared__NodeSink.yaml | 9 + ...ect__platform-node-shared__NodeSocket.yaml | 3 + ...ect__platform-node-shared__NodeStream.yaml | 15 + ...t__platform-node__NodeCommandExecutor.yaml | 3 + .../effect__platform-node__NodeContext.yaml | 6 + ...m-node__NodeFileSystem__ParcelWatcher.yaml | 3 + ...effect__platform-node__NodeHttpClient.yaml | 33 + ...effect__platform-node__NodeHttpServer.yaml | 3 + ...ect__platform-node__NodeKeyValueStore.yaml | 3 + .../effect__platform-node__NodeWorker.yaml | 6 + .../effect__platform-node__Undici.yaml | 117 + .../effect__platform-node__index.yaml | 3 + migration/annotations/effect__platform.yaml | 3 + .../effect__platform__ChannelSchema.yaml | 9 + .../effect__platform__Command.yaml | 54 + .../effect__platform__CommandExecutor.yaml | 18 + .../effect__platform__Cookies.yaml | 24 + .../effect__platform__Effectify.yaml | 3 + .../annotations/effect__platform__Error.yaml | 18 + .../annotations/effect__platform__Etag.yaml | 9 + .../effect__platform__FetchHttpClient.yaml | 6 + .../effect__platform__FileSystem.yaml | 90 + .../effect__platform__Headers.yaml | 45 + .../effect__platform__HttpApi.yaml | 15 + .../effect__platform__HttpApiBuilder.yaml | 51 + .../effect__platform__HttpApiClient.yaml | 12 + .../effect__platform__HttpApiEndpoint.yaml | 105 + .../effect__platform__HttpApiError.yaml | 12 + .../effect__platform__HttpApiGroup.yaml | 54 + .../effect__platform__HttpApiMiddleware.yaml | 60 + .../effect__platform__HttpApiScalar.yaml | 9 + .../effect__platform__HttpApiSchema.yaml | 99 + .../effect__platform__HttpApiSecurity.yaml | 15 + .../effect__platform__HttpApiSwagger.yaml | 6 + .../effect__platform__HttpApp.yaml | 21 + .../effect__platform__HttpBody.yaml | 57 + .../effect__platform__HttpClient.yaml | 51 + .../effect__platform__HttpClientError.yaml | 9 + .../effect__platform__HttpClientRequest.yaml | 48 + .../effect__platform__HttpClientResponse.yaml | 30 + ...effect__platform__HttpIncomingMessage.yaml | 9 + .../effect__platform__HttpLayerRouter.yaml | 69 + .../effect__platform__HttpMethod.yaml | 3 + .../effect__platform__HttpMiddleware.yaml | 27 + .../effect__platform__HttpMultiplex.yaml | 3 + .../effect__platform__HttpPlatform.yaml | 12 + .../effect__platform__HttpRouter.yaml | 102 + .../effect__platform__HttpServer.yaml | 30 + .../effect__platform__HttpServerError.yaml | 15 + .../effect__platform__HttpServerRequest.yaml | 27 + ...fect__platform__HttpServerRespondable.yaml | 3 + .../effect__platform__HttpServerResponse.yaml | 66 + .../effect__platform__KeyValueStore.yaml | 27 + .../effect__platform__MsgPack.yaml | 30 + .../effect__platform__Multipart.yaml | 57 + .../annotations/effect__platform__Ndjson.yaml | 39 + .../effect__platform__OpenApi.yaml | 18 + .../effect__platform__OpenApiJsonSchema.yaml | 60 + .../annotations/effect__platform__Path.yaml | 3 + ...ect__platform__PlatformConfigProvider.yaml | 15 + .../effect__platform__PlatformLogger.yaml | 3 + .../effect__platform__Runtime.yaml | 3 + .../annotations/effect__platform__Socket.yaml | 36 + .../effect__platform__SocketServer.yaml | 12 + .../effect__platform__Template.yaml | 9 + .../effect__platform__Terminal.yaml | 6 + .../effect__platform__Transferable.yaml | 12 + .../annotations/effect__platform__Url.yaml | 9 + .../effect__platform__UrlParams.yaml | 54 + .../annotations/effect__platform__Worker.yaml | 66 + .../effect__platform__WorkerError.yaml | 6 + .../effect__platform__WorkerRunner.yaml | 54 + .../annotations/effect__platform__index.yaml | 3 + .../annotations/effect__printer-ansi.yaml | 3 + .../effect__printer-ansi__Ansi.yaml | 3 + .../effect__printer-ansi__AnsiDoc.yaml | 3 + .../effect__printer-ansi__Color.yaml | 3 + .../effect__printer-ansi__index.yaml | 3 + migration/annotations/effect__printer.yaml | 3 + .../annotations/effect__printer__Doc.yaml | 3 + .../effect__printer__DocStream.yaml | 3 + .../annotations/effect__printer__DocTree.yaml | 3 + .../annotations/effect__printer__Flatten.yaml | 3 + .../annotations/effect__printer__Layout.yaml | 3 + .../effect__printer__Optimize.yaml | 3 + .../effect__printer__PageWidth.yaml | 3 + .../annotations/effect__printer__index.yaml | 3 + migration/annotations/effect__rpc.yaml | 3 + migration/annotations/effect__rpc__Rpc.yaml | 75 + .../annotations/effect__rpc__RpcClient.yaml | 27 + .../effect__rpc__RpcClientError.yaml | 3 + .../annotations/effect__rpc__RpcGroup.yaml | 15 + .../annotations/effect__rpc__RpcMessage.yaml | 9 + .../effect__rpc__RpcMiddleware.yaml | 45 + .../annotations/effect__rpc__RpcSchema.yaml | 15 + .../effect__rpc__RpcSerialization.yaml | 15 + .../annotations/effect__rpc__RpcServer.yaml | 51 + .../annotations/effect__rpc__RpcTest.yaml | 3 + migration/annotations/effect__rpc__index.yaml | 3 + ...ect__sql-clickhouse__ClickhouseClient.yaml | 12 + ...t__sql-clickhouse__ClickhouseMigrator.yaml | 3 + .../effect__sql-clickhouse__index.yaml | 3 + .../annotations/effect__sql-d1__D1Client.yaml | 3 + .../annotations/effect__sql-d1__index.yaml | 3 + .../effect__sql-drizzle__Mysql.yaml | 15 + .../annotations/effect__sql-drizzle__Pg.yaml | 15 + .../effect__sql-drizzle__Sqlite.yaml | 15 + .../effect__sql-kysely__Kysely.yaml | 3 + .../effect__sql-kysely__Mssql.yaml | 3 + .../effect__sql-kysely__Mysql.yaml | 3 + .../annotations/effect__sql-kysely__Pg.yaml | 3 + .../effect__sql-kysely__Sqlite.yaml | 3 + .../effect__sql-kysely__patch.types.yaml | 3 + .../effect__sql-libsql__index.yaml | 3 + .../effect__sql-mssql__MssqlClient.yaml | 6 + .../effect__sql-mssql__Parameter.yaml | 6 + .../effect__sql-mssql__Procedure.yaml | 6 + .../annotations/effect__sql-mssql__index.yaml | 3 + .../effect__sql-mysql2__MysqlClient.yaml | 3 + .../effect__sql-mysql2__index.yaml | 3 + .../annotations/effect__sql-pg__PgClient.yaml | 15 + .../annotations/effect__sql-pg__index.yaml | 3 + .../effect__sql-sqlite-bun__SqliteClient.yaml | 3 + .../effect__sql-sqlite-bun__index.yaml | 3 + .../effect__sql-sqlite-do__SqliteClient.yaml | 3 + .../effect__sql-sqlite-do__index.yaml | 3 + ...effect__sql-sqlite-node__SqliteClient.yaml | 6 + .../effect__sql-sqlite-node__index.yaml | 3 + ...sql-sqlite-react-native__SqliteClient.yaml | 3 + ...ffect__sql-sqlite-react-native__index.yaml | 3 + ...effect__sql-sqlite-wasm__SqliteClient.yaml | 6 + .../effect__sql-sqlite-wasm__index.yaml | 3 + migration/annotations/effect__sql.yaml | 3 + .../annotations/effect__sql__Migrator.yaml | 3 + .../effect__sql__Migrator__FileSystem.yaml | 3 + migration/annotations/effect__sql__Model.yaml | 36 + .../annotations/effect__sql__SqlClient.yaml | 12 + .../effect__sql__SqlConnection.yaml | 3 + .../annotations/effect__sql__SqlError.yaml | 3 + .../effect__sql__SqlEventJournal.yaml | 6 + .../effect__sql__SqlEventLogServer.yaml | 6 + .../effect__sql__SqlPersistedQueue.yaml | 6 + .../annotations/effect__sql__SqlResolver.yaml | 6 + .../annotations/effect__sql__SqlSchema.yaml | 6 + .../annotations/effect__sql__Statement.yaml | 39 + migration/annotations/effect__sql__index.yaml | 3 + migration/annotations/effect__typeclass.yaml | 3 + .../effect__typeclass__Alternative.yaml | 3 + .../effect__typeclass__Applicative.yaml | 3 + .../effect__typeclass__Bicovariant.yaml | 3 + .../effect__typeclass__Bounded.yaml | 21 + .../effect__typeclass__Chainable.yaml | 3 + .../effect__typeclass__Contravariant.yaml | 3 + .../effect__typeclass__Coproduct.yaml | 3 + .../effect__typeclass__Covariant.yaml | 3 + .../effect__typeclass__Filterable.yaml | 3 + .../effect__typeclass__FlatMap.yaml | 3 + .../effect__typeclass__Foldable.yaml | 3 + .../effect__typeclass__Invariant.yaml | 3 + .../annotations/effect__typeclass__Monad.yaml | 3 + .../effect__typeclass__Monoid.yaml | 24 + .../annotations/effect__typeclass__Of.yaml | 3 + .../effect__typeclass__Pointed.yaml | 3 + .../effect__typeclass__Product.yaml | 3 + .../effect__typeclass__SemiAlternative.yaml | 3 + .../effect__typeclass__SemiApplicative.yaml | 3 + .../effect__typeclass__SemiCoproduct.yaml | 3 + .../effect__typeclass__SemiProduct.yaml | 3 + .../effect__typeclass__Semigroup.yaml | 51 + .../effect__typeclass__Traversable.yaml | 3 + ...ect__typeclass__TraversableFilterable.yaml | 3 + .../effect__typeclass__data__Array.yaml | 51 + .../effect__typeclass__data__BigInt.yaml | 18 + .../effect__typeclass__data__Boolean.yaml | 24 + .../effect__typeclass__data__Duration.yaml | 21 + .../effect__typeclass__data__Effect.yaml | 36 + .../effect__typeclass__data__Either.yaml | 48 + .../effect__typeclass__data__Identity.yaml | 3 + .../effect__typeclass__data__Micro.yaml | 36 + .../effect__typeclass__data__Number.yaml | 27 + .../effect__typeclass__data__Option.yaml | 57 + .../effect__typeclass__data__Ordering.yaml | 6 + .../effect__typeclass__data__Predicate.yaml | 39 + .../effect__typeclass__data__Record.yaml | 39 + .../effect__typeclass__data__String.yaml | 6 + .../effect__typeclass__data__Tuple.yaml | 3 + .../annotations/effect__typeclass__index.yaml | 3 + .../annotations/effect__vitest__index.yaml | 279 + .../annotations/effect__vitest__utils.yaml | 15 + migration/annotations/effect__workflow.yaml | 3 + .../effect__workflow__Activity.yaml | 18 + .../effect__workflow__DurableClock.yaml | 6 + .../effect__workflow__DurableDeferred.yaml | 30 + .../effect__workflow__DurableQueue.yaml | 6 + .../effect__workflow__DurableRateLimiter.yaml | 3 + .../effect__workflow__Workflow.yaml | 51 + .../effect__workflow__WorkflowEngine.yaml | 6 + .../effect__workflow__WorkflowProxy.yaml | 6 + ...effect__workflow__WorkflowProxyServer.yaml | 9 + migration/fiberref.md | 13 +- migration/schema.md | 33 +- migration/v3-to-v4.md | 16470 +++++- package.json | 117 +- packages/ai/anthropic/CHANGELOG.md | 187 + packages/ai/anthropic/README.md | 14 + packages/ai/anthropic/codegen.yml | 47 +- packages/ai/anthropic/docgen.json | 26 - packages/ai/anthropic/package.json | 18 +- packages/ai/anthropic/src/AnthropicClient.ts | 23 +- packages/ai/anthropic/src/AnthropicConfig.ts | 4 +- packages/ai/anthropic/src/AnthropicError.ts | 20 +- .../anthropic/src/AnthropicLanguageModel.ts | 221 +- .../ai/anthropic/src/AnthropicTelemetry.ts | 2 +- packages/ai/anthropic/src/AnthropicTool.ts | 284 +- packages/ai/anthropic/src/Generated.ts | 70 +- packages/ai/anthropic/src/internal/errors.ts | 59 +- .../ai/anthropic/src/internal/utilities.ts | 2 +- .../ai/anthropic/test/AnthropicClient.test.ts | 227 + .../test/AnthropicLanguageModel.test.ts | 924 +- packages/ai/anthropic/tsconfig.json | 2 +- .../typetest/AnthropicLanguageModel.tst.ts | 14 + packages/ai/anthropic/vitest.config.ts | 6 - packages/ai/openai-compat/CHANGELOG.md | 181 + packages/ai/openai-compat/README.md | 14 + packages/ai/openai-compat/docgen.json | 24 - packages/ai/openai-compat/package.json | 18 +- packages/ai/openai-compat/src/OpenAiClient.ts | 227 +- packages/ai/openai-compat/src/OpenAiConfig.ts | 4 +- .../openai-compat/src/OpenAiEmbeddingModel.ts | 37 +- .../openai-compat/src/OpenAiLanguageModel.ts | 265 +- .../ai/openai-compat/src/OpenAiTelemetry.ts | 2 +- .../ai/openai-compat/src/internal/errors.ts | 64 +- .../openai-compat/src/internal/utilities.ts | 2 +- .../openai-compat/test/OpenAiClient.test.ts | 671 +- .../test/OpenAiLanguageModel.test.ts | 392 +- packages/ai/openai-compat/tsconfig.json | 2 +- .../typetest/OpenAiConfig.tst.ts | 26 + packages/ai/openai-compat/vitest.config.ts | 6 - packages/ai/openai/CHANGELOG.md | 185 + packages/ai/openai/README.md | 14 + packages/ai/openai/codegen.yaml | 1 - packages/ai/openai/docgen.json | 24 - packages/ai/openai/package.json | 18 +- packages/ai/openai/src/Generated.ts | 14 +- packages/ai/openai/src/OpenAiClient.ts | 141 +- .../ai/openai/src/OpenAiClientGenerated.ts | 10 +- packages/ai/openai/src/OpenAiConfig.ts | 4 +- .../ai/openai/src/OpenAiEmbeddingModel.ts | 7 +- packages/ai/openai/src/OpenAiError.ts | 20 +- packages/ai/openai/src/OpenAiLanguageModel.ts | 184 +- packages/ai/openai/src/OpenAiSchema.ts | 174 +- packages/ai/openai/src/OpenAiTelemetry.ts | 2 +- packages/ai/openai/src/internal/errors.ts | 96 +- packages/ai/openai/src/internal/utilities.ts | 2 +- packages/ai/openai/test/OpenAiClient.test.ts | 1188 +- .../openai/test/OpenAiLanguageModel.test.ts | 712 +- packages/ai/openai/test/OpenAiSchema.test.ts | 185 +- packages/ai/openai/tsconfig.json | 2 +- packages/ai/openai/vitest.config.ts | 6 - packages/ai/openrouter/CHANGELOG.md | 180 + packages/ai/openrouter/README.md | 14 + packages/ai/openrouter/codegen.yml | 39 +- packages/ai/openrouter/docgen.json | 24 - packages/ai/openrouter/package.json | 18 +- packages/ai/openrouter/src/Generated.ts | 44386 ++++++++++++---- .../ai/openrouter/src/OpenRouterClient.ts | 21 +- .../ai/openrouter/src/OpenRouterConfig.ts | 4 +- packages/ai/openrouter/src/OpenRouterError.ts | 20 +- .../openrouter/src/OpenRouterLanguageModel.ts | 131 +- packages/ai/openrouter/src/internal/errors.ts | 62 +- .../ai/openrouter/src/internal/utilities.ts | 10 +- packages/ai/openrouter/test/Generated.test.ts | 158 + .../openrouter/test/OpenRouterClient.test.ts | 203 + .../test/OpenRouterLanguageModel.test.ts | 283 +- packages/ai/openrouter/tsconfig.json | 2 +- packages/ai/openrouter/vitest.config.ts | 6 - packages/atom/react/CHANGELOG.md | 160 + packages/atom/react/README.md | 13 +- packages/atom/react/docgen.json | 25 - packages/atom/react/package.json | 38 +- packages/atom/react/src/Hooks.ts | 21 +- packages/atom/react/src/ScopedAtom.ts | 10 +- packages/atom/react/test/index.test.tsx | 74 +- packages/atom/react/tsconfig.json | 2 +- packages/atom/react/vitest.config.ts | 9 - packages/atom/solid/CHANGELOG.md | 157 + packages/atom/solid/README.md | 13 +- packages/atom/solid/docgen.json | 26 - packages/atom/solid/package.json | 28 +- packages/atom/solid/src/RegistryContext.ts | 2 +- packages/atom/solid/tsconfig.json | 2 +- packages/atom/solid/vitest.config.ts | 15 - packages/atom/vue/CHANGELOG.md | 157 + packages/atom/vue/README.md | 13 +- packages/atom/vue/docgen.json | 24 - packages/atom/vue/package.json | 22 +- packages/atom/vue/src/index.ts | 17 +- packages/atom/vue/test/index.test.ts | 23 +- packages/atom/vue/tsconfig.json | 2 +- packages/atom/vue/vitest.config.ts | 8 - packages/effect/ARBITRARY-FOLLOW-UPS.md | 62 + packages/effect/ARBITRARY-MIGRATION.md | 335 + packages/effect/ARBITRARY.md | 1300 + packages/effect/CHANGELOG.md | 1752 +- packages/effect/CONFIG.md | 217 +- packages/effect/HTTPAPI.md | 826 +- packages/effect/MCP.md | 55 +- packages/effect/README.md | 42 +- packages/effect/SCHEMA.md | 1112 +- packages/effect/benchmark/Pool.ts | 76 + packages/effect/benchmark/http/multipart.ts | 122 + .../benchmark/http/serverAllocations.ts | 138 + packages/effect/benchmark/http/tracer.ts | 143 + .../benchmark/httpapi/sseClientDecoder.ts | 52 + .../effect/benchmark/rpc/RpcSerialization.ts | 297 + packages/effect/benchmark/schema/Enums.ts | 40 - packages/effect/benchmark/schema/Optic.ts | 101 +- .../effect/benchmark/schema/SchemaBinary.md | 13 + .../effect/benchmark/schema/SchemaBinary.ts | 688 + .../effect/benchmark/schema/SchemaError.ts | 46 + packages/effect/benchmark/schema/array.ts | 74 - packages/effect/benchmark/schema/filter.ts | 74 - packages/effect/benchmark/schema/object.ts | 82 - .../effect/benchmark/schema/tagged-union.ts | 69 - .../effect/benchmark/schema/transformation.ts | 91 - packages/effect/docgen.json | 38 - packages/effect/package.json | 50 +- packages/effect/runtimeperf/README.md | 165 + packages/effect/runtimeperf/compare.mts | 265 + packages/effect/runtimeperf/config.json | 1594 + packages/effect/runtimeperf/materialize.mts | 14 + packages/effect/runtimeperf/run.mts | 163 + packages/effect/runtimeperf/stats.mts | 131 + .../arbitrary/fixtures/fast-check-v4.ts | 414 + .../suites/arbitrary/fixtures/native.ts | 438 + .../suites/arbitrary/fixtures/schema.ts | 134 + .../suites/schema-benchmarks/fixtures/data.ts | 159 + .../schema-benchmarks/fixtures/effect-beta.ts | 133 + .../schema-benchmarks/fixtures/valibot.ts | 80 + .../suites/schema-benchmarks/fixtures/zod.ts | 85 + .../suites/schema/fixtures/adapters.ts | 83 + .../suites/schema/fixtures/behavior.ts | 215 + .../suites/schema/fixtures/cold.ts | 87 + .../suites/schema/fixtures/comparison.ts | 91 + .../runtimeperf/test/materialize.test.mts | 24 + .../effect/runtimeperf/test/registry.test.mts | 134 + .../effect/runtimeperf/test/stats.test.mts | 46 + .../effect/runtimeperf/test/worker.test.mts | 60 + packages/effect/runtimeperf/utils.mts | 253 + packages/effect/runtimeperf/worker.mts | 154 + packages/effect/src/Array.ts | 1018 +- packages/effect/src/BigDecimal.ts | 609 +- packages/effect/src/BigInt.ts | 246 +- packages/effect/src/Boolean.ts | 148 +- packages/effect/src/Brand.ts | 8 +- packages/effect/src/ByteSize.ts | 658 + packages/effect/src/Cache.ts | 406 +- packages/effect/src/Cause.ts | 410 +- packages/effect/src/Channel.ts | 1282 +- packages/effect/src/ChannelSchema.ts | 2 +- packages/effect/src/Chunk.ts | 850 +- packages/effect/src/Clock.ts | 230 +- packages/effect/src/Combiner.ts | 53 +- packages/effect/src/Config.ts | 1179 +- packages/effect/src/ConfigProvider.ts | 464 +- packages/effect/src/Console.ts | 266 +- packages/effect/src/Context.ts | 560 +- packages/effect/src/Cron.ts | 608 +- packages/effect/src/Crypto.ts | 101 +- packages/effect/src/Data.ts | 75 +- packages/effect/src/DateTime.ts | 692 +- packages/effect/src/Deferred.ts | 254 +- packages/effect/src/Duration.ts | 359 +- packages/effect/src/Effect.ts | 3319 +- packages/effect/src/Effectable.ts | 87 +- packages/effect/src/Encoding.ts | 451 +- packages/effect/src/Equal.ts | 102 +- packages/effect/src/Equivalence.ts | 172 +- packages/effect/src/ErrorReporter.ts | 80 +- packages/effect/src/ExecutionPlan.ts | 150 +- packages/effect/src/Exit.ts | 230 +- packages/effect/src/Fiber.ts | 206 +- packages/effect/src/FiberHandle.ts | 200 +- packages/effect/src/FiberMap.ts | 228 +- packages/effect/src/FiberSet.ts | 152 +- packages/effect/src/FileSystem.ts | 624 +- packages/effect/src/Filter.ts | 52 +- packages/effect/src/Formatter.ts | 159 +- packages/effect/src/Function.ts | 224 +- packages/effect/src/Graph.ts | 9424 +++- packages/effect/src/HKT.ts | 18 +- packages/effect/src/Hash.ts | 140 +- packages/effect/src/HashMap.ts | 328 +- packages/effect/src/HashRing.ts | 12 +- packages/effect/src/HashSet.ts | 193 +- packages/effect/src/Inspectable.ts | 42 +- packages/effect/src/Iterable.ts | 684 +- packages/effect/src/JsonPatch.ts | 179 +- packages/effect/src/JsonPointer.ts | 117 +- packages/effect/src/JsonSchema.ts | 1615 +- packages/effect/src/Latch.ts | 58 +- packages/effect/src/Layer.ts | 447 +- packages/effect/src/LayerMap.ts | 124 +- packages/effect/src/LayerRef.ts | 383 + packages/effect/src/LogLevel.ts | 128 +- packages/effect/src/Logger.ts | 799 +- packages/effect/src/ManagedRuntime.ts | 27 +- packages/effect/src/Match.ts | 664 +- packages/effect/src/Metric.ts | 1301 +- packages/effect/src/MutableHashMap.ts | 163 +- packages/effect/src/MutableHashSet.ts | 107 +- packages/effect/src/MutableList.ts | 467 +- packages/effect/src/MutableRef.ts | 341 +- packages/effect/src/Newtype.ts | 35 +- packages/effect/src/NonEmptyIterable.ts | 144 +- packages/effect/src/Number.ts | 242 +- packages/effect/src/Optic.ts | 993 +- packages/effect/src/Option.ts | 623 +- packages/effect/src/Order.ts | 194 +- packages/effect/src/Ordering.ts | 25 +- packages/effect/src/PartitionedSemaphore.ts | 8 +- packages/effect/src/Path.ts | 81 +- packages/effect/src/Pipeable.ts | 11 +- packages/effect/src/PlatformError.ts | 10 +- packages/effect/src/Pool.ts | 602 +- packages/effect/src/Predicate.ts | 253 +- packages/effect/src/PrimaryKey.ts | 16 +- packages/effect/src/PubSub.ts | 841 +- packages/effect/src/Pull.ts | 82 +- packages/effect/src/Queue.ts | 468 +- packages/effect/src/Random.ts | 115 +- packages/effect/src/RcMap.ts | 179 +- packages/effect/src/RcRef.ts | 37 +- packages/effect/src/Record.ts | 436 +- packages/effect/src/Redactable.ts | 13 +- packages/effect/src/Redacted.ts | 52 +- packages/effect/src/Reducer.ts | 23 +- packages/effect/src/Ref.ts | 224 +- packages/effect/src/References.ts | 316 +- packages/effect/src/RegExp.ts | 24 +- packages/effect/src/Request.ts | 64 +- packages/effect/src/RequestResolver.ts | 186 +- packages/effect/src/Result.ts | 389 +- packages/effect/src/Runtime.ts | 125 +- packages/effect/src/Schedule.ts | 3057 +- packages/effect/src/Scheduler.ts | 45 +- packages/effect/src/Schema.ts | 12316 ++--- packages/effect/src/SchemaAST.ts | 2536 +- packages/effect/src/SchemaGetter.ts | 650 +- packages/effect/src/SchemaIssue.ts | 957 +- packages/effect/src/SchemaParser.ts | 406 +- packages/effect/src/SchemaRepresentation.ts | 5285 +- packages/effect/src/SchemaTransformation.ts | 455 +- packages/effect/src/SchemaUtils.ts | 51 - packages/effect/src/Scope.ts | 253 +- packages/effect/src/ScopedCache.ts | 15 +- packages/effect/src/ScopedRef.ts | 6 +- packages/effect/src/Semaphore.ts | 165 +- packages/effect/src/Sink.ts | 271 +- packages/effect/src/StandardSchema.ts | 207 + packages/effect/src/Stdio.ts | 29 +- packages/effect/src/Stream.ts | 2885 +- packages/effect/src/String.ts | 486 +- packages/effect/src/Struct.ts | 140 +- packages/effect/src/SubscriptionRef.ts | 196 +- packages/effect/src/Symbol.ts | 6 +- packages/effect/src/SynchronizedRef.ts | 6 +- packages/effect/src/Terminal.ts | 12 +- packages/effect/src/Tracer.ts | 227 +- packages/effect/src/Trie.ts | 380 +- packages/effect/src/Tuple.ts | 114 +- packages/effect/src/TxChunk.ts | 150 +- packages/effect/src/TxDeferred.ts | 62 +- packages/effect/src/TxHashMap.ts | 644 +- packages/effect/src/TxHashSet.ts | 251 +- packages/effect/src/TxPriorityQueue.ts | 139 +- packages/effect/src/TxPubSub.ts | 151 +- packages/effect/src/TxQueue.ts | 346 +- packages/effect/src/TxReentrantLock.ts | 225 +- packages/effect/src/TxRef.ts | 47 +- packages/effect/src/TxSemaphore.ts | 166 +- packages/effect/src/TxSubscriptionRef.ts | 89 +- packages/effect/src/Types.ts | 225 +- packages/effect/src/Unify.ts | 20 +- packages/effect/src/Utils.ts | 43 +- packages/effect/src/index.ts | 20 +- .../effect/src/internal/arbitrary/model.ts | 701 + .../effect/src/internal/arbitrary/regexp.ts | 855 + .../effect/src/internal/arbitrary/runner.ts | 608 + .../effect/src/internal/arbitrary/schema.ts | 1800 + packages/effect/src/internal/core.ts | 83 +- packages/effect/src/internal/count.ts | 14 + packages/effect/src/internal/dateTime.ts | 26 +- packages/effect/src/internal/effect.ts | 1313 +- packages/effect/src/internal/executionPlan.ts | 116 +- packages/effect/src/internal/graph.ts | 237 + packages/effect/src/internal/graphCsr.ts | 279 + packages/effect/src/internal/hashMap.ts | 42 +- packages/effect/src/internal/hashSet.ts | 2 +- packages/effect/src/internal/matcher.ts | 279 +- packages/effect/src/internal/metric.ts | 2 +- packages/effect/src/internal/option.ts | 14 +- packages/effect/src/internal/persistence.ts | 2 + packages/effect/src/internal/random.ts | 27 +- packages/effect/src/internal/rcRef.ts | 53 +- packages/effect/src/internal/record.ts | 18 +- packages/effect/src/internal/references.ts | 11 +- packages/effect/src/internal/request.ts | 21 +- packages/effect/src/internal/result.ts | 26 +- packages/effect/src/internal/schedule.ts | 8 +- .../effect/src/internal/schema/annotations.ts | 49 +- .../effect/src/internal/schema/arbitrary.ts | 915 - .../internal/schema/fromJsonSchemaDocument.ts | 1190 + .../src/internal/schema/fromRepresentation.ts | 341 + packages/effect/src/internal/schema/make.ts | 38 + packages/effect/src/internal/schema/parser.ts | 30 + .../src/internal/schema/representation.ts | 795 - packages/effect/src/internal/schema/schema.ts | 128 - .../src/internal/schema/standardSchema.ts | 125 + .../src/internal/schema/toCodeDocument.ts | 578 + .../effect/src/internal/schema/toCodec.ts | 312 + .../src/internal/schema/toDifferJsonPatch.ts | 24 + .../src/internal/schema/toEncoderXml.ts | 110 + .../{equivalence.ts => toEquivalence.ts} | 98 +- .../effect/src/internal/schema/toFormatter.ts | 230 + packages/effect/src/internal/schema/toIso.ts | 20 + .../internal/schema/toJsonSchemaDocument.ts | 612 + .../src/internal/schema/toRepresentation.ts | 361 + packages/effect/src/internal/schemaError.ts | 8 + .../effect/src/internal/stackTraceLimit.ts | 10 +- packages/effect/src/internal/stream.ts | 20 +- packages/effect/src/internal/tracer.ts | 4 + packages/effect/src/internal/trie.ts | 40 +- packages/effect/src/internal/uuid.ts | 55 + packages/effect/src/internal/version.ts | 2 - packages/effect/src/testing/FastCheck.ts | 89 - packages/effect/src/testing/TestClock.ts | 139 +- packages/effect/src/testing/TestConsole.ts | 63 +- packages/effect/src/testing/TestSchema.ts | 138 +- packages/effect/src/testing/index.ts | 5 - packages/effect/src/unstable/ai/AiError.ts | 392 +- .../unstable/ai/AnthropicStructuredOutput.ts | 439 +- packages/effect/src/unstable/ai/Chat.ts | 253 +- .../effect/src/unstable/ai/EmbeddingModel.ts | 2 +- .../effect/src/unstable/ai/IdGenerator.ts | 60 +- .../effect/src/unstable/ai/LanguageModel.ts | 589 +- .../effect/src/unstable/ai/McpProtocol.ts | 146 + packages/effect/src/unstable/ai/McpSchema.ts | 745 +- packages/effect/src/unstable/ai/McpServer.ts | 1892 +- packages/effect/src/unstable/ai/Model.ts | 29 +- .../src/unstable/ai/OpenAiStructuredOutput.ts | 629 +- packages/effect/src/unstable/ai/Prompt.ts | 274 +- packages/effect/src/unstable/ai/Response.ts | 378 +- packages/effect/src/unstable/ai/Telemetry.ts | 49 +- packages/effect/src/unstable/ai/Tokenizer.ts | 26 +- packages/effect/src/unstable/ai/Tool.ts | 245 +- packages/effect/src/unstable/ai/Toolkit.ts | 142 +- packages/effect/src/unstable/ai/index.ts | 5 + .../unstable/ai/internal/codec-transformer.ts | 23 +- .../src/unstable/ai/internal/mcpCore.ts | 455 + .../src/unstable/ai/internal/mcpProtocol.ts | 350 + .../ai/internal/mcpProtocol/v2024_11_05.ts | 321 + .../ai/internal/mcpProtocol/v2025_03_26.ts | 347 + .../ai/internal/mcpProtocol/v2025_06_18.ts | 364 + .../ai/internal/mcpProtocol/v2025_11_25.ts | 417 + .../ai/internal/mcpProtocolRegistry.ts | 102 + .../ai/internal/mcpSchema/v2024_11_05.ts | 521 + .../ai/internal/mcpSchema/v2025_03_26.ts | 204 + .../ai/internal/mcpSchema/v2025_06_18.ts | 404 + .../ai/internal/mcpSchema/v2025_11_25.ts | 481 + .../unstable/ai/internal/structured-output.ts | 476 + .../src/unstable/arbitrary/Arbitrary.ts | 549 + .../effect/src/unstable/arbitrary/index.ts | 10 + packages/effect/src/unstable/cli/Argument.ts | 291 +- packages/effect/src/unstable/cli/CliConfig.ts | 85 + packages/effect/src/unstable/cli/CliError.ts | 275 +- packages/effect/src/unstable/cli/CliOutput.ts | 147 +- packages/effect/src/unstable/cli/Command.ts | 598 +- .../effect/src/unstable/cli/Completions.ts | 8 +- packages/effect/src/unstable/cli/Flag.ts | 447 +- .../effect/src/unstable/cli/GlobalFlag.ts | 106 +- packages/effect/src/unstable/cli/HelpDoc.ts | 16 +- packages/effect/src/unstable/cli/Param.ts | 519 +- packages/effect/src/unstable/cli/Primitive.ts | 646 +- packages/effect/src/unstable/cli/Prompt.ts | 988 +- packages/effect/src/unstable/cli/SEMANTICS.md | 157 - packages/effect/src/unstable/cli/index.ts | 5 + .../effect/src/unstable/cli/internal/ansi.ts | 7 +- .../src/unstable/cli/internal/command.ts | 43 +- .../unstable/cli/internal/completions/bash.ts | 217 +- .../cli/internal/completions/descriptor.ts | 68 +- .../unstable/cli/internal/completions/fish.ts | 38 +- .../unstable/cli/internal/completions/zsh.ts | 21 +- .../src/unstable/cli/internal/config.ts | 26 +- .../effect/src/unstable/cli/internal/help.ts | 6 +- .../effect/src/unstable/cli/internal/lexer.ts | 10 +- .../src/unstable/cli/internal/parser.ts | 28 +- .../src/unstable/cli/internal/wizard.ts | 311 + .../src/unstable/cluster/ClusterCron.ts | 7 +- .../src/unstable/cluster/ClusterError.ts | 18 +- .../src/unstable/cluster/ClusterSchema.ts | 36 +- .../unstable/cluster/ClusterWorkflowEngine.ts | 194 +- .../effect/src/unstable/cluster/Entity.ts | 60 +- .../src/unstable/cluster/EntityProxy.ts | 23 +- .../src/unstable/cluster/EntityProxyServer.ts | 38 +- .../src/unstable/cluster/EntityResource.ts | 10 +- .../effect/src/unstable/cluster/Envelope.ts | 44 +- .../effect/src/unstable/cluster/HttpRunner.ts | 87 +- .../src/unstable/cluster/K8sHttpClient.ts | 4 +- .../effect/src/unstable/cluster/K8sTypes.ts | 2467 + .../effect/src/unstable/cluster/Message.ts | 66 +- .../src/unstable/cluster/MessageStorage.ts | 226 +- packages/effect/src/unstable/cluster/Reply.ts | 74 +- .../effect/src/unstable/cluster/Runner.ts | 2 +- .../src/unstable/cluster/RunnerAddress.ts | 2 +- .../src/unstable/cluster/RunnerHealth.ts | 2 +- .../src/unstable/cluster/RunnerServer.ts | 75 +- .../src/unstable/cluster/RunnerStorage.ts | 22 +- .../effect/src/unstable/cluster/Runners.ts | 229 +- .../effect/src/unstable/cluster/ShardId.ts | 2 +- .../effect/src/unstable/cluster/Sharding.ts | 737 +- .../src/unstable/cluster/ShardingConfig.ts | 96 +- .../src/unstable/cluster/SingleRunner.ts | 6 +- .../src/unstable/cluster/SingletonAddress.ts | 2 +- .../effect/src/unstable/cluster/Snowflake.ts | 20 +- .../src/unstable/cluster/SocketRunner.ts | 9 +- .../src/unstable/cluster/SqlMessageStorage.ts | 292 +- .../src/unstable/cluster/SqlRunnerStorage.ts | 366 +- packages/effect/src/unstable/cluster/index.ts | 5 + .../cluster/internal/clusterAbandon.ts | 14 + .../cluster/internal/entityManager.ts | 232 +- .../unstable/cluster/internal/entityReaper.ts | 5 +- .../unstable/cluster/internal/interruptors.ts | 59 +- .../unstable/cluster/internal/resourceMap.ts | 99 +- .../unstable/cluster/internal/resourceRef.ts | 54 +- .../unstable/cluster/internal/shardLock.ts | 9 + .../src/unstable/devtools/DevToolsClient.ts | 15 +- .../src/unstable/devtools/DevToolsSchema.ts | 15 +- packages/effect/src/unstable/encoding/Ini.ts | 176 + .../effect/src/unstable/encoding/Msgpack.ts | 385 - .../effect/src/unstable/encoding/Ndjson.ts | 10 +- .../src/unstable/encoding/SchemaBinary.ts | 5263 ++ packages/effect/src/unstable/encoding/Sse.ts | 154 +- packages/effect/src/unstable/encoding/Toml.ts | 506 + packages/effect/src/unstable/encoding/Yaml.ts | 554 + .../effect/src/unstable/encoding/index.ts | 17 +- .../effect/src/unstable/eventlog/Event.ts | 54 +- .../src/unstable/eventlog/EventGroup.ts | 8 +- .../src/unstable/eventlog/EventJournal.ts | 95 +- .../effect/src/unstable/eventlog/EventLog.ts | 73 +- .../unstable/eventlog/EventLogEncryption.ts | 35 +- .../src/unstable/eventlog/EventLogMessage.ts | 64 +- .../src/unstable/eventlog/EventLogRemote.ts | 29 +- .../src/unstable/eventlog/EventLogServer.ts | 48 +- .../eventlog/EventLogServerEncrypted.ts | 24 +- .../eventlog/EventLogServerUnencrypted.ts | 26 +- .../unstable/eventlog/EventLogSessionAuth.ts | 2 +- .../src/unstable/eventlog/SqlEventJournal.ts | 59 +- .../eventlog/SqlEventLogServerEncrypted.ts | 2 +- .../eventlog/SqlEventLogServerUnencrypted.ts | 19 +- packages/effect/src/unstable/http/Cookies.ts | 181 +- packages/effect/src/unstable/http/Etag.ts | 2 +- .../src/unstable/http/FetchHttpClient.ts | 8 +- .../effect/src/unstable/http/FindMyWay.ts | 81 +- .../http/FindMyWay/internal/queryString.ts | 420 + .../http/FindMyWay/internal/router.ts | 1056 + packages/effect/src/unstable/http/Headers.ts | 80 +- packages/effect/src/unstable/http/HttpBody.ts | 148 +- .../effect/src/unstable/http/HttpClient.ts | 254 +- .../src/unstable/http/HttpClientError.ts | 2 +- .../src/unstable/http/HttpClientRequest.ts | 98 +- .../src/unstable/http/HttpClientResponse.ts | 49 +- .../effect/src/unstable/http/HttpEffect.ts | 261 +- .../src/unstable/http/HttpIncomingMessage.ts | 40 +- .../effect/src/unstable/http/HttpMethod.ts | 15 +- .../src/unstable/http/HttpMiddleware.ts | 300 +- .../effect/src/unstable/http/HttpPlatform.ts | 207 +- .../effect/src/unstable/http/HttpRouter.ts | 265 +- .../effect/src/unstable/http/HttpServer.ts | 87 +- .../src/unstable/http/HttpServerError.ts | 4 +- .../src/unstable/http/HttpServerRequest.ts | 121 +- .../unstable/http/HttpServerRespondable.ts | 2 +- .../src/unstable/http/HttpServerResponse.ts | 109 +- .../src/unstable/http/HttpStaticServer.ts | 96 +- .../effect/src/unstable/http/HttpStatus.ts | 100 + packages/effect/src/unstable/http/Mime.ts | 76 + .../effect/src/unstable/http/Multipart.ts | 171 +- .../src/unstable/http/MultipartParser.ts | 116 + .../http/MultipartParser/HeadersParser.ts | 68 + .../unstable/http/MultipartParser/Search.ts | 17 + .../MultipartParser/internal/contentType.ts | 103 + .../http/MultipartParser/internal/headers.ts | 244 + .../MultipartParser/internal/multipart.ts | 286 + .../http/MultipartParser/internal/search.ts | 167 + .../effect/src/unstable/http/Multipasta.ts | 15 - .../unstable/http/Multipasta/HeadersParser.ts | 14 - .../src/unstable/http/Multipasta/Node.ts | 14 - .../src/unstable/http/Multipasta/Search.ts | 14 - .../src/unstable/http/Multipasta/Web.ts | 14 - packages/effect/src/unstable/http/Template.ts | 13 +- packages/effect/src/unstable/http/Url.ts | 111 +- .../effect/src/unstable/http/UrlParams.ts | 253 +- packages/effect/src/unstable/http/index.ts | 12 +- .../src/unstable/http/internal/compression.ts | 193 + .../src/unstable/http/internal/headers.ts | 7 + .../src/unstable/http/internal/httpBody.ts | 24 + .../src/unstable/http/internal/mimeTypes.ts | 395 + .../effect/src/unstable/httpapi/HttpApi.ts | 140 +- .../src/unstable/httpapi/HttpApiBuilder.ts | 826 +- .../src/unstable/httpapi/HttpApiClient.ts | 588 +- .../src/unstable/httpapi/HttpApiEndpoint.ts | 963 +- .../src/unstable/httpapi/HttpApiError.ts | 117 +- .../src/unstable/httpapi/HttpApiGroup.ts | 197 +- .../src/unstable/httpapi/HttpApiMiddleware.ts | 124 +- .../src/unstable/httpapi/HttpApiScalar.ts | 74 +- .../src/unstable/httpapi/HttpApiSchema.ts | 456 +- .../src/unstable/httpapi/HttpApiSwagger.ts | 13 +- .../src/unstable/httpapi/HttpApiTest.ts | 36 +- .../effect/src/unstable/httpapi/OpenApi.ts | 366 +- .../src/unstable/httpapi/internal/html.ts | 11 +- .../unstable/httpapi/internal/mediaType.ts | 6 + .../effect/src/unstable/net/IpInterface.ts | 242 + packages/effect/src/unstable/net/IpNetwork.ts | 321 + .../effect/src/unstable/net/NetAddress.ts | 1254 + packages/effect/src/unstable/net/index.ts | 20 + .../unstable/observability/OtlpExporter.ts | 155 +- .../src/unstable/observability/OtlpLogger.ts | 32 +- .../src/unstable/observability/OtlpMetrics.ts | 80 +- .../unstable/observability/OtlpResource.ts | 50 +- .../src/unstable/observability/OtlpTracer.ts | 213 +- .../observability/PrometheusMetrics.ts | 17 +- .../observability/internal/otlpEnv.ts | 23 +- .../observability/internal/protobuf.ts | 3 + .../src/unstable/persistence/KeyValueStore.ts | 121 +- .../src/unstable/persistence/Persistable.ts | 21 +- .../unstable/persistence/PersistedCache.ts | 2 +- .../unstable/persistence/PersistedQueue.ts | 1626 +- .../src/unstable/persistence/Persistence.ts | 179 +- .../src/unstable/persistence/RateLimiter.ts | 246 +- .../effect/src/unstable/persistence/Redis.ts | 77 +- .../src/unstable/process/ChildProcess.ts | 62 +- .../unstable/process/ChildProcessSpawner.ts | 35 +- .../src/unstable/reactivity/AsyncResult.ts | 41 +- .../effect/src/unstable/reactivity/Atom.ts | 182 +- .../src/unstable/reactivity/AtomHttpApi.ts | 354 +- .../src/unstable/reactivity/AtomRegistry.ts | 114 +- .../effect/src/unstable/reactivity/AtomRpc.ts | 20 +- .../src/unstable/reactivity/Hydration.ts | 35 +- .../src/unstable/reactivity/Reactivity.ts | 5 +- packages/effect/src/unstable/rpc/Rpc.ts | 68 +- packages/effect/src/unstable/rpc/RpcClient.ts | 289 +- .../effect/src/unstable/rpc/RpcClientError.ts | 4 +- packages/effect/src/unstable/rpc/RpcGroup.ts | 18 +- .../effect/src/unstable/rpc/RpcMessage.ts | 171 +- .../effect/src/unstable/rpc/RpcMiddleware.ts | 32 +- packages/effect/src/unstable/rpc/RpcSchema.ts | 8 +- .../src/unstable/rpc/RpcSerialization.ts | 384 +- packages/effect/src/unstable/rpc/RpcServer.ts | 365 +- packages/effect/src/unstable/rpc/RpcWorker.ts | 10 +- packages/effect/src/unstable/schema/Model.ts | 133 +- .../src/unstable/schema/VariantSchema.ts | 97 +- packages/effect/src/unstable/socket/Socket.ts | 1072 +- .../src/unstable/socket/SocketServer.ts | 40 +- packages/effect/src/unstable/sql/Migrator.ts | 31 +- packages/effect/src/unstable/sql/SqlClient.ts | 183 +- .../effect/src/unstable/sql/SqlConnection.ts | 18 + packages/effect/src/unstable/sql/SqlError.ts | 30 +- packages/effect/src/unstable/sql/SqlModel.ts | 6 +- .../effect/src/unstable/sql/SqlResolver.ts | 73 +- packages/effect/src/unstable/sql/Statement.ts | 145 +- .../src/unstable/workers/Transferable.ts | 2 +- .../effect/src/unstable/workers/Worker.ts | 35 +- .../src/unstable/workers/WorkerError.ts | 24 +- .../src/unstable/workers/WorkerRunner.ts | 2 +- .../effect/src/unstable/workflow/Activity.ts | 44 +- .../src/unstable/workflow/DurableClock.ts | 4 +- .../src/unstable/workflow/DurableDeferred.ts | 31 +- .../src/unstable/workflow/DurableQueue.ts | 57 +- .../effect/src/unstable/workflow/Workflow.ts | 150 +- .../src/unstable/workflow/WorkflowEngine.ts | 158 +- .../src/unstable/workflow/WorkflowProxy.ts | 18 +- .../unstable/workflow/WorkflowProxyServer.ts | 23 +- packages/effect/test/Array.test.ts | 102 +- packages/effect/test/ArrayNaNIndex.test.ts | 40 + packages/effect/test/BigDecimal.test.ts | 53 +- packages/effect/test/BigInt.test.ts | 81 +- packages/effect/test/Brand.test.ts | 24 +- packages/effect/test/ByteSize.test.ts | 176 + packages/effect/test/Cache.test.ts | 262 +- packages/effect/test/Cause.test.ts | 29 + .../effect/test/CauseMapAnnotations.test.ts | 15 + packages/effect/test/Channel.test.ts | 127 + packages/effect/test/Chunk.test.ts | 42 +- packages/effect/test/Clock.test.ts | 37 + packages/effect/test/Config.test.ts | 2558 +- packages/effect/test/ConfigProvider.test.ts | 917 +- packages/effect/test/Context.test.ts | 218 + packages/effect/test/Cron.test.ts | 371 +- packages/effect/test/Crypto.test.ts | 40 +- packages/effect/test/DateTime.test.ts | 46 +- packages/effect/test/Deferred.test.ts | 78 +- packages/effect/test/Duration.test.ts | 37 +- packages/effect/test/Effect.test.ts | 1087 +- packages/effect/test/EffectEager.test.ts | 13 + packages/effect/test/EffectKeepAlive.test.ts | 2 +- packages/effect/test/Effectable.test.ts | 103 + packages/effect/test/Encoding.test.ts | 32 + packages/effect/test/Equal.test.ts | 38 + packages/effect/test/ExecutionPlan.test.ts | 466 +- packages/effect/test/Fiber.test.ts | 215 +- packages/effect/test/FiberHandle.test.ts | 62 + packages/effect/test/FiberMap.test.ts | 80 + packages/effect/test/FiberSet.test.ts | 6 + .../test/FiberSetRuntimePropagation.test.ts | 16 + packages/effect/test/FileSystem.test-utils.ts | 565 + packages/effect/test/Formatter.test.ts | 512 +- packages/effect/test/Function.test.ts | 29 + packages/effect/test/Graph.test.ts | 5478 +- packages/effect/test/HashMap.test.ts | 28 +- packages/effect/test/HashRing.test.ts | 82 + packages/effect/test/HttpClient.test.ts | 145 +- packages/effect/test/Iterable.test.ts | 60 + packages/effect/test/JsonPatch.test.ts | 77 + packages/effect/test/JsonPointer.test.ts | 84 +- packages/effect/test/JsonSchema.test.ts | 2556 +- packages/effect/test/Latch.test.ts | 216 +- packages/effect/test/Layer.test.ts | 43 +- packages/effect/test/LayerMap.test.ts | 57 +- packages/effect/test/LayerRef.test.ts | 131 + packages/effect/test/Logger.test.ts | 50 + packages/effect/test/ManagedRuntime.test.ts | 15 + packages/effect/test/Match.test.ts | 45 + packages/effect/test/Metric.test.ts | 259 +- packages/effect/test/Migrator.test.ts | 61 +- packages/effect/test/MutableList.test.ts | 63 + packages/effect/test/Number.test.ts | 67 +- packages/effect/test/Optic.test.ts | 302 +- packages/effect/test/Order.test.ts | 6 + packages/effect/test/Ordering.test.ts | 23 + .../effect/test/PartitionedSemaphore.test.ts | 63 +- packages/effect/test/Pathfinding.test.ts | 15 +- packages/effect/test/Pool.test.ts | 340 + packages/effect/test/PubSub.test.ts | 272 +- packages/effect/test/Pull.test.ts | 40 + packages/effect/test/Queue.test.ts | 280 + packages/effect/test/Random.test.ts | 45 + packages/effect/test/RcMap.test.ts | 315 +- packages/effect/test/RcRef.test.ts | 266 +- packages/effect/test/Record.test.ts | 21 +- packages/effect/test/Request.test.ts | 473 +- packages/effect/test/Result.test.ts | 10 +- packages/effect/test/Schedule.test.ts | 364 +- packages/effect/test/Scheduler.test.ts | 57 +- packages/effect/test/ScopedCache.test.ts | 223 +- packages/effect/test/ScopedRef.test.ts | 51 +- packages/effect/test/Semaphore.test.ts | 177 + packages/effect/test/Sink.test.ts | 141 +- packages/effect/test/StackCapture.test.ts | 57 + packages/effect/test/Stdio.test.ts | 22 + packages/effect/test/Stream.test.ts | 831 +- packages/effect/test/String.test.ts | 62 +- packages/effect/test/Struct.test.ts | 32 + packages/effect/test/SubscriptionRef.test.ts | 18 +- .../SynchronizedRef.modifySomeEffect.test.ts | 26 + packages/effect/test/SynchronizedRef.test.ts | 9 +- packages/effect/test/TestClock.test.ts | 71 + packages/effect/test/Tracer.test.ts | 114 +- packages/effect/test/Trie.test.ts | 47 + packages/effect/test/Tuple.test.ts | 32 +- packages/effect/test/TxPubSub.test.ts | 40 +- packages/effect/test/TxQueue.test.ts | 55 + packages/effect/test/TxReentrantLock.test.ts | 28 +- .../cluster/ClusterWorkflowEngine.test.ts | 1471 +- packages/effect/test/cluster/Entity.test.ts | 91 +- packages/effect/test/cluster/Envelope.test.ts | 58 + .../test/cluster/MessageStorage.test.ts | 242 +- .../effect/test/cluster/ResourceMap.test.ts | 18 + .../effect/test/cluster/ResourceRef.test.ts | 107 + .../effect/test/cluster/RunnerServer.test.ts | 170 + .../effect/test/cluster/RunnerStorage.test.ts | 31 + packages/effect/test/cluster/Runners.test.ts | 474 + packages/effect/test/cluster/Sharding.test.ts | 1541 +- .../test/cluster/ShardingConfig.test.ts | 79 + packages/effect/test/cluster/TestEntity.ts | 29 +- .../effect/test/cluster/interruptors.test.ts | 37 + .../test/fixtures/migrator/0001_first.js | 1 + .../test/fixtures/text.txt | 0 packages/effect/test/internal/Uuid.test.ts | 26 + packages/effect/test/reactivity/Atom.test.ts | 377 +- .../test/reactivity/AtomHttpApi.test.ts | 196 +- .../effect/test/reactivity/AtomRpc.test.ts | 175 +- packages/effect/test/rpc/Rpc.test.ts | 2 +- packages/effect/test/rpc/RpcClient.test.ts | 258 +- .../effect/test/rpc/RpcSerialization.test.ts | 535 +- packages/effect/test/rpc/RpcServer.test.ts | 176 + packages/effect/test/schema/Graph.test.ts | 329 + packages/effect/test/schema/HMR.test.ts | 3 + packages/effect/test/schema/Schema.test.ts | 2580 +- packages/effect/test/schema/SchemaAST.test.ts | 325 +- .../effect/test/schema/SchemaGetter.test.ts | 145 +- .../effect/test/schema/SchemaIssue.test.ts | 71 +- .../effect/test/schema/SchemaParser.test.ts | 467 +- .../test/schema/SchemaTransformation.test.ts | 10 + .../test/schema/TemplateLiteral.test.ts | 183 + .../test/schema/jsonSchemaRoundTrip.test.ts | 265 + .../representation/builtInRevivers.test.ts | 1094 + .../schema/representation/fromASTs.test.ts | 1109 - .../schema/representation/fromJson.test.ts | 436 + .../fromJsonMultiDocument.test.ts | 48 + .../fromJsonSchemaDocument.test.ts | 5774 +- .../fromJsonSchemaMultiDocument.test.ts | 254 + .../representation/fromRepresentation.test.ts | 488 + .../fromRepresentations.test.ts | 170 + .../schema/representation/makeCode.test.ts | 11 + .../schemaToJsonSchemaDocument.test.ts | 221 + .../stringTreeRoundtrip.test.ts | 72 + .../toCodeDocument.annotations.test.ts | 598 + .../representation/toCodeDocument.test.ts | 679 +- .../test/schema/representation/toJson.test.ts | 607 + .../toJsonMultiDocument.test.ts | 40 + .../toJsonSchemaDocument.test.ts | 1149 + .../toJsonSchemaMultiDocument.test.ts | 750 +- .../representation/toMultiDocument.test.ts | 18 + .../representation/toRepresentation.test.ts | 780 + .../representation/toRepresentations.test.ts | 290 + .../schema/representation/toSchema.test.ts | 506 - .../effect/test/schema/toArbitrary.test.ts | 1708 - packages/effect/test/schema/toCodec.test.ts | 464 +- .../test/schema/toDifferJsonPatch.test.ts | 189 +- .../effect/test/schema/toEquivalence.test.ts | 121 +- .../effect/test/schema/toFormatter.test.ts | 71 +- packages/effect/test/schema/toIso.test.ts | 49 +- .../test/schema/toJsonSchemaDocument.test.ts | 544 +- .../schema/toStandardJSONSchemaV1.test.ts | 2 +- .../test/schema/toStandardSchemaV1.test.ts | 47 +- packages/effect/test/schema/v3-v4.test.ts | 16 +- .../effect/test/testing/TestSchema.test.ts | 120 +- .../effect/test/unstable/ai/AiError.test.ts | 57 +- .../ai/AnthropicStructuredOutput.test.ts | 120 +- ...opicStructuredOutputRepresentation.test.ts | 147 + packages/effect/test/unstable/ai/Chat.test.ts | 53 +- .../test/unstable/ai/EmbeddingModel.test.ts | 14 +- .../test/unstable/ai/LanguageModel.test.ts | 1409 +- .../ai/LanguageModelRepresentation.test.ts | 24 + .../ai/LanguageModelTrackerLifecycle.test.ts | 3 +- .../effect/test/unstable/ai/McpSchema.test.ts | 15 + .../effect/test/unstable/ai/McpServer.test.ts | 82 - .../McpConformance/BaseProtocolTest.ts | 299 + .../McpConformance/CompletionTest.ts | 170 + .../McpConformance/ElicitationTest.ts | 331 + .../McpServer/McpConformance/LifecycleTest.ts | 147 + .../McpServer/McpConformance/LoggingTest.ts | 203 + .../McpConformance/McpConformance.ts | 247 + .../McpConformance/McpConformanceFixtures.ts | 308 + .../McpServer/McpConformance/McpTestPeer.ts | 131 + .../McpServer/McpConformance/PromptsTest.ts | 388 + .../McpServer/McpConformance/ResourcesTest.ts | 439 + .../ai/McpServer/McpConformance/RootsTest.ts | 134 + .../McpServer/McpConformance/SamplingTest.ts | 467 + .../ai/McpServer/McpConformance/ToolsTest.ts | 433 + .../McpConformance/TransportsTest.ts | 451 + .../McpServer/McpConformance/UtilitiesTest.ts | 223 + .../unstable/ai/McpServer/McpProtocol.test.ts | 200 + .../unstable/ai/McpServer/McpServer.test.ts | 1006 + .../ai/McpServer/ProtocolAdapters.test.ts | 1290 + .../ai/McpServer/TestUtils/McpHttpHarness.ts | 60 + .../ai/McpServer/TestUtils/McpServerLayer.ts | 35 + .../ai/McpServer/TestUtils/McpStdioHarness.ts | 213 + .../unstable/ai/McpServer/v2024_11_05.test.ts | 44 + .../unstable/ai/McpServer/v2025_03_26.test.ts | 134 + .../unstable/ai/McpServer/v2025_06_18.test.ts | 30 + .../unstable/ai/McpServer/v2025_11_25.test.ts | 53 + .../ai/OpenAiStructuredOutput.test.ts | 159 +- ...enAiStructuredOutputRepresentation.test.ts | 250 + .../effect/test/unstable/ai/Prompt.test.ts | 288 + .../effect/test/unstable/ai/Response.test.ts | 304 + .../unstable/ai/ResponseIdTracker.test.ts | 3 +- .../effect/test/unstable/ai/Tokenizer.test.ts | 24 + packages/effect/test/unstable/ai/Tool.test.ts | 238 +- .../unstable/ai/ToolRepresentation.test.ts | 35 + packages/effect/test/unstable/ai/utils.ts | 48 +- .../test/unstable/arbitrary/Arbitrary.test.ts | 3639 ++ .../effect/test/unstable/cli/Ansi.test.ts | 10 + .../test/unstable/cli/Arguments.test.ts | 114 +- .../effect/test/unstable/cli/Command.test.ts | 713 +- .../effect/test/unstable/cli/Errors.test.ts | 303 +- .../effect/test/unstable/cli/Help.test.ts | 175 +- .../effect/test/unstable/cli/Lexer.test.ts | 15 + .../effect/test/unstable/cli/LogLevel.test.ts | 2 +- .../effect/test/unstable/cli/Param.test.ts | 271 +- .../test/unstable/cli/Primitive.test.ts | 149 +- .../effect/test/unstable/cli/Prompt.test.ts | 798 +- .../cli/completions/completions.test.ts | 329 +- .../cli/completions/descriptor.test.ts | 72 +- .../unstable/cli/fixtures/ComprehensiveCli.ts | 143 +- .../unstable/cli/services/MockTerminal.ts | 14 +- .../unstable/devtools/DevToolsClient.test.ts | 70 + .../effect/test/unstable/encoding/Ini.test.ts | 48 + .../test/unstable/encoding/Ndjson.test.ts | 16 + .../unstable/encoding/SchemaBinary.test.ts | 3349 ++ .../effect/test/unstable/encoding/Sse.test.ts | 155 + .../test/unstable/encoding/Toml.test.ts | 84 + .../test/unstable/encoding/Yaml.test.ts | 111 + .../unstable/eventlog/EventJournal.test.ts | 91 +- .../test/unstable/eventlog/EventLog.test.ts | 118 +- .../unstable/eventlog/EventLogMessage.test.ts | 34 + .../unstable/eventlog/EventLogRemote.test.ts | 38 + .../EventLogServerUnencrypted.test.ts | 117 + .../effect/test/unstable/http/Cookies.test.ts | 92 +- .../unstable/http/FetchHttpClient.test.ts | 25 + .../http/FindMyWay/case-insensitive.test.ts | 153 + .../http/FindMyWay/matching-order.test.ts | 16 + .../unstable/http/FindMyWay/methods.test.ts | 16 + .../http/FindMyWay/optional-params.test.ts | 119 + .../http/FindMyWay/params-collisions.test.ts | 101 + .../http/FindMyWay/path-params-match.test.ts | 61 + .../http/FindMyWay/querystring.test.ts | 38 + .../unstable/http/FindMyWay/regex.test.ts | 124 + .../effect/test/unstable/http/Headers.test.ts | 42 +- .../test/unstable/http/HttpBody.test.ts | 108 + .../test/unstable/http/HttpClient.test.ts | 570 +- .../unstable/http/HttpClientRequest.test.ts | 131 +- .../unstable/http/HttpCompression.test.ts | 429 + .../test/unstable/http/HttpEffect.test.ts | 129 +- .../test/unstable/http/HttpMiddleware.test.ts | 194 + .../test/unstable/http/HttpPlatform.test.ts | 122 + .../test/unstable/http/HttpRouter.test.ts | 89 + .../test/unstable/http/HttpServer.test.ts | 99 + .../unstable/http/HttpServerRequest.test.ts | 77 +- .../http/HttpServerRespondable.test.ts | 22 + .../unstable/http/HttpServerResponse.test.ts | 71 +- .../unstable/http/HttpStaticServer.test.ts | 110 + .../test/unstable/http/HttpStatus.test.ts | 16 + .../effect/test/unstable/http/Mime.test.ts | 40 + .../test/unstable/http/Multipart.test.ts | 383 +- .../http/SchemaRepresentation.test.ts | 48 + .../test/unstable/http/Template.test.ts | 38 + .../effect/test/unstable/http/Url.test.ts | 31 +- .../test/unstable/http/UrlParams.test.ts | 50 +- .../test/unstable/httpapi/HttpApi.test.ts | 96 + .../unstable/httpapi/HttpApiBuilder.test.ts | 1355 +- .../unstable/httpapi/HttpApiClient.test.ts | 477 +- .../httpapi/HttpApiDocumentation.test.ts | 334 + .../unstable/httpapi/HttpApiEndpoint.test.ts | 336 +- .../unstable/httpapi/HttpApiSchema.test.ts | 184 + .../unstable/httpapi/HttpApiSecurity.test.ts | 45 +- .../test/unstable/httpapi/OpenApi.test.ts | 607 +- .../httpapi/OpenApiRepresentation.test.ts | 180 + .../test/unstable/net/IpInterface.test.ts | 109 + .../test/unstable/net/IpNetwork.test.ts | 284 + .../test/unstable/net/NetAddress.test.ts | 593 + .../observability/OtlpEnvHeaders.test.ts | 19 + .../observability/OtlpExporter.test.ts | 524 +- .../observability/OtlpMetrics.test.ts | 142 +- .../observability/OtlpResource.test.ts | 76 +- .../observability/OtlpSerialization.test.ts | 10 + .../persistence/KeyValueStore.test.ts | 199 +- .../unstable/persistence/KeyValueStoreTest.ts | 3 +- .../persistence/PersistedCacheTest.ts | 37 +- .../persistence/PersistedQueue.test.ts | 75 + .../persistence/PersistedQueueTest.ts | 317 +- .../unstable/persistence/RateLimiter.test.ts | 117 + .../unstable/persistence/RateLimiterTest.ts | 311 + .../test/unstable/persistence/Redis.test.ts | 183 +- .../unstable/persistence/SqlCleanupTest.ts | 22 + .../unstable/process/ChildProcess.test.ts | 22 +- .../process/ChildProcessSpawnerTest.ts | 1170 + .../process}/fixtures/bash/no-permissions.sh | 0 .../fixtures/bash/parent-exits-early.sh | 8 +- .../process}/fixtures/bash/spawn-children.sh | 4 +- .../process}/fixtures/config/SHOUTING | 0 .../unstable/process}/fixtures/config/integer | 0 .../process}/fixtures/config/nested/config | 0 .../unstable/process}/fixtures/config/secret | 0 .../unstable/schema/VariantSchema.test.ts | 78 + .../test/unstable/socket/Socket.test.ts | 219 + .../test/unstable/sql/SqlClient.test.ts | 178 + .../test/unstable/sql/SqlResolver.test.ts | 118 +- .../test/unstable/sql/Statement.test.ts | 126 + .../test/unstable/workers/Worker.test.ts | 91 + .../test/unstable/workers/WorkerError.test.ts | 42 +- .../unstable/workflow/WorkflowEngine.test.ts | 451 +- packages/effect/test/utils/assert.ts | 31 +- packages/effect/tsconfig.json | 2 +- packages/effect/typeperf/README.md | 60 +- packages/effect/typeperf/compare.mjs | 390 + packages/effect/typeperf/compare/httpapi.mjs | 269 + packages/effect/typeperf/config.json | 67 + packages/effect/typeperf/run.mjs | 76 +- .../typeperf/suites/httpapi/baseline.ts | 8 + .../fixtures/_endpoint-selection-types.ts | 5 + .../httpapi/fixtures/_grouped-api-500.ts | 2022 + .../httpapi/fixtures/_grouped-api-types.ts | 41 + .../fixtures/builder-endpoint-count-500.ts | 25 + .../fixtures/builder-handle-all-count-500.ts | 4529 ++ ...uilder-handle-all-two-batches-count-500.ts | 4532 ++ .../fixtures/builder-handlers-count-100.ts | 928 + .../builder-raw-handlers-count-100.ts | 928 + .../fixtures/client-endpoint-count-500.ts | 26 + .../fixtures/client-group-count-500.ts | 17 + .../fixtures/client-groups-100x5-count-500.ts | 30 + .../fixtures/client-methods-count-500.ts | 20 + .../fixtures/client-top-level-count-500.ts | 19 + .../fixtures/endpoint-class-count-500.ts | 3031 ++ .../httpapi/fixtures/endpoint-count-500.ts | 2031 + .../fixtures/group-endpoints-count-500.ts | 14 + .../httpapi/fixtures/url-builder-count-500.ts | 20 + .../url-builder-top-level-count-500.ts | 20 + .../typeperf/suites/httpapi/thresholds.json | 62 + .../typeperf/suites/schema/thresholds.json | 66 +- packages/effect/typetest/Array.tst.ts | 28 +- packages/effect/typetest/ByteSize.tst.ts | 37 + packages/effect/typetest/Channel.tst.ts | 20 +- packages/effect/typetest/ChannelSchema.tst.ts | 32 + packages/effect/typetest/Clock.tst.ts | 13 + packages/effect/typetest/Config.tst.ts | 23 +- .../effect/typetest/ConfigProvider.tst.ts | 20 + packages/effect/typetest/Context.tst.ts | 18 + packages/effect/typetest/Effect.tst.ts | 389 +- packages/effect/typetest/Effectable.tst.ts | 72 + packages/effect/typetest/Fiber.tst.ts | 13 + packages/effect/typetest/FileSystem.tst.ts | 17 + packages/effect/typetest/Function.tst.ts | 26 + packages/effect/typetest/Graph.tst.ts | 382 + packages/effect/typetest/Iterable.tst.ts | 46 + packages/effect/typetest/Layer.tst.ts | 23 + packages/effect/typetest/Match.tst.ts | 289 + packages/effect/typetest/Optic.tst.ts | 129 +- packages/effect/typetest/Record.tst.ts | 89 + packages/effect/typetest/Schedule.tst.ts | 65 + packages/effect/typetest/Sink.tst.ts | 13 + packages/effect/typetest/Stream.tst.ts | 52 +- .../effect/typetest/SynchronizedRef.tst.ts | 30 + packages/effect/typetest/TestSchema.tst.ts | 20 + packages/effect/typetest/Tuple.tst.ts | 13 + packages/effect/typetest/Types.tst.ts | 44 + packages/effect/typetest/VariantSchema.tst.ts | 27 + packages/effect/typetest/schema/Array.tst.ts | 18 +- .../typetest/schema/FromJsonSchema.tst.ts | 49 + packages/effect/typetest/schema/Graph.tst.ts | 63 + packages/effect/typetest/schema/Schema.tst.ts | 176 +- .../schema/SchemaBuiltInRevivers.tst.ts | 98 + .../schema/SchemaJsonSchemaConsumer.tst.ts | 22 + .../schema/SchemaRepresentation.tst.ts | 95 + .../SchemaRepresentationCompilers.tst.ts | 49 + .../schema/SchemaRepresentationReviver.tst.ts | 128 + packages/effect/typetest/schema/Struct.tst.ts | 25 + packages/effect/typetest/schema/Union.tst.ts | 76 + .../effect/typetest/schema/toArbitrary.tst.ts | 65 - packages/effect/typetest/schema/toIso.tst.ts | 29 +- .../typetest/unstable/ai/LanguageModel.tst.ts | 174 +- .../typetest/unstable/ai/McpServer.tst.ts | 131 + .../typetest/unstable/ai/Response.tst.ts | 71 + .../effect/typetest/unstable/ai/Tool.tst.ts | 28 +- .../unstable/arbitrary/Arbitrary.tst.ts | 103 + .../typetest/unstable/cli/Command.tst.ts | 75 +- .../effect/typetest/unstable/cli/Param.tst.ts | 12 +- .../typetest/unstable/cli/Prompt.tst.ts | 42 +- .../unstable/cluster/EntityProxyServer.tst.ts | 41 + .../unstable/cluster/K8sHttpClient.tst.ts | 55 + .../typetest/unstable/cluster/Reply.tst.ts | 21 + .../typetest/unstable/http/HttpClient.tst.ts | 20 +- .../unstable/http/HttpPlatform.tst.ts | 10 + .../typetest/unstable/http/HttpRouter.tst.ts | 86 + .../typetest/unstable/httpapi/HttpApi.tst.ts | 61 + .../unstable/httpapi/HttpApiBuilder.tst.ts | 729 +- .../unstable/httpapi/HttpApiClient.tst.ts | 870 +- .../unstable/httpapi/HttpApiEndpoint.tst.ts | 451 +- .../unstable/httpapi/HttpApiGroup.tst.ts | 125 + .../unstable/httpapi/HttpApiMiddleware.tst.ts | 43 +- .../unstable/httpapi/HttpApiSchema.tst.ts | 77 +- .../httpapi/OpenApiRepresentation.tst.ts | 12 + .../typetest/unstable/net/IpInterface.tst.ts | 36 + .../typetest/unstable/net/IpNetwork.tst.ts | 73 + .../typetest/unstable/net/NetAddress.tst.ts | 119 + .../typetest/unstable/reactivity/Atom.tst.ts | 23 + .../unstable/reactivity/AtomHttpApi.tst.ts | 99 +- .../unstable/reactivity/AtomRpc.tst.ts | 39 + .../typetest/unstable/socket/Socket.tst.ts | 59 + packages/effect/vitest.config.ts | 11 - packages/opentelemetry/CHANGELOG.md | 175 + packages/opentelemetry/README.md | 15 +- packages/opentelemetry/docgen.json | 30 - packages/opentelemetry/package.json | 58 +- packages/opentelemetry/src/NodeSdk.ts | 20 +- .../src/{Logger.ts => OtelLogger.ts} | 10 +- .../src/{Metrics.ts => OtelMetrics.ts} | 55 +- .../src/{Tracer.ts => OtelTracer.ts} | 64 +- packages/opentelemetry/src/Resource.ts | 17 +- packages/opentelemetry/src/WebSdk.ts | 23 +- packages/opentelemetry/src/index.ts | 10 +- .../opentelemetry/src/internal/attributes.ts | 3 +- .../opentelemetry/src/internal/metrics.ts | 19 +- .../{Logger.test.ts => OtelLogger.test.ts} | 76 +- .../{Metrics.test.ts => OtelMetrics.test.ts} | 27 + .../{Tracer.test.ts => OtelTracer.test.ts} | 144 +- packages/opentelemetry/tsconfig.json | 2 +- packages/opentelemetry/vitest.config.ts | 6 - packages/platform-browser/README.md | 1 - packages/platform-browser/docgen.json | 21 - .../test/BrowserCrypto.test.ts | 74 - .../test/BrowserKeyValueStore.test.ts | 23 - packages/platform-browser/tsconfig.json | 8 - packages/platform-browser/vitest.config.ts | 17 - packages/platform-bun/README.md | 7 - packages/platform-bun/docgen.json | 20 - packages/platform-bun/tsconfig.json | 12 - packages/platform-node-shared/README.md | 7 - packages/platform-node-shared/docgen.json | 38 - .../platform-node-shared/src/NodeSocket.ts | 269 - .../test/NodeChildProcessSpawner.test.ts | 1069 - .../test/NodeFileSystem.test.ts | 214 - .../test/NodeSink.test.ts | 153 - packages/platform-node-shared/tsconfig.json | 11 - .../platform-node-shared/vitest.config.ts | 6 - packages/platform-node/README.md | 7 - packages/platform-node/docgen.json | 24 - packages/platform-node/src/Mime.ts | 25 - .../platform-node/src/NodeHttpPlatform.ts | 70 - packages/platform-node/src/NodeRedis.ts | 92 - .../platform-node/test/NodeHttpClient.test.ts | 157 - .../test/NodeHttpPlatform.test.ts | 51 - packages/platform-node/test/NodeRedis.test.ts | 31 - .../platform-node/test/NodeSocket.test.ts | 153 - .../test/cluster/SocketRunner.test.ts | 120 - .../test/cluster/SqlMessageStorage.test.ts | 222 - .../test/cluster/SqlRunnerStorage.test.ts | 101 - packages/platform-node/tsconfig.json | 12 - packages/platform-node/vitest.config.ts | 6 - .../browser}/CHANGELOG.md | 172 + .../browser}/LICENSE | 0 packages/platform/browser/README.md | 14 + .../browser}/package.json | 27 +- .../browser}/src/BrowserCrypto.ts | 8 +- .../browser}/src/BrowserHttpClient.ts | 39 +- .../browser}/src/BrowserKeyValueStore.ts | 70 +- .../browser}/src/BrowserPersistence.ts | 0 .../browser}/src/BrowserRuntime.ts | 27 +- .../browser}/src/BrowserSocket.ts | 10 +- .../browser}/src/BrowserStream.ts | 4 +- .../browser}/src/BrowserWorker.ts | 0 .../browser}/src/BrowserWorkerRunner.ts | 3 +- .../browser}/src/Clipboard.ts | 2 +- .../browser}/src/Geolocation.ts | 2 +- .../browser}/src/IndexedDb.ts | 6 +- .../browser}/src/IndexedDbDatabase.ts | 24 +- .../browser}/src/IndexedDbQueryBuilder.ts | 97 +- .../browser}/src/IndexedDbTable.ts | 18 +- .../browser}/src/IndexedDbVersion.ts | 8 +- .../browser}/src/Permissions.ts | 2 +- .../browser}/src/index.ts | 0 .../browser/test/BrowserCrypto.test.ts | 170 + .../browser}/test/BrowserHttpClient.test.ts | 66 +- .../browser/test/BrowserKeyValueStore.test.ts | 103 + .../browser}/test/BrowserPersistence.test.ts | 2 +- .../BrowserPersistencePersistedCache.test.ts | 8 +- .../browser/test/BrowserWorkerRunner.test.ts | 101 + .../browser}/test/IndexedDbDatabase.test.ts | 36 +- .../test/IndexedDbQueryBuilder.test.ts | 171 +- .../browser}/test/IndexedDbTable.test.ts | 0 .../browser}/test/IndexedDbVersion.test.ts | 0 .../browser}/test/Permissions.test.ts | 0 .../browser}/test/RpcWorker.test.ts | 0 .../browser}/test/fixtures/rpc-e2e.ts | 8 +- .../browser}/test/fixtures/rpc-schemas.ts | 16 +- .../browser}/test/fixtures/rpc-worker.ts | 6 +- packages/platform/browser/tsconfig.json | 8 + .../browser}/vitest.setup.ts | 0 .../bun}/CHANGELOG.md | 225 + .../{platform-bun => platform/bun}/LICENSE | 0 packages/platform/bun/README.md | 14 + .../bun}/package.json | 22 +- .../bun}/src/BunChildProcessSpawner.ts | 2 +- .../bun}/src/BunClusterHttp.ts | 12 +- .../bun}/src/BunClusterSocket.ts | 12 +- .../bun}/src/BunCrypto.ts | 0 .../bun}/src/BunFileSystem.ts | 0 .../bun}/src/BunHttpClient.ts | 0 .../bun}/src/BunHttpPlatform.ts | 29 +- .../bun}/src/BunHttpServer.ts | 271 +- .../bun}/src/BunHttpServerRequest.ts | 0 .../bun}/src/BunMultipart.ts | 15 +- .../bun}/src/BunPath.ts | 0 .../bun}/src/BunRedis.ts | 38 +- .../bun}/src/BunRuntime.ts | 0 .../bun}/src/BunServices.ts | 0 .../bun}/src/BunSink.ts | 0 .../bun}/src/BunSocket.ts | 11 +- .../bun}/src/BunSocketServer.ts | 0 .../bun}/src/BunStdio.ts | 0 .../bun}/src/BunStream.ts | 50 +- .../bun}/src/BunTerminal.ts | 0 .../bun}/src/BunWorker.ts | 0 .../bun}/src/BunWorkerRunner.ts | 13 +- .../bun}/src/index.ts | 0 .../bun/test/BunHttpCompression.test.ts | 110 + .../platform/bun/test/BunHttpPlatform.test.ts | 159 + .../platform/bun/test/BunHttpServer.test.ts | 299 + packages/platform/bun/test/BunStream.test.ts | 27 + packages/platform/bun/tsconfig.json | 12 + packages/platform/deno/CHANGELOG.md | 139 + packages/platform/deno/LICENSE | 21 + packages/platform/deno/README.md | 14 + packages/platform/deno/package.json | 83 + .../deno/src/DenoChildProcessSpawner.ts | 439 + packages/platform/deno/src/DenoClusterHttp.ts | 156 + .../platform/deno/src/DenoClusterSocket.ts | 202 + packages/platform/deno/src/DenoCrypto.ts | 63 + packages/platform/deno/src/DenoFileSystem.ts | 516 + packages/platform/deno/src/DenoHttpClient.ts | 9 + .../platform/deno/src/DenoHttpPlatform.ts | 110 + packages/platform/deno/src/DenoHttpServer.ts | 573 + .../deno/src/DenoHttpServerRequest.ts | 18 + .../platform/deno/src/DenoKeyValueStore.ts | 28 + packages/platform/deno/src/DenoMultipart.ts | 53 + packages/platform/deno/src/DenoPath.ts | 102 + packages/platform/deno/src/DenoRedis.ts | 150 + packages/platform/deno/src/DenoRuntime.ts | 59 + packages/platform/deno/src/DenoServices.ts | 49 + packages/platform/deno/src/DenoSocket.ts | 398 + .../deno/src/DenoSocketServer.ts} | 0 packages/platform/deno/src/DenoStdio.ts | 53 + packages/platform/deno/src/DenoTerminal.ts | 32 + packages/platform/deno/src/DenoWorker.ts | 75 + .../platform/deno/src/DenoWorkerRunner.ts | 175 + packages/platform/deno/src/index.ts | 110 + packages/platform/deno/src/internal/error.ts | 66 + .../deno/test/DenoChildProcessSpawner.test.ts | 153 + .../platform/deno/test/DenoCrypto.test.ts | 37 + .../platform/deno/test/DenoFileSystem.test.ts | 69 + .../deno/test/DenoHttpCompression.test.ts | 158 + .../deno/test/DenoHttpPlatform.test.ts | 145 + .../platform/deno/test/DenoHttpServer.test.ts | 755 + .../deno/test/DenoKeyValueStore.test.ts | 12 + packages/platform/deno/test/DenoPath.test.ts | 39 + .../deno/test/DenoRedis.integration.test.ts | 231 + .../platform/deno/test/DenoSocket.test.ts | 442 + .../deno/test/DenoSocketServer.test.ts | 119 + packages/platform/deno/test/DenoStdio.test.ts | 50 + .../platform/deno/test/DenoTerminal.test.ts | 100 + .../deno/test/DenoWorkerRunner.test.ts | 101 + packages/platform/deno/test/RpcWorker.test.ts | 21 + .../deno/test/cluster/SocketRunner.test.ts | 199 + .../deno/test/fixtures/deno-terminal.ts | 127 + .../platform/deno/test/fixtures/rpc-e2e.ts | 142 + .../deno/test/fixtures/rpc-schemas.ts | 157 + .../platform/deno/test/fixtures/rpc-worker.ts | 12 + .../deno}/test/fixtures/text.txt | 0 .../platform/deno/test/fixtures/tls/ca.pem | 10 + .../platform/deno/test/fixtures/tls/cert.pem | 11 + .../platform/deno/test/fixtures/tls/key.pem | 3 + .../platform/deno/test/internal/error.test.ts | 39 + packages/platform/deno/tsconfig.json | 12 + .../node-shared}/CHANGELOG.md | 188 + .../node-shared}/LICENSE | 0 packages/platform/node-shared/README.md | 14 + .../platform/node-shared/benchmark/Socket.ts | 370 + .../node-shared}/package.json | 28 +- .../src/NodeChildProcessSpawner.ts | 194 +- .../node-shared}/src/NodeClusterSocket.ts | 27 +- .../node-shared}/src/NodeCrypto.ts | 0 .../node-shared}/src/NodeFileSystem.ts | 277 +- .../node-shared/src/NodeHttpCompression.ts | 153 + .../node-shared}/src/NodePath.ts | 52 +- .../node-shared}/src/NodeRuntime.ts | 0 .../node-shared}/src/NodeSink.ts | 51 +- .../platform/node-shared/src/NodeSocket.ts | 596 + .../node-shared}/src/NodeSocketServer.ts | 370 +- .../node-shared}/src/NodeStdio.ts | 2 + .../node-shared}/src/NodeStream.ts | 43 +- .../node-shared}/src/NodeTerminal.ts | 75 +- .../node-shared}/src/index.ts | 5 + .../src/internal/nodeChildProcessSpawner.ts | 16 + .../node-shared}/src/internal/utils.ts | 2 +- .../test/HttpPlatform.test-utils.ts | 27 + .../test/NodeChildProcessSpawner.test.ts | 226 + .../node-shared/test/NodeFileSystem.test.ts | 259 + .../node-shared/test/NodePath.test.ts | 27 + .../node-shared/test/NodeSink.test.ts | 282 + .../node-shared/test/NodeStdio.test.ts | 50 + .../node-shared}/test/NodeStream.test.ts | 78 + .../node-shared/test/NodeTerminal.test.ts | 75 + .../test/fixtures/helloworld.tar.gz | Bin .../test/fixtures/node-terminal.ts | 108 + .../test/fixtures/process-group.ts | 29 + packages/platform/node-shared/tsconfig.json | 11 + .../node}/CHANGELOG.md | 231 + .../{platform-node => platform/node}/LICENSE | 0 packages/platform/node/README.md | 14 + .../node}/package.json | 36 +- .../node}/src/NodeChildProcessSpawner.ts | 0 .../node}/src/NodeClusterHttp.ts | 10 +- .../node}/src/NodeClusterSocket.ts | 14 +- .../node}/src/NodeCrypto.ts | 0 .../node}/src/NodeFileSystem.ts | 0 .../node}/src/NodeHttpClient.ts | 103 +- .../node}/src/NodeHttpIncomingMessage.ts | 14 +- .../platform/node/src/NodeHttpPlatform.ts | 122 + .../node}/src/NodeHttpServer.ts | 173 +- .../node}/src/NodeHttpServerRequest.ts | 0 .../node}/src/NodeMultipart.ts | 9 +- .../platform/node/src/NodeMultipartParser.ts | 184 + .../node}/src/NodePath.ts | 0 packages/platform/node/src/NodeRedis.ts | 205 + .../node}/src/NodeRuntime.ts | 0 .../node}/src/NodeServices.ts | 0 .../node}/src/NodeSink.ts | 0 .../node}/src/NodeSocket.ts | 20 +- .../platform/node/src/NodeSocketServer.ts | 8 + .../node}/src/NodeStdio.ts | 0 .../node}/src/NodeStream.ts | 0 .../node}/src/NodeTerminal.ts | 0 .../node}/src/NodeWorker.ts | 2 +- .../node}/src/NodeWorkerRunner.ts | 70 +- .../node}/src/Undici.ts | 4 +- .../node}/src/index.ts | 17 +- .../node}/test/HttpApi.test.ts | 302 +- .../node}/test/HttpStaticServer.test.ts | 47 + .../test/HttpStaticServerConditional.test.ts | 14 +- .../platform/node/test/KeyValueStore.test.ts | 50 + .../node/test/MultipartParser.test.ts | 887 + .../node}/test/NodeCrypto.test.ts | 0 .../platform/node/test/NodeHttpClient.test.ts | 253 + .../node/test/NodeHttpCompression.test.ts | 208 + .../node/test/NodeHttpPlatform.test.ts | 134 + .../node}/test/NodeHttpServer.test.ts | 575 +- .../node/test/NodeRedis.integration.test.ts | 299 + .../platform/node/test/NodeSocket.test.ts | 819 + .../node/test/NodeWorkerRunner.test.ts | 31 + .../node}/test/OpenApi.test.ts | 0 .../node}/test/RpcServer.test.ts | 184 +- .../test/__snapshots__/HttpApi.test.ts.snap | 49 +- .../cluster-integration/ClusterCron.test.ts | 301 + .../test/cluster-integration/Entity.test.ts | 738 + .../test/cluster-integration/Locks.test.ts | 155 + .../cluster-integration/Persistence.test.ts | 762 + .../node/test/cluster-integration/README.md | 39 + .../cluster-integration/Residency.test.ts | 76 + .../test/cluster-integration/Smoke.test.ts | 55 + .../cluster-integration/Transport.test.ts | 84 + .../test/cluster-integration/Workflow.test.ts | 981 + .../test/cluster-integration/globalSetup.ts | 38 + .../node/test/cluster-integration/harness.ts | 707 + .../node}/test/cluster/MessageStorageTest.ts | 25 +- .../node/test/cluster/SocketRunner.test.ts | 324 + .../SqlMessageStorage.integration.test.ts | 547 + .../SqlRunnerStorage.integration.test.ts | 536 + .../fixtures/http-static-server-outside.txt | 0 .../http-static-server/conditional.txt | 0 .../http-static-server/custom/home.html | 0 .../fixtures/http-static-server/file.binx | 0 .../http-static-server/guide/index.html | 0 .../fixtures/http-static-server/hello.txt | 0 .../fixtures/http-static-server/index.html | 0 .../fixtures/http-static-server/range.txt | 0 .../node}/test/fixtures/mysql2-utils.ts | 13 +- .../node}/test/fixtures/pg-utils.ts | 0 .../node}/test/fixtures/rpc-e2e.ts | 4 +- .../node}/test/fixtures/rpc-schemas.ts | 16 +- packages/platform/node/test/fixtures/text.txt | 1 + .../platform/node/test/fixtures/tls/cert.pem | 8 + .../platform/node/test/fixtures/tls/key.pem | 3 + .../node/test/fixtures/worker-runner.ts | 15 + packages/platform/node/tsconfig.json | 12 + packages/sql/clickhouse/CHANGELOG.md | 185 + packages/sql/clickhouse/README.md | 13 +- packages/sql/clickhouse/docgen.json | 24 - packages/sql/clickhouse/package.json | 20 +- .../sql/clickhouse/src/ClickhouseClient.ts | 61 +- .../sql/clickhouse/src/ClickhouseMigrator.ts | 2 +- packages/sql/clickhouse/test/Client.test.ts | 94 +- .../test/SqlErrorClassification.test.ts | 28 +- packages/sql/clickhouse/tsconfig.json | 4 +- packages/sql/clickhouse/vitest.config.ts | 6 - packages/sql/d1/CHANGELOG.md | 161 + packages/sql/d1/README.md | 13 +- packages/sql/d1/docgen.json | 24 - packages/sql/d1/package.json | 22 +- packages/sql/d1/src/D1Client.ts | 171 +- packages/sql/d1/test/Client.test.ts | 160 + packages/sql/d1/test/Resolver.test.ts | 13 +- packages/sql/d1/test/utils.ts | 27 +- packages/sql/d1/tsconfig.json | 2 +- packages/sql/d1/vitest.config.ts | 6 - packages/sql/libsql/CHANGELOG.md | 158 + packages/sql/libsql/README.md | 13 +- packages/sql/libsql/docgen.json | 24 - packages/sql/libsql/package.json | 22 +- packages/sql/libsql/src/LibsqlClient.ts | 14 +- packages/sql/libsql/src/LibsqlMigrator.ts | 4 +- ...ent.test.ts => Client.integration.test.ts} | 57 +- ...r.test.ts => Resolver.integration.test.ts} | 19 +- packages/sql/libsql/tsconfig.json | 4 +- packages/sql/libsql/vitest.config.ts | 6 - packages/sql/mssql/CHANGELOG.md | 167 + packages/sql/mssql/README.md | 13 +- packages/sql/mssql/docgen.json | 24 - packages/sql/mssql/package.json | 21 +- packages/sql/mssql/src/MssqlClient.ts | 46 +- packages/sql/mssql/src/MssqlMigrator.ts | 2 +- packages/sql/mssql/test/Binary.test.ts | 43 + packages/sql/mssql/test/Client.test.ts | 146 +- .../test/Persistence.integration.test.ts} | 16 +- .../mssql/test/SqlErrorClassification.test.ts | 2 +- packages/sql/mssql/test/utils.ts | 36 + packages/sql/mssql/tsconfig.json | 5 +- packages/sql/mssql/vitest.config.ts | 6 - packages/sql/mysql2/CHANGELOG.md | 159 + packages/sql/mysql2/README.md | 13 +- packages/sql/mysql2/docgen.json | 24 - packages/sql/mysql2/package.json | 22 +- packages/sql/mysql2/src/MysqlClient.ts | 19 +- packages/sql/mysql2/src/MysqlMigrator.ts | 2 +- ...ent.test.ts => Client.integration.test.ts} | 0 ...t.ts => KeyValueStore.integration.test.ts} | 0 ...odel.test.ts => Model.integration.test.ts} | 65 +- ...est.ts => MysqlClient.integration.test.ts} | 31 + .../test/Persistence.integration.test.ts | 87 + .../test/SqlErrorClassification.test.ts | 2 +- ...tLogServerUnencrypted.integration.test.ts} | 0 packages/sql/mysql2/test/utils.ts | 17 +- packages/sql/mysql2/tsconfig.json | 4 +- packages/sql/mysql2/vitest.config.ts | 6 - packages/sql/pg/CHANGELOG.md | 166 + packages/sql/pg/README.md | 13 +- packages/sql/pg/benchmark/PgClient.ts | 142 + packages/sql/pg/benchmark/PgCodec.ts | 378 + packages/sql/pg/benchmark/README.md | 92 + packages/sql/pg/docgen.json | 24 - packages/sql/pg/package.json | 36 +- packages/sql/pg/src/PgAuth.ts | 318 + packages/sql/pg/src/PgClient.ts | 813 +- packages/sql/pg/src/PgConnection.ts | 2294 + packages/sql/pg/src/PgMigrator.ts | 2 +- packages/sql/pg/src/PgPool.ts | 231 + packages/sql/pg/src/PgProtocol.ts | 1863 + packages/sql/pg/src/PgTypes.ts | 1998 + packages/sql/pg/src/index.ts | 25 + packages/sql/pg/src/internal/connection.ts | 26 + packages/sql/pg/src/internal/sqlError.ts | 75 + ...ent.test.ts => Client.integration.test.ts} | 269 +- ...t.ts => KeyValueStore.integration.test.ts} | 0 .../pg/test/Persistence.integration.test.ts | 279 + packages/sql/pg/test/Persistence.test.ts | 20 - packages/sql/pg/test/PgAuth.test.ts | 172 + .../pg/test/PgConnection.in-process.test.ts | 573 + .../pg/test/PgConnection.integration.test.ts | 373 + packages/sql/pg/test/PgConnection.test.ts | 47 + .../sql/pg/test/PgPool.integration.test.ts | 265 + packages/sql/pg/test/PgProtocol.test.ts | 791 + packages/sql/pg/test/PgTypes.test.ts | 1072 + .../pg/test/SqlErrorClassification.test.ts | 109 +- ...tLogServerUnencrypted.integration.test.ts} | 0 .../sql/pg/test/TransactionAcquire.test.ts | 45 +- packages/sql/pg/test/fixtures/goldens.ts | 140 + packages/sql/pg/test/fixtures/regenerate.ts | 354 + packages/sql/pg/test/utils.ts | 25 +- packages/sql/pg/tsconfig.json | 9 +- packages/sql/pg/vitest.config.ts | 6 - packages/sql/pglite/CHANGELOG.md | 157 + packages/sql/pglite/README.md | 32 +- packages/sql/pglite/docgen.json | 24 - packages/sql/pglite/package.json | 20 +- packages/sql/pglite/src/PgliteClient.ts | 58 +- packages/sql/pglite/src/PgliteMigrator.ts | 4 +- packages/sql/pglite/test/Client.test.ts | 26 +- packages/sql/pglite/test/Json.test.ts | 19 + .../sql/pglite/test/PersistedQueue.test.ts | 41 + packages/sql/pglite/test/Transaction.test.ts | 33 +- packages/sql/pglite/tsconfig.json | 2 +- packages/sql/pglite/vitest.config.ts | 6 - packages/sql/sqlite-bun/CHANGELOG.md | 158 + packages/sql/sqlite-bun/README.md | 13 +- packages/sql/sqlite-bun/docgen.json | 24 - packages/sql/sqlite-bun/package.json | 20 +- packages/sql/sqlite-bun/src/SqliteClient.ts | 47 +- packages/sql/sqlite-bun/src/SqliteMigrator.ts | 4 +- packages/sql/sqlite-bun/test/Client.test.ts | 90 +- packages/sql/sqlite-bun/tsconfig.json | 2 +- packages/sql/sqlite-bun/vitest.config.ts | 6 - packages/sql/sqlite-do/CHANGELOG.md | 158 + packages/sql/sqlite-do/README.md | 13 +- packages/sql/sqlite-do/docgen.json | 24 - packages/sql/sqlite-do/package.json | 20 +- packages/sql/sqlite-do/src/SqliteClient.ts | 12 +- packages/sql/sqlite-do/src/SqliteMigrator.ts | 4 +- packages/sql/sqlite-do/test/Client.test.ts | 18 +- packages/sql/sqlite-do/tsconfig.json | 2 +- packages/sql/sqlite-do/vitest.config.ts | 6 - packages/sql/sqlite-node/CHANGELOG.md | 159 + packages/sql/sqlite-node/README.md | 17 +- packages/sql/sqlite-node/docgen.json | 24 - packages/sql/sqlite-node/package.json | 23 +- packages/sql/sqlite-node/src/SqliteClient.ts | 213 +- .../sql/sqlite-node/src/SqliteMigrator.ts | 4 +- packages/sql/sqlite-node/test/Client.test.ts | 114 +- .../sql/sqlite-node/test/Persistence.test.ts | 70 +- .../sqlite-node/test/SqlEventJournal.test.ts | 66 +- .../test/SqlEventLogServerEncrypted.test.ts | 215 +- .../test/SqlEventLogServerUnencrypted.test.ts | 59 +- .../sqlite-node/test/SqlModelServices.test.ts | 106 + packages/sql/sqlite-node/tsconfig.json | 7 +- packages/sql/sqlite-node/vitest.config.ts | 6 - packages/sql/sqlite-react-native/CHANGELOG.md | 158 + packages/sql/sqlite-react-native/README.md | 13 +- packages/sql/sqlite-react-native/docgen.json | 24 - packages/sql/sqlite-react-native/package.json | 22 +- .../sqlite-react-native/src/SqliteClient.ts | 65 +- .../sqlite-react-native/src/SqliteMigrator.ts | 4 +- .../sqlite-react-native/test/Client.test.ts | 38 +- .../sql/sqlite-react-native/tsconfig.json | 2 +- .../sql/sqlite-react-native/vitest.config.ts | 6 - packages/sql/sqlite-wasm/CHANGELOG.md | 162 + packages/sql/sqlite-wasm/README.md | 13 +- packages/sql/sqlite-wasm/docgen.json | 24 - packages/sql/sqlite-wasm/package.json | 20 +- packages/sql/sqlite-wasm/src/OpfsWorker.ts | 19 +- packages/sql/sqlite-wasm/src/SqliteClient.ts | 39 +- .../sql/sqlite-wasm/src/SqliteMigrator.ts | 4 +- packages/sql/sqlite-wasm/test/Client.test.ts | 162 +- packages/sql/sqlite-wasm/tsconfig.json | 2 +- packages/sql/sqlite-wasm/vitest.config.ts | 6 - packages/tools/ai-codegen/docgen.json | 4 - packages/tools/ai-codegen/package.json | 9 +- packages/tools/ai-codegen/src/Config.ts | 20 +- packages/tools/ai-codegen/src/Discovery.ts | 32 +- packages/tools/ai-codegen/src/Generator.ts | 80 +- packages/tools/ai-codegen/src/Glob.ts | 2 +- packages/tools/ai-codegen/src/PostProcess.ts | 7 +- packages/tools/ai-codegen/src/SpecFetcher.ts | 7 +- packages/tools/ai-codegen/src/main.ts | 14 +- .../tools/ai-codegen/test/Generator.test.ts | 68 + packages/tools/ai-codegen/tsconfig.json | 4 +- packages/tools/ai-docgen/docgen.json | 4 - packages/tools/ai-docgen/package.json | 9 +- packages/tools/ai-docgen/src/main.ts | 13 +- packages/tools/ai-docgen/tsconfig.json | 4 +- packages/tools/api-diff/README.md | 56 + packages/tools/api-diff/package.json | 30 + packages/tools/api-diff/src/Annotations.ts | 72 + packages/tools/api-diff/src/ApiDiff.ts | 170 + packages/tools/api-diff/src/Cli.ts | 62 + packages/tools/api-diff/src/Diff.ts | 504 + packages/tools/api-diff/src/Discovery.ts | 242 + packages/tools/api-diff/src/Error.ts | 9 + packages/tools/api-diff/src/Json.ts | 26 + packages/tools/api-diff/src/MigrationDoc.ts | 462 + packages/tools/api-diff/src/Model.ts | 156 + packages/tools/api-diff/src/Report.ts | 141 + packages/tools/api-diff/src/Snapshot.ts | 905 + packages/tools/api-diff/src/Worktrees.ts | 243 + packages/tools/api-diff/src/bin.ts | 18 + .../tools/api-diff/test/Annotations.test.ts | 59 + packages/tools/api-diff/test/Cli.test.ts | 111 + packages/tools/api-diff/test/Diff.test.ts | 284 + .../tools/api-diff/test/Discovery.test.ts | 62 + packages/tools/api-diff/test/Error.test.ts | 25 + .../tools/api-diff/test/MigrationDoc.test.ts | 456 + packages/tools/api-diff/test/Snapshot.test.ts | 130 + .../tools/api-diff/test/Worktrees.test.ts | 86 + packages/tools/api-diff/test/utils.ts | 37 + packages/tools/api-diff/tsconfig.json | 8 + packages/tools/bundle/README.md | 18 +- packages/tools/bundle/docgen.json | 4 - .../bundle/fixtures/arbitrary-combinators.ts | 12 + packages/tools/bundle/fixtures/brand.ts | 2 +- packages/tools/bundle/fixtures/differ.ts | 8 +- packages/tools/bundle/fixtures/http-router.ts | 17 + packages/tools/bundle/fixtures/optic.ts | 2 +- .../tools/bundle/fixtures/schema-binary.ts | 10 + .../tools/bundle/fixtures/schema-class.ts | 12 + .../fixtures/schema-fromJsonSchemaDocument.ts | 4 +- .../schema-representation-roundtrip.ts | 9 +- .../fixtures/schema-string-transformation.ts | 2 +- ...ArbitraryLazy.ts => schema-toArbitrary.ts} | 3 +- .../bundle/fixtures/schema-toCodeDocument.ts | 4 +- .../bundle/fixtures/schema-toCodecJson.ts | 2 +- .../bundle/fixtures/schema-toEquivalence.ts | 2 +- .../bundle/fixtures/schema-toFormatter.ts | 2 +- .../fixtures/schema-toJsonSchemaDocument.ts | 2 +- .../fixtures/schema-toRepresentation.ts | 2 +- packages/tools/bundle/package.json | 12 +- packages/tools/bundle/src/Cli.ts | 10 +- packages/tools/bundle/src/Fixtures.ts | 3 +- packages/tools/bundle/src/Plugins.ts | 4 +- packages/tools/bundle/src/Reporter.ts | 16 +- packages/tools/bundle/src/Rollup.ts | 49 +- .../bundle/test/HttpRouterEsbuild.test.ts | 76 + packages/tools/bundle/test/Plugins.test.ts | 29 +- packages/tools/bundle/tsconfig.fixtures.json | 4 +- packages/tools/bundle/tsconfig.json | 2 +- packages/tools/bundle/tsconfig.src.json | 4 +- packages/tools/bundle/vitest.config.ts | 6 - packages/tools/docgen/CHANGELOG.md | 317 + packages/tools/docgen/LICENSE | 21 + packages/tools/docgen/README.md | 146 + packages/tools/docgen/package.json | 84 + packages/tools/docgen/schema.json | 113 + packages/tools/docgen/src/CLI.ts | 244 + packages/tools/docgen/src/Checker.ts | 248 + packages/tools/docgen/src/Configuration.ts | 380 + packages/tools/docgen/src/Core.ts | 627 + packages/tools/docgen/src/Domain.ts | 348 + packages/tools/docgen/src/Parser.ts | 745 + packages/tools/docgen/src/Printer.ts | 383 + packages/tools/docgen/src/bin.ts | 22 + packages/tools/docgen/src/index.ts | 28 + .../docgen/src/internal/markdown-toc.d.ts | 4 + packages/tools/docgen/test/Checker.test.ts | 220 + .../tools/docgen/test/Configuration.test.ts | 334 + packages/tools/docgen/test/Core.test.ts | 278 + packages/tools/docgen/test/Parser.test.ts | 1829 + .../docgen/test/fixtures/invalid-json.txt | 1 + packages/tools/docgen/tsconfig.json | 8 + packages/tools/doctest/CHANGELOG.md | 76 + packages/tools/doctest/README.md | 88 + packages/tools/doctest/package.json | 63 + packages/tools/doctest/src/Plugin.ts | 126 + packages/tools/doctest/src/Protocol.ts | 145 + packages/tools/doctest/src/Runner.ts | 41 + packages/tools/doctest/src/Runtime.ts | 33 + packages/tools/doctest/src/Source.ts | 109 + packages/tools/doctest/src/Transform.ts | 208 + packages/tools/doctest/src/index.ts | 35 + packages/tools/doctest/test/Plugin.test.ts | 199 + packages/tools/doctest/test/Protocol.test.ts | 78 + packages/tools/doctest/test/Runtime.test.ts | 18 + packages/tools/doctest/test/Source.test.ts | 139 + packages/tools/doctest/test/Transform.test.ts | 65 + .../test/fixtures/markdown/docs/helper.ts | 1 + .../test/fixtures/markdown/docs/typed.md | 43 + .../test/fixtures/markdown/docs/typed.mdx | 10 + .../test/fixtures/markdown/src/typed.ts | 12 + .../test/fixtures/markdown/vitest.config.ts | 16 + packages/tools/doctest/tsconfig.json | 8 + packages/tools/jsdocs/docgen.json | 4 - packages/tools/jsdocs/package.json | 11 +- packages/tools/jsdocs/src/Jsdocs.ts | 163 +- packages/tools/jsdocs/test/jsdocs.test.ts | 39 +- packages/tools/jsdocs/tsconfig.json | 2 +- packages/tools/jsdocs/vitest.config.ts | 6 - packages/tools/openapi-generator/CHANGELOG.md | 207 + packages/tools/openapi-generator/README.md | 14 + packages/tools/openapi-generator/docgen.json | 4 - packages/tools/openapi-generator/package.json | 18 +- .../src/JsonSchemaGenerator.ts | 169 +- .../openapi-generator/src/OpenApiGenerator.ts | 257 +- .../openapi-generator/src/OpenApiPatch.ts | 103 +- .../src/OpenApiTransformer.ts | 304 +- .../openapi-generator/src/ParsedOperation.ts | 38 +- packages/tools/openapi-generator/src/Utils.ts | 2 +- packages/tools/openapi-generator/src/main.ts | 8 +- .../test/JsonSchemaGenerator.test.ts | 106 +- .../JsonSchemaGeneratorRepresentation.test.ts | 34 + .../test/OpenApiGenerator.test.ts | 802 +- .../test/OpenApiPatch.test.ts | 7 +- .../test/OpenApiTransformer.test.ts | 219 + .../openapi-generator/test/Utils.test.ts | 196 +- .../tools/openapi-generator/tsconfig.json | 4 +- .../tools/openapi-generator/vitest.config.ts | 6 - packages/tools/oxc/docgen.json | 4 - packages/tools/oxc/oxlintrc.json | 3 +- packages/tools/oxc/package.json | 15 +- packages/tools/oxc/src/oxlint/index.ts | 4 +- packages/tools/oxc/src/oxlint/rules/jsdocs.ts | 108 - .../src/oxlint/rules/no-bigint-literals.ts | 4 +- .../rules/no-import-from-barrel-package.ts | 2 +- .../oxlint/rules/no-js-extension-imports.ts | 4 +- .../oxlint/rules/no-opaque-instance-fields.ts | 2 +- .../src/oxlint/rules/no-unused-internal.ts | 74 +- packages/tools/oxc/test/jsdocs.test.ts | 75 - .../oxc/test/no-js-extension-imports.test.ts | 37 + .../tools/oxc/test/no-unused-internal.test.ts | 15 + packages/tools/oxc/test/utils.ts | 3 +- packages/tools/oxc/tsconfig.json | 2 +- packages/tools/oxc/vitest.config.ts | 6 - packages/tools/utils/docgen.json | 4 - packages/tools/utils/package.json | 9 +- packages/tools/utils/src/Codegen.ts | 7 +- packages/tools/utils/src/Glob.ts | 2 +- packages/tools/utils/src/commands/codegen.ts | 6 +- packages/tools/utils/test/Codegen.test.ts | 25 + packages/tools/utils/tsconfig.json | 4 +- packages/vitest/CHANGELOG.md | 169 + packages/vitest/README.md | 115 +- packages/vitest/docgen.json | 24 - packages/vitest/package.json | 25 +- packages/vitest/src/index.ts | 86 +- packages/vitest/src/internal/internal.ts | 160 +- packages/vitest/src/utils.ts | 16 +- packages/vitest/test/index.test.ts | 196 +- .../vitest/test/layer-concurrency.test.ts | 72 + packages/vitest/test/nested-isolation.test.ts | 28 +- packages/vitest/tsconfig.json | 2 +- packages/vitest/typetest/index.tst.ts | 74 +- packages/vitest/vitest.config.ts | 6 - .../@changesets__assemble-release-plan.patch | 26 - patches/@changesets__get-github-info.patch | 44 - .../@changesets__get-github-info@1.0.1.patch | 21 + pnpm-lock.yaml | 8780 ++- pnpm-workspace.yaml | 20 + scratchpad/package.json | 1 - scratchpad/tsconfig.json | 2 +- scripts/bundle-analyze.sh | 2 +- scripts/bundle-compare-selected.sh | 4 +- scripts/bundle-compare.sh | 4 +- scripts/circular.mjs | 2 +- scripts/clean.mjs | 7 +- scripts/copy-ai-docs.mjs | 33 + scripts/docs.mjs | 64 - scripts/set-strip-internal.mjs | 11 + scripts/setup-agents.mjs | 13 + scripts/tsconfig.json | 2 +- scripts/version.mjs | 9 - scripts/version.template.txt | 2 - scripts/worktree-setup.sh | 11 +- stats.txt | 30 - tsconfig.base.json | 5 +- tsconfig.json | 135 +- tsconfig.packages.json | 14 +- tsconfig.tests.json | 120 + tstyche.json | 3 +- vitest.config.ts | 222 +- vitest.docs.ts | 18 + vitest.shared.ts | 61 - 3815 files changed, 347986 insertions(+), 89839 deletions(-) create mode 100644 .agents/AGENTS.md create mode 100644 .agents/skills/ai-docs/SKILL.md create mode 100644 .agents/skills/bundle-analysis/SKILL.md create mode 100644 .agents/skills/changesets/SKILL.md create mode 100644 .agents/skills/changesets/authoring.md create mode 100644 .agents/skills/ci-maintenance/SKILL.md create mode 100644 .agents/skills/ci-maintenance/privileged-workflows.md create mode 100644 .agents/skills/dependency-maintenance/SKILL.md create mode 100644 .agents/skills/dependency-maintenance/coordinated-upgrades.md create mode 100644 .agents/skills/dependency-maintenance/manifest-roles.md create mode 100644 .agents/skills/effect-development/SKILL.md delete mode 100644 .agents/skills/grill-me/SKILL.md create mode 100644 .agents/skills/jsdocs/categories.md create mode 100644 .agents/skills/jsdocs/declarations.md create mode 100644 .agents/skills/jsdocs/examples.md create mode 100644 .agents/skills/migration-guidance/SKILL.md create mode 100644 .agents/skills/migration-guidance/annotations.md create mode 100644 .agents/skills/migration-guidance/generation.md create mode 100644 .agents/skills/package-development/SKILL.md create mode 100644 .agents/skills/package-development/dependencies.md create mode 100644 .agents/skills/package-development/publishing.md create mode 100644 .agents/skills/package-development/registration.md create mode 100644 .agents/skills/performance-analysis/SKILL.md create mode 100644 .agents/skills/performance-analysis/runtime.md create mode 100644 .agents/skills/performance-analysis/types.md delete mode 100644 .agents/skills/scratchpad/SKILL.md delete mode 100644 .agents/skills/scratchpad/agents/openai.yaml delete mode 100644 .agents/skills/scratchpad/scripts/extract-example.mjs create mode 100644 .agents/skills/test-development/SKILL.md create mode 100644 .agents/skills/test-development/displayed-types.md create mode 100644 .agents/skills/test-development/runtime.md create mode 100644 .agents/skills/test-development/types.md create mode 100644 .agents/skills/vendored-assets/SKILL.md create mode 100644 .agents/skills/vendored-assets/browser-assets.md create mode 100644 .changeset/ai-approved-tool-results.md create mode 100644 .changeset/align-type-id-paths.md create mode 100644 .changeset/all-union-record-channels.md create mode 100644 .changeset/anthropic-image-strings.md create mode 100644 .changeset/anthropic-strict-json-schema.md create mode 100644 .changeset/anthropic-structured-output-fallback.md create mode 100644 .changeset/arbitrary-index-constraints.md create mode 100644 .changeset/array-ensure-array-elements.md create mode 100644 .changeset/atom-rpc-query-requires.md create mode 100644 .changeset/atom-solid-idle-ttl.md create mode 100644 .changeset/bright-mimes-leave.md create mode 100644 .changeset/bright-sockets-connect.md create mode 100644 .changeset/browser-crypto-missing-subtle.md create mode 100644 .changeset/browser-indexeddb-binary-key-existence.md create mode 100644 .changeset/browser-runtime-custom-teardown.md create mode 100644 .changeset/bun-http-server-unix-address.md create mode 100644 .changeset/bun-stream-preserve-failure.md create mode 100644 .changeset/cache-invalidate-when-replacement.md create mode 100644 .changeset/cache-refresh-cancellation-ownership.md create mode 100644 .changeset/cache-refresh-capacity.md create mode 100644 .changeset/cache-refresh-zero-ttl-ownership.md create mode 100644 .changeset/cache-synchronous-interruption.md create mode 100644 .changeset/calm-ducks-fail.md create mode 100644 .changeset/calm-headers-hide.md create mode 100644 .changeset/calm-readers-transact.md create mode 100644 .changeset/calm-trees-generate.md create mode 100644 .changeset/catch-stream-channel-defects.md create mode 100644 .changeset/channel-rundone-completion.md create mode 100644 .changeset/child-process-astral-escape-arguments.md create mode 100644 .changeset/childprocess-process-group-wait.md create mode 100644 .changeset/chunk-slice-concatenation.md create mode 100644 .changeset/clean-config-names.md create mode 100644 .changeset/clean-yaks-parse.md create mode 100644 .changeset/cleanup-before-use-callbacks.md create mode 100644 .changeset/clear-wings-relax.md create mode 100644 .changeset/cli-completion-command-aliases.md create mode 100644 .changeset/cli-completion-shared-flags.md create mode 100644 .changeset/cli-key-value-pair-first-separator.md create mode 100644 .changeset/cli-optional-alternative-flags.md create mode 100644 .changeset/cli-scalar-constructor-names.md create mode 100644 .changeset/cli-variadic-absence-defaults.md create mode 100644 .changeset/cli-wizard-option-looking-values.md create mode 100644 .changeset/cluster-active-teardowns.md create mode 100644 .changeset/cluster-reply-codec-services.md create mode 100644 .changeset/cold-shards-continue.md create mode 100644 .changeset/compatible-dependency-refresh.md create mode 100644 .changeset/context-saved-getter-inference.md create mode 100644 .changeset/cookies-error-tag.md create mode 100644 .changeset/d1-raw-native-results.md create mode 100644 .changeset/datetime-calendar-parts.md create mode 100644 .changeset/deno-redis-url-credentials.md create mode 100644 .changeset/deno-writefile-existing-mode.md create mode 100644 .changeset/docgen-alias-signature.md create mode 100644 .changeset/docgen-class-property-examples.md create mode 100644 .changeset/docgen-source-relative-module-pages.md create mode 100644 .changeset/docgen-unique-example-files.md create mode 100644 .changeset/doctest-markdown-typescript.md create mode 100644 .changeset/doctest-statement-boundary.md create mode 100644 .changeset/dotenv-literal-substitution.md create mode 100644 .changeset/durable-clock-zero-threshold.md create mode 100644 .changeset/durable-deferred-into-encoding-services.md create mode 100644 .changeset/dynamic-tool-parameter-schema.md create mode 100644 .changeset/eff-1004-cli-display-width.md create mode 100644 .changeset/eff-1008-tool-param-failure-mode.md create mode 100644 .changeset/eff-1036-fiber-allocations.md create mode 100644 .changeset/eff-1038-http-server-allocations.md create mode 100644 .changeset/eff-1039-rpc-server-allocations.md create mode 100644 .changeset/eff-1042-http-server-performance.md create mode 100644 .changeset/eff-1201-http-overhead.md create mode 100644 .changeset/eff-854-pg-connection-startup.md create mode 100644 .changeset/eff-961-node-socket-tls.md create mode 100644 .changeset/eff-963-node-socket-server-tls.md create mode 100644 .changeset/eff-965-socket-upgrade.md create mode 100644 .changeset/eff-969-dependency-updates.md create mode 100644 .changeset/eff-972-deno-socket-server.md create mode 100644 .changeset/eff-997-service-keys.md create mode 100644 .changeset/effect-eager-transform-arguments.md create mode 100644 .changeset/effect-scoped-service-restoration.md create mode 100644 .changeset/effect-unmatched-reason-preservation.md create mode 100644 .changeset/effectable-class-override-delegation.md create mode 100644 .changeset/effectable-mixin.md create mode 100644 .changeset/effectify-mapper-input-tuples.md create mode 100644 .changeset/entity-proxy-client-codec-services.md create mode 100644 .changeset/entity-test-client-fatal-defect-option.md create mode 100644 .changeset/eventlog-authentication-forbidden-retry.md create mode 100644 .changeset/eventlog-empty-chunk-framing.md create mode 100644 .changeset/exact-byte-size.md create mode 100644 .changeset/exact-http-file-body-length.md create mode 100644 .changeset/execution-plan-captured-predicate.md create mode 100644 .changeset/fair-matches-infer.md create mode 100644 .changeset/fair-workflows-suspend.md create mode 100644 .changeset/fetch-raw-stream-duplex.md create mode 100644 .changeset/fiber-map-reentrant-replacement.md create mode 100644 .changeset/fiber-registration-same-fiber.md create mode 100644 .changeset/fifty-carrots-punch.md create mode 100644 .changeset/filesystem-sink-undefined-flag.md create mode 100644 .changeset/five-spoons-visit.md create mode 100644 .changeset/fix-atom-http-stream-success-types.md create mode 100644 .changeset/fix-atom-http-top-level-dispatch.md create mode 100644 .changeset/fix-atom-query-zero-ttl.md create mode 100644 .changeset/fix-atom-rpc-client-middleware-errors.md create mode 100644 .changeset/fix-atom-writable-fallback.md create mode 100644 .changeset/fix-cors-vary.md create mode 100644 .changeset/fix-encoded-header-metadata.md create mode 100644 .changeset/fix-filesystem-integer-precision.md create mode 100644 .changeset/fix-http-platform-file-ranges.md create mode 100644 .changeset/fix-httpapi-form-responses.md create mode 100644 .changeset/fix-httpapi-test-pre-response-handlers.md create mode 100644 .changeset/fix-httpapi-url-builder-base-path.md create mode 100644 .changeset/fix-json-schema-percent-references.md create mode 100644 .changeset/fix-mime-parameter-normalization.md create mode 100644 .changeset/fix-negative-file-seek.md create mode 100644 .changeset/fix-openapi-client-form-url-encoding.md create mode 100644 .changeset/fix-openapi-client-multipart-records.md create mode 100644 .changeset/fix-openapi-endpoint-transform-order.md create mode 100644 .changeset/fix-openapi-stream-transform-client.md delete mode 100644 .changeset/fix-otel-logger-clock-skew.md create mode 100644 .changeset/fix-prompt-date-tab-buffer.md create mode 100644 .changeset/fix-react-ref-switch.md create mode 100644 .changeset/fix-reactivity-duplicate-keys.md create mode 100644 .changeset/fix-read-alloc-invalid-sizes.md create mode 100644 .changeset/fix-sink-flatmap-leftovers.md create mode 100644 .changeset/fix-sse-mixed-line-endings.md create mode 100644 .changeset/fix-static-head-range.md create mode 100644 .changeset/fix-static-oversized-ranges.md create mode 100644 .changeset/fix-stream-wrapper-status.md create mode 100644 .changeset/fix-toml-array-subtables.md create mode 100644 .changeset/fix-tool-result-serialization.md create mode 100644 .changeset/fix-vue-ref-switch.md create mode 100644 .changeset/fix-yaml-folded-scalars.md create mode 100644 .changeset/flat-pipelines-share.md create mode 100644 .changeset/formatter-defined-error-causes.md create mode 100644 .changeset/fresh-pools-rotate.md create mode 100644 .changeset/fuzzy-classes-compare.md create mode 100644 .changeset/graph-bellman-ford-infinite-cycle-barriers.md create mode 100644 .changeset/hashmap-collision-entries.md create mode 100644 .changeset/hashring-exclusion-endpoint.md create mode 100644 .changeset/headers-redacted-name-case.md create mode 100644 .changeset/headers-stateful-patterns.md create mode 100644 .changeset/http-client-catch-response-result.md create mode 100644 .changeset/http-client-redirect-preprocessing-recovery.md create mode 100644 .changeset/http-head-stream-scope.md create mode 100644 .changeset/http-response-content-length.md create mode 100644 .changeset/http-router-normalized-prefix.md create mode 100644 .changeset/http-runner-path-boundary.md create mode 100644 .changeset/http-web-handler-cold-start.md create mode 100644 .changeset/httpapi-middleware-error-dedupe.md create mode 100644 .changeset/httpapi-sse-decode-options.md create mode 100644 .changeset/indexeddb-out-of-line-primary-keys.md create mode 100644 .changeset/indexeddb-stream-query-limits.md create mode 100644 .changeset/isolate-unencodable-hydration.md create mode 100644 .changeset/large-postgres-messages.md create mode 100644 .changeset/layer-error-observer-types.md create mode 100644 .changeset/layer-span-trace-options.md create mode 100644 .changeset/layermap-preloaded-acquisition-errors.md create mode 100644 .changeset/lazy-bun-redis-import.md create mode 100644 .changeset/lazy-undici-loading.md create mode 100644 .changeset/libsql-transaction-client-isolation.md create mode 100644 .changeset/logger-complete-file-writes.md create mode 100644 .changeset/mcp-http-resource-template-origins.md create mode 100644 .changeset/mcp-prompt-decoded-parameter-types.md create mode 100644 .changeset/mcp-structured-content-object.md create mode 100644 .changeset/memory-message-storage-clear-controls.md create mode 100644 .changeset/metric-attribute-order-identity.md create mode 100644 .changeset/metric-registry-isolation.md create mode 100644 .changeset/model-field-option-undefined.md create mode 100644 .changeset/mssql-binary-parameters.md create mode 100644 .changeset/mssql-ntlm-domain.md create mode 100644 .changeset/multipart-streamed-part-guard.md create mode 100644 .changeset/mutable-arrays-preserve.md create mode 100644 .changeset/mutable-list-bulk-prepend-tail.md create mode 100644 .changeset/name-random-services.md create mode 100644 .changeset/net-address-values.md create mode 100644 .changeset/ninety-books-sit.md create mode 100644 .changeset/node-http-status-text.md create mode 100644 .changeset/node-response-preserve-bytes.md create mode 100644 .changeset/node-sink-cancel-drain.md create mode 100644 .changeset/node-sink-finalization-errors.md create mode 100644 .changeset/node-stream-buffer-size.md create mode 100644 .changeset/node-watch-relative.md create mode 100644 .changeset/node-worker-unsafe-send-envelope.md create mode 100644 .changeset/node-writeall-empty.md create mode 100644 .changeset/normalize-collection-counts.md create mode 100644 .changeset/number-remainder-negative-zero-dividend.md create mode 100644 .changeset/olive-queues-persist.md create mode 100644 .changeset/openai-image-strings.md create mode 100644 .changeset/openai-optional-sequence-number.md create mode 100644 .changeset/openapi-omitted-additional-properties.md create mode 100644 .changeset/openrouter-encrypted-tool-finish.md create mode 100644 .changeset/openrouter-strict-json-schema.md create mode 100644 .changeset/opentelemetry-delta-interval-starts.md create mode 100644 .changeset/optic-projection-replacement.md create mode 100644 .changeset/optic-string-index-delete.md create mode 100644 .changeset/order-consumed-criteria.md create mode 100644 .changeset/otlp-disabled-batch-flush.md create mode 100644 .changeset/otlp-tracer-span-performance.md create mode 100644 .changeset/partitioned-semaphore-stale-cleanup.md create mode 100644 .changeset/persisted-cache-lookup-throw.md create mode 100644 .changeset/pg-explicit-ssl-precedence.md create mode 100644 .changeset/pglite-json-string-values.md create mode 100644 .changeset/pipeline-root-stdin.md create mode 100644 .changeset/plain-tool-results.md create mode 100644 .changeset/pool-preserve-reservations.md create mode 100644 .changeset/port-http-api-builder-handler.md create mode 100644 .changeset/postgres-channel-names.md rename .changeset/{ => pre}/add-adaptive-rate-limiter-store.md (100%) create mode 100644 .changeset/pre/add-atom-equality.md rename .changeset/{ => pre}/add-bigdecimal-sumall-multiplyall.md (100%) rename .changeset/{ => pre}/add-chunk-schema.md (100%) rename .changeset/{ => pre}/add-command-hidden.md (100%) rename .changeset/{ => pre}/add-config-nested.md (100%) create mode 100644 .changeset/pre/add-deno-file-system.md create mode 100644 .changeset/pre/add-deno-http-client.md create mode 100644 .changeset/pre/add-deno-multipart.md create mode 100644 .changeset/pre/add-deno-socket-server.md create mode 100644 .changeset/pre/add-deno-socket.md rename .changeset/{ => pre}/add-flag-hidden.md (100%) rename .changeset/{ => pre}/add-from-string-schemas.md (100%) rename .changeset/{ => pre}/add-headers-remove-many.md (100%) create mode 100644 .changeset/pre/add-http-client-request-update-headers.md create mode 100644 .changeset/pre/add-http-client-tracer-header-filter.md create mode 100644 .changeset/pre/add-httpapi-with-headers.md rename .changeset/{ => pre}/add-indexeddb-kvs-layer.md (100%) create mode 100644 .changeset/pre/add-json-schema-draft-04.md rename .changeset/{ => pre}/add-make-msgpack.md (100%) rename .changeset/{ => pre}/add-make-option.md (100%) create mode 100644 .changeset/pre/add-mcp-2025-11-25-protocol.md create mode 100644 .changeset/pre/add-mcp-icons.md rename .changeset/{ => pre}/add-missing-tx-modules.md (100%) rename .changeset/{ => pre}/add-newtype-module.md (100%) create mode 100644 .changeset/pre/add-otlp-manual-flush.md create mode 100644 .changeset/pre/add-platform-deno.md rename .changeset/{ => pre}/add-scalar-show-operation-id.md (100%) rename .changeset/{ => pre}/add-schedule-tap.md (100%) rename .changeset/{ => pre}/add-schema-annotate-encoded.md (100%) rename .changeset/{ => pre}/add-schema-array-ensure.md (100%) rename .changeset/{ => pre}/add-schema-bigdecimal.md (100%) rename .changeset/{ => pre}/add-schema-datetime.md (100%) create mode 100644 .changeset/pre/add-schema-error-module.md rename .changeset/{ => pre}/add-schema-option-from-optional-nullor.md (100%) rename .changeset/{ => pre}/add-schema-option-from-undefined-nullish.md (100%) rename .changeset/{ => pre}/add-schema-string-encoding.md (100%) create mode 100644 .changeset/pre/add-schema-tagged-union-discriminants.md create mode 100644 .changeset/pre/add-semaphore-take-if-available.md rename .changeset/{ => pre}/add-sql-pglite.md (100%) rename .changeset/{ => pre}/add-standard-jsdoc-rule.md (100%) rename .changeset/{ => pre}/add-stream-broadcastn.md (100%) create mode 100644 .changeset/pre/add-tool-set-needs-approval.md rename .changeset/{ => pre}/add-unstable-encoding-export.md (100%) create mode 100644 .changeset/pre/add-update-service-scoped.md rename .changeset/{ => pre}/add-values-unprepared.md (100%) rename .changeset/{ => pre}/afraid-cobras-like.md (100%) rename .changeset/{ => pre}/ai-openai-config-field-leak.md (100%) rename .changeset/{ => pre}/ai-openai-file-nullable-fields.md (100%) rename .changeset/{ => pre}/anthropic-4-6-structured-output.md (100%) create mode 100644 .changeset/pre/anthropic-claude-5-capabilities.md create mode 100644 .changeset/pre/anthropic-open-model-enum.md create mode 100644 .changeset/pre/anthropic-stale-max-output-tokens.md create mode 100644 .changeset/pre/apply-httpapi-endpoint-client-transform.md rename .changeset/{ => pre}/asyncresult-exhaustive.md (100%) rename .changeset/{ => pre}/atom-stream-error-type.md (100%) create mode 100644 .changeset/pre/atomic-optional-rc-map.md create mode 100644 .changeset/pre/authentication-error-description.md create mode 100644 .changeset/pre/autocomplete-jk-filter.md create mode 100644 .changeset/pre/batch-persistence-expiration-cleanup.md create mode 100644 .changeset/pre/beige-goats-sin.md rename .changeset/{ => pre}/beige-paths-sort.md (100%) rename .changeset/{ => pre}/better-apples-nail.md (100%) rename .changeset/{ => pre}/better-rocks-arrive.md (100%) create mode 100644 .changeset/pre/better-shrimps-follow.md create mode 100644 .changeset/pre/big-masks-care.md rename .changeset/{ => pre}/big-pans-look.md (100%) rename .changeset/{ => pre}/blue-dingos-greet.md (100%) rename .changeset/{ => pre}/blue-ligers-cheat.md (100%) rename .changeset/{ => pre}/blue-onions-smile.md (100%) create mode 100644 .changeset/pre/blue-pigs-push.md rename .changeset/{ => pre}/blue-ravens-type.md (100%) rename .changeset/{ => pre}/blue-trams-kiss.md (100%) rename .changeset/{ => pre}/bold-chairs-yawn.md (100%) rename .changeset/{ => pre}/bold-planets-shout.md (100%) create mode 100644 .changeset/pre/brave-keys-commit.md create mode 100644 .changeset/pre/brave-rings-update.md rename .changeset/{ => pre}/breezy-meals-see.md (100%) rename .changeset/{ => pre}/bright-bugs-bow.md (100%) rename .changeset/{ => pre}/bright-canyons-clean.md (100%) create mode 100644 .changeset/pre/bright-clocks-count.md create mode 100644 .changeset/pre/bright-docs-teach.md rename .changeset/{ => pre}/bright-dogs-fail.md (100%) rename .changeset/{ => pre}/bright-flags-stand.md (100%) create mode 100644 .changeset/pre/bright-graphs-calculate.md create mode 100644 .changeset/pre/bright-graphs-trace.md create mode 100644 .changeset/pre/bright-journals-commit.md rename .changeset/{ => pre}/bright-laws-teach.md (100%) rename .changeset/{ => pre}/bright-lemons-dance.md (100%) rename .changeset/{ => pre}/bright-planes-smash.md (100%) rename .changeset/{ => pre}/bright-rats-attend.md (100%) create mode 100644 .changeset/pre/bright-tags-recognize.md rename .changeset/{ => pre}/bright-toes-rush.md (100%) create mode 100644 .changeset/pre/brown-glasses-thank.md create mode 100644 .changeset/pre/brown-peas-enter.md rename .changeset/{ => pre}/bumpy-boxes-teach.md (100%) create mode 100644 .changeset/pre/bun-websocket-outgoing-compression.md rename .changeset/{ => pre}/busy-lions-sneeze.md (100%) rename .changeset/{ => pre}/busy-maps-attend.md (100%) create mode 100644 .changeset/pre/calm-atoms-relax.md create mode 100644 .changeset/pre/calm-bash-completions.md rename .changeset/{ => pre}/calm-buckets-own.md (100%) create mode 100644 .changeset/pre/calm-buses-smile.md rename .changeset/{ => pre}/calm-carrots-march.md (100%) rename .changeset/{ => pre}/calm-cars-rest.md (100%) create mode 100644 .changeset/pre/calm-coins-smile.md create mode 100644 .changeset/pre/calm-contexts-rest.md create mode 100644 .changeset/pre/calm-dates-view.md create mode 100644 .changeset/pre/calm-dragons-command.md create mode 100644 .changeset/pre/calm-graphs-span.md create mode 100644 .changeset/pre/calm-graphs-travel.md create mode 100644 .changeset/pre/calm-heads-close.md create mode 100644 .changeset/pre/calm-hounds-smile.md create mode 100644 .changeset/pre/calm-keys-repeat.md create mode 100644 .changeset/pre/calm-masks-count.md create mode 100644 .changeset/pre/calm-pages-hide.md create mode 100644 .changeset/pre/calm-pandas-retry.md rename .changeset/{ => pre}/calm-panthers-nail.md (100%) create mode 100644 .changeset/pre/calm-pears-smile.md create mode 100644 .changeset/pre/calm-queues-await.md create mode 100644 .changeset/pre/calm-ravens-reflect.md create mode 100644 .changeset/pre/calm-redis-clear.md create mode 100644 .changeset/pre/calm-results-align.md create mode 100644 .changeset/pre/calm-schemas-encode.md rename .changeset/{ => pre}/calm-seas-smile.md (100%) create mode 100644 .changeset/pre/calm-servers-share.md create mode 100644 .changeset/pre/calm-services-rest.md rename .changeset/{ => pre}/calm-squids-hug.md (100%) create mode 100644 .changeset/pre/calm-tools-parse.md create mode 100644 .changeset/pre/calm-tools-remember.md rename .changeset/{ => pre}/calm-tracers-sample.md (100%) create mode 100644 .changeset/pre/calm-tuples-align.md create mode 100644 .changeset/pre/calm-tuples-pick.md create mode 100644 .changeset/pre/calm-wolves-reduce.md create mode 100644 .changeset/pre/cancel-tedious-requests.md create mode 100644 .changeset/pre/canonical-number-schemas.md create mode 100644 .changeset/pre/cap-rpc-streaming-buffers.md rename .changeset/{ => pre}/chatty-poets-type.md (100%) rename .changeset/{ => pre}/chilled-mice-wash.md (100%) rename .changeset/{ => pre}/chilly-pumas-rule.md (100%) rename .changeset/{ => pre}/chubby-buckets-feel.md (100%) rename .changeset/{ => pre}/chubby-parents-flow.md (100%) rename .changeset/{ => pre}/chubby-planets-fall.md (100%) rename .changeset/{ => pre}/clean-balloons-tan.md (100%) rename .changeset/{ => pre}/clean-bulldogs-care.md (100%) create mode 100644 .changeset/pre/clean-cats-document.md rename .changeset/{ => pre}/clean-dryers-sneeze.md (100%) create mode 100644 .changeset/pre/clean-formatters-agree.md rename .changeset/{ => pre}/clean-geese-work.md (100%) rename .changeset/{ => pre}/clean-goats-wave.md (100%) create mode 100644 .changeset/pre/clean-lions-cancel.md rename .changeset/{ => pre}/clean-needles-shake.md (100%) rename .changeset/{ => pre}/clean-tires-guess.md (100%) create mode 100644 .changeset/pre/clear-graphs-reduce.md create mode 100644 .changeset/pre/clear-hairs-pump.md rename .changeset/{ => pre}/clear-spies-boil.md (100%) create mode 100644 .changeset/pre/clever-maps-care.md create mode 100644 .changeset/pre/cli-config-built-ins.md rename .changeset/{ => pre}/cli-help-choices.md (100%) create mode 100644 .changeset/pre/cli-no-color-values.md create mode 100644 .changeset/pre/cli-wizard-mode.md create mode 100644 .changeset/pre/close-failed-resource-map-scopes.md rename .changeset/{ => pre}/cold-knives-lie.md (100%) rename .changeset/{ => pre}/cold-rooms-show.md (100%) rename .changeset/{ => pre}/cold-sloths-wave.md (100%) create mode 100644 .changeset/pre/common-mammals-tickle.md create mode 100644 .changeset/pre/compact-json-schema-checks.md rename .changeset/{ => pre}/compact-json-schema-enum.md (100%) create mode 100644 .changeset/pre/config-provider-option-lookup.md rename .changeset/{ => pre}/config-withdefault-eager.md (100%) create mode 100644 .changeset/pre/configurable-schema-reference-policy.md create mode 100644 .changeset/pre/configure-cluster-rpc-buffer-limits.md rename .changeset/{ => pre}/consolidate-encoding.md (100%) rename .changeset/{ => pre}/consolidate-sql-error.md (100%) create mode 100644 .changeset/pre/cozy-geese-remain.md rename .changeset/{ => pre}/crisp-seas-warn.md (100%) create mode 100644 .changeset/pre/cron-locale-independent-aliases.md create mode 100644 .changeset/pre/cron-single-value-step.md create mode 100644 .changeset/pre/cron-testclock-infinity.md rename .changeset/{ => pre}/cuddly-rooms-bet.md (100%) create mode 100644 .changeset/pre/curly-files-range.md create mode 100644 .changeset/pre/curly-graphs-connect.md create mode 100644 .changeset/pre/curly-graphs-snapshot.md rename .changeset/{ => pre}/curly-poems-talk.md (100%) create mode 100644 .changeset/pre/curly-ravens-decode.md rename .changeset/{ => pre}/curly-spies-relax.md (100%) create mode 100644 .changeset/pre/curly-streams-stop.md rename .changeset/{ => pre}/curvy-apples-float.md (100%) rename .changeset/{ => pre}/curvy-birds-float.md (100%) create mode 100644 .changeset/pre/curvy-graphs-code.md create mode 100644 .changeset/pre/curvy-melons-stare.md create mode 100644 .changeset/pre/curvy-otters-sync.md create mode 100644 .changeset/pre/custom-cli-prompt-prefix.md rename .changeset/{ => pre}/custom-http-security-openapi-generator.md (100%) rename .changeset/{ => pre}/cute-heads-thank.md (100%) rename .changeset/{ => pre}/cyan-loops-grow.md (100%) rename .changeset/{ => pre}/cyan-radios-switch.md (100%) create mode 100644 .changeset/pre/cyan-shirts-grin.md create mode 100644 .changeset/pre/d1-batch-statements.md create mode 100644 .changeset/pre/dark-dancers-rule.md create mode 100644 .changeset/pre/deduplicate-json-schema-fallbacks.md rename .changeset/{ => pre}/deep-rivers-spend.md (100%) create mode 100644 .changeset/pre/deferred-cleanup-after-completion.md create mode 100644 .changeset/pre/deferred-resume-starvation.md create mode 100644 .changeset/pre/deno-write-copy-errors.md rename .changeset/{ => pre}/dirty-lamps-trade.md (100%) rename .changeset/{ => pre}/dirty-laws-wear.md (100%) create mode 100644 .changeset/pre/doctest-console-output.md create mode 100644 .changeset/pre/document-child-process-env.md create mode 100644 .changeset/pre/dry-bugs-hug.md rename .changeset/{ => pre}/duration-temporal-object-input.md (100%) rename .changeset/{ => pre}/eager-coats-cheat.md (100%) rename .changeset/{ => pre}/early-birds-dream.md (100%) rename .changeset/{ => pre}/early-donuts-argue.md (100%) create mode 100644 .changeset/pre/early-jobs-bow.md rename .changeset/{ => pre}/early-peaches-check.md (100%) create mode 100644 .changeset/pre/eff-115-sync-scheduler-microtask.md create mode 100644 .changeset/pre/eff-117-hydration-reactivity.md create mode 100644 .changeset/pre/eff-121-http-router-web-handler.md create mode 100644 .changeset/pre/eff-123-openai-compat-unknown-events.md create mode 100644 .changeset/pre/eff-137-web-stream-interop.md create mode 100644 .changeset/pre/eff-140-deno-crypto.md create mode 100644 .changeset/pre/eff-141-deno-child-process.md create mode 100644 .changeset/pre/eff-142-deno-terminal.md create mode 100644 .changeset/pre/eff-143-deno-stdio.md create mode 100644 .changeset/pre/eff-145-deno-services.md create mode 100644 .changeset/pre/eff-148-deno-http-platform.md create mode 100644 .changeset/pre/eff-151-deno-redis.md create mode 100644 .changeset/pre/eff-153-deno-http-server.md create mode 100644 .changeset/pre/eff-153-websocket-initial-frames.md create mode 100644 .changeset/pre/eff-154-deno-cluster-http.md create mode 100644 .changeset/pre/eff-155-deno-cluster-socket.md create mode 100644 .changeset/pre/eff-162-browser-crypto-chunks.md create mode 100644 .changeset/pre/eff-170-bun-multipart-stream.md create mode 100644 .changeset/pre/eff-210-cookie-validation.md create mode 100644 .changeset/pre/eff-212-secure-mssql-transport.md create mode 100644 .changeset/pre/eff-216-secure-http-redirects.md create mode 100644 .changeset/pre/eff-218-bound-sse-pending-state.md create mode 100644 .changeset/pre/eff-219-key-value-store-file-keys.md create mode 100644 .changeset/pre/eff-220-cli-control-characters.md create mode 100644 .changeset/pre/eff-332-http-response-compression.md create mode 100644 .changeset/pre/eff-337-preserve-mssql-parameters.md create mode 100644 .changeset/pre/eff-342-clickhouse-number-binding.md create mode 100644 .changeset/pre/eff-389-execution-plan-attempts.md create mode 100644 .changeset/pre/eff-428-pg-transaction-permit.md create mode 100644 .changeset/pre/eff-467-execution-plan-events.md create mode 100644 .changeset/pre/eff-477-schedule-concat.md create mode 100644 .changeset/pre/eff-487-web-tracer-shutdown.md create mode 100644 .changeset/pre/eff-51-partitioned-semaphore-interruption.md create mode 100644 .changeset/pre/eff-523-registry-scoped-atom-runtime.md create mode 100644 .changeset/pre/eff-532-stdio-terminal.md create mode 100644 .changeset/pre/eff-537-preserve-response-metadata.md create mode 100644 .changeset/pre/eff-542-rc-ref-generation.md create mode 100644 .changeset/pre/eff-547-node-tracer-timeout.md create mode 100644 .changeset/pre/eff-548-bun-serve-scope.md create mode 100644 .changeset/pre/eff-549-worker-send-error.md create mode 100644 .changeset/pre/eff-552-txqueue-shutdown.md create mode 100644 .changeset/pre/eff-554-sql-resolver.md create mode 100644 .changeset/pre/eff-558-deno-file-web-range.md rename .changeset/{ => pre}/eff-691-default-logger-ordering.md (100%) rename .changeset/{ => pre}/eff-693-rpcgroup-handler-deps.md (100%) rename .changeset/{ => pre}/eff-694-cli-completions-module.md (100%) rename .changeset/{ => pre}/eff-695-layer-mock-dual-api.md (100%) rename .changeset/{ => pre}/eff-697-rpcserialization-json-array-decode.md (100%) rename .changeset/{ => pre}/eff-698-rpcserialization-unreachable-branch.md (100%) rename .changeset/{ => pre}/eff-700-httpapi-middleware-errors.md (91%) rename .changeset/{ => pre}/eff-701-httpapierror-respondable.md (100%) rename .changeset/{ => pre}/eff-704-stream-merge-predicate.md (100%) rename .changeset/{ => pre}/eff-705-layer-tap-apis.md (100%) rename .changeset/{ => pre}/eff-706-servicemap-mutate.md (100%) rename .changeset/{ => pre}/eff-716-response-id-tracker-map.md (100%) rename .changeset/{ => pre}/eff-717-openai-socket-cancel.md (100%) rename .changeset/{ => pre}/eff-718-embedding-model-surface.md (100%) rename .changeset/{ => pre}/eff-725-fix-catch-jsdoc.md (100%) rename .changeset/{ => pre}/eff-726-model-dimensions.md (100%) rename .changeset/{ => pre}/eff-727-cli-help-alignment.md (100%) rename .changeset/{ => pre}/eff-730-language-model-incremental-fallback.md (100%) rename .changeset/{ => pre}/eff-736-cached-with-ttl.md (100%) rename .changeset/{ => pre}/eff-738-cron-prev.md (100%) rename .changeset/{ => pre}/eff-739-openai-function-call-done.md (100%) rename .changeset/{ => pre}/eff-740-missing-summary-parts.md (100%) rename .changeset/{ => pre}/eff-742-http-client-request-web.md (100%) rename .changeset/{ => pre}/eff-744-sqlite-migrator-lock.md (100%) rename .changeset/{ => pre}/eff-746-fixed-iteration-catchup.md (100%) rename .changeset/{ => pre}/eff-747-unify-effect.md (100%) rename .changeset/{ => pre}/eff-754-url-builder-any.md (100%) rename .changeset/{ => pre}/eff-755-references-core.md (100%) rename .changeset/{ => pre}/eff-769-select-text-highlight.md (100%) rename .changeset/{ => pre}/eff-774-mutable-list-append-all-empty-array.md (100%) rename .changeset/{ => pre}/eff-777-schema-make-effect.md (100%) rename .changeset/{ => pre}/eff-778-http-middleware-path-logger.md (100%) rename .changeset/{ => pre}/eff-779-keyvaluestore-layer-sql.md (100%) rename .changeset/{ => pre}/eff-780-layer-unify.md (100%) rename .changeset/{ => pre}/eff-781-fix-stream-toqueue-types.md (100%) rename .changeset/{ => pre}/eff-782-httpapi-status-literals.md (100%) rename .changeset/{ => pre}/eff-783-atom-http-api-errors.md (100%) create mode 100644 .changeset/pre/eff-798-schema-parser.md create mode 100644 .changeset/pre/eff-802-tagged-union-match-or-else.md create mode 100644 .changeset/pre/eff-806-schema-error-stack.md rename .changeset/{ => pre}/eff-819-cluster-workflow-shard-groups.md (100%) create mode 100644 .changeset/pre/eff-825-pool-performance.md create mode 100644 .changeset/pre/eff-825-pool-use.md create mode 100644 .changeset/pre/eff-825-scope-single-finalizer.md create mode 100644 .changeset/pre/eff-837-dependency-updates.md rename .changeset/{ => pre}/eff-849-transpose-option.md (100%) create mode 100644 .changeset/pre/eff-851-skip-empty-remote-write.md create mode 100644 .changeset/pre/eff-861-indexeddb-equals.md create mode 100644 .changeset/pre/eff-946-concurrent-traversal-cleanup.md create mode 100644 .changeset/pre/eff-952-terminal-failure-stack.md create mode 100644 .changeset/pre/eff-953-interruptor-stack-trace.md create mode 100644 .changeset/pre/eff-955-run-sync-dispatcher.md create mode 100644 .changeset/pre/eff-956-await-all-children.md rename .changeset/{ => pre}/eight-turkeys-own.md (100%) rename .changeset/{ => pre}/eighty-lies-deny.md (100%) rename .changeset/{ => pre}/eighty-poets-draw.md (100%) rename .changeset/{ => pre}/eighty-swans-scream.md (100%) rename .changeset/{ => pre}/eighty-teeth-sniff.md (100%) rename .changeset/{ => pre}/eleven-apes-share.md (100%) rename .changeset/{ => pre}/eleven-numbers-bake.md (100%) create mode 100644 .changeset/pre/empty-env-values-missing.md create mode 100644 .changeset/pre/empty-geckos-dispatch.md rename .changeset/{ => pre}/empty-gifts-beg.md (100%) rename .changeset/{ => pre}/empty-http-rpc-client.md (100%) create mode 100644 .changeset/pre/empty-snakes-return.md create mode 100644 .changeset/pre/escape-completion-choice-values.md rename .changeset/{ => pre}/eventlog-unencrypted.md (100%) rename .changeset/{ => pre}/every-olives-burn.md (100%) rename .changeset/{ => pre}/expand-schema-filter-output.md (100%) create mode 100644 .changeset/pre/explicit-env-record.md create mode 100644 .changeset/pre/explicit-otel-service-identity.md rename .changeset/{ => pre}/export-schema-encode-keys-interface.md (100%) create mode 100644 .changeset/pre/expose-ai-prompt-part-schemas.md rename .changeset/{ => pre}/extract-semaphore-latch.md (100%) create mode 100644 .changeset/pre/failed-otlp-checkpoints.md rename .changeset/{ => pre}/fair-bees-relax.md (100%) create mode 100644 .changeset/pre/fair-birds-limit.md rename .changeset/{ => pre}/fair-buttons-share.md (100%) create mode 100644 .changeset/pre/fair-citations-stream.md rename .changeset/{ => pre}/fair-cooks-stop.md (100%) rename .changeset/{ => pre}/fair-cups-train.md (100%) rename .changeset/{ => pre}/fair-dryers-speak.md (100%) rename .changeset/{ => pre}/fair-forks-shake.md (100%) create mode 100644 .changeset/pre/fair-jobs-like.md create mode 100644 .changeset/pre/fair-logs-correlate.md create mode 100644 .changeset/pre/fair-logs-listen.md rename .changeset/{ => pre}/fair-pandas-prove.md (100%) rename .changeset/{ => pre}/fair-pants-float.md (100%) rename .changeset/{ => pre}/fair-poems-visit.md (100%) create mode 100644 .changeset/pre/fair-sampling-content.md create mode 100644 .changeset/pre/fair-sinks-catch.md create mode 100644 .changeset/pre/famous-loops-flow.md rename .changeset/{ => pre}/famous-wolves-lead.md (100%) rename .changeset/{ => pre}/fancy-glasses-grow.md (100%) create mode 100644 .changeset/pre/fast-graph-path-queues.md rename .changeset/{ => pre}/fast-times-camp.md (100%) rename .changeset/{ => pre}/few-birds-matter.md (100%) rename .changeset/{ => pre}/few-cougars-dig.md (100%) rename .changeset/{ => pre}/few-foxes-grin.md (100%) rename .changeset/{ => pre}/few-mirrors-pull.md (100%) rename .changeset/{ => pre}/few-socks-poke.md (100%) create mode 100644 .changeset/pre/fiber-join-all-errors.md rename .changeset/{ => pre}/fiber-runtime-start-metrics.md (100%) rename .changeset/{ => pre}/fiery-jokes-care.md (100%) rename .changeset/{ => pre}/fiery-mammals-call.md (100%) create mode 100644 .changeset/pre/file-prompt-jk-filter.md rename .changeset/{ => pre}/fine-walls-decide.md (100%) rename .changeset/{ => pre}/first-success-of.md (100%) rename .changeset/{ => pre}/five-parents-relax.md (100%) rename .changeset/{ => pre}/five-worms-rhyme.md (100%) rename .changeset/{ => pre}/fix-1332.md (100%) rename .changeset/{ => pre}/fix-1917.md (100%) rename .changeset/{ => pre}/fix-1927.md (100%) rename .changeset/{ => pre}/fix-1940.md (100%) rename .changeset/{ => pre}/fix-1947.md (100%) rename .changeset/{ => pre}/fix-2002.md (100%) rename .changeset/{ => pre}/fix-2012.md (100%) rename .changeset/{ => pre}/fix-2015.md (100%) rename .changeset/{ => pre}/fix-2260.md (100%) rename .changeset/{ => pre}/fix-2268.md (100%) rename .changeset/{ => pre}/fix-2271.md (100%) rename .changeset/{ => pre}/fix-2384.md (100%) rename .changeset/{ => pre}/fix-2414.md (100%) rename .changeset/{ => pre}/fix-2419.md (100%) create mode 100644 .changeset/pre/fix-2497.md create mode 100644 .changeset/pre/fix-2499.md create mode 100644 .changeset/pre/fix-6464.md create mode 100644 .changeset/pre/fix-6491.md create mode 100644 .changeset/pre/fix-6521.md rename .changeset/{ => pre}/fix-ai-empty-params-structured-output.md (100%) rename .changeset/{ => pre}/fix-ai-text-toolkit-typing.md (100%) create mode 100644 .changeset/pre/fix-ai-tool-call-id.md create mode 100644 .changeset/pre/fix-ansi-cursor-to.md rename .changeset/{ => pre}/fix-anthropic-caller-toolid.md (100%) create mode 100644 .changeset/pre/fix-anthropic-code-execution-deltas.md create mode 100644 .changeset/pre/fix-anthropic-header-redaction.md create mode 100644 .changeset/pre/fix-anthropic-memory-tool-requires-handler.md create mode 100644 .changeset/pre/fix-anthropic-memory-tool.md create mode 100644 .changeset/pre/fix-anthropic-plaintext-bytes.md create mode 100644 .changeset/pre/fix-array-non-finite-indexes.md create mode 100644 .changeset/pre/fix-atom-batch-dependencies.md create mode 100644 .changeset/pre/fix-atom-kvs-async-write.md create mode 100644 .changeset/pre/fix-atom-suspense-registry-cache.md create mode 100644 .changeset/pre/fix-bash-subcommand-dispatch.md create mode 100644 .changeset/pre/fix-bigint-gcd-lcm.md create mode 100644 .changeset/pre/fix-cache-set-race.md rename .changeset/{ => pre}/fix-catch-orelse-error-erasure.md (100%) create mode 100644 .changeset/pre/fix-cause-map-annotations.md create mode 100644 .changeset/pre/fix-channel-effects-finalizer.md create mode 100644 .changeset/pre/fix-channel-schema-decode-unknown.md create mode 100644 .changeset/pre/fix-chunk-fractional-counts.md rename .changeset/{ => pre}/fix-class-constructor-defaults.md (100%) create mode 100644 .changeset/pre/fix-cli-help-table-spacing.md create mode 100644 .changeset/pre/fix-cli-missing-flag-values.md rename .changeset/{ => pre}/fix-cli-mixed-global-flag-context.md (100%) create mode 100644 .changeset/pre/fix-cli-subcommands-requirements.md create mode 100644 .changeset/pre/fix-cli-unexpected-arguments.md create mode 100644 .changeset/pre/fix-clickhouse-connect-timeout.md create mode 100644 .changeset/pre/fix-cluster-entity-context-bleed.md create mode 100644 .changeset/pre/fix-cluster-reply-defect-isolation.md create mode 100644 .changeset/pre/fix-cluster-shutdown-deadlock.md create mode 100644 .changeset/pre/fix-cluster-strand-request-shutdown.md create mode 100644 .changeset/pre/fix-cluster-stream-recovery.md rename .changeset/{ => pre}/fix-config-array-default.md (100%) create mode 100644 .changeset/pre/fix-config-or-else-evidence.md rename .changeset/{ => pre}/fix-config-withDefault.md (100%) rename .changeset/{ => pre}/fix-config-withdefault-filter.md (100%) create mode 100644 .changeset/pre/fix-context-add-or-omit-types.md create mode 100644 .changeset/pre/fix-cron-and-representations.md create mode 100644 .changeset/pre/fix-cron-make-validation.md rename .changeset/{ => pre}/fix-cron-next-missing-day-overflow.md (100%) create mode 100644 .changeset/pre/fix-cron-parser-semantics.md create mode 100644 .changeset/pre/fix-cron-prev-month-rollover.md create mode 100644 .changeset/pre/fix-cron-prev-weekday-wrap.md create mode 100644 .changeset/pre/fix-cron-timezone-hash.md rename .changeset/{ => pre}/fix-datetime-gmt.md (100%) rename .changeset/{ => pre}/fix-devtools-flush-on-teardown.md (100%) create mode 100644 .changeset/pre/fix-devtools-span-snapshot.md create mode 100644 .changeset/pre/fix-durable-clock-fractional-wakeup.md create mode 100644 .changeset/pre/fix-durable-deferred-race.md create mode 100644 .changeset/pre/fix-durable-race-replay.md create mode 100644 .changeset/pre/fix-duration-decimal-precision.md rename .changeset/{ => pre}/fix-duration-symmetric-rounding.md (100%) create mode 100644 .changeset/pre/fix-effect-fn-self.md create mode 100644 .changeset/pre/fix-effect-schedule-errors.md rename .changeset/{ => pre}/fix-entity-manager-defect-replay.md (100%) rename .changeset/{ => pre}/fix-entity-proxy-rpc-handler-context.md (100%) rename .changeset/{ => pre}/fix-entity-proxy-server-path-params.md (100%) create mode 100644 .changeset/pre/fix-eventlog-duplicate-chunks.md create mode 100644 .changeset/pre/fix-fiber-exit-observers.md create mode 100644 .changeset/pre/fix-fiberhandle-clear-race.md create mode 100644 .changeset/pre/fix-fiberset-json-id.md create mode 100644 .changeset/pre/fix-fiberset-runtime-interruption.md create mode 100644 .changeset/pre/fix-fish-command-path.md create mode 100644 .changeset/pre/fix-formatter-output-contracts.md create mode 100644 .changeset/pre/fix-from-json-string-identifier.md create mode 100644 .changeset/pre/fix-from-option-inline-inference.md rename .changeset/{ => pre}/fix-from-readable-stream-cancel-defect.md (100%) create mode 100644 .changeset/pre/fix-graph-allocator-equality.md create mode 100644 .changeset/pre/fix-graph-bellman-ford-self-cycle.md create mode 100644 .changeset/pre/fix-graph-curried-getters.md create mode 100644 .changeset/pre/fix-graph-dfs-astar-edge-cases.md create mode 100644 .changeset/pre/fix-graph-edge-transforms.md create mode 100644 .changeset/pre/fix-graph-finite-edge-weights.md create mode 100644 .changeset/pre/fix-graph-mutable-hash.md create mode 100644 .changeset/pre/fix-graph-mutable-topo.md create mode 100644 .changeset/pre/fix-graph-mutation-errors.md create mode 100644 .changeset/pre/fix-graph-topo-types.md rename .changeset/{ => pre}/fix-graph-undirected-traversal.md (100%) create mode 100644 .changeset/pre/fix-graph-walker-repeatability.md create mode 100644 .changeset/pre/fix-graphviz-dot-escaping.md create mode 100644 .changeset/pre/fix-group-by-key-types.md rename .changeset/{ => pre}/fix-has-interrupts-only-empty.md (100%) rename .changeset/{ => pre}/fix-hashmap-bit31-ordering.md (100%) create mode 100644 .changeset/pre/fix-hashmap-modify-hash.md rename .changeset/{ => pre}/fix-headers-proto-enumerability.md (100%) create mode 100644 .changeset/pre/fix-http-client-request-content-length.md rename .changeset/{ => pre}/fix-http-incoming-message-parse-options.md (100%) create mode 100644 .changeset/pre/fix-http-pre-response-handler-types.md create mode 100644 .changeset/pre/fix-http-server-request-raw-body.md create mode 100644 .changeset/pre/fix-http-server-response-body-headers.md rename .changeset/{ => pre}/fix-http-tracer-response-cause.md (100%) create mode 100644 .changeset/pre/fix-httpapi-authorization-decoding.md create mode 100644 .changeset/pre/fix-httpapi-client-error-content-type.md rename .changeset/{ => pre}/fix-httpapi-endpoint-error-inference.md (100%) create mode 100644 .changeset/pre/fix-httpapi-malformed-json-400.md create mode 100644 .changeset/pre/fix-httpapi-runtime-shape.md rename .changeset/{ => pre}/fix-httpapi-schema-types.md (100%) rename .changeset/{ => pre}/fix-httpapi-security-middleware-cache.md (100%) create mode 100644 .changeset/pre/fix-httpapi-single-array-query.md create mode 100644 .changeset/pre/fix-invalid-openapi-examples.md create mode 100644 .changeset/pre/fix-invalid-value-doubled-expected.md rename .changeset/{ => pre}/fix-is-json-dag.md (100%) create mode 100644 .changeset/pre/fix-iterable-flatten-stack-safety.md rename .changeset/{ => pre}/fix-json-schema-anyof-oneof-siblings.md (100%) rename .changeset/{ => pre}/fix-json-schema-import-json.md (100%) create mode 100644 .changeset/pre/fix-json-schema-reference-resolution.md create mode 100644 .changeset/pre/fix-json-schema-unique-items-false.md rename .changeset/{ => pre}/fix-keepalive-blocked-timers.md (100%) create mode 100644 .changeset/pre/fix-language-model-stream-concurrency.md create mode 100644 .changeset/pre/fix-latch-stale-flush.md create mode 100644 .changeset/pre/fix-layermap-preload.md create mode 100644 .changeset/pre/fix-mcp-call-tool-arguments.md create mode 100644 .changeset/pre/fix-mcp-completion-context.md rename .changeset/{ => pre}/fix-mcp-param-name-resolution.md (100%) create mode 100644 .changeset/pre/fix-mcp-request-errors.md create mode 100644 .changeset/pre/fix-memory-journal-conflicts.md create mode 100644 .changeset/pre/fix-memory-journal-next-sequence.md create mode 100644 .changeset/pre/fix-memory-journal-relay.md create mode 100644 .changeset/pre/fix-memory-runner-health.md create mode 100644 .changeset/pre/fix-memory-workflow-interrupt.md rename .changeset/{ => pre}/fix-mermaid-escape-special-chars.md (100%) create mode 100644 .changeset/pre/fix-message-storage-clear-address-dedup.md create mode 100644 .changeset/pre/fix-metric-attribute-key-collisions.md create mode 100644 .changeset/pre/fix-metric-is-metric.md create mode 100644 .changeset/pre/fix-metric-linear-boundaries.md create mode 100644 .changeset/pre/fix-metric-negative-max.md create mode 100644 .changeset/pre/fix-mssql-multitable-persistence-upsert.md create mode 100644 .changeset/pre/fix-multipart-file-stream-limits.md create mode 100644 .changeset/pre/fix-mutable-list-bounds.md create mode 100644 .changeset/pre/fix-mutable-list-empty-filter.md rename .changeset/{ => pre}/fix-mutable-list-filter-length.md (100%) create mode 100644 .changeset/pre/fix-ndjson-split-utf8.md create mode 100644 .changeset/pre/fix-node-http-stream-failure.md create mode 100644 .changeset/pre/fix-node-path-file-url-flavor.md create mode 100644 .changeset/pre/fix-node-pipeline-kill.md create mode 100644 .changeset/pre/fix-number-remainder-scientific-notation.md rename .changeset/{ => pre}/fix-object-keyword-json-schema.md (100%) create mode 100644 .changeset/pre/fix-one-shot-iterables.md create mode 100644 .changeset/pre/fix-openai-header-redaction.md rename .changeset/{ => pre}/fix-openai-mcp-tool-names.md (100%) create mode 100644 .changeset/pre/fix-openai-response-failure.md create mode 100644 .changeset/pre/fix-openai-specialized-tool-output.md create mode 100644 .changeset/pre/fix-openai-system-input-text.md create mode 100644 .changeset/pre/fix-openai-telemetry-response-namespace.md create mode 100644 .changeset/pre/fix-openai-web-search-action.md create mode 100644 .changeset/pre/fix-openapi-from-api-cache-copy.md rename .changeset/{ => pre}/fix-openapi-generator-form-urlencoded.md (100%) rename .changeset/{ => pre}/fix-openapi-generator-swagger2openapi.md (100%) rename .changeset/{ => pre}/fix-openapi-preserve-multiple-response-content-types.md (100%) rename .changeset/{ => pre}/fix-openrouter-sparse-array.md (100%) create mode 100644 .changeset/pre/fix-otel-logger-clock-skew.md rename .changeset/{ => pre}/fix-otel-logger-severity-number.md (100%) create mode 100644 .changeset/pre/fix-otel-logger-shutdown.md create mode 100644 .changeset/pre/fix-otlp-exporter-shutdown.md create mode 100644 .changeset/pre/fix-otlp-resource-attributes.md create mode 100644 .changeset/pre/fix-pending-interruptible-mask.md create mode 100644 .changeset/pre/fix-persisted-cluster-reply-hang.md create mode 100644 .changeset/pre/fix-persisted-queue-attempt-accounting.md create mode 100644 .changeset/pre/fix-prompt-all-iterables.md create mode 100644 .changeset/pre/fix-proto-record-assignment.md create mode 100644 .changeset/pre/fix-pubsub-replay-retention.md rename .changeset/{ => pre}/fix-queue-collect-duplication.md (100%) rename .changeset/{ => pre}/fix-random-string-seeds.md (100%) create mode 100644 .changeset/pre/fix-rate-limiter-sleep.md rename .changeset/{ => pre}/fix-ratelimiter-tokenbucket-redis-ttl.md (100%) create mode 100644 .changeset/pre/fix-react-native-sqlite-values.md create mode 100644 .changeset/pre/fix-reactive-query-metadata.md create mode 100644 .changeset/pre/fix-redis-persisted-queue.md rename .changeset/{ => pre}/fix-remainder-scientific-notation.md (100%) rename .changeset/{ => pre}/fix-request-resolver-pending-batches-leak.md (100%) rename .changeset/{ => pre}/fix-retry-transient-autocomplete.md (100%) rename .changeset/{ => pre}/fix-rpc-http-requestids-finalizer.md (100%) rename .changeset/{ => pre}/fix-rpc-json-id-edges.md (100%) rename .changeset/{ => pre}/fix-rpc-unknown-tag-isolation.md (100%) create mode 100644 .changeset/pre/fix-runner-stream-completion.md create mode 100644 .changeset/pre/fix-schedule-during.md rename .changeset/{ => pre}/fix-schedule-fixed-double-exec.md (100%) rename .changeset/{ => pre}/fix-schedule-reduce-sync-state.md (100%) rename .changeset/{ => pre}/fix-schema-arbitrary-exclusive-bounds.md (100%) create mode 100644 .changeset/pre/fix-schema-bracket-prototype-pollution.md rename .changeset/{ => pre}/fix-schema-defect-message.md (100%) rename .changeset/{ => pre}/fix-schema-encode-keys-property-keys.md (100%) rename .changeset/{ => pre}/fix-schema-encodekeys-class.md (100%) rename .changeset/{ => pre}/fix-schema-encodekeys-struct.md (100%) rename .changeset/{ => pre}/fix-schema-encoding-checks.md (100%) rename .changeset/{ => pre}/fix-schema-identifier-expected-message.md (100%) create mode 100644 .changeset/pre/fix-schema-is-json-records.md rename .changeset/{ => pre}/fix-schema-is-uuid.md (100%) create mode 100644 .changeset/pre/fix-schema-json-tuple-allof.md create mode 100644 .changeset/pre/fix-schema-make-nested-class-union.md rename .changeset/{ => pre}/fix-schema-option-non-schema-failures.md (100%) rename .changeset/{ => pre}/fix-schema-parser-checks.md (100%) create mode 100644 .changeset/pre/fix-schema-pattern-state.md create mode 100644 .changeset/pre/fix-schema-representation-identifiers.md create mode 100644 .changeset/pre/fix-schema-sentinel-declaration.md create mode 100644 .changeset/pre/fix-schema-tuple-post-rest-indexing.md create mode 100644 .changeset/pre/fix-schema-union-dispatch-order.md create mode 100644 .changeset/pre/fix-schema-union-pruning.md create mode 100644 .changeset/pre/fix-scoped-ref-failed-replacement.md rename .changeset/{ => pre}/fix-searchparam-initial-decode.md (100%) create mode 100644 .changeset/pre/fix-semaphore-with-permits-interrupt-leak.md rename .changeset/{ => pre}/fix-serializable-wire-transfer.md (100%) create mode 100644 .changeset/pre/fix-sliding-size-chunks.md create mode 100644 .changeset/pre/fix-sql-persisted-queue-lock-refresh.md create mode 100644 .changeset/pre/fix-sqlite-persisted-queue.md create mode 100644 .changeset/pre/fix-sse-last-event-id.md create mode 100644 .changeset/pre/fix-sse-leading-bom.md create mode 100644 .changeset/pre/fix-sse-retry-directives.md create mode 100644 .changeset/pre/fix-stream-aggregate-within-idle.md create mode 100644 .changeset/pre/fix-stream-execution-plan-retries.md rename .changeset/{ => pre}/fix-stream-grouped-within-flush.md (100%) create mode 100644 .changeset/pre/fix-stream-haltwhen.md create mode 100644 .changeset/pre/fix-stream-map-accum-array-effect.md create mode 100644 .changeset/pre/fix-stream-range-zero-chunk.md rename .changeset/{ => pre}/fix-stream-run-for-each-while.md (100%) rename .changeset/{ => pre}/fix-stream-scan-effect.md (100%) rename .changeset/{ => pre}/fix-stream-scoped-scope.md (100%) create mode 100644 .changeset/pre/fix-string-case-digits.md rename .changeset/{ => pre}/fix-strip-approval-artifacts-multi-round.md (100%) rename .changeset/{ => pre}/fix-struct-utility-types-simplify.md (100%) create mode 100644 .changeset/pre/fix-structural-proto-equality.md rename .changeset/{ => pre}/fix-structwithrest-index-signatures.md (100%) create mode 100644 .changeset/pre/fix-subscription-ref-get-and-update-some.md create mode 100644 .changeset/pre/fix-subscriptionref-getandupdateeffect.md rename .changeset/{ => pre}/fix-tagged-union-class-sentinels.md (100%) rename .changeset/{ => pre}/fix-tagged-union-match-unify.md (100%) rename .changeset/{ => pre}/fix-to-tagged-union-isanyof-custom-tags.md (100%) create mode 100644 .changeset/pre/fix-tool-provider-defined-clone.md create mode 100644 .changeset/pre/fix-trie-key-replacement.md create mode 100644 .changeset/pre/fix-trie-longest-prefix.md create mode 100644 .changeset/pre/fix-trie-undefined-values.md rename .changeset/{ => pre}/fix-tuple-with-rest-post-rest-index-drift.md (100%) rename .changeset/{ => pre}/fix-tuple-with-rest-post-rest-validation.md (100%) create mode 100644 .changeset/pre/fix-txpubsub-publish-all-iterables.md create mode 100644 .changeset/pre/fix-txqueue-closing-drain.md create mode 100644 .changeset/pre/fix-txqueue-offer-all-iterables.md rename .changeset/{ => pre}/fix-types-voidifempty.md (100%) create mode 100644 .changeset/pre/fix-variant-schema-default-cache.md create mode 100644 .changeset/pre/fix-vitest-proxy-chained-helpers.md create mode 100644 .changeset/pre/fix-vitest-record-schema-arbitrary.md create mode 100644 .changeset/pre/fix-vitest-runner-import.md create mode 100644 .changeset/pre/fix-vitest-throws-assertions.md create mode 100644 .changeset/pre/fix-void-mcp-tool-results.md rename .changeset/{ => pre}/fix-void-response-encoding.md (100%) create mode 100644 .changeset/pre/fix-worker-runner-cleanup.md rename .changeset/{ => pre}/fix-workflow-defect-reply-serialization.md (100%) create mode 100644 .changeset/pre/fix-workflow-entity-client-collision.md rename .changeset/{ => pre}/fix-workflow-proxy-rpc-handler-context.md (100%) create mode 100644 .changeset/pre/fix-workflow-trace-context.md create mode 100644 .changeset/pre/fix-xhr-form-data.md create mode 100644 .changeset/pre/fix-zero-max-bytes.md create mode 100644 .changeset/pre/fix-zsh-mixed-command-completions.md rename .changeset/{ => pre}/flat-chicken-remain.md (100%) create mode 100644 .changeset/pre/flat-graphs-agree.md rename .changeset/{ => pre}/floppy-cows-spend.md (100%) create mode 100644 .changeset/pre/floppy-frogs-juggle.md rename .changeset/{ => pre}/floppy-items-admire.md (100%) rename .changeset/{ => pre}/floppy-pigs-kiss.md (100%) create mode 100644 .changeset/pre/floppy-rats-leave.md create mode 100644 .changeset/pre/floyd-warshall-null-edge-data.md rename .changeset/{ => pre}/fluffy-meals-matter.md (100%) rename .changeset/{ => pre}/fluffy-pumas-push.md (100%) rename .changeset/{ => pre}/forked-memo-maps.md (100%) rename .changeset/{ => pre}/forty-hounds-cheer.md (100%) rename .changeset/{ => pre}/forty-otters-cry.md (100%) rename .changeset/{ => pre}/forty-rings-film.md (100%) rename .changeset/{ => pre}/forty-signs-stay.md (100%) rename .changeset/{ => pre}/forty-swans-divide.md (100%) rename .changeset/{ => pre}/forty-trees-pay.md (100%) rename .changeset/{ => pre}/four-papayas-bow.md (100%) rename .changeset/{ => pre}/four-points-repeat.md (100%) create mode 100644 .changeset/pre/frank-apes-vanish.md rename .changeset/{ => pre}/fresh-cats-smash.md (100%) create mode 100644 .changeset/pre/fresh-cycles-wait.md create mode 100644 .changeset/pre/fresh-deps-update.md rename .changeset/{ => pre}/fresh-emus-cheat.md (100%) create mode 100644 .changeset/pre/fresh-files-seek.md create mode 100644 .changeset/pre/fresh-forms-travel.md create mode 100644 .changeset/pre/fresh-graphs-cycle.md create mode 100644 .changeset/pre/fresh-images-generate.md create mode 100644 .changeset/pre/fresh-lines-wait.md rename .changeset/{ => pre}/fresh-monkeys-smoke.md (100%) create mode 100644 .changeset/pre/fresh-rivers-report.md rename .changeset/{ => pre}/frozen-intrinsics-stack-trace-limit.md (100%) rename .changeset/{ => pre}/fruity-houses-learn.md (100%) create mode 100644 .changeset/pre/fruity-sloths-walk.md rename .changeset/{ => pre}/full-adults-double.md (100%) rename .changeset/{ => pre}/funny-crabs-hang.md (100%) create mode 100644 .changeset/pre/funny-ears-beam.md rename .changeset/{ => pre}/funny-forks-move.md (100%) create mode 100644 .changeset/pre/fuzzy-batches-stop.md create mode 100644 .changeset/pre/fuzzy-caches-expire.md rename .changeset/{ => pre}/fuzzy-camels-hunt.md (100%) create mode 100644 .changeset/pre/fuzzy-cats-kill.md create mode 100644 .changeset/pre/fuzzy-cats-listen.md rename .changeset/{ => pre}/fuzzy-crews-fold.md (100%) create mode 100644 .changeset/pre/fuzzy-databases-abort.md rename .changeset/{ => pre}/fuzzy-dodos-help.md (100%) create mode 100644 .changeset/pre/fuzzy-files-slice.md create mode 100644 .changeset/pre/fuzzy-graphs-transform.md create mode 100644 .changeset/pre/fuzzy-hornets-wish.md rename .changeset/{ => pre}/fuzzy-lions-perform.md (100%) create mode 100644 .changeset/pre/fuzzy-lions-study.md create mode 100644 .changeset/pre/fuzzy-pandas-smile.md rename .changeset/{ => pre}/fuzzy-planets-sneeze.md (100%) create mode 100644 .changeset/pre/fuzzy-rabbits-cancel.md create mode 100644 .changeset/pre/fuzzy-ravens-reason.md create mode 100644 .changeset/pre/fuzzy-routers-smile.md rename .changeset/{icy-flies-cross.md => pre/fuzzy-stamps-care.md} (100%) create mode 100644 .changeset/pre/fuzzy-timers-smile.md create mode 100644 .changeset/pre/general-json-schema-converters.md create mode 100644 .changeset/pre/giant-jeans-float.md rename .changeset/{ => pre}/gold-meteors-move.md (100%) rename .changeset/{ => pre}/gold-readers-hug.md (100%) rename .changeset/{ => pre}/gold-rings-start.md (100%) create mode 100644 .changeset/pre/good-cups-reply.md rename .changeset/{ => pre}/good-tools-work.md (100%) rename .changeset/{ => pre}/good-trees-pull.md (100%) create mode 100644 .changeset/pre/graph-acyclic-parallel-undirected-edges.md rename .changeset/{ => pre}/graph-algorithm-fixes.md (100%) create mode 100644 .changeset/pre/graph-finalized-mutation-handle.md create mode 100644 .changeset/pre/graph-guard-predicates.md create mode 100644 .changeset/pre/graph-sync-mutation-callbacks.md create mode 100644 .changeset/pre/graph-undirected-edge-equality.md create mode 100644 .changeset/pre/graph-walker-iterator-receiver.md rename .changeset/{ => pre}/great-trains-mate.md (100%) rename .changeset/{ => pre}/great-trams-report.md (100%) create mode 100644 .changeset/pre/green-ads-camp.md rename .changeset/{ => pre}/green-beds-unref.md (100%) create mode 100644 .changeset/pre/green-birds-close.md rename .changeset/{ => pre}/green-chips-wash.md (100%) rename .changeset/{ => pre}/green-moons-smile.md (100%) rename .changeset/{ => pre}/green-pugs-play.md (100%) rename .changeset/{ => pre}/green-rings-prove.md (100%) rename .changeset/{ => pre}/happy-mirrors-dream.md (100%) create mode 100644 .changeset/pre/harden-httpapi-documentation-html.md create mode 100644 .changeset/pre/hash-sql-message-dedupe-keys.md rename .changeset/{ => pre}/heavy-loops-cut.md (100%) rename .changeset/{ => pre}/heavy-trams-fix.md (100%) create mode 100644 .changeset/pre/hip-friends-kiss.md rename .changeset/{ => pre}/hip-socks-travel.md (100%) rename .changeset/{ => pre}/honest-pens-thank.md (100%) rename .changeset/{ => pre}/honest-rivers-notice.md (100%) rename .changeset/{ => pre}/hot-taxis-fry.md (100%) create mode 100644 .changeset/pre/hot-teeth-clean.md create mode 100644 .changeset/pre/http-json-reviver.md create mode 100644 .changeset/pre/http-server-websocket-options.md create mode 100644 .changeset/pre/http-status-from-literal.md rename .changeset/{ => pre}/httpapi-endpoint-relax-constraints.md (100%) rename .changeset/{ => pre}/httpapi-schema-service-types.md (100%) rename .changeset/{ => pre}/huge-moons-rhyme.md (100%) rename .changeset/{ => pre}/humble-pigs-dig.md (100%) create mode 100644 .changeset/pre/hungry-kings-look.md rename .changeset/{stale-snakes-know.md => pre/icy-flies-cross.md} (100%) create mode 100644 .changeset/pre/internal-json-string-schema.md create mode 100644 .changeset/pre/isolate-sql-compiler-cache.md rename .changeset/{ => pre}/itchy-radios-poke.md (100%) rename .changeset/{ => pre}/itchy-results-bet.md (100%) rename .changeset/{ => pre}/itchy-shrimps-deny.md (100%) rename .changeset/{ => pre}/itchy-toes-promise.md (100%) rename .changeset/{ => pre}/k8s-last-transition-null.md (100%) create mode 100644 .changeset/pre/keep-httpapi-composition-immutable.md rename .changeset/{ => pre}/khaki-cats-learn.md (100%) rename .changeset/{ => pre}/khaki-melons-appear.md (100%) create mode 100644 .changeset/pre/kind-flags-help.md rename .changeset/{ => pre}/kind-hounds-float.md (100%) rename .changeset/{ => pre}/kind-windows-fall.md (100%) rename .changeset/{ => pre}/late-hotels-rule.md (100%) rename .changeset/{ => pre}/late-lamps-care.md (100%) rename .changeset/{ => pre}/late-rivers-applaud.md (100%) create mode 100644 .changeset/pre/late-sockets-rest.md rename .changeset/{ => pre}/layer-map-dynamic-idle-ttl.md (100%) create mode 100644 .changeset/pre/layered-context-storage.md create mode 100644 .changeset/pre/lazy-openapi-initialization.md rename .changeset/{ => pre}/lazy-queens-rush.md (100%) rename .changeset/{ => pre}/lazy-recursive-forward-refs.md (100%) rename .changeset/{ => pre}/lazy-timers-exist.md (100%) rename .changeset/{ => pre}/legal-pants-drop.md (100%) rename .changeset/{ => pre}/lemon-taxis-sin.md (100%) rename .changeset/{ => pre}/light-kids-sneeze.md (100%) rename .changeset/{ => pre}/little-dryers-allow.md (100%) rename .changeset/{ => pre}/long-cameras-think.md (100%) create mode 100644 .changeset/pre/loose-wings-lie.md rename .changeset/{ => pre}/lovely-cobras-change.md (100%) rename .changeset/{ => pre}/lovely-frogs-rescue.md (100%) rename .changeset/{ => pre}/lucky-buttons-jump.md (100%) create mode 100644 .changeset/pre/lucky-dingos-smile.md rename .changeset/{ => pre}/lucky-phones-listen.md (100%) rename .changeset/{ => pre}/lucky-worms-type.md (100%) create mode 100644 .changeset/pre/major-chairs-design.md create mode 100644 .changeset/pre/managed-runtime-async-dispose.md rename .changeset/{ => pre}/many-badgers-obey.md (100%) create mode 100644 .changeset/pre/match-generic-value-terminals.md create mode 100644 .changeset/pre/mcp-http-initialize-version-header.md create mode 100644 .changeset/pre/mcp-protocol-versions.md create mode 100644 .changeset/pre/mcp-tool-output-schema.md rename .changeset/{ => pre}/mean-dingos-share.md (100%) rename .changeset/{ => pre}/mean-trains-smash.md (100%) create mode 100644 .changeset/pre/memoize-idempotent-asts.md create mode 100644 .changeset/pre/merge-resource-failures.md create mode 100644 .changeset/pre/metal-nails-sneeze.md rename .changeset/{ => pre}/metal-parts-yell.md (100%) rename .changeset/{ => pre}/mighty-games-matter.md (100%) create mode 100644 .changeset/pre/migrate-persisted-queues.md create mode 100644 .changeset/pre/migrator-windows-file-url.md rename .changeset/{ => pre}/modern-carrots-see.md (100%) rename .changeset/{ => pre}/modern-uuid-guid-filter.md (100%) create mode 100644 .changeset/pre/multipart-collect-linear.md create mode 100644 .changeset/pre/multipart-onDone-clobbers-error.md create mode 100644 .changeset/pre/multipart-parser-limits.md create mode 100644 .changeset/pre/mysql2-disable-prepared-statements.md create mode 100644 .changeset/pre/narrow-schedule-while.md rename .changeset/{ => pre}/nasty-geese-grow.md (100%) rename .changeset/{ => pre}/neat-goats-wave.md (100%) create mode 100644 .changeset/pre/neat-graphs-induced.md rename .changeset/{ => pre}/neat-kings-chew.md (91%) rename .changeset/{ => pre}/neat-lions-rest.md (100%) create mode 100644 .changeset/pre/neat-pandas-query.md rename .changeset/{ => pre}/neat-snails-wash.md (100%) rename .changeset/{ => pre}/neat-taxis-notice.md (100%) create mode 100644 .changeset/pre/neat-tuples-remember.md rename .changeset/{ => pre}/neat-windows-buy.md (100%) create mode 100644 .changeset/pre/nested-union-sentinels.md rename .changeset/{ => pre}/new-dogs-swim.md (100%) rename .changeset/{ => pre}/new-toes-stop.md (100%) rename .changeset/{ => pre}/ninety-geese-exist.md (100%) create mode 100644 .changeset/pre/node-redis-client.md create mode 100644 .changeset/pre/node-terminal-idle-ttl.md create mode 100644 .changeset/pre/node-terminal-stdin-eof.md create mode 100644 .changeset/pre/normalize-httpapi-payload-media-types.md create mode 100644 .changeset/pre/normalize-unbounded-pubsub-replay.md create mode 100644 .changeset/pre/o8drprcu-sqlite-node-node-sqlite.md rename .changeset/{ => pre}/odd-boats-think.md (100%) rename .changeset/{ => pre}/odd-bulldogs-sleep.md (100%) rename .changeset/{ => pre}/odd-fans-glow.md (100%) rename .changeset/{ => pre}/odd-forks-talk.md (100%) rename .changeset/{ => pre}/odd-laws-draw.md (100%) rename .changeset/{ => pre}/odd-owls-smoke.md (100%) rename .changeset/{ => pre}/odd-socks-boil.md (100%) rename .changeset/{ => pre}/odd-suns-dance.md (100%) rename .changeset/{ => pre}/old-brooms-cry.md (100%) rename .changeset/{ => pre}/old-facts-stand.md (100%) rename .changeset/{ => pre}/old-mirrors-float.md (100%) rename .changeset/{ => pre}/olive-poems-visit.md (100%) create mode 100644 .changeset/pre/opaque-graph-interface.md rename .changeset/{ => pre}/open-hotels-remain.md (100%) create mode 100644 .changeset/pre/open-objects-intersect.md create mode 100644 .changeset/pre/openai-compat-decode-tool-params.md create mode 100644 .changeset/pre/openai-compat-empty-assistant-content.md create mode 100644 .changeset/pre/openai-compat-null-tool-calls.md rename .changeset/{ => pre}/openai-compat-nullable-tool-name.md (100%) create mode 100644 .changeset/pre/openai-compat-parallel-tool-calls.md rename .changeset/{ => pre}/openai-compat-reasoning.md (100%) create mode 100644 .changeset/pre/openai-compatible-quota-errors.md create mode 100644 .changeset/pre/openai-telemetry-response.md create mode 100644 .changeset/pre/openai-text-prompt-cache.md create mode 100644 .changeset/pre/openai-tolerant-error-stream-event.md rename .changeset/{ => pre}/openapi-generator-sse-constraint-decoder.md (100%) create mode 100644 .changeset/pre/openapi-path-parameters.md create mode 100644 .changeset/pre/openapi-pointer-references.md create mode 100644 .changeset/pre/openapi-response-variants.md create mode 100644 .changeset/pre/openrouter-generation-usage-schema.md rename .changeset/{ => pre}/openrouter-input-audio.md (100%) create mode 100644 .changeset/pre/openrouter-tool-parameter-deltas.md create mode 100644 .changeset/pre/opentelemetry-render-causes.md create mode 100644 .changeset/pre/optic-standalone-functions.md create mode 100644 .changeset/pre/optimize-array-equality.md create mode 100644 .changeset/pre/optimize-httpapi-handler-types.md create mode 100644 .changeset/pre/optimize-node-http-server-response.md create mode 100644 .changeset/pre/optimize-schema-class-decoding.md create mode 100644 .changeset/pre/optional-ai-embedding-usage.md create mode 100644 .changeset/pre/optional-ai-response-fields.md rename .changeset/{ => pre}/otel-resource-env-precedence.md (100%) rename .changeset/{ => pre}/perfect-buckets-tickle.md (100%) rename .changeset/{ => pre}/petite-months-allow.md (100%) create mode 100644 .changeset/pre/pg-client-connect-error-handler.md create mode 100644 .changeset/pre/plain-variant-unions.md rename .changeset/{ => pre}/platform-crypto-service.md (100%) rename .changeset/{ => pre}/platform-node-shared-barrel.md (100%) rename .changeset/{ => pre}/plenty-moons-pull.md (100%) rename .changeset/{ => pre}/polite-brooms-tickle.md (100%) create mode 100644 .changeset/pre/polite-cameras-rest.md create mode 100644 .changeset/pre/polite-dingos-unite.md rename .changeset/{ => pre}/polite-pigs-speak.md (100%) rename .changeset/{ => pre}/polite-tables-kneel.md (100%) create mode 100644 .changeset/pre/port-effect-reduce.md rename .changeset/{ => pre}/port-react-hydration.md (100%) create mode 100644 .changeset/pre/precise-clocks-rest.md create mode 100644 .changeset/pre/precise-test-clock-nanos.md create mode 100644 .changeset/pre/preserve-command-hidden-metadata.md create mode 100644 .changeset/pre/preserve-config-all-input-evidence.md create mode 100644 .changeset/pre/preserve-openapi-raw-json.md create mode 100644 .changeset/pre/preserve-otel-parent-context.md create mode 100644 .changeset/pre/preserve-unsafe-workflow-interrupts.md create mode 100644 .changeset/pre/pretty-moments-clap.md create mode 100644 .changeset/pre/protect-schema-issue-actuals.md create mode 100644 .changeset/pre/proud-ears-say.md create mode 100644 .changeset/pre/provider-executed-tool-results.md rename .changeset/{ => pre}/public-deer-ring.md (100%) rename .changeset/{ => pre}/public-jeans-stop.md (100%) rename .changeset/{ => pre}/pubsub-publish-false.md (100%) rename .changeset/{ => pre}/puny-pens-clap.md (100%) rename .changeset/{ => pre}/purple-bars-prove.md (100%) rename .changeset/{ => pre}/purple-schools-float.md (100%) rename .changeset/{ => pre}/purple-turtles-draw.md (100%) rename .changeset/{ => pre}/quick-dragons-fix.md (100%) rename .changeset/{ => pre}/quick-falcons-travel.md (100%) rename .changeset/{ => pre}/quick-geese-relax.md (100%) create mode 100644 .changeset/pre/quick-graphs-paths.md create mode 100644 .changeset/pre/quick-kiwis-remember.md rename .changeset/{ => pre}/quick-lamps-dig.md (100%) rename .changeset/{ => pre}/quick-lizards-fall.md (100%) create mode 100644 .changeset/pre/quick-schedulers-promise.md rename .changeset/{ => pre}/quick-trees-join.md (100%) rename .changeset/{ => pre}/quiet-carpets-grin.md (100%) create mode 100644 .changeset/pre/quiet-clis-parse.md create mode 100644 .changeset/pre/quiet-crons-report.md create mode 100644 .changeset/pre/quiet-fibers-settle.md rename .changeset/{ => pre}/quiet-files-hunt.md (100%) create mode 100644 .changeset/pre/quiet-files-write.md create mode 100644 .changeset/pre/quiet-graphs-guard.md create mode 100644 .changeset/pre/quiet-graphs-search.md rename .changeset/{ => pre}/quiet-lamps-jam.md (100%) create mode 100644 .changeset/pre/quiet-mice-negotiate.md create mode 100644 .changeset/pre/quiet-observers-report.md create mode 100644 .changeset/pre/quiet-otters-retry.md create mode 100644 .changeset/pre/quiet-owls-validate.md create mode 100644 .changeset/pre/quiet-pandas-rebuild.md create mode 100644 .changeset/pre/quiet-pandas-respond.md rename .changeset/{ => pre}/quiet-radios-wave.md (100%) rename .changeset/{ => pre}/quiet-redis-scripts.md (100%) create mode 100644 .changeset/pre/quiet-rpcs-buffer.md create mode 100644 .changeset/pre/quiet-savepoints-wait.md create mode 100644 .changeset/pre/quiet-sockets-close.md create mode 100644 .changeset/pre/quiet-spans-rest.md rename .changeset/{ => pre}/quiet-tigers-yell.md (100%) create mode 100644 .changeset/pre/quiet-tools-smile.md create mode 100644 .changeset/pre/quiet-turtles-smile.md rename .changeset/{ => pre}/random-choice.md (100%) create mode 100644 .changeset/pre/random-hex-encoding.md create mode 100644 .changeset/pre/read-only-bun-sqlite.md rename .changeset/{ => pre}/ready-olives-divide.md (100%) rename .changeset/{ => pre}/real-trains-ring.md (100%) create mode 100644 .changeset/pre/record-from-iterable-by-dual.md rename .changeset/{ => pre}/red-pigs-repair.md (100%) create mode 100644 .changeset/pre/redact-cli-wizard-values.md rename .changeset/{ => pre}/redacted-representation-options.md (100%) create mode 100644 .changeset/pre/redis-subscribe-api.md rename .changeset/{ => pre}/refactor-cli-global-flags.md (100%) rename .changeset/{ => pre}/refactor-config-provider.md (100%) rename .changeset/{ => pre}/refactor-representation-references.md (100%) create mode 100644 .changeset/pre/refine-config-absence.md create mode 100644 .changeset/pre/refresh-mcp-roots.md create mode 100644 .changeset/pre/register-param-alternate-flags.md create mode 100644 .changeset/pre/release-rpc-worker-pool-entries.md create mode 100644 .changeset/pre/remove-context-mutate.md rename .changeset/{ => pre}/remove-effect-transactionwith.md (100%) rename .changeset/{ => pre}/remove-http-span-counter.md (100%) create mode 100644 .changeset/pre/remove-kubernetes-types.md rename .changeset/{ => pre}/remove-nullor.md (100%) rename .changeset/{ => pre}/remove-openapi-fromapi-options.md (100%) create mode 100644 .changeset/pre/remove-schedule-apis.md create mode 100644 .changeset/pre/remove-schedule-either.md create mode 100644 .changeset/pre/remove-schedule-elapsed.md create mode 100644 .changeset/pre/remove-schedule-taps.md create mode 100644 .changeset/pre/remove-schema-key-value-combiner.md rename .changeset/{ => pre}/remove-schema-stringtree-keep-declarations.md (100%) create mode 100644 .changeset/pre/remove-schema-utils.md rename .changeset/{ => pre}/remove-types-mergerecord.md (100%) rename .changeset/{ => pre}/remove-unused-utils-apis.md (100%) create mode 100644 .changeset/pre/remove-with-concurrency.md rename .changeset/{ => pre}/rename-rebuild-out.md (100%) create mode 100644 .changeset/pre/rename-schema-error-constructors.md create mode 100644 .changeset/pre/render-cli-user-errors.md create mode 100644 .changeset/pre/report-schema-input.md create mode 100644 .changeset/pre/report-transient-rpc-socket-errors.md create mode 100644 .changeset/pre/required-boolean-flags.md create mode 100644 .changeset/pre/resource-subscriptions.md create mode 100644 .changeset/pre/restore-effect-head.md rename .changeset/{ => pre}/restore-schema-parse-options.md (100%) create mode 100644 .changeset/pre/result-map-error-success-identity.md create mode 100644 .changeset/pre/retry-redis-script-load.md create mode 100644 .changeset/pre/reuse-httpapi-response-schemas.md rename .changeset/{ => pre}/rich-dots-push.md (100%) rename .changeset/{ => pre}/rich-hoops-nail.md (100%) rename .changeset/{ => pre}/rich-sloths-draw.md (100%) rename .changeset/{ => pre}/ripe-lies-battle.md (100%) rename .changeset/{ => pre}/rpc-client-http-early-close.md (100%) rename .changeset/{ => pre}/rpc-middleware-provides-fix.md (100%) create mode 100644 .changeset/pre/rpc-server-notifications.md create mode 100644 .changeset/pre/safe-json-schema-patterns.md rename .changeset/{ => pre}/scalar-custom-fetch.md (100%) create mode 100644 .changeset/pre/schema-arbitrary-factory.md rename .changeset/{ => pre}/schema-as-class.md (100%) rename .changeset/{ => pre}/schema-asserts-signature.md (100%) create mode 100644 .changeset/pre/schema-aware-rpc-serialization.md create mode 100644 .changeset/pre/schema-binary-codec.md rename .changeset/{ => pre}/schema-clean-up-additionalProperties.md (100%) create mode 100644 .changeset/pre/schema-codec-narrowing.md create mode 100644 .changeset/pre/schema-date-valid.md rename .changeset/{ => pre}/schema-datetime-utc-from-string.md (100%) rename .changeset/{ => pre}/schema-decoding-defaults-services.md (100%) rename .changeset/{ => pre}/schema-defaults-issue-channel.md (100%) create mode 100644 .changeset/pre/schema-direct-class-extension.md rename .changeset/{ => pre}/schema-dollar-prefix.md (100%) rename .changeset/{ => pre}/schema-lazy-bottom.md (100%) rename .changeset/{ => pre}/schema-missing-self-generic.md (100%) rename .changeset/{ => pre}/schema-ordered-arbitrary-constraints.md (100%) rename .changeset/{ => pre}/schema-parser-adapter-errors.md (100%) rename .changeset/{ => pre}/schema-refactor-toCodecJson.md (100%) rename .changeset/{ => pre}/schema-remove-annotate-in.md (100%) rename .changeset/{ => pre}/schema-rename-makeUnsafe-to-make.md (100%) rename .changeset/{ => pre}/schema-rename-parser-makeUnsafe.md (100%) create mode 100644 .changeset/pre/schema-representation-refactoring.md rename .changeset/{ => pre}/schema-result-combinators.md (100%) create mode 100644 .changeset/pre/schema-runtime-performance.md rename .changeset/{ => pre}/schema-struct-simplify.md (100%) create mode 100644 .changeset/pre/schema-union-type-derivation.md create mode 100644 .changeset/pre/scope-persisted-queue-ids.md create mode 100644 .changeset/pre/secure-eventlog-identities.md rename .changeset/{ => pre}/semantic-matching.md (100%) rename .changeset/{ => pre}/seven-mugs-marry.md (100%) create mode 100644 .changeset/pre/seven-poems-divide.md rename .changeset/{ => pre}/shaggy-birds-stay.md (100%) rename .changeset/{ => pre}/shaggy-cities-push.md (100%) rename .changeset/{ => pre}/shaggy-numbers-accept.md (100%) create mode 100644 .changeset/pre/shaky-beans-throw.md rename .changeset/{ => pre}/sharp-emus-applaud.md (100%) rename .changeset/{ => pre}/sharp-goats-wink.md (100%) rename .changeset/{ => pre}/sharp-pandas-care.md (100%) rename .changeset/{ => pre}/sharp-peas-march.md (100%) rename .changeset/{ => pre}/sharp-rules-draw.md (100%) rename .changeset/{ => pre}/sharp-singers-sort.md (100%) rename .changeset/{ => pre}/shiny-trains-hug.md (100%) rename .changeset/{ => pre}/short-cows-relate.md (100%) rename .changeset/{ => pre}/short-foxes-admire.md (100%) rename .changeset/{ => pre}/short-stamps-throw.md (100%) rename .changeset/{ => pre}/shy-cycles-flow.md (100%) rename .changeset/{ => pre}/shy-geckos-sniff.md (100%) rename .changeset/{ => pre}/silent-geckos-matter.md (100%) rename .changeset/{ => pre}/silent-needles-design.md (100%) rename .changeset/{ => pre}/silent-plants-matter.md (100%) rename .changeset/{ => pre}/silent-spoons-stare.md (100%) create mode 100644 .changeset/pre/silly-dodos-update.md rename .changeset/{ => pre}/silly-loops-tickle.md (100%) rename .changeset/{ => pre}/silver-bulk-indexeddb.md (100%) rename .changeset/{ => pre}/silver-emus-smoke.md (100%) rename .changeset/{ => pre}/silver-kings-poke.md (100%) rename .changeset/{ => pre}/silver-snails-sqlite.md (100%) rename .changeset/{ => pre}/silver-wings-watch.md (100%) create mode 100644 .changeset/pre/simplify-optic-composition.md rename .changeset/{ => pre}/six-cups-taste.md (100%) create mode 100644 .changeset/pre/six-pumas-take.md rename .changeset/{ => pre}/sixty-mails-shout.md (100%) rename .changeset/{ => pre}/sixty-socks-yell.md (100%) rename .changeset/{ => pre}/slick-signs-wish.md (100%) rename .changeset/{ => pre}/slick-toes-rush.md (100%) create mode 100644 .changeset/pre/slimy-melons-admire.md rename .changeset/{ => pre}/slimy-planets-divide.md (100%) rename .changeset/{ => pre}/slimy-turtles-juggle.md (100%) rename .changeset/{ => pre}/slow-beans-battle.md (100%) rename .changeset/{ => pre}/slow-berries-enjoy.md (100%) create mode 100644 .changeset/pre/slow-entities-register.md create mode 100644 .changeset/pre/slow-spiders-refresh.md rename .changeset/{ => pre}/small-bugs-hunt.md (100%) rename .changeset/{ => pre}/small-crabs-care.md (100%) create mode 100644 .changeset/pre/small-pandas-cache.md rename .changeset/{ => pre}/small-pets-sit.md (100%) rename .changeset/{ => pre}/smart-ducks-jump.md (100%) rename .changeset/{ => pre}/smart-pillows-buy.md (100%) rename .changeset/{ => pre}/smart-timers-fly.md (100%) rename .changeset/{ => pre}/smart-tips-sort.md (100%) create mode 100644 .changeset/pre/social-hoops-knock.md rename .changeset/{ => pre}/social-pumas-prove.md (100%) rename .changeset/{ => pre}/soft-comics-wink.md (100%) rename .changeset/{ => pre}/soft-delete-sqlmodel.md (100%) rename .changeset/{ => pre}/soft-seals-allow.md (100%) create mode 100644 .changeset/pre/soft-sockets-write.md rename .changeset/{ => pre}/solid-cougars-attack.md (100%) rename .changeset/{ => pre}/solid-doors-ring.md (100%) rename .changeset/{ => pre}/solid-items-tease.md (100%) rename .changeset/{ => pre}/solid-towns-smoke.md (100%) create mode 100644 .changeset/pre/sour-bees-sleep.md rename .changeset/{ => pre}/sour-canyons-rescue.md (100%) rename .changeset/{ => pre}/sparkly-bears-act.md (100%) rename .changeset/{ => pre}/sparkly-coins-sit.md (100%) create mode 100644 .changeset/pre/spicy-doors-unlist.md create mode 100644 .changeset/pre/split-clock-semantics.md rename .changeset/{ => pre}/spotty-comics-fry.md (100%) create mode 100644 .changeset/pre/sql-begin-failure-typed.md rename .changeset/{ => pre}/sql-migrator-mjs-mts.md (100%) rename .changeset/{ => pre}/sqlite-bun-prepare-error-channel.md (100%) create mode 100644 .changeset/pre/sqlite-client-locking-defaults.md rename .changeset/{ => pre}/sqlite-do-durable-object-transactions.md (100%) create mode 100644 .changeset/pre/stable-graph-traversal-config.md rename .changeset/{ => pre}/stale-dots-tell.md (100%) create mode 100644 .changeset/pre/stale-graph-traversal-skips.md create mode 100644 .changeset/pre/stale-laws-do.md create mode 100644 .changeset/pre/stale-snakes-know.md create mode 100644 .changeset/pre/standard-schema-module.md rename .changeset/{ => pre}/strict-areas-end.md (100%) rename .changeset/{ => pre}/strict-buckets-hug.md (100%) rename .changeset/{ => pre}/strip-resolved-approvals.md (100%) rename .changeset/{ => pre}/strong-balloons-tickle.md (100%) rename .changeset/{ => pre}/strong-bees-queue.md (100%) create mode 100644 .changeset/pre/strong-insects-film.md rename .changeset/{ => pre}/struct-record.md (100%) rename .changeset/{ => pre}/sunny-ads-hang.md (100%) rename .changeset/{ => pre}/sunny-bikes-sleep.md (100%) rename .changeset/{ => pre}/sunny-rooms-invent.md (100%) rename .changeset/{ => pre}/sweet-donuts-bet.md (100%) rename .changeset/{ => pre}/sweet-hotels-give.md (100%) create mode 100644 .changeset/pre/sweet-lizards-sing.md create mode 100644 .changeset/pre/sweet-schedules-matter.md rename .changeset/{ => pre}/sweet-views-learn.md (100%) create mode 100644 .changeset/pre/swift-geese-count.md create mode 100644 .changeset/pre/swift-pandas-parse.md rename .changeset/{ => pre}/swift-spiders-unpack.md (100%) rename .changeset/{ => pre}/swift-symbols-stand.md (100%) rename .changeset/{ => pre}/tagged-error-class-optional-empty-props.md (100%) rename .changeset/{ => pre}/tall-hairs-return.md (100%) create mode 100644 .changeset/pre/tall-ideas-fix.md rename .changeset/{ => pre}/tall-mails-listen.md (100%) create mode 100644 .changeset/pre/tall-melons-shave.md rename .changeset/{ => pre}/tall-queens-cheer.md (100%) rename .changeset/{ => pre}/tall-wombats-wave.md (100%) create mode 100644 .changeset/pre/tame-json-schema-intersections.md rename .changeset/{ => pre}/tangy-colts-lose.md (100%) create mode 100644 .changeset/pre/tangy-plants-run.md rename .changeset/{ => pre}/tasty-comics-send.md (100%) create mode 100644 .changeset/pre/tasty-moments-post.md rename .changeset/{ => pre}/ten-kings-fry.md (100%) create mode 100644 .changeset/pre/tender-deserts-pull.md create mode 100644 .changeset/pre/tender-files-complete.md create mode 100644 .changeset/pre/tender-points-sleep.md create mode 100644 .changeset/pre/terminate-openai-failed-streams.md create mode 100644 .changeset/pre/theme-cli-prompts.md rename .changeset/{ => pre}/thick-pandas-wait.md (100%) rename .changeset/{ => pre}/thin-ducks-wonder.md (100%) rename .changeset/{ => pre}/thirty-ducks-go.md (100%) create mode 100644 .changeset/pre/thirty-forks-march.md rename .changeset/{ => pre}/thirty-pans-love.md (100%) rename .changeset/{ => pre}/three-corners-sort.md (100%) rename .changeset/{ => pre}/three-ravens-jam.md (100%) rename .changeset/{ => pre}/three-tomatoes-wave.md (100%) create mode 100644 .changeset/pre/tidy-apples-rest.md create mode 100644 .changeset/pre/tidy-carpets-smile.md create mode 100644 .changeset/pre/tidy-cats-smile.md create mode 100644 .changeset/pre/tidy-cats-stream.md create mode 100644 .changeset/pre/tidy-codecs-inherit.md create mode 100644 .changeset/pre/tidy-dates-smile.md create mode 100644 .changeset/pre/tidy-floats-edit.md rename .changeset/{ => pre}/tidy-foxes-own.md (100%) create mode 100644 .changeset/pre/tidy-geese-release.md create mode 100644 .changeset/pre/tidy-graphs-cycle.md rename .changeset/{ => pre}/tidy-icons-glow.md (100%) create mode 100644 .changeset/pre/tidy-int32-annotations.md create mode 100644 .changeset/pre/tidy-json-objects.md create mode 100644 .changeset/pre/tidy-json-schema-object-scopes.md create mode 100644 .changeset/pre/tidy-lions-smile.md create mode 100644 .changeset/pre/tidy-matches-select.md create mode 100644 .changeset/pre/tidy-mice-grin.md create mode 100644 .changeset/pre/tidy-pandas-smile.md create mode 100644 .changeset/pre/tidy-plums-remember.md create mode 100644 .changeset/pre/tidy-ravens-import.md create mode 100644 .changeset/pre/tidy-schema-errors.md create mode 100644 .changeset/pre/tidy-scoped-refs-close.md create mode 100644 .changeset/pre/tidy-spans-rest.md create mode 100644 .changeset/pre/tidy-sse-events.md rename .changeset/{ => pre}/tidy-stacks-encode.md (100%) rename .changeset/{ => pre}/tidy-stars-drive.md (100%) create mode 100644 .changeset/pre/tidy-tools-handle.md create mode 100644 .changeset/pre/tidy-tools-juggle.md create mode 100644 .changeset/pre/tidy-tuples-rest.md create mode 100644 .changeset/pre/tidy-wasps-wait.md rename .changeset/{ => pre}/tiny-buckets-wave.md (100%) create mode 100644 .changeset/pre/tiny-dodos-juggle.md create mode 100644 .changeset/pre/tiny-files-flow.md rename .changeset/{ => pre}/tiny-lilies-flash.md (100%) create mode 100644 .changeset/pre/tiny-lizards-correct.md rename .changeset/{ => pre}/tiny-rabbits-smile.md (100%) rename .changeset/{ => pre}/to-codec-json-schema.md (100%) rename .changeset/{ => pre}/tocodecjson-return-json-type.md (100%) rename .changeset/{ => pre}/tool-get-json-schema-tests.md (100%) create mode 100644 .changeset/pre/tough-graphs-cache.md create mode 100644 .changeset/pre/tough-rooms-camp.md create mode 100644 .changeset/pre/tough-taxis-own.md create mode 100644 .changeset/pre/tracer-disabled-timing.md create mode 100644 .changeset/pre/tracer-perf.md rename .changeset/{ => pre}/true-actors-battle.md (100%) rename .changeset/{ => pre}/try-promise-catch-defect.md (100%) rename .changeset/{ => pre}/twelve-dragons-move.md (100%) rename .changeset/{ => pre}/twenty-buttons-cheer.md (100%) create mode 100644 .changeset/pre/twenty-facts-laugh.md create mode 100644 .changeset/pre/twenty-garlics-marry.md rename .changeset/{ => pre}/two-roses-double.md (100%) rename .changeset/{ => pre}/unify-error-defect-stack-options.md (100%) create mode 100644 .changeset/pre/unique-directed-neighbors.md rename .changeset/{ => pre}/update-schema-arbitrary-report.md (100%) create mode 100644 .changeset/pre/upgrade-socket-error-listener.md rename .changeset/{ => pre}/upset-colts-stick.md (100%) rename .changeset/{ => pre}/use-url-can-parse.md (100%) create mode 100644 .changeset/pre/valid-owls-rest.md create mode 100644 .changeset/pre/validate-httpapi-handler-registration.md create mode 100644 .changeset/pre/validate-openapi-global-conflicts.md rename .changeset/{ => pre}/vast-bananas-send.md (100%) rename .changeset/{ => pre}/vast-deserts-travel.md (100%) rename .changeset/{ => pre}/violet-peaches-feel.md (100%) create mode 100644 .changeset/pre/violet-tips-open.md rename .changeset/{ => pre}/vitest-layer-top-level-options.md (100%) rename .changeset/{ => pre}/wacky-grapes-poke.md (100%) rename .changeset/{ => pre}/wacky-rice-add.md (100%) create mode 100644 .changeset/pre/warm-clocks-format.md rename .changeset/{ => pre}/warm-dolls-brake.md (100%) rename .changeset/{ => pre}/warm-friends-tie.md (100%) create mode 100644 .changeset/pre/warm-graphs-degree.md create mode 100644 .changeset/pre/warm-rivers-cache.md rename .changeset/{ => pre}/warm-snails-shop.md (100%) rename .changeset/{ => pre}/wet-news-invent.md (100%) create mode 100644 .changeset/pre/whole-pets-build.md create mode 100644 .changeset/pre/wild-donuts-brake.md rename .changeset/{ => pre}/wild-readers-clean.md (100%) rename .changeset/{ => pre}/wild-suns-bearer-space.md (100%) create mode 100644 .changeset/pre/windows-hide-child-process-console.md rename .changeset/{ => pre}/wise-ants-wave.md (100%) create mode 100644 .changeset/pre/wise-bats-encrypt.md create mode 100644 .changeset/pre/wise-files-watch.md rename .changeset/{ => pre}/wise-flags-shift.md (100%) create mode 100644 .changeset/pre/wise-graphs-connect.md rename .changeset/{ => pre}/wise-oranges-stay.md (100%) create mode 100644 .changeset/pre/wise-pandas-lock.md rename .changeset/{ => pre}/witty-lobsters-share.md (100%) create mode 100644 .changeset/pre/worker-runner-multi-disconnect.md create mode 100644 .changeset/pre/workflow-proxy-discard-execution-id.md rename .changeset/{ => pre}/yellow-adults-study.md (100%) rename .changeset/{ => pre}/yellow-clocks-dance.md (100%) rename .changeset/{ => pre}/yellow-dingos-jump.md (100%) rename .changeset/{ => pre}/young-doors-change.md (100%) create mode 100644 .changeset/pre/zero-node-socket-timeout.md create mode 100644 .changeset/preserve-default-variant-class.md create mode 100644 .changeset/preserve-negative-counter-deltas.md create mode 100644 .changeset/principled-collection-counts.md create mode 100644 .changeset/prompt-buffer-render.md create mode 100644 .changeset/prompt-lossless-text-serialization.md create mode 100644 .changeset/prompt-response-files.md create mode 100644 .changeset/pubsub-sliding-single-subscriber.md create mode 100644 .changeset/pull-based-socket.md create mode 100644 .changeset/queue-manual-flush.md create mode 100644 .changeset/queue-reentrant-producers.md create mode 100644 .changeset/quiet-buffers-decode.md create mode 100644 .changeset/quiet-buns-route.md create mode 100644 .changeset/quiet-graphs-simplify.md create mode 100644 .changeset/quiet-pandas-listen.md create mode 100644 .changeset/quiet-streams-take.md create mode 100644 .changeset/random-exclusive-upper-endpoint.md create mode 100644 .changeset/rate-limiter-reset-lifetime.md create mode 100644 .changeset/rcmap-invalidation-release.md create mode 100644 .changeset/rcmap-throwing-lookup.md create mode 100644 .changeset/rcref-acquisition-shutdown.md create mode 100644 .changeset/rcref-release-ownership.md create mode 100644 .changeset/reason-annotate-context-only.md create mode 100644 .changeset/remove-msgpack.md create mode 100644 .changeset/repeat-or-else-metadata-argument.md create mode 100644 .changeset/report-mcp-tool-failures.md create mode 100644 .changeset/request-cache-cancellation.md create mode 100644 .changeset/request-persisted-resolver-failures.md create mode 100644 .changeset/request-race-cache.md create mode 100644 .changeset/request-resolver-failure-causes.md create mode 100644 .changeset/request-resolver-iterable-results.md create mode 100644 .changeset/required-keys-index-signatures.md create mode 100644 .changeset/resolved-client-urls.md create mode 100644 .changeset/rpc-falsy-control-ids.md create mode 100644 .changeset/scheduler-global-scope-timers.md create mode 100644 .changeset/schema-api-cleanup.md create mode 100644 .changeset/schema-array-leaf-aggregation.md create mode 100644 .changeset/schema-binary-preserve-bom.md create mode 100644 .changeset/scoped-cache-invalidate-all-reentrancy.md create mode 100644 .changeset/scoped-cache-refresh-lookup-defects.md create mode 100644 .changeset/scoped-log-nan-restoration.md create mode 100644 .changeset/shaky-terms-push.md create mode 100644 .changeset/sharding-registration-context-overrides.md create mode 100644 .changeset/shy-seals-smile.md create mode 100644 .changeset/skip-disabled-stack-capture.md create mode 100644 .changeset/small-basic-bundle.md create mode 100644 .changeset/socket-paused-websocket-handoff.md create mode 100644 .changeset/socket-websocket-text-byte-watermark.md create mode 100644 .changeset/split-lines-completed-cr.md create mode 100644 .changeset/spotty-masks-own.md create mode 100644 .changeset/sql-event-journal-callback-errors.md create mode 100644 .changeset/sql-message-storage-joined-reply-id.md create mode 100644 .changeset/sql-nested-placeholder-cache.md create mode 100644 .changeset/sql-optional-span-propagation.md create mode 100644 .changeset/sql-returning-identifier.md create mode 100644 .changeset/sql-runner-requested-shard-results.md create mode 100644 .changeset/sqlite-bun-transaction-export.md create mode 100644 .changeset/sqlite-do-stream-errors.md create mode 100644 .changeset/sqlite-node-unprepared-errors.md create mode 100644 .changeset/sqlite-wasm-messageport-startup.md create mode 100644 .changeset/sqlite-wasm-statement-columns.md create mode 100644 .changeset/sqlite-wasm-worker-error-metadata.md create mode 100644 .changeset/sqlmodel-insert-decoding-services.md create mode 100644 .changeset/stream-rechunk-large-source.md create mode 100644 .changeset/strict-content-length.md create mode 100644 .changeset/strict-cookie-names.md create mode 100644 .changeset/struct-numeric-selection.md create mode 100644 .changeset/structural-schema-nodes.md create mode 100644 .changeset/synchronized-ref-modify-some-effect-currying.md create mode 100644 .changeset/synchronized-ref-not-ref.md create mode 100644 .changeset/tap-defect-saved-error-inference.md create mode 100644 .changeset/tcp-schema-binary-default.md create mode 100644 .changeset/template-literal-parts-without-encoding.md create mode 100644 .changeset/testschema-own-field-asts.md create mode 100644 .changeset/thin-ends-hug.md create mode 100644 .changeset/tidy-parts-narrow.md create mode 100644 .changeset/tidy-tools-finish.md create mode 100644 .changeset/timeout-error-message.md create mode 100644 .changeset/timeout-or-else-cleanup-before-fallback.md create mode 100644 .changeset/token-bucket-elapsed-refill.md create mode 100644 .changeset/tokenizer-whole-prompt-truncation.md create mode 100644 .changeset/tool-result-branch-encoding.md create mode 100644 .changeset/track-mapped-error-domain.md create mode 100644 .changeset/trie-remove-valued-prefix.md create mode 100644 .changeset/try-direct-error-types.md create mode 100644 .changeset/tuple-optional-evolve-result-types.md create mode 100644 .changeset/undici-response-form-data.md create mode 100644 .changeset/unstable-http-schemas.md create mode 100644 .changeset/upgraded-request-skips-http-response-write.md create mode 100644 .changeset/urlparams-null-input.md create mode 100644 .changeset/urlparams-setall-immutable.md create mode 100644 .changeset/use-canonical-array-indices.md create mode 100644 .changeset/violet-pugs-tickle.md create mode 100644 .changeset/vitest-five-migration.md create mode 100644 .changeset/vitest-layer-concurrency.md create mode 100644 .changeset/with-error-reporting-effect-result.md create mode 100644 .changeset/worker-run-early-exit.md create mode 100644 .changeset/xhr-arraybuffer-readers.md create mode 100644 .github/actions/deploy-website/action.yml create mode 100644 .github/workflows/cluster.yml create mode 100644 .github/workflows/release-queue.yml delete mode 100644 .patterns/effect.md delete mode 100644 .patterns/jsdoc.md delete mode 100644 .patterns/testing.md delete mode 100644 .specs/README.md delete mode 100644 AGENTS.md delete mode 100644 TODOS.md create mode 100644 ai-docs/src/01_effect/02_schema/10_schema-basics.ts create mode 100644 ai-docs/src/01_effect/02_schema/index.md rename ai-docs/src/01_effect/{02_services => 03_services}/01_service.ts (93%) rename ai-docs/src/01_effect/{02_services => 03_services}/10_reference.ts (100%) rename ai-docs/src/01_effect/{02_services => 03_services}/20_layer-composition.ts (93%) rename ai-docs/src/01_effect/{02_services => 03_services}/20_layer-unwrap.ts (87%) rename ai-docs/src/01_effect/{02_services => 03_services}/index.md (100%) rename ai-docs/src/01_effect/{03_errors => 04_errors}/01_error-handling.ts (75%) rename ai-docs/src/01_effect/{03_errors => 04_errors}/10_catch-tags.ts (72%) rename ai-docs/src/01_effect/{03_errors => 04_errors}/20_reason-errors.ts (82%) rename ai-docs/src/01_effect/{03_errors => 04_errors}/index.md (100%) rename ai-docs/src/01_effect/{04_resources => 05_resources}/10_acquire-release.ts (91%) rename ai-docs/src/01_effect/{04_resources => 05_resources}/20_layer-side-effects.ts (100%) rename ai-docs/src/01_effect/{04_resources => 05_resources}/30_layer-map.ts (96%) rename ai-docs/src/01_effect/{04_resources => 05_resources}/index.md (100%) rename ai-docs/src/01_effect/{05_running => 06_running}/10_run-main.ts (100%) rename ai-docs/src/01_effect/{05_running => 06_running}/20_layer-launch.ts (100%) rename ai-docs/src/01_effect/{05_running => 06_running}/index.md (100%) rename ai-docs/src/01_effect/{06_pubsub => 07_pubsub}/10_pubsub.ts (100%) rename ai-docs/src/01_effect/{06_pubsub => 07_pubsub}/index.md (100%) rename ai-docs/src/{02_stream => 03_stream}/10_creating-streams.ts (94%) rename ai-docs/src/{02_stream => 03_stream}/20_consuming-streams.ts (100%) rename ai-docs/src/{02_stream => 03_stream}/30_encoding.ts (93%) rename ai-docs/src/{02_stream => 03_stream}/index.md (100%) rename ai-docs/src/{03_integration => 04_integration}/10_managed-runtime.ts (96%) rename ai-docs/src/{03_integration => 04_integration}/index.md (100%) create mode 100644 ai-docs/src/10_predicate/01_basics.ts create mode 100644 ai-docs/src/10_predicate/index.md create mode 100644 ai-docs/src/40_sql/10_basics.ts create mode 100644 ai-docs/src/40_sql/index.md create mode 100644 ai-docs/src/51_http-server/20_testing.ts delete mode 100644 cookbooks/schedule.md delete mode 100644 docker-compose.yaml create mode 100644 migration/annotations/README.md create mode 100644 migration/annotations/effect__Arbitrary.yaml create mode 100644 migration/annotations/effect__Array.yaml create mode 100644 migration/annotations/effect__BigDecimal.yaml create mode 100644 migration/annotations/effect__BigInt.yaml create mode 100644 migration/annotations/effect__Brand.yaml create mode 100644 migration/annotations/effect__Cache.yaml create mode 100644 migration/annotations/effect__Cause.yaml create mode 100644 migration/annotations/effect__Channel.yaml create mode 100644 migration/annotations/effect__ChildExecutorDecision.yaml create mode 100644 migration/annotations/effect__Chunk.yaml create mode 100644 migration/annotations/effect__Clock.yaml create mode 100644 migration/annotations/effect__Config.yaml create mode 100644 migration/annotations/effect__ConfigError.yaml create mode 100644 migration/annotations/effect__ConfigProvider.yaml create mode 100644 migration/annotations/effect__ConfigProviderPathPatch.yaml create mode 100644 migration/annotations/effect__Console.yaml create mode 100644 migration/annotations/effect__Context.yaml create mode 100644 migration/annotations/effect__Cron.yaml create mode 100644 migration/annotations/effect__Data.yaml create mode 100644 migration/annotations/effect__DateTime.yaml create mode 100644 migration/annotations/effect__DefaultServices.yaml create mode 100644 migration/annotations/effect__Deferred.yaml create mode 100644 migration/annotations/effect__Differ.yaml create mode 100644 migration/annotations/effect__Duration.yaml create mode 100644 migration/annotations/effect__Effect.yaml create mode 100644 migration/annotations/effect__Effectable.yaml create mode 100644 migration/annotations/effect__Either.yaml create mode 100644 migration/annotations/effect__Encoding.yaml create mode 100644 migration/annotations/effect__Equal.yaml create mode 100644 migration/annotations/effect__Equivalence.yaml create mode 100644 migration/annotations/effect__ExecutionPlan.yaml create mode 100644 migration/annotations/effect__ExecutionStrategy.yaml create mode 100644 migration/annotations/effect__Exit.yaml create mode 100644 migration/annotations/effect__FastCheck.yaml create mode 100644 migration/annotations/effect__Fiber.yaml create mode 100644 migration/annotations/effect__FiberHandle.yaml create mode 100644 migration/annotations/effect__FiberId.yaml create mode 100644 migration/annotations/effect__FiberMap.yaml create mode 100644 migration/annotations/effect__FiberRef.yaml create mode 100644 migration/annotations/effect__FiberRefs.yaml create mode 100644 migration/annotations/effect__FiberRefsPatch.yaml create mode 100644 migration/annotations/effect__FiberSet.yaml create mode 100644 migration/annotations/effect__FiberStatus.yaml create mode 100644 migration/annotations/effect__Function.yaml create mode 100644 migration/annotations/effect__GlobalValue.yaml create mode 100644 migration/annotations/effect__Graph.yaml create mode 100644 migration/annotations/effect__GroupBy.yaml create mode 100644 migration/annotations/effect__Hash.yaml create mode 100644 migration/annotations/effect__HashMap.yaml create mode 100644 migration/annotations/effect__HashSet.yaml create mode 100644 migration/annotations/effect__Inspectable.yaml create mode 100644 migration/annotations/effect__Iterable.yaml create mode 100644 migration/annotations/effect__JSONSchema.yaml create mode 100644 migration/annotations/effect__KeyedPool.yaml create mode 100644 migration/annotations/effect__Layer.yaml create mode 100644 migration/annotations/effect__LayerMap.yaml create mode 100644 migration/annotations/effect__List.yaml create mode 100644 migration/annotations/effect__LogLevel.yaml create mode 100644 migration/annotations/effect__LogSpan.yaml create mode 100644 migration/annotations/effect__Logger.yaml create mode 100644 migration/annotations/effect__Mailbox.yaml create mode 100644 migration/annotations/effect__ManagedRuntime.yaml create mode 100644 migration/annotations/effect__Match.yaml create mode 100644 migration/annotations/effect__MergeDecision.yaml create mode 100644 migration/annotations/effect__MergeState.yaml create mode 100644 migration/annotations/effect__MergeStrategy.yaml create mode 100644 migration/annotations/effect__Metric.yaml create mode 100644 migration/annotations/effect__MetricBoundaries.yaml create mode 100644 migration/annotations/effect__MetricHook.yaml create mode 100644 migration/annotations/effect__MetricKey.yaml create mode 100644 migration/annotations/effect__MetricKeyType.yaml create mode 100644 migration/annotations/effect__MetricLabel.yaml create mode 100644 migration/annotations/effect__MetricPair.yaml create mode 100644 migration/annotations/effect__MetricPolling.yaml create mode 100644 migration/annotations/effect__MetricRegistry.yaml create mode 100644 migration/annotations/effect__MetricState.yaml create mode 100644 migration/annotations/effect__Micro.yaml create mode 100644 migration/annotations/effect__ModuleVersion.yaml create mode 100644 migration/annotations/effect__MutableHashMap.yaml create mode 100644 migration/annotations/effect__MutableHashSet.yaml create mode 100644 migration/annotations/effect__MutableList.yaml create mode 100644 migration/annotations/effect__MutableQueue.yaml create mode 100644 migration/annotations/effect__MutableRef.yaml create mode 100644 migration/annotations/effect__Number.yaml create mode 100644 migration/annotations/effect__Option.yaml create mode 100644 migration/annotations/effect__Order.yaml create mode 100644 migration/annotations/effect__Ordering.yaml create mode 100644 migration/annotations/effect__ParseResult.yaml create mode 100644 migration/annotations/effect__PartitionedSemaphore.yaml create mode 100644 migration/annotations/effect__Pipeable.yaml create mode 100644 migration/annotations/effect__Pool.yaml create mode 100644 migration/annotations/effect__Predicate.yaml create mode 100644 migration/annotations/effect__Pretty.yaml create mode 100644 migration/annotations/effect__PubSub.yaml create mode 100644 migration/annotations/effect__Queue.yaml create mode 100644 migration/annotations/effect__Random.yaml create mode 100644 migration/annotations/effect__RateLimiter.yaml create mode 100644 migration/annotations/effect__RcMap.yaml create mode 100644 migration/annotations/effect__RcRef.yaml create mode 100644 migration/annotations/effect__Readable.yaml create mode 100644 migration/annotations/effect__Record.yaml create mode 100644 migration/annotations/effect__RedBlackTree.yaml create mode 100644 migration/annotations/effect__Redacted.yaml create mode 100644 migration/annotations/effect__Ref.yaml create mode 100644 migration/annotations/effect__Reloadable.yaml create mode 100644 migration/annotations/effect__Request.yaml create mode 100644 migration/annotations/effect__RequestBlock.yaml create mode 100644 migration/annotations/effect__RequestResolver.yaml create mode 100644 migration/annotations/effect__Resource.yaml create mode 100644 migration/annotations/effect__Runtime.yaml create mode 100644 migration/annotations/effect__RuntimeFlags.yaml create mode 100644 migration/annotations/effect__RuntimeFlagsPatch.yaml create mode 100644 migration/annotations/effect__STM.yaml create mode 100644 migration/annotations/effect__Schedule.yaml create mode 100644 migration/annotations/effect__ScheduleDecision.yaml create mode 100644 migration/annotations/effect__ScheduleInterval.yaml create mode 100644 migration/annotations/effect__ScheduleIntervals.yaml create mode 100644 migration/annotations/effect__Scheduler.yaml create mode 100644 migration/annotations/effect__Schema.yaml create mode 100644 migration/annotations/effect__SchemaAST.yaml create mode 100644 migration/annotations/effect__Scope.yaml create mode 100644 migration/annotations/effect__ScopedCache.yaml create mode 100644 migration/annotations/effect__ScopedRef.yaml create mode 100644 migration/annotations/effect__Secret.yaml create mode 100644 migration/annotations/effect__SingleProducerAsyncInput.yaml create mode 100644 migration/annotations/effect__Sink.yaml create mode 100644 migration/annotations/effect__SortedMap.yaml create mode 100644 migration/annotations/effect__SortedSet.yaml create mode 100644 migration/annotations/effect__Stream.yaml create mode 100644 migration/annotations/effect__StreamEmit.yaml create mode 100644 migration/annotations/effect__StreamHaltStrategy.yaml create mode 100644 migration/annotations/effect__Streamable.yaml create mode 100644 migration/annotations/effect__Struct.yaml create mode 100644 migration/annotations/effect__Subscribable.yaml create mode 100644 migration/annotations/effect__SubscriptionRef.yaml create mode 100644 migration/annotations/effect__Supervisor.yaml create mode 100644 migration/annotations/effect__Symbol.yaml create mode 100644 migration/annotations/effect__SynchronizedRef.yaml create mode 100644 migration/annotations/effect__TArray.yaml create mode 100644 migration/annotations/effect__TDeferred.yaml create mode 100644 migration/annotations/effect__TMap.yaml create mode 100644 migration/annotations/effect__TPriorityQueue.yaml create mode 100644 migration/annotations/effect__TPubSub.yaml create mode 100644 migration/annotations/effect__TQueue.yaml create mode 100644 migration/annotations/effect__TRandom.yaml create mode 100644 migration/annotations/effect__TReentrantLock.yaml create mode 100644 migration/annotations/effect__TRef.yaml create mode 100644 migration/annotations/effect__TSemaphore.yaml create mode 100644 migration/annotations/effect__TSet.yaml create mode 100644 migration/annotations/effect__TSubscriptionRef.yaml create mode 100644 migration/annotations/effect__Take.yaml create mode 100644 migration/annotations/effect__TestAnnotation.yaml create mode 100644 migration/annotations/effect__TestAnnotationMap.yaml create mode 100644 migration/annotations/effect__TestAnnotations.yaml create mode 100644 migration/annotations/effect__TestClock.yaml create mode 100644 migration/annotations/effect__TestConfig.yaml create mode 100644 migration/annotations/effect__TestContext.yaml create mode 100644 migration/annotations/effect__TestLive.yaml create mode 100644 migration/annotations/effect__TestServices.yaml create mode 100644 migration/annotations/effect__TestSized.yaml create mode 100644 migration/annotations/effect__Tracer.yaml create mode 100644 migration/annotations/effect__Trie.yaml create mode 100644 migration/annotations/effect__Tuple.yaml create mode 100644 migration/annotations/effect__Types.yaml create mode 100644 migration/annotations/effect__UpstreamPullRequest.yaml create mode 100644 migration/annotations/effect__UpstreamPullStrategy.yaml create mode 100644 migration/annotations/effect__Utils.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockClient.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockConfig.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockLanguageModel.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockSchema.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockTool.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__EventStreamEncoding.yaml create mode 100644 migration/annotations/effect__ai-amazon-bedrock__index.yaml create mode 100644 migration/annotations/effect__ai-anthropic__AnthropicClient.yaml create mode 100644 migration/annotations/effect__ai-anthropic__AnthropicConfig.yaml create mode 100644 migration/annotations/effect__ai-anthropic__AnthropicLanguageModel.yaml create mode 100644 migration/annotations/effect__ai-anthropic__AnthropicTokenizer.yaml create mode 100644 migration/annotations/effect__ai-anthropic__AnthropicTool.yaml create mode 100644 migration/annotations/effect__ai-anthropic__Generated.yaml create mode 100644 migration/annotations/effect__ai-anthropic__index.yaml create mode 100644 migration/annotations/effect__ai-google.yaml create mode 100644 migration/annotations/effect__ai-google__Generated.yaml create mode 100644 migration/annotations/effect__ai-google__GoogleClient.yaml create mode 100644 migration/annotations/effect__ai-google__GoogleConfig.yaml create mode 100644 migration/annotations/effect__ai-google__GoogleLanguageModel.yaml create mode 100644 migration/annotations/effect__ai-google__GoogleTool.yaml create mode 100644 migration/annotations/effect__ai-google__index.yaml create mode 100644 migration/annotations/effect__ai-openai__Generated.yaml create mode 100644 migration/annotations/effect__ai-openai__OpenAiClient.yaml create mode 100644 migration/annotations/effect__ai-openai__OpenAiConfig.yaml create mode 100644 migration/annotations/effect__ai-openai__OpenAiEmbeddingModel.yaml create mode 100644 migration/annotations/effect__ai-openai__OpenAiLanguageModel.yaml create mode 100644 migration/annotations/effect__ai-openai__OpenAiTelemetry.yaml create mode 100644 migration/annotations/effect__ai-openai__OpenAiTokenizer.yaml create mode 100644 migration/annotations/effect__ai-openai__index.yaml create mode 100644 migration/annotations/effect__ai-openrouter__Generated.yaml create mode 100644 migration/annotations/effect__ai-openrouter__OpenRouterClient.yaml create mode 100644 migration/annotations/effect__ai-openrouter__OpenRouterConfig.yaml create mode 100644 migration/annotations/effect__ai-openrouter__OpenRouterLanguageModel.yaml create mode 100644 migration/annotations/effect__ai-openrouter__index.yaml create mode 100644 migration/annotations/effect__ai.yaml create mode 100644 migration/annotations/effect__ai__AiError.yaml create mode 100644 migration/annotations/effect__ai__EmbeddingModel.yaml create mode 100644 migration/annotations/effect__ai__IdGenerator.yaml create mode 100644 migration/annotations/effect__ai__LanguageModel.yaml create mode 100644 migration/annotations/effect__ai__McpSchema.yaml create mode 100644 migration/annotations/effect__ai__McpServer.yaml create mode 100644 migration/annotations/effect__ai__Model.yaml create mode 100644 migration/annotations/effect__ai__Prompt.yaml create mode 100644 migration/annotations/effect__ai__Response.yaml create mode 100644 migration/annotations/effect__ai__Tool.yaml create mode 100644 migration/annotations/effect__ai__Toolkit.yaml create mode 100644 migration/annotations/effect__ai__index.yaml create mode 100644 migration/annotations/effect__cli.yaml create mode 100644 migration/annotations/effect__cli__Args.yaml create mode 100644 migration/annotations/effect__cli__AutoCorrect.yaml create mode 100644 migration/annotations/effect__cli__BuiltInOptions.yaml create mode 100644 migration/annotations/effect__cli__CliApp.yaml create mode 100644 migration/annotations/effect__cli__CliConfig.yaml create mode 100644 migration/annotations/effect__cli__Command.yaml create mode 100644 migration/annotations/effect__cli__CommandDescriptor.yaml create mode 100644 migration/annotations/effect__cli__CommandDirective.yaml create mode 100644 migration/annotations/effect__cli__ConfigFile.yaml create mode 100644 migration/annotations/effect__cli__HelpDoc.yaml create mode 100644 migration/annotations/effect__cli__HelpDoc__Span.yaml create mode 100644 migration/annotations/effect__cli__Options.yaml create mode 100644 migration/annotations/effect__cli__Primitive.yaml create mode 100644 migration/annotations/effect__cli__Prompt.yaml create mode 100644 migration/annotations/effect__cli__Usage.yaml create mode 100644 migration/annotations/effect__cli__ValidationError.yaml create mode 100644 migration/annotations/effect__cli__index.yaml create mode 100644 migration/annotations/effect__cluster.yaml create mode 100644 migration/annotations/effect__cluster__ClusterCron.yaml create mode 100644 migration/annotations/effect__cluster__ClusterError.yaml create mode 100644 migration/annotations/effect__cluster__ClusterSchema.yaml create mode 100644 migration/annotations/effect__cluster__ClusterWorkflowEngine.yaml create mode 100644 migration/annotations/effect__cluster__DeliverAt.yaml create mode 100644 migration/annotations/effect__cluster__Entity.yaml create mode 100644 migration/annotations/effect__cluster__EntityAddress.yaml create mode 100644 migration/annotations/effect__cluster__EntityId.yaml create mode 100644 migration/annotations/effect__cluster__EntityProxy.yaml create mode 100644 migration/annotations/effect__cluster__EntityProxyServer.yaml create mode 100644 migration/annotations/effect__cluster__EntityResource.yaml create mode 100644 migration/annotations/effect__cluster__Envelope.yaml create mode 100644 migration/annotations/effect__cluster__HttpRunner.yaml create mode 100644 migration/annotations/effect__cluster__K8sHttpClient.yaml create mode 100644 migration/annotations/effect__cluster__MachineId.yaml create mode 100644 migration/annotations/effect__cluster__Message.yaml create mode 100644 migration/annotations/effect__cluster__MessageStorage.yaml create mode 100644 migration/annotations/effect__cluster__Reply.yaml create mode 100644 migration/annotations/effect__cluster__Runner.yaml create mode 100644 migration/annotations/effect__cluster__RunnerAddress.yaml create mode 100644 migration/annotations/effect__cluster__RunnerHealth.yaml create mode 100644 migration/annotations/effect__cluster__RunnerServer.yaml create mode 100644 migration/annotations/effect__cluster__RunnerStorage.yaml create mode 100644 migration/annotations/effect__cluster__Runners.yaml create mode 100644 migration/annotations/effect__cluster__ShardId.yaml create mode 100644 migration/annotations/effect__cluster__Sharding.yaml create mode 100644 migration/annotations/effect__cluster__ShardingConfig.yaml create mode 100644 migration/annotations/effect__cluster__ShardingRegistrationEvent.yaml create mode 100644 migration/annotations/effect__cluster__SingleRunner.yaml create mode 100644 migration/annotations/effect__cluster__Singleton.yaml create mode 100644 migration/annotations/effect__cluster__SingletonAddress.yaml create mode 100644 migration/annotations/effect__cluster__Snowflake.yaml create mode 100644 migration/annotations/effect__cluster__SocketRunner.yaml create mode 100644 migration/annotations/effect__cluster__SqlMessageStorage.yaml create mode 100644 migration/annotations/effect__cluster__SqlRunnerStorage.yaml create mode 100644 migration/annotations/effect__cluster__TestRunner.yaml create mode 100644 migration/annotations/effect__cluster__index.yaml create mode 100644 migration/annotations/effect__experimental.yaml create mode 100644 migration/annotations/effect__experimental__DevTools.yaml create mode 100644 migration/annotations/effect__experimental__DevTools__Client.yaml create mode 100644 migration/annotations/effect__experimental__DevTools__Domain.yaml create mode 100644 migration/annotations/effect__experimental__DevTools__Server.yaml create mode 100644 migration/annotations/effect__experimental__Event.yaml create mode 100644 migration/annotations/effect__experimental__EventGroup.yaml create mode 100644 migration/annotations/effect__experimental__EventJournal.yaml create mode 100644 migration/annotations/effect__experimental__EventLog.yaml create mode 100644 migration/annotations/effect__experimental__EventLogRemote.yaml create mode 100644 migration/annotations/effect__experimental__EventLogServer.yaml create mode 100644 migration/annotations/effect__experimental__EventLogServer__Cloudflare.yaml create mode 100644 migration/annotations/effect__experimental__Machine.yaml create mode 100644 migration/annotations/effect__experimental__Machine__Procedure.yaml create mode 100644 migration/annotations/effect__experimental__Machine__ProcedureList.yaml create mode 100644 migration/annotations/effect__experimental__Machine__SerializableProcedureList.yaml create mode 100644 migration/annotations/effect__experimental__PersistedCache.yaml create mode 100644 migration/annotations/effect__experimental__PersistedQueue.yaml create mode 100644 migration/annotations/effect__experimental__PersistedQueue__Redis.yaml create mode 100644 migration/annotations/effect__experimental__Persistence.yaml create mode 100644 migration/annotations/effect__experimental__Persistence__Lmdb.yaml create mode 100644 migration/annotations/effect__experimental__Persistence__Redis.yaml create mode 100644 migration/annotations/effect__experimental__RateLimiter.yaml create mode 100644 migration/annotations/effect__experimental__RateLimiter__Redis.yaml create mode 100644 migration/annotations/effect__experimental__Reactivity.yaml create mode 100644 migration/annotations/effect__experimental__RequestResolver.yaml create mode 100644 migration/annotations/effect__experimental__Sse.yaml create mode 100644 migration/annotations/effect__experimental__VariantSchema.yaml create mode 100644 migration/annotations/effect__experimental__index.yaml create mode 100644 migration/annotations/effect__index.yaml create mode 100644 migration/annotations/effect__opentelemetry__Logger.yaml create mode 100644 migration/annotations/effect__opentelemetry__Metrics.yaml create mode 100644 migration/annotations/effect__opentelemetry__NodeSdk.yaml create mode 100644 migration/annotations/effect__opentelemetry__Otlp.yaml create mode 100644 migration/annotations/effect__opentelemetry__OtlpLogger.yaml create mode 100644 migration/annotations/effect__opentelemetry__OtlpMetrics.yaml create mode 100644 migration/annotations/effect__opentelemetry__OtlpResource.yaml create mode 100644 migration/annotations/effect__opentelemetry__OtlpTracer.yaml create mode 100644 migration/annotations/effect__opentelemetry__Resource.yaml create mode 100644 migration/annotations/effect__opentelemetry__Tracer.yaml create mode 100644 migration/annotations/effect__opentelemetry__WebSdk.yaml create mode 100644 migration/annotations/effect__opentelemetry__index.yaml create mode 100644 migration/annotations/effect__platform-browser__BrowserHttpClient.yaml create mode 100644 migration/annotations/effect__platform-browser__BrowserWorker.yaml create mode 100644 migration/annotations/effect__platform-browser__BrowserWorkerRunner.yaml create mode 100644 migration/annotations/effect__platform-browser__Clipboard.yaml create mode 100644 migration/annotations/effect__platform-browser__Geolocation.yaml create mode 100644 migration/annotations/effect__platform-browser__Permissions.yaml create mode 100644 migration/annotations/effect__platform-browser__index.yaml create mode 100644 migration/annotations/effect__platform-bun__BunCommandExecutor.yaml create mode 100644 migration/annotations/effect__platform-bun__BunContext.yaml create mode 100644 migration/annotations/effect__platform-bun__BunFileSystem__ParcelWatcher.yaml create mode 100644 migration/annotations/effect__platform-bun__BunHttpPlatform.yaml create mode 100644 migration/annotations/effect__platform-bun__BunHttpServer.yaml create mode 100644 migration/annotations/effect__platform-bun__BunHttpServerRequest.yaml create mode 100644 migration/annotations/effect__platform-bun__BunKeyValueStore.yaml create mode 100644 migration/annotations/effect__platform-bun__BunSink.yaml create mode 100644 migration/annotations/effect__platform-bun__BunSocket.yaml create mode 100644 migration/annotations/effect__platform-bun__BunSocketServer.yaml create mode 100644 migration/annotations/effect__platform-bun__BunStream.yaml create mode 100644 migration/annotations/effect__platform-bun__BunWorker.yaml create mode 100644 migration/annotations/effect__platform-bun__index.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeCommandExecutor.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeFileSystem__ParcelWatcher.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeKeyValueStore.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeMultipart.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeSink.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeSocket.yaml create mode 100644 migration/annotations/effect__platform-node-shared__NodeStream.yaml create mode 100644 migration/annotations/effect__platform-node__NodeCommandExecutor.yaml create mode 100644 migration/annotations/effect__platform-node__NodeContext.yaml create mode 100644 migration/annotations/effect__platform-node__NodeFileSystem__ParcelWatcher.yaml create mode 100644 migration/annotations/effect__platform-node__NodeHttpClient.yaml create mode 100644 migration/annotations/effect__platform-node__NodeHttpServer.yaml create mode 100644 migration/annotations/effect__platform-node__NodeKeyValueStore.yaml create mode 100644 migration/annotations/effect__platform-node__NodeWorker.yaml create mode 100644 migration/annotations/effect__platform-node__Undici.yaml create mode 100644 migration/annotations/effect__platform-node__index.yaml create mode 100644 migration/annotations/effect__platform.yaml create mode 100644 migration/annotations/effect__platform__ChannelSchema.yaml create mode 100644 migration/annotations/effect__platform__Command.yaml create mode 100644 migration/annotations/effect__platform__CommandExecutor.yaml create mode 100644 migration/annotations/effect__platform__Cookies.yaml create mode 100644 migration/annotations/effect__platform__Effectify.yaml create mode 100644 migration/annotations/effect__platform__Error.yaml create mode 100644 migration/annotations/effect__platform__Etag.yaml create mode 100644 migration/annotations/effect__platform__FetchHttpClient.yaml create mode 100644 migration/annotations/effect__platform__FileSystem.yaml create mode 100644 migration/annotations/effect__platform__Headers.yaml create mode 100644 migration/annotations/effect__platform__HttpApi.yaml create mode 100644 migration/annotations/effect__platform__HttpApiBuilder.yaml create mode 100644 migration/annotations/effect__platform__HttpApiClient.yaml create mode 100644 migration/annotations/effect__platform__HttpApiEndpoint.yaml create mode 100644 migration/annotations/effect__platform__HttpApiError.yaml create mode 100644 migration/annotations/effect__platform__HttpApiGroup.yaml create mode 100644 migration/annotations/effect__platform__HttpApiMiddleware.yaml create mode 100644 migration/annotations/effect__platform__HttpApiScalar.yaml create mode 100644 migration/annotations/effect__platform__HttpApiSchema.yaml create mode 100644 migration/annotations/effect__platform__HttpApiSecurity.yaml create mode 100644 migration/annotations/effect__platform__HttpApiSwagger.yaml create mode 100644 migration/annotations/effect__platform__HttpApp.yaml create mode 100644 migration/annotations/effect__platform__HttpBody.yaml create mode 100644 migration/annotations/effect__platform__HttpClient.yaml create mode 100644 migration/annotations/effect__platform__HttpClientError.yaml create mode 100644 migration/annotations/effect__platform__HttpClientRequest.yaml create mode 100644 migration/annotations/effect__platform__HttpClientResponse.yaml create mode 100644 migration/annotations/effect__platform__HttpIncomingMessage.yaml create mode 100644 migration/annotations/effect__platform__HttpLayerRouter.yaml create mode 100644 migration/annotations/effect__platform__HttpMethod.yaml create mode 100644 migration/annotations/effect__platform__HttpMiddleware.yaml create mode 100644 migration/annotations/effect__platform__HttpMultiplex.yaml create mode 100644 migration/annotations/effect__platform__HttpPlatform.yaml create mode 100644 migration/annotations/effect__platform__HttpRouter.yaml create mode 100644 migration/annotations/effect__platform__HttpServer.yaml create mode 100644 migration/annotations/effect__platform__HttpServerError.yaml create mode 100644 migration/annotations/effect__platform__HttpServerRequest.yaml create mode 100644 migration/annotations/effect__platform__HttpServerRespondable.yaml create mode 100644 migration/annotations/effect__platform__HttpServerResponse.yaml create mode 100644 migration/annotations/effect__platform__KeyValueStore.yaml create mode 100644 migration/annotations/effect__platform__MsgPack.yaml create mode 100644 migration/annotations/effect__platform__Multipart.yaml create mode 100644 migration/annotations/effect__platform__Ndjson.yaml create mode 100644 migration/annotations/effect__platform__OpenApi.yaml create mode 100644 migration/annotations/effect__platform__OpenApiJsonSchema.yaml create mode 100644 migration/annotations/effect__platform__Path.yaml create mode 100644 migration/annotations/effect__platform__PlatformConfigProvider.yaml create mode 100644 migration/annotations/effect__platform__PlatformLogger.yaml create mode 100644 migration/annotations/effect__platform__Runtime.yaml create mode 100644 migration/annotations/effect__platform__Socket.yaml create mode 100644 migration/annotations/effect__platform__SocketServer.yaml create mode 100644 migration/annotations/effect__platform__Template.yaml create mode 100644 migration/annotations/effect__platform__Terminal.yaml create mode 100644 migration/annotations/effect__platform__Transferable.yaml create mode 100644 migration/annotations/effect__platform__Url.yaml create mode 100644 migration/annotations/effect__platform__UrlParams.yaml create mode 100644 migration/annotations/effect__platform__Worker.yaml create mode 100644 migration/annotations/effect__platform__WorkerError.yaml create mode 100644 migration/annotations/effect__platform__WorkerRunner.yaml create mode 100644 migration/annotations/effect__platform__index.yaml create mode 100644 migration/annotations/effect__printer-ansi.yaml create mode 100644 migration/annotations/effect__printer-ansi__Ansi.yaml create mode 100644 migration/annotations/effect__printer-ansi__AnsiDoc.yaml create mode 100644 migration/annotations/effect__printer-ansi__Color.yaml create mode 100644 migration/annotations/effect__printer-ansi__index.yaml create mode 100644 migration/annotations/effect__printer.yaml create mode 100644 migration/annotations/effect__printer__Doc.yaml create mode 100644 migration/annotations/effect__printer__DocStream.yaml create mode 100644 migration/annotations/effect__printer__DocTree.yaml create mode 100644 migration/annotations/effect__printer__Flatten.yaml create mode 100644 migration/annotations/effect__printer__Layout.yaml create mode 100644 migration/annotations/effect__printer__Optimize.yaml create mode 100644 migration/annotations/effect__printer__PageWidth.yaml create mode 100644 migration/annotations/effect__printer__index.yaml create mode 100644 migration/annotations/effect__rpc.yaml create mode 100644 migration/annotations/effect__rpc__Rpc.yaml create mode 100644 migration/annotations/effect__rpc__RpcClient.yaml create mode 100644 migration/annotations/effect__rpc__RpcClientError.yaml create mode 100644 migration/annotations/effect__rpc__RpcGroup.yaml create mode 100644 migration/annotations/effect__rpc__RpcMessage.yaml create mode 100644 migration/annotations/effect__rpc__RpcMiddleware.yaml create mode 100644 migration/annotations/effect__rpc__RpcSchema.yaml create mode 100644 migration/annotations/effect__rpc__RpcSerialization.yaml create mode 100644 migration/annotations/effect__rpc__RpcServer.yaml create mode 100644 migration/annotations/effect__rpc__RpcTest.yaml create mode 100644 migration/annotations/effect__rpc__index.yaml create mode 100644 migration/annotations/effect__sql-clickhouse__ClickhouseClient.yaml create mode 100644 migration/annotations/effect__sql-clickhouse__ClickhouseMigrator.yaml create mode 100644 migration/annotations/effect__sql-clickhouse__index.yaml create mode 100644 migration/annotations/effect__sql-d1__D1Client.yaml create mode 100644 migration/annotations/effect__sql-d1__index.yaml create mode 100644 migration/annotations/effect__sql-drizzle__Mysql.yaml create mode 100644 migration/annotations/effect__sql-drizzle__Pg.yaml create mode 100644 migration/annotations/effect__sql-drizzle__Sqlite.yaml create mode 100644 migration/annotations/effect__sql-kysely__Kysely.yaml create mode 100644 migration/annotations/effect__sql-kysely__Mssql.yaml create mode 100644 migration/annotations/effect__sql-kysely__Mysql.yaml create mode 100644 migration/annotations/effect__sql-kysely__Pg.yaml create mode 100644 migration/annotations/effect__sql-kysely__Sqlite.yaml create mode 100644 migration/annotations/effect__sql-kysely__patch.types.yaml create mode 100644 migration/annotations/effect__sql-libsql__index.yaml create mode 100644 migration/annotations/effect__sql-mssql__MssqlClient.yaml create mode 100644 migration/annotations/effect__sql-mssql__Parameter.yaml create mode 100644 migration/annotations/effect__sql-mssql__Procedure.yaml create mode 100644 migration/annotations/effect__sql-mssql__index.yaml create mode 100644 migration/annotations/effect__sql-mysql2__MysqlClient.yaml create mode 100644 migration/annotations/effect__sql-mysql2__index.yaml create mode 100644 migration/annotations/effect__sql-pg__PgClient.yaml create mode 100644 migration/annotations/effect__sql-pg__index.yaml create mode 100644 migration/annotations/effect__sql-sqlite-bun__SqliteClient.yaml create mode 100644 migration/annotations/effect__sql-sqlite-bun__index.yaml create mode 100644 migration/annotations/effect__sql-sqlite-do__SqliteClient.yaml create mode 100644 migration/annotations/effect__sql-sqlite-do__index.yaml create mode 100644 migration/annotations/effect__sql-sqlite-node__SqliteClient.yaml create mode 100644 migration/annotations/effect__sql-sqlite-node__index.yaml create mode 100644 migration/annotations/effect__sql-sqlite-react-native__SqliteClient.yaml create mode 100644 migration/annotations/effect__sql-sqlite-react-native__index.yaml create mode 100644 migration/annotations/effect__sql-sqlite-wasm__SqliteClient.yaml create mode 100644 migration/annotations/effect__sql-sqlite-wasm__index.yaml create mode 100644 migration/annotations/effect__sql.yaml create mode 100644 migration/annotations/effect__sql__Migrator.yaml create mode 100644 migration/annotations/effect__sql__Migrator__FileSystem.yaml create mode 100644 migration/annotations/effect__sql__Model.yaml create mode 100644 migration/annotations/effect__sql__SqlClient.yaml create mode 100644 migration/annotations/effect__sql__SqlConnection.yaml create mode 100644 migration/annotations/effect__sql__SqlError.yaml create mode 100644 migration/annotations/effect__sql__SqlEventJournal.yaml create mode 100644 migration/annotations/effect__sql__SqlEventLogServer.yaml create mode 100644 migration/annotations/effect__sql__SqlPersistedQueue.yaml create mode 100644 migration/annotations/effect__sql__SqlResolver.yaml create mode 100644 migration/annotations/effect__sql__SqlSchema.yaml create mode 100644 migration/annotations/effect__sql__Statement.yaml create mode 100644 migration/annotations/effect__sql__index.yaml create mode 100644 migration/annotations/effect__typeclass.yaml create mode 100644 migration/annotations/effect__typeclass__Alternative.yaml create mode 100644 migration/annotations/effect__typeclass__Applicative.yaml create mode 100644 migration/annotations/effect__typeclass__Bicovariant.yaml create mode 100644 migration/annotations/effect__typeclass__Bounded.yaml create mode 100644 migration/annotations/effect__typeclass__Chainable.yaml create mode 100644 migration/annotations/effect__typeclass__Contravariant.yaml create mode 100644 migration/annotations/effect__typeclass__Coproduct.yaml create mode 100644 migration/annotations/effect__typeclass__Covariant.yaml create mode 100644 migration/annotations/effect__typeclass__Filterable.yaml create mode 100644 migration/annotations/effect__typeclass__FlatMap.yaml create mode 100644 migration/annotations/effect__typeclass__Foldable.yaml create mode 100644 migration/annotations/effect__typeclass__Invariant.yaml create mode 100644 migration/annotations/effect__typeclass__Monad.yaml create mode 100644 migration/annotations/effect__typeclass__Monoid.yaml create mode 100644 migration/annotations/effect__typeclass__Of.yaml create mode 100644 migration/annotations/effect__typeclass__Pointed.yaml create mode 100644 migration/annotations/effect__typeclass__Product.yaml create mode 100644 migration/annotations/effect__typeclass__SemiAlternative.yaml create mode 100644 migration/annotations/effect__typeclass__SemiApplicative.yaml create mode 100644 migration/annotations/effect__typeclass__SemiCoproduct.yaml create mode 100644 migration/annotations/effect__typeclass__SemiProduct.yaml create mode 100644 migration/annotations/effect__typeclass__Semigroup.yaml create mode 100644 migration/annotations/effect__typeclass__Traversable.yaml create mode 100644 migration/annotations/effect__typeclass__TraversableFilterable.yaml create mode 100644 migration/annotations/effect__typeclass__data__Array.yaml create mode 100644 migration/annotations/effect__typeclass__data__BigInt.yaml create mode 100644 migration/annotations/effect__typeclass__data__Boolean.yaml create mode 100644 migration/annotations/effect__typeclass__data__Duration.yaml create mode 100644 migration/annotations/effect__typeclass__data__Effect.yaml create mode 100644 migration/annotations/effect__typeclass__data__Either.yaml create mode 100644 migration/annotations/effect__typeclass__data__Identity.yaml create mode 100644 migration/annotations/effect__typeclass__data__Micro.yaml create mode 100644 migration/annotations/effect__typeclass__data__Number.yaml create mode 100644 migration/annotations/effect__typeclass__data__Option.yaml create mode 100644 migration/annotations/effect__typeclass__data__Ordering.yaml create mode 100644 migration/annotations/effect__typeclass__data__Predicate.yaml create mode 100644 migration/annotations/effect__typeclass__data__Record.yaml create mode 100644 migration/annotations/effect__typeclass__data__String.yaml create mode 100644 migration/annotations/effect__typeclass__data__Tuple.yaml create mode 100644 migration/annotations/effect__typeclass__index.yaml create mode 100644 migration/annotations/effect__vitest__index.yaml create mode 100644 migration/annotations/effect__vitest__utils.yaml create mode 100644 migration/annotations/effect__workflow.yaml create mode 100644 migration/annotations/effect__workflow__Activity.yaml create mode 100644 migration/annotations/effect__workflow__DurableClock.yaml create mode 100644 migration/annotations/effect__workflow__DurableDeferred.yaml create mode 100644 migration/annotations/effect__workflow__DurableQueue.yaml create mode 100644 migration/annotations/effect__workflow__DurableRateLimiter.yaml create mode 100644 migration/annotations/effect__workflow__Workflow.yaml create mode 100644 migration/annotations/effect__workflow__WorkflowEngine.yaml create mode 100644 migration/annotations/effect__workflow__WorkflowProxy.yaml create mode 100644 migration/annotations/effect__workflow__WorkflowProxyServer.yaml create mode 100644 packages/ai/anthropic/README.md delete mode 100644 packages/ai/anthropic/docgen.json create mode 100644 packages/ai/anthropic/test/AnthropicClient.test.ts create mode 100644 packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts delete mode 100644 packages/ai/anthropic/vitest.config.ts create mode 100644 packages/ai/openai-compat/README.md delete mode 100644 packages/ai/openai-compat/docgen.json create mode 100644 packages/ai/openai-compat/typetest/OpenAiConfig.tst.ts delete mode 100644 packages/ai/openai-compat/vitest.config.ts create mode 100644 packages/ai/openai/README.md delete mode 100644 packages/ai/openai/docgen.json delete mode 100644 packages/ai/openai/vitest.config.ts create mode 100644 packages/ai/openrouter/README.md delete mode 100644 packages/ai/openrouter/docgen.json create mode 100644 packages/ai/openrouter/test/Generated.test.ts create mode 100644 packages/ai/openrouter/test/OpenRouterClient.test.ts delete mode 100644 packages/ai/openrouter/vitest.config.ts delete mode 100644 packages/atom/react/docgen.json delete mode 100644 packages/atom/react/vitest.config.ts delete mode 100644 packages/atom/solid/docgen.json delete mode 100644 packages/atom/solid/vitest.config.ts delete mode 100644 packages/atom/vue/docgen.json delete mode 100644 packages/atom/vue/vitest.config.ts create mode 100644 packages/effect/ARBITRARY-FOLLOW-UPS.md create mode 100644 packages/effect/ARBITRARY-MIGRATION.md create mode 100644 packages/effect/ARBITRARY.md create mode 100644 packages/effect/benchmark/Pool.ts create mode 100644 packages/effect/benchmark/http/multipart.ts create mode 100644 packages/effect/benchmark/http/serverAllocations.ts create mode 100644 packages/effect/benchmark/http/tracer.ts create mode 100644 packages/effect/benchmark/httpapi/sseClientDecoder.ts create mode 100644 packages/effect/benchmark/rpc/RpcSerialization.ts delete mode 100644 packages/effect/benchmark/schema/Enums.ts create mode 100644 packages/effect/benchmark/schema/SchemaBinary.md create mode 100644 packages/effect/benchmark/schema/SchemaBinary.ts create mode 100644 packages/effect/benchmark/schema/SchemaError.ts delete mode 100644 packages/effect/benchmark/schema/array.ts delete mode 100644 packages/effect/benchmark/schema/filter.ts delete mode 100644 packages/effect/benchmark/schema/object.ts delete mode 100644 packages/effect/benchmark/schema/tagged-union.ts delete mode 100644 packages/effect/benchmark/schema/transformation.ts delete mode 100644 packages/effect/docgen.json create mode 100644 packages/effect/runtimeperf/README.md create mode 100644 packages/effect/runtimeperf/compare.mts create mode 100644 packages/effect/runtimeperf/config.json create mode 100644 packages/effect/runtimeperf/materialize.mts create mode 100644 packages/effect/runtimeperf/run.mts create mode 100644 packages/effect/runtimeperf/stats.mts create mode 100644 packages/effect/runtimeperf/suites/arbitrary/fixtures/fast-check-v4.ts create mode 100644 packages/effect/runtimeperf/suites/arbitrary/fixtures/native.ts create mode 100644 packages/effect/runtimeperf/suites/arbitrary/fixtures/schema.ts create mode 100644 packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/data.ts create mode 100644 packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/effect-beta.ts create mode 100644 packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/valibot.ts create mode 100644 packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/zod.ts create mode 100644 packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts create mode 100644 packages/effect/runtimeperf/suites/schema/fixtures/behavior.ts create mode 100644 packages/effect/runtimeperf/suites/schema/fixtures/cold.ts create mode 100644 packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts create mode 100644 packages/effect/runtimeperf/test/materialize.test.mts create mode 100644 packages/effect/runtimeperf/test/registry.test.mts create mode 100644 packages/effect/runtimeperf/test/stats.test.mts create mode 100644 packages/effect/runtimeperf/test/worker.test.mts create mode 100644 packages/effect/runtimeperf/utils.mts create mode 100644 packages/effect/runtimeperf/worker.mts create mode 100644 packages/effect/src/ByteSize.ts create mode 100644 packages/effect/src/LayerRef.ts delete mode 100644 packages/effect/src/SchemaUtils.ts create mode 100644 packages/effect/src/StandardSchema.ts create mode 100644 packages/effect/src/internal/arbitrary/model.ts create mode 100644 packages/effect/src/internal/arbitrary/regexp.ts create mode 100644 packages/effect/src/internal/arbitrary/runner.ts create mode 100644 packages/effect/src/internal/arbitrary/schema.ts create mode 100644 packages/effect/src/internal/count.ts create mode 100644 packages/effect/src/internal/graph.ts create mode 100644 packages/effect/src/internal/graphCsr.ts create mode 100644 packages/effect/src/internal/persistence.ts delete mode 100644 packages/effect/src/internal/schema/arbitrary.ts create mode 100644 packages/effect/src/internal/schema/fromJsonSchemaDocument.ts create mode 100644 packages/effect/src/internal/schema/fromRepresentation.ts create mode 100644 packages/effect/src/internal/schema/make.ts create mode 100644 packages/effect/src/internal/schema/parser.ts delete mode 100644 packages/effect/src/internal/schema/representation.ts delete mode 100644 packages/effect/src/internal/schema/schema.ts create mode 100644 packages/effect/src/internal/schema/standardSchema.ts create mode 100644 packages/effect/src/internal/schema/toCodeDocument.ts create mode 100644 packages/effect/src/internal/schema/toCodec.ts create mode 100644 packages/effect/src/internal/schema/toDifferJsonPatch.ts create mode 100644 packages/effect/src/internal/schema/toEncoderXml.ts rename packages/effect/src/internal/schema/{equivalence.ts => toEquivalence.ts} (58%) create mode 100644 packages/effect/src/internal/schema/toFormatter.ts create mode 100644 packages/effect/src/internal/schema/toIso.ts create mode 100644 packages/effect/src/internal/schema/toJsonSchemaDocument.ts create mode 100644 packages/effect/src/internal/schema/toRepresentation.ts create mode 100644 packages/effect/src/internal/schemaError.ts create mode 100644 packages/effect/src/internal/uuid.ts delete mode 100644 packages/effect/src/internal/version.ts delete mode 100644 packages/effect/src/testing/FastCheck.ts create mode 100644 packages/effect/src/unstable/ai/McpProtocol.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpCore.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpProtocol.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpProtocol/v2024_11_05.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpProtocol/v2025_03_26.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpProtocol/v2025_06_18.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpProtocol/v2025_11_25.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpProtocolRegistry.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpSchema/v2024_11_05.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpSchema/v2025_03_26.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpSchema/v2025_06_18.ts create mode 100644 packages/effect/src/unstable/ai/internal/mcpSchema/v2025_11_25.ts create mode 100644 packages/effect/src/unstable/ai/internal/structured-output.ts create mode 100644 packages/effect/src/unstable/arbitrary/Arbitrary.ts create mode 100644 packages/effect/src/unstable/arbitrary/index.ts create mode 100644 packages/effect/src/unstable/cli/CliConfig.ts delete mode 100644 packages/effect/src/unstable/cli/SEMANTICS.md create mode 100644 packages/effect/src/unstable/cli/internal/wizard.ts create mode 100644 packages/effect/src/unstable/cluster/K8sTypes.ts create mode 100644 packages/effect/src/unstable/cluster/internal/clusterAbandon.ts create mode 100644 packages/effect/src/unstable/cluster/internal/shardLock.ts create mode 100644 packages/effect/src/unstable/encoding/Ini.ts delete mode 100644 packages/effect/src/unstable/encoding/Msgpack.ts create mode 100644 packages/effect/src/unstable/encoding/SchemaBinary.ts create mode 100644 packages/effect/src/unstable/encoding/Toml.ts create mode 100644 packages/effect/src/unstable/encoding/Yaml.ts create mode 100644 packages/effect/src/unstable/http/FindMyWay/internal/queryString.ts create mode 100644 packages/effect/src/unstable/http/FindMyWay/internal/router.ts create mode 100644 packages/effect/src/unstable/http/HttpStatus.ts create mode 100644 packages/effect/src/unstable/http/Mime.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser/HeadersParser.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser/Search.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser/internal/contentType.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser/internal/headers.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser/internal/multipart.ts create mode 100644 packages/effect/src/unstable/http/MultipartParser/internal/search.ts delete mode 100644 packages/effect/src/unstable/http/Multipasta.ts delete mode 100644 packages/effect/src/unstable/http/Multipasta/HeadersParser.ts delete mode 100644 packages/effect/src/unstable/http/Multipasta/Node.ts delete mode 100644 packages/effect/src/unstable/http/Multipasta/Search.ts delete mode 100644 packages/effect/src/unstable/http/Multipasta/Web.ts create mode 100644 packages/effect/src/unstable/http/internal/compression.ts create mode 100644 packages/effect/src/unstable/http/internal/headers.ts create mode 100644 packages/effect/src/unstable/http/internal/httpBody.ts create mode 100644 packages/effect/src/unstable/http/internal/mimeTypes.ts create mode 100644 packages/effect/src/unstable/httpapi/internal/mediaType.ts create mode 100644 packages/effect/src/unstable/net/IpInterface.ts create mode 100644 packages/effect/src/unstable/net/IpNetwork.ts create mode 100644 packages/effect/src/unstable/net/NetAddress.ts create mode 100644 packages/effect/src/unstable/net/index.ts create mode 100644 packages/effect/test/ArrayNaNIndex.test.ts create mode 100644 packages/effect/test/ByteSize.test.ts create mode 100644 packages/effect/test/CauseMapAnnotations.test.ts create mode 100644 packages/effect/test/Clock.test.ts create mode 100644 packages/effect/test/Context.test.ts create mode 100644 packages/effect/test/Effectable.test.ts create mode 100644 packages/effect/test/Encoding.test.ts create mode 100644 packages/effect/test/FiberSetRuntimePropagation.test.ts create mode 100644 packages/effect/test/FileSystem.test-utils.ts create mode 100644 packages/effect/test/HashRing.test.ts create mode 100644 packages/effect/test/LayerRef.test.ts create mode 100644 packages/effect/test/Pull.test.ts create mode 100644 packages/effect/test/StackCapture.test.ts create mode 100644 packages/effect/test/Stdio.test.ts create mode 100644 packages/effect/test/SynchronizedRef.modifySomeEffect.test.ts create mode 100644 packages/effect/test/cluster/Envelope.test.ts create mode 100644 packages/effect/test/cluster/ResourceMap.test.ts create mode 100644 packages/effect/test/cluster/ResourceRef.test.ts create mode 100644 packages/effect/test/cluster/RunnerServer.test.ts create mode 100644 packages/effect/test/cluster/RunnerStorage.test.ts create mode 100644 packages/effect/test/cluster/Runners.test.ts create mode 100644 packages/effect/test/cluster/ShardingConfig.test.ts create mode 100644 packages/effect/test/cluster/interruptors.test.ts create mode 100644 packages/effect/test/fixtures/migrator/0001_first.js rename packages/{platform-node-shared => effect}/test/fixtures/text.txt (100%) create mode 100644 packages/effect/test/internal/Uuid.test.ts create mode 100644 packages/effect/test/rpc/RpcServer.test.ts create mode 100644 packages/effect/test/schema/Graph.test.ts create mode 100644 packages/effect/test/schema/SchemaTransformation.test.ts create mode 100644 packages/effect/test/schema/TemplateLiteral.test.ts create mode 100644 packages/effect/test/schema/jsonSchemaRoundTrip.test.ts create mode 100644 packages/effect/test/schema/representation/builtInRevivers.test.ts delete mode 100644 packages/effect/test/schema/representation/fromASTs.test.ts create mode 100644 packages/effect/test/schema/representation/fromJson.test.ts create mode 100644 packages/effect/test/schema/representation/fromJsonMultiDocument.test.ts create mode 100644 packages/effect/test/schema/representation/fromJsonSchemaMultiDocument.test.ts create mode 100644 packages/effect/test/schema/representation/fromRepresentation.test.ts create mode 100644 packages/effect/test/schema/representation/fromRepresentations.test.ts create mode 100644 packages/effect/test/schema/representation/makeCode.test.ts create mode 100644 packages/effect/test/schema/representation/schemaToJsonSchemaDocument.test.ts create mode 100644 packages/effect/test/schema/representation/stringTreeRoundtrip.test.ts create mode 100644 packages/effect/test/schema/representation/toCodeDocument.annotations.test.ts create mode 100644 packages/effect/test/schema/representation/toJson.test.ts create mode 100644 packages/effect/test/schema/representation/toJsonMultiDocument.test.ts create mode 100644 packages/effect/test/schema/representation/toJsonSchemaDocument.test.ts create mode 100644 packages/effect/test/schema/representation/toMultiDocument.test.ts create mode 100644 packages/effect/test/schema/representation/toRepresentation.test.ts create mode 100644 packages/effect/test/schema/representation/toRepresentations.test.ts delete mode 100644 packages/effect/test/schema/representation/toSchema.test.ts delete mode 100644 packages/effect/test/schema/toArbitrary.test.ts create mode 100644 packages/effect/test/unstable/ai/AnthropicStructuredOutputRepresentation.test.ts create mode 100644 packages/effect/test/unstable/ai/LanguageModelRepresentation.test.ts create mode 100644 packages/effect/test/unstable/ai/McpSchema.test.ts delete mode 100644 packages/effect/test/unstable/ai/McpServer.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/BaseProtocolTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/CompletionTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/ElicitationTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/LifecycleTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/LoggingTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/McpConformance.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/McpConformanceFixtures.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/McpTestPeer.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/PromptsTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/ResourcesTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/RootsTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/SamplingTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/ToolsTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/TransportsTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpConformance/UtilitiesTest.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpProtocol.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/McpServer.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/ProtocolAdapters.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/TestUtils/McpHttpHarness.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/TestUtils/McpServerLayer.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/TestUtils/McpStdioHarness.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/v2024_11_05.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/v2025_03_26.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/v2025_06_18.test.ts create mode 100644 packages/effect/test/unstable/ai/McpServer/v2025_11_25.test.ts create mode 100644 packages/effect/test/unstable/ai/OpenAiStructuredOutputRepresentation.test.ts create mode 100644 packages/effect/test/unstable/ai/Response.test.ts create mode 100644 packages/effect/test/unstable/ai/Tokenizer.test.ts create mode 100644 packages/effect/test/unstable/ai/ToolRepresentation.test.ts create mode 100644 packages/effect/test/unstable/arbitrary/Arbitrary.test.ts create mode 100644 packages/effect/test/unstable/cli/Ansi.test.ts create mode 100644 packages/effect/test/unstable/cli/Lexer.test.ts create mode 100644 packages/effect/test/unstable/devtools/DevToolsClient.test.ts create mode 100644 packages/effect/test/unstable/encoding/Ini.test.ts create mode 100644 packages/effect/test/unstable/encoding/SchemaBinary.test.ts create mode 100644 packages/effect/test/unstable/encoding/Toml.test.ts create mode 100644 packages/effect/test/unstable/encoding/Yaml.test.ts create mode 100644 packages/effect/test/unstable/eventlog/EventLogMessage.test.ts create mode 100644 packages/effect/test/unstable/eventlog/EventLogServerUnencrypted.test.ts create mode 100644 packages/effect/test/unstable/http/FetchHttpClient.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/case-insensitive.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/matching-order.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/methods.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/optional-params.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/params-collisions.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/path-params-match.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/querystring.test.ts create mode 100644 packages/effect/test/unstable/http/FindMyWay/regex.test.ts create mode 100644 packages/effect/test/unstable/http/HttpBody.test.ts create mode 100644 packages/effect/test/unstable/http/HttpCompression.test.ts create mode 100644 packages/effect/test/unstable/http/HttpPlatform.test.ts create mode 100644 packages/effect/test/unstable/http/HttpRouter.test.ts create mode 100644 packages/effect/test/unstable/http/HttpServer.test.ts create mode 100644 packages/effect/test/unstable/http/HttpServerRespondable.test.ts create mode 100644 packages/effect/test/unstable/http/HttpStaticServer.test.ts create mode 100644 packages/effect/test/unstable/http/HttpStatus.test.ts create mode 100644 packages/effect/test/unstable/http/Mime.test.ts create mode 100644 packages/effect/test/unstable/http/SchemaRepresentation.test.ts create mode 100644 packages/effect/test/unstable/http/Template.test.ts create mode 100644 packages/effect/test/unstable/httpapi/HttpApi.test.ts create mode 100644 packages/effect/test/unstable/httpapi/HttpApiDocumentation.test.ts create mode 100644 packages/effect/test/unstable/httpapi/OpenApiRepresentation.test.ts create mode 100644 packages/effect/test/unstable/net/IpInterface.test.ts create mode 100644 packages/effect/test/unstable/net/IpNetwork.test.ts create mode 100644 packages/effect/test/unstable/net/NetAddress.test.ts create mode 100644 packages/effect/test/unstable/observability/OtlpEnvHeaders.test.ts create mode 100644 packages/effect/test/unstable/observability/OtlpSerialization.test.ts create mode 100644 packages/effect/test/unstable/persistence/RateLimiterTest.ts create mode 100644 packages/effect/test/unstable/persistence/SqlCleanupTest.ts create mode 100644 packages/effect/test/unstable/process/ChildProcessSpawnerTest.ts rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/bash/no-permissions.sh (100%) rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/bash/parent-exits-early.sh (71%) rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/bash/spawn-children.sh (92%) rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/config/SHOUTING (100%) rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/config/integer (100%) rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/config/nested/config (100%) rename packages/{platform-node-shared/test => effect/test/unstable/process}/fixtures/config/secret (100%) create mode 100644 packages/effect/test/unstable/socket/Socket.test.ts create mode 100644 packages/effect/test/unstable/sql/SqlClient.test.ts create mode 100644 packages/effect/test/unstable/sql/Statement.test.ts create mode 100644 packages/effect/test/unstable/workers/Worker.test.ts create mode 100644 packages/effect/typeperf/compare.mjs create mode 100644 packages/effect/typeperf/compare/httpapi.mjs create mode 100644 packages/effect/typeperf/suites/httpapi/baseline.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/_endpoint-selection-types.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/_grouped-api-types.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/builder-endpoint-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/builder-handle-all-two-batches-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/builder-handlers-count-100.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/builder-raw-handlers-count-100.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/client-endpoint-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/client-group-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/client-groups-100x5-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/client-methods-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/client-top-level-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/endpoint-class-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/endpoint-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/group-endpoints-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/url-builder-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/fixtures/url-builder-top-level-count-500.ts create mode 100644 packages/effect/typeperf/suites/httpapi/thresholds.json create mode 100644 packages/effect/typetest/ByteSize.tst.ts create mode 100644 packages/effect/typetest/ChannelSchema.tst.ts create mode 100644 packages/effect/typetest/Clock.tst.ts create mode 100644 packages/effect/typetest/ConfigProvider.tst.ts create mode 100644 packages/effect/typetest/Context.tst.ts create mode 100644 packages/effect/typetest/Effectable.tst.ts create mode 100644 packages/effect/typetest/Fiber.tst.ts create mode 100644 packages/effect/typetest/FileSystem.tst.ts create mode 100644 packages/effect/typetest/Function.tst.ts create mode 100644 packages/effect/typetest/Graph.tst.ts create mode 100644 packages/effect/typetest/Iterable.tst.ts create mode 100644 packages/effect/typetest/Layer.tst.ts create mode 100644 packages/effect/typetest/Match.tst.ts create mode 100644 packages/effect/typetest/Record.tst.ts create mode 100644 packages/effect/typetest/Sink.tst.ts create mode 100644 packages/effect/typetest/SynchronizedRef.tst.ts create mode 100644 packages/effect/typetest/TestSchema.tst.ts create mode 100644 packages/effect/typetest/schema/FromJsonSchema.tst.ts create mode 100644 packages/effect/typetest/schema/Graph.tst.ts create mode 100644 packages/effect/typetest/schema/SchemaBuiltInRevivers.tst.ts create mode 100644 packages/effect/typetest/schema/SchemaJsonSchemaConsumer.tst.ts create mode 100644 packages/effect/typetest/schema/SchemaRepresentation.tst.ts create mode 100644 packages/effect/typetest/schema/SchemaRepresentationCompilers.tst.ts create mode 100644 packages/effect/typetest/schema/SchemaRepresentationReviver.tst.ts delete mode 100644 packages/effect/typetest/schema/toArbitrary.tst.ts create mode 100644 packages/effect/typetest/unstable/ai/McpServer.tst.ts create mode 100644 packages/effect/typetest/unstable/ai/Response.tst.ts create mode 100644 packages/effect/typetest/unstable/arbitrary/Arbitrary.tst.ts create mode 100644 packages/effect/typetest/unstable/cluster/EntityProxyServer.tst.ts create mode 100644 packages/effect/typetest/unstable/cluster/K8sHttpClient.tst.ts create mode 100644 packages/effect/typetest/unstable/cluster/Reply.tst.ts create mode 100644 packages/effect/typetest/unstable/http/HttpPlatform.tst.ts create mode 100644 packages/effect/typetest/unstable/http/HttpRouter.tst.ts create mode 100644 packages/effect/typetest/unstable/httpapi/HttpApi.tst.ts create mode 100644 packages/effect/typetest/unstable/httpapi/HttpApiGroup.tst.ts create mode 100644 packages/effect/typetest/unstable/httpapi/OpenApiRepresentation.tst.ts create mode 100644 packages/effect/typetest/unstable/net/IpInterface.tst.ts create mode 100644 packages/effect/typetest/unstable/net/IpNetwork.tst.ts create mode 100644 packages/effect/typetest/unstable/net/NetAddress.tst.ts create mode 100644 packages/effect/typetest/unstable/reactivity/Atom.tst.ts create mode 100644 packages/effect/typetest/unstable/reactivity/AtomRpc.tst.ts create mode 100644 packages/effect/typetest/unstable/socket/Socket.tst.ts delete mode 100644 packages/effect/vitest.config.ts delete mode 100644 packages/opentelemetry/docgen.json rename packages/opentelemetry/src/{Logger.ts => OtelLogger.ts} (96%) rename packages/opentelemetry/src/{Metrics.ts => OtelMetrics.ts} (72%) rename packages/opentelemetry/src/{Tracer.ts => OtelTracer.ts} (91%) rename packages/opentelemetry/test/{Logger.test.ts => OtelLogger.test.ts} (55%) rename packages/opentelemetry/test/{Metrics.test.ts => OtelMetrics.test.ts} (88%) rename packages/opentelemetry/test/{Tracer.test.ts => OtelTracer.test.ts} (52%) delete mode 100644 packages/opentelemetry/vitest.config.ts delete mode 100644 packages/platform-browser/README.md delete mode 100644 packages/platform-browser/docgen.json delete mode 100644 packages/platform-browser/test/BrowserCrypto.test.ts delete mode 100644 packages/platform-browser/test/BrowserKeyValueStore.test.ts delete mode 100644 packages/platform-browser/tsconfig.json delete mode 100644 packages/platform-browser/vitest.config.ts delete mode 100644 packages/platform-bun/README.md delete mode 100644 packages/platform-bun/docgen.json delete mode 100644 packages/platform-bun/tsconfig.json delete mode 100644 packages/platform-node-shared/README.md delete mode 100644 packages/platform-node-shared/docgen.json delete mode 100644 packages/platform-node-shared/src/NodeSocket.ts delete mode 100644 packages/platform-node-shared/test/NodeChildProcessSpawner.test.ts delete mode 100644 packages/platform-node-shared/test/NodeFileSystem.test.ts delete mode 100644 packages/platform-node-shared/test/NodeSink.test.ts delete mode 100644 packages/platform-node-shared/tsconfig.json delete mode 100644 packages/platform-node-shared/vitest.config.ts delete mode 100644 packages/platform-node/README.md delete mode 100644 packages/platform-node/docgen.json delete mode 100644 packages/platform-node/src/Mime.ts delete mode 100644 packages/platform-node/src/NodeHttpPlatform.ts delete mode 100644 packages/platform-node/src/NodeRedis.ts delete mode 100644 packages/platform-node/test/NodeHttpClient.test.ts delete mode 100644 packages/platform-node/test/NodeHttpPlatform.test.ts delete mode 100644 packages/platform-node/test/NodeRedis.test.ts delete mode 100644 packages/platform-node/test/NodeSocket.test.ts delete mode 100644 packages/platform-node/test/cluster/SocketRunner.test.ts delete mode 100644 packages/platform-node/test/cluster/SqlMessageStorage.test.ts delete mode 100644 packages/platform-node/test/cluster/SqlRunnerStorage.test.ts delete mode 100644 packages/platform-node/tsconfig.json delete mode 100644 packages/platform-node/vitest.config.ts rename packages/{platform-browser => platform/browser}/CHANGELOG.md (54%) rename packages/{platform-browser => platform/browser}/LICENSE (100%) create mode 100644 packages/platform/browser/README.md rename packages/{platform-browser => platform/browser}/package.json (72%) rename packages/{platform-browser => platform/browser}/src/BrowserCrypto.ts (93%) rename packages/{platform-browser => platform/browser}/src/BrowserHttpClient.ts (92%) rename packages/{platform-browser => platform/browser}/src/BrowserKeyValueStore.ts (75%) rename packages/{platform-browser => platform/browser}/src/BrowserPersistence.ts (100%) rename packages/{platform-browser => platform/browser}/src/BrowserRuntime.ts (60%) rename packages/{platform-browser => platform/browser}/src/BrowserSocket.ts (84%) rename packages/{platform-browser => platform/browser}/src/BrowserStream.ts (96%) rename packages/{platform-browser => platform/browser}/src/BrowserWorker.ts (100%) rename packages/{platform-browser => platform/browser}/src/BrowserWorkerRunner.ts (98%) rename packages/{platform-browser => platform/browser}/src/Clipboard.ts (99%) rename packages/{platform-browser => platform/browser}/src/Geolocation.ts (99%) rename packages/{platform-browser => platform/browser}/src/IndexedDb.ts (98%) rename packages/{platform-browser => platform/browser}/src/IndexedDbDatabase.ts (98%) rename packages/{platform-browser => platform/browser}/src/IndexedDbQueryBuilder.ts (96%) rename packages/{platform-browser => platform/browser}/src/IndexedDbTable.ts (97%) rename packages/{platform-browser => platform/browser}/src/IndexedDbVersion.ts (98%) rename packages/{platform-browser => platform/browser}/src/Permissions.ts (99%) rename packages/{platform-browser => platform/browser}/src/index.ts (100%) create mode 100644 packages/platform/browser/test/BrowserCrypto.test.ts rename packages/{platform-browser => platform/browser}/test/BrowserHttpClient.test.ts (57%) create mode 100644 packages/platform/browser/test/BrowserKeyValueStore.test.ts rename packages/{platform-browser => platform/browser}/test/BrowserPersistence.test.ts (99%) rename packages/{platform-browser => platform/browser}/test/BrowserPersistencePersistedCache.test.ts (81%) create mode 100644 packages/platform/browser/test/BrowserWorkerRunner.test.ts rename packages/{platform-browser => platform/browser}/test/IndexedDbDatabase.test.ts (85%) rename packages/{platform-browser => platform/browser}/test/IndexedDbQueryBuilder.test.ts (88%) rename packages/{platform-browser => platform/browser}/test/IndexedDbTable.test.ts (100%) rename packages/{platform-browser => platform/browser}/test/IndexedDbVersion.test.ts (100%) rename packages/{platform-browser => platform/browser}/test/Permissions.test.ts (100%) rename packages/{platform-browser => platform/browser}/test/RpcWorker.test.ts (100%) rename packages/{platform-browser => platform/browser}/test/fixtures/rpc-e2e.ts (94%) rename packages/{platform-browser => platform/browser}/test/fixtures/rpc-schemas.ts (92%) rename packages/{platform-browser => platform/browser}/test/fixtures/rpc-worker.ts (73%) create mode 100644 packages/platform/browser/tsconfig.json rename packages/{platform-browser => platform/browser}/vitest.setup.ts (100%) rename packages/{platform-bun => platform/bun}/CHANGELOG.md (52%) rename packages/{platform-bun => platform/bun}/LICENSE (100%) create mode 100644 packages/platform/bun/README.md rename packages/{platform-bun => platform/bun}/package.json (77%) rename packages/{platform-bun => platform/bun}/src/BunChildProcessSpawner.ts (64%) rename packages/{platform-bun => platform/bun}/src/BunClusterHttp.ts (92%) rename packages/{platform-bun => platform/bun}/src/BunClusterSocket.ts (91%) rename packages/{platform-bun => platform/bun}/src/BunCrypto.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunFileSystem.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunHttpClient.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunHttpPlatform.ts (54%) rename packages/{platform-bun => platform/bun}/src/BunHttpServer.ts (67%) rename packages/{platform-bun => platform/bun}/src/BunHttpServerRequest.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunMultipart.ts (88%) rename packages/{platform-bun => platform/bun}/src/BunPath.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunRedis.ts (67%) rename packages/{platform-bun => platform/bun}/src/BunRuntime.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunServices.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunSink.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunSocket.ts (82%) rename packages/{platform-bun => platform/bun}/src/BunSocketServer.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunStdio.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunStream.ts (57%) rename packages/{platform-bun => platform/bun}/src/BunTerminal.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunWorker.ts (100%) rename packages/{platform-bun => platform/bun}/src/BunWorkerRunner.ts (90%) rename packages/{platform-bun => platform/bun}/src/index.ts (100%) create mode 100644 packages/platform/bun/test/BunHttpCompression.test.ts create mode 100644 packages/platform/bun/test/BunHttpPlatform.test.ts create mode 100644 packages/platform/bun/test/BunHttpServer.test.ts create mode 100644 packages/platform/bun/test/BunStream.test.ts create mode 100644 packages/platform/bun/tsconfig.json create mode 100644 packages/platform/deno/CHANGELOG.md create mode 100644 packages/platform/deno/LICENSE create mode 100644 packages/platform/deno/README.md create mode 100644 packages/platform/deno/package.json create mode 100644 packages/platform/deno/src/DenoChildProcessSpawner.ts create mode 100644 packages/platform/deno/src/DenoClusterHttp.ts create mode 100644 packages/platform/deno/src/DenoClusterSocket.ts create mode 100644 packages/platform/deno/src/DenoCrypto.ts create mode 100644 packages/platform/deno/src/DenoFileSystem.ts create mode 100644 packages/platform/deno/src/DenoHttpClient.ts create mode 100644 packages/platform/deno/src/DenoHttpPlatform.ts create mode 100644 packages/platform/deno/src/DenoHttpServer.ts create mode 100644 packages/platform/deno/src/DenoHttpServerRequest.ts create mode 100644 packages/platform/deno/src/DenoKeyValueStore.ts create mode 100644 packages/platform/deno/src/DenoMultipart.ts create mode 100644 packages/platform/deno/src/DenoPath.ts create mode 100644 packages/platform/deno/src/DenoRedis.ts create mode 100644 packages/platform/deno/src/DenoRuntime.ts create mode 100644 packages/platform/deno/src/DenoServices.ts create mode 100644 packages/platform/deno/src/DenoSocket.ts rename packages/{platform-node/src/NodeSocketServer.ts => platform/deno/src/DenoSocketServer.ts} (100%) create mode 100644 packages/platform/deno/src/DenoStdio.ts create mode 100644 packages/platform/deno/src/DenoTerminal.ts create mode 100644 packages/platform/deno/src/DenoWorker.ts create mode 100644 packages/platform/deno/src/DenoWorkerRunner.ts create mode 100644 packages/platform/deno/src/index.ts create mode 100644 packages/platform/deno/src/internal/error.ts create mode 100644 packages/platform/deno/test/DenoChildProcessSpawner.test.ts create mode 100644 packages/platform/deno/test/DenoCrypto.test.ts create mode 100644 packages/platform/deno/test/DenoFileSystem.test.ts create mode 100644 packages/platform/deno/test/DenoHttpCompression.test.ts create mode 100644 packages/platform/deno/test/DenoHttpPlatform.test.ts create mode 100644 packages/platform/deno/test/DenoHttpServer.test.ts create mode 100644 packages/platform/deno/test/DenoKeyValueStore.test.ts create mode 100644 packages/platform/deno/test/DenoPath.test.ts create mode 100644 packages/platform/deno/test/DenoRedis.integration.test.ts create mode 100644 packages/platform/deno/test/DenoSocket.test.ts create mode 100644 packages/platform/deno/test/DenoSocketServer.test.ts create mode 100644 packages/platform/deno/test/DenoStdio.test.ts create mode 100644 packages/platform/deno/test/DenoTerminal.test.ts create mode 100644 packages/platform/deno/test/DenoWorkerRunner.test.ts create mode 100644 packages/platform/deno/test/RpcWorker.test.ts create mode 100644 packages/platform/deno/test/cluster/SocketRunner.test.ts create mode 100644 packages/platform/deno/test/fixtures/deno-terminal.ts create mode 100644 packages/platform/deno/test/fixtures/rpc-e2e.ts create mode 100644 packages/platform/deno/test/fixtures/rpc-schemas.ts create mode 100644 packages/platform/deno/test/fixtures/rpc-worker.ts rename packages/{platform-node => platform/deno}/test/fixtures/text.txt (100%) create mode 100644 packages/platform/deno/test/fixtures/tls/ca.pem create mode 100644 packages/platform/deno/test/fixtures/tls/cert.pem create mode 100644 packages/platform/deno/test/fixtures/tls/key.pem create mode 100644 packages/platform/deno/test/internal/error.test.ts create mode 100644 packages/platform/deno/tsconfig.json rename packages/{platform-node-shared => platform/node-shared}/CHANGELOG.md (52%) rename packages/{platform-node-shared => platform/node-shared}/LICENSE (100%) create mode 100644 packages/platform/node-shared/README.md create mode 100644 packages/platform/node-shared/benchmark/Socket.ts rename packages/{platform-node-shared => platform/node-shared}/package.json (73%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeChildProcessSpawner.ts (81%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeClusterSocket.ts (81%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeCrypto.ts (100%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeFileSystem.ts (68%) create mode 100644 packages/platform/node-shared/src/NodeHttpCompression.ts rename packages/{platform-node-shared => platform/node-shared}/src/NodePath.ts (67%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeRuntime.ts (100%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeSink.ts (80%) create mode 100644 packages/platform/node-shared/src/NodeSocket.ts rename packages/{platform-node-shared => platform/node-shared}/src/NodeSocketServer.ts (60%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeStdio.ts (93%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeStream.ts (92%) rename packages/{platform-node-shared => platform/node-shared}/src/NodeTerminal.ts (62%) rename packages/{platform-node-shared => platform/node-shared}/src/index.ts (91%) create mode 100644 packages/platform/node-shared/src/internal/nodeChildProcessSpawner.ts rename packages/{platform-node-shared => platform/node-shared}/src/internal/utils.ts (91%) create mode 100644 packages/platform/node-shared/test/HttpPlatform.test-utils.ts create mode 100644 packages/platform/node-shared/test/NodeChildProcessSpawner.test.ts create mode 100644 packages/platform/node-shared/test/NodeFileSystem.test.ts create mode 100644 packages/platform/node-shared/test/NodePath.test.ts create mode 100644 packages/platform/node-shared/test/NodeSink.test.ts create mode 100644 packages/platform/node-shared/test/NodeStdio.test.ts rename packages/{platform-node-shared => platform/node-shared}/test/NodeStream.test.ts (68%) create mode 100644 packages/platform/node-shared/test/NodeTerminal.test.ts rename packages/{platform-node-shared => platform/node-shared}/test/fixtures/helloworld.tar.gz (100%) create mode 100644 packages/platform/node-shared/test/fixtures/node-terminal.ts create mode 100644 packages/platform/node-shared/test/fixtures/process-group.ts create mode 100644 packages/platform/node-shared/tsconfig.json rename packages/{platform-node => platform/node}/CHANGELOG.md (52%) rename packages/{platform-node => platform/node}/LICENSE (100%) create mode 100644 packages/platform/node/README.md rename packages/{platform-node => platform/node}/package.json (69%) rename packages/{platform-node => platform/node}/src/NodeChildProcessSpawner.ts (100%) rename packages/{platform-node => platform/node}/src/NodeClusterHttp.ts (92%) rename packages/{platform-node => platform/node}/src/NodeClusterSocket.ts (90%) rename packages/{platform-node => platform/node}/src/NodeCrypto.ts (100%) rename packages/{platform-node => platform/node}/src/NodeFileSystem.ts (100%) rename packages/{platform-node => platform/node}/src/NodeHttpClient.ts (90%) rename packages/{platform-node => platform/node}/src/NodeHttpIncomingMessage.ts (90%) create mode 100644 packages/platform/node/src/NodeHttpPlatform.ts rename packages/{platform-node => platform/node}/src/NodeHttpServer.ts (80%) rename packages/{platform-node => platform/node}/src/NodeHttpServerRequest.ts (100%) rename packages/{platform-node => platform/node}/src/NodeMultipart.ts (94%) create mode 100644 packages/platform/node/src/NodeMultipartParser.ts rename packages/{platform-node => platform/node}/src/NodePath.ts (100%) create mode 100644 packages/platform/node/src/NodeRedis.ts rename packages/{platform-node => platform/node}/src/NodeRuntime.ts (100%) rename packages/{platform-node => platform/node}/src/NodeServices.ts (100%) rename packages/{platform-node => platform/node}/src/NodeSink.ts (100%) rename packages/{platform-node => platform/node}/src/NodeSocket.ts (76%) create mode 100644 packages/platform/node/src/NodeSocketServer.ts rename packages/{platform-node => platform/node}/src/NodeStdio.ts (100%) rename packages/{platform-node => platform/node}/src/NodeStream.ts (100%) rename packages/{platform-node => platform/node}/src/NodeTerminal.ts (100%) rename packages/{platform-node => platform/node}/src/NodeWorker.ts (97%) rename packages/{platform-node => platform/node}/src/NodeWorkerRunner.ts (67%) rename packages/{platform-node => platform/node}/src/Undici.ts (94%) rename packages/{platform-node => platform/node}/src/index.ts (92%) rename packages/{platform-node => platform/node}/test/HttpApi.test.ts (88%) rename packages/{platform-node => platform/node}/test/HttpStaticServer.test.ts (82%) rename packages/{platform-node => platform/node}/test/HttpStaticServerConditional.test.ts (96%) create mode 100644 packages/platform/node/test/KeyValueStore.test.ts create mode 100644 packages/platform/node/test/MultipartParser.test.ts rename packages/{platform-node => platform/node}/test/NodeCrypto.test.ts (100%) create mode 100644 packages/platform/node/test/NodeHttpClient.test.ts create mode 100644 packages/platform/node/test/NodeHttpCompression.test.ts create mode 100644 packages/platform/node/test/NodeHttpPlatform.test.ts rename packages/{platform-node => platform/node}/test/NodeHttpServer.test.ts (50%) create mode 100644 packages/platform/node/test/NodeRedis.integration.test.ts create mode 100644 packages/platform/node/test/NodeSocket.test.ts create mode 100644 packages/platform/node/test/NodeWorkerRunner.test.ts rename packages/{platform-node => platform/node}/test/OpenApi.test.ts (100%) rename packages/{platform-node => platform/node}/test/RpcServer.test.ts (63%) rename packages/{platform-node => platform/node}/test/__snapshots__/HttpApi.test.ts.snap (92%) create mode 100644 packages/platform/node/test/cluster-integration/ClusterCron.test.ts create mode 100644 packages/platform/node/test/cluster-integration/Entity.test.ts create mode 100644 packages/platform/node/test/cluster-integration/Locks.test.ts create mode 100644 packages/platform/node/test/cluster-integration/Persistence.test.ts create mode 100644 packages/platform/node/test/cluster-integration/README.md create mode 100644 packages/platform/node/test/cluster-integration/Residency.test.ts create mode 100644 packages/platform/node/test/cluster-integration/Smoke.test.ts create mode 100644 packages/platform/node/test/cluster-integration/Transport.test.ts create mode 100644 packages/platform/node/test/cluster-integration/Workflow.test.ts create mode 100644 packages/platform/node/test/cluster-integration/globalSetup.ts create mode 100644 packages/platform/node/test/cluster-integration/harness.ts rename packages/{platform-node => platform/node}/test/cluster/MessageStorageTest.ts (90%) create mode 100644 packages/platform/node/test/cluster/SocketRunner.test.ts create mode 100644 packages/platform/node/test/cluster/SqlMessageStorage.integration.test.ts create mode 100644 packages/platform/node/test/cluster/SqlRunnerStorage.integration.test.ts rename packages/{platform-node => platform/node}/test/fixtures/http-static-server-outside.txt (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/conditional.txt (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/custom/home.html (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/file.binx (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/guide/index.html (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/hello.txt (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/index.html (100%) rename packages/{platform-node => platform/node}/test/fixtures/http-static-server/range.txt (100%) rename packages/{platform-node => platform/node}/test/fixtures/mysql2-utils.ts (84%) rename packages/{platform-node => platform/node}/test/fixtures/pg-utils.ts (100%) rename packages/{platform-node => platform/node}/test/fixtures/rpc-e2e.ts (96%) rename packages/{platform-node => platform/node}/test/fixtures/rpc-schemas.ts (92%) create mode 100644 packages/platform/node/test/fixtures/text.txt create mode 100644 packages/platform/node/test/fixtures/tls/cert.pem create mode 100644 packages/platform/node/test/fixtures/tls/key.pem create mode 100644 packages/platform/node/test/fixtures/worker-runner.ts create mode 100644 packages/platform/node/tsconfig.json delete mode 100644 packages/sql/clickhouse/docgen.json delete mode 100644 packages/sql/clickhouse/vitest.config.ts delete mode 100644 packages/sql/d1/docgen.json delete mode 100644 packages/sql/d1/vitest.config.ts delete mode 100644 packages/sql/libsql/docgen.json rename packages/sql/libsql/test/{Client.test.ts => Client.integration.test.ts} (66%) rename packages/sql/libsql/test/{Resolver.test.ts => Resolver.integration.test.ts} (94%) delete mode 100644 packages/sql/libsql/vitest.config.ts delete mode 100644 packages/sql/mssql/docgen.json create mode 100644 packages/sql/mssql/test/Binary.test.ts rename packages/sql/{mysql2/test/Persistence.test.ts => mssql/test/Persistence.integration.test.ts} (55%) create mode 100644 packages/sql/mssql/test/utils.ts delete mode 100644 packages/sql/mssql/vitest.config.ts delete mode 100644 packages/sql/mysql2/docgen.json rename packages/sql/mysql2/test/{Client.test.ts => Client.integration.test.ts} (100%) rename packages/sql/mysql2/test/{KeyValueStore.test.ts => KeyValueStore.integration.test.ts} (100%) rename packages/sql/mysql2/test/{Model.test.ts => Model.integration.test.ts} (77%) rename packages/sql/mysql2/test/{MysqlClient.test.ts => MysqlClient.integration.test.ts} (52%) create mode 100644 packages/sql/mysql2/test/Persistence.integration.test.ts rename packages/sql/mysql2/test/{SqlEventLogServerUnencrypted.test.ts => SqlEventLogServerUnencrypted.integration.test.ts} (100%) delete mode 100644 packages/sql/mysql2/vitest.config.ts create mode 100644 packages/sql/pg/benchmark/PgClient.ts create mode 100644 packages/sql/pg/benchmark/PgCodec.ts create mode 100644 packages/sql/pg/benchmark/README.md delete mode 100644 packages/sql/pg/docgen.json create mode 100644 packages/sql/pg/src/PgAuth.ts create mode 100644 packages/sql/pg/src/PgConnection.ts create mode 100644 packages/sql/pg/src/PgPool.ts create mode 100644 packages/sql/pg/src/PgProtocol.ts create mode 100644 packages/sql/pg/src/PgTypes.ts create mode 100644 packages/sql/pg/src/internal/connection.ts create mode 100644 packages/sql/pg/src/internal/sqlError.ts rename packages/sql/pg/test/{Client.test.ts => Client.integration.test.ts} (53%) rename packages/sql/pg/test/{KeyValueStore.test.ts => KeyValueStore.integration.test.ts} (100%) create mode 100644 packages/sql/pg/test/Persistence.integration.test.ts delete mode 100644 packages/sql/pg/test/Persistence.test.ts create mode 100644 packages/sql/pg/test/PgAuth.test.ts create mode 100644 packages/sql/pg/test/PgConnection.in-process.test.ts create mode 100644 packages/sql/pg/test/PgConnection.integration.test.ts create mode 100644 packages/sql/pg/test/PgConnection.test.ts create mode 100644 packages/sql/pg/test/PgPool.integration.test.ts create mode 100644 packages/sql/pg/test/PgProtocol.test.ts create mode 100644 packages/sql/pg/test/PgTypes.test.ts rename packages/sql/pg/test/{SqlEventLogServerUnencrypted.test.ts => SqlEventLogServerUnencrypted.integration.test.ts} (100%) create mode 100644 packages/sql/pg/test/fixtures/goldens.ts create mode 100644 packages/sql/pg/test/fixtures/regenerate.ts delete mode 100644 packages/sql/pg/vitest.config.ts delete mode 100644 packages/sql/pglite/docgen.json create mode 100644 packages/sql/pglite/test/Json.test.ts create mode 100644 packages/sql/pglite/test/PersistedQueue.test.ts delete mode 100644 packages/sql/pglite/vitest.config.ts delete mode 100644 packages/sql/sqlite-bun/docgen.json delete mode 100644 packages/sql/sqlite-bun/vitest.config.ts delete mode 100644 packages/sql/sqlite-do/docgen.json delete mode 100644 packages/sql/sqlite-do/vitest.config.ts delete mode 100644 packages/sql/sqlite-node/docgen.json create mode 100644 packages/sql/sqlite-node/test/SqlModelServices.test.ts delete mode 100644 packages/sql/sqlite-node/vitest.config.ts delete mode 100644 packages/sql/sqlite-react-native/docgen.json delete mode 100644 packages/sql/sqlite-react-native/vitest.config.ts delete mode 100644 packages/sql/sqlite-wasm/docgen.json delete mode 100644 packages/sql/sqlite-wasm/vitest.config.ts delete mode 100644 packages/tools/ai-codegen/docgen.json create mode 100644 packages/tools/ai-codegen/test/Generator.test.ts delete mode 100644 packages/tools/ai-docgen/docgen.json create mode 100644 packages/tools/api-diff/README.md create mode 100644 packages/tools/api-diff/package.json create mode 100644 packages/tools/api-diff/src/Annotations.ts create mode 100644 packages/tools/api-diff/src/ApiDiff.ts create mode 100644 packages/tools/api-diff/src/Cli.ts create mode 100644 packages/tools/api-diff/src/Diff.ts create mode 100644 packages/tools/api-diff/src/Discovery.ts create mode 100644 packages/tools/api-diff/src/Error.ts create mode 100644 packages/tools/api-diff/src/Json.ts create mode 100644 packages/tools/api-diff/src/MigrationDoc.ts create mode 100644 packages/tools/api-diff/src/Model.ts create mode 100644 packages/tools/api-diff/src/Report.ts create mode 100644 packages/tools/api-diff/src/Snapshot.ts create mode 100644 packages/tools/api-diff/src/Worktrees.ts create mode 100644 packages/tools/api-diff/src/bin.ts create mode 100644 packages/tools/api-diff/test/Annotations.test.ts create mode 100644 packages/tools/api-diff/test/Cli.test.ts create mode 100644 packages/tools/api-diff/test/Diff.test.ts create mode 100644 packages/tools/api-diff/test/Discovery.test.ts create mode 100644 packages/tools/api-diff/test/Error.test.ts create mode 100644 packages/tools/api-diff/test/MigrationDoc.test.ts create mode 100644 packages/tools/api-diff/test/Snapshot.test.ts create mode 100644 packages/tools/api-diff/test/Worktrees.test.ts create mode 100644 packages/tools/api-diff/test/utils.ts create mode 100644 packages/tools/api-diff/tsconfig.json delete mode 100644 packages/tools/bundle/docgen.json create mode 100644 packages/tools/bundle/fixtures/arbitrary-combinators.ts create mode 100644 packages/tools/bundle/fixtures/http-router.ts create mode 100644 packages/tools/bundle/fixtures/schema-binary.ts create mode 100644 packages/tools/bundle/fixtures/schema-class.ts rename packages/tools/bundle/fixtures/{schema-toArbitraryLazy.ts => schema-toArbitrary.ts} (60%) create mode 100644 packages/tools/bundle/test/HttpRouterEsbuild.test.ts delete mode 100644 packages/tools/bundle/vitest.config.ts create mode 100644 packages/tools/docgen/CHANGELOG.md create mode 100644 packages/tools/docgen/LICENSE create mode 100644 packages/tools/docgen/README.md create mode 100644 packages/tools/docgen/package.json create mode 100644 packages/tools/docgen/schema.json create mode 100644 packages/tools/docgen/src/CLI.ts create mode 100644 packages/tools/docgen/src/Checker.ts create mode 100644 packages/tools/docgen/src/Configuration.ts create mode 100644 packages/tools/docgen/src/Core.ts create mode 100644 packages/tools/docgen/src/Domain.ts create mode 100644 packages/tools/docgen/src/Parser.ts create mode 100644 packages/tools/docgen/src/Printer.ts create mode 100755 packages/tools/docgen/src/bin.ts create mode 100644 packages/tools/docgen/src/index.ts create mode 100644 packages/tools/docgen/src/internal/markdown-toc.d.ts create mode 100644 packages/tools/docgen/test/Checker.test.ts create mode 100644 packages/tools/docgen/test/Configuration.test.ts create mode 100644 packages/tools/docgen/test/Core.test.ts create mode 100644 packages/tools/docgen/test/Parser.test.ts create mode 100644 packages/tools/docgen/test/fixtures/invalid-json.txt create mode 100644 packages/tools/docgen/tsconfig.json create mode 100644 packages/tools/doctest/CHANGELOG.md create mode 100644 packages/tools/doctest/README.md create mode 100644 packages/tools/doctest/package.json create mode 100644 packages/tools/doctest/src/Plugin.ts create mode 100644 packages/tools/doctest/src/Protocol.ts create mode 100644 packages/tools/doctest/src/Runner.ts create mode 100644 packages/tools/doctest/src/Runtime.ts create mode 100644 packages/tools/doctest/src/Source.ts create mode 100644 packages/tools/doctest/src/Transform.ts create mode 100644 packages/tools/doctest/src/index.ts create mode 100644 packages/tools/doctest/test/Plugin.test.ts create mode 100644 packages/tools/doctest/test/Protocol.test.ts create mode 100644 packages/tools/doctest/test/Runtime.test.ts create mode 100644 packages/tools/doctest/test/Source.test.ts create mode 100644 packages/tools/doctest/test/Transform.test.ts create mode 100644 packages/tools/doctest/test/fixtures/markdown/docs/helper.ts create mode 100644 packages/tools/doctest/test/fixtures/markdown/docs/typed.md create mode 100644 packages/tools/doctest/test/fixtures/markdown/docs/typed.mdx create mode 100644 packages/tools/doctest/test/fixtures/markdown/src/typed.ts create mode 100644 packages/tools/doctest/test/fixtures/markdown/vitest.config.ts create mode 100644 packages/tools/doctest/tsconfig.json delete mode 100644 packages/tools/jsdocs/docgen.json delete mode 100644 packages/tools/jsdocs/vitest.config.ts create mode 100644 packages/tools/openapi-generator/README.md delete mode 100644 packages/tools/openapi-generator/docgen.json create mode 100644 packages/tools/openapi-generator/test/JsonSchemaGeneratorRepresentation.test.ts create mode 100644 packages/tools/openapi-generator/test/OpenApiTransformer.test.ts delete mode 100644 packages/tools/openapi-generator/vitest.config.ts delete mode 100644 packages/tools/oxc/docgen.json delete mode 100644 packages/tools/oxc/src/oxlint/rules/jsdocs.ts delete mode 100644 packages/tools/oxc/test/jsdocs.test.ts delete mode 100644 packages/tools/oxc/vitest.config.ts delete mode 100644 packages/tools/utils/docgen.json create mode 100644 packages/tools/utils/test/Codegen.test.ts delete mode 100644 packages/vitest/docgen.json create mode 100644 packages/vitest/test/layer-concurrency.test.ts delete mode 100644 packages/vitest/vitest.config.ts delete mode 100644 patches/@changesets__assemble-release-plan.patch delete mode 100644 patches/@changesets__get-github-info.patch create mode 100644 patches/@changesets__get-github-info@1.0.1.patch create mode 100644 scripts/copy-ai-docs.mjs delete mode 100644 scripts/docs.mjs create mode 100644 scripts/set-strip-internal.mjs create mode 100644 scripts/setup-agents.mjs delete mode 100644 scripts/version.mjs delete mode 100644 scripts/version.template.txt delete mode 100644 stats.txt create mode 100644 tsconfig.tests.json create mode 100644 vitest.docs.ts delete mode 100644 vitest.shared.ts diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md new file mode 100644 index 000000000..1f33cf814 --- /dev/null +++ b/.agents/AGENTS.md @@ -0,0 +1,46 @@ +This is the Effect TypeScript monorepo. The git base branch is `main`; use `pnpm` from the repository root. + +## Layout + +- Core library source, runtime tests, and type tests are in `packages/effect/src`, `packages/effect/test`, and + `packages/effect/typetest`. +- Other package families include `packages/ai`, `packages/atom`, `packages/platform`, `packages/sql`, and + `packages/tools`; standalone packages also live directly under `packages`. +- Package tests and type tests live beside source in `test` and `typetest` directories. +- AI documentation sources are in `ai-docs/src`. +- Changesets are in `.changeset`. +- Migration sources (v3-to-v4) are in `migration/annotations`. +- Inspect nearby code before editing. + +## Validation + +Use the narrowest validation that covers the change: + +| Change type | Validation | +| -------------------------------- | ---------------------------------------------------------------------------------- | +| Code changes | `pnpm lint-fix`, targeted `pnpm test --run `, `pnpm check` | +| Tests-only changes | `pnpm lint-fix`, targeted `pnpm test --run `, `pnpm check` | +| Type-level/API type changes | Targeted `pnpm test-types `, plus `pnpm check` when source types changed | +| JSDoc text/category/link changes | `pnpm jsdocs --check`, `pnpm lint` | +| JSDoc example changes | `pnpm jsdocs --check`, `pnpm lint`, root `pnpm doctest --run ` | +| Docs-only changes | `pnpm lint-fix`; no tests unless examples or code changed | + +Never run bare `pnpm test` or `pnpm doctest`; both start the full suite in watch mode. Always pass `--run` and the +specific files covering the change. CI runs the full suite. + +For an ad hoc runnable probe, create `scratchpad/.ts`, run it with plain `node`, and remove it when finished. + +Report any commands that could not be run. + +## Generated Files + +Do not edit generated output directly. + +Some `index.ts` sections marked with `@barrel` are generated. Do not edit those +sections manually; update their source modules and run `pnpm codegen`. +Hand-maintained `index.ts` files and unmarked sections are not covered by this +rule. + +`LLMS.md` is generated from `ai-docs/src`, and `migration/v3-to-v4.md` is +generated from `migration/annotations`. Update checked-in third-party assets +through their generator or documented import procedure. diff --git a/.agents/skills/ai-docs/SKILL.md b/.agents/skills/ai-docs/SKILL.md new file mode 100644 index 000000000..02e675733 --- /dev/null +++ b/.agents/skills/ai-docs/SKILL.md @@ -0,0 +1,16 @@ +--- +name: ai-docs +description: AI documentation. Use when editing ai-docs/src or regenerating LLMS.md. +--- + +Read `ai-docs/README.md`; it owns source structure, style, examples, generation, +and changeset policy. + +1. Inspect neighboring source examples and the relevant current `LLMS.md`. +2. Edit sources under `ai-docs/src`, then run `pnpm ai-docgen`. +3. Review generated output for ordering, missing content, and unrelated changes. +4. Run applicable root validation. + +The task is complete when sources and generated output agree, every generated +difference is explained, TypeScript checks pass when examples or fixtures +change, and applicable checks pass or are reported as not runnable. diff --git a/.agents/skills/bundle-analysis/SKILL.md b/.agents/skills/bundle-analysis/SKILL.md new file mode 100644 index 000000000..28d88976e --- /dev/null +++ b/.agents/skills/bundle-analysis/SKILL.md @@ -0,0 +1,25 @@ +--- +name: bundle-analysis +description: Bundle analysis. Use when measuring current bundle size, comparing stable or selected fixtures, inspecting composition, or cleaning retained comparison state. +--- + +Read `packages/tools/bundle/README.md`, then select one workflow: + +- **Stable comparison:** `pnpm bundle-compare `. Use `HEAD~1` for the + latest commit. Read `tmp/bundle-stats.txt` and report non-zero differences. +- **Selected comparison:** `pnpm bundle-compare-selected --base + scratchpad/.ts`. Use only user-named or investigation-local fixtures; + keep temporary fixtures out of the stable corpus. +- **Composition:** `pnpm bundle-analyze scratchpad/.ts`. Read raw data + first and report the largest modules, dependency groups, and surprising + inclusions. Its readable-name output is not an exact size measurement. +- **Current size:** Build immediately before the direct bundle report so Effect + packages resolve from current `dist` output. +- **Cleanup:** Stable comparisons retain `tmp/bundle-base` for reuse unless + cleanup is requested. Remove selected-comparison state retained with + `--keep-base` after its final use. Verify cleanup without disturbing unrelated + worktrees. + +The task is complete when the selected artifact is inspected, requested size or +composition findings are reported, generated paths are named, and comparison +state follows the selected workflow's retention policy. diff --git a/.agents/skills/changesets/SKILL.md b/.agents/skills/changesets/SKILL.md new file mode 100644 index 000000000..0f4186cfc --- /dev/null +++ b/.agents/skills/changesets/SKILL.md @@ -0,0 +1,58 @@ +--- +name: changesets +description: Changesets. Use after consumer-visible runtime, public API, entrypoint, lifecycle, or wire-format changes, when deciding whether a change is breaking, or when authoring changesets and consumer release notes. +--- + +Record what consumers need to know after implementation and focused validation. + +## Workflow + +1. Inspect the complete diff and identify directly affected published packages. +2. Classify impact across source types, runtime behavior, entrypoints, required + services, lifecycle, and persisted or wire data. +3. Perform the breaking audit below. +4. Record either a reason no changeset is required or one coherent changeset. + When required, read [authoring.md](authoring.md). +5. Validate package names, frontmatter, bump policy, and consumer-facing text. + +Do not include unrelated worktree changes. + +## Requirement + +Create a changeset for observable runtime behavior changes, including bug +fixes; exported value or public type changes; entrypoint or export-map changes; +changes to required services, errors, ownership, defaults, or lifecycle; and +persisted, serialization, protocol, or wire-format changes. + +Tests-only changes, behavior-preserving internal refactors, documentation or +JSDoc maintenance, and unpublished tooling normally do not need one. When +unclear, inspect exports and consumer-visible declarations rather than inferring +from source location. + +## Breaking Audit + +A change is breaking when valid existing consumer code, configuration, or data +must change to keep compiling or behaving according to the previous contract. +Audit every surface: + +- **Names and locations:** exports, entrypoints, compatibility exports, and + module paths. +- **Call compatibility:** parameters, accepted inputs, overload resolution, + generic parameters, and defaults. +- **Result compatibility:** return and error types, output narrowing, members, + inference, and required services. +- **Runtime contracts:** defaults, failures, interruption, concurrency, + ordering, resource lifetime, acquisition, cleanup, and mutation. +- **Data compatibility:** persisted schemas, encodings, database layouts, + protocols, and wire formats. + +Additive exports, optional parameters, and behavior-preserving implementations +are normally non-breaking. A fix restoring the documented contract is normally +non-breaking but still needs a changeset when its operational impact is +consumer-visible. Verify representative existing calls when overload ordering, +structural assignability, or inference makes compatibility uncertain. API diff +output is mechanical evidence, not a semantic-version decision. + +The task is complete when every affected surface and published package is +accounted for and either the no-changeset decision is explicit or one coherent, +valid changeset describes the consumer impact and migration for every break. diff --git a/.agents/skills/changesets/authoring.md b/.agents/skills/changesets/authoring.md new file mode 100644 index 000000000..cb8fcb99c --- /dev/null +++ b/.agents/skills/changesets/authoring.md @@ -0,0 +1,33 @@ +# Authoring Changesets + +Create one `.changeset/.md` per coherent change: + +```md +--- +"effect": patch +"@effect/affected-package": patch +--- + +Describe the consumer-visible change and why it matters. +``` + +List every directly affected published package. Do not list packages merely +because they share the fixed release group in `.changeset/config.json`. + +Choose the bump from current release policy: + +- On a stable line, use `patch` for compatible fixes, `minor` for compatible + additions, and `major` for breaks. +- In `.changeset/pre.json` `rc` mode, follow the current convention of recording + v4 release-candidate changes, including breaking cleanups, as `patch` unless a + maintainer requests another level. +- Ask when release mode or intent is ambiguous. + +Write for consumers. State the changed behavior or API and give concrete +migration guidance for every break. Use a `### Breaking changes` section when +several breaks need separate scanning. Include before/after examples only when +they materially clarify migration. Omit implementation and test details. + +Validate frontmatter against published package names and inspect nearby current +changesets for wording and release convention. Never run `changeset-version` or +`changeset-publish` as contributor validation. diff --git a/.agents/skills/ci-maintenance/SKILL.md b/.agents/skills/ci-maintenance/SKILL.md new file mode 100644 index 000000000..bfabe0b4d --- /dev/null +++ b/.agents/skills/ci-maintenance/SKILL.md @@ -0,0 +1,48 @@ +--- +name: ci-maintenance +description: GitHub Actions maintenance. Use when authoring or reviewing workflows or composite actions, especially event, permission, action-reference, artifact, setup, or concurrency changes. +--- + +Audit the trust boundary before editing. This skill owns Actions event +semantics, workflow security, permissions, action pinning, setup reuse, +untrusted inputs, timeouts, concurrency, and artifact trust boundaries. + +## Workflow + +1. Decide whether this is a review or an implementation. Reviews produce + evidence-backed findings without editing; implementations continue through + the smallest boundary-preserving change. +2. Inspect every affected workflow, composite action, and caller. List all + affected jobs and actions before proceeding. +3. For each job, record the trigger, actor/fork status, workflow revision, + checked-out revision, permissions, credentials, and every external input and + consumer. Include artifacts, caches, outputs, PR fields, refs, dispatch + inputs, generated files, and called actions. +4. Consult current GitHub documentation for every platform-sensitive decision. + Repository examples establish local convention, not platform semantics. +5. Apply every applicable rule: + - Default workflow permissions to empty and grant minimal job permissions. + - Reuse the repository's shared setup action unless the environment must differ. + - Give executable jobs intentional matrices and realistic timeouts. + - Pin external actions and reusable workflows to full commit SHAs with + readable release comments; verify each SHA belongs to that upstream release. + - Analyze event choice, checkout revision, and credential persistence + together. + - Pass untrusted expressions through environment variables; validate and + quote them before use. + - Derive concurrency from interruption safety. +6. For forks, privileged credentials, publication, or cross-run data, read + [privileged-workflows.md](privileged-workflows.md). +7. Run the narrowest syntax and repository checks, inspect the complete diff, + and identify behavior testable only on GitHub-hosted runners. + +For local patterns, inspect the current shared setup action and the nearest +workflow with the same trust boundary. Use ordinary check workflows for routine +precedent and privileged publication workflows only for equivalent trust +boundaries. + +The task is complete when every affected job and caller has a recorded trust +boundary, every applicable rule has evidence or a compensating control, local +checks pass, and GitHub-only verification is identified. Reviews report every +failure with evidence; implementations resolve every failure and contain only +intended behavior. diff --git a/.agents/skills/ci-maintenance/privileged-workflows.md b/.agents/skills/ci-maintenance/privileged-workflows.md new file mode 100644 index 000000000..3cc61cd8f --- /dev/null +++ b/.agents/skills/ci-maintenance/privileged-workflows.md @@ -0,0 +1,20 @@ +# Privileged Workflows + +- Privileged jobs must not run untrusted repository code with secrets or write + credentials. +- Validate cross-run data for expected source, identity, type, shape, and + bounded size before using it in commands, outputs, APIs, or comments. +- Require the intended approval boundary before fork-originated work reaches + privileged execution. +- Preserve publication and other irreversible operations until completion; + cancel only replaceable checks and previews. +- Make checkout revision and credential persistence explicit. Account for + every write permission and credential. + +Search current workflows by event, job purpose, action, and permission to find +the nearest precedent for fork approval gates, publication, non-cancelable +concurrency, and validation of cross-run artifacts before privileged use. + +Consult current GitHub documentation for event, token, secret, permission, +checkout, artifact, reusable-workflow, and environment semantics. Start with +the workflow events, workflow syntax, and secure-use references. diff --git a/.agents/skills/dependency-maintenance/SKILL.md b/.agents/skills/dependency-maintenance/SKILL.md new file mode 100644 index 000000000..5a409b3ad --- /dev/null +++ b/.agents/skills/dependency-maintenance/SKILL.md @@ -0,0 +1,58 @@ +--- +name: dependency-maintenance +description: Dependency maintenance. Use when adding, moving, or upgrading dependencies, changing pnpm or JavaScript runtimes, updating TypeScript or build/test tools, or changing native-build policy, patches, or test images. +--- + +Treat an upgrade as a synchronization task, not a lockfile refresh. Derive +versions, commands, and compatibility points from the current repository. + +## Discover + +1. Read affected manifests and scripts, `pnpm-workspace.yaml`, setup actions, + workflows, test configuration, and compatibility documentation. +2. Search for the dependency and every current version, range, runtime input, + image, engine constraint, patch, and compatibility claim. +3. Record each match as a development version, tested version, peer range, + engine minimum, advertised minimum, or intentionally different constraint. +4. When adding or moving a manifest entry, read + [manifest-roles.md](manifest-roles.md) before selecting its role. +5. Select the package-local or coordinated branch and its validation matrix. + +Discovery is complete when every match and affected validation surface is +accounted for. + +## Package-local branch + +Use this branch only for one dependency in one workspace package when runtime, +compiler, package-manager, image, patch, native-build, and shared-tooling policy +are unchanged. + +Update only the owning manifest and keep peer compatibility independent from +the development version tested here. If any coordinated surface appears, +switch branches. + +This branch is complete when the manifest and lockfile agree, focused checks +pass, and every search result is intentionally unchanged or package-local. + +## Coordinated branch + +Read [coordinated-upgrades.md](coordinated-upgrades.md), select every applicable +row, and update all listed synchronization points before installing. Apply root +workflow and generated-file requirements before editing those surfaces, then +return here to finish matrix and lockfile review. + +## Install and finish + +Run root `pnpm install` after all selected edits. Inspect warnings and the +semantic lockfile diff for specifiers, resolutions, duplicate transitives, peer +changes, integrity, patch hashes, lifecycle scripts, and `allowBuilds` effects. +A successful install alone does not complete this review. + +Run the narrowest correctness and performance checks that cover every selected +matrix row. +Apply the root changeset routing after implementation and focused validation. + +The task is complete when repeated repository searches find no unclassified +synchronization point, manifest roles and compatibility ranges are intentional, +the lockfile has no unexplained churn, and every selected check passes or is +reported as not runnable. diff --git a/.agents/skills/dependency-maintenance/coordinated-upgrades.md b/.agents/skills/dependency-maintenance/coordinated-upgrades.md new file mode 100644 index 000000000..8bb028021 --- /dev/null +++ b/.agents/skills/dependency-maintenance/coordinated-upgrades.md @@ -0,0 +1,21 @@ +# Coordinated Upgrades + +Select every applicable row and derive commands from the cited current source. + +| Change | Synchronize | Validation source | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- | +| Shared dependency or test/build tool | Owning manifests, peer/development pairs, configs, and generated artifacts owned by that tool | Root/package scripts and affected Vitest projects | +| pnpm | Root `packageManager`, workspace settings, setup/cache assumptions, workflows, and lockfile format | Current install, lint, check, build, and focused test scripts | +| Node, Deno, or Bun | Setup inputs, workflow jobs, engines, runtime metadata, compatibility docs, and runtime config | Current runtime workflow jobs and test configuration | +| TypeScript support or compiler | Compiler/tooling dependencies, `test-types` target, CI target, tool peer ranges, compatibility docs, and typetests | Targeted typetests; typeperf protocol for measured paths | +| Native package or install policy | Owning manifests and `pnpm-workspace.yaml#allowBuilds` | Fresh install plus focused package build and tests | +| Patched dependency | Manifest/range, `patchedDependencies`, patch file, and lockfile patch hash | Fresh install and the behavior that required the patch | +| Container image or Testcontainers package | Workflow pre-pulls, image call sites, manifests, and integration project names | Current integration workflow and Vitest configuration | + +For a patched dependency, test the new release without the patch when feasible. +Remove obsolete registration and patch files. Otherwise refresh the patch using +the current pnpm workflow and verify both that it applies and that the original +patched behavior still requires it. + +When an upgraded component lies on a measured path, use the repository's +runtimeperf or typeperf comparison protocol rather than inventing a benchmark. diff --git a/.agents/skills/dependency-maintenance/manifest-roles.md b/.agents/skills/dependency-maintenance/manifest-roles.md new file mode 100644 index 000000000..0023a32f9 --- /dev/null +++ b/.agents/skills/dependency-maintenance/manifest-roles.md @@ -0,0 +1,15 @@ +# Manifest Roles + +Inspect the nearest package with the same integration shape, then justify each +added or moved entry: + +| Role | Use when | Completion check | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| `dependencies` | Published runtime code requires its own installed copy. | A packed consumer receives it without adding an undeclared package. | +| `peerDependencies` | Consumers provide a compatible shared package or the public contract integrates with their copy. | The range states supported consumer versions; add a development entry when local build or tests need an installed copy. | +| `devDependencies` | Only repository build, test, type, benchmark, or code-generation work needs it. | Published runtime code and declarations do not require consumers to install it. | +| `optionalDependencies` | A runtime feature handles absence and installation failure must not block the base package. | Focused tests cover present and absent behavior. | +| Optional peer via `peerDependenciesMeta` | A consumer-provided integration is genuinely optional. | The peer remains in `peerDependencies`, and code requires it only when selected. | + +Role selection is complete when every changed entry has one justified role and +peer compatibility remains independent from repository validation versions. diff --git a/.agents/skills/effect-development/SKILL.md b/.agents/skills/effect-development/SKILL.md new file mode 100644 index 000000000..079f8e08d --- /dev/null +++ b/.agents/skills/effect-development/SKILL.md @@ -0,0 +1,16 @@ +--- +name: effect-development +description: Effect development guidance. Use when implementing or reviewing code that composes Effect APIs or patterns. +--- + +Use the repository's AI documentation as targeted reference, not ambient +context: + +1. Identify the Effect APIs and concepts involved in the change. +2. Search `LLMS.md` and `ai-docs/src` for those names and concepts, then read + the matching sections and examples. +3. Apply the relevant guidance and inspect nearby source and tests for any + repository-internal conventions not covered by the documentation. + +The task is ready for implementation when every material Effect API or pattern +has matching local guidance, or the search has established that none exists. diff --git a/.agents/skills/grill-me/SKILL.md b/.agents/skills/grill-me/SKILL.md deleted file mode 100644 index d65f67b93..000000000 --- a/.agents/skills/grill-me/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: grill-me -description: Interview the user about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me". ---- - -Interview me about every aspect of this plan until we reach a shared understanding and a defensible design. - -Ask exactly one question at a time, then wait for my answer before asking the next question. - -Use each answer to choose the next highest-leverage unresolved question. Maintain an implicit decision tree of resolved decisions, open questions, assumptions, dependencies, risks, and rejected alternatives. - -For each question, include: - -- clear answer options when appropriate -- your recommended answer, marked as recommended -- a brief reason for the recommendation - -Use open-ended questions when fixed options would prematurely constrain the design space. - -Challenge vague, inconsistent, risky, or unsupported assumptions. If an answer creates a contradiction or unresolved dependency, ask a follow-up before moving on. - -Cover, as relevant: - -- goals and non-goals -- users and stakeholders -- constraints -- alternatives -- APIs and interfaces -- data model -- error handling -- security -- observability -- testing -- migration and rollout -- failure modes -- operational ownership -- success criteria - -If repository facts are needed, inspect the codebase instead of asking the user. Do not ask me to provide information that can be determined locally. - -When an available user-input tool such as `request_user_input` fits the question, use it to ask one short question with a small set of mutually exclusive options. Otherwise, ask in plain text and present clear possible answers as a numbered list when that helps me answer quickly. Include your recommended option and mark it as recommended. - -Stop when the major branches of the design tree have been resolved. Then summarize the agreed design, remaining risks, assumptions, rejected alternatives, and next steps. diff --git a/.agents/skills/jsdocs/SKILL.md b/.agents/skills/jsdocs/SKILL.md index 10b368bfe..dac9327ee 100644 --- a/.agents/skills/jsdocs/SKILL.md +++ b/.agents/skills/jsdocs/SKILL.md @@ -1,203 +1,33 @@ --- name: jsdocs -description: Write, insert, or update Effect public API JSDoc so it satisfies the jsdocs oxlint rule. Use when adding or fixing JSDoc comments, resolving jsdocs diagnostics, preparing docs for JSON extraction, or reviewing public API documentation. +description: Public API JSDoc. Use when authoring or reviewing Effect API documentation, refining a module's documentation, or fixing documentation related diagnostics. --- -Use this skill to write well-formed JSDoc for Effect public APIs. - ## Workflow -When updating public API JSDoc: - -1. Inspect the declaration, implementation, nearby tests, and nearby JSDoc before editing. -2. Decide whether the task is a single API fix or a module refinement pass. -3. Rewrite comments into the required documentation shape while preserving correct facts and examples. -4. For module refinements, complex APIs, or APIs with related alternatives, run the `@see` and `**Gotchas**` audits. -5. Run the narrowest relevant validation. - -## Required documentation shape - -Use a normal multiline JSDoc comment in TypeScript source: - -```ts -/** - * Short description as one paragraph. - * - * **When to use** - * - * Optional practical usage guidance. - * - * **Details** - * - * Optional details for complex APIs, options, overloads, or behavior. - * - * **Gotchas** - * - * Optional edge cases, footguns, or surprising behavior. - * - * **Example** (Short title) - * - * Optional prose explaining the example. - * - * ```ts - * const result = example() - * ``` - * - * @category constructors - * @since 1.0.0 - */ -``` - -## Prose Rules - -- Use sober, practical prose. -- Write all public JSDoc prose in English. -- Do not use jargon when a plain word works. -- Do not be clever. -- Do not add filler sections. -- The short description is required and must be exactly one paragraph. -- Make the short description stand on its own. Do not rely on `**When to use**` - to make the API understandable. -- For functions and methods, prefer present-tense, action-first prose such as - `Creates`, `Returns`, `Checks`, `Provides`, `Represents`, `Converts`, - `Decodes`, or `Formats`. -- For technical value exports, use consistent noun forms such as `Schema for`, - `Layer that`, `Service that`, `Context reference that`, or - `Constructors and matchers for`. -- Avoid leading `A` or `An` for canonical technical nouns when the surrounding - module uses a standard noun family, for example prefer `Schema for ...` over - `A schema for ...`. -- Do not describe implementation mechanics when a public concept is clearer. - For example, prefer `Constructors and matchers for ...` over wording that - only says an API uses `Data.taggedEnum`. -- Avoid generic purity or non-mutation remarks unless they document a real - surprise, caveat, or meaningful contrast with a mutating-looking API. -- Optional sections must appear in this order: - 1. `**When to use**` - 2. `**Details**` - 3. `**Gotchas**` -- Include an optional section only when it has useful, non-empty content. -- Prefer prose over bullet lists for single-item `**Details**`, `**When to use**`, or `**Gotchas**` sections. Use bullets only when there are two or more parallel facts, options, cases, or caveats. -- `**When to use**` describes the positive use case for the documented API. Do not use it as a routing section for sibling APIs. If neighboring APIs need to be mentioned, put that boundary in `@see` tag text instead. -- `**When to use**` is important when the API has close alternatives, trade-offs, or `@see` tags. If `@see` tags are present, inspect the referenced APIs and add `**When to use**` when it clarifies the documented API's own use case. -- `**When to use**` must start with one of these practical guidance forms: `Use to`, `Use when`, `Use as`, or `Use with`. Avoid bullet lists and vague openers such as `Use this...` or `Useful for...`. -- Prefer reader-centered `**When to use**` wording, especially `Use when you ...`, - when the sentence describes a user's goal. Avoid third-person noun-phrase - subjects such as `the input is ...`, `a service needs ...`, or - `values should ...` when they would become awkward in generated prompts. -- A good `**When to use**` sentence should still read naturally if reused as - a user intent prompt, for example after `I need ...` or `I have ...`. -- Keep `short` and `**When to use**` distinct: the short description says what - the API is or does; `**When to use**` says when to choose it. -- Add internal `@see` tags only for semantically useful related public APIs. -- Write `@see` tag text as normal prose after the link; no special separator is required. Prefer forms like `@see {@link otherApi} for ...` when a short explanation helps. -- Use exactly one blank line between the short description, sections, examples, and tags. -- Do not use Markdown headings such as `# Heading` or ad hoc bold headings such as `**Notes**`; only the standard headings are allowed. -- Examples must use `**Example** (Title)`, optional prose, and exactly one non-empty `ts` code fence. -- Example titles must be unique after trimming and lowercasing. -- Example titles should be short use-case phrases, not generic labels. -- Prefer gerund or action-noun titles that read naturally after `for`, for - example `Parsing JSON`, `Creating a scoped runtime`, or `Comparing structs`. -- Avoid imperative titles such as `Parse JSON`, vague labels such as `Syntax` - or `Basic usage`, and title-cased fragments such as `String Ordering`. -- Preserve canonical technical capitalization inside the phrase, such as - `Option`, `Effect`, `Schema`, `DateTime`, `HashMap`, `Base64`, and `JSON`. -- For multiple examples on the same API, make each title describe the distinct - use case shown by that example. -- Prefer examples with stable, deterministic output. Avoid assertions or - `console.log` comments that depend on stack traces, object inspection, - `Error` formatting, concurrency order, timing, randomness, or - environment-specific formatting. Examples may assume Node.js console - formatting. Direct `Set` / `Map` output is acceptable when insertion order is - deterministic and the expected output uses Node's format; otherwise - demonstrate a stable property instead. -- Do not use `@example`. -- Do not put TypeScript code fences outside `**Example** (Title)` sections. -- Inline `{@link Symbol}` targets must resolve to TypeScript symbols; do not link to URLs with `{@link}`. -- Avoid overlinking in prose. Use `{@link Symbol}` only when navigation to - that symbol helps the reader choose or understand the API. For the API being - documented, the module's central type, nearby obvious names, or repeated - mentions, prefer plain code formatting such as `Cause`, `Effect`, or - `Context`. -- Do not document module-level comments; module JSDoc is ignored by this rule. -- `@internal` means the item is ignored; do not rewrite it as public docs. -- Default exports are ignored by this rule and do not need JSDoc. -- Do not add unsupported constructs such as enums or empty exports in checked files. -- For low-level public values, prefer accurate categories such as `symbols`, - `type IDs`, or `prototypes` over compensating with verbose descriptions. - -## Tag rules - -When multiple tags are present, keep them in this order: - -1. `@deprecated` -2. `@default` -3. `@see` -4. `@category` -5. `@since` - -Tag requirements by declaration kind: - -- Root declarations require `@category` and stable-semver `@since`, and must - not use `@default`. -- Namespaces and declarations inside namespaces require stable-semver `@since`, - may use `@category`, and must not use `@default`. -- Member JSDoc is optional. When present, it follows the same prose and layout - rules, may use optional stable-semver `@since`, may use non-empty `@default`, - and must not use `@category`. -- Any declaration may use `@deprecated` with a non-empty message and repeated - non-empty `@see` tags for semantically useful related public APIs. - -## Updating existing JSDoc - -When fixing or updating existing docs: - -1. Preserve correct facts and examples. -2. Rewrite the layout into the standard template. -3. Move usage guidance into `**When to use**`, behavior details into `**Details**`, and real caveats into `**Gotchas**`. -4. Convert `@example` tags and loose `ts` fences into `**Example** (Title)` sections. -5. Preserve valid `@see`, `@deprecated`, `@default`, `@category`, and `@since` tags. -6. Remove `@see` tags that do not point to semantically useful related public APIs. -7. Replace redundant inline `{@link ...}` tags with plain code formatting when - the link target is already obvious from the current declaration or module. -8. Remove sections that would be empty. - -## Module refinement - -When asked to refine an existing module: - -1. First scan the module for local documentation patterns, repeated API families, and category conventions. -2. Keep the change focused on documentation quality unless the user also asked for rule or source changes. -3. Prefer improving existing comments over rewriting every comment into a new voice. -4. Preserve examples unless they are wrong, stale, nondeterministic, or fail - the required documentation shape. -5. Apply the `@see` and `**Gotchas**` audits across the module before finishing. - -## See audit - -When refining an existing public API module, always do a dedicated `@see` pass: - -1. Inspect existing `@see` tags and referenced APIs before keeping, changing, or removing them. -2. Look for close alternatives in the same module or API family when the documented API is one of several ways to do similar work. -3. Keep or add `@see` only when the linked API is semantically useful to understand the documented API. -4. Good `@see` targets include sibling APIs, alternatives, inverse operations, lower-level or higher-level variants, complementary operations, and closely returned, consumed, or configured types/values. -5. Do not use `@see` for implementation dependencies, broad concepts, external background links, APIs that merely share a word or name, helper APIs used only inside examples, undocumented/private members, or APIs that are only generally compatible. -6. When `@see` tags are kept or added, include `**When to use**` guidance if the documented API's own use case is not obvious from the short description. Keep comparisons with sibling APIs in the `@see` tag text. - -## Gotchas audit - -When refining an existing public API module, always do a dedicated `**Gotchas**` pass: - -1. Scan existing prose for caveat language: warnings, exceptions, limitations, preconditions, special cases, or behavior that is easy to misuse. -2. Inspect the implementation and nearby tests for behavior that is not obvious from the type signature or short description. -3. Move real caveats from `**Details**` into `**Gotchas**` when they describe edge cases, footguns, preconditions, surprising behavior, or important failure modes. -4. Add `**Gotchas**` only when the caveat is concrete and useful to a reader choosing or using the API. -5. If no gotchas are added during a refinement pass, state that a gotchas audit was performed and why no caveats were worth documenting. - -## Validation - -Run the narrowest validation that matches the change: - -- For JSDoc or example changes in a package with generated docs, run `pnpm docgen` from that package directory. -- Run `pnpm lint` because the linter includes the custom rule that checks public API JSDoc. -- Do not run broad validation for prose-only skill edits. +1. Inspect the declaration, implementation, nearby JSDoc, tests, and call sites. +2. Select every applicable branch and load its reference before editing: + - tags, modules, or links: [declarations.md](declarations.md); + - categories: [categories.md](categories.md); + - examples: [examples.md](examples.md). +3. Make a focused API fix or module refinement. Preserve verified facts and + valuable examples rather than rewriting mechanically. +4. For module refinement or APIs with close alternatives, audit `@see` links + and inspect implementation and tests for concrete `**Gotchas**`. +5. Run `pnpm jsdocs --check` and every applicable root validation command. + +`@internal` declarations and default exports are outside public JSDoc +authoring. Checked files do not support exported enums or empty export +declarations. + +Public declarations use a multiline block with one self-contained practical +description paragraph. Start functions and methods with a present-tense action; +match nearby noun-family phrasing for values. Optional non-empty sections appear +once in this order: `**When to use**`, `**Details**`, `**Gotchas**`. Separate +descriptions, sections, examples, and tags with one blank line. `**When to +use**` states a positive use case distinct from the description; reserve +`**Gotchas**` for concrete caveats and failure modes. + +The task is complete when every changed public declaration satisfies each +applicable reference, the checker passes, runnable examples pass their targeted +doctest, and all other applicable root checks pass or are reported as not run. diff --git a/.agents/skills/jsdocs/categories.md b/.agents/skills/jsdocs/categories.md new file mode 100644 index 000000000..1b0817e57 --- /dev/null +++ b/.agents/skills/jsdocs/categories.md @@ -0,0 +1,24 @@ +# Categories + +Root declarations require one non-empty `@category`. Reuse nearby categories; +prefer lowercase plurals and gerunds while preserving canonical domain casing. + +Common categories include: + +- Shapes: `constructors`, `destructors`, `models`, `schemas`, `guards`, + `predicates`, `getters`, `accessors`, `instances`, `constants`, `protocols`, + `prototypes`, `re-exports`, `unsafe`, `testing`. +- Effect: `services`, `tags`, `layers`, `context`, `resource management`, + `running`, `errors`, `error handling`. +- Operations: `combinators`, `filtering`, `mapping`, `sequencing`, `zipping`, + `combining`, `merging`, `converting`, `transforming`, `folding`, `splitting`, + `repetition`. +- Shared: `utility types`, `encoding`, `decoding`, `serialization`, `tracing`, + `metrics`, `logging`, `annotations`, `references`, `symbols`, `type IDs`, + `configuration`, `math`, `comparisons`, `ordering`. + +Keep these boundaries: services are contracts, tags identify services, and +layers provide them; getters retrieve values while accessors read context; +errors model failures while error handling recovers or maps them; models are +domain data while utility types are type-level contracts; guards narrow while +predicates return booleans. diff --git a/.agents/skills/jsdocs/declarations.md b/.agents/skills/jsdocs/declarations.md new file mode 100644 index 000000000..62937bd36 --- /dev/null +++ b/.agents/skills/jsdocs/declarations.md @@ -0,0 +1,87 @@ +# Tags, Modules, And Links + +## Declaration Shape + +Use a multiline JSDoc block for a public declaration: + +````ts +/** + * Short description as one paragraph. + * + * **When to use** + * + * Optional practical usage guidance. + * + * **Details** + * + * Optional details for complex behavior. + * + * **Gotchas** + * + * Optional concrete caveats. + * + * **Example** (Parsing JSON) + * + * ```ts import.meta.vitest + * operation() // => expected + * ``` + * + * @category constructors + * @since 1.0.0 + */ +```` + +Write practical English about the public concept, not its implementation. Start +functions and methods with a present-tense action such as `Creates`, `Returns`, +or `Converts`; match nearby noun families for values, such as `Schema for`, +`Layer that`, or `Service that`. + +Optional non-empty sections appear once in this order: `**When to use**`, +`**Details**`, `**Gotchas**`. Separate descriptions, sections, examples, and +tags with exactly one blank line. Use prose for one fact and bullets for +parallel facts. Do not add other headings. + +`**When to use**` states a positive use case distinct from the description and +begins with `Use to`, `Use when`, `Use as`, or `Use with`. Put sibling +comparisons in `@see`. Reserve `**Gotchas**` for concrete preconditions, edge +cases, surprising behavior, and important failure modes. + +## Tags + +Declaration tags appear in this order: + +1. `@deprecated` +2. `@default` +3. `@see` +4. `@category` +5. `@since` + +- Roots require stable-semver `@since` and no `@default`; category requirements + live in [categories.md](categories.md). +- Namespaces and their declarations require stable-semver `@since`, permit + `@category`, and reject `@default`. +- Member JSDoc is optional; when present it permits stable-semver `@since` and + non-empty `@default`, rejects `@category`, and follows the prose contract. +- Any declaration permits one non-empty `@deprecated` and repeated non-empty + `@see` tags. + +Use canonical `**Example**` sections rather than `@example` tags or loose code +fences. + +## Modules And Links + +When present, the first top-level JSDoc is the module block unless TypeScript +attaches it to a non-import first declaration. An `@internal` module is omitted. +Module prose does not use the declaration template. Its tags are optional +non-empty `@deprecated`, repeated non-empty `@see`, then required stable-semver +`@since`. Its examples and links follow the declaration contracts. + +Inline `{@link Symbol}` targets must resolve to TypeScript symbols; use normal +Markdown links for URLs. Prefer code formatting when navigation does not help a +reader understand or choose the API. + +Use `@see` only for a verified related public API: a close alternative, +inverse, complement, level variant, or closely returned, consumed, or +configured type. Explain non-obvious relationships. Exclude implementation +dependencies, broad concepts, example-only helpers, private APIs, and merely +lexical matches. diff --git a/.agents/skills/jsdocs/examples.md b/.agents/skills/jsdocs/examples.md new file mode 100644 index 000000000..2b78e6daf --- /dev/null +++ b/.agents/skills/jsdocs/examples.md @@ -0,0 +1,26 @@ +# Examples + +Examples are optional. Keep or add one only for behavior not evident from the +signature, meaningful composition, or useful inference or narrowing. Replace +or remove examples that are trivial, misleading, contrived, or +scaffolding-heavy. + +Use `**Example** (Unique use-case title)`, optional prose, and exactly one +non-empty `ts` fence. Titles must remain unique after trimming and lowercasing. + +Read `packages/tools/doctest/README.md` for runnable-fence and inline-assertion +syntax. Additionally: + +- Use public imports and arrange nontrivial examples as setup, operation, then + semantic observation. +- The transform does not run Effects or await promises automatically. Prefer + awaited `Effect.runPromise`; use `Effect.runSync` only when synchronous + execution is the documented contract. +- Keep type-level examples marked without tautological runtime assertions. +- Leave examples that register tests and intentionally non-executable examples + as plain `ts` fences. +- Use `Ref`, `Deferred`, or `Queue` rather than mutable probes for concurrency, + interruption, or races. + +If example research suggests an implementation or type bug, report it instead +of changing runtime code during a documentation-only pass. diff --git a/.agents/skills/migration-guidance/SKILL.md b/.agents/skills/migration-guidance/SKILL.md new file mode 100644 index 000000000..752cba35e --- /dev/null +++ b/.agents/skills/migration-guidance/SKILL.md @@ -0,0 +1,21 @@ +--- +name: migration-guidance +description: Use when changing how a v3 public API maps to v4, editing migration annotations, or regenerating migration/v3-to-v4.md. +--- + +Update guidance when a v3 public module or API is renamed, moved, removed, +replaced, or gains a materially different v4 contract. A v4-only API without a +v3 counterpart does not automatically need an annotation. + +1. Account for every affected v3 symbol. +2. Read [annotations.md](annotations.md) before adding or changing annotation + YAML. Verify every suggested replacement against implementation and tests. +3. When checking or regenerating the reference, or when the API change exists in + a committed ref, read [generation.md](generation.md). Otherwise report that + generation is deferred. +4. When output was generated, inspect it for unrelated movement and stale refs. + +The task is complete when every affected v3 symbol is accounted for, annotation +replacements are verified, checks and generation succeed when the change is in +a committed ref, and every generated difference is explained. Report deferred +checks explicitly for uncommitted API changes. diff --git a/.agents/skills/migration-guidance/annotations.md b/.agents/skills/migration-guidance/annotations.md new file mode 100644 index 000000000..ac5a1ca88 --- /dev/null +++ b/.agents/skills/migration-guidance/annotations.md @@ -0,0 +1,15 @@ +# Migration Annotations + +Add or update one YAML file per v3 module under `migration/annotations/`. Use +stable API IDs without trailing `#type` or `#value` facets: + +```yaml +effect/Effect#async: + replacement: Effect.callback + note: Use the callback constructor. + example: Effect.callback((resume) => resume(Effect.void)) +``` + +Every annotation requires `replacement` and `note`; `example` is optional. Use +`replacement: none` when there is no direct replacement and explain the +supported migration strategy instead of inventing an equivalent API. diff --git a/.agents/skills/migration-guidance/generation.md b/.agents/skills/migration-guidance/generation.md new file mode 100644 index 000000000..2eeb6155c --- /dev/null +++ b/.agents/skills/migration-guidance/generation.md @@ -0,0 +1,14 @@ +# Generated Migration Reference + +`migration/v3-to-v4.md` is generated. Check annotations and regenerate using +explicit committed refs containing the change: + +```sh +pnpm api-diff --base-ref origin/v3 --head-ref HEAD --check +pnpm api-diff --base-ref origin/v3 --head-ref HEAD --write-doc migration/v3-to-v4.md +``` + +The API diff reads refs through detached worktrees. If the API change is +uncommitted, `HEAD` does not contain it. Update known annotation IDs, defer the +check and regeneration, and report the limitation. Do not create a temporary +commit solely to run the tool. diff --git a/.agents/skills/package-development/SKILL.md b/.agents/skills/package-development/SKILL.md new file mode 100644 index 000000000..5b419895e --- /dev/null +++ b/.agents/skills/package-development/SKILL.md @@ -0,0 +1,35 @@ +--- +name: package-development +description: Package registration. Use when adding a workspace package, making a private package publishable, renaming a package, or moving its workspace path. +--- + +Treat package work as a registration change, not a directory copy. + +## Workflow + +1. Inspect the closest package in the same family. Classify publication status, + runtime, test environment, platform support, barrels, and dependency shape. + Justify each proposed file by precedent or an explicit repository need. + Continue when every proposed file is justified. +2. Create or move only required source, test, TypeScript, and manifest files. + When manifest dependencies change, read [dependencies.md](dependencies.md) + and classify every entry by role. + Continue when pnpm discovers the intended name and manifests reference no + absent file. +3. Read [registration.md](registration.md). Classify every registration surface + as applicable or not applicable and update every applicable surface. + Continue when every surface is classified and old references are explained. +4. For a published package, read [publishing.md](publishing.md) and verify its + development and packed surfaces. +5. Run codegen when the package owns generated barrels, then inspect generated + sections rather than editing them manually. + Continue when generated sections match source modules without manual edits. +6. Run focused package tests and scripts plus applicable root checks. Include + public API type tests when applicable. For published packages, build and dry + pack the package as described in [publishing.md](publishing.md). + Continue when focused and root checks pass and the packed surface is intended. +7. Apply root changeset, generated-file, documentation, and workflow routing. + +The task is complete when package discovery, registration, generated output, +validation, packed output, and changeset routing are all verified or reported +as not applicable. diff --git a/.agents/skills/package-development/dependencies.md b/.agents/skills/package-development/dependencies.md new file mode 100644 index 000000000..45dd603f3 --- /dev/null +++ b/.agents/skills/package-development/dependencies.md @@ -0,0 +1,30 @@ +# Package Dependencies + +Inspect the closest package with the same publication and integration shape. +Classify every manifest entry by the role it serves: + +| Role | Use when | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `dependencies` | Published runtime code requires the package to receive its own installed copy. | +| `peerDependencies` | Consumers provide a compatible shared package or the public contract integrates with their copy. | +| `devDependencies` | Only repository build, test, type test, benchmark, or code generation needs the package. | +| `optionalDependencies` | A runtime feature handles absence and installation failure must not block the base package. | +| Optional peer via `peerDependenciesMeta` | A consumer-provided integration is genuinely optional. Keep its version in `peerDependencies`. | + +Use `workspace:^` for workspace packages unless neighboring packages establish a +different policy. When local build or tests need an installed peer, add the peer +to `devDependencies` at the repository's tested version while keeping the peer +range based on supported consumer versions. + +Derive external versions from current consumers with the same integration +shape. Add only direct requirements; do not copy a neighboring manifest entry +that the new package's runtime, declarations, build, or tests do not use. + +After editing manifests, run root `pnpm install`. Inspect warnings and the +package's `pnpm-lock.yaml` importer for intended specifiers and resolutions. If +a dependency has an install or native build, classify it under +`pnpm-workspace.yaml#allowBuilds` using current repository policy. + +This branch is complete when every entry has one justified role, workspace and +peer ranges follow repository policy, the lockfile importer agrees with the +manifest, and install/build policy is explicit. diff --git a/.agents/skills/package-development/publishing.md b/.agents/skills/package-development/publishing.md new file mode 100644 index 000000000..0058c874f --- /dev/null +++ b/.agents/skills/package-development/publishing.md @@ -0,0 +1,19 @@ +# Publishing A Package + +Compare the package manifest with a neighboring published package in the same +family. + +- Keep development `exports` and `publishConfig.exports` aligned by key. + Source targets become their intended built targets; blocked internal and + legacy paths remain blocked. +- Check current AI documentation copy tooling and the package `files` list for + every file required in the published payload. +- Ensure each public source entrypoint has exactly one published counterpart + and no internal entrypoint becomes exposed. + +Run the current root build so publication payload generation executes. Create a +tarball in a temporary destination without publishing, inspect its file list, +and inspect the packed `package.json` whenever manifest transformation matters. + +This branch is complete when package and root checks pass and the packed +surface contains exactly the intended files, exports, and metadata. diff --git a/.agents/skills/package-development/registration.md b/.agents/skills/package-development/registration.md new file mode 100644 index 000000000..5fdf21a32 --- /dev/null +++ b/.agents/skills/package-development/registration.md @@ -0,0 +1,41 @@ +# Package Registration + +Use current configuration and the closest package in the same family as the +sources of truth. A package-local build does not prove repository registration. +Classify every row as applicable or not applicable. + +## Workspace And TypeScript + +| Surface | Check | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pnpm-workspace.yaml` | Its globs cover the package path and exact-name pnpm selection discovers it. | +| `pnpm-lock.yaml` | The current importer, name, and dependencies exist; renamed or moved importers are gone. | +| `tsconfig.packages.json` | Packages intended to participate in the root TypeScript project-reference graph are referenced. Follow neighboring package policy; buildable private tools are not automatically included. | +| `tsconfig.tests.json` | Broad test globs cover the path. Add source aliases only when tests require workspace source routing or would otherwise create a dependency cycle. Preserve intentional platform exclusions. | + +## Tests And Documentation + +| Surface | Check | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `vitest.config.ts` | A package with runtime tests has the intended project, environment, setup, and inclusion rules. | +| `tstyche.json` | Type-test discovery covers the package depth; current globs cover one- and two-level package layouts. | +| `vitest.docs.ts` | Doctest source discovery covers packages with runnable JSDoc examples; current globs cover one- and two-level package layouts. | +| `jsdocs.config.json` | Public source is included in JSDoc checks and exclusions by package family remain intentional. | +| `deno.json` | Deno checks cover the package family, or its exclusion is intentional. | + +## Release And Discovery + +| Surface | Check | +| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `.changeset/config.json` | Published fixed-group membership uses the current name; private tooling is not added solely because it builds. | +| `README.md` | Public catalog entries have the current name, path, description, and documentation link. | +| AI documentation copy tooling and package `files` | Published payloads include required generated documentation. Continue with [publishing.md](publishing.md) for the full packed-surface audit. | +| Snapshot publishing workflows | Snapshot publishing selects the package path. Existing globs may already cover it; apply root workflow requirements before editing. | +| Runtime-specific CI workflows | Runtime-specific packages participate in applicable runtime jobs; apply root workflow requirements before editing. | + +## Paths And Consumers + +Audit explicit workspace package names in root tooling manifests. Search scripts +for the package family, old name, and old path to find path-sensitive clean, +codemod, circularity, copy, and related behavior. Explain every remaining old +reference after a rename or move. diff --git a/.agents/skills/performance-analysis/SKILL.md b/.agents/skills/performance-analysis/SKILL.md new file mode 100644 index 000000000..d21e7f4b3 --- /dev/null +++ b/.agents/skills/performance-analysis/SKILL.md @@ -0,0 +1,17 @@ +--- +name: performance-analysis +description: Performance analysis. Use for runtime throughput or latency benchmarks, TypeScript compiler cost, performance regressions, comparison harnesses, or thresholds. +--- + +Validate correctness with focused tests before measuring. Compare the same +focused operation and environment across revisions; a single unpaired run is +not evidence of improvement. + +Select every applicable branch: + +- **Runtime performance:** Read [runtime.md](runtime.md). +- **Type performance:** Read [types.md](types.md). + +The task is complete when every selected branch meets its measurement and +reporting criteria and applicable root checks pass or are reported as not +runnable. diff --git a/.agents/skills/performance-analysis/runtime.md b/.agents/skills/performance-analysis/runtime.md new file mode 100644 index 000000000..501b5c900 --- /dev/null +++ b/.agents/skills/performance-analysis/runtime.md @@ -0,0 +1,19 @@ +# Runtime Performance + +For authoritative base/head comparisons or fixture work, read +`packages/effect/runtimeperf/README.md`. Use `pnpm runtimeperf-compare +` for local worktree comparisons and explicit `--base`/`--head` +only for committed refs. Report the resolved comparison head and measurement +configuration. + +For exploratory scripts under `benchmark/`, match nearby Tinybench scripts, +keep setup outside the measured callback unless setup is the operation, warm +reusable state, close resources, and print the table. Label results exploratory +unless the task defines a repeatable comparison protocol. + +Focused runtimeperf work is complete when fixture validation and the selected +run succeed. Comparison work additionally requires every configured round to +succeed, the paired statistical classification to support the conclusion, and +resolved refs and settings to be reported. Harness changes require its focused +checks. Tinybench work is complete when resources close, the table is reported, +and claims remain exploratory unless a repeatable protocol was run. diff --git a/.agents/skills/performance-analysis/types.md b/.agents/skills/performance-analysis/types.md new file mode 100644 index 000000000..2028c2d6b --- /dev/null +++ b/.agents/skills/performance-analysis/types.md @@ -0,0 +1,17 @@ +# Type Performance + +Read `packages/effect/typeperf/README.md` before adding fixtures, changing the +harness, updating thresholds, or running cross-ref comparisons. Isolate one +realistic public type path and compare the same fixture and compiler environment +across revisions. Derive supported suites and comparison commands from the +current harness documentation and CLI. + +Before changing a threshold, record its previous value and measured result, +explain the expected delta, update it, and run a clean focused verification. +Keep ordinary threshold fixtures separate from cross-ref fixtures. + +This branch is complete when the focused measurement is repeatable, threshold +verification is clean when applicable, and reported comparisons include +resolved refs, compiler version, and an explanation of the observed delta. +Fixture or harness changes also require the focused validation and formatting +checks documented in the README. diff --git a/.agents/skills/scratchpad/SKILL.md b/.agents/skills/scratchpad/SKILL.md deleted file mode 100644 index b42bdc88f..000000000 --- a/.agents/skills/scratchpad/SKILL.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: scratchpad -description: Extract the JSDoc example nearest the active source selection or cursor into ./scratchpad as a TypeScript file. Use when the user asks to dump, copy, open, or try a source example in scratchpad. ---- - -Use this skill to create a scratchpad TypeScript file from the JSDoc `**Example**` -nearest the user's active source cursor or selection. - -## Workflow - -1. Determine the source path and line: - - Use the IDE active file and selection/cursor line when present. - - Use an explicit file and line when the user provides them. - - If no line or selection is available, ask for it. -2. Run: - - ```sh - node .agents/skills/scratchpad/scripts/extract-example.mjs - ``` - -3. If the script exits with code 2 because there is no obvious runner, ask the - user whether to preserve the example exactly, name an Effect value to run, or - cancel. - - To preserve exactly, rerun with `--mode preserve`. - - To run a specific Effect value, rerun with `--runner `. -4. Report the created path as a clickable file link. This is the deterministic - way to open it in the code pane. -5. Do not run the scratchpad file unless the user explicitly asks. - -## Behavior - -- The script chooses the example whose `**Example**` section contains the line; - otherwise it chooses the first following example; otherwise the nearest - previous example. -- Filenames are derived from the source file and example title, for example - `scratchpad/Schedule-retrying-and-repeating-effects.ts`. -- Existing files are not overwritten. The script appends a numeric suffix. -- In auto mode, if a top-level `program` binding exists, the script appends: - - ```ts - Effect.runPromise(program).then(console.log, console.error) - ``` - -- If the example already contains an Effect runner, the script preserves it. diff --git a/.agents/skills/scratchpad/agents/openai.yaml b/.agents/skills/scratchpad/agents/openai.yaml deleted file mode 100644 index f83a2a4b0..000000000 --- a/.agents/skills/scratchpad/agents/openai.yaml +++ /dev/null @@ -1,7 +0,0 @@ -interface: - display_name: "Scratchpad" - short_description: "Extract examples into scratchpad" - default_prompt: "Use $scratchpad to extract the active JSDoc example into scratchpad." - -policy: - allow_implicit_invocation: true diff --git a/.agents/skills/scratchpad/scripts/extract-example.mjs b/.agents/skills/scratchpad/scripts/extract-example.mjs deleted file mode 100644 index e66812292..000000000 --- a/.agents/skills/scratchpad/scripts/extract-example.mjs +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env node - -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs" -import { basename, extname, isAbsolute, join, relative, resolve } from "node:path" - -const usage = `Usage: - node .agents/skills/scratchpad/scripts/extract-example.mjs [--mode auto|preserve] [--runner ] [--out-dir ] - -Examples: - node .agents/skills/scratchpad/scripts/extract-example.mjs packages/effect/src/Schedule.ts 9 - node .agents/skills/scratchpad/scripts/extract-example.mjs packages/effect/src/Schedule.ts 9 --mode preserve - node .agents/skills/scratchpad/scripts/extract-example.mjs packages/effect/src/Schedule.ts 9 --runner myProgram -` - -const args = process.argv.slice(2) -const sourcePath = args[0] -const lineInput = args[1] -let mode = "auto" -let runner = undefined -let outDir = "scratchpad" - -for (let index = 2; index < args.length; index++) { - const arg = args[index] - if (arg === "--mode") { - mode = args[++index] - } else if (arg === "--runner") { - runner = args[++index] - } else if (arg === "--out-dir") { - outDir = args[++index] - } else { - fail(`Unknown option: ${arg}`) - } -} - -if (!sourcePath || !lineInput) { - fail(usage) -} - -if (mode !== "auto" && mode !== "preserve") { - fail(`Invalid --mode: ${mode}`) -} - -if (runner !== undefined && !/^[A-Za-z_$][\w$]*$/.test(runner)) { - fail(`Invalid --runner identifier: ${runner}`) -} - -const line = Number.parseInt(lineInput, 10) - -if (!Number.isSafeInteger(line) || line < 1) { - fail(`Invalid line number: ${lineInput}`) -} - -const resolvedSourcePath = resolve(sourcePath) -const source = readFileSync(resolvedSourcePath, "utf8") -const sourceLines = source.split(/\r?\n/) -const examples = findExamples(sourceLines) - -if (examples.length === 0) { - fail(`No JSDoc examples found in ${sourcePath}`) -} - -const example = chooseExample(examples, line) -const hasRunner = /\bEffect\.run[A-Za-z]*\s*\(/.test(example.code) -const programRunner = /^\s*(?:export\s+)?(?:const|let|var)\s+program\s*=/m.test(example.code) - -let code = example.code.trimEnd() -let runnerStatus = "none" - -if (runner !== undefined) { - code = appendRunner(code, runner) - runnerStatus = `appended:${runner}` -} else if (mode === "auto") { - if (hasRunner) { - runnerStatus = "already-present" - } else if (programRunner) { - code = appendRunner(code, "program") - runnerStatus = "appended:program" - } else { - const payload = { - status: "needs-runner", - title: example.title, - sourcePath: displayPath(resolvedSourcePath), - titleLine: example.titleLine, - codeStartLine: example.codeStartLine, - codeEndLine: example.codeEndLine - } - process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`) - process.exit(2) - } -} - -mkdirSync(outDir, { recursive: true }) - -const outputPath = uniqueOutputPath(outDir, resolvedSourcePath, example.title) -writeFileSync(outputPath, `${code}\n`, "utf8") - -process.stdout.write( - `${JSON.stringify( - { - outputPath: displayPath(resolve(outputPath)), - title: example.title, - sourcePath: displayPath(resolvedSourcePath), - titleLine: example.titleLine, - codeStartLine: example.codeStartLine, - codeEndLine: example.codeEndLine, - runner: runnerStatus - }, - null, - 2 - )}\n` -) - -function findExamples(lines) { - const examples = [] - let blockStart = -1 - let block = [] - - for (let index = 0; index < lines.length; index++) { - const line = lines[index] - - if (blockStart === -1 && line.includes("/**")) { - blockStart = index - block = [line] - if (line.includes("*/")) { - collectExamples(examples, block, blockStart) - blockStart = -1 - } - continue - } - - if (blockStart !== -1) { - block.push(line) - if (line.includes("*/")) { - collectExamples(examples, block, blockStart) - blockStart = -1 - } - } - } - - return examples -} - -function collectExamples(examples, block, blockStart) { - const cleaned = block.map(cleanJSDocLine) - - for (let index = 0; index < cleaned.length; index++) { - const line = cleaned[index] - const titleMatch = line.match(/\*\*Example\*\*(?:\s*\(([^)]+)\))?/) - - if (titleMatch === null) { - continue - } - - const title = titleMatch[1]?.trim() || `example-${blockStart + index + 1}` - const fenceStart = findFenceStart(cleaned, index + 1) - - if (fenceStart === -1) { - continue - } - - const fenceEnd = findFenceEnd(cleaned, fenceStart + 1) - - if (fenceEnd === -1) { - continue - } - - examples.push({ - title, - titleLine: blockStart + index + 1, - codeStartLine: blockStart + fenceStart + 2, - codeEndLine: blockStart + fenceEnd, - code: cleaned.slice(fenceStart + 1, fenceEnd).join("\n") - }) - - index = fenceEnd - } -} - -function findFenceStart(lines, startIndex) { - for (let index = startIndex; index < lines.length; index++) { - const trimmed = lines[index].trim() - - if (trimmed.startsWith("**Example**")) { - return -1 - } - - if (/^```(?:ts|typescript)?\s*$/.test(trimmed)) { - return index - } - } - - return -1 -} - -function findFenceEnd(lines, startIndex) { - for (let index = startIndex; index < lines.length; index++) { - if (lines[index].trim() === "```") { - return index - } - } - - return -1 -} - -function cleanJSDocLine(line) { - return line.replace(/^\s*\/\*\*\s?/, "").replace(/^\s*\*\/\s?$/, "").replace(/^\s*\* ?/, "") -} - -function chooseExample(examples, line) { - const containing = examples.find((example) => example.titleLine <= line && line <= example.codeEndLine) - - if (containing !== undefined) { - return containing - } - - const following = examples.find((example) => line < example.titleLine) - - if (following !== undefined) { - return following - } - - return examples[examples.length - 1] -} - -function appendRunner(code, identifier) { - return `${code.trimEnd()}\n\nEffect.runPromise(${identifier}).then(console.log, console.error)` -} - -function uniqueOutputPath(directory, source, title) { - const sourceName = basename(source, extname(source)) - const titleSlug = slug(title) || "example" - const base = `${sourceName}-${titleSlug}` - let candidate = join(directory, `${base}.ts`) - let suffix = 2 - - while (existsSync(candidate)) { - candidate = join(directory, `${base}-${suffix}.ts`) - suffix++ - } - - return candidate -} - -function slug(value) { - return value - .normalize("NFKD") - .replace(/[\u0300-\u036f]/g, "") - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, "") -} - -function displayPath(path) { - return isAbsolute(path) ? relative(process.cwd(), path) || "." : path -} - -function fail(message) { - process.stderr.write(`${message}\n`) - process.exit(1) -} diff --git a/.agents/skills/test-development/SKILL.md b/.agents/skills/test-development/SKILL.md new file mode 100644 index 000000000..3c6082e7c --- /dev/null +++ b/.agents/skills/test-development/SKILL.md @@ -0,0 +1,13 @@ +--- +name: test-development +description: Test development. Use when adding or changing runtime Vitest coverage or Tstyche contracts for behavior, inference, assignability, or displayed public types. +--- + +Select every applicable branch before editing: + +- **Runtime behavior:** Read [runtime.md](runtime.md). +- **Type contracts:** Read [types.md](types.md). + +Some changes may require both branches. The task is complete when every +changed runtime behavior and type contract has focused coverage, targeted tests +pass, and applicable root checks pass or are reported as not runnable. diff --git a/.agents/skills/test-development/displayed-types.md b/.agents/skills/test-development/displayed-types.md new file mode 100644 index 000000000..6edcc977a --- /dev/null +++ b/.agents/skills/test-development/displayed-types.md @@ -0,0 +1,20 @@ +# Displayed Types + +Deliberately produce an assignment error and match a distinctive substring with +Tstyche's checked `@ts-expect-error` message: + +```ts +it("simplifies the displayed type", () => { + const value = null as unknown as PublicType + + // @ts-expect-error Type '{ readonly value: string; }' + const displayed: never = value + + void displayed +}) +``` + +Keep the expected substring as small as possible while distinguishing the +public type from the leaked implementation type. Before accepting the test, +temporarily restore the broken type and confirm the diagnostic-message match +fails. diff --git a/.agents/skills/test-development/runtime.md b/.agents/skills/test-development/runtime.md new file mode 100644 index 000000000..864755bef --- /dev/null +++ b/.agents/skills/test-development/runtime.md @@ -0,0 +1,13 @@ +# Runtime Tests + +- Use `it.effect` for Effect-returning tests and regular `it` for pure + synchronous tests. +- `it.effect` and `it.live` provide and close a `Scope`; return scoped effects + directly instead of wrapping the body in `Effect.scoped`. +- Use `assert` from `@effect/vitest`, not Vitest's `expect`. +- Use `TestClock` for time-dependent behavior. +- Keep `Effect.runSync` out of unit tests; runnable documentation follows the + root JSDoc validation rules. + +Inspect nearby tests for imports and structure. This branch is complete when +every changed behavior has focused coverage and its targeted test passes. diff --git a/.agents/skills/test-development/types.md b/.agents/skills/test-development/types.md new file mode 100644 index 000000000..3bc747789 --- /dev/null +++ b/.agents/skills/test-development/types.md @@ -0,0 +1,14 @@ +# Type Tests + +Inspect nearby `.tst.ts` files and use their imports and assertion style. Use +ordinary Tstyche assertions such as `toBe` for structural equality and choose a +specific assertion for the inference or assignability contract under test. + +Structural equality does not verify editor quick-info rendering. For internal +aliases, unsimplified intersections, or other displayed-type regressions, read +[displayed-types.md](displayed-types.md). + +Run targeted `pnpm test-types `; the root command covers every +configured TypeScript version. For a regression fix, confirm the assertion +fails against the pre-fix type. This branch is complete when the assertion +proves the intended contract and the target passes. diff --git a/.agents/skills/vendored-assets/SKILL.md b/.agents/skills/vendored-assets/SKILL.md new file mode 100644 index 000000000..6c84d0a82 --- /dev/null +++ b/.agents/skills/vendored-assets/SKILL.md @@ -0,0 +1,47 @@ +--- +name: vendored-assets +description: Vendored assets. Use when importing or updating checked-in third-party or externally generated JavaScript, CSS, registries, schemas, snapshots, or Scalar, Swagger, and MIME artifacts. +--- + +Treat upstream artifacts as supply-chain inputs. Change their generator or +documented import source, then regenerate; generated output is review evidence, +not an editing surface. + +## Workflow + +1. **Trace ownership.** Identify the artifact, generator or exact import + procedure, upstream source/version, license, consumers, tests, and shipped + package or bundle. Search by asset and upstream project name to find current + packaging scripts, generated artifacts, consumers, and history. + Continue when every input and consumer is accounted for. +2. **Pin provenance.** Resolve moving URLs, tags, branches, and omitted versions + to an immutable release or artifact. Record project, version, path or URL, + and digest in the generator or maintenance header. Omit a digest only when + another enforced immutable source is recorded. + Continue when a future run cannot silently select different bytes. +3. **Clear the license gate.** Verify release metadata, license files, bundled + notices, and the license trail retained in the distributed form. + Continue when every distributed artifact has a verified retained license trail. +4. **Regenerate.** Improve the generator or import reference first. Recover an + exact procedure for generatorless assets; add a deterministic script only + for recurring updates. Record a one-off procedure beside the artifact. A + clean rerun must produce no diff. +5. **Audit the complete diff.** Account for behavior, URLs and runtime fetches, + source maps, notices, encoding, format changes, and additions or removals. + For browser assets, also read [browser-assets.md](browser-assets.md). + Continue when every change is attributable and suspicious content is resolved + or reported. +6. **Clear test and size gates.** Record byte sizes before and after, use + focused consumer tests, and compare bundle size or composition when shipped + JavaScript, CSS, or registry output changes materially. Compare that output + against the pre-update revision rather than measuring only current size. + Continue when focused checks pass and every non-trivial size delta is explained. +7. **Close release impact.** Apply root changeset routing for consumer-visible + behavior, browser support, wire data, or meaningful shipped-size changes. + Generated sections owned by barrels, AI docs, or migration tooling remain + with the owners named in root instructions. + +The task is complete when every input and consumer is accounted for, provenance +and licenses are immutable and retained, regeneration is deterministic, every +output change and non-trivial size delta is explained, focused checks pass or +are reported as not runnable, and release impact is recorded. diff --git a/.agents/skills/vendored-assets/browser-assets.md b/.agents/skills/vendored-assets/browser-assets.md new file mode 100644 index 000000000..a95b21fe7 --- /dev/null +++ b/.agents/skills/vendored-assets/browser-assets.md @@ -0,0 +1,13 @@ +# Browser Asset Audit + +For browser-delivered JavaScript or CSS, inspect the complete generated output +for: + +- CSP requirements and dynamic code execution; +- injected markup, scripts, or styles; +- network destinations and runtime loading; +- external URLs, source maps, and runtime fetches; +- bundled code or notices absent from upstream top-level metadata. + +Every destination and executable behavior must be attributable to the selected +upstream release. Resolve or report suspicious content before completion. diff --git a/.changeset/ai-approved-tool-results.md b/.changeset/ai-approved-tool-results.md new file mode 100644 index 000000000..203cf0284 --- /dev/null +++ b/.changeset/ai-approved-tool-results.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Retain completed tool approval results in non-streaming responses so Chat records them and does not replay approved tools on later turns. diff --git a/.changeset/align-type-id-paths.md b/.changeset/align-type-id-paths.md new file mode 100644 index 000000000..932d2d2c0 --- /dev/null +++ b/.changeset/align-type-id-paths.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/opentelemetry": patch +--- + +Align runtime type IDs with their module paths. Effect markers now omit legacy grouping prefixes and the `unstable` path segment, while OpenTelemetry spans use the `OtelTracer` module path. Custom implementations that copy these marker strings must adopt the corrected IDs. diff --git a/.changeset/all-union-record-channels.md b/.changeset/all-union-record-channels.md new file mode 100644 index 000000000..ddafd3ab6 --- /dev/null +++ b/.changeset/all-union-record-channels.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.all` to retain errors and required services from every branch of a union of record inputs. diff --git a/.changeset/anthropic-image-strings.md b/.changeset/anthropic-image-strings.md new file mode 100644 index 000000000..7cb1c1467 --- /dev/null +++ b/.changeset/anthropic-image-strings.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Preserve base64 image strings in Anthropic requests. diff --git a/.changeset/anthropic-strict-json-schema.md b/.changeset/anthropic-strict-json-schema.md new file mode 100644 index 000000000..9a1caae53 --- /dev/null +++ b/.changeset/anthropic-strict-json-schema.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Exclude the provider-only `strictJsonSchema` option from Anthropic Messages request bodies while preserving its control over tool strictness. diff --git a/.changeset/anthropic-structured-output-fallback.md b/.changeset/anthropic-structured-output-fallback.md new file mode 100644 index 000000000..3ed06de12 --- /dev/null +++ b/.changeset/anthropic-structured-output-fallback.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Fix non-native structured output generation by always requesting the response tool and excluding accompanying prose when decoding its JSON payload. diff --git a/.changeset/arbitrary-index-constraints.md b/.changeset/arbitrary-index-constraints.md new file mode 100644 index 000000000..9cff5c7da --- /dev/null +++ b/.changeset/arbitrary-index-constraints.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `Arbitrary.schema` to respect applicable index signatures when generating and shrinking object properties, including fixed fields in `Schema.StructWithRest` and overlapping records. + +Combine compatible string, number, and bigint constraints during generation so cases such as a `String` field constrained by a `NonEmptyString` record remain productive at size zero. Other intersections are validated and may exhaust the discard budget. diff --git a/.changeset/array-ensure-array-elements.md b/.changeset/array-ensure-array-elements.md new file mode 100644 index 000000000..762cc5afa --- /dev/null +++ b/.changeset/array-ensure-array-elements.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.ArrayEnsure` to preserve array-valued element branches and outer-array encoding cardinality. diff --git a/.changeset/atom-rpc-query-requires.md b/.changeset/atom-rpc-query-requires.md new file mode 100644 index 000000000..7169977a4 --- /dev/null +++ b/.changeset/atom-rpc-query-requires.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `AtomRpc.query` returning `never` for RPCs whose middleware declares service `requires`. The return-type conditional now infers all six `Rpc` type parameters, matching `mutation` and every utility in `Rpc`. diff --git a/.changeset/atom-solid-idle-ttl.md b/.changeset/atom-solid-idle-ttl.md new file mode 100644 index 000000000..e7a9989e5 --- /dev/null +++ b/.changeset/atom-solid-idle-ttl.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-solid": patch +--- + +Allow `RegistryProvider` to leave `defaultIdleTTL` undefined, matching the React binding and enabling immediate cleanup of unused atoms unless a TTL is explicitly configured. diff --git a/.changeset/bright-mimes-leave.md b/.changeset/bright-mimes-leave.md new file mode 100644 index 000000000..09ca9859a --- /dev/null +++ b/.changeset/bright-mimes-leave.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-node": patch +"effect": patch +--- + +Remove the `mime` runtime dependency. The new `effect/unstable/http/Mime` module provides top-level lookup functions +backed by a vendored standard MIME registry. diff --git a/.changeset/bright-sockets-connect.md b/.changeset/bright-sockets-connect.md new file mode 100644 index 000000000..ca176f381 --- /dev/null +++ b/.changeset/bright-sockets-connect.md @@ -0,0 +1,9 @@ +--- +"@effect/ai-openai": patch +"@effect/platform-bun": patch +"@effect/platform-node": patch +"effect": patch +--- + +Allow sockets to use browser, Bun, and Node WebSocket implementations without consumer casts. Platform constructors +now support typed opening-handshake headers where available. diff --git a/.changeset/browser-crypto-missing-subtle.md b/.changeset/browser-crypto-missing-subtle.md new file mode 100644 index 000000000..0fedd8a2b --- /dev/null +++ b/.changeset/browser-crypto-missing-subtle.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Return a typed `PlatformError` from `BrowserCrypto` digest operations when `crypto.subtle` is unavailable, instead of failing with a defect. diff --git a/.changeset/browser-indexeddb-binary-key-existence.md b/.changeset/browser-indexeddb-binary-key-existence.md new file mode 100644 index 000000000..c5462e706 --- /dev/null +++ b/.changeset/browser-indexeddb-binary-key-existence.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix `BrowserKeyValueStore.layerIndexedDb` to report keys containing `Uint8Array` values from `has`. diff --git a/.changeset/browser-runtime-custom-teardown.md b/.changeset/browser-runtime-custom-teardown.md new file mode 100644 index 000000000..443d692f7 --- /dev/null +++ b/.changeset/browser-runtime-custom-teardown.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Invoke custom `BrowserRuntime.runMain` teardown callbacks when the main effect completes, and remove the `pagehide` listener once the main fiber finishes. diff --git a/.changeset/bun-http-server-unix-address.md b/.changeset/bun-http-server-unix-address.md new file mode 100644 index 000000000..b5abe92de --- /dev/null +++ b/.changeset/bun-http-server-unix-address.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Report Unix socket addresses from Bun HTTP servers. diff --git a/.changeset/bun-stream-preserve-failure.md b/.changeset/bun-stream-preserve-failure.md new file mode 100644 index 000000000..c5756f3ee --- /dev/null +++ b/.changeset/bun-stream-preserve-failure.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Preserve mapped errors from Bun readable streams. diff --git a/.changeset/cache-invalidate-when-replacement.md b/.changeset/cache-invalidate-when-replacement.md new file mode 100644 index 000000000..b8b861c28 --- /dev/null +++ b/.changeset/cache-invalidate-when-replacement.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cache.invalidateWhen` and `ScopedCache.invalidateWhen` deleting a replacement entry while waiting for an earlier lookup. diff --git a/.changeset/cache-refresh-cancellation-ownership.md b/.changeset/cache-refresh-cancellation-ownership.md new file mode 100644 index 000000000..7ad98b91c --- /dev/null +++ b/.changeset/cache-refresh-cancellation-ownership.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix interruption of `Cache.refresh` for a missing key removing a newer value written by `Cache.set`. diff --git a/.changeset/cache-refresh-capacity.md b/.changeset/cache-refresh-capacity.md new file mode 100644 index 000000000..d03ac1d50 --- /dev/null +++ b/.changeset/cache-refresh-capacity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cache.refresh` and `ScopedCache.refresh` exceeding capacity when an existing key is evicted while its refresh is in progress. Publishing the refreshed entry now evicts older entries as needed, releasing their resources in `ScopedCache`. diff --git a/.changeset/cache-refresh-zero-ttl-ownership.md b/.changeset/cache-refresh-zero-ttl-ownership.md new file mode 100644 index 000000000..06f9ee7f2 --- /dev/null +++ b/.changeset/cache-refresh-zero-ttl-ownership.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cache.refresh` for an initially missing key deleting a newer cached value when the refresh completes with zero time to live. diff --git a/.changeset/cache-synchronous-interruption.md b/.changeset/cache-synchronous-interruption.md new file mode 100644 index 000000000..23fde1c27 --- /dev/null +++ b/.changeset/cache-synchronous-interruption.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent `Cache` from retaining synchronously interrupted lookups. diff --git a/.changeset/calm-ducks-fail.md b/.changeset/calm-ducks-fail.md new file mode 100644 index 000000000..7c801d321 --- /dev/null +++ b/.changeset/calm-ducks-fail.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Wake `NodeStream.pipeThroughDuplex` readers when the upstream fails so the original error is propagated instead of hanging. diff --git a/.changeset/calm-headers-hide.md b/.changeset/calm-headers-hide.md new file mode 100644 index 000000000..0e3cff5d5 --- /dev/null +++ b/.changeset/calm-headers-hide.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Respect custom HTTP header redaction when recording server span attributes. diff --git a/.changeset/calm-readers-transact.md b/.changeset/calm-readers-transact.md new file mode 100644 index 000000000..011717a3c --- /dev/null +++ b/.changeset/calm-readers-transact.md @@ -0,0 +1,7 @@ +--- +"@effect/sql-sqlite-bun": patch +"@effect/sql-sqlite-node": patch +--- + +Allow read-only SQLite clients to use `withTransaction` when `PRAGMA query_only` is enabled. Writable clients continue +to reserve the writer lock when a transaction starts. diff --git a/.changeset/calm-trees-generate.md b/.changeset/calm-trees-generate.md new file mode 100644 index 000000000..8179f0ba7 --- /dev/null +++ b/.changeset/calm-trees-generate.md @@ -0,0 +1,120 @@ +--- +"@effect/vitest": patch +"effect": patch +--- + +Add the experimental Schema-first `effect/unstable/arbitrary/Arbitrary` module for native generation without +fast-check. `Arbitrary.schema` derives an opaque arbitrary from the decoded Schema `Type`, `Arbitrary.sampleEffect` +provides interruptible sampling with typed exhaustion, and `Arbitrary.checkEffect` returns structured property results. +The initial implementation supports bounded discards, shrinking, replay, and recursive and mutually recursive Schemas. +`SampleError` and `Exhausted` include the effective seed so discarded runs remain reproducible even when the caller did +not provide one. `Arbitrary.isArbitrary` identifies values through the module's nominal protocol. Numeric constraints +retain `NaN` when it is accepted by their supported `Order.Number` bounds. Union derivation validates `oneOf` +exclusivity and isolates lazy cross-member shrinking from unrelated random generation. Object derivation keeps +optional-property selection constructive when candidate fields have different recursive costs. +Struct, Record, JSON-object, and record-shaped `Arbitrary.all` outputs periodically use a null prototype as an edge +case, preserving that prototype throughout shrinking and replay without perturbing structural PRNG choices. The change +adds 0.01–0.03 KB gzip to representative Arbitrary fixtures and leaves production-only bundle sentinels unchanged. + +Add `Arbitrary.map`, `Arbitrary.flatMap`, `Arbitrary.filter`, `Arbitrary.filterMap`, and `Arbitrary.all` for composing +derived Arbitraries without exposing a second catalog of primitive constructors. Filtering remains bounded and +promotes valid shrink descendants through rejected nodes. `maxShrinks` bounds every inspected shrink candidate, +including candidates rejected before property evaluation, while retaining the best shrunk input found when the +budget is exhausted. `flatMap` provides deterministic dependent generation, source-first shrinking, post-source PRNG +checkpoints, and one shared residual recursion budget. `all` combines tuples, iterables, and records with a shared +budget, randomized internal generation order, stable output shape, and independent member shrinking. Arbitrary values +implement `Pipeable` for composition with data-last combinators. + +Add the experimental Schema `arbitraryConstraint` and `toCodecArbitrary` annotations and their +`Schema.Annotations.ToArbitrary` types. Declarations can provide a Schema Link optimized for generation, while filters +can contribute native semantic constraints. The callback receives decoded type parameters and normalized constraints. +The compiler owns efficient representations for common built-ins, including JSON, RegExp, URL, Date, byte arrays, +ReadonlyMap, and ReadonlySet. Effect-specific HashMap, HashSet, Chunk, Graph, BigDecimal, and date-time declarations keep +local generation Links, while declarations with productive canonical codecs require no arbitrary-specific annotation. +`Schema.isUniqueKey` provides key-based Map uniqueness for explicit array representations. + +The same ownership policy applies to formatter and equivalence derivation: implementations for common declarations +live in their compiler, while domain-specific and dynamically constructed declarations retain local annotations. +Declarations whose intrinsic `Equal` implementation already matches their Schema equivalence need no annotation or +compiler special case. This keeps unused common callbacks out of production Schema bundles. + +Against the previous layout, `schema-toArbitrary` decreases from 36.68 KB to 33.24 KB gzip and +`arbitrary-combinators` decreases from 37.16 KB to 33.70 KB. `schema-toFormatter` increases from 18.92 KB to 19.49 KB +and `schema-toEquivalence` increases from 19.05 KB to 19.39 KB because callers that explicitly derive these capabilities +now retain the common declaration handlers. Generic production fixtures remain unchanged; an equivalence-specific +production fixture using common declarations decreases from 20.75 KB to 20.48 KB, while declarations whose intrinsic +equality is sufficient decrease from 23.42 KB to 23.34 KB. An Arbitrary-specific production fixture using common +declarations decreases from 20.35 KB to 19.61 KB, while one using the locally annotated BigDecimal and date-time +declarations increases from 18.34 KB to 23.01 KB. +The complete 31-scenario native Arbitrary comparison reports no statistically classified runtime regression; the five +moved BigDecimal and date-time scenarios remain within measurement noise. + +Add `SchemaGetter.forbiddenEncoding`, a reusable getter for the encode side of decode-only Schema transformations. + +Remove the fast-check bridge from the `effect` package, including `Schema.toArbitrary` and +`effect/testing/FastCheck`. Replace the legacy `Schema.Annotations.ToArbitrary` callback contract with the native +Schema-first types. The `effect` package no longer depends on fast-check. + +Migrate `TestSchema.Asserts.verifyLosslessTransformation` and `TestSchema.Asserts.arbitrary().verifyGeneration` to the +native runner. Both methods now accept native check options directly, bound unsuccessful generation, and include the +shrunk input and replay token in property failures. + +Use the Arbitrary runner for all `@effect/vitest` property tests. Property inputs may combine Schemas and Arbitraries, +and are composed directly with `Arbitrary.all`; check options are available through `arbitrary`. Raw fast-check +arbitraries and the `fastCheck` options object are no longer supported. As with the previous fast-check adapter, thrown +exceptions, defects, and typed failures from a property are shrinkable falsifications; Effect interruption remains an +interruption. + +Optimize constructive regular-expression generation by caching feasible lengths on the compiled pattern, computing +sequence-suffix feasibility once, and precomputing character-class metadata. Seeded generation, shrinking, and replay +remain unchanged. + +Optimize `BigDecimal.Order` and `BigDecimal.Equivalence` with a shared hybrid comparator. Ordinary scale differences +use cached, bounded coefficient alignment, while large differences are compared without materializing their decimal +zeroes. `BigDecimal.make` now rejects scales that are not safe integers. + +Before its removal, the materialized fast-check bridge fixture +`schema-toArbitrary-materialized-fast-check.ts` measured 79.00 KB minified and gzipped. + +Representative runtime measurements against corresponding hand-written fast-check 4.9.0 arbitraries are shown below. +Values are median latency on Node 24.12.0 and Apple M3; lower is better. Both implementations validate the +same output domains, although their generation distributions are not identical. Native speedup is fast-check latency +divided by Native latency, so higher is better. + +| Scenario | fast-check | Native | Native speedup | +| ----------------------------------- | ---------: | ------: | -------------: | +| 32 recursive samples | 150 µs | 103 µs | 1.45x | +| 128 optional Struct samples | 244 µs | 86.0 µs | 2.84x | +| 128 constrained strings | 742 µs | 49.7 µs | 14.86x | +| RegExp derivation and first sample | 13.4 ms | 30.8 µs | 429.02x | +| 64 RegExp strings | 595 µs | 919 µs | 0.64x | +| RegExp failure and shrinking | 168 µs | 88.2 µs | 1.91x | +| 128 bounded numbers | 68.9 µs | 21.8 µs | 3.18x | +| 128 `Uint8Array` samples | 98.3 µs | 74.4 µs | 1.32x | +| 128 `BigDecimal` samples | 66.6 µs | 56.3 µs | 1.18x | +| 128 `DateTime.Utc` samples | 71.2 µs | 50.5 µs | 1.42x | +| 128 named time zones | 52.2 µs | 27.9 µs | 1.85x | +| 128 time zones | 63.7 µs | 33.8 µs | 1.89x | +| 128 zoned date-times | 130 µs | 112 µs | 1.16x | +| 32 samples through Schema filter | 65.9 µs | 49.4 µs | 1.33x | +| 32 unique arrays | 156 µs | 132 µs | 1.18x | +| 128 literal samples | 40.0 µs | 3.70 µs | 10.78x | +| 128 mapped samples | 59.0 µs | 14.1 µs | 4.21x | +| 128 samples through passing filter | 58.9 µs | 13.9 µs | 4.23x | +| 32 samples through selective filter | 66.1 µs | 42.9 µs | 1.54x | +| 128 `filterMap` samples | 75.7 µs | 31.5 µs | 2.40x | +| Filtered failure and shrinking | 12.7 µs | 7.71 µs | 1.66x | +| 128 `all` tuple samples | 43.5 µs | 18.5 µs | 2.35x | +| 128 `all` record samples | 81.0 µs | 30.4 µs | 2.66x | +| 128 dependent `flatMap` samples | 125 µs | 67.2 µs | 1.86x | +| `flatMap` failure and shrinking | 20.1 µs | 6.71 µs | 2.99x | +| Replay `flatMap` shrink path | 14.3 µs | 6.57 µs | 2.17x | +| Passing property, 100 runs | 42.3 µs | 27.1 µs | 1.56x | +| `TestSchema`, 100 generations | 44.5 µs | 35.9 µs | 1.24x | +| First failure plus one shrink | 8.77 µs | 1.30 µs | 6.75x | +| Replay recorded failure | 6.35 µs | 1.19 µs | 5.36x | + +Cold recursive derivation is not included because the native fixture constructs and compiles a Schema, while the +fast-check fixture constructs a hand-written arbitrary; it is not a like-for-like warm-generator comparison. + +Add a guide for the native module and a migration guide from the fast-check bridge published in `effect@4.0.0-rc.109`. diff --git a/.changeset/catch-stream-channel-defects.md b/.changeset/catch-stream-channel-defects.md new file mode 100644 index 000000000..6757799f4 --- /dev/null +++ b/.changeset/catch-stream-channel-defects.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Stream.catchDefect` and `Channel.catchDefect` for recovering from defects without catching typed failures or interruptions. diff --git a/.changeset/channel-rundone-completion.md b/.changeset/channel-rundone-completion.md new file mode 100644 index 000000000..c0a74a290 --- /dev/null +++ b/.changeset/channel-rundone-completion.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Channel.runDone`; use `Channel.runDrain` to consume all output and return the completion value. diff --git a/.changeset/child-process-astral-escape-arguments.md b/.changeset/child-process-astral-escape-arguments.md new file mode 100644 index 000000000..f68fd0049 --- /dev/null +++ b/.changeset/child-process-astral-escape-arguments.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve astral Unicode escapes and following arguments in `ChildProcess.make` and `ChildProcess.prefix` template literals. diff --git a/.changeset/childprocess-process-group-wait.md b/.changeset/childprocess-process-group-wait.md new file mode 100644 index 000000000..0a6f55a4e --- /dev/null +++ b/.changeset/childprocess-process-group-wait.md @@ -0,0 +1,10 @@ +--- +"@effect/platform-node-shared": patch +"effect": patch +--- + +Wait for Node child process groups to exit during scoped release and `kill`. + +After signalling a process group, both operations now wait for its leader and descendants. Without `forceKillAfter`, the wait is limited to one second and never escalates. With `forceKillAfter`, the group receives `SIGKILL` at the deadline, followed by a final wait of up to one second. Native timers keep escalation working under a `TestClock`, and cleanup no longer depends on stdio closing. + +`exitCode` and `isRunning` remain tied to the leader's exit, and a leader that already exited successfully still leaves its group untouched. Process group checks count zombies, so cleanup may wait for the full bound under a non-reaping PID 1. diff --git a/.changeset/chunk-slice-concatenation.md b/.changeset/chunk-slice-concatenation.md new file mode 100644 index 000000000..593f5c2a6 --- /dev/null +++ b/.changeset/chunk-slice-concatenation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Chunk` concatenation to preserve sliced elements. diff --git a/.changeset/clean-config-names.md b/.changeset/clean-config-names.md new file mode 100644 index 000000000..0128f0660 --- /dev/null +++ b/.changeset/clean-config-names.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Rename the built-in `Config` constructors to PascalCase and rename `Config.mapOrFail` to `Config.mapEffect`. `Config.Array` and `Config.Record` now construct configs directly, with overloads for pathless options or a path followed by options, while their specialized schemas and the other built-in schemas are kept internal. + +This is a breaking naming cleanup for the Effect 4 release candidate. It makes casing consistently identify typed config constructors, aligns effectful mapping with the rest of the library, and prevents implementation schemas from expanding the public `Config` interface. diff --git a/.changeset/clean-yaks-parse.md b/.changeset/clean-yaks-parse.md new file mode 100644 index 000000000..baf2d838c --- /dev/null +++ b/.changeset/clean-yaks-parse.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Use Effect's YAML parser for OpenAPI patch files and remove the direct `yaml` dependency. diff --git a/.changeset/cleanup-before-use-callbacks.md b/.changeset/cleanup-before-use-callbacks.md new file mode 100644 index 000000000..414cb54e6 --- /dev/null +++ b/.changeset/cleanup-before-use-callbacks.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `Effect.acquireUseRelease` releases an acquired resource and `Effect.useSpan` ends its span when the use callback throws before returning an effect. The thrown exception remains a defect, but no longer skips cleanup. diff --git a/.changeset/clear-wings-relax.md b/.changeset/clear-wings-relax.md new file mode 100644 index 000000000..526b3bc30 --- /dev/null +++ b/.changeset/clear-wings-relax.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +use `new` instantiation for streams diff --git a/.changeset/cli-completion-command-aliases.md b/.changeset/cli-completion-command-aliases.md new file mode 100644 index 000000000..00d824e98 --- /dev/null +++ b/.changeset/cli-completion-command-aliases.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve flags and nested commands when completing a CLI subcommand through its alias. diff --git a/.changeset/cli-completion-shared-flags.md b/.changeset/cli-completion-shared-flags.md new file mode 100644 index 000000000..9e27bd98d --- /dev/null +++ b/.changeset/cli-completion-shared-flags.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include inherited shared flags in descendant CLI completions. diff --git a/.changeset/cli-key-value-pair-first-separator.md b/.changeset/cli-key-value-pair-first-separator.md new file mode 100644 index 000000000..d8241fd5a --- /dev/null +++ b/.changeset/cli-key-value-pair-first-separator.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow `=` in values parsed by `Primitive.keyValuePair`, `Flag.keyValuePair`, and `Param.keyValuePair` in `effect/unstable/cli`. diff --git a/.changeset/cli-optional-alternative-flags.md b/.changeset/cli-optional-alternative-flags.md new file mode 100644 index 000000000..743c995ff --- /dev/null +++ b/.changeset/cli-optional-alternative-flags.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow optional alternative CLI flags. diff --git a/.changeset/cli-scalar-constructor-names.md b/.changeset/cli-scalar-constructor-names.md new file mode 100644 index 000000000..e5476bb8a --- /dev/null +++ b/.changeset/cli-scalar-constructor-names.md @@ -0,0 +1,27 @@ +--- +"effect": patch +--- + +Rename CLI constructors to PascalCase, aligning scalar names with `Schema` and `Config`. This is a breaking change; parsing behavior is unchanged. + +In `Primitive`, `Param`, `Flag`, and `Argument`, capitalize existing constructor names, with these exceptions: + +| Previous | New | Modules | +| --------- | ---------- | --------------------- | +| `integer` | `Int` | All four | +| `float` | `Finite` | All four | +| `none` | `Never` | All four | +| `choice` | `Literals` | Param, Flag, Argument | + +`Primitive.choice` becomes `Primitive.Choice`; `choiceWithValue` becomes `ChoiceWithValue` where available. + +In `Prompt`, capitalize control constructors except `text` → `String`, `integer` → `Int`, and `float` → `Number`. Rename public types `IntegerOptions` → `IntOptions` and `FloatOptions` → `NumberOptions`. Shared `TextOptions` is unchanged. `Prompt.Number` retains its existing parser, without a finite-number restriction. + +In `GlobalFlag`, rename `action` → `Action` and `setting` → `Setting`. Factories and combinators, including `Command.make` and `Prompt.succeed`, keep their names. + +Update public `_tag` matches and completion descriptors: + +- `Primitive`: `"Integer"` → `"Int"`, `"Float"` → `"Finite"`, `"None"` → `"Never"`. +- `Completions.FlagType` and `Completions.ArgumentType`: `"Integer"` → `"Int"`, `"Float"` → `"Finite"`. + +Sentinels still always fail; their internal parameter name is now `"__never__"`. Help labels and completion scripts are unchanged. diff --git a/.changeset/cli-variadic-absence-defaults.md b/.changeset/cli-variadic-absence-defaults.md new file mode 100644 index 000000000..7f3f05e30 --- /dev/null +++ b/.changeset/cli-variadic-absence-defaults.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix defaulted variadic arguments when omitted. diff --git a/.changeset/cli-wizard-option-looking-values.md b/.changeset/cli-wizard-option-looking-values.md new file mode 100644 index 000000000..852a59382 --- /dev/null +++ b/.changeset/cli-wizard-option-looking-values.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix CLI wizard handling of negative numbers and other flag values beginning with `-`. diff --git a/.changeset/cluster-active-teardowns.md b/.changeset/cluster-active-teardowns.md new file mode 100644 index 000000000..c57eceae0 --- /dev/null +++ b/.changeset/cluster-active-teardowns.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Cluster no longer retains fiber ids for every local teardown. + +Transient persisted interrupts are now classified from live teardown state +(entity, shard, singleton, entity type, and node shutdown) instead of a +process-lifetime set of fiber ids. The registry is bounded by in-flight +teardowns and returns to baseline after entity reap storms. diff --git a/.changeset/cluster-reply-codec-services.md b/.changeset/cluster-reply-codec-services.md new file mode 100644 index 000000000..1ee7d1c8f --- /dev/null +++ b/.changeset/cluster-reply-codec-services.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Reply.Reply` codecs to require client services when decoding and server services when encoding. diff --git a/.changeset/cold-shards-continue.md b/.changeset/cold-shards-continue.md new file mode 100644 index 000000000..e71308b84 --- /dev/null +++ b/.changeset/cold-shards-continue.md @@ -0,0 +1,15 @@ +--- +"effect": patch +--- + +Transient routing states for persisted cluster messages no longer surface as errors. + +If an entity moves runners or is shut down before replying, the caller keeps +waiting for the reply via message storage while the entity moves. If the local +runner is shutting down while a caller is waiting, the call is interrupted +instead of failing with `EntityNotAssignedToRunner`: the request is already +durable and will be served under the next owner. + +Durable workflows treat such an interrupt as an abandoned run attempt: the run +stops with nothing persisted, without running compensations or resuming the +parent, ready to replay on the replacement runner. diff --git a/.changeset/compatible-dependency-refresh.md b/.changeset/compatible-dependency-refresh.md new file mode 100644 index 000000000..a2d211be4 --- /dev/null +++ b/.changeset/compatible-dependency-refresh.md @@ -0,0 +1,8 @@ +--- +"@effect/platform-node": patch +"@effect/sql-libsql": patch +"@effect/sql-mysql2": patch +"@effect/doctest": patch +--- + +Update dependencies to their latest compatible versions. diff --git a/.changeset/config.json b/.changeset/config.json index b45ea0c70..0a755e5f0 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,7 +1,8 @@ { - "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "Effect-TS/effect-smol" }], + "$schema": "https://unpkg.com/@changesets/config@4.0.0-next.8/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "Effect-TS/effect" }], "commit": false, + "format": false, "linked": [], "access": "restricted", "baseBranch": "main", @@ -14,7 +15,35 @@ "fixed": [ [ "effect", - "@effect/*" + "@effect/ai-anthropic", + "@effect/ai-openai", + "@effect/ai-openai-compat", + "@effect/ai-openrouter", + "@effect/atom-react", + "@effect/atom-solid", + "@effect/atom-vue", + "@effect/docgen", + "@effect/doctest", + "@effect/openapi-generator", + "@effect/opentelemetry", + "@effect/platform-browser", + "@effect/platform-bun", + "@effect/platform-deno", + "@effect/platform-node", + "@effect/platform-node-shared", + "@effect/sql-clickhouse", + "@effect/sql-d1", + "@effect/sql-libsql", + "@effect/sql-mssql", + "@effect/sql-mysql2", + "@effect/sql-pg", + "@effect/sql-pglite", + "@effect/sql-sqlite-bun", + "@effect/sql-sqlite-do", + "@effect/sql-sqlite-node", + "@effect/sql-sqlite-react-native", + "@effect/sql-sqlite-wasm", + "@effect/vitest" ] ], "snapshot": { diff --git a/.changeset/context-saved-getter-inference.md b/.changeset/context-saved-getter-inference.md new file mode 100644 index 000000000..c3028f360 --- /dev/null +++ b/.changeset/context-saved-getter-inference.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix saved curried `Context.get` calls incorrectly inferring their required service as `unknown`. diff --git a/.changeset/cookies-error-tag.md b/.changeset/cookies-error-tag.md new file mode 100644 index 000000000..c523bca08 --- /dev/null +++ b/.changeset/cookies-error-tag.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the `CookiesError` tag in `effect/unstable/http` from `CookieError` to `CookiesError` to match the class name. diff --git a/.changeset/d1-raw-native-results.md b/.changeset/d1-raw-native-results.md new file mode 100644 index 000000000..5441b86d1 --- /dev/null +++ b/.changeset/d1-raw-native-results.md @@ -0,0 +1,7 @@ +--- +"@effect/sql-d1": patch +--- + +Return the complete native `D1Result` from D1 statement `.raw`, preserving `success`, `meta`, and `results` instead of returning only the row array. + +Callers that treated `.raw` as an array should read `.results` or use an ordinary or `.unprepared` statement when only rows are needed. diff --git a/.changeset/datetime-calendar-parts.md b/.changeset/datetime-calendar-parts.md new file mode 100644 index 000000000..5529fb6e2 --- /dev/null +++ b/.changeset/datetime-calendar-parts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Apply DateTime calendar parts without intermediate overflow. diff --git a/.changeset/deno-redis-url-credentials.md b/.changeset/deno-redis-url-credentials.md new file mode 100644 index 000000000..c150c6fef --- /dev/null +++ b/.changeset/deno-redis-url-credentials.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Decode percent-encoded Redis URL authority credentials before authentication. diff --git a/.changeset/deno-writefile-existing-mode.md b/.changeset/deno-writefile-existing-mode.md new file mode 100644 index 000000000..17d44f9ed --- /dev/null +++ b/.changeset/deno-writefile-existing-mode.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Fix `FileSystem.writeFile` on Deno to preserve permissions when writing an existing file with an explicit mode. diff --git a/.changeset/docgen-alias-signature.md b/.changeset/docgen-alias-signature.md new file mode 100644 index 000000000..58b06047f --- /dev/null +++ b/.changeset/docgen-alias-signature.md @@ -0,0 +1,5 @@ +--- +"@effect/docgen": patch +--- + +Preserve alias names, constraints, and defaults in generated type signatures. diff --git a/.changeset/docgen-class-property-examples.md b/.changeset/docgen-class-property-examples.md new file mode 100644 index 000000000..68bfef540 --- /dev/null +++ b/.changeset/docgen-class-property-examples.md @@ -0,0 +1,5 @@ +--- +"@effect/docgen": patch +--- + +Type-check parsed class property examples and execute them when `runExamples` is enabled. Previously unchecked property examples with type errors now cause docgen to fail. diff --git a/.changeset/docgen-source-relative-module-pages.md b/.changeset/docgen-source-relative-module-pages.md new file mode 100644 index 000000000..92efd2667 --- /dev/null +++ b/.changeset/docgen-source-relative-module-pages.md @@ -0,0 +1,5 @@ +--- +"@effect/docgen": patch +--- + +Fix module page paths for `.`, nested, and absolute source directories so modules with the same filename in different subdirectories produce distinct pages. diff --git a/.changeset/docgen-unique-example-files.md b/.changeset/docgen-unique-example-files.md new file mode 100644 index 000000000..15a55b136 --- /dev/null +++ b/.changeset/docgen-unique-example-files.md @@ -0,0 +1,5 @@ +--- +"@effect/docgen": patch +--- + +Prevent examples from modules with colliding flattened paths from overwriting each other, so all examples are checked and optionally executed. Temporary example filenames and diagnostic paths now include a numeric prefix. diff --git a/.changeset/doctest-markdown-typescript.md b/.changeset/doctest-markdown-typescript.md new file mode 100644 index 000000000..a42719023 --- /dev/null +++ b/.changeset/doctest-markdown-typescript.md @@ -0,0 +1,5 @@ +--- +"@effect/doctest": patch +--- + +Fix spurious TypeScript syntax errors when running marked code fences in Markdown and MDX documents. diff --git a/.changeset/doctest-statement-boundary.md b/.changeset/doctest-statement-boundary.md new file mode 100644 index 000000000..9e69e68ff --- /dev/null +++ b/.changeset/doctest-statement-boundary.md @@ -0,0 +1,5 @@ +--- +"@effect/doctest": patch +--- + +Preserve statement boundaries after generated doctest assertions. diff --git a/.changeset/dotenv-literal-substitution.md b/.changeset/dotenv-literal-substitution.md new file mode 100644 index 000000000..0e351277d --- /dev/null +++ b/.changeset/dotenv-literal-substitution.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `ConfigProvider.fromDotEnvContents` variable expansion to preserve replacement tokens such as `$&` in referenced values. diff --git a/.changeset/durable-clock-zero-threshold.md b/.changeset/durable-clock-zero-threshold.md new file mode 100644 index 000000000..3a23956d3 --- /dev/null +++ b/.changeset/durable-clock-zero-threshold.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `DurableClock.sleep` to preserve explicit `0` and `0n` in-memory thresholds. diff --git a/.changeset/durable-deferred-into-encoding-services.md b/.changeset/durable-deferred-into-encoding-services.md new file mode 100644 index 000000000..6a823609a --- /dev/null +++ b/.changeset/durable-deferred-into-encoding-services.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Require schema encoding services when `DurableDeferred.into` records an exit. diff --git a/.changeset/dynamic-tool-parameter-schema.md b/.changeset/dynamic-tool-parameter-schema.md new file mode 100644 index 000000000..7d16a999d --- /dev/null +++ b/.changeset/dynamic-tool-parameter-schema.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update dynamic tools to advertise replacement parameter schemas after `setParameters`. diff --git a/.changeset/eff-1004-cli-display-width.md b/.changeset/eff-1004-cli-display-width.md new file mode 100644 index 000000000..6c0fdf593 --- /dev/null +++ b/.changeset/eff-1004-cli-display-width.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Align CLI help tables by terminal display width for wide, emoji, combining, and zero-width graphemes. diff --git a/.changeset/eff-1008-tool-param-failure-mode.md b/.changeset/eff-1008-tool-param-failure-mode.md new file mode 100644 index 000000000..8939684f6 --- /dev/null +++ b/.changeset/eff-1008-tool-param-failure-mode.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +"@effect/ai-openrouter": patch +--- + +Route tool call parameter validation failures through the tool's `failureMode` and drop `ToolParameterValidationError.toolParams`. diff --git a/.changeset/eff-1036-fiber-allocations.md b/.changeset/eff-1036-fiber-allocations.md new file mode 100644 index 000000000..ddd8bbc1c --- /dev/null +++ b/.changeset/eff-1036-fiber-allocations.md @@ -0,0 +1,12 @@ +--- +"effect": patch +"@effect/opentelemetry": patch +--- + +Reduce memory usage in Effect primitives and fibers. + +Breaking: context-derived `Fiber` fields now live under `fiber.cache`. The +`currentScheduler`, `currentSpan`, `currentLogLevel`, `currentStackFrame`, and +`currentPreventYield` fields are now `scheduler`, `span`, `logLevel`, +`stackFrame`, and `preventYield`. Access `minimumLogLevel` and +`maxOpsBeforeYield` through `cache` as well. diff --git a/.changeset/eff-1038-http-server-allocations.md b/.changeset/eff-1038-http-server-allocations.md new file mode 100644 index 000000000..26a8ea83b --- /dev/null +++ b/.changeset/eff-1038-http-server-allocations.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/platform-node": patch +--- + +Reduce HTTP server allocation churn when tracing is not configured and for requests that complete synchronously. diff --git a/.changeset/eff-1039-rpc-server-allocations.md b/.changeset/eff-1039-rpc-server-allocations.md new file mode 100644 index 000000000..5973fa3cf --- /dev/null +++ b/.changeset/eff-1039-rpc-server-allocations.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reduce per-request RPC server allocations. diff --git a/.changeset/eff-1042-http-server-performance.md b/.changeset/eff-1042-http-server-performance.md new file mode 100644 index 000000000..bbdd46323 --- /dev/null +++ b/.changeset/eff-1042-http-server-performance.md @@ -0,0 +1,10 @@ +--- +"effect": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +--- + +Improve HTTP server throughput by reducing routing, request handling, response +construction, and body encoding overhead. Add `Effect.withFiberSucceed` for +synchronously computing successful values from the current fiber. Copy pooled +byte views by their exact range when exposing `ArrayBuffer` values. diff --git a/.changeset/eff-1201-http-overhead.md b/.changeset/eff-1201-http-overhead.md new file mode 100644 index 000000000..84500451c --- /dev/null +++ b/.changeset/eff-1201-http-overhead.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Reduce HTTP server overhead: complete freshly created header maps in place in +`HttpServerResponse.setHeader` and `setHeaders`, compare static route prefixes +with a prepared `startsWith`, map `HttpApi` schema errors eagerly for completed +decoder results, and implement `Effect.cached` as a dedicated one-time memo +without time-to-live machinery. diff --git a/.changeset/eff-854-pg-connection-startup.md b/.changeset/eff-854-pg-connection-startup.md new file mode 100644 index 000000000..f9b351dd9 --- /dev/null +++ b/.changeset/eff-854-pg-connection-startup.md @@ -0,0 +1,19 @@ +--- +"@effect/sql-pg": patch +"effect": patch +--- + +Replace `@effect/sql-pg`'s `pg` runtime with a native PostgreSQL client. `PgConnection` and `PgPool` now handle connection setup, binary queries, prepared statements, pipelining, streaming, notifications, cancellation, and custom codecs. `PgConnection.listen` and `PgClient.listen` return scoped notification dequeues after PostgreSQL confirms the subscription. `PgClient` uses the native stack, and the legacy `fromPool`, `fromClient`, and `makeWith` constructors are removed. + +### Breaking changes + +- `fromPool`, `fromClient`, and `makeWith` are removed. Use `make` for a pool or `makeClient` for one connection. +- `PgClient.listen` returns a scoped `Effect, SqlError, Scope>` instead of a `Stream`. Acquisition completes after PostgreSQL confirms `LISTEN`, so notifications sent after it returns cannot be missed. +- `PgClientConfig.types` now accepts a `PgTypes.Registry` instead of `pg.CustomTypesConfig`. Plain object parameters are no longer inferred as JSON; wrap them with `sql.json`. +- Query strings must contain one statement. PostgreSQL's extended protocol rejects multi-statement strings. +- Results use the native binary codecs. In particular, `int8` decodes to `bigint`, `date` to a string, timestamps to Unix epoch milliseconds, and `bytea` or unknown OIDs to `Uint8Array`. `executeRaw` returns the native `PgConnection.Result` shape rather than `pg.Result`. +- Named prepared statements are enabled by default. Set `prepare: false` when using a pooler that cannot preserve prepared statements between queries. `Statement.unprepared` and `Statement.valuesUnprepared` use unnamed extended queries without adding entries to the prepared-statement cache. + +Inferred parameters stay permissive: strings bind untyped so the backend derives the type from the statement, and safe integers beyond the `int4` range bind as `int8`. + +Add `Pool.reserve` for exclusive access to a concurrent pool item, and fix waiter wakeups and capacity replacement after invalidation. diff --git a/.changeset/eff-961-node-socket-tls.md b/.changeset/eff-961-node-socket-tls.md new file mode 100644 index 000000000..f00fe3a71 --- /dev/null +++ b/.changeset/eff-961-node-socket-tls.md @@ -0,0 +1,11 @@ +--- +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +--- + +Add `NodeSocket.makeTls`, `NodeSocket.makeTlsChannel`, and `NodeSocket.layerTls` for TLS client connections. + +These mirror the existing `makeNet` family but dial `tls.connect`, so they take the full `tls.ConnectionOptions` set: +trust anchors (`ca`), client certificates (`cert` / `key`), ALPN protocols, and `servername`. The socket opens once the +handshake completes; a failed handshake fails with a `SocketOpenError`. diff --git a/.changeset/eff-963-node-socket-server-tls.md b/.changeset/eff-963-node-socket-server-tls.md new file mode 100644 index 000000000..dfac557a2 --- /dev/null +++ b/.changeset/eff-963-node-socket-server-tls.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +--- + +Add `NodeSocketServer.makeTls` and `NodeSocketServer.layerTls` for TLS socket servers. diff --git a/.changeset/eff-965-socket-upgrade.md b/.changeset/eff-965-socket-upgrade.md new file mode 100644 index 000000000..019a3c48c --- /dev/null +++ b/.changeset/eff-965-socket-upgrade.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +--- + +Add Socket.upgrade, for upgrading tcp sockets using STARTTLS diff --git a/.changeset/eff-969-dependency-updates.md b/.changeset/eff-969-dependency-updates.md new file mode 100644 index 000000000..2d249ad2c --- /dev/null +++ b/.changeset/eff-969-dependency-updates.md @@ -0,0 +1,8 @@ +--- +"@effect/doctest": patch +"@effect/sql-d1": patch +"@effect/sql-mysql2": patch +"@effect/sql-pglite": patch +--- + +Update production dependencies to their latest releases. diff --git a/.changeset/eff-972-deno-socket-server.md b/.changeset/eff-972-deno-socket-server.md new file mode 100644 index 000000000..87e1b20d5 --- /dev/null +++ b/.changeset/eff-972-deno-socket-server.md @@ -0,0 +1,11 @@ +--- +"@effect/platform-deno": patch +"@effect/platform-node-shared": patch +--- + +Use the node-shared socket server on Deno so accepted TCP connections support reader-scoped server TLS upgrades. + +### Breaking changes + +`DenoSocketServer.make` and `layer` now accept Node listen options. Use `host` instead of `hostname` for TCP and +`{ path }` instead of `{ transport: "unix", path }` for Unix sockets. Deno 2.8.3 or newer is now required. diff --git a/.changeset/eff-997-service-keys.md b/.changeset/eff-997-service-keys.md new file mode 100644 index 000000000..8ba1ef8c6 --- /dev/null +++ b/.changeset/eff-997-service-keys.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize core service and runtime identities under their owning module namespaces. diff --git a/.changeset/effect-eager-transform-arguments.md b/.changeset/effect-eager-transform-arguments.md new file mode 100644 index 000000000..05be690d2 --- /dev/null +++ b/.changeset/effect-eager-transform-arguments.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.fnUntracedEager` to pass the original function arguments to each transform after the current effect, matching `Effect.fn` and `Effect.fnUntraced`. diff --git a/.changeset/effect-scoped-service-restoration.md b/.changeset/effect-scoped-service-restoration.md new file mode 100644 index 000000000..ffc7b79ab --- /dev/null +++ b/.changeset/effect-scoped-service-restoration.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.updateServiceScoped` cleanup when an inner service provider has already completed. Closing the scope now preserves the service's absence instead of failing with a missing-service defect. diff --git a/.changeset/effect-unmatched-reason-preservation.md b/.changeset/effect-unmatched-reason-preservation.md new file mode 100644 index 000000000..38f3af546 --- /dev/null +++ b/.changeset/effect-unmatched-reason-preservation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve the original cause in `Effect.catchReason` and `Effect.catchReasons` when no nested reason matches and no fallback is provided. diff --git a/.changeset/effectable-class-override-delegation.md b/.changeset/effectable-class-override-delegation.md new file mode 100644 index 000000000..9a99f8e88 --- /dev/null +++ b/.changeset/effectable-class-override-delegation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effectable.Class` evaluation by delegating to its abstract `asEffect()` method. The method is called on the instance for each execution, preserving current receiver state and provided services. diff --git a/.changeset/effectable-mixin.md b/.changeset/effectable-mixin.md new file mode 100644 index 000000000..b087c5cde --- /dev/null +++ b/.changeset/effectable-mixin.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effectable.Mixin` to insert the Effect prototype into an existing class inheritance chain. The returned abstract class requires an `asEffect` method and derives its Effect type from that method through polymorphic `this`. diff --git a/.changeset/effectify-mapper-input-tuples.md b/.changeset/effectify-mapper-input-tuples.md new file mode 100644 index 000000000..9f6495308 --- /dev/null +++ b/.changeset/effectify-mapper-input-tuples.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the `onError` and `onSyncError` argument tuple types in `Effect.effectify` to include only caller inputs, excluding the synthesized callback. Mapper annotations that expected a callback slot must use the caller-input tuple instead. Runtime behavior is unchanged. diff --git a/.changeset/entity-proxy-client-codec-services.md b/.changeset/entity-proxy-client-codec-services.md new file mode 100644 index 000000000..a7e883600 --- /dev/null +++ b/.changeset/entity-proxy-client-codec-services.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `EntityProxyServer` handler layers to include client-side codec service requirements. diff --git a/.changeset/entity-test-client-fatal-defect-option.md b/.changeset/entity-test-client-fatal-defect-option.md new file mode 100644 index 000000000..403acc21d --- /dev/null +++ b/.changeset/entity-test-client-fatal-defect-option.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor the entity layer's `disableFatalDefects` option in `Entity.makeTestClient`. When enabled, a handler defect no longer fails other pending calls to the same entity ID. The failing call still reports its defect; omitted or false options retain fatal-defect behavior. diff --git a/.changeset/eventlog-authentication-forbidden-retry.md b/.changeset/eventlog-authentication-forbidden-retry.md new file mode 100644 index 000000000..44b83c11b --- /dev/null +++ b/.changeset/eventlog-authentication-forbidden-retry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Retry `EventLogRemote` writes and change streams when authentication returns `Forbidden`. diff --git a/.changeset/eventlog-empty-chunk-framing.md b/.changeset/eventlog-empty-chunk-framing.md new file mode 100644 index 000000000..c28935464 --- /dev/null +++ b/.changeset/eventlog-empty-chunk-framing.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Return one empty chunk when `ChunkedMessage.split` receives an empty `Uint8Array`. diff --git a/.changeset/exact-byte-size.md b/.changeset/exact-byte-size.md new file mode 100644 index 000000000..d3f07145d --- /dev/null +++ b/.changeset/exact-byte-size.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +--- + +Add `ByteSize` module and use it across the ecosystem diff --git a/.changeset/exact-http-file-body-length.md b/.changeset/exact-http-file-body-length.md new file mode 100644 index 000000000..72c8cd6b8 --- /dev/null +++ b/.changeset/exact-http-file-body-length.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Calculate `HttpBody.file`, `HttpBody.fileFromInfo`, and `HttpClientRequest.bodyFile` content lengths with exact bigint arithmetic and EOF clamping. diff --git a/.changeset/execution-plan-captured-predicate.md b/.changeset/execution-plan-captured-predicate.md new file mode 100644 index 000000000..5dbd29881 --- /dev/null +++ b/.changeset/execution-plan-captured-predicate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `ExecutionPlan.captureRequirements` provides captured services to effectful `while` predicates. diff --git a/.changeset/fair-matches-infer.md b/.changeset/fair-matches-infer.md new file mode 100644 index 000000000..8f7c4ab30 --- /dev/null +++ b/.changeset/fair-matches-infer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix contextual typing for `Match` tag and discriminator handler maps when handlers use `Effect.fn` or `Effect.fnUntraced`. diff --git a/.changeset/fair-workflows-suspend.md b/.changeset/fair-workflows-suspend.md new file mode 100644 index 000000000..0c9810dea --- /dev/null +++ b/.changeset/fair-workflows-suspend.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix parallel child workflows inside activities to dispatch before suspending, release activity resources during durable waits, and resume reliably when children complete during cleanup. diff --git a/.changeset/fetch-raw-stream-duplex.md b/.changeset/fetch-raw-stream-duplex.md new file mode 100644 index 000000000..ebf4b4415 --- /dev/null +++ b/.changeset/fetch-raw-stream-duplex.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Set `duplex` for raw Web stream request bodies in `FetchHttpClient`. diff --git a/.changeset/fiber-map-reentrant-replacement.md b/.changeset/fiber-map-reentrant-replacement.md new file mode 100644 index 000000000..6153f0ac2 --- /dev/null +++ b/.changeset/fiber-map-reentrant-replacement.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `FiberMap` losing track of fibers started under the same key by a replaced fiber's synchronous finalizer, ensuring they are interrupted when the map's scope closes. diff --git a/.changeset/fiber-registration-same-fiber.md b/.changeset/fiber-registration-same-fiber.md new file mode 100644 index 000000000..ee7c0ef2c --- /dev/null +++ b/.changeset/fiber-registration-same-fiber.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve an already registered fiber when `FiberHandle` or `FiberMap` registers it again with `onlyIfMissing: true`, instead of interrupting it and clearing the entry. diff --git a/.changeset/fifty-carrots-punch.md b/.changeset/fifty-carrots-punch.md new file mode 100644 index 000000000..4ba43f52d --- /dev/null +++ b/.changeset/fifty-carrots-punch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Exposed the platform specific pretty loggers separately. diff --git a/.changeset/filesystem-sink-undefined-flag.md b/.changeset/filesystem-sink-undefined-flag.md new file mode 100644 index 000000000..4c26793e9 --- /dev/null +++ b/.changeset/filesystem-sink-undefined-flag.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `FileSystem.sink` to retain its default write flag when `flag` is undefined. diff --git a/.changeset/five-spoons-visit.md b/.changeset/five-spoons-visit.md new file mode 100644 index 000000000..f3d0b9e51 --- /dev/null +++ b/.changeset/five-spoons-visit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Removed unused stderr option from Logger.consolePretty signature diff --git a/.changeset/fix-atom-http-stream-success-types.md b/.changeset/fix-atom-http-stream-success-types.md new file mode 100644 index 000000000..186f81b12 --- /dev/null +++ b/.changeset/fix-atom-http-stream-success-types.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Match `AtomHttpApi` query and mutation success types to the generated HTTP client, +including SSE, binary streams, and header-wrapped responses. Stream transport, +decoding, and SSE errors now appear in the stream's error channel instead of +`never`, so code that assumed a failure-free stream may need to handle them. +Runtime and serialization behavior are unchanged. diff --git a/.changeset/fix-atom-http-top-level-dispatch.md b/.changeset/fix-atom-http-top-level-dispatch.md new file mode 100644 index 000000000..d38505b77 --- /dev/null +++ b/.changeset/fix-atom-http-top-level-dispatch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `AtomHttpApi` query and mutation dispatch for top-level API groups. diff --git a/.changeset/fix-atom-query-zero-ttl.md b/.changeset/fix-atom-query-zero-ttl.md new file mode 100644 index 000000000..f29a0e995 --- /dev/null +++ b/.changeset/fix-atom-query-zero-ttl.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor explicit `timeToLive: 0` and `timeToLive: 0n` in `AtomRpc` and `AtomHttpApi` queries. Zero now opts out of the registry's default idle retention, matching other zero-duration inputs, so an unmounted query can be disposed and fetched again on remount. Omitting `timeToLive` still uses the registry default. diff --git a/.changeset/fix-atom-rpc-client-middleware-errors.md b/.changeset/fix-atom-rpc-client-middleware-errors.md new file mode 100644 index 000000000..882cdf455 --- /dev/null +++ b/.changeset/fix-atom-rpc-client-middleware-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `AtomRpc` mutation and query atoms to include client middleware errors in their result error types. diff --git a/.changeset/fix-atom-writable-fallback.md b/.changeset/fix-atom-writable-fallback.md new file mode 100644 index 000000000..711e5279a --- /dev/null +++ b/.changeset/fix-atom-writable-fallback.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Forward `Atom.withFallback` writes to the primary atom. diff --git a/.changeset/fix-cors-vary.md b/.changeset/fix-cors-vary.md new file mode 100644 index 000000000..b5c0fe58b --- /dev/null +++ b/.changeset/fix-cors-vary.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpMiddleware.cors` to preserve `Origin` and other required `Vary` dimensions. diff --git a/.changeset/fix-encoded-header-metadata.md b/.changeset/fix-encoded-header-metadata.md new file mode 100644 index 000000000..d44380628 --- /dev/null +++ b/.changeset/fix-encoded-header-metadata.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use body status and encoding defaults in `HttpApiSchema.encodeToWithHeaders`. diff --git a/.changeset/fix-filesystem-integer-precision.md b/.changeset/fix-filesystem-integer-precision.md new file mode 100644 index 000000000..70e684da5 --- /dev/null +++ b/.changeset/fix-filesystem-integer-precision.md @@ -0,0 +1,9 @@ +--- +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"effect": patch +--- + +Prevent precision loss in Node/Bun filesystem operations and `HttpPlatform` file responses. diff --git a/.changeset/fix-http-platform-file-ranges.md b/.changeset/fix-http-platform-file-ranges.md new file mode 100644 index 000000000..87b522ab5 --- /dev/null +++ b/.changeset/fix-http-platform-file-ranges.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Clamp HttpPlatform file response ranges to the file size so Content-Length matches the bytes available. Oversized reads stop at EOF, and offsets at or past EOF return an empty body with Content-Length 0. Apply the same clamping to the default Web file response implementation. diff --git a/.changeset/fix-httpapi-form-responses.md b/.changeset/fix-httpapi-form-responses.md new file mode 100644 index 000000000..9e9f6f333 --- /dev/null +++ b/.changeset/fix-httpapi-form-responses.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApiClient` decoding form-urlencoded responses. diff --git a/.changeset/fix-httpapi-test-pre-response-handlers.md b/.changeset/fix-httpapi-test-pre-response-handlers.md new file mode 100644 index 000000000..d1b399851 --- /dev/null +++ b/.changeset/fix-httpapi-test-pre-response-handlers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Run registered pre-response handlers before `HttpApiTest` returns responses. diff --git a/.changeset/fix-httpapi-url-builder-base-path.md b/.changeset/fix-httpapi-url-builder-base-path.md new file mode 100644 index 000000000..9733d4db0 --- /dev/null +++ b/.changeset/fix-httpapi-url-builder-base-path.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApiClient.urlBuilder` dropping base URL pathnames. diff --git a/.changeset/fix-json-schema-percent-references.md b/.changeset/fix-json-schema-percent-references.md new file mode 100644 index 000000000..01c6a3c47 --- /dev/null +++ b/.changeset/fix-json-schema-percent-references.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `JsonPointer.parseUriFragment` and `JsonPointer.formatUriFragment` for converting RFC 6901 URI fragments, and use them to preserve percent-encoded definition names in exported JSON Schema references. JSON Schema compilation now rejects malformed local definition references returned by `toJsonSchema` hooks. Such hooks must percent-encode characters that URI fragments do not permit, for example `%` as `%25` and `#` as `%23`. diff --git a/.changeset/fix-mime-parameter-normalization.md b/.changeset/fix-mime-parameter-normalization.md new file mode 100644 index 000000000..545607aac --- /dev/null +++ b/.changeset/fix-mime-parameter-normalization.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize MIME type parameters and whitespace in `Mime.getAllExtensions`. diff --git a/.changeset/fix-negative-file-seek.md b/.changeset/fix-negative-file-seek.md new file mode 100644 index 000000000..50227499b --- /dev/null +++ b/.changeset/fix-negative-file-seek.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/platform-node-shared": patch +"@effect/platform-deno": patch +--- + +On Node and Deno, `FileSystem.File.seek` now rejects negative resulting positions with a `BadArgument` platform error, leaving the cursor unchanged. Its return type is now `Effect`. diff --git a/.changeset/fix-openapi-client-form-url-encoding.md b/.changeset/fix-openapi-client-form-url-encoding.md new file mode 100644 index 000000000..99c7fd034 --- /dev/null +++ b/.changeset/fix-openapi-client-form-url-encoding.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Encode form-urlencoded request bodies in generated HTTP clients. diff --git a/.changeset/fix-openapi-client-multipart-records.md b/.changeset/fix-openapi-client-multipart-records.md new file mode 100644 index 000000000..c4c36d038 --- /dev/null +++ b/.changeset/fix-openapi-client-multipart-records.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Encode multipart record payloads as form data in generated schema-backed clients. diff --git a/.changeset/fix-openapi-endpoint-transform-order.md b/.changeset/fix-openapi-endpoint-transform-order.md new file mode 100644 index 000000000..ff1335312 --- /dev/null +++ b/.changeset/fix-openapi-endpoint-transform-order.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Apply endpoint OpenAPI overrides and transforms after schema generation. diff --git a/.changeset/fix-openapi-stream-transform-client.md b/.changeset/fix-openapi-stream-transform-client.md new file mode 100644 index 000000000..13f781b60 --- /dev/null +++ b/.changeset/fix-openapi-stream-transform-client.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Apply `transformClient` when generated binary and SSE streams are consumed, preserving lazy stream construction. diff --git a/.changeset/fix-otel-logger-clock-skew.md b/.changeset/fix-otel-logger-clock-skew.md deleted file mode 100644 index 8e6ae2967..000000000 --- a/.changeset/fix-otel-logger-clock-skew.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@effect/opentelemetry": patch ---- - -Use monotonic clock for log timestamps to match span timestamps. - -The Logger used `Date.now()` (wall clock) for log `timestamp` while the Tracer used `clock.currentTimeNanosUnsafe()` (monotonic clock) for span `startTime`. This caused logs to appear before their parent span due to clock drift between the two sources. Both now use the same monotonic clock via `nanosToHrTime(clock.currentTimeNanosUnsafe())`. diff --git a/.changeset/fix-prompt-date-tab-buffer.md b/.changeset/fix-prompt-date-tab-buffer.md new file mode 100644 index 000000000..43f07df7a --- /dev/null +++ b/.changeset/fix-prompt-date-tab-buffer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Prompt.date` carrying typed digits into the next field when pressing Tab, including when navigation wraps. diff --git a/.changeset/fix-react-ref-switch.md b/.changeset/fix-react-ref-switch.md new file mode 100644 index 000000000..0044f5227 --- /dev/null +++ b/.changeset/fix-react-ref-switch.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-react": patch +--- + +Ensure `useAtomRef` updates after switching refs when the new value matches the previous ref's value. diff --git a/.changeset/fix-reactivity-duplicate-keys.md b/.changeset/fix-reactivity-duplicate-keys.md new file mode 100644 index 000000000..2f0111087 --- /dev/null +++ b/.changeset/fix-reactivity-duplicate-keys.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent `Reactivity.query` cleanup from failing when keys are repeated. diff --git a/.changeset/fix-read-alloc-invalid-sizes.md b/.changeset/fix-read-alloc-invalid-sizes.md new file mode 100644 index 000000000..b656ca94c --- /dev/null +++ b/.changeset/fix-read-alloc-invalid-sizes.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-node-shared": patch +"@effect/platform-deno": patch +--- + +Fix `File.readAlloc` on Node and Deno to fail with `PlatformError` (`BadArgument`) for negative, fractional, non-finite, or unallocatable sizes without moving the cursor. Zero-size reads continue to return `Option.none()` without moving the cursor. diff --git a/.changeset/fix-sink-flatmap-leftovers.md b/.changeset/fix-sink-flatmap-leftovers.md new file mode 100644 index 000000000..0afe46e74 --- /dev/null +++ b/.changeset/fix-sink-flatmap-leftovers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve pending leftovers when a `Sink.flatMap` continuation completes without consuming input. diff --git a/.changeset/fix-sse-mixed-line-endings.md b/.changeset/fix-sse-mixed-line-endings.md new file mode 100644 index 000000000..c1b272729 --- /dev/null +++ b/.changeset/fix-sse-mixed-line-endings.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve SSE events with mixed line endings. diff --git a/.changeset/fix-static-head-range.md b/.changeset/fix-static-head-range.md new file mode 100644 index 000000000..915c8deb8 --- /dev/null +++ b/.changeset/fix-static-head-range.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore Range headers on non-GET requests in HttpStaticServer. diff --git a/.changeset/fix-static-oversized-ranges.md b/.changeset/fix-static-oversized-ranges.md new file mode 100644 index 000000000..583891479 --- /dev/null +++ b/.changeset/fix-static-oversized-ranges.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Parse HttpStaticServer byte range integers exactly, including values above Number.MAX_SAFE_INTEGER. Oversized starts now return 416 with Content-Range instead of falling back to 200. Oversized ends clamp to the last byte, and oversized suffixes return the whole file as 206. diff --git a/.changeset/fix-stream-wrapper-status.md b/.changeset/fix-stream-wrapper-status.md new file mode 100644 index 000000000..9568a5cfd --- /dev/null +++ b/.changeset/fix-stream-wrapper-status.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApiBuilder` ignoring the status annotation on a `HttpApiSchema.WithHeaders` wrapper around a streaming success, which defected when the wrapper and inner statuses differed. diff --git a/.changeset/fix-toml-array-subtables.md b/.changeset/fix-toml-array-subtables.md new file mode 100644 index 000000000..07d3edbae --- /dev/null +++ b/.changeset/fix-toml-array-subtables.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Toml.parse` rejecting child tables in separate array-of-tables entries. diff --git a/.changeset/fix-tool-result-serialization.md b/.changeset/fix-tool-result-serialization.md new file mode 100644 index 000000000..65accd0f1 --- /dev/null +++ b/.changeset/fix-tool-result-serialization.md @@ -0,0 +1,13 @@ +--- +"effect": patch +--- + +Fix tool result serialization to select the codec using `isFailure` and preserve `encodedResult` through `Response.AllParts` round trips. + +Add `Tool.failureResultSchema(tool)` and `Tool.ExecutionFailure` to handle user failures, `AiError`, and denied or interrupted calls consistently. Also export `HttpRequestDetails` and `HttpResponseDetails` from `AiError`; the `Response` exports remain available. + +### Breaking changes + +- Stored results must match the selected schema. With success `Schema.Number` and failure `Schema.NumberFromString`, migrate failed results from `404` to `"404"`. +- `Response.ToolResultPart` returns `Schema.Codec` instead of `Schema.decodeTo`. Update annotations that depend on the old type. +- `Tool.FailureResult` and `Tool.Result`, including their encoded variants, now include `Tool.ExecutionFailure` in both failure modes. Handle it when narrowing failed results. diff --git a/.changeset/fix-vue-ref-switch.md b/.changeset/fix-vue-ref-switch.md new file mode 100644 index 000000000..b804de834 --- /dev/null +++ b/.changeset/fix-vue-ref-switch.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-vue": patch +--- + +Fix `useAtomRef` returning a stale value after switching refs. diff --git a/.changeset/fix-yaml-folded-scalars.md b/.changeset/fix-yaml-folded-scalars.md new file mode 100644 index 000000000..eeb3dfef2 --- /dev/null +++ b/.changeset/fix-yaml-folded-scalars.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix folded YAML scalars to preserve paragraph and indentation breaks. diff --git a/.changeset/flat-pipelines-share.md b/.changeset/flat-pipelines-share.md new file mode 100644 index 000000000..271699472 --- /dev/null +++ b/.changeset/flat-pipelines-share.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Set the default `multiplexConcurrency` to 32 when PostgreSQL connection multiplexing is enabled. Set a lower value explicitly to limit how many statements share each connection. diff --git a/.changeset/formatter-defined-error-causes.md b/.changeset/formatter-defined-error-causes.md new file mode 100644 index 000000000..51f66c0f4 --- /dev/null +++ b/.changeset/formatter-defined-error-causes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve defined falsy Error causes (`0`, `false`, `""`, `null`, `0n`, and `NaN`) in `Formatter.format` output. Missing and explicitly `undefined` causes remain omitted. diff --git a/.changeset/fresh-pools-rotate.md b/.changeset/fresh-pools-rotate.md new file mode 100644 index 000000000..492f3ef41 --- /dev/null +++ b/.changeset/fresh-pools-rotate.md @@ -0,0 +1,6 @@ +--- +"@effect/sql-pg": patch +--- + +Allow each PostgreSQL pool connection to complete its first checkout before applying `connectionTTL`, so a zero TTL +disables connection reuse without entering an invalidate/reconnect loop. diff --git a/.changeset/fuzzy-classes-compare.md b/.changeset/fuzzy-classes-compare.md new file mode 100644 index 000000000..eb54cb7f0 --- /dev/null +++ b/.changeset/fuzzy-classes-compare.md @@ -0,0 +1,8 @@ +--- +"effect": patch +--- + +Fix equivalence derivation for schema class APIs by adopting the equivalence of +their declared fields. Class declarations previously fell back to +`Equal.equals`, which also compared runtime properties outside the schema and +could make field-equivalent class instances compare as unequal. diff --git a/.changeset/graph-bellman-ford-infinite-cycle-barriers.md b/.changeset/graph-bellman-ford-infinite-cycle-barriers.md new file mode 100644 index 000000000..e016e3903 --- /dev/null +++ b/.changeset/graph-bellman-ford-infinite-cycle-barriers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.bellmanFord` reporting a negative cycle as affecting a target across an impassable, positive-infinite-weight edge. Targets separated from the cycle by such edges now retain their finite shortest path or remain unreachable, while targets reachable from the cycle through finite-weight edges still report an error. diff --git a/.changeset/hashmap-collision-entries.md b/.changeset/hashmap-collision-entries.md new file mode 100644 index 000000000..fa077d00b --- /dev/null +++ b/.changeset/hashmap-collision-entries.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent `HashMap` iterators from exposing mutable internal collision entries. diff --git a/.changeset/hashring-exclusion-endpoint.md b/.changeset/hashring-exclusion-endpoint.md new file mode 100644 index 000000000..3e54251e1 --- /dev/null +++ b/.changeset/hashring-exclusion-endpoint.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HashRing.getShards` skipping an eligible node at the first ring position when other nodes have reached their allocation quota. diff --git a/.changeset/headers-redacted-name-case.md b/.changeset/headers-redacted-name-case.md new file mode 100644 index 000000000..70d6358da --- /dev/null +++ b/.changeset/headers-redacted-name-case.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Compare header names case-insensitively in `Headers.isRedactedName`. diff --git a/.changeset/headers-stateful-patterns.md b/.changeset/headers-stateful-patterns.md new file mode 100644 index 000000000..db5ebef9e --- /dev/null +++ b/.changeset/headers-stateful-patterns.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Headers.redact` and `Headers.isRedactedName` skipping matches when a redaction pattern is a global or sticky regular expression. diff --git a/.changeset/http-client-catch-response-result.md b/.changeset/http-client-catch-response-result.md new file mode 100644 index 000000000..29bd409fb --- /dev/null +++ b/.changeset/http-client-catch-response-result.md @@ -0,0 +1,8 @@ +--- +"effect": patch +--- + +Constrain the data-first `HttpClient.catch(client, recover)` overload to recover with +`HttpClientResponse` values, matching the data-last overload. Callbacks returning other +success types are now rejected; use `Effect.catch` on the result of `client.execute(request)` +to recover to arbitrary values. diff --git a/.changeset/http-client-redirect-preprocessing-recovery.md b/.changeset/http-client-redirect-preprocessing-recovery.md new file mode 100644 index 000000000..70c1b6e20 --- /dev/null +++ b/.changeset/http-client-redirect-preprocessing-recovery.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpClient.followRedirects` bypassing response-level recovery when request preprocessing fails. diff --git a/.changeset/http-head-stream-scope.md b/.changeset/http-head-stream-scope.md new file mode 100644 index 000000000..93f35c109 --- /dev/null +++ b/.changeset/http-head-stream-scope.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Close request scopes for streaming HEAD responses. diff --git a/.changeset/http-response-content-length.md b/.changeset/http-response-content-length.md new file mode 100644 index 000000000..55326a913 --- /dev/null +++ b/.changeset/http-response-content-length.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `Content-Length` headers in `HttpServerResponse.fromWeb`. diff --git a/.changeset/http-router-normalized-prefix.md b/.changeset/http-router-normalized-prefix.md new file mode 100644 index 000000000..a19f44155 --- /dev/null +++ b/.changeset/http-router-normalized-prefix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize router prefixes before removing them from handler request URLs. diff --git a/.changeset/http-runner-path-boundary.md b/.changeset/http-runner-path-boundary.md new file mode 100644 index 000000000..412f194b1 --- /dev/null +++ b/.changeset/http-runner-path-boundary.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `HttpRunner` HTTP and WebSocket client URLs adding an extra leading slash to slash-prefixed paths. Insert the address/path separator only when it is missing, preserving intentional leading and interior slashes. + +This path correction is normally masked by router normalization, but prevents route misses for non-root paths when duplicate-slash normalization is disabled. Applications that compensate for the extra slash may need to remove that compensation. Router defaults and shared trailing-slash handling are unchanged. diff --git a/.changeset/http-web-handler-cold-start.md b/.changeset/http-web-handler-cold-start.md new file mode 100644 index 000000000..5fb4aabd0 --- /dev/null +++ b/.changeset/http-web-handler-cold-start.md @@ -0,0 +1,8 @@ +--- +"effect": patch +--- + +Reduce cold start cost of `HttpRouter` and `HttpEffect` web handlers. + +- `HttpServerRespondable` no longer imports `Schema` to detect schema errors, which removes the Schema modules from bundles that do not otherwise use them (about 23% of a minimal `HttpRouter` bundle). +- `HttpRouter.toWebHandler`, `HttpEffect.toWebHandlerLayer` and `HttpEffect.toWebHandlerLayerWith` now build the layer immediately instead of on the first request. A failed build never surfaces as an unhandled rejection; every request rejects with the build error instead. diff --git a/.changeset/httpapi-middleware-error-dedupe.md b/.changeset/httpapi-middleware-error-dedupe.md new file mode 100644 index 000000000..76d74654b --- /dev/null +++ b/.changeset/httpapi-middleware-error-dedupe.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApiMiddleware`-declared errors being duplicated and mis-encoded. diff --git a/.changeset/httpapi-sse-decode-options.md b/.changeset/httpapi-sse-decode-options.md new file mode 100644 index 000000000..eb84e9032 --- /dev/null +++ b/.changeset/httpapi-sse-decode-options.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow generated `HttpApiClient` methods and `AtomHttpApi` queries and mutations to accept native SSE decode options per call through the request's `sseOptions` field. diff --git a/.changeset/indexeddb-out-of-line-primary-keys.md b/.changeset/indexeddb-out-of-line-primary-keys.md new file mode 100644 index 000000000..30b66a4e5 --- /dev/null +++ b/.changeset/indexeddb-out-of-line-primary-keys.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Preserve out-of-line primary keys in IndexedDB query results, including secondary-index reads. diff --git a/.changeset/indexeddb-stream-query-limits.md b/.changeset/indexeddb-stream-query-limits.md new file mode 100644 index 000000000..c12593165 --- /dev/null +++ b/.changeset/indexeddb-stream-query-limits.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Keep streamed IndexedDB selects within their query limits. diff --git a/.changeset/isolate-unencodable-hydration.md b/.changeset/isolate-unencodable-hydration.md new file mode 100644 index 000000000..fa72c498e --- /dev/null +++ b/.changeset/isolate-unencodable-hydration.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent unencodable atom values from aborting dehydration of the rest of an atom registry. diff --git a/.changeset/large-postgres-messages.md b/.changeset/large-postgres-messages.md new file mode 100644 index 000000000..a4fdd57f7 --- /dev/null +++ b/.changeset/large-postgres-messages.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Add a `maxMessageSize` connection option so PostgreSQL clients can receive backend messages larger than the 16 MiB default. diff --git a/.changeset/layer-error-observer-types.md b/.changeset/layer-error-observer-types.md new file mode 100644 index 000000000..24bcdb3e2 --- /dev/null +++ b/.changeset/layer-error-observer-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Layer.tapError` and `Layer.tapCause` to require observers that accept the source layer's complete error type. diff --git a/.changeset/layer-span-trace-options.md b/.changeset/layer-span-trace-options.md new file mode 100644 index 000000000..d0cb11a3b --- /dev/null +++ b/.changeset/layer-span-trace-options.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor `captureStackTrace` in both forms of `Layer.withSpan`. Layer construction diagnostics previously reported a location inside `Layer.ts` instead of the `withSpan` call site, and ignored `captureStackTrace: false` or a supplied lazy stack. diff --git a/.changeset/layermap-preloaded-acquisition-errors.md b/.changeset/layermap-preloaded-acquisition-errors.md new file mode 100644 index 000000000..402864fb9 --- /dev/null +++ b/.changeset/layermap-preloaded-acquisition-errors.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Preserve resource acquisition errors on `LayerMap.Service` when `preload: true` is set. The yielded service instance and its `get`, `contextEffect`, and `contextEffectOption` accessors now retain the resource error type because a resource can fail when reacquired, even if preloading succeeded. + +Consumers that assumed these accessors had a `never` error must handle the resource error. Runtime behavior is unchanged. diff --git a/.changeset/lazy-bun-redis-import.md b/.changeset/lazy-bun-redis-import.md new file mode 100644 index 000000000..9255d6d97 --- /dev/null +++ b/.changeset/lazy-bun-redis-import.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Load Bun's `RedisClient` lazily in `BunRedis`. diff --git a/.changeset/lazy-undici-loading.md b/.changeset/lazy-undici-loading.md new file mode 100644 index 000000000..9c767e4ea --- /dev/null +++ b/.changeset/lazy-undici-loading.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Defer loading Undici until an Undici-backed layer is acquired, preventing Node HTTP client imports from replacing Node's global fetch dispatcher. Import Undici APIs from `@effect/platform-node/Undici` instead of the package root. diff --git a/.changeset/libsql-transaction-client-isolation.md b/.changeset/libsql-transaction-client-isolation.md new file mode 100644 index 000000000..186ff4348 --- /dev/null +++ b/.changeset/libsql-transaction-client-isolation.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-libsql": patch +--- + +Isolate transaction contexts between separately created libSQL clients. diff --git a/.changeset/logger-complete-file-writes.md b/.changeset/logger-complete-file-writes.md new file mode 100644 index 000000000..0c2c6983d --- /dev/null +++ b/.changeset/logger-complete-file-writes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Logger.toFile` dropping the remainder of a log batch when a successful file write writes only part of the buffer. File logging now uses the complete-write contract; write errors continue to be ignored. diff --git a/.changeset/mcp-http-resource-template-origins.md b/.changeset/mcp-http-resource-template-origins.md new file mode 100644 index 000000000..b94ed9b0d --- /dev/null +++ b/.changeset/mcp-http-resource-template-origins.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `McpServer` HTTP resource templates failing to resolve. diff --git a/.changeset/mcp-prompt-decoded-parameter-types.md b/.changeset/mcp-prompt-decoded-parameter-types.md new file mode 100644 index 000000000..180a6f1ad --- /dev/null +++ b/.changeset/mcp-prompt-decoded-parameter-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `McpServer.registerPrompt` callback types to use decoded prompt parameters. diff --git a/.changeset/mcp-structured-content-object.md b/.changeset/mcp-structured-content-object.md new file mode 100644 index 000000000..d3d85d737 --- /dev/null +++ b/.changeset/mcp-structured-content-object.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +McpServer no longer sends `null` or array tool results as `structuredContent`, which MCP requires to be a JSON object. diff --git a/.changeset/memory-message-storage-clear-controls.md b/.changeset/memory-message-storage-clear-controls.md new file mode 100644 index 000000000..d4f524b69 --- /dev/null +++ b/.changeset/memory-message-storage-clear-controls.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove queued control envelopes when clearing an address from in-memory message storage. diff --git a/.changeset/metric-attribute-order-identity.md b/.changeset/metric-attribute-order-identity.md new file mode 100644 index 000000000..608c6d396 --- /dev/null +++ b/.changeset/metric-attribute-order-identity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure metrics with equal attributes share a series regardless of attribute insertion order. diff --git a/.changeset/metric-registry-isolation.md b/.changeset/metric-registry-isolation.md new file mode 100644 index 000000000..68aa520b5 --- /dev/null +++ b/.changeset/metric-registry-isolation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix metrics reused across different `MetricRegistry` services to read and update the active registry while preserving each registry's values when revisited. diff --git a/.changeset/model-field-option-undefined.md b/.changeset/model-field-option-undefined.md new file mode 100644 index 000000000..3bca7a691 --- /dev/null +++ b/.changeset/model-field-option-undefined.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Model.FieldOption` to preserve omitted variants. diff --git a/.changeset/mssql-binary-parameters.md b/.changeset/mssql-binary-parameters.md new file mode 100644 index 000000000..9ceac54a6 --- /dev/null +++ b/.changeset/mssql-binary-parameters.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-mssql": patch +--- + +Fix automatic binary parameter binding in `@effect/sql-mssql`. diff --git a/.changeset/mssql-ntlm-domain.md b/.changeset/mssql-ntlm-domain.md new file mode 100644 index 000000000..eaa55815f --- /dev/null +++ b/.changeset/mssql-ntlm-domain.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-mssql": patch +--- + +Forward the configured domain to the SQL Server driver so NTLM clients can be constructed with a supplied domain. diff --git a/.changeset/multipart-streamed-part-guard.md b/.changeset/multipart-streamed-part-guard.md new file mode 100644 index 000000000..f593fc224 --- /dev/null +++ b/.changeset/multipart-streamed-part-guard.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Multipart.isStreamPart` to recognize only a text `Field` or streamed `File`, while preserving `Multipart.isPart` for all branded multipart parts, including `PersistedFile` values. diff --git a/.changeset/mutable-arrays-preserve.md b/.changeset/mutable-arrays-preserve.md new file mode 100644 index 000000000..17e37d48a --- /dev/null +++ b/.changeset/mutable-arrays-preserve.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.mutable` to preserve array and tuple metadata and reject node-level encodings. diff --git a/.changeset/mutable-list-bulk-prepend-tail.md b/.changeset/mutable-list-bulk-prepend-tail.md new file mode 100644 index 000000000..61e8ed55a --- /dev/null +++ b/.changeset/mutable-list-bulk-prepend-tail.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve values added to an empty `MutableList` by `prependAll` when appending more values. diff --git a/.changeset/name-random-services.md b/.changeset/name-random-services.md new file mode 100644 index 000000000..2fef9ba2e --- /dev/null +++ b/.changeset/name-random-services.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Export the `Random.Random` service interface and `Metric.MetricRegistry` type so custom service implementations can be annotated without accessing `Context.Reference` phantom types. diff --git a/.changeset/net-address-values.md b/.changeset/net-address-values.md new file mode 100644 index 000000000..b6db60e64 --- /dev/null +++ b/.changeset/net-address-values.md @@ -0,0 +1,14 @@ +--- +"effect": patch +"@effect/sql-pg": patch +"@effect/platform-node": patch +"@effect/platform-node-shared": patch +"@effect/platform-deno": patch +"@effect/platform-bun": patch +--- + +Add `NetAddress` under `effect/unstable/net` for MAC, IP, internet socket, and Unix socket addresses, with checked parsing, schemas, equality, canonical string serialization, and URL formatting. Companion modules `IpInterface` and `IpNetwork` represent IP interfaces and CIDR networks. + +HTTP and socket servers now expose `NetAddress.SocketAddress`. Replace TCP `hostname` access with `NetAddress.formatIp(address.address)` and use `UnixPathAddress.path` for Unix sockets. URL helpers bracket IPv6 addresses and reject scoped IPv6. Bun and Deno HTTP server layers can now fail with `ServeError` when listener address conversion fails. + +PostgreSQL `inet` values now use `IpInterface`; `cidr` values use `IpNetwork` and reject addresses with host bits set. diff --git a/.changeset/ninety-books-sit.md b/.changeset/ninety-books-sit.md new file mode 100644 index 000000000..8f347d035 --- /dev/null +++ b/.changeset/ninety-books-sit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Terminate the `Stream.fromEventListener` stream after one item if `once: true`. diff --git a/.changeset/node-http-status-text.md b/.changeset/node-http-status-text.md new file mode 100644 index 000000000..b83133d66 --- /dev/null +++ b/.changeset/node-http-status-text.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Forward custom and empty status text from NodeHttpServer responses. diff --git a/.changeset/node-response-preserve-bytes.md b/.changeset/node-response-preserve-bytes.md new file mode 100644 index 000000000..add6daba8 --- /dev/null +++ b/.changeset/node-response-preserve-bytes.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Preserve response bytes when selecting the text reader in Node HTTP clients. diff --git a/.changeset/node-sink-cancel-drain.md b/.changeset/node-sink-cancel-drain.md new file mode 100644 index 000000000..257aead2e --- /dev/null +++ b/.changeset/node-sink-cancel-drain.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Prevent Node sinks from submitting buffered writes after interruption while waiting for writable backpressure. diff --git a/.changeset/node-sink-finalization-errors.md b/.changeset/node-sink-finalization-errors.md new file mode 100644 index 000000000..308442c81 --- /dev/null +++ b/.changeset/node-sink-finalization-errors.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Fix `NodeSink` hanging when a writable reports an error during finalization. diff --git a/.changeset/node-stream-buffer-size.md b/.changeset/node-stream-buffer-size.md new file mode 100644 index 000000000..82b55372d --- /dev/null +++ b/.changeset/node-stream-buffer-size.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Remove the ignored `bufferSize` option from `NodeStream`. diff --git a/.changeset/node-watch-relative.md b/.changeset/node-watch-relative.md new file mode 100644 index 000000000..792683eba --- /dev/null +++ b/.changeset/node-watch-relative.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Fix watch event classification for targets outside the current working directory. diff --git a/.changeset/node-worker-unsafe-send-envelope.md b/.changeset/node-worker-unsafe-send-envelope.md new file mode 100644 index 000000000..999baa91a --- /dev/null +++ b/.changeset/node-worker-unsafe-send-envelope.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Preserve reply payloads sent through `NodeWorkerRunner.sendUnsafe`. diff --git a/.changeset/node-writeall-empty.md b/.changeset/node-writeall-empty.md new file mode 100644 index 000000000..f503e0d70 --- /dev/null +++ b/.changeset/node-writeall-empty.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Allow `File.writeAll` to accept empty buffers. diff --git a/.changeset/normalize-collection-counts.md b/.changeset/normalize-collection-counts.md new file mode 100644 index 000000000..878b66795 --- /dev/null +++ b/.changeset/normalize-collection-counts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize numeric collection and batch counts across `Stream`, `Channel`, `Sink`, `MutableList`, `RequestResolver`, `Queue`, `TxQueue`, `PubSub`, and `HashRing`, preventing fractional, `NaN`, and non-positive counts from producing incorrect output, exceptions, waits for the wrong batch size, or non-terminating pulls. diff --git a/.changeset/number-remainder-negative-zero-dividend.md b/.changeset/number-remainder-negative-zero-dividend.md new file mode 100644 index 000000000..054499696 --- /dev/null +++ b/.changeset/number-remainder-negative-zero-dividend.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Number.remainder` to preserve negative-zero dividends with ordinary finite divisors. diff --git a/.changeset/olive-queues-persist.md b/.changeset/olive-queues-persist.md new file mode 100644 index 000000000..ee9e644cd --- /dev/null +++ b/.changeset/olive-queues-persist.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve `PersistedQueue` reliability across SQL, Redis, and memory stores. Retry policy now lives on `make()`, attempts count on claim, retries follow a `Schedule`, and exhausted or undecodable elements are dead-lettered. Add retention cleanup, durable acknowledgement retries, storage schema fixes, local poll wakeups, and fixes for the memory take race and Redis dedup growth. diff --git a/.changeset/openai-image-strings.md b/.changeset/openai-image-strings.md new file mode 100644 index 000000000..5a8aae24e --- /dev/null +++ b/.changeset/openai-image-strings.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +Preserve image data supplied as strings in OpenAI Responses and OpenAI-compatible chat requests. diff --git a/.changeset/openai-optional-sequence-number.md b/.changeset/openai-optional-sequence-number.md new file mode 100644 index 000000000..ffd414cd8 --- /dev/null +++ b/.changeset/openai-optional-sequence-number.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Allow OpenAI-compatible Responses API stream events to omit `sequence_number`. diff --git a/.changeset/openapi-omitted-additional-properties.md b/.changeset/openapi-omitted-additional-properties.md new file mode 100644 index 000000000..4af8dc5ed --- /dev/null +++ b/.changeset/openapi-omitted-additional-properties.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Preserve omitted `additionalProperties` when generating schemas from OpenAPI documents. diff --git a/.changeset/openrouter-encrypted-tool-finish.md b/.changeset/openrouter-encrypted-tool-finish.md new file mode 100644 index 000000000..4f5006ac1 --- /dev/null +++ b/.changeset/openrouter-encrypted-tool-finish.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Report `tool-calls` instead of `unknown` when an OpenRouter stream ends with `stop` after emitting tool calls and nonempty encrypted reasoning. diff --git a/.changeset/openrouter-strict-json-schema.md b/.changeset/openrouter-strict-json-schema.md new file mode 100644 index 000000000..736612759 --- /dev/null +++ b/.changeset/openrouter-strict-json-schema.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Exclude the provider-only `strictJsonSchema` option from outgoing OpenRouter request bodies while preserving structured-output and tool strictness settings. diff --git a/.changeset/opentelemetry-delta-interval-starts.md b/.changeset/opentelemetry-delta-interval-starts.md new file mode 100644 index 000000000..c536f6823 --- /dev/null +++ b/.changeset/opentelemetry-delta-interval-starts.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Use collection interval starts for OpenTelemetry delta metrics. diff --git a/.changeset/optic-projection-replacement.md b/.changeset/optic-projection-replacement.md new file mode 100644 index 000000000..3523631ae --- /dev/null +++ b/.changeset/optic-projection-replacement.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `Optic.pick` and `Optic.omit` delete focused optional fields omitted from a replacement. diff --git a/.changeset/optic-string-index-delete.md b/.changeset/optic-string-index-delete.md new file mode 100644 index 000000000..8468eda4a --- /dev/null +++ b/.changeset/optic-string-index-delete.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Optic.optionalKey` to splice tuple elements selected by string indices. diff --git a/.changeset/order-consumed-criteria.md b/.changeset/order-consumed-criteria.md new file mode 100644 index 000000000..85dd26e64 --- /dev/null +++ b/.changeset/order-consumed-criteria.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Order.combineAll` consuming one-shot iterables after the first comparison. diff --git a/.changeset/otlp-disabled-batch-flush.md b/.changeset/otlp-disabled-batch-flush.md new file mode 100644 index 000000000..9ab34b05a --- /dev/null +++ b/.changeset/otlp-disabled-batch-flush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix disabled OTLP batching to skip empty exports and avoid resending buffered items. diff --git a/.changeset/otlp-tracer-span-performance.md b/.changeset/otlp-tracer-span-performance.md new file mode 100644 index 000000000..2319594b7 --- /dev/null +++ b/.changeset/otlp-tracer-span-performance.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Speed up `OtlpTracer` span creation and export. Spans now allocate identifiers, attributes, and events lazily, and `Encoding.randomHex` produces flat strings for 16 and 32 character identifiers so serialization no longer flattens ropes. diff --git a/.changeset/partitioned-semaphore-stale-cleanup.md b/.changeset/partitioned-semaphore-stale-cleanup.md new file mode 100644 index 000000000..72b3fba03 --- /dev/null +++ b/.changeset/partitioned-semaphore-stale-cleanup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `PartitionedSemaphore` leaving a new waiter suspended when a previously resumed waiter for the same partition is interrupted before its acquisition completes. diff --git a/.changeset/persisted-cache-lookup-throw.md b/.changeset/persisted-cache-lookup-throw.md new file mode 100644 index 000000000..81b54be07 --- /dev/null +++ b/.changeset/persisted-cache-lookup-throw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Persist synchronous defects thrown by `PersistedCache` lookups. diff --git a/.changeset/pg-explicit-ssl-precedence.md b/.changeset/pg-explicit-ssl-precedence.md new file mode 100644 index 000000000..e52b6246f --- /dev/null +++ b/.changeset/pg-explicit-ssl-precedence.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Honor explicit `ssl` options when PostgreSQL URLs use `sslmode=prefer` or `sslmode=allow`. diff --git a/.changeset/pglite-json-string-values.md b/.changeset/pglite-json-string-values.md new file mode 100644 index 000000000..f30fa9521 --- /dev/null +++ b/.changeset/pglite-json-string-values.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pglite": patch +--- + +Preserve string values passed to `sql.json` when using PGlite. diff --git a/.changeset/pipeline-root-stdin.md b/.changeset/pipeline-root-stdin.md new file mode 100644 index 000000000..1c380f884 --- /dev/null +++ b/.changeset/pipeline-root-stdin.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Route pipeline handle input through the first process. diff --git a/.changeset/plain-tool-results.md b/.changeset/plain-tool-results.md new file mode 100644 index 000000000..b58b40275 --- /dev/null +++ b/.changeset/plain-tool-results.md @@ -0,0 +1,7 @@ +--- +"@effect/ai-openai": patch +"@effect/ai-anthropic": patch +"@effect/ai-openrouter": patch +--- + +Preserve plain-text client tool results when encoding provider requests. diff --git a/.changeset/pool-preserve-reservations.md b/.changeset/pool-preserve-reservations.md new file mode 100644 index 000000000..0f87426b2 --- /dev/null +++ b/.changeset/pool-preserve-reservations.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep `Pool.reserve` items out of shared circulation when other borrowers return or overlapping reservations close. Restore available slots only after the last reservation closes. diff --git a/.changeset/port-http-api-builder-handler.md b/.changeset/port-http-api-builder-handler.md new file mode 100644 index 000000000..a499e2fe2 --- /dev/null +++ b/.changeset/port-http-api-builder-handler.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Port `HttpApiBuilder.handler` from v3 to define reusable endpoint callbacks with inferred request, response, error, and service types. diff --git a/.changeset/postgres-channel-names.md b/.changeset/postgres-channel-names.md new file mode 100644 index 000000000..7662aa95f --- /dev/null +++ b/.changeset/postgres-channel-names.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Reject PostgreSQL LISTEN and NOTIFY channel names longer than 63 UTF-8 bytes. diff --git a/.changeset/pre.json b/.changeset/pre.json index 1f26858d1..d1deaa4cf 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,630 +1,4 @@ { "mode": "pre", - "tag": "beta", - "initialVersions": { - "@effect/ai-anthropic": "3.0.0", - "@effect/ai-openai": "3.0.0", - "@effect/ai-openrouter": "3.0.0", - "@effect/atom-react": "3.0.0", - "@effect/atom-solid": "3.0.0", - "@effect/atom-vue": "3.0.0", - "effect": "3.0.0", - "@effect/opentelemetry": "3.0.0", - "@effect/platform-browser": "3.0.0", - "@effect/platform-bun": "3.0.0", - "@effect/platform-node": "3.0.0", - "@effect/platform-node-shared": "3.0.0", - "@effect/sql-clickhouse": "3.0.0", - "@effect/sql-d1": "3.0.0", - "@effect/sql-libsql": "3.0.0", - "@effect/sql-mssql": "3.0.0", - "@effect/sql-mysql2": "3.0.0", - "@effect/sql-pg": "3.0.0", - "@effect/sql-sqlite-bun": "3.0.0", - "@effect/sql-sqlite-do": "3.0.0", - "@effect/sql-sqlite-node": "3.0.0", - "@effect/sql-sqlite-react-native": "3.0.0", - "@effect/sql-sqlite-wasm": "3.0.0", - "@effect/openapi-generator": "3.0.0", - "@effect/oxc": "3.0.0", - "@effect/utils": "3.0.0", - "@effect/vitest": "3.0.0", - "scratchpad": "0.0.0", - "scripts": "0.0.0", - "@effect/ai-openai-compat": "3.0.0", - "@effect/ai-codegen": "0.0.0", - "@effect/bundle": "0.0.0", - "ai-docs": "0.0.0", - "@effect/ai-docgen": "0.0.0", - "@effect/sql-pglite": "4.0.0-beta.52", - "@effect/jsdocs": "0.0.0" - }, - "changesets": [ - "add-adaptive-rate-limiter-store", - "add-bigdecimal-sumall-multiplyall", - "add-chunk-schema", - "add-command-hidden", - "add-config-nested", - "add-flag-hidden", - "add-from-string-schemas", - "add-headers-remove-many", - "add-indexeddb-kvs-layer", - "add-make-msgpack", - "add-make-option", - "add-missing-tx-modules", - "add-newtype-module", - "add-scalar-show-operation-id", - "add-schedule-tap", - "add-schema-annotate-encoded", - "add-schema-array-ensure", - "add-schema-bigdecimal", - "add-schema-datetime", - "add-schema-option-from-optional-nullor", - "add-schema-option-from-undefined-nullish", - "add-schema-string-encoding", - "add-sql-pglite", - "add-standard-jsdoc-rule", - "add-stream-broadcastn", - "add-unstable-encoding-export", - "add-values-unprepared", - "afraid-cobras-like", - "ai-openai-config-field-leak", - "ai-openai-file-nullable-fields", - "anthropic-4-6-structured-output", - "asyncresult-exhaustive", - "atom-stream-error-type", - "beige-paths-sort", - "better-apples-nail", - "better-rocks-arrive", - "big-pans-look", - "blue-dingos-greet", - "blue-ligers-cheat", - "blue-onions-smile", - "blue-ravens-type", - "blue-trams-kiss", - "bold-chairs-yawn", - "bold-planets-shout", - "breezy-meals-see", - "bright-bugs-bow", - "bright-canyons-clean", - "bright-dogs-fail", - "bright-flags-stand", - "bright-laws-teach", - "bright-lemons-dance", - "bright-planes-smash", - "bright-rats-attend", - "bright-toes-rush", - "bumpy-boxes-teach", - "busy-lions-sneeze", - "busy-maps-attend", - "calm-buckets-own", - "calm-carrots-march", - "calm-cars-rest", - "calm-panthers-nail", - "calm-seas-smile", - "calm-squids-hug", - "calm-tracers-sample", - "chatty-poets-type", - "chilled-mice-wash", - "chilly-pumas-rule", - "chubby-buckets-feel", - "chubby-parents-flow", - "chubby-planets-fall", - "clean-balloons-tan", - "clean-bulldogs-care", - "clean-dryers-sneeze", - "clean-geese-work", - "clean-goats-wave", - "clean-needles-shake", - "clean-tires-guess", - "clear-spies-boil", - "cli-help-choices", - "cold-knives-lie", - "cold-rooms-show", - "cold-sloths-wave", - "compact-json-schema-enum", - "config-withdefault-eager", - "consolidate-encoding", - "consolidate-sql-error", - "crisp-seas-warn", - "cuddly-rooms-bet", - "curly-poems-talk", - "curly-spies-relax", - "curvy-apples-float", - "curvy-birds-float", - "custom-http-security-openapi-generator", - "cute-heads-thank", - "cyan-loops-grow", - "cyan-radios-switch", - "deep-rivers-spend", - "dirty-lamps-trade", - "dirty-laws-wear", - "duration-temporal-object-input", - "eager-coats-cheat", - "early-birds-dream", - "early-donuts-argue", - "early-peaches-check", - "eff-691-default-logger-ordering", - "eff-693-rpcgroup-handler-deps", - "eff-694-cli-completions-module", - "eff-695-layer-mock-dual-api", - "eff-697-rpcserialization-json-array-decode", - "eff-698-rpcserialization-unreachable-branch", - "eff-700-httpapi-middleware-errors", - "eff-701-httpapierror-respondable", - "eff-704-stream-merge-predicate", - "eff-705-layer-tap-apis", - "eff-706-servicemap-mutate", - "eff-716-response-id-tracker-map", - "eff-717-openai-socket-cancel", - "eff-718-embedding-model-surface", - "eff-725-fix-catch-jsdoc", - "eff-726-model-dimensions", - "eff-727-cli-help-alignment", - "eff-730-language-model-incremental-fallback", - "eff-736-cached-with-ttl", - "eff-738-cron-prev", - "eff-739-openai-function-call-done", - "eff-740-missing-summary-parts", - "eff-742-http-client-request-web", - "eff-744-sqlite-migrator-lock", - "eff-746-fixed-iteration-catchup", - "eff-747-unify-effect", - "eff-754-url-builder-any", - "eff-755-references-core", - "eff-769-select-text-highlight", - "eff-774-mutable-list-append-all-empty-array", - "eff-777-schema-make-effect", - "eff-778-http-middleware-path-logger", - "eff-779-keyvaluestore-layer-sql", - "eff-780-layer-unify", - "eff-781-fix-stream-toqueue-types", - "eff-782-httpapi-status-literals", - "eff-783-atom-http-api-errors", - "eff-819-cluster-workflow-shard-groups", - "eff-849-transpose-option", - "eight-turkeys-own", - "eighty-lies-deny", - "eighty-poets-draw", - "eighty-swans-scream", - "eighty-teeth-sniff", - "eleven-apes-share", - "eleven-numbers-bake", - "empty-gifts-beg", - "empty-http-rpc-client", - "eventlog-unencrypted", - "every-olives-burn", - "expand-schema-filter-output", - "export-schema-encode-keys-interface", - "extract-semaphore-latch", - "fair-bees-relax", - "fair-buttons-share", - "fair-cooks-stop", - "fair-cups-train", - "fair-dryers-speak", - "fair-forks-shake", - "fair-pandas-prove", - "fair-pants-float", - "fair-poems-visit", - "famous-wolves-lead", - "fancy-glasses-grow", - "fast-times-camp", - "few-birds-matter", - "few-cougars-dig", - "few-foxes-grin", - "few-mirrors-pull", - "few-socks-poke", - "fiber-runtime-start-metrics", - "fiery-jokes-care", - "fiery-mammals-call", - "fine-walls-decide", - "first-success-of", - "five-parents-relax", - "five-worms-rhyme", - "fix-1332", - "fix-1917", - "fix-1927", - "fix-1940", - "fix-1947", - "fix-2002", - "fix-2012", - "fix-2015", - "fix-2260", - "fix-2268", - "fix-2271", - "fix-2384", - "fix-2414", - "fix-2419", - "fix-ai-empty-params-structured-output", - "fix-ai-text-toolkit-typing", - "fix-anthropic-caller-toolid", - "fix-catch-orelse-error-erasure", - "fix-class-constructor-defaults", - "fix-cli-mixed-global-flag-context", - "fix-config-array-default", - "fix-config-withDefault", - "fix-config-withdefault-filter", - "fix-cron-next-missing-day-overflow", - "fix-datetime-gmt", - "fix-devtools-flush-on-teardown", - "fix-duration-symmetric-rounding", - "fix-entity-manager-defect-replay", - "fix-entity-proxy-rpc-handler-context", - "fix-entity-proxy-server-path-params", - "fix-from-readable-stream-cancel-defect", - "fix-graph-undirected-traversal", - "fix-has-interrupts-only-empty", - "fix-hashmap-bit31-ordering", - "fix-headers-proto-enumerability", - "fix-http-incoming-message-parse-options", - "fix-http-tracer-response-cause", - "fix-httpapi-endpoint-error-inference", - "fix-httpapi-schema-types", - "fix-httpapi-security-middleware-cache", - "fix-is-json-dag", - "fix-json-schema-anyof-oneof-siblings", - "fix-json-schema-import-json", - "fix-keepalive-blocked-timers", - "fix-mcp-param-name-resolution", - "fix-mermaid-escape-special-chars", - "fix-mutable-list-filter-length", - "fix-object-keyword-json-schema", - "fix-openai-mcp-tool-names", - "fix-openapi-generator-form-urlencoded", - "fix-openapi-generator-swagger2openapi", - "fix-openapi-preserve-multiple-response-content-types", - "fix-openrouter-sparse-array", - "fix-otel-logger-clock-skew", - "fix-otel-logger-severity-number", - "fix-queue-collect-duplication", - "fix-random-string-seeds", - "fix-ratelimiter-tokenbucket-redis-ttl", - "fix-remainder-scientific-notation", - "fix-request-resolver-pending-batches-leak", - "fix-retry-transient-autocomplete", - "fix-rpc-http-requestids-finalizer", - "fix-rpc-json-id-edges", - "fix-rpc-unknown-tag-isolation", - "fix-schedule-fixed-double-exec", - "fix-schedule-reduce-sync-state", - "fix-schema-arbitrary-exclusive-bounds", - "fix-schema-defect-message", - "fix-schema-encode-keys-property-keys", - "fix-schema-encodekeys-class", - "fix-schema-encodekeys-struct", - "fix-schema-encoding-checks", - "fix-schema-identifier-expected-message", - "fix-schema-is-uuid", - "fix-schema-option-non-schema-failures", - "fix-schema-parser-checks", - "fix-searchparam-initial-decode", - "fix-serializable-wire-transfer", - "fix-stream-grouped-within-flush", - "fix-stream-run-for-each-while", - "fix-stream-scan-effect", - "fix-stream-scoped-scope", - "fix-strip-approval-artifacts-multi-round", - "fix-struct-utility-types-simplify", - "fix-structwithrest-index-signatures", - "fix-tagged-union-class-sentinels", - "fix-tagged-union-match-unify", - "fix-to-tagged-union-isanyof-custom-tags", - "fix-tuple-with-rest-post-rest-index-drift", - "fix-tuple-with-rest-post-rest-validation", - "fix-types-voidifempty", - "fix-void-response-encoding", - "fix-workflow-defect-reply-serialization", - "fix-workflow-proxy-rpc-handler-context", - "flat-chicken-remain", - "floppy-cows-spend", - "floppy-items-admire", - "floppy-pigs-kiss", - "fluffy-meals-matter", - "fluffy-pumas-push", - "forked-memo-maps", - "forty-hounds-cheer", - "forty-otters-cry", - "forty-rings-film", - "forty-signs-stay", - "forty-swans-divide", - "forty-trees-pay", - "four-papayas-bow", - "four-points-repeat", - "fresh-cats-smash", - "fresh-emus-cheat", - "fresh-monkeys-smoke", - "frozen-intrinsics-stack-trace-limit", - "fruity-houses-learn", - "full-adults-double", - "funny-crabs-hang", - "funny-forks-move", - "fuzzy-camels-hunt", - "fuzzy-crews-fold", - "fuzzy-dodos-help", - "fuzzy-lions-perform", - "fuzzy-planets-sneeze", - "gold-meteors-move", - "gold-readers-hug", - "gold-rings-start", - "good-tools-work", - "good-trees-pull", - "graph-algorithm-fixes", - "great-trains-mate", - "great-trams-report", - "green-beds-unref", - "green-chips-wash", - "green-moons-smile", - "green-pugs-play", - "green-rings-prove", - "happy-mirrors-dream", - "heavy-loops-cut", - "heavy-trams-fix", - "hip-socks-travel", - "honest-pens-thank", - "honest-rivers-notice", - "hot-taxis-fry", - "httpapi-endpoint-relax-constraints", - "httpapi-schema-service-types", - "huge-moons-rhyme", - "humble-pigs-dig", - "icy-flies-cross", - "itchy-radios-poke", - "itchy-results-bet", - "itchy-shrimps-deny", - "itchy-toes-promise", - "k8s-last-transition-null", - "khaki-cats-learn", - "khaki-melons-appear", - "kind-hounds-float", - "kind-windows-fall", - "late-hotels-rule", - "late-lamps-care", - "late-rivers-applaud", - "layer-map-dynamic-idle-ttl", - "lazy-queens-rush", - "lazy-recursive-forward-refs", - "lazy-timers-exist", - "legal-pants-drop", - "lemon-taxis-sin", - "light-kids-sneeze", - "little-dryers-allow", - "long-cameras-think", - "lovely-cobras-change", - "lovely-frogs-rescue", - "lucky-buttons-jump", - "lucky-phones-listen", - "lucky-worms-type", - "many-badgers-obey", - "mean-dingos-share", - "mean-trains-smash", - "metal-parts-yell", - "mighty-games-matter", - "modern-carrots-see", - "modern-uuid-guid-filter", - "nasty-geese-grow", - "neat-goats-wave", - "neat-kings-chew", - "neat-lions-rest", - "neat-snails-wash", - "neat-taxis-notice", - "neat-windows-buy", - "new-dogs-swim", - "new-toes-stop", - "ninety-geese-exist", - "odd-boats-think", - "odd-bulldogs-sleep", - "odd-fans-glow", - "odd-forks-talk", - "odd-laws-draw", - "odd-owls-smoke", - "odd-socks-boil", - "odd-suns-dance", - "old-brooms-cry", - "old-facts-stand", - "old-mirrors-float", - "olive-poems-visit", - "open-hotels-remain", - "openai-compat-nullable-tool-name", - "openai-compat-reasoning", - "openapi-generator-sse-constraint-decoder", - "openrouter-input-audio", - "otel-resource-env-precedence", - "perfect-buckets-tickle", - "petite-months-allow", - "platform-crypto-service", - "platform-node-shared-barrel", - "plenty-moons-pull", - "polite-brooms-tickle", - "polite-pigs-speak", - "polite-tables-kneel", - "port-react-hydration", - "public-deer-ring", - "public-jeans-stop", - "pubsub-publish-false", - "puny-pens-clap", - "purple-bars-prove", - "purple-schools-float", - "purple-turtles-draw", - "quick-dragons-fix", - "quick-falcons-travel", - "quick-geese-relax", - "quick-lamps-dig", - "quick-lizards-fall", - "quick-trees-join", - "quiet-carpets-grin", - "quiet-files-hunt", - "quiet-lamps-jam", - "quiet-radios-wave", - "quiet-redis-scripts", - "quiet-tigers-yell", - "random-choice", - "ready-olives-divide", - "real-trains-ring", - "red-pigs-repair", - "redacted-representation-options", - "refactor-cli-global-flags", - "refactor-config-provider", - "refactor-representation-references", - "remove-effect-transactionwith", - "remove-http-span-counter", - "remove-nullor", - "remove-openapi-fromapi-options", - "remove-schema-stringtree-keep-declarations", - "remove-types-mergerecord", - "remove-unused-utils-apis", - "rename-rebuild-out", - "restore-schema-parse-options", - "rich-dots-push", - "rich-hoops-nail", - "rich-sloths-draw", - "ripe-lies-battle", - "rpc-client-http-early-close", - "rpc-middleware-provides-fix", - "scalar-custom-fetch", - "schema-as-class", - "schema-asserts-signature", - "schema-clean-up-additionalProperties", - "schema-datetime-utc-from-string", - "schema-decoding-defaults-services", - "schema-defaults-issue-channel", - "schema-dollar-prefix", - "schema-lazy-bottom", - "schema-missing-self-generic", - "schema-ordered-arbitrary-constraints", - "schema-parser-adapter-errors", - "schema-refactor-toCodecJson", - "schema-remove-annotate-in", - "schema-rename-makeUnsafe-to-make", - "schema-rename-parser-makeUnsafe", - "schema-result-combinators", - "schema-struct-simplify", - "semantic-matching", - "seven-mugs-marry", - "shaggy-birds-stay", - "shaggy-cities-push", - "shaggy-numbers-accept", - "sharp-emus-applaud", - "sharp-goats-wink", - "sharp-pandas-care", - "sharp-peas-march", - "sharp-rules-draw", - "sharp-singers-sort", - "shiny-trains-hug", - "short-cows-relate", - "short-foxes-admire", - "short-stamps-throw", - "shy-cycles-flow", - "shy-geckos-sniff", - "silent-geckos-matter", - "silent-needles-design", - "silent-plants-matter", - "silent-spoons-stare", - "silly-loops-tickle", - "silver-bulk-indexeddb", - "silver-emus-smoke", - "silver-kings-poke", - "silver-snails-sqlite", - "silver-wings-watch", - "six-cups-taste", - "sixty-mails-shout", - "sixty-socks-yell", - "slick-signs-wish", - "slick-toes-rush", - "slimy-planets-divide", - "slimy-turtles-juggle", - "slow-beans-battle", - "slow-berries-enjoy", - "small-bugs-hunt", - "small-crabs-care", - "small-pets-sit", - "smart-ducks-jump", - "smart-pillows-buy", - "smart-timers-fly", - "smart-tips-sort", - "social-pumas-prove", - "soft-comics-wink", - "soft-delete-sqlmodel", - "soft-seals-allow", - "solid-cougars-attack", - "solid-doors-ring", - "solid-items-tease", - "solid-towns-smoke", - "sour-canyons-rescue", - "sparkly-bears-act", - "sparkly-coins-sit", - "spotty-comics-fry", - "sql-migrator-mjs-mts", - "sqlite-bun-prepare-error-channel", - "sqlite-do-durable-object-transactions", - "stale-dots-tell", - "stale-snakes-know", - "strict-areas-end", - "strict-buckets-hug", - "strip-resolved-approvals", - "strong-balloons-tickle", - "strong-bees-queue", - "struct-record", - "sunny-ads-hang", - "sunny-bikes-sleep", - "sunny-rooms-invent", - "sweet-donuts-bet", - "sweet-hotels-give", - "sweet-views-learn", - "swift-spiders-unpack", - "swift-symbols-stand", - "tagged-error-class-optional-empty-props", - "tall-hairs-return", - "tall-mails-listen", - "tall-queens-cheer", - "tall-wombats-wave", - "tangy-colts-lose", - "tasty-comics-send", - "ten-kings-fry", - "thick-pandas-wait", - "thin-ducks-wonder", - "thirty-ducks-go", - "thirty-pans-love", - "three-corners-sort", - "three-ravens-jam", - "three-tomatoes-wave", - "tidy-foxes-own", - "tidy-icons-glow", - "tidy-stacks-encode", - "tidy-stars-drive", - "tiny-buckets-wave", - "tiny-lilies-flash", - "tiny-rabbits-smile", - "to-codec-json-schema", - "tocodecjson-return-json-type", - "tool-get-json-schema-tests", - "true-actors-battle", - "try-promise-catch-defect", - "twelve-dragons-move", - "twenty-buttons-cheer", - "two-roses-double", - "unify-error-defect-stack-options", - "update-schema-arbitrary-report", - "upset-colts-stick", - "use-url-can-parse", - "vast-bananas-send", - "vast-deserts-travel", - "violet-peaches-feel", - "vitest-layer-top-level-options", - "wacky-grapes-poke", - "wacky-rice-add", - "warm-dolls-brake", - "warm-friends-tie", - "warm-snails-shop", - "wet-news-invent", - "wild-readers-clean", - "wild-suns-bearer-space", - "wise-ants-wave", - "wise-flags-shift", - "wise-oranges-stay", - "witty-lobsters-share", - "yellow-adults-study", - "yellow-clocks-dance", - "yellow-dingos-jump", - "young-doors-change" - ] + "tag": "rc" } diff --git a/.changeset/add-adaptive-rate-limiter-store.md b/.changeset/pre/add-adaptive-rate-limiter-store.md similarity index 100% rename from .changeset/add-adaptive-rate-limiter-store.md rename to .changeset/pre/add-adaptive-rate-limiter-store.md diff --git a/.changeset/pre/add-atom-equality.md b/.changeset/pre/add-atom-equality.md new file mode 100644 index 000000000..c2600bfa5 --- /dev/null +++ b/.changeset/pre/add-atom-equality.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +unstable/reactivity Atom: add `withEquality` combinator for customizing how the registry detects value changes diff --git a/.changeset/add-bigdecimal-sumall-multiplyall.md b/.changeset/pre/add-bigdecimal-sumall-multiplyall.md similarity index 100% rename from .changeset/add-bigdecimal-sumall-multiplyall.md rename to .changeset/pre/add-bigdecimal-sumall-multiplyall.md diff --git a/.changeset/add-chunk-schema.md b/.changeset/pre/add-chunk-schema.md similarity index 100% rename from .changeset/add-chunk-schema.md rename to .changeset/pre/add-chunk-schema.md diff --git a/.changeset/add-command-hidden.md b/.changeset/pre/add-command-hidden.md similarity index 100% rename from .changeset/add-command-hidden.md rename to .changeset/pre/add-command-hidden.md diff --git a/.changeset/add-config-nested.md b/.changeset/pre/add-config-nested.md similarity index 100% rename from .changeset/add-config-nested.md rename to .changeset/pre/add-config-nested.md diff --git a/.changeset/pre/add-deno-file-system.md b/.changeset/pre/add-deno-file-system.md new file mode 100644 index 000000000..6bd6a73df --- /dev/null +++ b/.changeset/pre/add-deno-file-system.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a Deno-backed FileSystem layer. diff --git a/.changeset/pre/add-deno-http-client.md b/.changeset/pre/add-deno-http-client.md new file mode 100644 index 000000000..2bf5753f8 --- /dev/null +++ b/.changeset/pre/add-deno-http-client.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-deno": patch +--- + +Add `DenoHttpClient`, re-exporting `effect/unstable/http/FetchHttpClient` + +Deno's `fetch` is spec-compliant, so the core fetch-based `HttpClient` works on Deno unmodified. This module mirrors `BunHttpClient` so the platform packages expose a consistent surface. diff --git a/.changeset/pre/add-deno-multipart.md b/.changeset/pre/add-deno-multipart.md new file mode 100644 index 000000000..b396bfd8c --- /dev/null +++ b/.changeset/pre/add-deno-multipart.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add web-standard multipart request parsing helpers for Deno. diff --git a/.changeset/pre/add-deno-socket-server.md b/.changeset/pre/add-deno-socket-server.md new file mode 100644 index 000000000..d72662428 --- /dev/null +++ b/.changeset/pre/add-deno-socket-server.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add native Deno TCP, Unix, and TLS socket server adapters. diff --git a/.changeset/pre/add-deno-socket.md b/.changeset/pre/add-deno-socket.md new file mode 100644 index 000000000..da98c6486 --- /dev/null +++ b/.changeset/pre/add-deno-socket.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add native Deno TCP, Unix, and WebSocket integrations for Effect sockets. diff --git a/.changeset/add-flag-hidden.md b/.changeset/pre/add-flag-hidden.md similarity index 100% rename from .changeset/add-flag-hidden.md rename to .changeset/pre/add-flag-hidden.md diff --git a/.changeset/add-from-string-schemas.md b/.changeset/pre/add-from-string-schemas.md similarity index 100% rename from .changeset/add-from-string-schemas.md rename to .changeset/pre/add-from-string-schemas.md diff --git a/.changeset/add-headers-remove-many.md b/.changeset/pre/add-headers-remove-many.md similarity index 100% rename from .changeset/add-headers-remove-many.md rename to .changeset/pre/add-headers-remove-many.md diff --git a/.changeset/pre/add-http-client-request-update-headers.md b/.changeset/pre/add-http-client-request-update-headers.md new file mode 100644 index 000000000..391894aa6 --- /dev/null +++ b/.changeset/pre/add-http-client-request-update-headers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +unstable/http HttpClientRequest: add `updateHeaders` and `removeHeader` combinators for transforming or removing request headers, closes #6271 diff --git a/.changeset/pre/add-http-client-tracer-header-filter.md b/.changeset/pre/add-http-client-tracer-header-filter.md new file mode 100644 index 000000000..44d804467 --- /dev/null +++ b/.changeset/pre/add-http-client-tracer-header-filter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add a configurable filter for HTTP client request and response header span attributes. diff --git a/.changeset/pre/add-httpapi-with-headers.md b/.changeset/pre/add-httpapi-with-headers.md new file mode 100644 index 000000000..e91cccacc --- /dev/null +++ b/.changeset/pre/add-httpapi-with-headers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +httpapi: add typed response headers across handlers, generated clients (including `HttpApiTest`), streaming responses, and OpenAPI with `HttpApiSchema.WithHeaders`. Add `HttpApiSchema.encodeToWithHeaders` for folding response headers into domain types such as error classes. Explicit `content-type` and `content-length` values applied with `HttpServerResponse.setHeader` or `setHeaders` now override body-derived values. diff --git a/.changeset/add-indexeddb-kvs-layer.md b/.changeset/pre/add-indexeddb-kvs-layer.md similarity index 100% rename from .changeset/add-indexeddb-kvs-layer.md rename to .changeset/pre/add-indexeddb-kvs-layer.md diff --git a/.changeset/pre/add-json-schema-draft-04.md b/.changeset/pre/add-json-schema-draft-04.md new file mode 100644 index 000000000..440b16628 --- /dev/null +++ b/.changeset/pre/add-json-schema-draft-04.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add support for converting JSON Schema documents to Draft-04, preserve literal `$ref` values, `$ref` sibling constraints, `not`, `readOnly`, and `writeOnly` in Draft-07 conversions, correct the Draft-07 meta-schema URI, and prevent OpenAPI component-key collisions during conversion. diff --git a/.changeset/add-make-msgpack.md b/.changeset/pre/add-make-msgpack.md similarity index 100% rename from .changeset/add-make-msgpack.md rename to .changeset/pre/add-make-msgpack.md diff --git a/.changeset/add-make-option.md b/.changeset/pre/add-make-option.md similarity index 100% rename from .changeset/add-make-option.md rename to .changeset/pre/add-make-option.md diff --git a/.changeset/pre/add-mcp-2025-11-25-protocol.md b/.changeset/pre/add-mcp-2025-11-25-protocol.md new file mode 100644 index 000000000..fbc935c0b --- /dev/null +++ b/.changeset/pre/add-mcp-2025-11-25-protocol.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +MCP servers can now use the 2025-11-25 protocol, including sampling with tools and both form- and URL-based elicitation. + +Enable it by adding `McpProtocol.v2025_11_25` to the server's `protocols` option. diff --git a/.changeset/pre/add-mcp-icons.md b/.changeset/pre/add-mcp-icons.md new file mode 100644 index 000000000..8e2383a62 --- /dev/null +++ b/.changeset/pre/add-mcp-icons.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +MCP servers can now provide icons for server information, resources, resource templates, prompts, and tools using `McpSchema.Icon`. + +Each icon can specify its source URI, MIME type, supported sizes, and light or dark theme. diff --git a/.changeset/add-missing-tx-modules.md b/.changeset/pre/add-missing-tx-modules.md similarity index 100% rename from .changeset/add-missing-tx-modules.md rename to .changeset/pre/add-missing-tx-modules.md diff --git a/.changeset/add-newtype-module.md b/.changeset/pre/add-newtype-module.md similarity index 100% rename from .changeset/add-newtype-module.md rename to .changeset/pre/add-newtype-module.md diff --git a/.changeset/pre/add-otlp-manual-flush.md b/.changeset/pre/add-otlp-manual-flush.md new file mode 100644 index 000000000..fbbd9b16a --- /dev/null +++ b/.changeset/pre/add-otlp-manual-flush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add manual flushing to the OTLP exporters through a shared `Flusher` service exposed by each signal layer. The signal layer output types now include `Flusher`, and `OtlpExporter.make` requires it so custom exporters register unconditionally. diff --git a/.changeset/pre/add-platform-deno.md b/.changeset/pre/add-platform-deno.md new file mode 100644 index 000000000..9237eee68 --- /dev/null +++ b/.changeset/pre/add-platform-deno.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add Deno platform integrations for paths, runtime execution, workers, and Web Storage. diff --git a/.changeset/add-scalar-show-operation-id.md b/.changeset/pre/add-scalar-show-operation-id.md similarity index 100% rename from .changeset/add-scalar-show-operation-id.md rename to .changeset/pre/add-scalar-show-operation-id.md diff --git a/.changeset/add-schedule-tap.md b/.changeset/pre/add-schedule-tap.md similarity index 100% rename from .changeset/add-schedule-tap.md rename to .changeset/pre/add-schedule-tap.md diff --git a/.changeset/add-schema-annotate-encoded.md b/.changeset/pre/add-schema-annotate-encoded.md similarity index 100% rename from .changeset/add-schema-annotate-encoded.md rename to .changeset/pre/add-schema-annotate-encoded.md diff --git a/.changeset/add-schema-array-ensure.md b/.changeset/pre/add-schema-array-ensure.md similarity index 100% rename from .changeset/add-schema-array-ensure.md rename to .changeset/pre/add-schema-array-ensure.md diff --git a/.changeset/add-schema-bigdecimal.md b/.changeset/pre/add-schema-bigdecimal.md similarity index 100% rename from .changeset/add-schema-bigdecimal.md rename to .changeset/pre/add-schema-bigdecimal.md diff --git a/.changeset/add-schema-datetime.md b/.changeset/pre/add-schema-datetime.md similarity index 100% rename from .changeset/add-schema-datetime.md rename to .changeset/pre/add-schema-datetime.md diff --git a/.changeset/pre/add-schema-error-module.md b/.changeset/pre/add-schema-error-module.md new file mode 100644 index 000000000..d28c2c29a --- /dev/null +++ b/.changeset/pre/add-schema-error-module.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Expose `SchemaError` as a public module and re-export `Schema.isSchemaError`. + +This gives consumers a stable import path and guard for schema failures without +depending on the internal schema implementation, while preserving the existing +`Schema.SchemaError` surface. diff --git a/.changeset/add-schema-option-from-optional-nullor.md b/.changeset/pre/add-schema-option-from-optional-nullor.md similarity index 100% rename from .changeset/add-schema-option-from-optional-nullor.md rename to .changeset/pre/add-schema-option-from-optional-nullor.md diff --git a/.changeset/add-schema-option-from-undefined-nullish.md b/.changeset/pre/add-schema-option-from-undefined-nullish.md similarity index 100% rename from .changeset/add-schema-option-from-undefined-nullish.md rename to .changeset/pre/add-schema-option-from-undefined-nullish.md diff --git a/.changeset/add-schema-string-encoding.md b/.changeset/pre/add-schema-string-encoding.md similarity index 100% rename from .changeset/add-schema-string-encoding.md rename to .changeset/pre/add-schema-string-encoding.md diff --git a/.changeset/pre/add-schema-tagged-union-discriminants.md b/.changeset/pre/add-schema-tagged-union-discriminants.md new file mode 100644 index 000000000..5930ed631 --- /dev/null +++ b/.changeset/pre/add-schema-tagged-union-discriminants.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Add a `discriminants` tuple to schemas augmented with `Schema.toTaggedUnion` and reject duplicate discriminant +property keys. diff --git a/.changeset/pre/add-semaphore-take-if-available.md b/.changeset/pre/add-semaphore-take-if-available.md new file mode 100644 index 000000000..8cb5182cc --- /dev/null +++ b/.changeset/pre/add-semaphore-take-if-available.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Add `Semaphore.takeIfAvailable` for non-blocking manual permit acquisition. diff --git a/.changeset/add-sql-pglite.md b/.changeset/pre/add-sql-pglite.md similarity index 100% rename from .changeset/add-sql-pglite.md rename to .changeset/pre/add-sql-pglite.md diff --git a/.changeset/add-standard-jsdoc-rule.md b/.changeset/pre/add-standard-jsdoc-rule.md similarity index 100% rename from .changeset/add-standard-jsdoc-rule.md rename to .changeset/pre/add-standard-jsdoc-rule.md diff --git a/.changeset/add-stream-broadcastn.md b/.changeset/pre/add-stream-broadcastn.md similarity index 100% rename from .changeset/add-stream-broadcastn.md rename to .changeset/pre/add-stream-broadcastn.md diff --git a/.changeset/pre/add-tool-set-needs-approval.md b/.changeset/pre/add-tool-set-needs-approval.md new file mode 100644 index 000000000..959b43579 --- /dev/null +++ b/.changeset/pre/add-tool-set-needs-approval.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Tool.setNeedsApproval` for replacing the approval policy of an existing tool. diff --git a/.changeset/add-unstable-encoding-export.md b/.changeset/pre/add-unstable-encoding-export.md similarity index 100% rename from .changeset/add-unstable-encoding-export.md rename to .changeset/pre/add-unstable-encoding-export.md diff --git a/.changeset/pre/add-update-service-scoped.md b/.changeset/pre/add-update-service-scoped.md new file mode 100644 index 000000000..95f49061a --- /dev/null +++ b/.changeset/pre/add-update-service-scoped.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.updateServiceScoped` for updating a context service until the current scope closes, with customizable reset behavior. diff --git a/.changeset/add-values-unprepared.md b/.changeset/pre/add-values-unprepared.md similarity index 100% rename from .changeset/add-values-unprepared.md rename to .changeset/pre/add-values-unprepared.md diff --git a/.changeset/afraid-cobras-like.md b/.changeset/pre/afraid-cobras-like.md similarity index 100% rename from .changeset/afraid-cobras-like.md rename to .changeset/pre/afraid-cobras-like.md diff --git a/.changeset/ai-openai-config-field-leak.md b/.changeset/pre/ai-openai-config-field-leak.md similarity index 100% rename from .changeset/ai-openai-config-field-leak.md rename to .changeset/pre/ai-openai-config-field-leak.md diff --git a/.changeset/ai-openai-file-nullable-fields.md b/.changeset/pre/ai-openai-file-nullable-fields.md similarity index 100% rename from .changeset/ai-openai-file-nullable-fields.md rename to .changeset/pre/ai-openai-file-nullable-fields.md diff --git a/.changeset/anthropic-4-6-structured-output.md b/.changeset/pre/anthropic-4-6-structured-output.md similarity index 100% rename from .changeset/anthropic-4-6-structured-output.md rename to .changeset/pre/anthropic-4-6-structured-output.md diff --git a/.changeset/pre/anthropic-claude-5-capabilities.md b/.changeset/pre/anthropic-claude-5-capabilities.md new file mode 100644 index 000000000..785acd535 --- /dev/null +++ b/.changeset/pre/anthropic-claude-5-capabilities.md @@ -0,0 +1,7 @@ +--- +"@effect/ai-anthropic": patch +--- + +Default new Anthropic models to modern capabilities while preserving the limits of legacy Claude models. + +Unknown models now default to native structured outputs and 128K output tokens, so future model releases do not require capability-table updates. Use the new `structuredOutputs` model config option to override capability detection when needed. diff --git a/.changeset/pre/anthropic-open-model-enum.md b/.changeset/pre/anthropic-open-model-enum.md new file mode 100644 index 000000000..b5ab0393d --- /dev/null +++ b/.changeset/pre/anthropic-open-model-enum.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-anthropic": patch +--- + +Widen the Anthropic `Model` schema to accept both known model identifiers as well +as any string to allow for newer models diff --git a/.changeset/pre/anthropic-stale-max-output-tokens.md b/.changeset/pre/anthropic-stale-max-output-tokens.md new file mode 100644 index 000000000..f7e83ff6e --- /dev/null +++ b/.changeset/pre/anthropic-stale-max-output-tokens.md @@ -0,0 +1,7 @@ +--- +"@effect/ai-anthropic": patch +--- + +Correct the maximum output tokens for Claude Opus 4.6, 4.7, 4.8 and Sonnet 4.6. + +These models were grouped with the 4.5 family at 64000 output tokens, half of the 128000 the API actually allows, so requests defaulted to a cap far below the model's real limit. The 4.5 models keep 64000, which is correct for them. diff --git a/.changeset/pre/apply-httpapi-endpoint-client-transform.md b/.changeset/pre/apply-httpapi-endpoint-client-transform.md new file mode 100644 index 000000000..ce9d74cba --- /dev/null +++ b/.changeset/pre/apply-httpapi-endpoint-client-transform.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Apply `transformClient` when building an individual HttpApi endpoint client, preserving the supplied client's error and service channels. diff --git a/.changeset/asyncresult-exhaustive.md b/.changeset/pre/asyncresult-exhaustive.md similarity index 100% rename from .changeset/asyncresult-exhaustive.md rename to .changeset/pre/asyncresult-exhaustive.md diff --git a/.changeset/atom-stream-error-type.md b/.changeset/pre/atom-stream-error-type.md similarity index 100% rename from .changeset/atom-stream-error-type.md rename to .changeset/pre/atom-stream-error-type.md diff --git a/.changeset/pre/atomic-optional-rc-map.md b/.changeset/pre/atomic-optional-rc-map.md new file mode 100644 index 000000000..2e6c8caa8 --- /dev/null +++ b/.changeset/pre/atomic-optional-rc-map.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Add `RcMap.getOption` and `LayerMap.contextEffectOption` for atomically retaining +entries only when they are already cached. diff --git a/.changeset/pre/authentication-error-description.md b/.changeset/pre/authentication-error-description.md new file mode 100644 index 000000000..f5745934d --- /dev/null +++ b/.changeset/pre/authentication-error-description.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +"@effect/ai-openrouter": patch +--- + +Add an optional `description` to `AiError.AuthenticationError`, rendered after the kind-based suggestion, and pass the provider's own error text through it on HTTP 401 and 403, so authentication failures report what actually went wrong instead of only a category. diff --git a/.changeset/pre/autocomplete-jk-filter.md b/.changeset/pre/autocomplete-jk-filter.md new file mode 100644 index 000000000..00c1ec668 --- /dev/null +++ b/.changeset/pre/autocomplete-jk-filter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Prompt.autoComplete` swallowing `j` and `k` while typing a filter query. diff --git a/.changeset/pre/batch-persistence-expiration-cleanup.md b/.changeset/pre/batch-persistence-expiration-cleanup.md new file mode 100644 index 000000000..3409edfb6 --- /dev/null +++ b/.changeset/pre/batch-persistence-expiration-cleanup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Run shared-table SQL persistence expiration cleanup in indexed, bounded background batches. diff --git a/.changeset/pre/beige-goats-sin.md b/.changeset/pre/beige-goats-sin.md new file mode 100644 index 000000000..22cef1f7f --- /dev/null +++ b/.changeset/pre/beige-goats-sin.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Fix dynamic tools defined with a raw JSON schema sending empty parameter schema to OpenRouter diff --git a/.changeset/beige-paths-sort.md b/.changeset/pre/beige-paths-sort.md similarity index 100% rename from .changeset/beige-paths-sort.md rename to .changeset/pre/beige-paths-sort.md diff --git a/.changeset/better-apples-nail.md b/.changeset/pre/better-apples-nail.md similarity index 100% rename from .changeset/better-apples-nail.md rename to .changeset/pre/better-apples-nail.md diff --git a/.changeset/better-rocks-arrive.md b/.changeset/pre/better-rocks-arrive.md similarity index 100% rename from .changeset/better-rocks-arrive.md rename to .changeset/pre/better-rocks-arrive.md diff --git a/.changeset/pre/better-shrimps-follow.md b/.changeset/pre/better-shrimps-follow.md new file mode 100644 index 000000000..95cb59640 --- /dev/null +++ b/.changeset/pre/better-shrimps-follow.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +prefix opentelemetry modules to avoid collisions diff --git a/.changeset/pre/big-masks-care.md b/.changeset/pre/big-masks-care.md new file mode 100644 index 000000000..8b3a17254 --- /dev/null +++ b/.changeset/pre/big-masks-care.md @@ -0,0 +1,5 @@ +--- +"@effect/doctest": patch +--- + +Support `.mdx` files diff --git a/.changeset/big-pans-look.md b/.changeset/pre/big-pans-look.md similarity index 100% rename from .changeset/big-pans-look.md rename to .changeset/pre/big-pans-look.md diff --git a/.changeset/blue-dingos-greet.md b/.changeset/pre/blue-dingos-greet.md similarity index 100% rename from .changeset/blue-dingos-greet.md rename to .changeset/pre/blue-dingos-greet.md diff --git a/.changeset/blue-ligers-cheat.md b/.changeset/pre/blue-ligers-cheat.md similarity index 100% rename from .changeset/blue-ligers-cheat.md rename to .changeset/pre/blue-ligers-cheat.md diff --git a/.changeset/blue-onions-smile.md b/.changeset/pre/blue-onions-smile.md similarity index 100% rename from .changeset/blue-onions-smile.md rename to .changeset/pre/blue-onions-smile.md diff --git a/.changeset/pre/blue-pigs-push.md b/.changeset/pre/blue-pigs-push.md new file mode 100644 index 000000000..7cb74e13f --- /dev/null +++ b/.changeset/pre/blue-pigs-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Channel.mergeAll` to propagate outer failures promptly and interrupt active inner channels. diff --git a/.changeset/blue-ravens-type.md b/.changeset/pre/blue-ravens-type.md similarity index 100% rename from .changeset/blue-ravens-type.md rename to .changeset/pre/blue-ravens-type.md diff --git a/.changeset/blue-trams-kiss.md b/.changeset/pre/blue-trams-kiss.md similarity index 100% rename from .changeset/blue-trams-kiss.md rename to .changeset/pre/blue-trams-kiss.md diff --git a/.changeset/bold-chairs-yawn.md b/.changeset/pre/bold-chairs-yawn.md similarity index 100% rename from .changeset/bold-chairs-yawn.md rename to .changeset/pre/bold-chairs-yawn.md diff --git a/.changeset/bold-planets-shout.md b/.changeset/pre/bold-planets-shout.md similarity index 100% rename from .changeset/bold-planets-shout.md rename to .changeset/pre/bold-planets-shout.md diff --git a/.changeset/pre/brave-keys-commit.md b/.changeset/pre/brave-keys-commit.md new file mode 100644 index 000000000..f9358594f --- /dev/null +++ b/.changeset/pre/brave-keys-commit.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix IndexedDB-backed key-value writes to wait for transaction commit before reporting success. diff --git a/.changeset/pre/brave-rings-update.md b/.changeset/pre/brave-rings-update.md new file mode 100644 index 000000000..21931bcb5 --- /dev/null +++ b/.changeset/pre/brave-rings-update.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update existing `HashRing` nodes when adding a value with the same primary key. diff --git a/.changeset/breezy-meals-see.md b/.changeset/pre/breezy-meals-see.md similarity index 100% rename from .changeset/breezy-meals-see.md rename to .changeset/pre/breezy-meals-see.md diff --git a/.changeset/bright-bugs-bow.md b/.changeset/pre/bright-bugs-bow.md similarity index 100% rename from .changeset/bright-bugs-bow.md rename to .changeset/pre/bright-bugs-bow.md diff --git a/.changeset/bright-canyons-clean.md b/.changeset/pre/bright-canyons-clean.md similarity index 100% rename from .changeset/bright-canyons-clean.md rename to .changeset/pre/bright-canyons-clean.md diff --git a/.changeset/pre/bright-clocks-count.md b/.changeset/pre/bright-clocks-count.md new file mode 100644 index 000000000..8f3df8ee0 --- /dev/null +++ b/.changeset/pre/bright-clocks-count.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `DateTime.toEpochSeconds` and `DateTime.fromEpochSeconds` for converting date-time values to and from Unix epoch seconds. diff --git a/.changeset/pre/bright-docs-teach.md b/.changeset/pre/bright-docs-teach.md new file mode 100644 index 000000000..3e26d3391 --- /dev/null +++ b/.changeset/pre/bright-docs-teach.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add SQL, HttpApi testing, and CLI schema examples to the published AI documentation. diff --git a/.changeset/bright-dogs-fail.md b/.changeset/pre/bright-dogs-fail.md similarity index 100% rename from .changeset/bright-dogs-fail.md rename to .changeset/pre/bright-dogs-fail.md diff --git a/.changeset/bright-flags-stand.md b/.changeset/pre/bright-flags-stand.md similarity index 100% rename from .changeset/bright-flags-stand.md rename to .changeset/pre/bright-flags-stand.md diff --git a/.changeset/pre/bright-graphs-calculate.md b/.changeset/pre/bright-graphs-calculate.md new file mode 100644 index 000000000..86935a995 --- /dev/null +++ b/.changeset/pre/bright-graphs-calculate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject graph shortest-path calculations that overflow or underflow the finite number range. diff --git a/.changeset/pre/bright-graphs-trace.md b/.changeset/pre/bright-graphs-trace.md new file mode 100644 index 000000000..1f08e6a7f --- /dev/null +++ b/.changeset/pre/bright-graphs-trace.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include traversed edge indexes in graph shortest-path results. diff --git a/.changeset/pre/bright-journals-commit.md b/.changeset/pre/bright-journals-commit.md new file mode 100644 index 000000000..5c312a18f --- /dev/null +++ b/.changeset/pre/bright-journals-commit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Commit SQL event journal entries only after their write callback succeeds. diff --git a/.changeset/bright-laws-teach.md b/.changeset/pre/bright-laws-teach.md similarity index 100% rename from .changeset/bright-laws-teach.md rename to .changeset/pre/bright-laws-teach.md diff --git a/.changeset/bright-lemons-dance.md b/.changeset/pre/bright-lemons-dance.md similarity index 100% rename from .changeset/bright-lemons-dance.md rename to .changeset/pre/bright-lemons-dance.md diff --git a/.changeset/bright-planes-smash.md b/.changeset/pre/bright-planes-smash.md similarity index 100% rename from .changeset/bright-planes-smash.md rename to .changeset/pre/bright-planes-smash.md diff --git a/.changeset/bright-rats-attend.md b/.changeset/pre/bright-rats-attend.md similarity index 100% rename from .changeset/bright-rats-attend.md rename to .changeset/pre/bright-rats-attend.md diff --git a/.changeset/pre/bright-tags-recognize.md b/.changeset/pre/bright-tags-recognize.md new file mode 100644 index 000000000..edead0f68 --- /dev/null +++ b/.changeset/pre/bright-tags-recognize.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Recognize tagged Config and RPC errors across duplicated `effect` package copies. diff --git a/.changeset/bright-toes-rush.md b/.changeset/pre/bright-toes-rush.md similarity index 100% rename from .changeset/bright-toes-rush.md rename to .changeset/pre/bright-toes-rush.md diff --git a/.changeset/pre/brown-glasses-thank.md b/.changeset/pre/brown-glasses-thank.md new file mode 100644 index 000000000..0445b02de --- /dev/null +++ b/.changeset/pre/brown-glasses-thank.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Handle BigInt values safely and consistently across JSON diagnostics and logger formats. diff --git a/.changeset/pre/brown-peas-enter.md b/.changeset/pre/brown-peas-enter.md new file mode 100644 index 000000000..2779fa9ef --- /dev/null +++ b/.changeset/pre/brown-peas-enter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP tool handler defects now return a stable internal error without exposing defect details. diff --git a/.changeset/bumpy-boxes-teach.md b/.changeset/pre/bumpy-boxes-teach.md similarity index 100% rename from .changeset/bumpy-boxes-teach.md rename to .changeset/pre/bumpy-boxes-teach.md diff --git a/.changeset/pre/bun-websocket-outgoing-compression.md b/.changeset/pre/bun-websocket-outgoing-compression.md new file mode 100644 index 000000000..9be7aeaf6 --- /dev/null +++ b/.changeset/pre/bun-websocket-outgoing-compression.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-bun": patch +--- + +Compress outgoing Bun WebSocket messages when per-message deflate is configured and negotiated. Messages +smaller than 1 KiB are left uncompressed, matching the default threshold used by Node's `ws` server. +The threshold is configurable via the new `websocket.compressionThreshold` server option. diff --git a/.changeset/busy-lions-sneeze.md b/.changeset/pre/busy-lions-sneeze.md similarity index 100% rename from .changeset/busy-lions-sneeze.md rename to .changeset/pre/busy-lions-sneeze.md diff --git a/.changeset/busy-maps-attend.md b/.changeset/pre/busy-maps-attend.md similarity index 100% rename from .changeset/busy-maps-attend.md rename to .changeset/pre/busy-maps-attend.md diff --git a/.changeset/pre/calm-atoms-relax.md b/.changeset/pre/calm-atoms-relax.md new file mode 100644 index 000000000..a7464d543 --- /dev/null +++ b/.changeset/pre/calm-atoms-relax.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-react": patch +--- + +Relax react peer dependency range diff --git a/.changeset/pre/calm-bash-completions.md b/.changeset/pre/calm-bash-completions.md new file mode 100644 index 000000000..ca77256ce --- /dev/null +++ b/.changeset/pre/calm-bash-completions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Select Bash completions for the active positional argument. diff --git a/.changeset/calm-buckets-own.md b/.changeset/pre/calm-buckets-own.md similarity index 100% rename from .changeset/calm-buckets-own.md rename to .changeset/pre/calm-buckets-own.md diff --git a/.changeset/pre/calm-buses-smile.md b/.changeset/pre/calm-buses-smile.md new file mode 100644 index 000000000..e7fe600ce --- /dev/null +++ b/.changeset/pre/calm-buses-smile.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix IndexedDB query range, ordering, streaming, and transaction semantics. diff --git a/.changeset/calm-carrots-march.md b/.changeset/pre/calm-carrots-march.md similarity index 100% rename from .changeset/calm-carrots-march.md rename to .changeset/pre/calm-carrots-march.md diff --git a/.changeset/calm-cars-rest.md b/.changeset/pre/calm-cars-rest.md similarity index 100% rename from .changeset/calm-cars-rest.md rename to .changeset/pre/calm-cars-rest.md diff --git a/.changeset/pre/calm-coins-smile.md b/.changeset/pre/calm-coins-smile.md new file mode 100644 index 000000000..973771855 --- /dev/null +++ b/.changeset/pre/calm-coins-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Generate even and odd safe integers in Crypto random APIs. diff --git a/.changeset/pre/calm-contexts-rest.md b/.changeset/pre/calm-contexts-rest.md new file mode 100644 index 000000000..1dede3ad9 --- /dev/null +++ b/.changeset/pre/calm-contexts-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve the `Context.mapUnsafe` accessor when code is compiled with loose object spread transforms. diff --git a/.changeset/pre/calm-dates-view.md b/.changeset/pre/calm-dates-view.md new file mode 100644 index 000000000..b9e963edb --- /dev/null +++ b/.changeset/pre/calm-dates-view.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Equal.equals` and `Hash.hash` to handle invalid dates and `DataView` values without throwing. diff --git a/.changeset/pre/calm-dragons-command.md b/.changeset/pre/calm-dragons-command.md new file mode 100644 index 000000000..d98e1edd6 --- /dev/null +++ b/.changeset/pre/calm-dragons-command.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct the runtime tag spelling for `CliError.UnknownSubcommand`. diff --git a/.changeset/pre/calm-graphs-span.md b/.changeset/pre/calm-graphs-span.md new file mode 100644 index 000000000..c53042d54 --- /dev/null +++ b/.changeset/pre/calm-graphs-span.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add deterministic, index-preserving `Graph.minimumSpanningForest`. diff --git a/.changeset/pre/calm-graphs-travel.md b/.changeset/pre/calm-graphs-travel.md new file mode 100644 index 000000000..85618505b --- /dev/null +++ b/.changeset/pre/calm-graphs-travel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix graph index exhaustion, A* path consistency, snapshot validation, Mermaid line endings, and topological initials. diff --git a/.changeset/pre/calm-heads-close.md b/.changeset/pre/calm-heads-close.md new file mode 100644 index 000000000..c717945ad --- /dev/null +++ b/.changeset/pre/calm-heads-close.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Ensure aborted `HEAD` responses do not block `NodeHttpServer` disposal. diff --git a/.changeset/pre/calm-hounds-smile.md b/.changeset/pre/calm-hounds-smile.md new file mode 100644 index 000000000..8f7a1640a --- /dev/null +++ b/.changeset/pre/calm-hounds-smile.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-clickhouse": patch +--- + +Parameterize ClickHouse query IDs when cancelling queries and inserts. diff --git a/.changeset/pre/calm-keys-repeat.md b/.changeset/pre/calm-keys-repeat.md new file mode 100644 index 000000000..82f669b0e --- /dev/null +++ b/.changeset/pre/calm-keys-repeat.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix SQL-backed Persistence `getMany` to preserve duplicate key positions. diff --git a/.changeset/pre/calm-masks-count.md b/.changeset/pre/calm-masks-count.md new file mode 100644 index 000000000..b70fc9ae8 --- /dev/null +++ b/.changeset/pre/calm-masks-count.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.DateFromMillis` and `SchemaTransformation.dateFromMillis` for decoding millisecond timestamps into `Date` values. diff --git a/.changeset/pre/calm-pages-hide.md b/.changeset/pre/calm-pages-hide.md new file mode 100644 index 000000000..22600772a --- /dev/null +++ b/.changeset/pre/calm-pages-hide.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Keep BrowserRuntime main programs running when navigation does not unload the document, while interrupting on non-bfcache pagehide events. diff --git a/.changeset/pre/calm-pandas-retry.md b/.changeset/pre/calm-pandas-retry.md new file mode 100644 index 000000000..b2372c6bd --- /dev/null +++ b/.changeset/pre/calm-pandas-retry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add bounded 429 retries and custom response header names to `HttpClient.withRateLimiter`. diff --git a/.changeset/calm-panthers-nail.md b/.changeset/pre/calm-panthers-nail.md similarity index 100% rename from .changeset/calm-panthers-nail.md rename to .changeset/pre/calm-panthers-nail.md diff --git a/.changeset/pre/calm-pears-smile.md b/.changeset/pre/calm-pears-smile.md new file mode 100644 index 000000000..a83413eea --- /dev/null +++ b/.changeset/pre/calm-pears-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Exclude disabled choices from multi-select prompt selection and submission. diff --git a/.changeset/pre/calm-queues-await.md b/.changeset/pre/calm-queues-await.md new file mode 100644 index 000000000..18ac80170 --- /dev/null +++ b/.changeset/pre/calm-queues-await.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Queue.await` failing with `Cause.Done` when registered before the queue ends. diff --git a/.changeset/pre/calm-ravens-reflect.md b/.changeset/pre/calm-ravens-reflect.md new file mode 100644 index 000000000..ba31e47c2 --- /dev/null +++ b/.changeset/pre/calm-ravens-reflect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `__proto__` group and endpoint identifiers in HTTP APIs, generated clients, and URL builders. diff --git a/.changeset/pre/calm-redis-clear.md b/.changeset/pre/calm-redis-clear.md new file mode 100644 index 000000000..a707eadd9 --- /dev/null +++ b/.changeset/pre/calm-redis-clear.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure clearing an empty Redis-backed persistence store succeeds. diff --git a/.changeset/pre/calm-results-align.md b/.changeset/pre/calm-results-align.md new file mode 100644 index 000000000..896040c70 --- /dev/null +++ b/.changeset/pre/calm-results-align.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep ordered SQL resolver results aligned when batched request encoding fails. diff --git a/.changeset/pre/calm-schemas-encode.md b/.changeset/pre/calm-schemas-encode.md new file mode 100644 index 000000000..6bec17848 --- /dev/null +++ b/.changeset/pre/calm-schemas-encode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the encoded output type of `TestSchema.Encoding.encodeUnknownEffect`. diff --git a/.changeset/calm-seas-smile.md b/.changeset/pre/calm-seas-smile.md similarity index 100% rename from .changeset/calm-seas-smile.md rename to .changeset/pre/calm-seas-smile.md diff --git a/.changeset/pre/calm-servers-share.md b/.changeset/pre/calm-servers-share.md new file mode 100644 index 000000000..504a98ce5 --- /dev/null +++ b/.changeset/pre/calm-servers-share.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update `McpServer.layerHttp` to return `405` for unsupported HTTP methods, reject unsupported `MCP-Protocol-Version` headers with `400`, and return an empty `202` for accepted notifications and responses. diff --git a/.changeset/pre/calm-services-rest.md b/.changeset/pre/calm-services-rest.md new file mode 100644 index 000000000..400dade63 --- /dev/null +++ b/.changeset/pre/calm-services-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Stop capturing definition-location stack frames in `Context.Service`. diff --git a/.changeset/calm-squids-hug.md b/.changeset/pre/calm-squids-hug.md similarity index 100% rename from .changeset/calm-squids-hug.md rename to .changeset/pre/calm-squids-hug.md diff --git a/.changeset/pre/calm-tools-parse.md b/.changeset/pre/calm-tools-parse.md new file mode 100644 index 000000000..779bde415 --- /dev/null +++ b/.changeset/pre/calm-tools-parse.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add lightweight INI, YAML, and TOML parsers under `effect/unstable/encoding` and remove their runtime dependencies. diff --git a/.changeset/pre/calm-tools-remember.md b/.changeset/pre/calm-tools-remember.md new file mode 100644 index 000000000..3fdf6410e --- /dev/null +++ b/.changeset/pre/calm-tools-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve equals signs in inline CLI option values after the first separator. diff --git a/.changeset/calm-tracers-sample.md b/.changeset/pre/calm-tracers-sample.md similarity index 100% rename from .changeset/calm-tracers-sample.md rename to .changeset/pre/calm-tracers-sample.md diff --git a/.changeset/pre/calm-tuples-align.md b/.changeset/pre/calm-tuples-align.md new file mode 100644 index 000000000..b62d5bdc4 --- /dev/null +++ b/.changeset/pre/calm-tuples-align.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix arbitrary generation for tuples with multiple optional elements. diff --git a/.changeset/pre/calm-tuples-pick.md b/.changeset/pre/calm-tuples-pick.md new file mode 100644 index 000000000..0422f5814 --- /dev/null +++ b/.changeset/pre/calm-tuples-pick.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Tuple.pick` return types to preserve the requested index order and duplicate indices. diff --git a/.changeset/pre/calm-wolves-reduce.md b/.changeset/pre/calm-wolves-reduce.md new file mode 100644 index 000000000..1b2556456 --- /dev/null +++ b/.changeset/pre/calm-wolves-reduce.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Sink.reduceWhileArray` applying its reducer more than once per input array. diff --git a/.changeset/pre/cancel-tedious-requests.md b/.changeset/pre/cancel-tedious-requests.md new file mode 100644 index 000000000..9b9ce459e --- /dev/null +++ b/.changeset/pre/cancel-tedious-requests.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-mssql": patch +--- + +Cancel in-flight Tedious requests when their Effects are interrupted. diff --git a/.changeset/pre/canonical-number-schemas.md b/.changeset/pre/canonical-number-schemas.md new file mode 100644 index 000000000..96fceebf4 --- /dev/null +++ b/.changeset/pre/canonical-number-schemas.md @@ -0,0 +1,12 @@ +--- +"effect": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +"@effect/ai-openrouter": patch +"@effect/openapi-generator": patch +--- + +Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + +Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. diff --git a/.changeset/pre/cap-rpc-streaming-buffers.md b/.changeset/pre/cap-rpc-streaming-buffers.md new file mode 100644 index 000000000..4f67ff43b --- /dev/null +++ b/.changeset/pre/cap-rpc-streaming-buffers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Cap incomplete RPC frames buffered by the NDJSON and MessagePack streaming decoders, and close socket transports when the limit is exceeded. diff --git a/.changeset/chatty-poets-type.md b/.changeset/pre/chatty-poets-type.md similarity index 100% rename from .changeset/chatty-poets-type.md rename to .changeset/pre/chatty-poets-type.md diff --git a/.changeset/chilled-mice-wash.md b/.changeset/pre/chilled-mice-wash.md similarity index 100% rename from .changeset/chilled-mice-wash.md rename to .changeset/pre/chilled-mice-wash.md diff --git a/.changeset/chilly-pumas-rule.md b/.changeset/pre/chilly-pumas-rule.md similarity index 100% rename from .changeset/chilly-pumas-rule.md rename to .changeset/pre/chilly-pumas-rule.md diff --git a/.changeset/chubby-buckets-feel.md b/.changeset/pre/chubby-buckets-feel.md similarity index 100% rename from .changeset/chubby-buckets-feel.md rename to .changeset/pre/chubby-buckets-feel.md diff --git a/.changeset/chubby-parents-flow.md b/.changeset/pre/chubby-parents-flow.md similarity index 100% rename from .changeset/chubby-parents-flow.md rename to .changeset/pre/chubby-parents-flow.md diff --git a/.changeset/chubby-planets-fall.md b/.changeset/pre/chubby-planets-fall.md similarity index 100% rename from .changeset/chubby-planets-fall.md rename to .changeset/pre/chubby-planets-fall.md diff --git a/.changeset/clean-balloons-tan.md b/.changeset/pre/clean-balloons-tan.md similarity index 100% rename from .changeset/clean-balloons-tan.md rename to .changeset/pre/clean-balloons-tan.md diff --git a/.changeset/clean-bulldogs-care.md b/.changeset/pre/clean-bulldogs-care.md similarity index 100% rename from .changeset/clean-bulldogs-care.md rename to .changeset/pre/clean-bulldogs-care.md diff --git a/.changeset/pre/clean-cats-document.md b/.changeset/pre/clean-cats-document.md new file mode 100644 index 000000000..b2d26821d --- /dev/null +++ b/.changeset/pre/clean-cats-document.md @@ -0,0 +1,5 @@ +--- +"@effect/docgen": major +--- + +Migrate `@effect/docgen` into the Effect monorepo and update it to Effect 4 while retaining existing behavior. diff --git a/.changeset/clean-dryers-sneeze.md b/.changeset/pre/clean-dryers-sneeze.md similarity index 100% rename from .changeset/clean-dryers-sneeze.md rename to .changeset/pre/clean-dryers-sneeze.md diff --git a/.changeset/pre/clean-formatters-agree.md b/.changeset/pre/clean-formatters-agree.md new file mode 100644 index 000000000..2fea77679 --- /dev/null +++ b/.changeset/pre/clean-formatters-agree.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prioritize redacted representations in formatters and normalize text logger levels to uppercase. diff --git a/.changeset/clean-geese-work.md b/.changeset/pre/clean-geese-work.md similarity index 100% rename from .changeset/clean-geese-work.md rename to .changeset/pre/clean-geese-work.md diff --git a/.changeset/clean-goats-wave.md b/.changeset/pre/clean-goats-wave.md similarity index 100% rename from .changeset/clean-goats-wave.md rename to .changeset/pre/clean-goats-wave.md diff --git a/.changeset/pre/clean-lions-cancel.md b/.changeset/pre/clean-lions-cancel.md new file mode 100644 index 000000000..f725e3d19 --- /dev/null +++ b/.changeset/pre/clean-lions-cancel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +RPC servers now suppress responses after a client cancels an in-flight request. diff --git a/.changeset/clean-needles-shake.md b/.changeset/pre/clean-needles-shake.md similarity index 100% rename from .changeset/clean-needles-shake.md rename to .changeset/pre/clean-needles-shake.md diff --git a/.changeset/clean-tires-guess.md b/.changeset/pre/clean-tires-guess.md similarity index 100% rename from .changeset/clean-tires-guess.md rename to .changeset/pre/clean-tires-guess.md diff --git a/.changeset/pre/clear-graphs-reduce.md b/.changeset/pre/clear-graphs-reduce.md new file mode 100644 index 000000000..0341ecbe6 --- /dev/null +++ b/.changeset/pre/clear-graphs-reduce.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add index-preserving transitive reduction for directed acyclic graphs. diff --git a/.changeset/pre/clear-hairs-pump.md b/.changeset/pre/clear-hairs-pump.md new file mode 100644 index 000000000..ac42927cd --- /dev/null +++ b/.changeset/pre/clear-hairs-pump.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix activity retry policy diff --git a/.changeset/clear-spies-boil.md b/.changeset/pre/clear-spies-boil.md similarity index 100% rename from .changeset/clear-spies-boil.md rename to .changeset/pre/clear-spies-boil.md diff --git a/.changeset/pre/clever-maps-care.md b/.changeset/pre/clever-maps-care.md new file mode 100644 index 000000000..51de31746 --- /dev/null +++ b/.changeset/pre/clever-maps-care.md @@ -0,0 +1,8 @@ +--- +"@effect/platform-deno": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +"effect": patch +--- + +add platform literal to HttpPlatform diff --git a/.changeset/pre/cli-config-built-ins.md b/.changeset/pre/cli-config-built-ins.md new file mode 100644 index 000000000..6e0dbcd40 --- /dev/null +++ b/.changeset/pre/cli-config-built-ins.md @@ -0,0 +1,24 @@ +--- +"effect": patch +--- + +Add a scoped `CliConfig` service for customizing the built-in global flags used by CLI command runners. + +For example, provide an explicit list that omits `GlobalFlag.LogLevel` to remove the built-in `--log-level` flag: + +```ts +import { Effect } from "effect" +import { CliConfig, Command, GlobalFlag } from "effect/unstable/cli" + +const program = Command.run(command, { version: "1.0.0" }).pipe( + Effect.provide( + CliConfig.layer({ + builtIns: [ + GlobalFlag.Help, + GlobalFlag.Version, + GlobalFlag.Completions + ] + }) + ) +) +``` diff --git a/.changeset/cli-help-choices.md b/.changeset/pre/cli-help-choices.md similarity index 100% rename from .changeset/cli-help-choices.md rename to .changeset/pre/cli-help-choices.md diff --git a/.changeset/pre/cli-no-color-values.md b/.changeset/pre/cli-no-color-values.md new file mode 100644 index 000000000..084859b63 --- /dev/null +++ b/.changeset/pre/cli-no-color-values.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Disable CLI formatter colors for every non-empty `NO_COLOR` value. diff --git a/.changeset/pre/cli-wizard-mode.md b/.changeset/pre/cli-wizard-mode.md new file mode 100644 index 000000000..0b7c9f2d6 --- /dev/null +++ b/.changeset/pre/cli-wizard-mode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reintroduce interactive CLI wizard mode through the `--wizard` flag and `Command.wizard`. diff --git a/.changeset/pre/close-failed-resource-map-scopes.md b/.changeset/pre/close-failed-resource-map-scopes.md new file mode 100644 index 000000000..2242910a8 --- /dev/null +++ b/.changeset/pre/close-failed-resource-map-scopes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Close `ResourceMap` acquisition scopes when a lookup fails. diff --git a/.changeset/cold-knives-lie.md b/.changeset/pre/cold-knives-lie.md similarity index 100% rename from .changeset/cold-knives-lie.md rename to .changeset/pre/cold-knives-lie.md diff --git a/.changeset/cold-rooms-show.md b/.changeset/pre/cold-rooms-show.md similarity index 100% rename from .changeset/cold-rooms-show.md rename to .changeset/pre/cold-rooms-show.md diff --git a/.changeset/cold-sloths-wave.md b/.changeset/pre/cold-sloths-wave.md similarity index 100% rename from .changeset/cold-sloths-wave.md rename to .changeset/pre/cold-sloths-wave.md diff --git a/.changeset/pre/common-mammals-tickle.md b/.changeset/pre/common-mammals-tickle.md new file mode 100644 index 000000000..510898292 --- /dev/null +++ b/.changeset/pre/common-mammals-tickle.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Fix generation order for recursive schemas referenced by earlier recursive definitions, closes #6357. diff --git a/.changeset/pre/compact-json-schema-checks.md b/.changeset/pre/compact-json-schema-checks.md new file mode 100644 index 000000000..957c58997 --- /dev/null +++ b/.changeset/pre/compact-json-schema-checks.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Compact JSON Schema check constraints when they can be safely merged without keyword collisions. diff --git a/.changeset/compact-json-schema-enum.md b/.changeset/pre/compact-json-schema-enum.md similarity index 100% rename from .changeset/compact-json-schema-enum.md rename to .changeset/pre/compact-json-schema-enum.md diff --git a/.changeset/pre/config-provider-option-lookup.md b/.changeset/pre/config-provider-option-lookup.md new file mode 100644 index 000000000..a671ebb39 --- /dev/null +++ b/.changeset/pre/config-provider-option-lookup.md @@ -0,0 +1,15 @@ +--- +"effect": patch +--- + +Refine the `ConfigProvider` interface so lookup absence uses `undefined` and +path transformation is provider behavior. + +`ConfigProvider.load` and the lookup function accepted by +`ConfigProvider.make` now return `Node | undefined`. Use `undefined` when a path +does not exist and return the `Node` directly when it does. + +`ConfigProvider` now exposes `mapInput` as a capability. The exported +`ConfigProvider.mapInput` combinator delegates to it, preserving transformation +order and composition through `orElse` without requiring provider +representation state. diff --git a/.changeset/config-withdefault-eager.md b/.changeset/pre/config-withdefault-eager.md similarity index 100% rename from .changeset/config-withdefault-eager.md rename to .changeset/pre/config-withdefault-eager.md diff --git a/.changeset/pre/configurable-schema-reference-policy.md b/.changeset/pre/configurable-schema-reference-policy.md new file mode 100644 index 000000000..c46a7a543 --- /dev/null +++ b/.changeset/pre/configurable-schema-reference-policy.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add configurable schema representation reference policies and propagate them through JSON Schema and OpenAPI generation. By default, only schemas with resolved identifiers become references. Closes #7357. diff --git a/.changeset/pre/configure-cluster-rpc-buffer-limits.md b/.changeset/pre/configure-cluster-rpc-buffer-limits.md new file mode 100644 index 000000000..76c3172e7 --- /dev/null +++ b/.changeset/pre/configure-cluster-rpc-buffer-limits.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-node": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +--- + +Allow configuring cluster RPC serialization buffer limits. diff --git a/.changeset/consolidate-encoding.md b/.changeset/pre/consolidate-encoding.md similarity index 100% rename from .changeset/consolidate-encoding.md rename to .changeset/pre/consolidate-encoding.md diff --git a/.changeset/consolidate-sql-error.md b/.changeset/pre/consolidate-sql-error.md similarity index 100% rename from .changeset/consolidate-sql-error.md rename to .changeset/pre/consolidate-sql-error.md diff --git a/.changeset/pre/cozy-geese-remain.md b/.changeset/pre/cozy-geese-remain.md new file mode 100644 index 000000000..52e90cd08 --- /dev/null +++ b/.changeset/pre/cozy-geese-remain.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix scoped reentrant lock finalizers releasing under the wrong fiber owner. diff --git a/.changeset/crisp-seas-warn.md b/.changeset/pre/crisp-seas-warn.md similarity index 100% rename from .changeset/crisp-seas-warn.md rename to .changeset/pre/crisp-seas-warn.md diff --git a/.changeset/pre/cron-locale-independent-aliases.md b/.changeset/pre/cron-locale-independent-aliases.md new file mode 100644 index 000000000..9b84a1ced --- /dev/null +++ b/.changeset/pre/cron-locale-independent-aliases.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize cron month and weekday aliases independently of the host locale. diff --git a/.changeset/pre/cron-single-value-step.md b/.changeset/pre/cron-single-value-step.md new file mode 100644 index 000000000..02a9fff68 --- /dev/null +++ b/.changeset/pre/cron-single-value-step.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow cron fields like `5/15` to expand from the starting value through the field maximum. diff --git a/.changeset/pre/cron-testclock-infinity.md b/.changeset/pre/cron-testclock-infinity.md new file mode 100644 index 000000000..3aea00e89 --- /dev/null +++ b/.changeset/pre/cron-testclock-infinity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schedule.cron` when the test clock is adjusted to infinity. diff --git a/.changeset/cuddly-rooms-bet.md b/.changeset/pre/cuddly-rooms-bet.md similarity index 100% rename from .changeset/cuddly-rooms-bet.md rename to .changeset/pre/cuddly-rooms-bet.md diff --git a/.changeset/pre/curly-files-range.md b/.changeset/pre/curly-files-range.md new file mode 100644 index 000000000..aa2a20ade --- /dev/null +++ b/.changeset/pre/curly-files-range.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Apply byte range and chunk size options to default Web file responses. diff --git a/.changeset/pre/curly-graphs-connect.md b/.changeset/pre/curly-graphs-connect.md new file mode 100644 index 000000000..37f344445 --- /dev/null +++ b/.changeset/pre/curly-graphs-connect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add graph snapshots, low-link connectivity analysis, bipartite matching, maximum flow, and minimum cut APIs. diff --git a/.changeset/pre/curly-graphs-snapshot.md b/.changeset/pre/curly-graphs-snapshot.md new file mode 100644 index 000000000..7c56a6daf --- /dev/null +++ b/.changeset/pre/curly-graphs-snapshot.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Graph.Snapshot` and `Graph.fromSnapshot` for constructing immutable graphs with explicit node and edge indexes, and simplify `Graph.Edge` to a type-only structural interface. diff --git a/.changeset/curly-poems-talk.md b/.changeset/pre/curly-poems-talk.md similarity index 100% rename from .changeset/curly-poems-talk.md rename to .changeset/pre/curly-poems-talk.md diff --git a/.changeset/pre/curly-ravens-decode.md b/.changeset/pre/curly-ravens-decode.md new file mode 100644 index 000000000..f8af5ea82 --- /dev/null +++ b/.changeset/pre/curly-ravens-decode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Decode percent-encoded OTLP environment header values. diff --git a/.changeset/curly-spies-relax.md b/.changeset/pre/curly-spies-relax.md similarity index 100% rename from .changeset/curly-spies-relax.md rename to .changeset/pre/curly-spies-relax.md diff --git a/.changeset/pre/curly-streams-stop.md b/.changeset/pre/curly-streams-stop.md new file mode 100644 index 000000000..e2a08a9df --- /dev/null +++ b/.changeset/pre/curly-streams-stop.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Interrupt in-flight stream pulls when closing an async iterator. diff --git a/.changeset/curvy-apples-float.md b/.changeset/pre/curvy-apples-float.md similarity index 100% rename from .changeset/curvy-apples-float.md rename to .changeset/pre/curvy-apples-float.md diff --git a/.changeset/curvy-birds-float.md b/.changeset/pre/curvy-birds-float.md similarity index 100% rename from .changeset/curvy-birds-float.md rename to .changeset/pre/curvy-birds-float.md diff --git a/.changeset/pre/curvy-graphs-code.md b/.changeset/pre/curvy-graphs-code.md new file mode 100644 index 000000000..758df43ff --- /dev/null +++ b/.changeset/pre/curvy-graphs-code.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.Graph` for schema-based encoding and decoding of immutable directed and undirected graphs. diff --git a/.changeset/pre/curvy-melons-stare.md b/.changeset/pre/curvy-melons-stare.md new file mode 100644 index 000000000..c9dd27576 --- /dev/null +++ b/.changeset/pre/curvy-melons-stare.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix MCP sampling metadata optionality and validate it as an object. diff --git a/.changeset/pre/curvy-otters-sync.md b/.changeset/pre/curvy-otters-sync.md new file mode 100644 index 000000000..aa98e610d --- /dev/null +++ b/.changeset/pre/curvy-otters-sync.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Retry transient EventLog remote write failures so pending local entries are synchronized after recovery. diff --git a/.changeset/pre/custom-cli-prompt-prefix.md b/.changeset/pre/custom-cli-prompt-prefix.md new file mode 100644 index 000000000..e6c00a55a --- /dev/null +++ b/.changeset/pre/custom-cli-prompt-prefix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow customizing the prefix displayed by CLI prompts. diff --git a/.changeset/custom-http-security-openapi-generator.md b/.changeset/pre/custom-http-security-openapi-generator.md similarity index 100% rename from .changeset/custom-http-security-openapi-generator.md rename to .changeset/pre/custom-http-security-openapi-generator.md diff --git a/.changeset/cute-heads-thank.md b/.changeset/pre/cute-heads-thank.md similarity index 100% rename from .changeset/cute-heads-thank.md rename to .changeset/pre/cute-heads-thank.md diff --git a/.changeset/cyan-loops-grow.md b/.changeset/pre/cyan-loops-grow.md similarity index 100% rename from .changeset/cyan-loops-grow.md rename to .changeset/pre/cyan-loops-grow.md diff --git a/.changeset/cyan-radios-switch.md b/.changeset/pre/cyan-radios-switch.md similarity index 100% rename from .changeset/cyan-radios-switch.md rename to .changeset/pre/cyan-radios-switch.md diff --git a/.changeset/pre/cyan-shirts-grin.md b/.changeset/pre/cyan-shirts-grin.md new file mode 100644 index 000000000..96d0ce0d7 --- /dev/null +++ b/.changeset/pre/cyan-shirts-grin.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fork memo map on nested builds diff --git a/.changeset/pre/d1-batch-statements.md b/.changeset/pre/d1-batch-statements.md new file mode 100644 index 000000000..779a545b7 --- /dev/null +++ b/.changeset/pre/d1-batch-statements.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-d1": minor +--- + +Add `D1Client.batch` for executing a collection of SQL statements as a single atomic D1 batch. diff --git a/.changeset/pre/dark-dancers-rule.md b/.changeset/pre/dark-dancers-rule.md new file mode 100644 index 000000000..e46ea884d --- /dev/null +++ b/.changeset/pre/dark-dancers-rule.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use Context mapUnsafe in less call sites diff --git a/.changeset/pre/deduplicate-json-schema-fallbacks.md b/.changeset/pre/deduplicate-json-schema-fallbacks.md new file mode 100644 index 000000000..1c5aa3349 --- /dev/null +++ b/.changeset/pre/deduplicate-json-schema-fallbacks.md @@ -0,0 +1,10 @@ +--- +"effect": patch +"@effect/openapi-generator": patch +--- + +Deduplicate equivalent fallback definitions when compiling JSON Schema, and reconstruct only definitions reachable from multi-document roots. + +Remove `SchemaMultiDocument` and `fromSchemaMultiDocument`; multi-document import and revival now return the ordered root schemas directly. + +Stop the OpenAPI generator from emitting component schemas that are not reachable from a generated root. diff --git a/.changeset/deep-rivers-spend.md b/.changeset/pre/deep-rivers-spend.md similarity index 100% rename from .changeset/deep-rivers-spend.md rename to .changeset/pre/deep-rivers-spend.md diff --git a/.changeset/pre/deferred-cleanup-after-completion.md b/.changeset/pre/deferred-cleanup-after-completion.md new file mode 100644 index 000000000..4434455d5 --- /dev/null +++ b/.changeset/pre/deferred-cleanup-after-completion.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Deferred.await` dying with a `TypeError` when a waiter is interrupted after the `Deferred` has been completed. diff --git a/.changeset/pre/deferred-resume-starvation.md b/.changeset/pre/deferred-resume-starvation.md new file mode 100644 index 000000000..9a30bd76d --- /dev/null +++ b/.changeset/pre/deferred-resume-starvation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Deferred` completion skipping waiters when an earlier waiter dies during resume. Completing a `Deferred` with an interrupt cause kills a suspended waiter synchronously inside its resume; the dying waiter's `await` cleanup spliced the shared `resumes` array mid-iteration, so the next waiter was never resumed and hung forever. Completion now clears `resumes` before resuming waiters. diff --git a/.changeset/pre/deno-write-copy-errors.md b/.changeset/pre/deno-write-copy-errors.md new file mode 100644 index 000000000..2b93ed38e --- /dev/null +++ b/.changeset/pre/deno-write-copy-errors.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Preserve high-level filesystem error context for `writeFile` and normalize Deno `AlreadyExists` errors from `copy`. diff --git a/.changeset/dirty-lamps-trade.md b/.changeset/pre/dirty-lamps-trade.md similarity index 100% rename from .changeset/dirty-lamps-trade.md rename to .changeset/pre/dirty-lamps-trade.md diff --git a/.changeset/dirty-laws-wear.md b/.changeset/pre/dirty-laws-wear.md similarity index 100% rename from .changeset/dirty-laws-wear.md rename to .changeset/pre/dirty-laws-wear.md diff --git a/.changeset/pre/doctest-console-output.md b/.changeset/pre/doctest-console-output.md new file mode 100644 index 000000000..cd06cfddb --- /dev/null +++ b/.changeset/pre/doctest-console-output.md @@ -0,0 +1,5 @@ +--- +"@effect/doctest": patch +--- + +Add convention-based `// =>` assertions that compare documentation example values using Effect equality. diff --git a/.changeset/pre/document-child-process-env.md b/.changeset/pre/document-child-process-env.md new file mode 100644 index 000000000..f28d72881 --- /dev/null +++ b/.changeset/pre/document-child-process-env.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Document that `CommandOptions.extendEnv` defaults to `false` and that providing `env` without enabling it replaces the inherited child environment. diff --git a/.changeset/pre/dry-bugs-hug.md b/.changeset/pre/dry-bugs-hug.md new file mode 100644 index 000000000..723f24935 --- /dev/null +++ b/.changeset/pre/dry-bugs-hug.md @@ -0,0 +1,15 @@ +--- +"@effect/atom-react": patch +"@effect/atom-solid": patch +"@effect/atom-vue": patch +"@effect/docgen": patch +"@effect/doctest": patch +"@effect/opentelemetry": patch +"@effect/platform-node": patch +"@effect/sql-pg": patch +"@effect/sql-sqlite-react-native": patch +"@effect/sql-sqlite-wasm": patch +"@effect/vitest": patch +--- + +Update peer dependencies diff --git a/.changeset/duration-temporal-object-input.md b/.changeset/pre/duration-temporal-object-input.md similarity index 100% rename from .changeset/duration-temporal-object-input.md rename to .changeset/pre/duration-temporal-object-input.md diff --git a/.changeset/eager-coats-cheat.md b/.changeset/pre/eager-coats-cheat.md similarity index 100% rename from .changeset/eager-coats-cheat.md rename to .changeset/pre/eager-coats-cheat.md diff --git a/.changeset/early-birds-dream.md b/.changeset/pre/early-birds-dream.md similarity index 100% rename from .changeset/early-birds-dream.md rename to .changeset/pre/early-birds-dream.md diff --git a/.changeset/early-donuts-argue.md b/.changeset/pre/early-donuts-argue.md similarity index 100% rename from .changeset/early-donuts-argue.md rename to .changeset/pre/early-donuts-argue.md diff --git a/.changeset/pre/early-jobs-bow.md b/.changeset/pre/early-jobs-bow.md new file mode 100644 index 000000000..f62abe015 --- /dev/null +++ b/.changeset/pre/early-jobs-bow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Defer memoized Layer state installation until Effect execution. diff --git a/.changeset/early-peaches-check.md b/.changeset/pre/early-peaches-check.md similarity index 100% rename from .changeset/early-peaches-check.md rename to .changeset/pre/early-peaches-check.md diff --git a/.changeset/pre/eff-115-sync-scheduler-microtask.md b/.changeset/pre/eff-115-sync-scheduler-microtask.md new file mode 100644 index 000000000..f0d120781 --- /dev/null +++ b/.changeset/pre/eff-115-sync-scheduler-microtask.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use cancellable microtasks when dispatching yielded work from synchronous Effect runs. diff --git a/.changeset/pre/eff-117-hydration-reactivity.md b/.changeset/pre/eff-117-hydration-reactivity.md new file mode 100644 index 000000000..948003938 --- /dev/null +++ b/.changeset/pre/eff-117-hydration-reactivity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix hydrated atoms with `Atom.withReactivity` to refresh after reactive mutations. diff --git a/.changeset/pre/eff-121-http-router-web-handler.md b/.changeset/pre/eff-121-http-router-web-handler.md new file mode 100644 index 000000000..4576cfec2 --- /dev/null +++ b/.changeset/pre/eff-121-http-router-web-handler.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpRouter.toWebHandler` context inference for services provided by the application layer. diff --git a/.changeset/pre/eff-123-openai-compat-unknown-events.md b/.changeset/pre/eff-123-openai-compat-unknown-events.md new file mode 100644 index 000000000..0d587a2b8 --- /dev/null +++ b/.changeset/pre/eff-123-openai-compat-unknown-events.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Surface parsed chat completion stream events that do not match the expected schema as `UnknownChatCompletionEvent`. diff --git a/.changeset/pre/eff-137-web-stream-interop.md b/.changeset/pre/eff-137-web-stream-interop.md new file mode 100644 index 000000000..d3f164a89 --- /dev/null +++ b/.changeset/pre/eff-137-web-stream-interop.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add Web Stream interoperability for `Channel` and `Sink`, plus byte limiting and `ArrayBuffer` collection for `Stream`. diff --git a/.changeset/pre/eff-140-deno-crypto.md b/.changeset/pre/eff-140-deno-crypto.md new file mode 100644 index 000000000..1ea8d267a --- /dev/null +++ b/.changeset/pre/eff-140-deno-crypto.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a Deno Web Crypto implementation of the `Crypto` service. diff --git a/.changeset/pre/eff-141-deno-child-process.md b/.changeset/pre/eff-141-deno-child-process.md new file mode 100644 index 000000000..746c3ff42 --- /dev/null +++ b/.changeset/pre/eff-141-deno-child-process.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a native Deno `ChildProcessSpawner` implementation and shared process conformance coverage. diff --git a/.changeset/pre/eff-142-deno-terminal.md b/.changeset/pre/eff-142-deno-terminal.md new file mode 100644 index 000000000..b7eb5a35a --- /dev/null +++ b/.changeset/pre/eff-142-deno-terminal.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-deno": patch +"@effect/platform-node-shared": patch +--- + +Add a Deno `Terminal` implementation and keep `NodeTerminal` input readers alive until stdin ends under Deno. diff --git a/.changeset/pre/eff-143-deno-stdio.md b/.changeset/pre/eff-143-deno-stdio.md new file mode 100644 index 000000000..c46576d61 --- /dev/null +++ b/.changeset/pre/eff-143-deno-stdio.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a native Deno implementation of the `Stdio` service. diff --git a/.changeset/pre/eff-145-deno-services.md b/.changeset/pre/eff-145-deno-services.md new file mode 100644 index 000000000..c9ef16084 --- /dev/null +++ b/.changeset/pre/eff-145-deno-services.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add the aggregate Deno platform services layer. diff --git a/.changeset/pre/eff-148-deno-http-platform.md b/.changeset/pre/eff-148-deno-http-platform.md new file mode 100644 index 000000000..62766839f --- /dev/null +++ b/.changeset/pre/eff-148-deno-http-platform.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a native Deno `HttpPlatform` layer with resource-backed file responses. diff --git a/.changeset/pre/eff-151-deno-redis.md b/.changeset/pre/eff-151-deno-redis.md new file mode 100644 index 000000000..292d3fed1 --- /dev/null +++ b/.changeset/pre/eff-151-deno-redis.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a native Deno Redis integration backed by `@db/redis`. diff --git a/.changeset/pre/eff-153-deno-http-server.md b/.changeset/pre/eff-153-deno-http-server.md new file mode 100644 index 000000000..a2e261164 --- /dev/null +++ b/.changeset/pre/eff-153-deno-http-server.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add a native Deno HTTP server with multipart requests, file responses, and WebSocket upgrades. diff --git a/.changeset/pre/eff-153-websocket-initial-frames.md b/.changeset/pre/eff-153-websocket-initial-frames.md new file mode 100644 index 000000000..1f1142a06 --- /dev/null +++ b/.changeset/pre/eff-153-websocket-initial-frames.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support replaying initial WebSocket messages and normalize `ArrayBuffer` frames to `Uint8Array`. diff --git a/.changeset/pre/eff-154-deno-cluster-http.md b/.changeset/pre/eff-154-deno-cluster-http.md new file mode 100644 index 000000000..36d032895 --- /dev/null +++ b/.changeset/pre/eff-154-deno-cluster-http.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add native Deno HTTP and WebSocket layers for Effect Cluster runners. diff --git a/.changeset/pre/eff-155-deno-cluster-socket.md b/.changeset/pre/eff-155-deno-cluster-socket.md new file mode 100644 index 000000000..b079d9319 --- /dev/null +++ b/.changeset/pre/eff-155-deno-cluster-socket.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Add native Deno socket layers for Effect Cluster runners. diff --git a/.changeset/pre/eff-162-browser-crypto-chunks.md b/.changeset/pre/eff-162-browser-crypto-chunks.md new file mode 100644 index 000000000..fb93845c9 --- /dev/null +++ b/.changeset/pre/eff-162-browser-crypto-chunks.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix `BrowserCrypto.randomBytes` for requests larger than the Web Crypto per-call limit. diff --git a/.changeset/pre/eff-170-bun-multipart-stream.md b/.changeset/pre/eff-170-bun-multipart-stream.md new file mode 100644 index 000000000..ccc6b55a9 --- /dev/null +++ b/.changeset/pre/eff-170-bun-multipart-stream.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Construct an empty multipart stream for each bodiless Bun request. diff --git a/.changeset/pre/eff-210-cookie-validation.md b/.changeset/pre/eff-210-cookie-validation.md new file mode 100644 index 000000000..7050ceb33 --- /dev/null +++ b/.changeset/pre/eff-210-cookie-validation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Validate cookie names, domains, and paths before constructing or serializing cookies. diff --git a/.changeset/pre/eff-212-secure-mssql-transport.md b/.changeset/pre/eff-212-secure-mssql-transport.md new file mode 100644 index 000000000..f662266f9 --- /dev/null +++ b/.changeset/pre/eff-212-secure-mssql-transport.md @@ -0,0 +1,7 @@ +--- +"@effect/sql-mssql": patch +--- + +**Breaking:** Secure Microsoft SQL Server connections by default by enabling encryption and validating server certificates. + +Users connecting to SQL Server instances without TLS must now explicitly set `encrypt: false`. Users connecting with untrusted or self-signed certificates must explicitly set `trustServer: true`. diff --git a/.changeset/pre/eff-216-secure-http-redirects.md b/.changeset/pre/eff-216-secure-http-redirects.md new file mode 100644 index 000000000..fbfe9fde4 --- /dev/null +++ b/.changeset/pre/eff-216-secure-http-redirects.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Strip credential headers on cross-origin HTTP redirects and align redirected request methods with fetch. diff --git a/.changeset/pre/eff-218-bound-sse-pending-state.md b/.changeset/pre/eff-218-bound-sse-pending-state.md new file mode 100644 index 000000000..be71140d5 --- /dev/null +++ b/.changeset/pre/eff-218-bound-sse-pending-state.md @@ -0,0 +1,10 @@ +--- +"effect": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +"@effect/ai-openrouter": patch +"@effect/openapi-generator": patch +--- + +Bound pending SSE decoder state with a configurable maximum event size. diff --git a/.changeset/pre/eff-219-key-value-store-file-keys.md b/.changeset/pre/eff-219-key-value-store-file-keys.md new file mode 100644 index 000000000..ea7b27d88 --- /dev/null +++ b/.changeset/pre/eff-219-key-value-store-file-keys.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject empty, `.` and `..` keys in file-backed key-value stores. diff --git a/.changeset/pre/eff-220-cli-control-characters.md b/.changeset/pre/eff-220-cli-control-characters.md new file mode 100644 index 000000000..7073b7cb3 --- /dev/null +++ b/.changeset/pre/eff-220-cli-control-characters.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Escape terminal control characters in unstable CLI error output. diff --git a/.changeset/pre/eff-332-http-response-compression.md b/.changeset/pre/eff-332-http-response-compression.md new file mode 100644 index 000000000..831d4f07b --- /dev/null +++ b/.changeset/pre/eff-332-http-response-compression.md @@ -0,0 +1,10 @@ +--- +"effect": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +--- + +Add HTTP response compression support. Node.js, Bun, and Deno use asynchronous +`node:zlib` one-shot compression for byte-array bodies, preserving an exact +`Content-Length`; stream and raw bodies remain streaming transforms. diff --git a/.changeset/pre/eff-337-preserve-mssql-parameters.md b/.changeset/pre/eff-337-preserve-mssql-parameters.md new file mode 100644 index 000000000..903a8ed19 --- /dev/null +++ b/.changeset/pre/eff-337-preserve-mssql-parameters.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-mssql": patch +--- + +Preserve fractional numbers and Unicode strings in default Microsoft SQL Server parameters. diff --git a/.changeset/pre/eff-342-clickhouse-number-binding.md b/.changeset/pre/eff-342-clickhouse-number-binding.md new file mode 100644 index 000000000..847d5b566 --- /dev/null +++ b/.changeset/pre/eff-342-clickhouse-number-binding.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-clickhouse": patch +--- + +Preserve fractional JavaScript numbers in inferred ClickHouse parameters. diff --git a/.changeset/pre/eff-389-execution-plan-attempts.md b/.changeset/pre/eff-389-execution-plan-attempts.md new file mode 100644 index 000000000..dc4262d0c --- /dev/null +++ b/.changeset/pre/eff-389-execution-plan-attempts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject zero execution attempts in `ExecutionPlan` steps. diff --git a/.changeset/pre/eff-428-pg-transaction-permit.md b/.changeset/pre/eff-428-pg-transaction-permit.md new file mode 100644 index 000000000..be6a9a20e --- /dev/null +++ b/.changeset/pre/eff-428-pg-transaction-permit.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Hold the shared PostgreSQL client permit for the full transaction lifetime. diff --git a/.changeset/pre/eff-467-execution-plan-events.md b/.changeset/pre/eff-467-execution-plan-events.md new file mode 100644 index 000000000..eae1e3bec --- /dev/null +++ b/.changeset/pre/eff-467-execution-plan-events.md @@ -0,0 +1,17 @@ +--- +"effect": patch +--- + +Add execution-plan lifecycle events via an optional `onEvent` handler on `Effect.withExecutionPlan` and `Stream.withExecutionPlan`. + +The handler receives an `ExecutionPlan.Event`, a tagged union of `AttemptStart`, `AttemptSuccess`, and `AttemptFailure`, allowing attempt outcomes to be observed from outside the effect for logging and metrics: + +```ts +import { Effect } from "effect" + +Effect.withExecutionPlan(program, plan, { + onEvent: (event) => Effect.log("execution plan event", event) +}) +``` + +Every `AttemptStart` is followed by exactly one terminal event. `AttemptFailure` carries the full failure `Cause`, so defects and interruption are reported as well as expected errors, and terminal events run like finalizers so they are emitted even when the attempt is interrupted. Event numbering matches `ExecutionPlan.CurrentMetadata`: `attempt` is cumulative across steps, while `stepAttempt` is 1-based within the current step. diff --git a/.changeset/pre/eff-477-schedule-concat.md b/.changeset/pre/eff-477-schedule-concat.md new file mode 100644 index 000000000..a9bbb3d9c --- /dev/null +++ b/.changeset/pre/eff-477-schedule-concat.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename `Schedule.andThen` and `Schedule.andThenResult` to `Schedule.concat` and `Schedule.concatResult`. diff --git a/.changeset/pre/eff-487-web-tracer-shutdown.md b/.changeset/pre/eff-487-web-tracer-shutdown.md new file mode 100644 index 000000000..bc9dba25a --- /dev/null +++ b/.changeset/pre/eff-487-web-tracer-shutdown.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Ensure Web and Node tracer providers shut down when flushing fails during layer release. diff --git a/.changeset/pre/eff-51-partitioned-semaphore-interruption.md b/.changeset/pre/eff-51-partitioned-semaphore-interruption.md new file mode 100644 index 000000000..3d36a756c --- /dev/null +++ b/.changeset/pre/eff-51-partitioned-semaphore-interruption.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `PartitionedSemaphore.take` leaking partially acquired permits when interrupted. diff --git a/.changeset/pre/eff-523-registry-scoped-atom-runtime.md b/.changeset/pre/eff-523-registry-scoped-atom-runtime.md new file mode 100644 index 000000000..2b7a28424 --- /dev/null +++ b/.changeset/pre/eff-523-registry-scoped-atom-runtime.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Scope `Atom.runtime` layer memoization to each `AtomRegistry` by default. Process-wide sharing is still available by passing a concrete `Layer.MemoMap` to `Atom.context`; the `Atom.defaultMemoMap` export has been removed. diff --git a/.changeset/pre/eff-532-stdio-terminal.md b/.changeset/pre/eff-532-stdio-terminal.md new file mode 100644 index 000000000..992ca220f --- /dev/null +++ b/.changeset/pre/eff-532-stdio-terminal.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/platform-node-shared": patch +"@effect/platform-deno": patch +--- + +Expose `stdinIsTerminal` and `stdoutIsTerminal` effects through the `Stdio` service. diff --git a/.changeset/pre/eff-537-preserve-response-metadata.md b/.changeset/pre/eff-537-preserve-response-metadata.md new file mode 100644 index 000000000..606ead5b4 --- /dev/null +++ b/.changeset/pre/eff-537-preserve-response-metadata.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve provider metadata when converting AI response parts into prompts. OpenAI chats using `store: true` now reuse restored item IDs as item references, while conversation-mode chats omit items already present in the conversation instead of inlining them. diff --git a/.changeset/pre/eff-542-rc-ref-generation.md b/.changeset/pre/eff-542-rc-ref-generation.md new file mode 100644 index 000000000..a7e8aeb36 --- /dev/null +++ b/.changeset/pre/eff-542-rc-ref-generation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure concurrent first `RcRef` borrowers share the same resource generation. diff --git a/.changeset/pre/eff-547-node-tracer-timeout.md b/.changeset/pre/eff-547-node-tracer-timeout.md new file mode 100644 index 000000000..67198957e --- /dev/null +++ b/.changeset/pre/eff-547-node-tracer-timeout.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Bound Node tracer provider shutdown by the configured `shutdownTimeout`. diff --git a/.changeset/pre/eff-548-bun-serve-scope.md b/.changeset/pre/eff-548-bun-serve-scope.md new file mode 100644 index 000000000..a2ae20dd5 --- /dev/null +++ b/.changeset/pre/eff-548-bun-serve-scope.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Fix Bun HTTP server handler restoration and defer shutdown while serve scopes remain active. diff --git a/.changeset/pre/eff-549-worker-send-error.md b/.changeset/pre/eff-549-worker-send-error.md new file mode 100644 index 000000000..5bab011f5 --- /dev/null +++ b/.changeset/pre/eff-549-worker-send-error.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Report buffered worker send failures as `WorkerError` values. diff --git a/.changeset/pre/eff-552-txqueue-shutdown.md b/.changeset/pre/eff-552-txqueue-shutdown.md new file mode 100644 index 000000000..8e02adb77 --- /dev/null +++ b/.changeset/pre/eff-552-txqueue-shutdown.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make `TxQueue.shutdown` safe to call after a queue has already been interrupted. diff --git a/.changeset/pre/eff-554-sql-resolver.md b/.changeset/pre/eff-554-sql-resolver.md new file mode 100644 index 000000000..a7581b87b --- /dev/null +++ b/.changeset/pre/eff-554-sql-resolver.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent SQL resolvers from invoking non-empty batch callbacks when every request fails encoding. diff --git a/.changeset/pre/eff-558-deno-file-web-range.md b/.changeset/pre/eff-558-deno-file-web-range.md new file mode 100644 index 000000000..e4027e6b4 --- /dev/null +++ b/.changeset/pre/eff-558-deno-file-web-range.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Honor `offset` and `bytesToRead` when creating Deno Web file responses. diff --git a/.changeset/eff-691-default-logger-ordering.md b/.changeset/pre/eff-691-default-logger-ordering.md similarity index 100% rename from .changeset/eff-691-default-logger-ordering.md rename to .changeset/pre/eff-691-default-logger-ordering.md diff --git a/.changeset/eff-693-rpcgroup-handler-deps.md b/.changeset/pre/eff-693-rpcgroup-handler-deps.md similarity index 100% rename from .changeset/eff-693-rpcgroup-handler-deps.md rename to .changeset/pre/eff-693-rpcgroup-handler-deps.md diff --git a/.changeset/eff-694-cli-completions-module.md b/.changeset/pre/eff-694-cli-completions-module.md similarity index 100% rename from .changeset/eff-694-cli-completions-module.md rename to .changeset/pre/eff-694-cli-completions-module.md diff --git a/.changeset/eff-695-layer-mock-dual-api.md b/.changeset/pre/eff-695-layer-mock-dual-api.md similarity index 100% rename from .changeset/eff-695-layer-mock-dual-api.md rename to .changeset/pre/eff-695-layer-mock-dual-api.md diff --git a/.changeset/eff-697-rpcserialization-json-array-decode.md b/.changeset/pre/eff-697-rpcserialization-json-array-decode.md similarity index 100% rename from .changeset/eff-697-rpcserialization-json-array-decode.md rename to .changeset/pre/eff-697-rpcserialization-json-array-decode.md diff --git a/.changeset/eff-698-rpcserialization-unreachable-branch.md b/.changeset/pre/eff-698-rpcserialization-unreachable-branch.md similarity index 100% rename from .changeset/eff-698-rpcserialization-unreachable-branch.md rename to .changeset/pre/eff-698-rpcserialization-unreachable-branch.md diff --git a/.changeset/eff-700-httpapi-middleware-errors.md b/.changeset/pre/eff-700-httpapi-middleware-errors.md similarity index 91% rename from .changeset/eff-700-httpapi-middleware-errors.md rename to .changeset/pre/eff-700-httpapi-middleware-errors.md index e95a36007..ef46ad705 100644 --- a/.changeset/eff-700-httpapi-middleware-errors.md +++ b/.changeset/pre/eff-700-httpapi-middleware-errors.md @@ -6,7 +6,7 @@ Improve unstable HttpApi runtime failures for missing server middleware and miss - HttpApiBuilder.applyMiddleware now resolves middleware services via Context.getUnsafe, so missing middleware fails with a clear "Service not found: " error instead of an opaque is not a function TypeError. - HttpApiBuilder.layer now reports missing groups with actionable context (group identifier, service key, suggested HttpApiBuilder.group(...) call, and available group keys). -- Added regression tests in packages/platform-node/test/HttpApi.test.ts covering: +- Added regression tests in packages/platform/node/test/HttpApi.test.ts covering: - addHttpApi + API-level middleware applied across merged groups - missing middleware service diagnostics - missing addHttpApi group layer diagnostics diff --git a/.changeset/eff-701-httpapierror-respondable.md b/.changeset/pre/eff-701-httpapierror-respondable.md similarity index 100% rename from .changeset/eff-701-httpapierror-respondable.md rename to .changeset/pre/eff-701-httpapierror-respondable.md diff --git a/.changeset/eff-704-stream-merge-predicate.md b/.changeset/pre/eff-704-stream-merge-predicate.md similarity index 100% rename from .changeset/eff-704-stream-merge-predicate.md rename to .changeset/pre/eff-704-stream-merge-predicate.md diff --git a/.changeset/eff-705-layer-tap-apis.md b/.changeset/pre/eff-705-layer-tap-apis.md similarity index 100% rename from .changeset/eff-705-layer-tap-apis.md rename to .changeset/pre/eff-705-layer-tap-apis.md diff --git a/.changeset/eff-706-servicemap-mutate.md b/.changeset/pre/eff-706-servicemap-mutate.md similarity index 100% rename from .changeset/eff-706-servicemap-mutate.md rename to .changeset/pre/eff-706-servicemap-mutate.md diff --git a/.changeset/eff-716-response-id-tracker-map.md b/.changeset/pre/eff-716-response-id-tracker-map.md similarity index 100% rename from .changeset/eff-716-response-id-tracker-map.md rename to .changeset/pre/eff-716-response-id-tracker-map.md diff --git a/.changeset/eff-717-openai-socket-cancel.md b/.changeset/pre/eff-717-openai-socket-cancel.md similarity index 100% rename from .changeset/eff-717-openai-socket-cancel.md rename to .changeset/pre/eff-717-openai-socket-cancel.md diff --git a/.changeset/eff-718-embedding-model-surface.md b/.changeset/pre/eff-718-embedding-model-surface.md similarity index 100% rename from .changeset/eff-718-embedding-model-surface.md rename to .changeset/pre/eff-718-embedding-model-surface.md diff --git a/.changeset/eff-725-fix-catch-jsdoc.md b/.changeset/pre/eff-725-fix-catch-jsdoc.md similarity index 100% rename from .changeset/eff-725-fix-catch-jsdoc.md rename to .changeset/pre/eff-725-fix-catch-jsdoc.md diff --git a/.changeset/eff-726-model-dimensions.md b/.changeset/pre/eff-726-model-dimensions.md similarity index 100% rename from .changeset/eff-726-model-dimensions.md rename to .changeset/pre/eff-726-model-dimensions.md diff --git a/.changeset/eff-727-cli-help-alignment.md b/.changeset/pre/eff-727-cli-help-alignment.md similarity index 100% rename from .changeset/eff-727-cli-help-alignment.md rename to .changeset/pre/eff-727-cli-help-alignment.md diff --git a/.changeset/eff-730-language-model-incremental-fallback.md b/.changeset/pre/eff-730-language-model-incremental-fallback.md similarity index 100% rename from .changeset/eff-730-language-model-incremental-fallback.md rename to .changeset/pre/eff-730-language-model-incremental-fallback.md diff --git a/.changeset/eff-736-cached-with-ttl.md b/.changeset/pre/eff-736-cached-with-ttl.md similarity index 100% rename from .changeset/eff-736-cached-with-ttl.md rename to .changeset/pre/eff-736-cached-with-ttl.md diff --git a/.changeset/eff-738-cron-prev.md b/.changeset/pre/eff-738-cron-prev.md similarity index 100% rename from .changeset/eff-738-cron-prev.md rename to .changeset/pre/eff-738-cron-prev.md diff --git a/.changeset/eff-739-openai-function-call-done.md b/.changeset/pre/eff-739-openai-function-call-done.md similarity index 100% rename from .changeset/eff-739-openai-function-call-done.md rename to .changeset/pre/eff-739-openai-function-call-done.md diff --git a/.changeset/eff-740-missing-summary-parts.md b/.changeset/pre/eff-740-missing-summary-parts.md similarity index 100% rename from .changeset/eff-740-missing-summary-parts.md rename to .changeset/pre/eff-740-missing-summary-parts.md diff --git a/.changeset/eff-742-http-client-request-web.md b/.changeset/pre/eff-742-http-client-request-web.md similarity index 100% rename from .changeset/eff-742-http-client-request-web.md rename to .changeset/pre/eff-742-http-client-request-web.md diff --git a/.changeset/eff-744-sqlite-migrator-lock.md b/.changeset/pre/eff-744-sqlite-migrator-lock.md similarity index 100% rename from .changeset/eff-744-sqlite-migrator-lock.md rename to .changeset/pre/eff-744-sqlite-migrator-lock.md diff --git a/.changeset/eff-746-fixed-iteration-catchup.md b/.changeset/pre/eff-746-fixed-iteration-catchup.md similarity index 100% rename from .changeset/eff-746-fixed-iteration-catchup.md rename to .changeset/pre/eff-746-fixed-iteration-catchup.md diff --git a/.changeset/eff-747-unify-effect.md b/.changeset/pre/eff-747-unify-effect.md similarity index 100% rename from .changeset/eff-747-unify-effect.md rename to .changeset/pre/eff-747-unify-effect.md diff --git a/.changeset/eff-754-url-builder-any.md b/.changeset/pre/eff-754-url-builder-any.md similarity index 100% rename from .changeset/eff-754-url-builder-any.md rename to .changeset/pre/eff-754-url-builder-any.md diff --git a/.changeset/eff-755-references-core.md b/.changeset/pre/eff-755-references-core.md similarity index 100% rename from .changeset/eff-755-references-core.md rename to .changeset/pre/eff-755-references-core.md diff --git a/.changeset/eff-769-select-text-highlight.md b/.changeset/pre/eff-769-select-text-highlight.md similarity index 100% rename from .changeset/eff-769-select-text-highlight.md rename to .changeset/pre/eff-769-select-text-highlight.md diff --git a/.changeset/eff-774-mutable-list-append-all-empty-array.md b/.changeset/pre/eff-774-mutable-list-append-all-empty-array.md similarity index 100% rename from .changeset/eff-774-mutable-list-append-all-empty-array.md rename to .changeset/pre/eff-774-mutable-list-append-all-empty-array.md diff --git a/.changeset/eff-777-schema-make-effect.md b/.changeset/pre/eff-777-schema-make-effect.md similarity index 100% rename from .changeset/eff-777-schema-make-effect.md rename to .changeset/pre/eff-777-schema-make-effect.md diff --git a/.changeset/eff-778-http-middleware-path-logger.md b/.changeset/pre/eff-778-http-middleware-path-logger.md similarity index 100% rename from .changeset/eff-778-http-middleware-path-logger.md rename to .changeset/pre/eff-778-http-middleware-path-logger.md diff --git a/.changeset/eff-779-keyvaluestore-layer-sql.md b/.changeset/pre/eff-779-keyvaluestore-layer-sql.md similarity index 100% rename from .changeset/eff-779-keyvaluestore-layer-sql.md rename to .changeset/pre/eff-779-keyvaluestore-layer-sql.md diff --git a/.changeset/eff-780-layer-unify.md b/.changeset/pre/eff-780-layer-unify.md similarity index 100% rename from .changeset/eff-780-layer-unify.md rename to .changeset/pre/eff-780-layer-unify.md diff --git a/.changeset/eff-781-fix-stream-toqueue-types.md b/.changeset/pre/eff-781-fix-stream-toqueue-types.md similarity index 100% rename from .changeset/eff-781-fix-stream-toqueue-types.md rename to .changeset/pre/eff-781-fix-stream-toqueue-types.md diff --git a/.changeset/eff-782-httpapi-status-literals.md b/.changeset/pre/eff-782-httpapi-status-literals.md similarity index 100% rename from .changeset/eff-782-httpapi-status-literals.md rename to .changeset/pre/eff-782-httpapi-status-literals.md diff --git a/.changeset/eff-783-atom-http-api-errors.md b/.changeset/pre/eff-783-atom-http-api-errors.md similarity index 100% rename from .changeset/eff-783-atom-http-api-errors.md rename to .changeset/pre/eff-783-atom-http-api-errors.md diff --git a/.changeset/pre/eff-798-schema-parser.md b/.changeset/pre/eff-798-schema-parser.md new file mode 100644 index 000000000..e7ca179c9 --- /dev/null +++ b/.changeset/pre/eff-798-schema-parser.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve synchronous Schema decode and encode performance by preserving completed parser exits and using a direct loop for common struct parsers. diff --git a/.changeset/pre/eff-802-tagged-union-match-or-else.md b/.changeset/pre/eff-802-tagged-union-match-or-else.md new file mode 100644 index 000000000..0336f28d2 --- /dev/null +++ b/.changeset/pre/eff-802-tagged-union-match-or-else.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Schema.TaggedUnion.matchOrElse` for partial case matching with a typed fallback. diff --git a/.changeset/pre/eff-806-schema-error-stack.md b/.changeset/pre/eff-806-schema-error-stack.md new file mode 100644 index 000000000..339f7f367 --- /dev/null +++ b/.changeset/pre/eff-806-schema-error-stack.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve `SchemaError` construction performance by skipping stack frame capture. diff --git a/.changeset/eff-819-cluster-workflow-shard-groups.md b/.changeset/pre/eff-819-cluster-workflow-shard-groups.md similarity index 100% rename from .changeset/eff-819-cluster-workflow-shard-groups.md rename to .changeset/pre/eff-819-cluster-workflow-shard-groups.md diff --git a/.changeset/pre/eff-825-pool-performance.md b/.changeset/pre/eff-825-pool-performance.md new file mode 100644 index 000000000..8f9d71792 --- /dev/null +++ b/.changeset/pre/eff-825-pool-performance.md @@ -0,0 +1,8 @@ +--- +"effect": patch +--- + +Improve Pool acquisition and release performance. Pool now tracks usage +incrementally, stores available items in an intrusive FIFO, and skips work for +fixed and empty pools. This changes the public `Pool.State` and `Pool.PoolItem` +interfaces. diff --git a/.changeset/pre/eff-825-pool-use.md b/.changeset/pre/eff-825-pool-use.md new file mode 100644 index 000000000..ddea9d1b0 --- /dev/null +++ b/.changeset/pre/eff-825-pool-use.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Add `Pool.use`, which borrows an item while an effect runs and returns it on any +exit. Unlike `Effect.scoped(Pool.get(pool))`, it does not require a `Scope`. diff --git a/.changeset/pre/eff-825-scope-single-finalizer.md b/.changeset/pre/eff-825-scope-single-finalizer.md new file mode 100644 index 000000000..fb77cd78c --- /dev/null +++ b/.changeset/pre/eff-825-scope-single-finalizer.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Reduce scoped resource acquisition allocations by storing the first Scope +finalizer inline and allocating a Map only when a second is added. This changes +the public `Scope.State.Open` interface. diff --git a/.changeset/pre/eff-837-dependency-updates.md b/.changeset/pre/eff-837-dependency-updates.md new file mode 100644 index 000000000..2d249ad2c --- /dev/null +++ b/.changeset/pre/eff-837-dependency-updates.md @@ -0,0 +1,8 @@ +--- +"@effect/doctest": patch +"@effect/sql-d1": patch +"@effect/sql-mysql2": patch +"@effect/sql-pglite": patch +--- + +Update production dependencies to their latest releases. diff --git a/.changeset/eff-849-transpose-option.md b/.changeset/pre/eff-849-transpose-option.md similarity index 100% rename from .changeset/eff-849-transpose-option.md rename to .changeset/pre/eff-849-transpose-option.md diff --git a/.changeset/pre/eff-851-skip-empty-remote-write.md b/.changeset/pre/eff-851-skip-empty-remote-write.md new file mode 100644 index 000000000..95de0f05e --- /dev/null +++ b/.changeset/pre/eff-851-skip-empty-remote-write.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Skip remote event journal write callbacks when there are no uncommitted entries and return an `Option` indicating +whether the callback ran. diff --git a/.changeset/pre/eff-861-indexeddb-equals.md b/.changeset/pre/eff-861-indexeddb-equals.md new file mode 100644 index 000000000..c3f17f1c8 --- /dev/null +++ b/.changeset/pre/eff-861-indexeddb-equals.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix IndexedDB `.equals` queries on tables without key paths to accept out-of-line `IDBValidKey` values. diff --git a/.changeset/pre/eff-946-concurrent-traversal-cleanup.md b/.changeset/pre/eff-946-concurrent-traversal-cleanup.md new file mode 100644 index 000000000..1ae7fabb1 --- /dev/null +++ b/.changeset/pre/eff-946-concurrent-traversal-cleanup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Interrupt and await concurrent traversal workers when mapper or refill callbacks throw. diff --git a/.changeset/pre/eff-952-terminal-failure-stack.md b/.changeset/pre/eff-952-terminal-failure-stack.md new file mode 100644 index 000000000..5c72f8d56 --- /dev/null +++ b/.changeset/pre/eff-952-terminal-failure-stack.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve current stack frame annotations on terminal root failures. diff --git a/.changeset/pre/eff-953-interruptor-stack-trace.md b/.changeset/pre/eff-953-interruptor-stack-trace.md new file mode 100644 index 000000000..aeca222b2 --- /dev/null +++ b/.changeset/pre/eff-953-interruptor-stack-trace.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Store interrupting fiber stack frames separately from interrupted target stack frames. diff --git a/.changeset/pre/eff-955-run-sync-dispatcher.md b/.changeset/pre/eff-955-run-sync-dispatcher.md new file mode 100644 index 000000000..22865608b --- /dev/null +++ b/.changeset/pre/eff-955-run-sync-dispatcher.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Avoid allocating a scheduler dispatcher when `runSyncExit` completes without yielding. diff --git a/.changeset/pre/eff-956-await-all-children.md b/.changeset/pre/eff-956-await-all-children.md new file mode 100644 index 000000000..8f892c153 --- /dev/null +++ b/.changeset/pre/eff-956-await-all-children.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make awaitAllChildren child selection linear in the number of fibers. diff --git a/.changeset/eight-turkeys-own.md b/.changeset/pre/eight-turkeys-own.md similarity index 100% rename from .changeset/eight-turkeys-own.md rename to .changeset/pre/eight-turkeys-own.md diff --git a/.changeset/eighty-lies-deny.md b/.changeset/pre/eighty-lies-deny.md similarity index 100% rename from .changeset/eighty-lies-deny.md rename to .changeset/pre/eighty-lies-deny.md diff --git a/.changeset/eighty-poets-draw.md b/.changeset/pre/eighty-poets-draw.md similarity index 100% rename from .changeset/eighty-poets-draw.md rename to .changeset/pre/eighty-poets-draw.md diff --git a/.changeset/eighty-swans-scream.md b/.changeset/pre/eighty-swans-scream.md similarity index 100% rename from .changeset/eighty-swans-scream.md rename to .changeset/pre/eighty-swans-scream.md diff --git a/.changeset/eighty-teeth-sniff.md b/.changeset/pre/eighty-teeth-sniff.md similarity index 100% rename from .changeset/eighty-teeth-sniff.md rename to .changeset/pre/eighty-teeth-sniff.md diff --git a/.changeset/eleven-apes-share.md b/.changeset/pre/eleven-apes-share.md similarity index 100% rename from .changeset/eleven-apes-share.md rename to .changeset/pre/eleven-apes-share.md diff --git a/.changeset/eleven-numbers-bake.md b/.changeset/pre/eleven-numbers-bake.md similarity index 100% rename from .changeset/eleven-numbers-bake.md rename to .changeset/pre/eleven-numbers-bake.md diff --git a/.changeset/pre/empty-env-values-missing.md b/.changeset/pre/empty-env-values-missing.md new file mode 100644 index 000000000..cd894f95d --- /dev/null +++ b/.changeset/pre/empty-env-values-missing.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Treat empty strings as missing values in built-in `ConfigProvider`s by default. + +`ConfigProvider.fromEnv`, `ConfigProvider.fromDotEnvContents`, `ConfigProvider.fromDotEnv`, `ConfigProvider.fromUnknown`, and `ConfigProvider.fromDir` now treat literal empty strings as absent values when loaded as values, allowing `Config.withDefault` and `Config.option` to recover. Container discovery still reflects the source structure. Pass `preserveEmptyStrings: true` to restore the previous behavior. + +`ConfigProvider.fromDotEnv({ expandVariables: true })` now expands variables consistently with `ConfigProvider.fromDotEnvContents`. diff --git a/.changeset/pre/empty-geckos-dispatch.md b/.changeset/pre/empty-geckos-dispatch.md new file mode 100644 index 000000000..e082b11e5 --- /dev/null +++ b/.changeset/pre/empty-geckos-dispatch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Encode SSE events with empty data as dispatchable events. diff --git a/.changeset/empty-gifts-beg.md b/.changeset/pre/empty-gifts-beg.md similarity index 100% rename from .changeset/empty-gifts-beg.md rename to .changeset/pre/empty-gifts-beg.md diff --git a/.changeset/empty-http-rpc-client.md b/.changeset/pre/empty-http-rpc-client.md similarity index 100% rename from .changeset/empty-http-rpc-client.md rename to .changeset/pre/empty-http-rpc-client.md diff --git a/.changeset/pre/empty-snakes-return.md b/.changeset/pre/empty-snakes-return.md new file mode 100644 index 000000000..94ae6a96f --- /dev/null +++ b/.changeset/pre/empty-snakes-return.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `String.snakeToCamel` and `String.snakeToPascal` to return an empty string for empty input. diff --git a/.changeset/pre/escape-completion-choice-values.md b/.changeset/pre/escape-completion-choice-values.md new file mode 100644 index 000000000..2b3433363 --- /dev/null +++ b/.changeset/pre/escape-completion-choice-values.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix shell completion for choice values containing quotes, spaces, word-break characters, Unicode, and shell metacharacters. + +Bash now quotes candidates for readline, keeps choice values intact when reconstructing words, and supports Bash 3.2 without associative arrays. Fish and Zsh escape choices across both parsing rounds, and Fish hides value-taking flags after use without suppressing their value completions. diff --git a/.changeset/eventlog-unencrypted.md b/.changeset/pre/eventlog-unencrypted.md similarity index 100% rename from .changeset/eventlog-unencrypted.md rename to .changeset/pre/eventlog-unencrypted.md diff --git a/.changeset/every-olives-burn.md b/.changeset/pre/every-olives-burn.md similarity index 100% rename from .changeset/every-olives-burn.md rename to .changeset/pre/every-olives-burn.md diff --git a/.changeset/expand-schema-filter-output.md b/.changeset/pre/expand-schema-filter-output.md similarity index 100% rename from .changeset/expand-schema-filter-output.md rename to .changeset/pre/expand-schema-filter-output.md diff --git a/.changeset/pre/explicit-env-record.md b/.changeset/pre/explicit-env-record.md new file mode 100644 index 000000000..2045bbd20 --- /dev/null +++ b/.changeset/pre/explicit-env-record.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `ConfigProvider.fromEnvRecord` for building a provider from an explicit environment record. diff --git a/.changeset/pre/explicit-otel-service-identity.md b/.changeset/pre/explicit-otel-service-identity.md new file mode 100644 index 000000000..a34030e33 --- /dev/null +++ b/.changeset/pre/explicit-otel-service-identity.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prefer explicit OTLP resource configuration over environment configuration. diff --git a/.changeset/export-schema-encode-keys-interface.md b/.changeset/pre/export-schema-encode-keys-interface.md similarity index 100% rename from .changeset/export-schema-encode-keys-interface.md rename to .changeset/pre/export-schema-encode-keys-interface.md diff --git a/.changeset/pre/expose-ai-prompt-part-schemas.md b/.changeset/pre/expose-ai-prompt-part-schemas.md new file mode 100644 index 000000000..b29af5e94 --- /dev/null +++ b/.changeset/pre/expose-ai-prompt-part-schemas.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose runtime schemas for AI prompt parts and message-specific part unions. diff --git a/.changeset/extract-semaphore-latch.md b/.changeset/pre/extract-semaphore-latch.md similarity index 100% rename from .changeset/extract-semaphore-latch.md rename to .changeset/pre/extract-semaphore-latch.md diff --git a/.changeset/pre/failed-otlp-checkpoints.md b/.changeset/pre/failed-otlp-checkpoints.md new file mode 100644 index 000000000..8e806105d --- /dev/null +++ b/.changeset/pre/failed-otlp-checkpoints.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve OTLP metric delta checkpoints when an export fails. diff --git a/.changeset/fair-bees-relax.md b/.changeset/pre/fair-bees-relax.md similarity index 100% rename from .changeset/fair-bees-relax.md rename to .changeset/pre/fair-bees-relax.md diff --git a/.changeset/pre/fair-birds-limit.md b/.changeset/pre/fair-birds-limit.md new file mode 100644 index 000000000..c72957459 --- /dev/null +++ b/.changeset/pre/fair-birds-limit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add Schedule.upTo options for limiting schedules by duration and/or recurrence count. diff --git a/.changeset/fair-buttons-share.md b/.changeset/pre/fair-buttons-share.md similarity index 100% rename from .changeset/fair-buttons-share.md rename to .changeset/pre/fair-buttons-share.md diff --git a/.changeset/pre/fair-citations-stream.md b/.changeset/pre/fair-citations-stream.md new file mode 100644 index 000000000..49f8e0ab1 --- /dev/null +++ b/.changeset/pre/fair-citations-stream.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Preserve start and end offsets for streamed OpenRouter citations. diff --git a/.changeset/fair-cooks-stop.md b/.changeset/pre/fair-cooks-stop.md similarity index 100% rename from .changeset/fair-cooks-stop.md rename to .changeset/pre/fair-cooks-stop.md diff --git a/.changeset/fair-cups-train.md b/.changeset/pre/fair-cups-train.md similarity index 100% rename from .changeset/fair-cups-train.md rename to .changeset/pre/fair-cups-train.md diff --git a/.changeset/fair-dryers-speak.md b/.changeset/pre/fair-dryers-speak.md similarity index 100% rename from .changeset/fair-dryers-speak.md rename to .changeset/pre/fair-dryers-speak.md diff --git a/.changeset/fair-forks-shake.md b/.changeset/pre/fair-forks-shake.md similarity index 100% rename from .changeset/fair-forks-shake.md rename to .changeset/pre/fair-forks-shake.md diff --git a/.changeset/pre/fair-jobs-like.md b/.changeset/pre/fair-jobs-like.md new file mode 100644 index 000000000..e98587334 --- /dev/null +++ b/.changeset/pre/fair-jobs-like.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add a `radius` option to `Graph` search configuration, allowing `dfs`, `bfs`, and `dfsPostOrder` traversals to limit returned nodes by edge distance from the configured start nodes. Traversals can also use `direction: "undirected"` to follow edges in either direction. diff --git a/.changeset/pre/fair-logs-correlate.md b/.changeset/pre/fair-logs-correlate.md new file mode 100644 index 000000000..dacdd7b02 --- /dev/null +++ b/.changeset/pre/fair-logs-correlate.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Prevent log annotations from overwriting active span correlation identifiers. diff --git a/.changeset/pre/fair-logs-listen.md b/.changeset/pre/fair-logs-listen.md new file mode 100644 index 000000000..6608c2236 --- /dev/null +++ b/.changeset/pre/fair-logs-listen.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now advertise logging and honor each client's selected log level when sending log notifications. diff --git a/.changeset/fair-pandas-prove.md b/.changeset/pre/fair-pandas-prove.md similarity index 100% rename from .changeset/fair-pandas-prove.md rename to .changeset/pre/fair-pandas-prove.md diff --git a/.changeset/fair-pants-float.md b/.changeset/pre/fair-pants-float.md similarity index 100% rename from .changeset/fair-pants-float.md rename to .changeset/pre/fair-pants-float.md diff --git a/.changeset/fair-poems-visit.md b/.changeset/pre/fair-poems-visit.md similarity index 100% rename from .changeset/fair-poems-visit.md rename to .changeset/pre/fair-poems-visit.md diff --git a/.changeset/pre/fair-sampling-content.md b/.changeset/pre/fair-sampling-content.md new file mode 100644 index 000000000..d88205d8f --- /dev/null +++ b/.changeset/pre/fair-sampling-content.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve MCP sampling request preferences and response content. diff --git a/.changeset/pre/fair-sinks-catch.md b/.changeset/pre/fair-sinks-catch.md new file mode 100644 index 000000000..6ea07ee3e --- /dev/null +++ b/.changeset/pre/fair-sinks-catch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the error type exposed by the curried `Sink.catch` overload. diff --git a/.changeset/pre/famous-loops-flow.md b/.changeset/pre/famous-loops-flow.md new file mode 100644 index 000000000..ac6581223 --- /dev/null +++ b/.changeset/pre/famous-loops-flow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Check symbol-keyed properties in Match object patterns. diff --git a/.changeset/famous-wolves-lead.md b/.changeset/pre/famous-wolves-lead.md similarity index 100% rename from .changeset/famous-wolves-lead.md rename to .changeset/pre/famous-wolves-lead.md diff --git a/.changeset/fancy-glasses-grow.md b/.changeset/pre/fancy-glasses-grow.md similarity index 100% rename from .changeset/fancy-glasses-grow.md rename to .changeset/pre/fancy-glasses-grow.md diff --git a/.changeset/pre/fast-graph-path-queues.md b/.changeset/pre/fast-graph-path-queues.md new file mode 100644 index 000000000..66d188167 --- /dev/null +++ b/.changeset/pre/fast-graph-path-queues.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve `Graph.dijkstra` and `Graph.astar` priority queue performance. diff --git a/.changeset/fast-times-camp.md b/.changeset/pre/fast-times-camp.md similarity index 100% rename from .changeset/fast-times-camp.md rename to .changeset/pre/fast-times-camp.md diff --git a/.changeset/few-birds-matter.md b/.changeset/pre/few-birds-matter.md similarity index 100% rename from .changeset/few-birds-matter.md rename to .changeset/pre/few-birds-matter.md diff --git a/.changeset/few-cougars-dig.md b/.changeset/pre/few-cougars-dig.md similarity index 100% rename from .changeset/few-cougars-dig.md rename to .changeset/pre/few-cougars-dig.md diff --git a/.changeset/few-foxes-grin.md b/.changeset/pre/few-foxes-grin.md similarity index 100% rename from .changeset/few-foxes-grin.md rename to .changeset/pre/few-foxes-grin.md diff --git a/.changeset/few-mirrors-pull.md b/.changeset/pre/few-mirrors-pull.md similarity index 100% rename from .changeset/few-mirrors-pull.md rename to .changeset/pre/few-mirrors-pull.md diff --git a/.changeset/few-socks-poke.md b/.changeset/pre/few-socks-poke.md similarity index 100% rename from .changeset/few-socks-poke.md rename to .changeset/pre/few-socks-poke.md diff --git a/.changeset/pre/fiber-join-all-errors.md b/.changeset/pre/fiber-join-all-errors.md new file mode 100644 index 000000000..ac883aedc --- /dev/null +++ b/.changeset/pre/fiber-join-all-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve input fiber error types in `Fiber.joinAll`. diff --git a/.changeset/fiber-runtime-start-metrics.md b/.changeset/pre/fiber-runtime-start-metrics.md similarity index 100% rename from .changeset/fiber-runtime-start-metrics.md rename to .changeset/pre/fiber-runtime-start-metrics.md diff --git a/.changeset/fiery-jokes-care.md b/.changeset/pre/fiery-jokes-care.md similarity index 100% rename from .changeset/fiery-jokes-care.md rename to .changeset/pre/fiery-jokes-care.md diff --git a/.changeset/fiery-mammals-call.md b/.changeset/pre/fiery-mammals-call.md similarity index 100% rename from .changeset/fiery-mammals-call.md rename to .changeset/pre/fiery-mammals-call.md diff --git a/.changeset/pre/file-prompt-jk-filter.md b/.changeset/pre/file-prompt-jk-filter.md new file mode 100644 index 000000000..d4d204e6b --- /dev/null +++ b/.changeset/pre/file-prompt-jk-filter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Prompt.file` swallowing `j` and `k` while typing a filter query. diff --git a/.changeset/fine-walls-decide.md b/.changeset/pre/fine-walls-decide.md similarity index 100% rename from .changeset/fine-walls-decide.md rename to .changeset/pre/fine-walls-decide.md diff --git a/.changeset/first-success-of.md b/.changeset/pre/first-success-of.md similarity index 100% rename from .changeset/first-success-of.md rename to .changeset/pre/first-success-of.md diff --git a/.changeset/five-parents-relax.md b/.changeset/pre/five-parents-relax.md similarity index 100% rename from .changeset/five-parents-relax.md rename to .changeset/pre/five-parents-relax.md diff --git a/.changeset/five-worms-rhyme.md b/.changeset/pre/five-worms-rhyme.md similarity index 100% rename from .changeset/five-worms-rhyme.md rename to .changeset/pre/five-worms-rhyme.md diff --git a/.changeset/fix-1332.md b/.changeset/pre/fix-1332.md similarity index 100% rename from .changeset/fix-1332.md rename to .changeset/pre/fix-1332.md diff --git a/.changeset/fix-1917.md b/.changeset/pre/fix-1917.md similarity index 100% rename from .changeset/fix-1917.md rename to .changeset/pre/fix-1917.md diff --git a/.changeset/fix-1927.md b/.changeset/pre/fix-1927.md similarity index 100% rename from .changeset/fix-1927.md rename to .changeset/pre/fix-1927.md diff --git a/.changeset/fix-1940.md b/.changeset/pre/fix-1940.md similarity index 100% rename from .changeset/fix-1940.md rename to .changeset/pre/fix-1940.md diff --git a/.changeset/fix-1947.md b/.changeset/pre/fix-1947.md similarity index 100% rename from .changeset/fix-1947.md rename to .changeset/pre/fix-1947.md diff --git a/.changeset/fix-2002.md b/.changeset/pre/fix-2002.md similarity index 100% rename from .changeset/fix-2002.md rename to .changeset/pre/fix-2002.md diff --git a/.changeset/fix-2012.md b/.changeset/pre/fix-2012.md similarity index 100% rename from .changeset/fix-2012.md rename to .changeset/pre/fix-2012.md diff --git a/.changeset/fix-2015.md b/.changeset/pre/fix-2015.md similarity index 100% rename from .changeset/fix-2015.md rename to .changeset/pre/fix-2015.md diff --git a/.changeset/fix-2260.md b/.changeset/pre/fix-2260.md similarity index 100% rename from .changeset/fix-2260.md rename to .changeset/pre/fix-2260.md diff --git a/.changeset/fix-2268.md b/.changeset/pre/fix-2268.md similarity index 100% rename from .changeset/fix-2268.md rename to .changeset/pre/fix-2268.md diff --git a/.changeset/fix-2271.md b/.changeset/pre/fix-2271.md similarity index 100% rename from .changeset/fix-2271.md rename to .changeset/pre/fix-2271.md diff --git a/.changeset/fix-2384.md b/.changeset/pre/fix-2384.md similarity index 100% rename from .changeset/fix-2384.md rename to .changeset/pre/fix-2384.md diff --git a/.changeset/fix-2414.md b/.changeset/pre/fix-2414.md similarity index 100% rename from .changeset/fix-2414.md rename to .changeset/pre/fix-2414.md diff --git a/.changeset/fix-2419.md b/.changeset/pre/fix-2419.md similarity index 100% rename from .changeset/fix-2419.md rename to .changeset/pre/fix-2419.md diff --git a/.changeset/pre/fix-2497.md b/.changeset/pre/fix-2497.md new file mode 100644 index 000000000..4425db712 --- /dev/null +++ b/.changeset/pre/fix-2497.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schedule.andThenResult` to emit `self` outputs as `Failure` and `other` outputs as `Success`, closes #2497. diff --git a/.changeset/pre/fix-2499.md b/.changeset/pre/fix-2499.md new file mode 100644 index 000000000..9e26f847e --- /dev/null +++ b/.changeset/pre/fix-2499.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix excess property handling in schema-backed class constructors, closes #2499. diff --git a/.changeset/pre/fix-6464.md b/.changeset/pre/fix-6464.md new file mode 100644 index 000000000..13d7b52d2 --- /dev/null +++ b/.changeset/pre/fix-6464.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Distribute `HttpApiBuilder` handler requirements per service so request middleware layers can provide them, closes #6464. diff --git a/.changeset/pre/fix-6491.md b/.changeset/pre/fix-6491.md new file mode 100644 index 000000000..a37e2d0cc --- /dev/null +++ b/.changeset/pre/fix-6491.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve nested class construction when applying constructor defaults, closes #6491. diff --git a/.changeset/pre/fix-6521.md b/.changeset/pre/fix-6521.md new file mode 100644 index 000000000..ca7c85ad3 --- /dev/null +++ b/.changeset/pre/fix-6521.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Simplify the displayed `Type`, `Encoded`, and `Iso` types of required readonly `Schema.Struct` fields, closes #6521. diff --git a/.changeset/fix-ai-empty-params-structured-output.md b/.changeset/pre/fix-ai-empty-params-structured-output.md similarity index 100% rename from .changeset/fix-ai-empty-params-structured-output.md rename to .changeset/pre/fix-ai-empty-params-structured-output.md diff --git a/.changeset/fix-ai-text-toolkit-typing.md b/.changeset/pre/fix-ai-text-toolkit-typing.md similarity index 100% rename from .changeset/fix-ai-text-toolkit-typing.md rename to .changeset/pre/fix-ai-text-toolkit-typing.md diff --git a/.changeset/pre/fix-ai-tool-call-id.md b/.changeset/pre/fix-ai-tool-call-id.md new file mode 100644 index 000000000..42d87284a --- /dev/null +++ b/.changeset/pre/fix-ai-tool-call-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Expose the tool call ID to AI tool handlers and `Toolkit.WithHandler.handle` wrappers. diff --git a/.changeset/pre/fix-ansi-cursor-to.md b/.changeset/pre/fix-ansi-cursor-to.md new file mode 100644 index 000000000..36309f832 --- /dev/null +++ b/.changeset/pre/fix-ansi-cursor-to.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Emit valid CSI sequences from the unstable CLI `cursorTo` helper. diff --git a/.changeset/fix-anthropic-caller-toolid.md b/.changeset/pre/fix-anthropic-caller-toolid.md similarity index 100% rename from .changeset/fix-anthropic-caller-toolid.md rename to .changeset/pre/fix-anthropic-caller-toolid.md diff --git a/.changeset/pre/fix-anthropic-code-execution-deltas.md b/.changeset/pre/fix-anthropic-code-execution-deltas.md new file mode 100644 index 000000000..a50a451a2 --- /dev/null +++ b/.changeset/pre/fix-anthropic-code-execution-deltas.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Fix malformed JSON in streamed Anthropic code-execution tool parameters. diff --git a/.changeset/pre/fix-anthropic-header-redaction.md b/.changeset/pre/fix-anthropic-header-redaction.md new file mode 100644 index 000000000..3a8bd80b7 --- /dev/null +++ b/.changeset/pre/fix-anthropic-header-redaction.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Redact the Anthropic API key from client error context. diff --git a/.changeset/pre/fix-anthropic-memory-tool-requires-handler.md b/.changeset/pre/fix-anthropic-memory-tool-requires-handler.md new file mode 100644 index 000000000..f9bd9843f --- /dev/null +++ b/.changeset/pre/fix-anthropic-memory-tool-requires-handler.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Fix `Memory_20250818` provider-defined tool missing `requiresHandler: true`. Like the other client-executed tools (`TextEditor_20250728`, `Bash_2025*`, `ComputerUse_2025*`), the memory tool requires the application to implement its execution (view/create/str_replace/insert/delete/rename over `/memories/*`). Without this flag, `Tool.HandlersFor` excluded it from the required handlers, making it impossible to type-check a handler for `Memory_20250818` in `Toolkit.toLayer`. diff --git a/.changeset/pre/fix-anthropic-memory-tool.md b/.changeset/pre/fix-anthropic-memory-tool.md new file mode 100644 index 000000000..3a9a9cdbc --- /dev/null +++ b/.changeset/pre/fix-anthropic-memory-tool.md @@ -0,0 +1,11 @@ +--- +"@effect/ai-anthropic": patch +--- + +Fix client-executed provider tools (Memory, Text Editor, Computer Use, Bash) which were unusable on the wire. + +- `makeResponse` (and the streaming equivalents) now map a provider `tool_use` wire name (e.g. `"memory"`) back to the tool's custom name (e.g. `"AnthropicMemory"`) that the toolkit is keyed by, instead of raising `ToolNotFoundError`. +- `AnthropicTool.MemoryCreateCommand` now includes the required `file_text` field, so a `create` command no longer drops the file body. +- Optional parameters on client-executed provider tools now use `Schema.optionalKey` instead of `Schema.optional`, which the Anthropic codec rejected with "Unsupported AST Undefined": `Memory`/`TextEditor` `view_range`, `ComputerUse` `coordinate`, and `Bash` `restart`. + +Closes #2615. diff --git a/.changeset/pre/fix-anthropic-plaintext-bytes.md b/.changeset/pre/fix-anthropic-plaintext-bytes.md new file mode 100644 index 000000000..5d6c5740e --- /dev/null +++ b/.changeset/pre/fix-anthropic-plaintext-bytes.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-anthropic": patch +--- + +Decode byte-backed plain-text attachments as UTF-8 text in Anthropic requests. diff --git a/.changeset/pre/fix-array-non-finite-indexes.md b/.changeset/pre/fix-array-non-finite-indexes.md new file mode 100644 index 000000000..dcfd9db0f --- /dev/null +++ b/.changeset/pre/fix-array-non-finite-indexes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Array index operations handling `NaN` and fractional indexes. diff --git a/.changeset/pre/fix-atom-batch-dependencies.md b/.changeset/pre/fix-atom-batch-dependencies.md new file mode 100644 index 000000000..5c1ad7071 --- /dev/null +++ b/.changeset/pre/fix-atom-batch-dependencies.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Atom dependency tracking and re-entrant invalidation during batch rebuilds. diff --git a/.changeset/pre/fix-atom-kvs-async-write.md b/.changeset/pre/fix-atom-kvs-async-write.md new file mode 100644 index 000000000..2227e7aed --- /dev/null +++ b/.changeset/pre/fix-atom-kvs-async-write.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Atom.kvs` async mode to retain its `AsyncResult` value shape after writes. diff --git a/.changeset/pre/fix-atom-suspense-registry-cache.md b/.changeset/pre/fix-atom-suspense-registry-cache.md new file mode 100644 index 000000000..220407192 --- /dev/null +++ b/.changeset/pre/fix-atom-suspense-registry-cache.md @@ -0,0 +1,5 @@ +--- +"@effect/atom-react": patch +--- + +Scope `useAtomSuspense` promises to their atom registry so concurrent registries resolve independently. diff --git a/.changeset/pre/fix-bash-subcommand-dispatch.md b/.changeset/pre/fix-bash-subcommand-dispatch.md new file mode 100644 index 000000000..c43cfc220 --- /dev/null +++ b/.changeset/pre/fix-bash-subcommand-dispatch.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent Bash completions from treating flag values as subcommands. diff --git a/.changeset/pre/fix-bigint-gcd-lcm.md b/.changeset/pre/fix-bigint-gcd-lcm.md new file mode 100644 index 000000000..bfffa0484 --- /dev/null +++ b/.changeset/pre/fix-bigint-gcd-lcm.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `BigInt.gcd` and `BigInt.lcm` return non-negative values and handle zero operands in `BigInt.lcm`. diff --git a/.changeset/pre/fix-cache-set-race.md b/.changeset/pre/fix-cache-set-race.md new file mode 100644 index 000000000..be4cf636d --- /dev/null +++ b/.changeset/pre/fix-cache-set-race.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent an interrupted cache lookup from removing a newer value written with `Cache.set`. diff --git a/.changeset/fix-catch-orelse-error-erasure.md b/.changeset/pre/fix-catch-orelse-error-erasure.md similarity index 100% rename from .changeset/fix-catch-orelse-error-erasure.md rename to .changeset/pre/fix-catch-orelse-error-erasure.md diff --git a/.changeset/pre/fix-cause-map-annotations.md b/.changeset/pre/fix-cause-map-annotations.md new file mode 100644 index 000000000..efc7e791f --- /dev/null +++ b/.changeset/pre/fix-cause-map-annotations.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve failure annotations when mapping errors with `Cause.map`. diff --git a/.changeset/pre/fix-channel-effects-finalizer.md b/.changeset/pre/fix-channel-effects-finalizer.md new file mode 100644 index 000000000..4c470c7fb --- /dev/null +++ b/.changeset/pre/fix-channel-effects-finalizer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Shut down the internal effects queue when ordered concurrent channel mapping closes. diff --git a/.changeset/pre/fix-channel-schema-decode-unknown.md b/.changeset/pre/fix-channel-schema-decode-unknown.md new file mode 100644 index 000000000..c7ee9a2ba --- /dev/null +++ b/.changeset/pre/fix-channel-schema-decode-unknown.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `ChannelSchema.decodeUnknown` to accept unknown input chunks while keeping `ChannelSchema.decode` typed to the schema's encoded input. diff --git a/.changeset/pre/fix-chunk-fractional-counts.md b/.changeset/pre/fix-chunk-fractional-counts.md new file mode 100644 index 000000000..0fae65c5a --- /dev/null +++ b/.changeset/pre/fix-chunk-fractional-counts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `Chunk.take` and `Chunk.drop` produce valid chunks for fractional counts. diff --git a/.changeset/fix-class-constructor-defaults.md b/.changeset/pre/fix-class-constructor-defaults.md similarity index 100% rename from .changeset/fix-class-constructor-defaults.md rename to .changeset/pre/fix-class-constructor-defaults.md diff --git a/.changeset/pre/fix-cli-help-table-spacing.md b/.changeset/pre/fix-cli-help-table-spacing.md new file mode 100644 index 000000000..e8b5ef302 --- /dev/null +++ b/.changeset/pre/fix-cli-help-table-spacing.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep long CLI subcommand and argument names separated from their descriptions in help output. diff --git a/.changeset/pre/fix-cli-missing-flag-values.md b/.changeset/pre/fix-cli-missing-flag-values.md new file mode 100644 index 000000000..0dfb373f9 --- /dev/null +++ b/.changeset/pre/fix-cli-missing-flag-values.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Report an error when a CLI flag, including `--completions`, is provided without its required value. diff --git a/.changeset/fix-cli-mixed-global-flag-context.md b/.changeset/pre/fix-cli-mixed-global-flag-context.md similarity index 100% rename from .changeset/fix-cli-mixed-global-flag-context.md rename to .changeset/pre/fix-cli-mixed-global-flag-context.md diff --git a/.changeset/pre/fix-cli-subcommands-requirements.md b/.changeset/pre/fix-cli-subcommands-requirements.md new file mode 100644 index 000000000..563526731 --- /dev/null +++ b/.changeset/pre/fix-cli-subcommands-requirements.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +- Fix `Command.withSubcommands` collapsing the inferred requirements type to `never` when given more than one subcommand +- Export a `Command.Services` utility type to extract the required services from a `Command` diff --git a/.changeset/pre/fix-cli-unexpected-arguments.md b/.changeset/pre/fix-cli-unexpected-arguments.md new file mode 100644 index 000000000..2f6b775cd --- /dev/null +++ b/.changeset/pre/fix-cli-unexpected-arguments.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject unexpected positional arguments left after command parsing, including values exceeding `Argument.variadic` maximum bounds. diff --git a/.changeset/pre/fix-clickhouse-connect-timeout.md b/.changeset/pre/fix-clickhouse-connect-timeout.md new file mode 100644 index 000000000..41a80e369 --- /dev/null +++ b/.changeset/pre/fix-clickhouse-connect-timeout.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-clickhouse": patch +--- + +Close the ClickHouse client when the startup connection check times out. diff --git a/.changeset/pre/fix-cluster-entity-context-bleed.md b/.changeset/pre/fix-cluster-entity-context-bleed.md new file mode 100644 index 000000000..c9037d3d5 --- /dev/null +++ b/.changeset/pre/fix-cluster-entity-context-bleed.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use registration context for cluster entities diff --git a/.changeset/pre/fix-cluster-reply-defect-isolation.md b/.changeset/pre/fix-cluster-reply-defect-isolation.md new file mode 100644 index 000000000..62f0d8472 --- /dev/null +++ b/.changeset/pre/fix-cluster-reply-defect-isolation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Scope cluster reply serialization failures and peer-delivered defects to their own request instead of the whole runner connection diff --git a/.changeset/pre/fix-cluster-shutdown-deadlock.md b/.changeset/pre/fix-cluster-shutdown-deadlock.md new file mode 100644 index 000000000..50649c4a0 --- /dev/null +++ b/.changeset/pre/fix-cluster-shutdown-deadlock.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix a `@effect/cluster` shutdown deadlock on single-runner topologies (e.g. single-node deployments and `TestRunner`), where `Sharding.sendOutgoing` retried `EntityNotAssignedToRunner` forever during teardown. diff --git a/.changeset/pre/fix-cluster-strand-request-shutdown.md b/.changeset/pre/fix-cluster-strand-request-shutdown.md new file mode 100644 index 000000000..886d22f7e --- /dev/null +++ b/.changeset/pre/fix-cluster-strand-request-shutdown.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix cluster shutdown hangs by failing abandoned non-discard requests and stream chunk acknowledgements with `EntityNotAssignedToRunner`, including persisted requests sent after runner unregistration. This adds `EntityNotAssignedToRunner` to the typed error channel of entity clients and request-only `EntityProxy` RPC/HTTP endpoints; discard endpoints remain unchanged. diff --git a/.changeset/pre/fix-cluster-stream-recovery.md b/.changeset/pre/fix-cluster-stream-recovery.md new file mode 100644 index 000000000..7c54c6381 --- /dev/null +++ b/.changeset/pre/fix-cluster-stream-recovery.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix persisted cluster stream recovery when SQL drivers return a null reply kind. diff --git a/.changeset/fix-config-array-default.md b/.changeset/pre/fix-config-array-default.md similarity index 100% rename from .changeset/fix-config-array-default.md rename to .changeset/pre/fix-config-array-default.md diff --git a/.changeset/pre/fix-config-or-else-evidence.md b/.changeset/pre/fix-config-or-else-evidence.md new file mode 100644 index 000000000..ced02896e --- /dev/null +++ b/.changeset/pre/fix-config-or-else-evidence.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve provider input evidence when `Config.orElse` recovers a configuration failure. diff --git a/.changeset/fix-config-withDefault.md b/.changeset/pre/fix-config-withDefault.md similarity index 100% rename from .changeset/fix-config-withDefault.md rename to .changeset/pre/fix-config-withDefault.md diff --git a/.changeset/fix-config-withdefault-filter.md b/.changeset/pre/fix-config-withdefault-filter.md similarity index 100% rename from .changeset/fix-config-withdefault-filter.md rename to .changeset/pre/fix-config-withdefault-filter.md diff --git a/.changeset/pre/fix-context-add-or-omit-types.md b/.changeset/pre/fix-context-add-or-omit-types.md new file mode 100644 index 000000000..f05f8bb40 --- /dev/null +++ b/.changeset/pre/fix-context-add-or-omit-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Omit services removed by `Context.addOrOmit` from the returned context type. diff --git a/.changeset/pre/fix-cron-and-representations.md b/.changeset/pre/fix-cron-and-representations.md new file mode 100644 index 000000000..064eeb3ee --- /dev/null +++ b/.changeset/pre/fix-cron-and-representations.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Cron` day and weekday intersection semantics in inspection representations. diff --git a/.changeset/pre/fix-cron-make-validation.md b/.changeset/pre/fix-cron-make-validation.md new file mode 100644 index 000000000..5f9e350f0 --- /dev/null +++ b/.changeset/pre/fix-cron-make-validation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Validate `Cron.make` field constraints and treat weekday `7` as Sunday consistently with cron parsing. diff --git a/.changeset/fix-cron-next-missing-day-overflow.md b/.changeset/pre/fix-cron-next-missing-day-overflow.md similarity index 100% rename from .changeset/fix-cron-next-missing-day-overflow.md rename to .changeset/pre/fix-cron-next-missing-day-overflow.md diff --git a/.changeset/pre/fix-cron-parser-semantics.md b/.changeset/pre/fix-cron-parser-semantics.md new file mode 100644 index 000000000..840d6f06b --- /dev/null +++ b/.changeset/pre/fix-cron-parser-semantics.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix cron parsing and scheduling edge cases for whitespace, Sunday `7`, strict numeric tokens, explicit full day ranges, and month-constrained day-of-month / weekday matching. diff --git a/.changeset/pre/fix-cron-prev-month-rollover.md b/.changeset/pre/fix-cron-prev-month-rollover.md new file mode 100644 index 000000000..8085ddc1d --- /dev/null +++ b/.changeset/pre/fix-cron-prev-month-rollover.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cron.prev` day-of-month rollover across shorter months and non-leap years. diff --git a/.changeset/pre/fix-cron-prev-weekday-wrap.md b/.changeset/pre/fix-cron-prev-weekday-wrap.md new file mode 100644 index 000000000..e4b830749 --- /dev/null +++ b/.changeset/pre/fix-cron-prev-weekday-wrap.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Cron.prev` weekday wrapping to always return a matching instant before the input. diff --git a/.changeset/pre/fix-cron-timezone-hash.md b/.changeset/pre/fix-cron-timezone-hash.md new file mode 100644 index 000000000..6ee572d04 --- /dev/null +++ b/.changeset/pre/fix-cron-timezone-hash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make Cron equality and hashing include the optional timezone consistently. diff --git a/.changeset/fix-datetime-gmt.md b/.changeset/pre/fix-datetime-gmt.md similarity index 100% rename from .changeset/fix-datetime-gmt.md rename to .changeset/pre/fix-datetime-gmt.md diff --git a/.changeset/fix-devtools-flush-on-teardown.md b/.changeset/pre/fix-devtools-flush-on-teardown.md similarity index 100% rename from .changeset/fix-devtools-flush-on-teardown.md rename to .changeset/pre/fix-devtools-flush-on-teardown.md diff --git a/.changeset/pre/fix-devtools-span-snapshot.md b/.changeset/pre/fix-devtools-span-snapshot.md new file mode 100644 index 000000000..ca381c8d5 --- /dev/null +++ b/.changeset/pre/fix-devtools-span-snapshot.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix DevTools span requests to preserve their state when queued for sending. diff --git a/.changeset/pre/fix-durable-clock-fractional-wakeup.md b/.changeset/pre/fix-durable-clock-fractional-wakeup.md new file mode 100644 index 000000000..d0a7390c8 --- /dev/null +++ b/.changeset/pre/fix-durable-clock-fractional-wakeup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize cluster durable clock wake-up timestamps to whole milliseconds. diff --git a/.changeset/pre/fix-durable-deferred-race.md b/.changeset/pre/fix-durable-deferred-race.md new file mode 100644 index 000000000..21b43f360 --- /dev/null +++ b/.changeset/pre/fix-durable-deferred-race.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `DurableDeferred.raceAll` so a completed deferred can wake an active workflow without changing success-biased race semantics diff --git a/.changeset/pre/fix-durable-race-replay.md b/.changeset/pre/fix-durable-race-replay.md new file mode 100644 index 000000000..2b0e3b242 --- /dev/null +++ b/.changeset/pre/fix-durable-race-replay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix replay of persisted `DurableDeferred.raceAll` results. diff --git a/.changeset/pre/fix-duration-decimal-precision.md b/.changeset/pre/fix-duration-decimal-precision.md new file mode 100644 index 000000000..2afc2db77 --- /dev/null +++ b/.changeset/pre/fix-duration-decimal-precision.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve integral precision when parsing decimal nano and micro duration inputs diff --git a/.changeset/fix-duration-symmetric-rounding.md b/.changeset/pre/fix-duration-symmetric-rounding.md similarity index 100% rename from .changeset/fix-duration-symmetric-rounding.md rename to .changeset/pre/fix-duration-symmetric-rounding.md diff --git a/.changeset/pre/fix-effect-fn-self.md b/.changeset/pre/fix-effect-fn-self.md new file mode 100644 index 000000000..80eda7033 --- /dev/null +++ b/.changeset/pre/fix-effect-fn-self.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.fn` binding the final transform as the generator body when using the `{ self }` overload. diff --git a/.changeset/pre/fix-effect-schedule-errors.md b/.changeset/pre/fix-effect-schedule-errors.md new file mode 100644 index 000000000..8a8910026 --- /dev/null +++ b/.changeset/pre/fix-effect-schedule-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include schedule errors in the error channel of `Effect.schedule` and `Effect.scheduleFrom`. diff --git a/.changeset/fix-entity-manager-defect-replay.md b/.changeset/pre/fix-entity-manager-defect-replay.md similarity index 100% rename from .changeset/fix-entity-manager-defect-replay.md rename to .changeset/pre/fix-entity-manager-defect-replay.md diff --git a/.changeset/fix-entity-proxy-rpc-handler-context.md b/.changeset/pre/fix-entity-proxy-rpc-handler-context.md similarity index 100% rename from .changeset/fix-entity-proxy-rpc-handler-context.md rename to .changeset/pre/fix-entity-proxy-rpc-handler-context.md diff --git a/.changeset/fix-entity-proxy-server-path-params.md b/.changeset/pre/fix-entity-proxy-server-path-params.md similarity index 100% rename from .changeset/fix-entity-proxy-server-path-params.md rename to .changeset/pre/fix-entity-proxy-server-path-params.md diff --git a/.changeset/pre/fix-eventlog-duplicate-chunks.md b/.changeset/pre/fix-eventlog-duplicate-chunks.md new file mode 100644 index 000000000..dc215a8b3 --- /dev/null +++ b/.changeset/pre/fix-eventlog-duplicate-chunks.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore duplicate chunk indexes when joining event log messages. diff --git a/.changeset/pre/fix-fiber-exit-observers.md b/.changeset/pre/fix-fiber-exit-observers.md new file mode 100644 index 000000000..50aebaee1 --- /dev/null +++ b/.changeset/pre/fix-fiber-exit-observers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure fiber observer cancellation during exit does not skip remaining observers. diff --git a/.changeset/pre/fix-fiberhandle-clear-race.md b/.changeset/pre/fix-fiberhandle-clear-race.md new file mode 100644 index 000000000..567ba19e0 --- /dev/null +++ b/.changeset/pre/fix-fiberhandle-clear-race.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix a race where FiberHandle.clear could remove a newer fiber installed while the previous fiber was still interrupting. diff --git a/.changeset/pre/fix-fiberset-json-id.md b/.changeset/pre/fix-fiberset-json-id.md new file mode 100644 index 000000000..158b963c1 --- /dev/null +++ b/.changeset/pre/fix-fiberset-json-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the inspectable JSON identity of `FiberSet`. diff --git a/.changeset/pre/fix-fiberset-runtime-interruption.md b/.changeset/pre/fix-fiberset-runtime-interruption.md new file mode 100644 index 000000000..0d550e711 --- /dev/null +++ b/.changeset/pre/fix-fiberset-runtime-interruption.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Propagate the `FiberSet.runtime` interruption option when registering managed fibers. diff --git a/.changeset/pre/fix-fish-command-path.md b/.changeset/pre/fix-fish-command-path.md new file mode 100644 index 000000000..15053389d --- /dev/null +++ b/.changeset/pre/fix-fish-command-path.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Match Fish completions against the full nested command path. diff --git a/.changeset/pre/fix-formatter-output-contracts.md b/.changeset/pre/fix-formatter-output-contracts.md new file mode 100644 index 000000000..5312f9d8e --- /dev/null +++ b/.changeset/pre/fix-formatter-output-contracts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Formatter.format` handling of shared references and ensure `Formatter.formatJson` always returns valid JSON. diff --git a/.changeset/pre/fix-from-json-string-identifier.md b/.changeset/pre/fix-from-json-string-identifier.md new file mode 100644 index 000000000..b2e478fe7 --- /dev/null +++ b/.changeset/pre/fix-from-json-string-identifier.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Preserve content schema identifiers when emitting JSON Schema for `Schema.fromJsonString`. + +This keeps user-defined identifiers attached to the decoded JSON payload while giving the generated JSON string wrapper its own derived name, avoiding client codegen outputs where the payload type is renamed behind the transport wrapper. diff --git a/.changeset/pre/fix-from-option-inline-inference.md b/.changeset/pre/fix-from-option-inline-inference.md new file mode 100644 index 000000000..8be078ddd --- /dev/null +++ b/.changeset/pre/fix-from-option-inline-inference.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.fromOption` data-first inference for inline `Option` expressions. diff --git a/.changeset/fix-from-readable-stream-cancel-defect.md b/.changeset/pre/fix-from-readable-stream-cancel-defect.md similarity index 100% rename from .changeset/fix-from-readable-stream-cancel-defect.md rename to .changeset/pre/fix-from-readable-stream-cancel-defect.md diff --git a/.changeset/pre/fix-graph-allocator-equality.md b/.changeset/pre/fix-graph-allocator-equality.md new file mode 100644 index 000000000..d796cad07 --- /dev/null +++ b/.changeset/pre/fix-graph-allocator-equality.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix immutable Graph equality and hashing to include future node and edge identifier allocation. diff --git a/.changeset/pre/fix-graph-bellman-ford-self-cycle.md b/.changeset/pre/fix-graph-bellman-ford-self-cycle.md new file mode 100644 index 000000000..07dcc32a6 --- /dev/null +++ b/.changeset/pre/fix-graph-bellman-ford-self-cycle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.bellmanFord` to detect reachable negative cycles when the source and target are the same node. diff --git a/.changeset/pre/fix-graph-curried-getters.md b/.changeset/pre/fix-graph-curried-getters.md new file mode 100644 index 000000000..4d7947920 --- /dev/null +++ b/.changeset/pre/fix-graph-curried-getters.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix standalone data-last `Graph.getNode` and `Graph.getEdge` inference. diff --git a/.changeset/pre/fix-graph-dfs-astar-edge-cases.md b/.changeset/pre/fix-graph-dfs-astar-edge-cases.md new file mode 100644 index 000000000..894c65d01 --- /dev/null +++ b/.changeset/pre/fix-graph-dfs-astar-edge-cases.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve depth-first traversal order with finite radii and validate A* heuristics for trivial paths. diff --git a/.changeset/pre/fix-graph-edge-transforms.md b/.changeset/pre/fix-graph-edge-transforms.md new file mode 100644 index 000000000..e0c410ee8 --- /dev/null +++ b/.changeset/pre/fix-graph-edge-transforms.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.mapEdges` and `Graph.filterMapEdges` to preserve `Graph.Edge` instances when transforming edge data. diff --git a/.changeset/pre/fix-graph-finite-edge-weights.md b/.changeset/pre/fix-graph-finite-edge-weights.md new file mode 100644 index 000000000..171304d07 --- /dev/null +++ b/.changeset/pre/fix-graph-finite-edge-weights.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject `NaN` and `-Infinity` edge weights in Graph shortest-path algorithms. diff --git a/.changeset/pre/fix-graph-mutable-hash.md b/.changeset/pre/fix-graph-mutable-hash.md new file mode 100644 index 000000000..62463090c --- /dev/null +++ b/.changeset/pre/fix-graph-mutable-hash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix mutable Graph equality and hashing to use reference identity while preserving structural semantics for immutable graphs. diff --git a/.changeset/pre/fix-graph-mutable-topo.md b/.changeset/pre/fix-graph-mutable-topo.md new file mode 100644 index 000000000..8a929bde8 --- /dev/null +++ b/.changeset/pre/fix-graph-mutable-topo.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix topological walkers silently completing with an incomplete order when a mutable graph becomes cyclic after walker creation. diff --git a/.changeset/pre/fix-graph-mutation-errors.md b/.changeset/pre/fix-graph-mutation-errors.md new file mode 100644 index 000000000..476d8c401 --- /dev/null +++ b/.changeset/pre/fix-graph-mutation-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve scoped Graph mutation callback errors when the callback manually finalizes its mutable handle. diff --git a/.changeset/pre/fix-graph-topo-types.md b/.changeset/pre/fix-graph-topo-types.md new file mode 100644 index 000000000..adab4eb46 --- /dev/null +++ b/.changeset/pre/fix-graph-topo-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Restrict `Graph.topo` to directed graphs at the type level while retaining runtime validation for unsafe undirected inputs. diff --git a/.changeset/fix-graph-undirected-traversal.md b/.changeset/pre/fix-graph-undirected-traversal.md similarity index 100% rename from .changeset/fix-graph-undirected-traversal.md rename to .changeset/pre/fix-graph-undirected-traversal.md diff --git a/.changeset/pre/fix-graph-walker-repeatability.md b/.changeset/pre/fix-graph-walker-repeatability.md new file mode 100644 index 000000000..25c1ccd98 --- /dev/null +++ b/.changeset/pre/fix-graph-walker-repeatability.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.Walker` to create a fresh iterable for each direct iteration. diff --git a/.changeset/pre/fix-graphviz-dot-escaping.md b/.changeset/pre/fix-graphviz-dot-escaping.md new file mode 100644 index 000000000..35bd254d6 --- /dev/null +++ b/.changeset/pre/fix-graphviz-dot-escaping.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.toGraphViz` to quote DOT graph names and escape labels as literal text. diff --git a/.changeset/pre/fix-group-by-key-types.md b/.changeset/pre/fix-group-by-key-types.md new file mode 100644 index 000000000..de711323a --- /dev/null +++ b/.changeset/pre/fix-group-by-key-types.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Preserve finite string and unique symbol key unions in the return types of `Array.groupBy` and `Iterable.groupBy`. + +Previously, grouping widened finite keys to `string` or `symbol`, which lost known-key autocomplete and allowed access to keys that the selector could never produce. The new `Record.ReadonlyRecord.GroupByResult` keeps finite keys and marks their properties optional because any group may be absent at runtime, while open `string` and `symbol` selectors retain their existing record index signatures. diff --git a/.changeset/fix-has-interrupts-only-empty.md b/.changeset/pre/fix-has-interrupts-only-empty.md similarity index 100% rename from .changeset/fix-has-interrupts-only-empty.md rename to .changeset/pre/fix-has-interrupts-only-empty.md diff --git a/.changeset/fix-hashmap-bit31-ordering.md b/.changeset/pre/fix-hashmap-bit31-ordering.md similarity index 100% rename from .changeset/fix-hashmap-bit31-ordering.md rename to .changeset/pre/fix-hashmap-bit31-ordering.md diff --git a/.changeset/pre/fix-hashmap-modify-hash.md b/.changeset/pre/fix-hashmap-modify-hash.md new file mode 100644 index 000000000..ca36cabf5 --- /dev/null +++ b/.changeset/pre/fix-hashmap-modify-hash.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use the supplied hash for `HashMap.modifyHash` insertions, updates, and removals. diff --git a/.changeset/fix-headers-proto-enumerability.md b/.changeset/pre/fix-headers-proto-enumerability.md similarity index 100% rename from .changeset/fix-headers-proto-enumerability.md rename to .changeset/pre/fix-headers-proto-enumerability.md diff --git a/.changeset/pre/fix-http-client-request-content-length.md b/.changeset/pre/fix-http-client-request-content-length.md new file mode 100644 index 000000000..12f66418c --- /dev/null +++ b/.changeset/pre/fix-http-client-request-content-length.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove stale `content-length` headers when replacing an HTTP client request body with one of unknown length. diff --git a/.changeset/fix-http-incoming-message-parse-options.md b/.changeset/pre/fix-http-incoming-message-parse-options.md similarity index 100% rename from .changeset/fix-http-incoming-message-parse-options.md rename to .changeset/pre/fix-http-incoming-message-parse-options.md diff --git a/.changeset/pre/fix-http-pre-response-handler-types.md b/.changeset/pre/fix-http-pre-response-handler-types.md new file mode 100644 index 000000000..66eeb8b70 --- /dev/null +++ b/.changeset/pre/fix-http-pre-response-handler-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the published declaration for `HttpEffect.appendPreResponseHandlerUnsafe`. diff --git a/.changeset/pre/fix-http-server-request-raw-body.md b/.changeset/pre/fix-http-server-request-raw-body.md new file mode 100644 index 000000000..148559e4a --- /dev/null +++ b/.changeset/pre/fix-http-server-request-raw-body.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support standard `BodyInit` values when reading converted client request bodies through `HttpServerRequest`. diff --git a/.changeset/pre/fix-http-server-response-body-headers.md b/.changeset/pre/fix-http-server-response-body-headers.md new file mode 100644 index 000000000..90f227817 --- /dev/null +++ b/.changeset/pre/fix-http-server-response-body-headers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Synchronize HTTP server response content headers when replacing the body. diff --git a/.changeset/fix-http-tracer-response-cause.md b/.changeset/pre/fix-http-tracer-response-cause.md similarity index 100% rename from .changeset/fix-http-tracer-response-cause.md rename to .changeset/pre/fix-http-tracer-response-cause.md diff --git a/.changeset/pre/fix-httpapi-authorization-decoding.md b/.changeset/pre/fix-httpapi-authorization-decoding.md new file mode 100644 index 000000000..b41cb0662 --- /dev/null +++ b/.changeset/pre/fix-httpapi-authorization-decoding.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Fix HttpApi authorization decoding. + +Previously, `HttpApiBuilder.securityDecode` removed the expected scheme length and one following character from the `Authorization` header without verifying either value. A Bearer decoder could therefore pass credentials from a different scheme such as `Basic`, accept a malformed header without a separating space, or retain leading spaces when more than one separator was present. + +The decoder now validates the declared scheme before returning credentials, matches it case-insensitively as required by [RFC 9110 section 11.1](https://www.rfc-editor.org/rfc/rfc9110.html#section-11.1), and consumes one or more separating spaces. Missing, malformed, or mismatched headers produce the existing empty credential value so security middleware can reject them consistently. + +Basic authentication previously split the decoded `user-pass` value at every colon, causing otherwise valid passwords containing `:` to be discarded. It now uses only the first colon as the separator and preserves the rest of the password, following [RFC 7617 section 2](https://www.rfc-editor.org/rfc/rfc7617.html#section-2). diff --git a/.changeset/pre/fix-httpapi-client-error-content-type.md b/.changeset/pre/fix-httpapi-client-error-content-type.md new file mode 100644 index 000000000..2de77be2c --- /dev/null +++ b/.changeset/pre/fix-httpapi-client-error-content-type.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Fix HttpApi client error decoding. + +Generated clients previously combined every error schema for a status into one union decoder. When schemas used different encodings, their declaration order could determine the decoded error instead of the response `Content-Type`; for example, a text decoder could accept a JSON response before the JSON decoder was tried. + +Error responses are now grouped and selected by normalized content type, matching buffered success responses. Normalization happens before grouping, so declarations that differ only by casing or parameters such as `charset` share one union decoder instead of making later schemas unreachable. + +No-content schemas are represented by a headerless alternative, allowing empty error responses without a `Content-Type` header to decode correctly. Unsupported content types preserve the existing combination of `StatusCodeError` and the response decoding failure. diff --git a/.changeset/fix-httpapi-endpoint-error-inference.md b/.changeset/pre/fix-httpapi-endpoint-error-inference.md similarity index 100% rename from .changeset/fix-httpapi-endpoint-error-inference.md rename to .changeset/pre/fix-httpapi-endpoint-error-inference.md diff --git a/.changeset/pre/fix-httpapi-malformed-json-400.md b/.changeset/pre/fix-httpapi-malformed-json-400.md new file mode 100644 index 000000000..2f704e0e8 --- /dev/null +++ b/.changeset/pre/fix-httpapi-malformed-json-400.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Map HttpApi json defects to SchemaError diff --git a/.changeset/pre/fix-httpapi-runtime-shape.md b/.changeset/pre/fix-httpapi-runtime-shape.md new file mode 100644 index 000000000..a8c9bd869 --- /dev/null +++ b/.changeset/pre/fix-httpapi-runtime-shape.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApi.make` so it stores the API identifier and starts with an empty `groups` object instead of a `Map`. This makes empty APIs match the shape they have after groups are added. diff --git a/.changeset/fix-httpapi-schema-types.md b/.changeset/pre/fix-httpapi-schema-types.md similarity index 100% rename from .changeset/fix-httpapi-schema-types.md rename to .changeset/pre/fix-httpapi-schema-types.md diff --git a/.changeset/fix-httpapi-security-middleware-cache.md b/.changeset/pre/fix-httpapi-security-middleware-cache.md similarity index 100% rename from .changeset/fix-httpapi-security-middleware-cache.md rename to .changeset/pre/fix-httpapi-security-middleware-cache.md diff --git a/.changeset/pre/fix-httpapi-single-array-query.md b/.changeset/pre/fix-httpapi-single-array-query.md new file mode 100644 index 000000000..6a8a671eb --- /dev/null +++ b/.changeset/pre/fix-httpapi-single-array-query.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpApi` query decoding for array parameters with a single value. diff --git a/.changeset/pre/fix-invalid-openapi-examples.md b/.changeset/pre/fix-invalid-openapi-examples.md new file mode 100644 index 000000000..3e0552953 --- /dev/null +++ b/.changeset/pre/fix-invalid-openapi-examples.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Drop invalid OpenAPI schema examples from generated Effect Schema annotations. diff --git a/.changeset/pre/fix-invalid-value-doubled-expected.md b/.changeset/pre/fix-invalid-value-doubled-expected.md new file mode 100644 index 000000000..a8e9deaee --- /dev/null +++ b/.changeset/pre/fix-invalid-value-doubled-expected.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix doubled `Expected: Expected ...` prefixes in CLI `InvalidValue` error messages, closes #6312. diff --git a/.changeset/fix-is-json-dag.md b/.changeset/pre/fix-is-json-dag.md similarity index 100% rename from .changeset/fix-is-json-dag.md rename to .changeset/pre/fix-is-json-dag.md diff --git a/.changeset/pre/fix-iterable-flatten-stack-safety.md b/.changeset/pre/fix-iterable-flatten-stack-safety.md new file mode 100644 index 000000000..54894d0c6 --- /dev/null +++ b/.changeset/pre/fix-iterable-flatten-stack-safety.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make `Iterable.flatten` stack safe across empty iterables. diff --git a/.changeset/fix-json-schema-anyof-oneof-siblings.md b/.changeset/pre/fix-json-schema-anyof-oneof-siblings.md similarity index 100% rename from .changeset/fix-json-schema-anyof-oneof-siblings.md rename to .changeset/pre/fix-json-schema-anyof-oneof-siblings.md diff --git a/.changeset/fix-json-schema-import-json.md b/.changeset/pre/fix-json-schema-import-json.md similarity index 100% rename from .changeset/fix-json-schema-import-json.md rename to .changeset/pre/fix-json-schema-import-json.md diff --git a/.changeset/pre/fix-json-schema-reference-resolution.md b/.changeset/pre/fix-json-schema-reference-resolution.md new file mode 100644 index 000000000..52c87639a --- /dev/null +++ b/.changeset/pre/fix-json-schema-reference-resolution.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject unsupported JSON Schema references instead of resolving them by their final path segment, closes #7409. diff --git a/.changeset/pre/fix-json-schema-unique-items-false.md b/.changeset/pre/fix-json-schema-unique-items-false.md new file mode 100644 index 000000000..0365d9dc5 --- /dev/null +++ b/.changeset/pre/fix-json-schema-unique-items-false.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore `uniqueItems` when set to `false` while importing JSON Schema documents. diff --git a/.changeset/fix-keepalive-blocked-timers.md b/.changeset/pre/fix-keepalive-blocked-timers.md similarity index 100% rename from .changeset/fix-keepalive-blocked-timers.md rename to .changeset/pre/fix-keepalive-blocked-timers.md diff --git a/.changeset/pre/fix-language-model-stream-concurrency.md b/.changeset/pre/fix-language-model-stream-concurrency.md new file mode 100644 index 000000000..491b4fdcd --- /dev/null +++ b/.changeset/pre/fix-language-model-stream-concurrency.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `LanguageModel.streamText` to apply the configured concurrency limit to tool call resolution, including approval checks. diff --git a/.changeset/pre/fix-latch-stale-flush.md b/.changeset/pre/fix-latch-stale-flush.md new file mode 100644 index 000000000..c36527840 --- /dev/null +++ b/.changeset/pre/fix-latch-stale-flush.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Fix Latch open/release resuming waiters that registered after a subsequent close. + +`Latch.open` and `Latch.release` schedule the waiter flush on the fiber's +dispatcher. Previously the flush drained whatever waiters existed at flush +time, so a waiter that registered after the latch was closed again could be +resumed by the stale flush. The waiters are now snapshotted at schedule time, +so only waiters covered by an `open`/`release` call are resumed. diff --git a/.changeset/pre/fix-layermap-preload.md b/.changeset/pre/fix-layermap-preload.md new file mode 100644 index 000000000..6b7260734 --- /dev/null +++ b/.changeset/pre/fix-layermap-preload.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `LayerMap` preload options so configured entries are acquired during construction. diff --git a/.changeset/pre/fix-mcp-call-tool-arguments.md b/.changeset/pre/fix-mcp-call-tool-arguments.md new file mode 100644 index 000000000..952f6d0da --- /dev/null +++ b/.changeset/pre/fix-mcp-call-tool-arguments.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow MCP tool calls to omit optional arguments. diff --git a/.changeset/pre/fix-mcp-completion-context.md b/.changeset/pre/fix-mcp-completion-context.md new file mode 100644 index 000000000..82c30fc89 --- /dev/null +++ b/.changeset/pre/fix-mcp-completion-context.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP completion handlers now receive resolved argument context, and completion responses are limited to one hundred values. diff --git a/.changeset/fix-mcp-param-name-resolution.md b/.changeset/pre/fix-mcp-param-name-resolution.md similarity index 100% rename from .changeset/fix-mcp-param-name-resolution.md rename to .changeset/pre/fix-mcp-param-name-resolution.md diff --git a/.changeset/pre/fix-mcp-request-errors.md b/.changeset/pre/fix-mcp-request-errors.md new file mode 100644 index 000000000..7da53c303 --- /dev/null +++ b/.changeset/pre/fix-mcp-request-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now return protocol errors for invalid tool, prompt, completion, resource, and logging requests. diff --git a/.changeset/pre/fix-memory-journal-conflicts.md b/.changeset/pre/fix-memory-journal-conflicts.md new file mode 100644 index 000000000..ae2c2ddd5 --- /dev/null +++ b/.changeset/pre/fix-memory-journal-conflicts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix memory journal conflict detection skipping the first newer entry. diff --git a/.changeset/pre/fix-memory-journal-next-sequence.md b/.changeset/pre/fix-memory-journal-next-sequence.md new file mode 100644 index 000000000..f515118f0 --- /dev/null +++ b/.changeset/pre/fix-memory-journal-next-sequence.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Return the first unused remote sequence from the in-memory event journal. diff --git a/.changeset/pre/fix-memory-journal-relay.md b/.changeset/pre/fix-memory-journal-relay.md new file mode 100644 index 000000000..cee740fdd --- /dev/null +++ b/.changeset/pre/fix-memory-journal-relay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Relay entries imported into an in-memory event journal to other remotes. diff --git a/.changeset/pre/fix-memory-runner-health.md b/.changeset/pre/fix-memory-runner-health.md new file mode 100644 index 000000000..77a0e9ce8 --- /dev/null +++ b/.changeset/pre/fix-memory-runner-health.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve and update runner health in the in-memory cluster runner storage. diff --git a/.changeset/pre/fix-memory-workflow-interrupt.md b/.changeset/pre/fix-memory-workflow-interrupt.md new file mode 100644 index 000000000..625013f2b --- /dev/null +++ b/.changeset/pre/fix-memory-workflow-interrupt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Align in-memory workflow interrupt finalization with the cluster workflow engine. diff --git a/.changeset/fix-mermaid-escape-special-chars.md b/.changeset/pre/fix-mermaid-escape-special-chars.md similarity index 100% rename from .changeset/fix-mermaid-escape-special-chars.md rename to .changeset/pre/fix-mermaid-escape-special-chars.md diff --git a/.changeset/pre/fix-message-storage-clear-address-dedup.md b/.changeset/pre/fix-message-storage-clear-address-dedup.md new file mode 100644 index 000000000..f8d75f32a --- /dev/null +++ b/.changeset/pre/fix-message-storage-clear-address-dedup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Clear in-memory message primary-key indexes when clearing an entity address. diff --git a/.changeset/pre/fix-metric-attribute-key-collisions.md b/.changeset/pre/fix-metric-attribute-key-collisions.md new file mode 100644 index 000000000..3d00cc8e7 --- /dev/null +++ b/.changeset/pre/fix-metric-attribute-key-collisions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent distinct metric attribute sets from sharing registry state. diff --git a/.changeset/pre/fix-metric-is-metric.md b/.changeset/pre/fix-metric-is-metric.md new file mode 100644 index 000000000..00e185430 --- /dev/null +++ b/.changeset/pre/fix-metric-is-metric.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Metric.isMetric` to recognize metrics using their current runtime brand. diff --git a/.changeset/pre/fix-metric-linear-boundaries.md b/.changeset/pre/fix-metric-linear-boundaries.md new file mode 100644 index 000000000..d5a0219a4 --- /dev/null +++ b/.changeset/pre/fix-metric-linear-boundaries.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Metric.linearBoundaries` to space boundaries by the configured width. diff --git a/.changeset/pre/fix-metric-negative-max.md b/.changeset/pre/fix-metric-negative-max.md new file mode 100644 index 000000000..a2f1d2450 --- /dev/null +++ b/.changeset/pre/fix-metric-negative-max.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix histogram and summary maximum values for negative-only observations. diff --git a/.changeset/pre/fix-mssql-multitable-persistence-upsert.md b/.changeset/pre/fix-mssql-multitable-persistence-upsert.md new file mode 100644 index 000000000..8c870fa22 --- /dev/null +++ b/.changeset/pre/fix-mssql-multitable-persistence-upsert.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Generate valid MSSQL upserts for multi-table persistence. diff --git a/.changeset/pre/fix-multipart-file-stream-limits.md b/.changeset/pre/fix-multipart-file-stream-limits.md new file mode 100644 index 000000000..fd4d227f4 --- /dev/null +++ b/.changeset/pre/fix-multipart-file-stream-limits.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Terminate active multipart file streams when a parser limit is exceeded or the body ends unexpectedly, so file parts fail instead of hanging. diff --git a/.changeset/pre/fix-mutable-list-bounds.md b/.changeset/pre/fix-mutable-list-bounds.md new file mode 100644 index 000000000..d481f7a74 --- /dev/null +++ b/.changeset/pre/fix-mutable-list-bounds.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `MutableList.prepend` on empty lists and handle non-positive `toArrayN` bounds. diff --git a/.changeset/pre/fix-mutable-list-empty-filter.md b/.changeset/pre/fix-mutable-list-empty-filter.md new file mode 100644 index 000000000..4352c7314 --- /dev/null +++ b/.changeset/pre/fix-mutable-list-empty-filter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `MutableList.filter` leaving an invalid empty bucket when no values match. diff --git a/.changeset/fix-mutable-list-filter-length.md b/.changeset/pre/fix-mutable-list-filter-length.md similarity index 100% rename from .changeset/fix-mutable-list-filter-length.md rename to .changeset/pre/fix-mutable-list-filter-length.md diff --git a/.changeset/pre/fix-ndjson-split-utf8.md b/.changeset/pre/fix-ndjson-split-utf8.md new file mode 100644 index 000000000..4474398b4 --- /dev/null +++ b/.changeset/pre/fix-ndjson-split-utf8.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Decode split UTF-8 sequences correctly in NDJSON streams. diff --git a/.changeset/pre/fix-node-http-stream-failure.md b/.changeset/pre/fix-node-http-stream-failure.md new file mode 100644 index 000000000..b1049b475 --- /dev/null +++ b/.changeset/pre/fix-node-http-stream-failure.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Fix Node HTTP client requests hanging when a streamed request body fails. diff --git a/.changeset/pre/fix-node-path-file-url-flavor.md b/.changeset/pre/fix-node-path-file-url-flavor.md new file mode 100644 index 000000000..d1cb74f62 --- /dev/null +++ b/.changeset/pre/fix-node-path-file-url-flavor.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +NodePath: `layerPosix` and `layerWin32` now convert between paths and `file:` URLs using their own platform flavor instead of the host's. diff --git a/.changeset/pre/fix-node-pipeline-kill.md b/.changeset/pre/fix-node-pipeline-kill.md new file mode 100644 index 000000000..b551d51da --- /dev/null +++ b/.changeset/pre/fix-node-pipeline-kill.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Kill every process in a Node child process pipeline when killing its aggregate handle. diff --git a/.changeset/pre/fix-number-remainder-scientific-notation.md b/.changeset/pre/fix-number-remainder-scientific-notation.md new file mode 100644 index 000000000..9b43310d2 --- /dev/null +++ b/.changeset/pre/fix-number-remainder-scientific-notation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Number.remainder` for very small and large values formatted in scientific notation. diff --git a/.changeset/fix-object-keyword-json-schema.md b/.changeset/pre/fix-object-keyword-json-schema.md similarity index 100% rename from .changeset/fix-object-keyword-json-schema.md rename to .changeset/pre/fix-object-keyword-json-schema.md diff --git a/.changeset/pre/fix-one-shot-iterables.md b/.changeset/pre/fix-one-shot-iterables.md new file mode 100644 index 000000000..9908168c1 --- /dev/null +++ b/.changeset/pre/fix-one-shot-iterables.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix one-shot iterable handling in Array.rotate, Iterable.cartesian, and in-memory RunnerStorage acquisition diff --git a/.changeset/pre/fix-openai-header-redaction.md b/.changeset/pre/fix-openai-header-redaction.md new file mode 100644 index 000000000..1357ea76b --- /dev/null +++ b/.changeset/pre/fix-openai-header-redaction.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +Redact OpenAI organization and project headers from client errors. diff --git a/.changeset/fix-openai-mcp-tool-names.md b/.changeset/pre/fix-openai-mcp-tool-names.md similarity index 100% rename from .changeset/fix-openai-mcp-tool-names.md rename to .changeset/pre/fix-openai-mcp-tool-names.md diff --git a/.changeset/pre/fix-openai-response-failure.md b/.changeset/pre/fix-openai-response-failure.md new file mode 100644 index 000000000..dbe8abb99 --- /dev/null +++ b/.changeset/pre/fix-openai-response-failure.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Preserve OpenAI provider errors from failed response stream events. diff --git a/.changeset/pre/fix-openai-specialized-tool-output.md b/.changeset/pre/fix-openai-specialized-tool-output.md new file mode 100644 index 000000000..ac215a269 --- /dev/null +++ b/.changeset/pre/fix-openai-specialized-tool-output.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Emit specialized OpenAI tool results only once. diff --git a/.changeset/pre/fix-openai-system-input-text.md b/.changeset/pre/fix-openai-system-input-text.md new file mode 100644 index 000000000..830f6d00e --- /dev/null +++ b/.changeset/pre/fix-openai-system-input-text.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Encode OpenAI Responses API system messages as typed input text content. diff --git a/.changeset/pre/fix-openai-telemetry-response-namespace.md b/.changeset/pre/fix-openai-telemetry-response-namespace.md new file mode 100644 index 000000000..d0488f9d6 --- /dev/null +++ b/.changeset/pre/fix-openai-telemetry-response-namespace.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +--- + +Fix OpenAI response telemetry attribute types to use the emitted response namespace. diff --git a/.changeset/pre/fix-openai-web-search-action.md b/.changeset/pre/fix-openai-web-search-action.md new file mode 100644 index 000000000..ac95aedf1 --- /dev/null +++ b/.changeset/pre/fix-openai-web-search-action.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Fix OpenAI stable web search response decoding by preserving the provider action in tool call parameters. diff --git a/.changeset/pre/fix-openapi-from-api-cache-copy.md b/.changeset/pre/fix-openapi-from-api-cache-copy.md new file mode 100644 index 000000000..68a5c04f4 --- /dev/null +++ b/.changeset/pre/fix-openapi-from-api-cache-copy.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Return fresh OpenAPI specs from cached `OpenApi.fromApi` calls. diff --git a/.changeset/fix-openapi-generator-form-urlencoded.md b/.changeset/pre/fix-openapi-generator-form-urlencoded.md similarity index 100% rename from .changeset/fix-openapi-generator-form-urlencoded.md rename to .changeset/pre/fix-openapi-generator-form-urlencoded.md diff --git a/.changeset/fix-openapi-generator-swagger2openapi.md b/.changeset/pre/fix-openapi-generator-swagger2openapi.md similarity index 100% rename from .changeset/fix-openapi-generator-swagger2openapi.md rename to .changeset/pre/fix-openapi-generator-swagger2openapi.md diff --git a/.changeset/fix-openapi-preserve-multiple-response-content-types.md b/.changeset/pre/fix-openapi-preserve-multiple-response-content-types.md similarity index 100% rename from .changeset/fix-openapi-preserve-multiple-response-content-types.md rename to .changeset/pre/fix-openapi-preserve-multiple-response-content-types.md diff --git a/.changeset/fix-openrouter-sparse-array.md b/.changeset/pre/fix-openrouter-sparse-array.md similarity index 100% rename from .changeset/fix-openrouter-sparse-array.md rename to .changeset/pre/fix-openrouter-sparse-array.md diff --git a/.changeset/pre/fix-otel-logger-clock-skew.md b/.changeset/pre/fix-otel-logger-clock-skew.md new file mode 100644 index 000000000..5783219d7 --- /dev/null +++ b/.changeset/pre/fix-otel-logger-clock-skew.md @@ -0,0 +1,7 @@ +--- +"@effect/opentelemetry": patch +--- + +Use the Effect wall clock for log timestamps to match span timestamps. + +The Logger used `Date.now()` directly for log `timestamp` while the Tracer used `clock.currentTimeNanosUnsafe()` for span `startTime`. These could diverge when the high-resolution wall-clock origin drifted, causing logs to appear before their parent span. Both now use the same Effect wall clock via `nanosToHrTime(clock.currentTimeNanosUnsafe())`. diff --git a/.changeset/fix-otel-logger-severity-number.md b/.changeset/pre/fix-otel-logger-severity-number.md similarity index 100% rename from .changeset/fix-otel-logger-severity-number.md rename to .changeset/pre/fix-otel-logger-severity-number.md diff --git a/.changeset/pre/fix-otel-logger-shutdown.md b/.changeset/pre/fix-otel-logger-shutdown.md new file mode 100644 index 000000000..d81109863 --- /dev/null +++ b/.changeset/pre/fix-otel-logger-shutdown.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Ensure logger providers shut down when flushing fails. diff --git a/.changeset/pre/fix-otlp-exporter-shutdown.md b/.changeset/pre/fix-otlp-exporter-shutdown.md new file mode 100644 index 000000000..444ba3ce4 --- /dev/null +++ b/.changeset/pre/fix-otlp-exporter-shutdown.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix OTLP exporter shutdown to await in-flight and final buffered exports up to the configured shutdown timeout. diff --git a/.changeset/pre/fix-otlp-resource-attributes.md b/.changeset/pre/fix-otlp-resource-attributes.md new file mode 100644 index 000000000..a461bfa33 --- /dev/null +++ b/.changeset/pre/fix-otlp-resource-attributes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `OtlpResource` to decode percent-encoded environment attributes and preserve bigint precision. diff --git a/.changeset/pre/fix-pending-interruptible-mask.md b/.changeset/pre/fix-pending-interruptible-mask.md new file mode 100644 index 000000000..e0a8494e4 --- /dev/null +++ b/.changeset/pre/fix-pending-interruptible-mask.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Deliver pending interrupts when interruptibleMask restores fiber interruptibility. diff --git a/.changeset/pre/fix-persisted-cluster-reply-hang.md b/.changeset/pre/fix-persisted-cluster-reply-hang.md new file mode 100644 index 000000000..2a1b8732f --- /dev/null +++ b/.changeset/pre/fix-persisted-cluster-reply-hang.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Persist a serializable defect when a cluster reply cannot be encoded, preventing persisted entity callers from hanging. diff --git a/.changeset/pre/fix-persisted-queue-attempt-accounting.md b/.changeset/pre/fix-persisted-queue-attempt-accounting.md new file mode 100644 index 000000000..8737be085 --- /dev/null +++ b/.changeset/pre/fix-persisted-queue-attempt-accounting.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `PersistedQueue` to count schema decoding and malformed SQL payload failures as processing attempts. diff --git a/.changeset/pre/fix-prompt-all-iterables.md b/.changeset/pre/fix-prompt-all-iterables.md new file mode 100644 index 000000000..070620a8c --- /dev/null +++ b/.changeset/pre/fix-prompt-all-iterables.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support empty records and non-array iterables in `Prompt.all`. diff --git a/.changeset/pre/fix-proto-record-assignment.md b/.changeset/pre/fix-proto-record-assignment.md new file mode 100644 index 000000000..1d960ffa4 --- /dev/null +++ b/.changeset/pre/fix-proto-record-assignment.md @@ -0,0 +1,16 @@ +--- +"effect": patch +"@effect/ai-anthropic": patch +"@effect/ai-openai": patch +"@effect/ai-openai-compat": patch +"@effect/ai-openrouter": patch +"@effect/docgen": patch +"@effect/openapi-generator": patch +"@effect/opentelemetry": patch +"@effect/sql-mssql": patch +"@effect/sql-sqlite-do": patch +"@effect/sql-sqlite-wasm": patch +"@effect/vitest": patch +--- + +Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. diff --git a/.changeset/pre/fix-pubsub-replay-retention.md b/.changeset/pre/fix-pubsub-replay-retention.md new file mode 100644 index 000000000..632765fdc --- /dev/null +++ b/.changeset/pre/fix-pubsub-replay-retention.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent replay-enabled PubSubs from retaining values beyond each subscription's replay window. diff --git a/.changeset/fix-queue-collect-duplication.md b/.changeset/pre/fix-queue-collect-duplication.md similarity index 100% rename from .changeset/fix-queue-collect-duplication.md rename to .changeset/pre/fix-queue-collect-duplication.md diff --git a/.changeset/fix-random-string-seeds.md b/.changeset/pre/fix-random-string-seeds.md similarity index 100% rename from .changeset/fix-random-string-seeds.md rename to .changeset/pre/fix-random-string-seeds.md diff --git a/.changeset/pre/fix-rate-limiter-sleep.md b/.changeset/pre/fix-rate-limiter-sleep.md new file mode 100644 index 000000000..73633920b --- /dev/null +++ b/.changeset/pre/fix-rate-limiter-sleep.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename `RateLimiter.makeSleep` to `RateLimiter.sleep` and support self-first partially applied and uncurried usage. diff --git a/.changeset/fix-ratelimiter-tokenbucket-redis-ttl.md b/.changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md similarity index 100% rename from .changeset/fix-ratelimiter-tokenbucket-redis-ttl.md rename to .changeset/pre/fix-ratelimiter-tokenbucket-redis-ttl.md diff --git a/.changeset/pre/fix-react-native-sqlite-values.md b/.changeset/pre/fix-react-native-sqlite-values.md new file mode 100644 index 000000000..97070d9b0 --- /dev/null +++ b/.changeset/pre/fix-react-native-sqlite-values.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-react-native": patch +--- + +Return selected rows from synchronous and asynchronous value queries. diff --git a/.changeset/pre/fix-reactive-query-metadata.md b/.changeset/pre/fix-reactive-query-metadata.md new file mode 100644 index 000000000..6617c533b --- /dev/null +++ b/.changeset/pre/fix-reactive-query-metadata.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve serialization and retention metadata on reactive `AtomRpc` and `AtomHttpApi` queries. diff --git a/.changeset/pre/fix-redis-persisted-queue.md b/.changeset/pre/fix-redis-persisted-queue.md new file mode 100644 index 000000000..8ec524967 --- /dev/null +++ b/.changeset/pre/fix-redis-persisted-queue.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Redis-backed `PersistedQueue` reset and failed-item handling. diff --git a/.changeset/fix-remainder-scientific-notation.md b/.changeset/pre/fix-remainder-scientific-notation.md similarity index 100% rename from .changeset/fix-remainder-scientific-notation.md rename to .changeset/pre/fix-remainder-scientific-notation.md diff --git a/.changeset/fix-request-resolver-pending-batches-leak.md b/.changeset/pre/fix-request-resolver-pending-batches-leak.md similarity index 100% rename from .changeset/fix-request-resolver-pending-batches-leak.md rename to .changeset/pre/fix-request-resolver-pending-batches-leak.md diff --git a/.changeset/fix-retry-transient-autocomplete.md b/.changeset/pre/fix-retry-transient-autocomplete.md similarity index 100% rename from .changeset/fix-retry-transient-autocomplete.md rename to .changeset/pre/fix-retry-transient-autocomplete.md diff --git a/.changeset/fix-rpc-http-requestids-finalizer.md b/.changeset/pre/fix-rpc-http-requestids-finalizer.md similarity index 100% rename from .changeset/fix-rpc-http-requestids-finalizer.md rename to .changeset/pre/fix-rpc-http-requestids-finalizer.md diff --git a/.changeset/fix-rpc-json-id-edges.md b/.changeset/pre/fix-rpc-json-id-edges.md similarity index 100% rename from .changeset/fix-rpc-json-id-edges.md rename to .changeset/pre/fix-rpc-json-id-edges.md diff --git a/.changeset/fix-rpc-unknown-tag-isolation.md b/.changeset/pre/fix-rpc-unknown-tag-isolation.md similarity index 100% rename from .changeset/fix-rpc-unknown-tag-isolation.md rename to .changeset/pre/fix-rpc-unknown-tag-isolation.md diff --git a/.changeset/pre/fix-runner-stream-completion.md b/.changeset/pre/fix-runner-stream-completion.md new file mode 100644 index 000000000..414fefe90 --- /dev/null +++ b/.changeset/pre/fix-runner-stream-completion.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +End runner streams after emitting their terminal replies. diff --git a/.changeset/pre/fix-schedule-during.md b/.changeset/pre/fix-schedule-during.md new file mode 100644 index 000000000..c881221af --- /dev/null +++ b/.changeset/pre/fix-schedule-during.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schedule.during` to recur until the configured duration has elapsed. diff --git a/.changeset/fix-schedule-fixed-double-exec.md b/.changeset/pre/fix-schedule-fixed-double-exec.md similarity index 100% rename from .changeset/fix-schedule-fixed-double-exec.md rename to .changeset/pre/fix-schedule-fixed-double-exec.md diff --git a/.changeset/fix-schedule-reduce-sync-state.md b/.changeset/pre/fix-schedule-reduce-sync-state.md similarity index 100% rename from .changeset/fix-schedule-reduce-sync-state.md rename to .changeset/pre/fix-schedule-reduce-sync-state.md diff --git a/.changeset/fix-schema-arbitrary-exclusive-bounds.md b/.changeset/pre/fix-schema-arbitrary-exclusive-bounds.md similarity index 100% rename from .changeset/fix-schema-arbitrary-exclusive-bounds.md rename to .changeset/pre/fix-schema-arbitrary-exclusive-bounds.md diff --git a/.changeset/pre/fix-schema-bracket-prototype-pollution.md b/.changeset/pre/fix-schema-bracket-prototype-pollution.md new file mode 100644 index 000000000..f570f34ca --- /dev/null +++ b/.changeset/pre/fix-schema-bracket-prototype-pollution.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix a bug where decoding bracket paths from FormData or URLSearchParams could mutate inherited object prototypes. diff --git a/.changeset/fix-schema-defect-message.md b/.changeset/pre/fix-schema-defect-message.md similarity index 100% rename from .changeset/fix-schema-defect-message.md rename to .changeset/pre/fix-schema-defect-message.md diff --git a/.changeset/fix-schema-encode-keys-property-keys.md b/.changeset/pre/fix-schema-encode-keys-property-keys.md similarity index 100% rename from .changeset/fix-schema-encode-keys-property-keys.md rename to .changeset/pre/fix-schema-encode-keys-property-keys.md diff --git a/.changeset/fix-schema-encodekeys-class.md b/.changeset/pre/fix-schema-encodekeys-class.md similarity index 100% rename from .changeset/fix-schema-encodekeys-class.md rename to .changeset/pre/fix-schema-encodekeys-class.md diff --git a/.changeset/fix-schema-encodekeys-struct.md b/.changeset/pre/fix-schema-encodekeys-struct.md similarity index 100% rename from .changeset/fix-schema-encodekeys-struct.md rename to .changeset/pre/fix-schema-encodekeys-struct.md diff --git a/.changeset/fix-schema-encoding-checks.md b/.changeset/pre/fix-schema-encoding-checks.md similarity index 100% rename from .changeset/fix-schema-encoding-checks.md rename to .changeset/pre/fix-schema-encoding-checks.md diff --git a/.changeset/fix-schema-identifier-expected-message.md b/.changeset/pre/fix-schema-identifier-expected-message.md similarity index 100% rename from .changeset/fix-schema-identifier-expected-message.md rename to .changeset/pre/fix-schema-identifier-expected-message.md diff --git a/.changeset/pre/fix-schema-is-json-records.md b/.changeset/pre/fix-schema-is-json-records.md new file mode 100644 index 000000000..5a71fc1cc --- /dev/null +++ b/.changeset/pre/fix-schema-is-json-records.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SchemaAST.isJson` to reject class instances and other non-record objects. diff --git a/.changeset/fix-schema-is-uuid.md b/.changeset/pre/fix-schema-is-uuid.md similarity index 100% rename from .changeset/fix-schema-is-uuid.md rename to .changeset/pre/fix-schema-is-uuid.md diff --git a/.changeset/pre/fix-schema-json-tuple-allof.md b/.changeset/pre/fix-schema-json-tuple-allof.md new file mode 100644 index 000000000..cbf16877b --- /dev/null +++ b/.changeset/pre/fix-schema-json-tuple-allof.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix JSON Schema `allOf` imports for tuple intersections and preserve primitive refinements when combining literal constraints. diff --git a/.changeset/pre/fix-schema-make-nested-class-union.md b/.changeset/pre/fix-schema-make-nested-class-union.md new file mode 100644 index 000000000..cc53b35a4 --- /dev/null +++ b/.changeset/pre/fix-schema-make-nested-class-union.md @@ -0,0 +1,45 @@ +--- +"effect": patch +--- + +Fix `Schema.make` to preserve existing nested `Schema.Class` instances, including in array fields, while recursively constructing plain class inputs provided at runtime inside unions. Constructor defaults remain scoped to structural field and element occurrences, with `SchemaAST.Context.constructorDefault` representing the single default link for each occurrence. + +Optimize `Function.memoize` to use a single `WeakMap` lookup for cached values. Its callback no longer accepts `undefined` as a return type because `undefined` represents a cache miss. + +The performance of the two array paths can be reproduced by saving the following program as +`scratchpad/schema-make-6890-benchmark.ts` and running `node scratchpad/schema-make-6890-benchmark.ts` from the repository +root: + +```ts +import { Schema } from "effect" +import { performance } from "node:perf_hooks" + +class Row extends Schema.Class("Row")({ value: Schema.String }) {} +class DirectTable extends Schema.Class("DirectTable")({ rows: Schema.Array(Row) }) {} +class UnionTable extends Schema.Class("UnionTable")({ rows: Schema.Array(Schema.Union([Row])) }) {} + +const rows = Array.from({ length: 30_000 }, (_, value) => Row.make({ value: String(value) })) + +function benchmark(label: string, make: () => { readonly rows: ReadonlyArray }) { + const samples: Array = [] + for (let i = 0; i < 6; i++) { + const start = performance.now() + const result = make() + samples.push(performance.now() - start) + if (result.rows[0] !== rows[0] || result.rows.at(-1) !== rows.at(-1)) { + throw new Error(`${label} did not preserve Row identity`) + } + } + console.log(`${label}: ${samples.slice(1).map((n) => n.toFixed(3)).join(", ")} ms`) +} + +benchmark("Array(Class)", () => DirectTable.make({ rows })) +benchmark("Array(Union([Class]))", () => UnionTable.make({ rows })) +``` + +Representative local results on Node 24.12.0 (six runs, with the first discarded): + +```text +Array(Class): 0.639, 0.498, 0.447, 0.448, 0.451 ms +Array(Union([Class])): 3.141, 2.195, 2.126, 2.108, 2.057 ms +``` diff --git a/.changeset/fix-schema-option-non-schema-failures.md b/.changeset/pre/fix-schema-option-non-schema-failures.md similarity index 100% rename from .changeset/fix-schema-option-non-schema-failures.md rename to .changeset/pre/fix-schema-option-non-schema-failures.md diff --git a/.changeset/fix-schema-parser-checks.md b/.changeset/pre/fix-schema-parser-checks.md similarity index 100% rename from .changeset/fix-schema-parser-checks.md rename to .changeset/pre/fix-schema-parser-checks.md diff --git a/.changeset/pre/fix-schema-pattern-state.md b/.changeset/pre/fix-schema-pattern-state.md new file mode 100644 index 000000000..2a321daad --- /dev/null +++ b/.changeset/pre/fix-schema-pattern-state.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make `Schema.isPattern` deterministic for regular expressions with global or sticky flags. diff --git a/.changeset/pre/fix-schema-representation-identifiers.md b/.changeset/pre/fix-schema-representation-identifiers.md new file mode 100644 index 000000000..f4a8a2391 --- /dev/null +++ b/.changeset/pre/fix-schema-representation-identifiers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +SchemaRepresentation: generate references from encoded AST identity, suffix colliding identifiers instead of throwing, and preserve sharing across property-key context. This avoids false-positive duplicate identifier errors while keeping referentially distinct schemas addressable; generated fallback definitions now use the clearer `Encoded` suffix. diff --git a/.changeset/pre/fix-schema-sentinel-declaration.md b/.changeset/pre/fix-schema-sentinel-declaration.md new file mode 100644 index 000000000..f042aef64 --- /dev/null +++ b/.changeset/pre/fix-schema-sentinel-declaration.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Mark the internal `~sentinels` Schema annotation as `@internal` so release declaration stripping removes it together with `SchemaAST.Sentinel`. This keeps the published declarations self-consistent for consumers that type-check dependencies with `skipLibCheck: false`. diff --git a/.changeset/pre/fix-schema-tuple-post-rest-indexing.md b/.changeset/pre/fix-schema-tuple-post-rest-indexing.md new file mode 100644 index 000000000..8546cc8be --- /dev/null +++ b/.changeset/pre/fix-schema-tuple-post-rest-indexing.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Schema.toFormatter` and `Schema.toEquivalence` indexing for tuples with multiple post-rest elements. diff --git a/.changeset/pre/fix-schema-union-dispatch-order.md b/.changeset/pre/fix-schema-union-dispatch-order.md new file mode 100644 index 000000000..61876f469 --- /dev/null +++ b/.changeset/pre/fix-schema-union-dispatch-order.md @@ -0,0 +1,12 @@ +--- +"effect": patch +--- + +Fix union candidate selection and decoding order so that unions now: + +- consider matches from every sentinel key instead of dropping valid members after the first match; +- reject ambiguous `oneOf` inputs when members with different sentinel keys both match; +- preserve declared member order when combining discriminated members with non-discriminated fallbacks; +- commit concurrent decoding results in declaration order instead of completion order. + +Reserved SSE failure event names with non-`Cause` data are now emitted as application events instead of producing a runtime defect. diff --git a/.changeset/pre/fix-schema-union-pruning.md b/.changeset/pre/fix-schema-union-pruning.md new file mode 100644 index 000000000..c1029ae56 --- /dev/null +++ b/.changeset/pre/fix-schema-union-pruning.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Union candidate selection for recovering middleware and suspended members. diff --git a/.changeset/pre/fix-scoped-ref-failed-replacement.md b/.changeset/pre/fix-scoped-ref-failed-replacement.md new file mode 100644 index 000000000..5caffbbca --- /dev/null +++ b/.changeset/pre/fix-scoped-ref-failed-replacement.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep the current `ScopedRef` resource alive when acquiring its replacement fails. diff --git a/.changeset/fix-searchparam-initial-decode.md b/.changeset/pre/fix-searchparam-initial-decode.md similarity index 100% rename from .changeset/fix-searchparam-initial-decode.md rename to .changeset/pre/fix-searchparam-initial-decode.md diff --git a/.changeset/pre/fix-semaphore-with-permits-interrupt-leak.md b/.changeset/pre/fix-semaphore-with-permits-interrupt-leak.md new file mode 100644 index 000000000..aba537cb5 --- /dev/null +++ b/.changeset/pre/fix-semaphore-with-permits-interrupt-leak.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Semaphore.withPermits` leaking permits when interrupted between acquiring them and installing their release. diff --git a/.changeset/fix-serializable-wire-transfer.md b/.changeset/pre/fix-serializable-wire-transfer.md similarity index 100% rename from .changeset/fix-serializable-wire-transfer.md rename to .changeset/pre/fix-serializable-wire-transfer.md diff --git a/.changeset/pre/fix-sliding-size-chunks.md b/.changeset/pre/fix-sliding-size-chunks.md new file mode 100644 index 000000000..9d06bc18d --- /dev/null +++ b/.changeset/pre/fix-sliding-size-chunks.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.slidingSize` to produce the same windows regardless of upstream chunk boundaries. diff --git a/.changeset/pre/fix-sql-persisted-queue-lock-refresh.md b/.changeset/pre/fix-sql-persisted-queue-lock-refresh.md new file mode 100644 index 000000000..19a98949b --- /dev/null +++ b/.changeset/pre/fix-sql-persisted-queue-lock-refresh.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix SQL-backed persisted queues to refresh locks for actively acquired elements. diff --git a/.changeset/pre/fix-sqlite-persisted-queue.md b/.changeset/pre/fix-sqlite-persisted-queue.md new file mode 100644 index 000000000..bb6bcc1b1 --- /dev/null +++ b/.changeset/pre/fix-sqlite-persisted-queue.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix SQL persisted queue delivery on SQLite builds without `SQLITE_ENABLE_UPDATE_DELETE_LIMIT`. diff --git a/.changeset/pre/fix-sse-last-event-id.md b/.changeset/pre/fix-sse-last-event-id.md new file mode 100644 index 000000000..69109fa35 --- /dev/null +++ b/.changeset/pre/fix-sse-last-event-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Retain the last SSE event ID across dispatched events. diff --git a/.changeset/pre/fix-sse-leading-bom.md b/.changeset/pre/fix-sse-leading-bom.md new file mode 100644 index 000000000..c1b7716c8 --- /dev/null +++ b/.changeset/pre/fix-sse-leading-bom.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Recognize and ignore a leading UTF-8 byte order mark in server-sent event streams. diff --git a/.changeset/pre/fix-sse-retry-directives.md b/.changeset/pre/fix-sse-retry-directives.md new file mode 100644 index 000000000..e80c35962 --- /dev/null +++ b/.changeset/pre/fix-sse-retry-directives.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore malformed retry directives when parsing server-sent event streams. diff --git a/.changeset/pre/fix-stream-aggregate-within-idle.md b/.changeset/pre/fix-stream-aggregate-within-idle.md new file mode 100644 index 000000000..96744190c --- /dev/null +++ b/.changeset/pre/fix-stream-aggregate-within-idle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.aggregateWithin` and `Stream.groupedWithin` retaining fiber continuations on every schedule tick while upstream is idle. diff --git a/.changeset/pre/fix-stream-execution-plan-retries.md b/.changeset/pre/fix-stream-execution-plan-retries.md new file mode 100644 index 000000000..0174fbabe --- /dev/null +++ b/.changeset/pre/fix-stream-execution-plan-retries.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.withExecutionPlan` retry limits resetting after partial stream emissions. diff --git a/.changeset/fix-stream-grouped-within-flush.md b/.changeset/pre/fix-stream-grouped-within-flush.md similarity index 100% rename from .changeset/fix-stream-grouped-within-flush.md rename to .changeset/pre/fix-stream-grouped-within-flush.md diff --git a/.changeset/pre/fix-stream-haltwhen.md b/.changeset/pre/fix-stream-haltwhen.md new file mode 100644 index 000000000..8cb383c06 --- /dev/null +++ b/.changeset/pre/fix-stream-haltwhen.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.haltWhen` to observe halt effects at pull boundaries for synchronous streams. diff --git a/.changeset/pre/fix-stream-map-accum-array-effect.md b/.changeset/pre/fix-stream-map-accum-array-effect.md new file mode 100644 index 000000000..c67a81799 --- /dev/null +++ b/.changeset/pre/fix-stream-map-accum-array-effect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix data-first dispatch for `Stream.mapAccumArrayEffect`. diff --git a/.changeset/pre/fix-stream-range-zero-chunk.md b/.changeset/pre/fix-stream-range-zero-chunk.md new file mode 100644 index 000000000..cc28d66a7 --- /dev/null +++ b/.changeset/pre/fix-stream-range-zero-chunk.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `Stream.range` emits the full range when the chunk size is zero. diff --git a/.changeset/fix-stream-run-for-each-while.md b/.changeset/pre/fix-stream-run-for-each-while.md similarity index 100% rename from .changeset/fix-stream-run-for-each-while.md rename to .changeset/pre/fix-stream-run-for-each-while.md diff --git a/.changeset/fix-stream-scan-effect.md b/.changeset/pre/fix-stream-scan-effect.md similarity index 100% rename from .changeset/fix-stream-scan-effect.md rename to .changeset/pre/fix-stream-scan-effect.md diff --git a/.changeset/fix-stream-scoped-scope.md b/.changeset/pre/fix-stream-scoped-scope.md similarity index 100% rename from .changeset/fix-stream-scoped-scope.md rename to .changeset/pre/fix-stream-scoped-scope.md diff --git a/.changeset/pre/fix-string-case-digits.md b/.changeset/pre/fix-string-case-digits.md new file mode 100644 index 000000000..5aaa2cd15 --- /dev/null +++ b/.changeset/pre/fix-string-case-digits.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `String.camelCase` and `String.pascalCase` handling of numeric word segments, and add `String.configCase` for configuration key casing. diff --git a/.changeset/fix-strip-approval-artifacts-multi-round.md b/.changeset/pre/fix-strip-approval-artifacts-multi-round.md similarity index 100% rename from .changeset/fix-strip-approval-artifacts-multi-round.md rename to .changeset/pre/fix-strip-approval-artifacts-multi-round.md diff --git a/.changeset/fix-struct-utility-types-simplify.md b/.changeset/pre/fix-struct-utility-types-simplify.md similarity index 100% rename from .changeset/fix-struct-utility-types-simplify.md rename to .changeset/pre/fix-struct-utility-types-simplify.md diff --git a/.changeset/pre/fix-structural-proto-equality.md b/.changeset/pre/fix-structural-proto-equality.md new file mode 100644 index 000000000..eae88bf89 --- /dev/null +++ b/.changeset/pre/fix-structural-proto-equality.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix structural equality for request-style values when structural hashes collide. diff --git a/.changeset/fix-structwithrest-index-signatures.md b/.changeset/pre/fix-structwithrest-index-signatures.md similarity index 100% rename from .changeset/fix-structwithrest-index-signatures.md rename to .changeset/pre/fix-structwithrest-index-signatures.md diff --git a/.changeset/pre/fix-subscription-ref-get-and-update-some.md b/.changeset/pre/fix-subscription-ref-get-and-update-some.md new file mode 100644 index 000000000..d231f9c6c --- /dev/null +++ b/.changeset/pre/fix-subscription-ref-get-and-update-some.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SubscriptionRef.getAndUpdateSome` to return the current value when no update is selected. diff --git a/.changeset/pre/fix-subscriptionref-getandupdateeffect.md b/.changeset/pre/fix-subscriptionref-getandupdateeffect.md new file mode 100644 index 000000000..4983ed0b0 --- /dev/null +++ b/.changeset/pre/fix-subscriptionref-getandupdateeffect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SubscriptionRef.getAndUpdateEffect` to execute the effectful update. diff --git a/.changeset/fix-tagged-union-class-sentinels.md b/.changeset/pre/fix-tagged-union-class-sentinels.md similarity index 100% rename from .changeset/fix-tagged-union-class-sentinels.md rename to .changeset/pre/fix-tagged-union-class-sentinels.md diff --git a/.changeset/fix-tagged-union-match-unify.md b/.changeset/pre/fix-tagged-union-match-unify.md similarity index 100% rename from .changeset/fix-tagged-union-match-unify.md rename to .changeset/pre/fix-tagged-union-match-unify.md diff --git a/.changeset/fix-to-tagged-union-isanyof-custom-tags.md b/.changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md similarity index 100% rename from .changeset/fix-to-tagged-union-isanyof-custom-tags.md rename to .changeset/pre/fix-to-tagged-union-isanyof-custom-tags.md diff --git a/.changeset/pre/fix-tool-provider-defined-clone.md b/.changeset/pre/fix-tool-provider-defined-clone.md new file mode 100644 index 000000000..6306c054d --- /dev/null +++ b/.changeset/pre/fix-tool-provider-defined-clone.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Tool: preserve the tool kind when cloning provider-defined and dynamic tools. + +`Tool.addDependency`, `setParameters`, `setSuccess`, `setFailure`, `annotate`, and `annotateMerge` previously rebuilt the tool as a user-defined tool, which flipped `Tool.isProviderDefined` to `false`, corrupted the provider `id` (e.g. `anthropic.memory_20250818`), and crashed `Tool.getStrictMode`. These operations now clone the tool while preserving its prototype, `id`, and kind. Provider-defined tools also now carry an empty annotations context so `Tool.getStrictMode`/`annotate` work on them. Closes #2615. diff --git a/.changeset/pre/fix-trie-key-replacement.md b/.changeset/pre/fix-trie-key-replacement.md new file mode 100644 index 000000000..00d370732 --- /dev/null +++ b/.changeset/pre/fix-trie-key-replacement.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Trie.insert` to replace existing values without mutating the original trie or increasing its size. diff --git a/.changeset/pre/fix-trie-longest-prefix.md b/.changeset/pre/fix-trie-longest-prefix.md new file mode 100644 index 000000000..bf422ac6d --- /dev/null +++ b/.changeset/pre/fix-trie-longest-prefix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Trie.longestPrefixOf` returning a valued sibling that does not match the input key. diff --git a/.changeset/pre/fix-trie-undefined-values.md b/.changeset/pre/fix-trie-undefined-values.md new file mode 100644 index 000000000..12a9e3f13 --- /dev/null +++ b/.changeset/pre/fix-trie-undefined-values.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Trie` to preserve entries whose value is `undefined`. diff --git a/.changeset/fix-tuple-with-rest-post-rest-index-drift.md b/.changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md similarity index 100% rename from .changeset/fix-tuple-with-rest-post-rest-index-drift.md rename to .changeset/pre/fix-tuple-with-rest-post-rest-index-drift.md diff --git a/.changeset/fix-tuple-with-rest-post-rest-validation.md b/.changeset/pre/fix-tuple-with-rest-post-rest-validation.md similarity index 100% rename from .changeset/fix-tuple-with-rest-post-rest-validation.md rename to .changeset/pre/fix-tuple-with-rest-post-rest-validation.md diff --git a/.changeset/pre/fix-txpubsub-publish-all-iterables.md b/.changeset/pre/fix-txpubsub-publish-all-iterables.md new file mode 100644 index 000000000..390063b2b --- /dev/null +++ b/.changeset/pre/fix-txpubsub-publish-all-iterables.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `TxPubSub.publishAll` dropping values from one-shot iterables when a transaction retries. diff --git a/.changeset/pre/fix-txqueue-closing-drain.md b/.changeset/pre/fix-txqueue-closing-drain.md new file mode 100644 index 000000000..41b0208f8 --- /dev/null +++ b/.changeset/pre/fix-txqueue-closing-drain.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `TxQueue.poll` and `TxQueue.clear` complete a closing queue after draining its buffered items. diff --git a/.changeset/pre/fix-txqueue-offer-all-iterables.md b/.changeset/pre/fix-txqueue-offer-all-iterables.md new file mode 100644 index 000000000..e6f159cf1 --- /dev/null +++ b/.changeset/pre/fix-txqueue-offer-all-iterables.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `TxQueue.offerAll` to preserve one-shot iterables across transaction retries and repeated runs. diff --git a/.changeset/fix-types-voidifempty.md b/.changeset/pre/fix-types-voidifempty.md similarity index 100% rename from .changeset/fix-types-voidifempty.md rename to .changeset/pre/fix-types-voidifempty.md diff --git a/.changeset/pre/fix-variant-schema-default-cache.md b/.changeset/pre/fix-variant-schema-default-cache.md new file mode 100644 index 000000000..4449cb175 --- /dev/null +++ b/.changeset/pre/fix-variant-schema-default-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Separate the default `VariantSchema` cache from named variant entries. diff --git a/.changeset/pre/fix-vitest-proxy-chained-helpers.md b/.changeset/pre/fix-vitest-proxy-chained-helpers.md new file mode 100644 index 000000000..a6b5ecbf2 --- /dev/null +++ b/.changeset/pre/fix-vitest-proxy-chained-helpers.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Preserve chained vitest helpers like `it.describe.each` and `it.skip.each` when accessed through the `it` proxy. Previously the proxy returned bound copies of vitest's functions, which stripped their static helper properties and caused `TypeError: it.describe.each is not a function`. diff --git a/.changeset/pre/fix-vitest-record-schema-arbitrary.md b/.changeset/pre/fix-vitest-record-schema-arbitrary.md new file mode 100644 index 000000000..840708d75 --- /dev/null +++ b/.changeset/pre/fix-vitest-record-schema-arbitrary.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Fix record-form property tests to convert Schema values to FastCheck arbitraries. diff --git a/.changeset/pre/fix-vitest-runner-import.md b/.changeset/pre/fix-vitest-runner-import.md new file mode 100644 index 000000000..1f714c770 --- /dev/null +++ b/.changeset/pre/fix-vitest-runner-import.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": minor +--- + +Require Vitest 4.1 or later and read suite state from `TestRunner`, removing the direct `@vitest/runner` import and support for Vitest 3 and 4.0. diff --git a/.changeset/pre/fix-vitest-throws-assertions.md b/.changeset/pre/fix-vitest-throws-assertions.md new file mode 100644 index 000000000..307bb3837 --- /dev/null +++ b/.changeset/pre/fix-vitest-throws-assertions.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Ensure `throws` and `throwsAsync` fail when the supplied operation returns or resolves without throwing. diff --git a/.changeset/pre/fix-void-mcp-tool-results.md b/.changeset/pre/fix-void-mcp-tool-results.md new file mode 100644 index 000000000..5febf7769 --- /dev/null +++ b/.changeset/pre/fix-void-mcp-tool-results.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep MCP tool calls that return void successful. diff --git a/.changeset/fix-void-response-encoding.md b/.changeset/pre/fix-void-response-encoding.md similarity index 100% rename from .changeset/fix-void-response-encoding.md rename to .changeset/pre/fix-void-response-encoding.md diff --git a/.changeset/pre/fix-worker-runner-cleanup.md b/.changeset/pre/fix-worker-runner-cleanup.md new file mode 100644 index 000000000..ea8d118e0 --- /dev/null +++ b/.changeset/pre/fix-worker-runner-cleanup.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-browser": patch +"@effect/platform-bun": patch +"@effect/platform-node": patch +--- + +Fix worker runner disconnect notifications and event listener cleanup. diff --git a/.changeset/fix-workflow-defect-reply-serialization.md b/.changeset/pre/fix-workflow-defect-reply-serialization.md similarity index 100% rename from .changeset/fix-workflow-defect-reply-serialization.md rename to .changeset/pre/fix-workflow-defect-reply-serialization.md diff --git a/.changeset/pre/fix-workflow-entity-client-collision.md b/.changeset/pre/fix-workflow-entity-client-collision.md new file mode 100644 index 000000000..b682dc921 --- /dev/null +++ b/.changeset/pre/fix-workflow-entity-client-collision.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix ClusterWorkflowEngine partial workflow clients colliding with full workflow clients. diff --git a/.changeset/fix-workflow-proxy-rpc-handler-context.md b/.changeset/pre/fix-workflow-proxy-rpc-handler-context.md similarity index 100% rename from .changeset/fix-workflow-proxy-rpc-handler-context.md rename to .changeset/pre/fix-workflow-proxy-rpc-handler-context.md diff --git a/.changeset/pre/fix-workflow-trace-context.md b/.changeset/pre/fix-workflow-trace-context.md new file mode 100644 index 000000000..5ede2477b --- /dev/null +++ b/.changeset/pre/fix-workflow-trace-context.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Propagate trace context through persisted cluster workflow requests. diff --git a/.changeset/pre/fix-xhr-form-data.md b/.changeset/pre/fix-xhr-form-data.md new file mode 100644 index 000000000..28bc21691 --- /dev/null +++ b/.changeset/pre/fix-xhr-form-data.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Fix form data decoding for XMLHttpRequest client responses. diff --git a/.changeset/pre/fix-zero-max-bytes.md b/.changeset/pre/fix-zero-max-bytes.md new file mode 100644 index 000000000..c38fd6e0b --- /dev/null +++ b/.changeset/pre/fix-zero-max-bytes.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Enforce zero-byte limits when consuming Node readable streams. diff --git a/.changeset/pre/fix-zsh-mixed-command-completions.md b/.changeset/pre/fix-zsh-mixed-command-completions.md new file mode 100644 index 000000000..67978c2bd --- /dev/null +++ b/.changeset/pre/fix-zsh-mixed-command-completions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Zsh completions for CLI commands with both positional arguments and subcommands. diff --git a/.changeset/flat-chicken-remain.md b/.changeset/pre/flat-chicken-remain.md similarity index 100% rename from .changeset/flat-chicken-remain.md rename to .changeset/pre/flat-chicken-remain.md diff --git a/.changeset/pre/flat-graphs-agree.md b/.changeset/pre/flat-graphs-agree.md new file mode 100644 index 000000000..8873ed9b4 --- /dev/null +++ b/.changeset/pre/flat-graphs-agree.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore removed allocator history when comparing and hashing immutable Graph values with the same active indexed structure. diff --git a/.changeset/floppy-cows-spend.md b/.changeset/pre/floppy-cows-spend.md similarity index 100% rename from .changeset/floppy-cows-spend.md rename to .changeset/pre/floppy-cows-spend.md diff --git a/.changeset/pre/floppy-frogs-juggle.md b/.changeset/pre/floppy-frogs-juggle.md new file mode 100644 index 000000000..4f4f40cd4 --- /dev/null +++ b/.changeset/pre/floppy-frogs-juggle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Duration`'s `Hash.symbol` implementation to hash a canonical nanoseconds form instead of the raw internal `Millis`/`Nanos` representation. Two durations that `Duration.equals`/`Equal.equals` consider equal (e.g. `Duration.seconds(5)` and `Duration.nanos(5_000_000_000n)`) previously hashed differently, violating the Hash/Equal contract and silently breaking `HashSet`/`HashMap` lookups keyed by `Duration`. diff --git a/.changeset/floppy-items-admire.md b/.changeset/pre/floppy-items-admire.md similarity index 100% rename from .changeset/floppy-items-admire.md rename to .changeset/pre/floppy-items-admire.md diff --git a/.changeset/floppy-pigs-kiss.md b/.changeset/pre/floppy-pigs-kiss.md similarity index 100% rename from .changeset/floppy-pigs-kiss.md rename to .changeset/pre/floppy-pigs-kiss.md diff --git a/.changeset/pre/floppy-rats-leave.md b/.changeset/pre/floppy-rats-leave.md new file mode 100644 index 000000000..cb520a1fd --- /dev/null +++ b/.changeset/pre/floppy-rats-leave.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fixed Clock.sleep handling of large durations diff --git a/.changeset/pre/floyd-warshall-null-edge-data.md b/.changeset/pre/floyd-warshall-null-edge-data.md new file mode 100644 index 000000000..368716363 --- /dev/null +++ b/.changeset/pre/floyd-warshall-null-edge-data.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve null edge data in Graph.floydWarshall costs. diff --git a/.changeset/fluffy-meals-matter.md b/.changeset/pre/fluffy-meals-matter.md similarity index 100% rename from .changeset/fluffy-meals-matter.md rename to .changeset/pre/fluffy-meals-matter.md diff --git a/.changeset/fluffy-pumas-push.md b/.changeset/pre/fluffy-pumas-push.md similarity index 100% rename from .changeset/fluffy-pumas-push.md rename to .changeset/pre/fluffy-pumas-push.md diff --git a/.changeset/forked-memo-maps.md b/.changeset/pre/forked-memo-maps.md similarity index 100% rename from .changeset/forked-memo-maps.md rename to .changeset/pre/forked-memo-maps.md diff --git a/.changeset/forty-hounds-cheer.md b/.changeset/pre/forty-hounds-cheer.md similarity index 100% rename from .changeset/forty-hounds-cheer.md rename to .changeset/pre/forty-hounds-cheer.md diff --git a/.changeset/forty-otters-cry.md b/.changeset/pre/forty-otters-cry.md similarity index 100% rename from .changeset/forty-otters-cry.md rename to .changeset/pre/forty-otters-cry.md diff --git a/.changeset/forty-rings-film.md b/.changeset/pre/forty-rings-film.md similarity index 100% rename from .changeset/forty-rings-film.md rename to .changeset/pre/forty-rings-film.md diff --git a/.changeset/forty-signs-stay.md b/.changeset/pre/forty-signs-stay.md similarity index 100% rename from .changeset/forty-signs-stay.md rename to .changeset/pre/forty-signs-stay.md diff --git a/.changeset/forty-swans-divide.md b/.changeset/pre/forty-swans-divide.md similarity index 100% rename from .changeset/forty-swans-divide.md rename to .changeset/pre/forty-swans-divide.md diff --git a/.changeset/forty-trees-pay.md b/.changeset/pre/forty-trees-pay.md similarity index 100% rename from .changeset/forty-trees-pay.md rename to .changeset/pre/forty-trees-pay.md diff --git a/.changeset/four-papayas-bow.md b/.changeset/pre/four-papayas-bow.md similarity index 100% rename from .changeset/four-papayas-bow.md rename to .changeset/pre/four-papayas-bow.md diff --git a/.changeset/four-points-repeat.md b/.changeset/pre/four-points-repeat.md similarity index 100% rename from .changeset/four-points-repeat.md rename to .changeset/pre/four-points-repeat.md diff --git a/.changeset/pre/frank-apes-vanish.md b/.changeset/pre/frank-apes-vanish.md new file mode 100644 index 000000000..603a16fd7 --- /dev/null +++ b/.changeset/pre/frank-apes-vanish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Close suspended workflow scopes after resumed completion. diff --git a/.changeset/fresh-cats-smash.md b/.changeset/pre/fresh-cats-smash.md similarity index 100% rename from .changeset/fresh-cats-smash.md rename to .changeset/pre/fresh-cats-smash.md diff --git a/.changeset/pre/fresh-cycles-wait.md b/.changeset/pre/fresh-cycles-wait.md new file mode 100644 index 000000000..f588122cd --- /dev/null +++ b/.changeset/pre/fresh-cycles-wait.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the `Schedule.both` APIs and add `Schedule.max` for combining schedules by their slowest delay. diff --git a/.changeset/pre/fresh-deps-update.md b/.changeset/pre/fresh-deps-update.md new file mode 100644 index 000000000..c6701bb51 --- /dev/null +++ b/.changeset/pre/fresh-deps-update.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update dependencies across the Effect workspace. diff --git a/.changeset/fresh-emus-cheat.md b/.changeset/pre/fresh-emus-cheat.md similarity index 100% rename from .changeset/fresh-emus-cheat.md rename to .changeset/pre/fresh-emus-cheat.md diff --git a/.changeset/pre/fresh-files-seek.md b/.changeset/pre/fresh-files-seek.md new file mode 100644 index 000000000..51fb44445 --- /dev/null +++ b/.changeset/pre/fresh-files-seek.md @@ -0,0 +1,7 @@ +--- +"effect": minor +"@effect/platform-node-shared": minor +"@effect/platform-deno": minor +--- + +Return the new file offset as a `Size` from `File.seek`. diff --git a/.changeset/pre/fresh-forms-travel.md b/.changeset/pre/fresh-forms-travel.md new file mode 100644 index 000000000..8b64d908d --- /dev/null +++ b/.changeset/pre/fresh-forms-travel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve FormData bodies when converting client requests through HttpServerRequest. diff --git a/.changeset/pre/fresh-graphs-cycle.md b/.changeset/pre/fresh-graphs-cycle.md new file mode 100644 index 000000000..d57f63749 --- /dev/null +++ b/.changeset/pre/fresh-graphs-cycle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Graph.findCycle` with exact node and edge witnesses. diff --git a/.changeset/pre/fresh-images-generate.md b/.changeset/pre/fresh-images-generate.md new file mode 100644 index 000000000..e8914d5f0 --- /dev/null +++ b/.changeset/pre/fresh-images-generate.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Accept image generation-specific lifecycle statuses and nullable results in OpenAI response items. diff --git a/.changeset/pre/fresh-lines-wait.md b/.changeset/pre/fresh-lines-wait.md new file mode 100644 index 000000000..d064c998e --- /dev/null +++ b/.changeset/pre/fresh-lines-wait.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +NodeTerminal: preserve buffered input across sequential `readLine` calls diff --git a/.changeset/fresh-monkeys-smoke.md b/.changeset/pre/fresh-monkeys-smoke.md similarity index 100% rename from .changeset/fresh-monkeys-smoke.md rename to .changeset/pre/fresh-monkeys-smoke.md diff --git a/.changeset/pre/fresh-rivers-report.md b/.changeset/pre/fresh-rivers-report.md new file mode 100644 index 000000000..121cea53e --- /dev/null +++ b/.changeset/pre/fresh-rivers-report.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Isolate delta metric baselines for each registered metric reader diff --git a/.changeset/frozen-intrinsics-stack-trace-limit.md b/.changeset/pre/frozen-intrinsics-stack-trace-limit.md similarity index 100% rename from .changeset/frozen-intrinsics-stack-trace-limit.md rename to .changeset/pre/frozen-intrinsics-stack-trace-limit.md diff --git a/.changeset/fruity-houses-learn.md b/.changeset/pre/fruity-houses-learn.md similarity index 100% rename from .changeset/fruity-houses-learn.md rename to .changeset/pre/fruity-houses-learn.md diff --git a/.changeset/pre/fruity-sloths-walk.md b/.changeset/pre/fruity-sloths-walk.md new file mode 100644 index 000000000..2e9ef2b1d --- /dev/null +++ b/.changeset/pre/fruity-sloths-walk.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now return standard JSON-RPC errors for malformed requests, unknown methods, and invalid parameters. diff --git a/.changeset/full-adults-double.md b/.changeset/pre/full-adults-double.md similarity index 100% rename from .changeset/full-adults-double.md rename to .changeset/pre/full-adults-double.md diff --git a/.changeset/funny-crabs-hang.md b/.changeset/pre/funny-crabs-hang.md similarity index 100% rename from .changeset/funny-crabs-hang.md rename to .changeset/pre/funny-crabs-hang.md diff --git a/.changeset/pre/funny-ears-beam.md b/.changeset/pre/funny-ears-beam.md new file mode 100644 index 000000000..00b7994c7 --- /dev/null +++ b/.changeset/pre/funny-ears-beam.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent Effect.updateService and Effect.updateServiceScoped supertype widening diff --git a/.changeset/funny-forks-move.md b/.changeset/pre/funny-forks-move.md similarity index 100% rename from .changeset/funny-forks-move.md rename to .changeset/pre/funny-forks-move.md diff --git a/.changeset/pre/fuzzy-batches-stop.md b/.changeset/pre/fuzzy-batches-stop.md new file mode 100644 index 000000000..0dbd54ab9 --- /dev/null +++ b/.changeset/pre/fuzzy-batches-stop.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now enforce revision-specific JSON-RPC batch and protocol-version header requirements. diff --git a/.changeset/pre/fuzzy-caches-expire.md b/.changeset/pre/fuzzy-caches-expire.md new file mode 100644 index 000000000..5e511fe65 --- /dev/null +++ b/.changeset/pre/fuzzy-caches-expire.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor numeric zero time-to-live values in `Cache.make` and `ScopedCache.make`. diff --git a/.changeset/fuzzy-camels-hunt.md b/.changeset/pre/fuzzy-camels-hunt.md similarity index 100% rename from .changeset/fuzzy-camels-hunt.md rename to .changeset/pre/fuzzy-camels-hunt.md diff --git a/.changeset/pre/fuzzy-cats-kill.md b/.changeset/pre/fuzzy-cats-kill.md new file mode 100644 index 000000000..35bae2e9f --- /dev/null +++ b/.changeset/pre/fuzzy-cats-kill.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Fix child process termination to escalate to `SIGKILL` when the initial signal does not stop the process within `forceKillAfter`. diff --git a/.changeset/pre/fuzzy-cats-listen.md b/.changeset/pre/fuzzy-cats-listen.md new file mode 100644 index 000000000..eb5b34707 --- /dev/null +++ b/.changeset/pre/fuzzy-cats-listen.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `HttpRouter.toWebHandler` middleware inference to exclude request services supplied by the HTTP adapter. diff --git a/.changeset/fuzzy-crews-fold.md b/.changeset/pre/fuzzy-crews-fold.md similarity index 100% rename from .changeset/fuzzy-crews-fold.md rename to .changeset/pre/fuzzy-crews-fold.md diff --git a/.changeset/pre/fuzzy-databases-abort.md b/.changeset/pre/fuzzy-databases-abort.md new file mode 100644 index 000000000..54923cc70 --- /dev/null +++ b/.changeset/pre/fuzzy-databases-abort.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Abort IndexedDB versionchange transactions when schema migrations fail. diff --git a/.changeset/fuzzy-dodos-help.md b/.changeset/pre/fuzzy-dodos-help.md similarity index 100% rename from .changeset/fuzzy-dodos-help.md rename to .changeset/pre/fuzzy-dodos-help.md diff --git a/.changeset/pre/fuzzy-files-slice.md b/.changeset/pre/fuzzy-files-slice.md new file mode 100644 index 000000000..8fde69fcc --- /dev/null +++ b/.changeset/pre/fuzzy-files-slice.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Honor offset and byte-count options in Bun Web File responses. diff --git a/.changeset/pre/fuzzy-graphs-transform.md b/.changeset/pre/fuzzy-graphs-transform.md new file mode 100644 index 000000000..c5d8fae75 --- /dev/null +++ b/.changeset/pre/fuzzy-graphs-transform.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add bulk node and edge removal operations, and disallow graph mutations from callbacks that traverse or transform the same graph. diff --git a/.changeset/pre/fuzzy-hornets-wish.md b/.changeset/pre/fuzzy-hornets-wish.md new file mode 100644 index 000000000..d9d06218b --- /dev/null +++ b/.changeset/pre/fuzzy-hornets-wish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Handle accepted undefined fields during variant extraction. diff --git a/.changeset/fuzzy-lions-perform.md b/.changeset/pre/fuzzy-lions-perform.md similarity index 100% rename from .changeset/fuzzy-lions-perform.md rename to .changeset/pre/fuzzy-lions-perform.md diff --git a/.changeset/pre/fuzzy-lions-study.md b/.changeset/pre/fuzzy-lions-study.md new file mode 100644 index 000000000..eb647e0ab --- /dev/null +++ b/.changeset/pre/fuzzy-lions-study.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SqlResolver.findById` failing to complete duplicate requests when id encoding fails, which surfaced as a `RequestResolver did not complete request` defect instead of the underlying `SchemaError`. diff --git a/.changeset/pre/fuzzy-pandas-smile.md b/.changeset/pre/fuzzy-pandas-smile.md new file mode 100644 index 000000000..835fefafd --- /dev/null +++ b/.changeset/pre/fuzzy-pandas-smile.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Preserve autocomplete for known OpenAI-compatible model config properties while allowing provider-specific properties. diff --git a/.changeset/fuzzy-planets-sneeze.md b/.changeset/pre/fuzzy-planets-sneeze.md similarity index 100% rename from .changeset/fuzzy-planets-sneeze.md rename to .changeset/pre/fuzzy-planets-sneeze.md diff --git a/.changeset/pre/fuzzy-rabbits-cancel.md b/.changeset/pre/fuzzy-rabbits-cancel.md new file mode 100644 index 000000000..4060d9c86 --- /dev/null +++ b/.changeset/pre/fuzzy-rabbits-cancel.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore MCP cancellation notifications for unknown request identifiers. diff --git a/.changeset/pre/fuzzy-ravens-reason.md b/.changeset/pre/fuzzy-ravens-reason.md new file mode 100644 index 000000000..1858474d8 --- /dev/null +++ b/.changeset/pre/fuzzy-ravens-reason.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Fix the casing of OpenRouter reasoning-end metadata. diff --git a/.changeset/pre/fuzzy-routers-smile.md b/.changeset/pre/fuzzy-routers-smile.md new file mode 100644 index 000000000..a286099cc --- /dev/null +++ b/.changeset/pre/fuzzy-routers-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix several edge cases in the vendored FindMyWay router. diff --git a/.changeset/icy-flies-cross.md b/.changeset/pre/fuzzy-stamps-care.md similarity index 100% rename from .changeset/icy-flies-cross.md rename to .changeset/pre/fuzzy-stamps-care.md diff --git a/.changeset/pre/fuzzy-timers-smile.md b/.changeset/pre/fuzzy-timers-smile.md new file mode 100644 index 000000000..874eef006 --- /dev/null +++ b/.changeset/pre/fuzzy-timers-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep TestClock nanosecond access total after infinite adjustments. diff --git a/.changeset/pre/general-json-schema-converters.md b/.changeset/pre/general-json-schema-converters.md new file mode 100644 index 000000000..366962f93 --- /dev/null +++ b/.changeset/pre/general-json-schema-converters.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make JSON Schema dialect conversions preserve custom keywords, translate conditionals, contains, dependencies, identifiers, and tuples where representable, relocate local references after structural changes, and throw instead of silently changing unsupported constraints. diff --git a/.changeset/pre/giant-jeans-float.md b/.changeset/pre/giant-jeans-float.md new file mode 100644 index 000000000..3f0eb7ef1 --- /dev/null +++ b/.changeset/pre/giant-jeans-float.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure one-shot iterables work with Fiber apis diff --git a/.changeset/gold-meteors-move.md b/.changeset/pre/gold-meteors-move.md similarity index 100% rename from .changeset/gold-meteors-move.md rename to .changeset/pre/gold-meteors-move.md diff --git a/.changeset/gold-readers-hug.md b/.changeset/pre/gold-readers-hug.md similarity index 100% rename from .changeset/gold-readers-hug.md rename to .changeset/pre/gold-readers-hug.md diff --git a/.changeset/gold-rings-start.md b/.changeset/pre/gold-rings-start.md similarity index 100% rename from .changeset/gold-rings-start.md rename to .changeset/pre/gold-rings-start.md diff --git a/.changeset/pre/good-cups-reply.md b/.changeset/pre/good-cups-reply.md new file mode 100644 index 000000000..344af728a --- /dev/null +++ b/.changeset/pre/good-cups-reply.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-mssql": patch +--- + +Return MSSQL procedure values through the output property. diff --git a/.changeset/good-tools-work.md b/.changeset/pre/good-tools-work.md similarity index 100% rename from .changeset/good-tools-work.md rename to .changeset/pre/good-tools-work.md diff --git a/.changeset/good-trees-pull.md b/.changeset/pre/good-trees-pull.md similarity index 100% rename from .changeset/good-trees-pull.md rename to .changeset/pre/good-trees-pull.md diff --git a/.changeset/pre/graph-acyclic-parallel-undirected-edges.md b/.changeset/pre/graph-acyclic-parallel-undirected-edges.md new file mode 100644 index 000000000..5bb98abbb --- /dev/null +++ b/.changeset/pre/graph-acyclic-parallel-undirected-edges.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.isAcyclic` to detect cycles formed by parallel undirected edges. diff --git a/.changeset/graph-algorithm-fixes.md b/.changeset/pre/graph-algorithm-fixes.md similarity index 100% rename from .changeset/graph-algorithm-fixes.md rename to .changeset/pre/graph-algorithm-fixes.md diff --git a/.changeset/pre/graph-finalized-mutation-handle.md b/.changeset/pre/graph-finalized-mutation-handle.md new file mode 100644 index 000000000..4f628c326 --- /dev/null +++ b/.changeset/pre/graph-finalized-mutation-handle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject `Graph` mutation operations on mutable handles after `Graph.endMutation` finalizes them. diff --git a/.changeset/pre/graph-guard-predicates.md b/.changeset/pre/graph-guard-predicates.md new file mode 100644 index 000000000..da96b1a56 --- /dev/null +++ b/.changeset/pre/graph-guard-predicates.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Graph.isGraph narrowing for mutable and undirected graphs. diff --git a/.changeset/pre/graph-sync-mutation-callbacks.md b/.changeset/pre/graph-sync-mutation-callbacks.md new file mode 100644 index 000000000..c006de9f8 --- /dev/null +++ b/.changeset/pre/graph-sync-mutation-callbacks.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject asynchronous `Graph` mutation callbacks and finalize scoped mutable handles when callbacks fail. diff --git a/.changeset/pre/graph-undirected-edge-equality.md b/.changeset/pre/graph-undirected-edge-equality.md new file mode 100644 index 000000000..25212ae68 --- /dev/null +++ b/.changeset/pre/graph-undirected-edge-equality.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix undirected `Graph` equality and hashing to ignore stored edge endpoint orientation. diff --git a/.changeset/pre/graph-walker-iterator-receiver.md b/.changeset/pre/graph-walker-iterator-receiver.md new file mode 100644 index 000000000..a44ad4655 --- /dev/null +++ b/.changeset/pre/graph-walker-iterator-receiver.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Graph.Walker` iteration for receiver-sensitive iterables. diff --git a/.changeset/great-trains-mate.md b/.changeset/pre/great-trains-mate.md similarity index 100% rename from .changeset/great-trains-mate.md rename to .changeset/pre/great-trains-mate.md diff --git a/.changeset/great-trams-report.md b/.changeset/pre/great-trams-report.md similarity index 100% rename from .changeset/great-trams-report.md rename to .changeset/pre/great-trams-report.md diff --git a/.changeset/pre/green-ads-camp.md b/.changeset/pre/green-ads-camp.md new file mode 100644 index 000000000..aa0410482 --- /dev/null +++ b/.changeset/pre/green-ads-camp.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Expose object-shaped Toolkit success schemas as MCP tool output schemas. diff --git a/.changeset/green-beds-unref.md b/.changeset/pre/green-beds-unref.md similarity index 100% rename from .changeset/green-beds-unref.md rename to .changeset/pre/green-beds-unref.md diff --git a/.changeset/pre/green-birds-close.md b/.changeset/pre/green-birds-close.md new file mode 100644 index 000000000..e21ba8438 --- /dev/null +++ b/.changeset/pre/green-birds-close.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-wasm": patch +--- + +Close OPFS access handles when the SQLite worker shuts down. diff --git a/.changeset/green-chips-wash.md b/.changeset/pre/green-chips-wash.md similarity index 100% rename from .changeset/green-chips-wash.md rename to .changeset/pre/green-chips-wash.md diff --git a/.changeset/green-moons-smile.md b/.changeset/pre/green-moons-smile.md similarity index 100% rename from .changeset/green-moons-smile.md rename to .changeset/pre/green-moons-smile.md diff --git a/.changeset/green-pugs-play.md b/.changeset/pre/green-pugs-play.md similarity index 100% rename from .changeset/green-pugs-play.md rename to .changeset/pre/green-pugs-play.md diff --git a/.changeset/green-rings-prove.md b/.changeset/pre/green-rings-prove.md similarity index 100% rename from .changeset/green-rings-prove.md rename to .changeset/pre/green-rings-prove.md diff --git a/.changeset/happy-mirrors-dream.md b/.changeset/pre/happy-mirrors-dream.md similarity index 100% rename from .changeset/happy-mirrors-dream.md rename to .changeset/pre/happy-mirrors-dream.md diff --git a/.changeset/pre/harden-httpapi-documentation-html.md b/.changeset/pre/harden-httpapi-documentation-html.md new file mode 100644 index 000000000..774dbe329 --- /dev/null +++ b/.changeset/pre/harden-httpapi-documentation-html.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Harden HttpApi documentation HTML rendering. + +Scalar descriptions and CDN versions were interpolated without attribute-safe escaping. Embedded OpenAPI JSON in Scalar and Swagger also handled only the exact `` sequence, not other valid [script end-tag forms](https://html.spec.whatwg.org/multipage/parsing.html#script-data-end-tag-name-state). + +Attribute values and CDN versions are now encoded for their contexts, and embedded JSON escapes `<` so it cannot close its script element. diff --git a/.changeset/pre/hash-sql-message-dedupe-keys.md b/.changeset/pre/hash-sql-message-dedupe-keys.md new file mode 100644 index 000000000..9e386e019 --- /dev/null +++ b/.changeset/pre/hash-sql-message-dedupe-keys.md @@ -0,0 +1,11 @@ +--- +"effect": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +--- + +unstable/cluster: hash over-length SQL message deduplication keys to prevent `message_id` overflow, closes #6317. + +The composed request deduplication key (`entityType/entityId/tag/primaryKey`) can legally exceed the 255-character `message_id` column — the address columns alone allow 458 characters before the RPC primary key is appended. `SqlMessageStorage` now stores a SHA-256 digest (64 hex characters) of the composed key in the unique `message_id` column when the key exceeds 255 characters, so keys of any length work on PostgreSQL, MySQL, MSSQL, and SQLite. Keys that fit are stored as plaintext, byte-compatible with rows written by previous versions, so existing deployments keep deduplicating with no migration or schema change. + +`SqlMessageStorage.layer`/`layerWith` (and consequently `SingleRunner.layer`) now require `Crypto.Crypto`. The Node and Bun cluster convenience layers provide the platform Crypto implementation internally, so their requirements are unchanged. diff --git a/.changeset/heavy-loops-cut.md b/.changeset/pre/heavy-loops-cut.md similarity index 100% rename from .changeset/heavy-loops-cut.md rename to .changeset/pre/heavy-loops-cut.md diff --git a/.changeset/heavy-trams-fix.md b/.changeset/pre/heavy-trams-fix.md similarity index 100% rename from .changeset/heavy-trams-fix.md rename to .changeset/pre/heavy-trams-fix.md diff --git a/.changeset/pre/hip-friends-kiss.md b/.changeset/pre/hip-friends-kiss.md new file mode 100644 index 000000000..a5c66356c --- /dev/null +++ b/.changeset/pre/hip-friends-kiss.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +added graph set operations for combining and comparing graphs + +- `Graph.make` - creates a graph constructor for a dynamically selected graph kind +- `Graph.compose` - composition of two graphs, merging nodes by identity +- `Graph.intersection` - intersection of two graphs, keeping only common nodes and edges +- `Graph.difference` - difference of two graphs, removing edges present in the second graph +- `Graph.symmetricDifference` - symmetric difference of two graphs, keeping edges present in exactly one graph diff --git a/.changeset/hip-socks-travel.md b/.changeset/pre/hip-socks-travel.md similarity index 100% rename from .changeset/hip-socks-travel.md rename to .changeset/pre/hip-socks-travel.md diff --git a/.changeset/honest-pens-thank.md b/.changeset/pre/honest-pens-thank.md similarity index 100% rename from .changeset/honest-pens-thank.md rename to .changeset/pre/honest-pens-thank.md diff --git a/.changeset/honest-rivers-notice.md b/.changeset/pre/honest-rivers-notice.md similarity index 100% rename from .changeset/honest-rivers-notice.md rename to .changeset/pre/honest-rivers-notice.md diff --git a/.changeset/hot-taxis-fry.md b/.changeset/pre/hot-taxis-fry.md similarity index 100% rename from .changeset/hot-taxis-fry.md rename to .changeset/pre/hot-taxis-fry.md diff --git a/.changeset/pre/hot-teeth-clean.md b/.changeset/pre/hot-teeth-clean.md new file mode 100644 index 000000000..c737634e7 --- /dev/null +++ b/.changeset/pre/hot-teeth-clean.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +change rpc ids to string | number diff --git a/.changeset/pre/http-json-reviver.md b/.changeset/pre/http-json-reviver.md new file mode 100644 index 000000000..51ceb6ddf --- /dev/null +++ b/.changeset/pre/http-json-reviver.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add custom reviver support to HTTP JSON parsing APIs. diff --git a/.changeset/pre/http-server-websocket-options.md b/.changeset/pre/http-server-websocket-options.md new file mode 100644 index 000000000..177d15b7e --- /dev/null +++ b/.changeset/pre/http-server-websocket-options.md @@ -0,0 +1,22 @@ +--- +"@effect/platform-node": patch +"@effect/platform-bun": patch +--- + +Allow configuring the WebSocket server in `NodeHttpServer` and `BunHttpServer`. + +Both servers now accept a `websocket` option that is forwarded to the underlying implementation, with the wiring/lifecycle options the server manages excluded from the type: + +```ts +// Node: forwarded to the `ws` WebSocketServer +NodeHttpServer.layer(() => createServer(), { + port: 3000, + websocket: { perMessageDeflate: true } +}) + +// Bun: merged into Bun.serve's websocket handler +BunHttpServer.layer({ + port: 3000, + websocket: { perMessageDeflate: true } +}) +``` diff --git a/.changeset/pre/http-status-from-literal.md b/.changeset/pre/http-status-from-literal.md new file mode 100644 index 000000000..e5942772a --- /dev/null +++ b/.changeset/pre/http-status-from-literal.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add a `HttpStatus` module to `effect/unstable/http` that centralizes the mapping from HTTP status literal names to numeric codes and exports `HttpStatus.fromLiteral`. `HttpApiSchema.status` now consumes the new module. diff --git a/.changeset/httpapi-endpoint-relax-constraints.md b/.changeset/pre/httpapi-endpoint-relax-constraints.md similarity index 100% rename from .changeset/httpapi-endpoint-relax-constraints.md rename to .changeset/pre/httpapi-endpoint-relax-constraints.md diff --git a/.changeset/httpapi-schema-service-types.md b/.changeset/pre/httpapi-schema-service-types.md similarity index 100% rename from .changeset/httpapi-schema-service-types.md rename to .changeset/pre/httpapi-schema-service-types.md diff --git a/.changeset/huge-moons-rhyme.md b/.changeset/pre/huge-moons-rhyme.md similarity index 100% rename from .changeset/huge-moons-rhyme.md rename to .changeset/pre/huge-moons-rhyme.md diff --git a/.changeset/humble-pigs-dig.md b/.changeset/pre/humble-pigs-dig.md similarity index 100% rename from .changeset/humble-pigs-dig.md rename to .changeset/pre/humble-pigs-dig.md diff --git a/.changeset/pre/hungry-kings-look.md b/.changeset/pre/hungry-kings-look.md new file mode 100644 index 000000000..602fb3422 --- /dev/null +++ b/.changeset/pre/hungry-kings-look.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-node-shared": patch +"effect": patch +--- + +Add glob to filesystem diff --git a/.changeset/stale-snakes-know.md b/.changeset/pre/icy-flies-cross.md similarity index 100% rename from .changeset/stale-snakes-know.md rename to .changeset/pre/icy-flies-cross.md diff --git a/.changeset/pre/internal-json-string-schema.md b/.changeset/pre/internal-json-string-schema.md new file mode 100644 index 000000000..2a5ab3549 --- /dev/null +++ b/.changeset/pre/internal-json-string-schema.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Mark `Schema.UnknownFromJsonString` as internal and remove its type-level interface. Use `Schema.fromJsonString(Schema.Unknown)` instead. Add `reviver`, callback or array `replacer`, and `space` options to `Schema.fromJsonString`, and make `SchemaTransformation.fromJsonString` a configurable factory. diff --git a/.changeset/pre/isolate-sql-compiler-cache.md b/.changeset/pre/isolate-sql-compiler-cache.md new file mode 100644 index 000000000..f45018a57 --- /dev/null +++ b/.changeset/pre/isolate-sql-compiler-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Isolate compiled SQL fragment caches by compiler instance. diff --git a/.changeset/itchy-radios-poke.md b/.changeset/pre/itchy-radios-poke.md similarity index 100% rename from .changeset/itchy-radios-poke.md rename to .changeset/pre/itchy-radios-poke.md diff --git a/.changeset/itchy-results-bet.md b/.changeset/pre/itchy-results-bet.md similarity index 100% rename from .changeset/itchy-results-bet.md rename to .changeset/pre/itchy-results-bet.md diff --git a/.changeset/itchy-shrimps-deny.md b/.changeset/pre/itchy-shrimps-deny.md similarity index 100% rename from .changeset/itchy-shrimps-deny.md rename to .changeset/pre/itchy-shrimps-deny.md diff --git a/.changeset/itchy-toes-promise.md b/.changeset/pre/itchy-toes-promise.md similarity index 100% rename from .changeset/itchy-toes-promise.md rename to .changeset/pre/itchy-toes-promise.md diff --git a/.changeset/k8s-last-transition-null.md b/.changeset/pre/k8s-last-transition-null.md similarity index 100% rename from .changeset/k8s-last-transition-null.md rename to .changeset/pre/k8s-last-transition-null.md diff --git a/.changeset/pre/keep-httpapi-composition-immutable.md b/.changeset/pre/keep-httpapi-composition-immutable.md new file mode 100644 index 000000000..a4f979384 --- /dev/null +++ b/.changeset/pre/keep-httpapi-composition-immutable.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Keep HttpApi composition immutable. + +`HttpApi.addHttpApi` applied annotations from the added API by mutating its shared groups. It now creates annotated group copies, keeping the source API and independently annotated variants unchanged while preserving annotation precedence. diff --git a/.changeset/khaki-cats-learn.md b/.changeset/pre/khaki-cats-learn.md similarity index 100% rename from .changeset/khaki-cats-learn.md rename to .changeset/pre/khaki-cats-learn.md diff --git a/.changeset/khaki-melons-appear.md b/.changeset/pre/khaki-melons-appear.md similarity index 100% rename from .changeset/khaki-melons-appear.md rename to .changeset/pre/khaki-melons-appear.md diff --git a/.changeset/pre/kind-flags-help.md b/.changeset/pre/kind-flags-help.md new file mode 100644 index 000000000..ecf9af6d2 --- /dev/null +++ b/.changeset/pre/kind-flags-help.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Mark omittable CLI flags and arguments as optional in structured help. diff --git a/.changeset/kind-hounds-float.md b/.changeset/pre/kind-hounds-float.md similarity index 100% rename from .changeset/kind-hounds-float.md rename to .changeset/pre/kind-hounds-float.md diff --git a/.changeset/kind-windows-fall.md b/.changeset/pre/kind-windows-fall.md similarity index 100% rename from .changeset/kind-windows-fall.md rename to .changeset/pre/kind-windows-fall.md diff --git a/.changeset/late-hotels-rule.md b/.changeset/pre/late-hotels-rule.md similarity index 100% rename from .changeset/late-hotels-rule.md rename to .changeset/pre/late-hotels-rule.md diff --git a/.changeset/late-lamps-care.md b/.changeset/pre/late-lamps-care.md similarity index 100% rename from .changeset/late-lamps-care.md rename to .changeset/pre/late-lamps-care.md diff --git a/.changeset/late-rivers-applaud.md b/.changeset/pre/late-rivers-applaud.md similarity index 100% rename from .changeset/late-rivers-applaud.md rename to .changeset/pre/late-rivers-applaud.md diff --git a/.changeset/pre/late-sockets-rest.md b/.changeset/pre/late-sockets-rest.md new file mode 100644 index 000000000..b2fecca5e --- /dev/null +++ b/.changeset/pre/late-sockets-rest.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Return `Option.none()` when reading an incoming message's remote address after Node clears its socket. diff --git a/.changeset/layer-map-dynamic-idle-ttl.md b/.changeset/pre/layer-map-dynamic-idle-ttl.md similarity index 100% rename from .changeset/layer-map-dynamic-idle-ttl.md rename to .changeset/pre/layer-map-dynamic-idle-ttl.md diff --git a/.changeset/pre/layered-context-storage.md b/.changeset/pre/layered-context-storage.md new file mode 100644 index 000000000..e1bd0703a --- /dev/null +++ b/.changeset/pre/layered-context-storage.md @@ -0,0 +1,9 @@ +--- +"effect": patch +"@effect/docgen": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"@effect/platform-node": patch +--- + +Use layered storage for Context, making `Context.add` O(1) and eliminating per-request service map clones in the HTTP servers. Docgen now omits `@internal` option properties from generated signatures. diff --git a/.changeset/pre/lazy-openapi-initialization.md b/.changeset/pre/lazy-openapi-initialization.md new file mode 100644 index 000000000..dc6a55330 --- /dev/null +++ b/.changeset/pre/lazy-openapi-initialization.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Defer built-in OpenAPI response generation until the documentation route is first requested, retrying after generation defects. diff --git a/.changeset/lazy-queens-rush.md b/.changeset/pre/lazy-queens-rush.md similarity index 100% rename from .changeset/lazy-queens-rush.md rename to .changeset/pre/lazy-queens-rush.md diff --git a/.changeset/lazy-recursive-forward-refs.md b/.changeset/pre/lazy-recursive-forward-refs.md similarity index 100% rename from .changeset/lazy-recursive-forward-refs.md rename to .changeset/pre/lazy-recursive-forward-refs.md diff --git a/.changeset/lazy-timers-exist.md b/.changeset/pre/lazy-timers-exist.md similarity index 100% rename from .changeset/lazy-timers-exist.md rename to .changeset/pre/lazy-timers-exist.md diff --git a/.changeset/legal-pants-drop.md b/.changeset/pre/legal-pants-drop.md similarity index 100% rename from .changeset/legal-pants-drop.md rename to .changeset/pre/legal-pants-drop.md diff --git a/.changeset/lemon-taxis-sin.md b/.changeset/pre/lemon-taxis-sin.md similarity index 100% rename from .changeset/lemon-taxis-sin.md rename to .changeset/pre/lemon-taxis-sin.md diff --git a/.changeset/light-kids-sneeze.md b/.changeset/pre/light-kids-sneeze.md similarity index 100% rename from .changeset/light-kids-sneeze.md rename to .changeset/pre/light-kids-sneeze.md diff --git a/.changeset/little-dryers-allow.md b/.changeset/pre/little-dryers-allow.md similarity index 100% rename from .changeset/little-dryers-allow.md rename to .changeset/pre/little-dryers-allow.md diff --git a/.changeset/long-cameras-think.md b/.changeset/pre/long-cameras-think.md similarity index 100% rename from .changeset/long-cameras-think.md rename to .changeset/pre/long-cameras-think.md diff --git a/.changeset/pre/loose-wings-lie.md b/.changeset/pre/loose-wings-lie.md new file mode 100644 index 000000000..fedc37514 --- /dev/null +++ b/.changeset/pre/loose-wings-lie.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent Effect.provideServiceEffect supertype widening diff --git a/.changeset/lovely-cobras-change.md b/.changeset/pre/lovely-cobras-change.md similarity index 100% rename from .changeset/lovely-cobras-change.md rename to .changeset/pre/lovely-cobras-change.md diff --git a/.changeset/lovely-frogs-rescue.md b/.changeset/pre/lovely-frogs-rescue.md similarity index 100% rename from .changeset/lovely-frogs-rescue.md rename to .changeset/pre/lovely-frogs-rescue.md diff --git a/.changeset/lucky-buttons-jump.md b/.changeset/pre/lucky-buttons-jump.md similarity index 100% rename from .changeset/lucky-buttons-jump.md rename to .changeset/pre/lucky-buttons-jump.md diff --git a/.changeset/pre/lucky-dingos-smile.md b/.changeset/pre/lucky-dingos-smile.md new file mode 100644 index 000000000..96d9a4ab3 --- /dev/null +++ b/.changeset/pre/lucky-dingos-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Map and Set equality allowing a right-side entry to match multiple left-side entries. diff --git a/.changeset/lucky-phones-listen.md b/.changeset/pre/lucky-phones-listen.md similarity index 100% rename from .changeset/lucky-phones-listen.md rename to .changeset/pre/lucky-phones-listen.md diff --git a/.changeset/lucky-worms-type.md b/.changeset/pre/lucky-worms-type.md similarity index 100% rename from .changeset/lucky-worms-type.md rename to .changeset/pre/lucky-worms-type.md diff --git a/.changeset/pre/major-chairs-design.md b/.changeset/pre/major-chairs-design.md new file mode 100644 index 000000000..6e00b3ce6 --- /dev/null +++ b/.changeset/pre/major-chairs-design.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix RequestResolver interruption diff --git a/.changeset/pre/managed-runtime-async-dispose.md b/.changeset/pre/managed-runtime-async-dispose.md new file mode 100644 index 000000000..1b4fe0b7c --- /dev/null +++ b/.changeset/pre/managed-runtime-async-dispose.md @@ -0,0 +1,14 @@ +--- +"effect": patch +--- + +ManagedRuntime: add `Symbol.asyncDispose`, enabling `await using` syntax + +```ts +import { Effect, Layer, ManagedRuntime } from "effect" + +await using runtime = ManagedRuntime.make(Layer.empty) + +await runtime.runPromise(Effect.log("Hello, world!")) +// runtime is disposed automatically at the end of the scope +``` diff --git a/.changeset/many-badgers-obey.md b/.changeset/pre/many-badgers-obey.md similarity index 100% rename from .changeset/many-badgers-obey.md rename to .changeset/pre/many-badgers-obey.md diff --git a/.changeset/pre/match-generic-value-terminals.md b/.changeset/pre/match-generic-value-terminals.md new file mode 100644 index 000000000..f72279c35 --- /dev/null +++ b/.changeset/pre/match-generic-value-terminals.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Fix `Match.value` terminal combinators failing to typecheck when the input +contains a generic type parameter. + +The fifth type argument of `Matcher` for value matchers is now `ValueFlavor`, +and `ValueMatcher` has a seventh flavor argument; update hand-written +annotations accordingly. diff --git a/.changeset/pre/mcp-http-initialize-version-header.md b/.changeset/pre/mcp-http-initialize-version-header.md new file mode 100644 index 000000000..5c49c3ad9 --- /dev/null +++ b/.changeset/pre/mcp-http-initialize-version-header.md @@ -0,0 +1,16 @@ +--- +"effect": patch +--- + +Fix MCP initialize rejected over the protocol version header + +`McpServer.layerHttp` validated the `MCP-Protocol-Version` header on every POST, including +the `initialize` request. That header reports the version negotiated by an earlier +`initialize`, so on a fresh connection a client can only send its own default. Whenever +that default was not among the server's registered protocols the `initialize` returned +`400` and never reached version negotiation, even when the body offered a version the +server supports. + +The header check now applies only to requests after initialization, where the +specification requires it. An `initialize` negotiates from the version offered in its +body, through the protocol registry, and reports the selected version in the response. diff --git a/.changeset/pre/mcp-protocol-versions.md b/.changeset/pre/mcp-protocol-versions.md new file mode 100644 index 000000000..6c20ac168 --- /dev/null +++ b/.changeset/pre/mcp-protocol-versions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now support the 2024-11-05 and 2025-03-26 RPC revisions through version-specific protocol adapters. diff --git a/.changeset/pre/mcp-tool-output-schema.md b/.changeset/pre/mcp-tool-output-schema.md new file mode 100644 index 000000000..3c0399361 --- /dev/null +++ b/.changeset/pre/mcp-tool-output-schema.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include typed tool output schemas in MCP `tools/list` responses. diff --git a/.changeset/mean-dingos-share.md b/.changeset/pre/mean-dingos-share.md similarity index 100% rename from .changeset/mean-dingos-share.md rename to .changeset/pre/mean-dingos-share.md diff --git a/.changeset/mean-trains-smash.md b/.changeset/pre/mean-trains-smash.md similarity index 100% rename from .changeset/mean-trains-smash.md rename to .changeset/pre/mean-trains-smash.md diff --git a/.changeset/pre/memoize-idempotent-asts.md b/.changeset/pre/memoize-idempotent-asts.md new file mode 100644 index 000000000..30c655d7c --- /dev/null +++ b/.changeset/pre/memoize-idempotent-asts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Function.memoizeIdempotent` and use it to avoid reprocessing canonical Schema ASTs, including optional and mutable property modifiers. Cache Config schema cursor AST compilation. diff --git a/.changeset/pre/merge-resource-failures.md b/.changeset/pre/merge-resource-failures.md new file mode 100644 index 000000000..0f5730493 --- /dev/null +++ b/.changeset/pre/merge-resource-failures.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Merge effect and finalizer failures during cleanup, preserving other failures alongside `Cause.Done`. diff --git a/.changeset/pre/metal-nails-sneeze.md b/.changeset/pre/metal-nails-sneeze.md new file mode 100644 index 000000000..148d8c62b --- /dev/null +++ b/.changeset/pre/metal-nails-sneeze.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add LayerRef module diff --git a/.changeset/metal-parts-yell.md b/.changeset/pre/metal-parts-yell.md similarity index 100% rename from .changeset/metal-parts-yell.md rename to .changeset/pre/metal-parts-yell.md diff --git a/.changeset/mighty-games-matter.md b/.changeset/pre/mighty-games-matter.md similarity index 100% rename from .changeset/mighty-games-matter.md rename to .changeset/pre/mighty-games-matter.md diff --git a/.changeset/pre/migrate-persisted-queues.md b/.changeset/pre/migrate-persisted-queues.md new file mode 100644 index 000000000..6fefa1078 --- /dev/null +++ b/.changeset/pre/migrate-persisted-queues.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Run SQL `PersistedQueue` table creation through versioned migrations so future schema changes can be applied safely. diff --git a/.changeset/pre/migrator-windows-file-url.md b/.changeset/pre/migrator-windows-file-url.md new file mode 100644 index 000000000..9ad8a9d4d --- /dev/null +++ b/.changeset/pre/migrator-windows-file-url.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Import migrations through a file URL in `Migrator.fromFileSystem`, so absolute Windows paths are accepted by the ESM loader. + +Previously the directory and file name were passed to `import` as a plain path. On Windows that produced a specifier such as `D:\migrations\1_init.ts`, which the ESM loader rejects with `Only URLs with a scheme in: file, data, and node are supported`. + +`fromFileSystem` now resolves the specifier through the `Path` service, so its type widens from `Loader` to `Loader`. Callers that already provide an aggregate platform layer such as `NodeServices.layer` are unaffected; callers that provide `FileSystem` on its own now also need a `Path` layer, and on Windows it must be a platform-aware one rather than the POSIX `Path.layer`. diff --git a/.changeset/modern-carrots-see.md b/.changeset/pre/modern-carrots-see.md similarity index 100% rename from .changeset/modern-carrots-see.md rename to .changeset/pre/modern-carrots-see.md diff --git a/.changeset/modern-uuid-guid-filter.md b/.changeset/pre/modern-uuid-guid-filter.md similarity index 100% rename from .changeset/modern-uuid-guid-filter.md rename to .changeset/pre/modern-uuid-guid-filter.md diff --git a/.changeset/pre/multipart-collect-linear.md b/.changeset/pre/multipart-collect-linear.md new file mode 100644 index 000000000..93f37d8f1 --- /dev/null +++ b/.changeset/pre/multipart-collect-linear.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Channel.mkUint8Array` and reuse it from `Stream` and multipart file collection. This also fixes quadratic buffering in `File.contentEffect`, improving collection of a 16 MiB chunked upload by approximately 90x. diff --git a/.changeset/pre/multipart-onDone-clobbers-error.md b/.changeset/pre/multipart-onDone-clobbers-error.md new file mode 100644 index 000000000..621db7284 --- /dev/null +++ b/.changeset/pre/multipart-onDone-clobbers-error.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix multipart parser limit violations being silently swallowed diff --git a/.changeset/pre/multipart-parser-limits.md b/.changeset/pre/multipart-parser-limits.md new file mode 100644 index 000000000..d62512922 --- /dev/null +++ b/.changeset/pre/multipart-parser-limits.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Stop multipart parsing after part count, part size, or field size limits are exceeded. diff --git a/.changeset/pre/mysql2-disable-prepared-statements.md b/.changeset/pre/mysql2-disable-prepared-statements.md new file mode 100644 index 000000000..02150bcc4 --- /dev/null +++ b/.changeset/pre/mysql2-disable-prepared-statements.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-mysql2": patch +--- + +Add `disablePreparedStatements` to `MysqlClientConfig`, to completely disable prepared statements diff --git a/.changeset/pre/narrow-schedule-while.md b/.changeset/pre/narrow-schedule-while.md new file mode 100644 index 000000000..85344bc32 --- /dev/null +++ b/.changeset/pre/narrow-schedule-while.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support narrowing schedule input and output types with type guard predicates passed to `Schedule.while`. diff --git a/.changeset/nasty-geese-grow.md b/.changeset/pre/nasty-geese-grow.md similarity index 100% rename from .changeset/nasty-geese-grow.md rename to .changeset/pre/nasty-geese-grow.md diff --git a/.changeset/neat-goats-wave.md b/.changeset/pre/neat-goats-wave.md similarity index 100% rename from .changeset/neat-goats-wave.md rename to .changeset/pre/neat-goats-wave.md diff --git a/.changeset/pre/neat-graphs-induced.md b/.changeset/pre/neat-graphs-induced.md new file mode 100644 index 000000000..74b21df26 --- /dev/null +++ b/.changeset/pre/neat-graphs-induced.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add index-preserving `Graph.inducedSubgraph`. diff --git a/.changeset/neat-kings-chew.md b/.changeset/pre/neat-kings-chew.md similarity index 91% rename from .changeset/neat-kings-chew.md rename to .changeset/pre/neat-kings-chew.md index 1986ccb6c..57a18a6c6 100644 --- a/.changeset/neat-kings-chew.md +++ b/.changeset/pre/neat-kings-chew.md @@ -1,5 +1,5 @@ --- -"effect": minor +"effect": patch --- Add a new `effect/unstable/http/HttpStaticServer` module for static file serving with MIME resolution, directory index fallback, SPA fallback, and safe path resolution. diff --git a/.changeset/neat-lions-rest.md b/.changeset/pre/neat-lions-rest.md similarity index 100% rename from .changeset/neat-lions-rest.md rename to .changeset/pre/neat-lions-rest.md diff --git a/.changeset/pre/neat-pandas-query.md b/.changeset/pre/neat-pandas-query.md new file mode 100644 index 000000000..f78f49222 --- /dev/null +++ b/.changeset/pre/neat-pandas-query.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-clickhouse": patch +--- + +Propagate ClickHouse result decoding failures as `SqlError` values. diff --git a/.changeset/neat-snails-wash.md b/.changeset/pre/neat-snails-wash.md similarity index 100% rename from .changeset/neat-snails-wash.md rename to .changeset/pre/neat-snails-wash.md diff --git a/.changeset/neat-taxis-notice.md b/.changeset/pre/neat-taxis-notice.md similarity index 100% rename from .changeset/neat-taxis-notice.md rename to .changeset/pre/neat-taxis-notice.md diff --git a/.changeset/pre/neat-tuples-remember.md b/.changeset/pre/neat-tuples-remember.md new file mode 100644 index 000000000..526502b74 --- /dev/null +++ b/.changeset/pre/neat-tuples-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve literal element types in `Tuple.make`. diff --git a/.changeset/neat-windows-buy.md b/.changeset/pre/neat-windows-buy.md similarity index 100% rename from .changeset/neat-windows-buy.md rename to .changeset/pre/neat-windows-buy.md diff --git a/.changeset/pre/nested-union-sentinels.md b/.changeset/pre/nested-union-sentinels.md new file mode 100644 index 000000000..bba5671a1 --- /dev/null +++ b/.changeset/pre/nested-union-sentinels.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve Union candidate selection: a nested union member is dispatched by the sentinels common to all its members, and candidates whose sentinel the input contradicts are excluded. diff --git a/.changeset/new-dogs-swim.md b/.changeset/pre/new-dogs-swim.md similarity index 100% rename from .changeset/new-dogs-swim.md rename to .changeset/pre/new-dogs-swim.md diff --git a/.changeset/new-toes-stop.md b/.changeset/pre/new-toes-stop.md similarity index 100% rename from .changeset/new-toes-stop.md rename to .changeset/pre/new-toes-stop.md diff --git a/.changeset/ninety-geese-exist.md b/.changeset/pre/ninety-geese-exist.md similarity index 100% rename from .changeset/ninety-geese-exist.md rename to .changeset/pre/ninety-geese-exist.md diff --git a/.changeset/pre/node-redis-client.md b/.changeset/pre/node-redis-client.md new file mode 100644 index 000000000..1e869ad6d --- /dev/null +++ b/.changeset/pre/node-redis-client.md @@ -0,0 +1,9 @@ +--- +"@effect/platform-node": patch +--- + +Migrate `NodeRedis` from `ioredis` to `redis` (node-redis), replacing the peer dependency with `redis: >=5.0.0 <7.0.0`. + +`layer` and `layerConfig` now accept `RedisClientOptions`: socket settings move under `socket`, `db` becomes `database`, command methods are camelCase, and arbitrary commands use `sendCommand`. Protocol selection follows the installed node-redis version's default. + +Layers connect while being built and can fail with `RedisError`. Initial connections fail fast unless a `socket.reconnectStrategy` is provided; after `ready`, the default reconnect behavior applies. Scope finalization uses `close()`, so in-flight or blocking commands can delay closure. diff --git a/.changeset/pre/node-terminal-idle-ttl.md b/.changeset/pre/node-terminal-idle-ttl.md new file mode 100644 index 000000000..71ac7237f --- /dev/null +++ b/.changeset/pre/node-terminal-idle-ttl.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Keep NodeTerminal's readline interface alive briefly between adjacent prompts to avoid a Windows TTY raw-mode hang. diff --git a/.changeset/pre/node-terminal-stdin-eof.md b/.changeset/pre/node-terminal-stdin-eof.md new file mode 100644 index 000000000..297b295aa --- /dev/null +++ b/.changeset/pre/node-terminal-stdin-eof.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +NodeTerminal: end key input and fail `readLine` with `QuitError` at stdin EOF instead of hanging diff --git a/.changeset/pre/normalize-httpapi-payload-media-types.md b/.changeset/pre/normalize-httpapi-payload-media-types.md new file mode 100644 index 000000000..ae6a0a78b --- /dev/null +++ b/.changeset/pre/normalize-httpapi-payload-media-types.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Normalize HttpApi payload media types. + +Payload schemas were stored under their exact declared `Content-Type`, but the server lowercased the incoming header and removed its parameters before looking it up. For example, a schema declared as `Application/Vnd.Effect+JSON; profile=declared` was stored under that value, while the server looked for `application/vnd.effect+json`. This could produce a `415` response even when the generated client and server used the same API. + +The same mismatch allowed incompatible encodings for equivalent media types to bypass validation. Generated form-urlencoded requests also ignored custom content types and always used the default one. + +Payload maps now use normalized keys for matching and conflict checks, while each encoding keeps its declared content type. Generated requests and OpenAPI use the declared values, including every parameterized variant, and custom form-urlencoded content types are preserved. diff --git a/.changeset/pre/normalize-unbounded-pubsub-replay.md b/.changeset/pre/normalize-unbounded-pubsub-replay.md new file mode 100644 index 000000000..8cc87d665 --- /dev/null +++ b/.changeset/pre/normalize-unbounded-pubsub-replay.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize unbounded PubSub replay capacities to positive integers. diff --git a/.changeset/pre/o8drprcu-sqlite-node-node-sqlite.md b/.changeset/pre/o8drprcu-sqlite-node-node-sqlite.md new file mode 100644 index 000000000..b1840466f --- /dev/null +++ b/.changeset/pre/o8drprcu-sqlite-node-node-sqlite.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-node": patch +--- + +Replace the `better-sqlite3` dependency with Node.js' built-in `node:sqlite` module. diff --git a/.changeset/odd-boats-think.md b/.changeset/pre/odd-boats-think.md similarity index 100% rename from .changeset/odd-boats-think.md rename to .changeset/pre/odd-boats-think.md diff --git a/.changeset/odd-bulldogs-sleep.md b/.changeset/pre/odd-bulldogs-sleep.md similarity index 100% rename from .changeset/odd-bulldogs-sleep.md rename to .changeset/pre/odd-bulldogs-sleep.md diff --git a/.changeset/odd-fans-glow.md b/.changeset/pre/odd-fans-glow.md similarity index 100% rename from .changeset/odd-fans-glow.md rename to .changeset/pre/odd-fans-glow.md diff --git a/.changeset/odd-forks-talk.md b/.changeset/pre/odd-forks-talk.md similarity index 100% rename from .changeset/odd-forks-talk.md rename to .changeset/pre/odd-forks-talk.md diff --git a/.changeset/odd-laws-draw.md b/.changeset/pre/odd-laws-draw.md similarity index 100% rename from .changeset/odd-laws-draw.md rename to .changeset/pre/odd-laws-draw.md diff --git a/.changeset/odd-owls-smoke.md b/.changeset/pre/odd-owls-smoke.md similarity index 100% rename from .changeset/odd-owls-smoke.md rename to .changeset/pre/odd-owls-smoke.md diff --git a/.changeset/odd-socks-boil.md b/.changeset/pre/odd-socks-boil.md similarity index 100% rename from .changeset/odd-socks-boil.md rename to .changeset/pre/odd-socks-boil.md diff --git a/.changeset/odd-suns-dance.md b/.changeset/pre/odd-suns-dance.md similarity index 100% rename from .changeset/odd-suns-dance.md rename to .changeset/pre/odd-suns-dance.md diff --git a/.changeset/old-brooms-cry.md b/.changeset/pre/old-brooms-cry.md similarity index 100% rename from .changeset/old-brooms-cry.md rename to .changeset/pre/old-brooms-cry.md diff --git a/.changeset/old-facts-stand.md b/.changeset/pre/old-facts-stand.md similarity index 100% rename from .changeset/old-facts-stand.md rename to .changeset/pre/old-facts-stand.md diff --git a/.changeset/old-mirrors-float.md b/.changeset/pre/old-mirrors-float.md similarity index 100% rename from .changeset/old-mirrors-float.md rename to .changeset/pre/old-mirrors-float.md diff --git a/.changeset/olive-poems-visit.md b/.changeset/pre/olive-poems-visit.md similarity index 100% rename from .changeset/olive-poems-visit.md rename to .changeset/pre/olive-poems-visit.md diff --git a/.changeset/pre/opaque-graph-interface.md b/.changeset/pre/opaque-graph-interface.md new file mode 100644 index 000000000..47e27a0b9 --- /dev/null +++ b/.changeset/pre/opaque-graph-interface.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make the public Graph interfaces opaque by hiding internal mutable storage fields from their TypeScript surface. diff --git a/.changeset/open-hotels-remain.md b/.changeset/pre/open-hotels-remain.md similarity index 100% rename from .changeset/open-hotels-remain.md rename to .changeset/pre/open-hotels-remain.md diff --git a/.changeset/pre/open-objects-intersect.md b/.changeset/pre/open-objects-intersect.md new file mode 100644 index 000000000..fcc55d4b2 --- /dev/null +++ b/.changeset/pre/open-objects-intersect.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/openapi-generator": patch +--- + +Emit mixed struct and record schema types as intersections, preventing optional +properties in open OpenAPI objects from conflicting with their index signature. diff --git a/.changeset/pre/openai-compat-decode-tool-params.md b/.changeset/pre/openai-compat-decode-tool-params.md new file mode 100644 index 000000000..5286f106e --- /dev/null +++ b/.changeset/pre/openai-compat-decode-tool-params.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Decode streaming and non-streaming tool call parameters with the provider-facing OpenAI schema codec. diff --git a/.changeset/pre/openai-compat-empty-assistant-content.md b/.changeset/pre/openai-compat-empty-assistant-content.md new file mode 100644 index 000000000..8426aa01f --- /dev/null +++ b/.changeset/pre/openai-compat-empty-assistant-content.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Normalize empty assistant message content to an empty string for OpenAI-compatible providers that reject null content values. diff --git a/.changeset/pre/openai-compat-null-tool-calls.md b/.changeset/pre/openai-compat-null-tool-calls.md new file mode 100644 index 000000000..06b23aed4 --- /dev/null +++ b/.changeset/pre/openai-compat-null-tool-calls.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Preserve streamed text from OpenAI-compatible providers that send `tool_calls: null` on text-only chunks. diff --git a/.changeset/openai-compat-nullable-tool-name.md b/.changeset/pre/openai-compat-nullable-tool-name.md similarity index 100% rename from .changeset/openai-compat-nullable-tool-name.md rename to .changeset/pre/openai-compat-nullable-tool-name.md diff --git a/.changeset/pre/openai-compat-parallel-tool-calls.md b/.changeset/pre/openai-compat-parallel-tool-calls.md new file mode 100644 index 000000000..d2b2ce441 --- /dev/null +++ b/.changeset/pre/openai-compat-parallel-tool-calls.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Group consecutive tool calls into one assistant message when using Chat Completions APIs. diff --git a/.changeset/openai-compat-reasoning.md b/.changeset/pre/openai-compat-reasoning.md similarity index 100% rename from .changeset/openai-compat-reasoning.md rename to .changeset/pre/openai-compat-reasoning.md diff --git a/.changeset/pre/openai-compatible-quota-errors.md b/.changeset/pre/openai-compatible-quota-errors.md new file mode 100644 index 000000000..a12a53a98 --- /dev/null +++ b/.changeset/pre/openai-compatible-quota-errors.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Support max reasoning effort and quota errors returned by OpenAI-compatible providers. diff --git a/.changeset/pre/openai-telemetry-response.md b/.changeset/pre/openai-telemetry-response.md new file mode 100644 index 000000000..c2bf786a2 --- /dev/null +++ b/.changeset/pre/openai-telemetry-response.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai-compat": patch +--- + +Fix the OpenAI-compatible telemetry response attribute namespace. diff --git a/.changeset/pre/openai-text-prompt-cache.md b/.changeset/pre/openai-text-prompt-cache.md new file mode 100644 index 000000000..f0f17bc01 --- /dev/null +++ b/.changeset/pre/openai-text-prompt-cache.md @@ -0,0 +1,6 @@ +--- +"@effect/ai-openai": patch +"effect": patch +--- + +Add support for explicit cache breakpoints on the OpenAI responses API for GPT-5.6-or-later. diff --git a/.changeset/pre/openai-tolerant-error-stream-event.md b/.changeset/pre/openai-tolerant-error-stream-event.md new file mode 100644 index 000000000..ee2e518a0 --- /dev/null +++ b/.changeset/pre/openai-tolerant-error-stream-event.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Decode nested OpenAI Responses API error events without changing their decoded type. diff --git a/.changeset/openapi-generator-sse-constraint-decoder.md b/.changeset/pre/openapi-generator-sse-constraint-decoder.md similarity index 100% rename from .changeset/openapi-generator-sse-constraint-decoder.md rename to .changeset/pre/openapi-generator-sse-constraint-decoder.md diff --git a/.changeset/pre/openapi-path-parameters.md b/.changeset/pre/openapi-path-parameters.md new file mode 100644 index 000000000..fcf771ed2 --- /dev/null +++ b/.changeset/pre/openapi-path-parameters.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/openapi-generator": patch +--- + +Allow path-level common parameters in OpenAPI generator input types. diff --git a/.changeset/pre/openapi-pointer-references.md b/.changeset/pre/openapi-pointer-references.md new file mode 100644 index 000000000..e7ef50995 --- /dev/null +++ b/.changeset/pre/openapi-pointer-references.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Decode JSON Pointer escapes when resolving local OpenAPI references. diff --git a/.changeset/pre/openapi-response-variants.md b/.changeset/pre/openapi-response-variants.md new file mode 100644 index 000000000..60a248f8c --- /dev/null +++ b/.changeset/pre/openapi-response-variants.md @@ -0,0 +1,6 @@ +--- +"@effect/openapi-generator": patch +--- + +Fix HttpClient response generation for mixed JSON-compatible representations, +binary success bodies, and bodiless error statuses. diff --git a/.changeset/pre/openrouter-generation-usage-schema.md b/.changeset/pre/openrouter-generation-usage-schema.md new file mode 100644 index 000000000..d9b9a6795 --- /dev/null +++ b/.changeset/pre/openrouter-generation-usage-schema.md @@ -0,0 +1,11 @@ +--- +"@effect/ai-openrouter": patch +--- + +Regenerate the `Generated` module against OpenRouter's current published specification. This preserves nullable +generation statistics and streamed usage cost metadata while incorporating the broader upstream schema changes. + +Notable generated schema renames include `ChatGenerationParams` to `ChatRequest`, `ChatGenerationTokenUsage` to +`ChatUsage`, `AssistantMessage` to `ChatAssistantMessage`, `ChatStreamingResponseChunk` to `ChatStreamingResponse`, +and `ChatMessageContentItemCacheControl` to `ChatContentCacheControl`. Handwritten public aliases such as +`ChatStreamingResponseChunkData`, `ReasoningDetails`, and `FileAnnotation` retain their existing names. diff --git a/.changeset/openrouter-input-audio.md b/.changeset/pre/openrouter-input-audio.md similarity index 100% rename from .changeset/openrouter-input-audio.md rename to .changeset/pre/openrouter-input-audio.md diff --git a/.changeset/pre/openrouter-tool-parameter-deltas.md b/.changeset/pre/openrouter-tool-parameter-deltas.md new file mode 100644 index 000000000..41da9fcad --- /dev/null +++ b/.changeset/pre/openrouter-tool-parameter-deltas.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openrouter": patch +--- + +Emit incremental tool parameter fragments from OpenRouter streaming responses. diff --git a/.changeset/pre/opentelemetry-render-causes.md b/.changeset/pre/opentelemetry-render-causes.md new file mode 100644 index 000000000..4ebcd6ef3 --- /dev/null +++ b/.changeset/pre/opentelemetry-render-causes.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Add `includeCauseInStack` option to `Cause.prettyErrors` diff --git a/.changeset/pre/optic-standalone-functions.md b/.changeset/pre/optic-standalone-functions.md new file mode 100644 index 000000000..fc0ebd947 --- /dev/null +++ b/.changeset/pre/optic-standalone-functions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add dual standalone functions for reading and updating values through optics, closes #7299. diff --git a/.changeset/pre/optimize-array-equality.md b/.changeset/pre/optimize-array-equality.md new file mode 100644 index 000000000..1ae1dede6 --- /dev/null +++ b/.changeset/pre/optimize-array-equality.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve the performance of `Array.dedupe`, `Array.union`, `Array.intersection`, `Array.difference`, and Schema unique item validation by using hash-based equality lookup. diff --git a/.changeset/pre/optimize-httpapi-handler-types.md b/.changeset/pre/optimize-httpapi-handler-types.md new file mode 100644 index 000000000..af3799598 --- /dev/null +++ b/.changeset/pre/optimize-httpapi-handler-types.md @@ -0,0 +1,204 @@ +--- +"effect": patch +--- + +Improve unstable `HttpApi` type-level performance. + +The implementation now uses identifier-keyed maps and lighter structural +constraints in several hot type-level paths. Generated group clients consume the +concrete endpoint map directly instead of rebuilding it from the endpoint union. + +## New Features + +- Add `HttpApiBuilder.Handlers.handleAll`, which registers an identifier-keyed batch of endpoint handlers for a group. Each entry can be either a handler function or `{ handler, options }`, and the object can be supplied in multiple partial batches. Endpoint identifiers that were already handled are rejected across batches. +- `HttpApi.groups` now preserves the concrete group type for each group identifier. For example, `Api.groups.users` is typed as the `users` group instead of the full group union. +- `HttpApiGroup.endpoints` now preserves the concrete endpoint type for each endpoint identifier. For example, `Group.endpoints.getUser` is typed as the `getUser` endpoint instead of the full endpoint union. +- `HttpApiEndpoint` values can now be extended as classes, matching the class-like + runtime shape already used by `HttpApi` and `HttpApiGroup`. + +## Measured Type-Level Performance + +Main/current comparisons use identical generated fixtures compiled once per +revision with TypeScript 7.0.2. The recorded revisions are `main` at +`97fdaa9c1f52` and the branch source at `5798fc5fafcd`. The focused pre/post +curves below were captured with the regular `httpapi` regression suite during +development. The retained suite uses representative stress points instead of +rerunning every point in those historical curves. All numbers are +type-instantiation deltas over the corresponding shared baseline. + +Endpoint declaration costs now grow with a lower slope: + +| endpoints | main | current | +| --------: | ------: | ------: | +| 10 | 4,580 | 2,808 | +| 50 | 15,500 | 9,168 | +| 100 | 29,150 | 17,118 | +| 500 | 138,350 | 80,718 | + +Class-like endpoint declarations are slightly cheaper than inline endpoint +values in the same 500-endpoint fixture shape: + +| fixture | inline | class-like | +| ------------- | -----: | ---------: | +| 500 endpoints | 82,207 | 71,850 | + +`HttpApiBuilder` fluent handler registration avoids the previous non-linear +blow-up in the cross-ref comparison: + +| fixture | main | current | +| ---------------- | ---------: | --------: | +| 10 endpoints | 37,856 | 11,582 | +| 50 endpoints | 568,576 | 63,702 | +| 100 endpoints | 2,154,476 | 182,852 | +| 500 endpoints | 51,741,676 | 3,296,052 | +| 500 raw handlers | 51,734,176 | 3,294,550 | + +In the recorded regular-suite measurements, `handleAll` remains the scalable +alternative to the equivalent fluent chain: + +| fixture | fluent | `handleAll` | +| -------------------- | --------: | ----------: | +| 10 endpoints | 11,579 | 9,146 | +| 50 endpoints | 63,699 | 25,106 | +| 100 endpoints | 182,849 | 45,056 | +| 500 endpoints | 3,296,049 | 204,656 | +| 500 eps, two batches | 3,296,049 | 223,613 | + +Generated-client type production also improves for the hot method-building +paths: + +| fixture | main | current | +| --------------------------------------- | ------: | ------: | +| client methods, 500 endpoints | 245,795 | 176,850 | +| top-level client methods, 500 endpoints | 243,651 | 179,809 | +| client endpoint method, 500 endpoints | 56,738 | 46,294 | +| client groups, 100 groups x 5 endpoints | 49,019 | 25,893 | + +The following focused curves were captured immediately before and after each +isolated type-level change. + +The focused `Client.Group` curve shows the improvement from consuming the +identifier-keyed endpoint map directly: + +| endpoints | union remapping | endpoint map | +| --------: | --------------: | -----------: | +| 10 | 12,448 | 12,294 | +| 50 | 19,169 | 18,935 | +| 100 | 27,570 | 27,236 | +| 500 | 94,770 | 93,636 | + +The focused `Client.TopLevelMethods` curve improves by reading endpoint +identifiers directly from the endpoint union: + +| endpoints | pre-change | post-change | +| --------: | ---------: | ----------: | +| 10 | 12,531 | 12,476 | +| 50 | 19,252 | 19,197 | +| 100 | 27,653 | 27,598 | +| 500 | 94,853 | 94,798 | + +The focused `HttpApiClient.endpoint` selection curve improves by reading +endpoint identifiers directly from the selected endpoint union: + +| endpoints | pre-change | post-change | +| --------: | ---------: | ----------: | +| 10 | 7,666 | 7,588 | +| 50 | 8,707 | 8,629 | +| 100 | 10,008 | 9,930 | +| 500 | 20,408 | 20,330 | + +The focused `HttpApiBuilder.endpoint` selection curve improves by reading +endpoint identifiers directly from the selected endpoint union: + +| endpoints | pre-change | post-change | +| --------: | ---------: | ----------: | +| 10 | 12,828 | 12,745 | +| 50 | 13,869 | 13,786 | +| 100 | 15,170 | 15,087 | +| 500 | 25,570 | 25,487 | + +URL builder types now avoid repeatedly expanding the full API/group shape: + +| fixture | main | current | +| ------------------------------------ | ------: | ------: | +| URL builder, 500 endpoints | 211,356 | 91,610 | +| top-level URL builder, 500 endpoints | 210,724 | 93,118 | +| builder endpoint, 500 endpoints | 62,894 | 51,952 | + +## Breaking Changes + +These changes affect unstable `HttpApi` type-level APIs and structural API, +group, and endpoint types. + +### Renamed Constraint Types + +- Broad structural constraint exports have been renamed to align with + `Schema.Constraint` terminology: `HttpApi.Any` to `HttpApi.Constraint`, + `HttpApi.AnyWithProps` to `HttpApi.Top`, `HttpApiGroup.Any` to + `HttpApiGroup.Constraint`, `HttpApiGroup.AnyWithProps` to `HttpApiGroup.Top`, + and `HttpApiEndpoint.Any` to `HttpApiEndpoint.Constraint`. +- `HttpApiEndpoint.AnyWithProps` has been replaced by `HttpApiEndpoint.Top`, whose + schema parameters are constrained to `Schema.Top`, including success and error + schemas. +- Type guards now expose the widened runtime-prop shapes: `HttpApi.isHttpApi` + returns `HttpApi.Top`, `HttpApiGroup.isHttpApiGroup` returns + `HttpApiGroup.Top`, and `HttpApiEndpoint.isHttpApiEndpoint` returns + `HttpApiEndpoint.Top`. +- `HttpApiGroup.ApiGroup` has been renamed to `HttpApiGroup.Service`. + +### API, Group, And Endpoint Shapes + +- `HttpApi.groups` is now typed as an identifier-keyed group map instead of + `ReadonlyRecord`, and `HttpApi` tracks its group union + invariantly. Dynamic string indexing must refine the key first or cast to a + broad runtime record. +- `HttpApiGroup.endpoints` is now typed as an identifier-keyed endpoint map instead of + `ReadonlyRecord`, and `HttpApiGroup` tracks its endpoint + union invariantly. Dynamic string indexing must refine the key first or cast to + a broad runtime record. +- `HttpApiEndpoint` now exposes its stable key as `identifier` instead of `name`, + aligning endpoints with APIs and groups and leaving `name` available for future + class-based endpoint patterns. +- `HttpApiEndpoint` values are now function objects instead of plain objects. + Runtime checks such as `typeof endpoint` now return `"function"`, and + `endpoint.name` is the native function name. Use `endpoint.identifier` for the + stable endpoint key. +- Identifier helper types have been renamed from `Name` / `WithName` to + `Identifier` / `WithIdentifier`; `HttpApiGroup.Service` now exposes + `identifier` instead of `name`. + +### Builder Handler Types + +- `HttpApiBuilder.Handlers` now tracks endpoints through an identifier-keyed endpoint map and a set of handled endpoint identifiers, instead of tracking the remaining endpoint union. Its public type parameters changed from `Handlers` to `Handlers`, and its phantom fields changed from `_Endpoints` to `~EndpointsByIdentifier` / `~HandledIdentifiers`. +- The unused `HttpApiBuilder.Handlers.Any` helper type has been removed. +- The exported `HttpApiBuilder.HandlersTypeId` symbol has been removed; `Handlers` + now uses a private string type id. +- Duplicate `handle` / `handleRaw` registrations for the same endpoint are rejected + at the call site, and `handleAll` rejects endpoint identifiers that were already + handled by an earlier batch. Missing endpoint handlers are still rejected by + the final `HttpApiBuilder.group` return validation. + +### Client Types + +- `HttpApiClient.Client.Group` now derives a client from a concrete group type: `Client.Group`. The previous group-union plus group-identifier form is no longer supported. +- `HttpApiClient.Client.TopLevelMethods` now returns an identifier-keyed method record instead of a union of `[identifier, method]` tuples. +- `HttpApiClient.makeWith` removes the default `HttpClientError.HttpClientError` from custom client error types in the returned `Client`, while preserving any additional custom client errors. + +### Endpoint Helper Types + +- `HttpApiEndpoint.HttpApiEndpoint` now stores lightweight phantom metadata for middleware and request shapes: `~Middleware`, `~MiddlewareServices`, `~Request`, and `~RequestRaw`. Its type identifier field is now `readonly [TypeId]: typeof TypeId`. +- `HttpApiEndpoint.Constraint` is now a lightweight structural endpoint constraint and does not extend `Pipeable`; values typed only as `HttpApiEndpoint.Constraint` do not expose `.pipe`. +- `HttpApiEndpoint.AddError` has been removed; it was not used internally by the `HttpApi` implementation. +- `HttpApiEndpoint.Json` and `HttpApiEndpoint.StringTree` have been removed in + favor of the canonical `Schema.toCodecJson` and `Schema.toCodecStringTree` + types. +- Omitted request-part metadata now remains `never` instead of being wrapped as + `Schema.toCodecStringTree`; codec metadata is applied only when + a params, query, payload, or headers schema is present. +- Success metadata now applies `Schema.toCodecJson` only to buffered + success schemas and preserves stream success schemas unchanged, including + mixed buffered and streaming success arrays. +- Handler request parts are now flattened with `Struct.Simplify`, improving + displayed request types while reducing handler instantiations. +- Endpoint helper types now read metadata fields directly instead of re-inferring all type parameters from the full `HttpApiEndpoint` interface. This affects helpers such as `Identifier`, `Success`, `Error`, `Params`, `Query`, `Payload`, `Headers`, `Middleware`, `MiddlewareServices`, `Errors`, `ErrorServicesEncode`, `ErrorServicesDecode`, `Request`, `RequestRaw`, `ServerServices`, and `ClientServices`. +- `HttpApiClient.Client.Method` and related generated-client helpers now require endpoint types that satisfy `HttpApiEndpoint.ConstraintRequest`. Endpoint-like structural types must include the lightweight request metadata fields to be accepted. diff --git a/.changeset/pre/optimize-node-http-server-response.md b/.changeset/pre/optimize-node-http-server-response.md new file mode 100644 index 000000000..1cb193cf5 --- /dev/null +++ b/.changeset/pre/optimize-node-http-server-response.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Optimize Node HTTP streaming responses and ensure HEAD completion and stream backpressure are handled once. diff --git a/.changeset/pre/optimize-schema-class-decoding.md b/.changeset/pre/optimize-schema-class-decoding.md new file mode 100644 index 000000000..61b14ad4c --- /dev/null +++ b/.changeset/pre/optimize-schema-class-decoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Avoid validating `Schema.Class` fields twice when decoding. diff --git a/.changeset/pre/optional-ai-embedding-usage.md b/.changeset/pre/optional-ai-embedding-usage.md new file mode 100644 index 000000000..beb5997ae --- /dev/null +++ b/.changeset/pre/optional-ai-embedding-usage.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow embedding usage input tokens to be omitted during decoding, including after JSON serialization. diff --git a/.changeset/pre/optional-ai-response-fields.md b/.changeset/pre/optional-ai-response-fields.md new file mode 100644 index 000000000..eebdd1ca0 --- /dev/null +++ b/.changeset/pre/optional-ai-response-fields.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow optional AI response fields to be omitted during decoding, including after JSON serialization. diff --git a/.changeset/otel-resource-env-precedence.md b/.changeset/pre/otel-resource-env-precedence.md similarity index 100% rename from .changeset/otel-resource-env-precedence.md rename to .changeset/pre/otel-resource-env-precedence.md diff --git a/.changeset/perfect-buckets-tickle.md b/.changeset/pre/perfect-buckets-tickle.md similarity index 100% rename from .changeset/perfect-buckets-tickle.md rename to .changeset/pre/perfect-buckets-tickle.md diff --git a/.changeset/petite-months-allow.md b/.changeset/pre/petite-months-allow.md similarity index 100% rename from .changeset/petite-months-allow.md rename to .changeset/pre/petite-months-allow.md diff --git a/.changeset/pre/pg-client-connect-error-handler.md b/.changeset/pre/pg-client-connect-error-handler.md new file mode 100644 index 000000000..3eed00972 --- /dev/null +++ b/.changeset/pre/pg-client-connect-error-handler.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Prevent unhandled `pg` client error events while `PgClient.makeClient` is connecting. diff --git a/.changeset/pre/plain-variant-unions.md b/.changeset/pre/plain-variant-unions.md new file mode 100644 index 000000000..367158347 --- /dev/null +++ b/.changeset/pre/plain-variant-unions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Include plain variant structs in the default variant union. diff --git a/.changeset/platform-crypto-service.md b/.changeset/pre/platform-crypto-service.md similarity index 100% rename from .changeset/platform-crypto-service.md rename to .changeset/pre/platform-crypto-service.md diff --git a/.changeset/platform-node-shared-barrel.md b/.changeset/pre/platform-node-shared-barrel.md similarity index 100% rename from .changeset/platform-node-shared-barrel.md rename to .changeset/pre/platform-node-shared-barrel.md diff --git a/.changeset/plenty-moons-pull.md b/.changeset/pre/plenty-moons-pull.md similarity index 100% rename from .changeset/plenty-moons-pull.md rename to .changeset/pre/plenty-moons-pull.md diff --git a/.changeset/polite-brooms-tickle.md b/.changeset/pre/polite-brooms-tickle.md similarity index 100% rename from .changeset/polite-brooms-tickle.md rename to .changeset/pre/polite-brooms-tickle.md diff --git a/.changeset/pre/polite-cameras-rest.md b/.changeset/pre/polite-cameras-rest.md new file mode 100644 index 000000000..348f740e9 --- /dev/null +++ b/.changeset/pre/polite-cameras-rest.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/platform-browser": patch +--- + +Preserve prototype accessors when code is compiled with loose object spread transforms. diff --git a/.changeset/pre/polite-dingos-unite.md b/.changeset/pre/polite-dingos-unite.md new file mode 100644 index 000000000..7ae08b5bc --- /dev/null +++ b/.changeset/pre/polite-dingos-unite.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve CRLF state across SSE input chunk boundaries. diff --git a/.changeset/polite-pigs-speak.md b/.changeset/pre/polite-pigs-speak.md similarity index 100% rename from .changeset/polite-pigs-speak.md rename to .changeset/pre/polite-pigs-speak.md diff --git a/.changeset/polite-tables-kneel.md b/.changeset/pre/polite-tables-kneel.md similarity index 100% rename from .changeset/polite-tables-kneel.md rename to .changeset/pre/polite-tables-kneel.md diff --git a/.changeset/pre/port-effect-reduce.md b/.changeset/pre/port-effect-reduce.md new file mode 100644 index 000000000..36632f7c2 --- /dev/null +++ b/.changeset/pre/port-effect-reduce.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Port `Effect.reduce` from Effect v3. diff --git a/.changeset/port-react-hydration.md b/.changeset/pre/port-react-hydration.md similarity index 100% rename from .changeset/port-react-hydration.md rename to .changeset/pre/port-react-hydration.md diff --git a/.changeset/pre/precise-clocks-rest.md b/.changeset/pre/precise-clocks-rest.md new file mode 100644 index 000000000..afe1d8a1e --- /dev/null +++ b/.changeset/pre/precise-clocks-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve nanosecond precision for large `TestClock` wall-clock timestamps. diff --git a/.changeset/pre/precise-test-clock-nanos.md b/.changeset/pre/precise-test-clock-nanos.md new file mode 100644 index 000000000..aec813c1a --- /dev/null +++ b/.changeset/pre/precise-test-clock-nanos.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve nanosecond precision when adjusting `TestClock` with large durations. diff --git a/.changeset/pre/preserve-command-hidden-metadata.md b/.changeset/pre/preserve-command-hidden-metadata.md new file mode 100644 index 000000000..ceb6c6868 --- /dev/null +++ b/.changeset/pre/preserve-command-hidden-metadata.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve hidden command metadata when adding subcommands or shared flags. diff --git a/.changeset/pre/preserve-config-all-input-evidence.md b/.changeset/pre/preserve-config-all-input-evidence.md new file mode 100644 index 000000000..bd8b92ad8 --- /dev/null +++ b/.changeset/pre/preserve-config-all-input-evidence.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve sibling provider input evidence when `Config.all` evaluates a failing child. diff --git a/.changeset/pre/preserve-openapi-raw-json.md b/.changeset/pre/preserve-openapi-raw-json.md new file mode 100644 index 000000000..8718904d1 --- /dev/null +++ b/.changeset/pre/preserve-openapi-raw-json.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `JSON.rawJSON` values when cloning cached OpenAPI specs. diff --git a/.changeset/pre/preserve-otel-parent-context.md b/.changeset/pre/preserve-otel-parent-context.md new file mode 100644 index 000000000..d436bd971 --- /dev/null +++ b/.changeset/pre/preserve-otel-parent-context.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Preserve trace state and locality when adapting active OpenTelemetry parent contexts. diff --git a/.changeset/pre/preserve-unsafe-workflow-interrupts.md b/.changeset/pre/preserve-unsafe-workflow-interrupts.md new file mode 100644 index 000000000..eaff87e4b --- /dev/null +++ b/.changeset/pre/preserve-unsafe-workflow-interrupts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve unsafe in-memory workflow interrupts across replay. diff --git a/.changeset/pre/pretty-moments-clap.md b/.changeset/pre/pretty-moments-clap.md new file mode 100644 index 000000000..0dd186ea2 --- /dev/null +++ b/.changeset/pre/pretty-moments-clap.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure handler errors don't cause httpapi security middleware to fallback diff --git a/.changeset/pre/protect-schema-issue-actuals.md b/.changeset/pre/protect-schema-issue-actuals.md new file mode 100644 index 000000000..0a1c82a3b --- /dev/null +++ b/.changeset/pre/protect-schema-issue-actuals.md @@ -0,0 +1,36 @@ +--- +"effect": patch +--- + +Remove `actual` fields from every `SchemaIssue` variant, together with +`SchemaIssue.getActual`, `SchemaIssue.redact`, and `Schema.redact`. Built-in +formatters now use static messages that do not interpolate rejected input, +while paths, AST metadata, union successes, and user-provided messages and +annotations are preserved unchanged. + +Runtime performance was measured across the 16 Effect fixtures in the +`schema-benchmarks` suite. These are the scenarios used for the cross-library +comparison with Valibot and Zod. The paired HEAD-versus-`main` run classified 3 +fixtures as improvements, 0 as regressions, and 13 as inconclusive. Negative +changes are faster. Absolute library values are medians from the same +cross-library run; `—` means that the corresponding adapter does not expose +that scenario. + +| Scenario | Effect (ns/op) | Valibot (ns/op) | Zod (ns/op) | HEAD vs main | Classification | +| ------------------------ | -------------: | --------------: | ----------: | -----------: | -------------- | +| `initialization-schema` | 108191.30 | **30549.81** | 212715.66 | -0.92% | inconclusive | +| `initialization-decoder` | **109796.34** | — | — | +1.98% | inconclusive | +| `validation-valid` | 5221.80 | **5070.81** | — | +2.06% | inconclusive | +| `validation-invalid` | 1279.77 | **234.92** | — | +0.59% | inconclusive | +| `parsing-all-valid` | **5144.58** | 5192.19 | 7176.19 | -3.79% | inconclusive | +| `parsing-all-invalid` | **7594.49** | 15236.82 | 37780.35 | -5.94% | improvement | +| `parsing-first-valid` | 5188.33 | **5135.75** | — | -1.49% | inconclusive | +| `parsing-first-invalid` | 1330.82 | **243.64** | — | +1.01% | inconclusive | +| `standard-all-valid` | 5722.01 | 5200.05 | **3801.26** | -1.78% | inconclusive | +| `standard-all-invalid` | **12024.65** | 15528.50 | 30982.17 | -7.78% | improvement | +| `standard-first-valid` | **5655.33** | — | — | +3.84% | inconclusive | +| `standard-first-invalid` | **2001.69** | — | — | -4.56% | inconclusive | +| `codec-typed-encode` | 342.59 | — | **39.29** | -7.62% | inconclusive | +| `codec-typed-decode` | 418.78 | — | **50.14** | -10.89% | improvement | +| `codec-unknown-encode` | **328.38** | — | — | -5.55% | inconclusive | +| `codec-unknown-decode` | **347.35** | — | — | -5.25% | inconclusive | diff --git a/.changeset/pre/proud-ears-say.md b/.changeset/pre/proud-ears-say.md new file mode 100644 index 000000000..36b35a7bb --- /dev/null +++ b/.changeset/pre/proud-ears-say.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Support standalone Effect.forEach data-last usage diff --git a/.changeset/pre/provider-executed-tool-results.md b/.changeset/pre/provider-executed-tool-results.md new file mode 100644 index 000000000..a589416b3 --- /dev/null +++ b/.changeset/pre/provider-executed-tool-results.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Route provider-executed tool results into the assistant message in `Prompt.fromResponseParts` diff --git a/.changeset/public-deer-ring.md b/.changeset/pre/public-deer-ring.md similarity index 100% rename from .changeset/public-deer-ring.md rename to .changeset/pre/public-deer-ring.md diff --git a/.changeset/public-jeans-stop.md b/.changeset/pre/public-jeans-stop.md similarity index 100% rename from .changeset/public-jeans-stop.md rename to .changeset/pre/public-jeans-stop.md diff --git a/.changeset/pubsub-publish-false.md b/.changeset/pre/pubsub-publish-false.md similarity index 100% rename from .changeset/pubsub-publish-false.md rename to .changeset/pre/pubsub-publish-false.md diff --git a/.changeset/puny-pens-clap.md b/.changeset/pre/puny-pens-clap.md similarity index 100% rename from .changeset/puny-pens-clap.md rename to .changeset/pre/puny-pens-clap.md diff --git a/.changeset/purple-bars-prove.md b/.changeset/pre/purple-bars-prove.md similarity index 100% rename from .changeset/purple-bars-prove.md rename to .changeset/pre/purple-bars-prove.md diff --git a/.changeset/purple-schools-float.md b/.changeset/pre/purple-schools-float.md similarity index 100% rename from .changeset/purple-schools-float.md rename to .changeset/pre/purple-schools-float.md diff --git a/.changeset/purple-turtles-draw.md b/.changeset/pre/purple-turtles-draw.md similarity index 100% rename from .changeset/purple-turtles-draw.md rename to .changeset/pre/purple-turtles-draw.md diff --git a/.changeset/quick-dragons-fix.md b/.changeset/pre/quick-dragons-fix.md similarity index 100% rename from .changeset/quick-dragons-fix.md rename to .changeset/pre/quick-dragons-fix.md diff --git a/.changeset/quick-falcons-travel.md b/.changeset/pre/quick-falcons-travel.md similarity index 100% rename from .changeset/quick-falcons-travel.md rename to .changeset/pre/quick-falcons-travel.md diff --git a/.changeset/quick-geese-relax.md b/.changeset/pre/quick-geese-relax.md similarity index 100% rename from .changeset/quick-geese-relax.md rename to .changeset/pre/quick-geese-relax.md diff --git a/.changeset/pre/quick-graphs-paths.md b/.changeset/pre/quick-graphs-paths.md new file mode 100644 index 000000000..2465d0a83 --- /dev/null +++ b/.changeset/pre/quick-graphs-paths.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add bounded lazy enumeration of simple paths and all tied shortest paths. diff --git a/.changeset/pre/quick-kiwis-remember.md b/.changeset/pre/quick-kiwis-remember.md new file mode 100644 index 000000000..2c1b55d09 --- /dev/null +++ b/.changeset/pre/quick-kiwis-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Persist permanent entries in KVS `setMany` operations. diff --git a/.changeset/quick-lamps-dig.md b/.changeset/pre/quick-lamps-dig.md similarity index 100% rename from .changeset/quick-lamps-dig.md rename to .changeset/pre/quick-lamps-dig.md diff --git a/.changeset/quick-lizards-fall.md b/.changeset/pre/quick-lizards-fall.md similarity index 100% rename from .changeset/quick-lizards-fall.md rename to .changeset/pre/quick-lizards-fall.md diff --git a/.changeset/pre/quick-schedulers-promise.md b/.changeset/pre/quick-schedulers-promise.md new file mode 100644 index 000000000..0cddcb3f3 --- /dev/null +++ b/.changeset/pre/quick-schedulers-promise.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use Promise microtasks for synchronous Scheduler dispatch. diff --git a/.changeset/quick-trees-join.md b/.changeset/pre/quick-trees-join.md similarity index 100% rename from .changeset/quick-trees-join.md rename to .changeset/pre/quick-trees-join.md diff --git a/.changeset/quiet-carpets-grin.md b/.changeset/pre/quiet-carpets-grin.md similarity index 100% rename from .changeset/quiet-carpets-grin.md rename to .changeset/pre/quiet-carpets-grin.md diff --git a/.changeset/pre/quiet-clis-parse.md b/.changeset/pre/quiet-clis-parse.md new file mode 100644 index 000000000..2fc507ec8 --- /dev/null +++ b/.changeset/pre/quiet-clis-parse.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix unstable CLI subcommands dropping operands after the `--` end-of-options terminator. diff --git a/.changeset/pre/quiet-crons-report.md b/.changeset/pre/quiet-crons-report.md new file mode 100644 index 000000000..d29a3bd23 --- /dev/null +++ b/.changeset/pre/quiet-crons-report.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct the diagnostic for cron step values above a field's maximum. diff --git a/.changeset/pre/quiet-fibers-settle.md b/.changeset/pre/quiet-fibers-settle.md new file mode 100644 index 000000000..81ca36e68 --- /dev/null +++ b/.changeset/pre/quiet-fibers-settle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix fiber self-interuption from inside a running operation diff --git a/.changeset/quiet-files-hunt.md b/.changeset/pre/quiet-files-hunt.md similarity index 100% rename from .changeset/quiet-files-hunt.md rename to .changeset/pre/quiet-files-hunt.md diff --git a/.changeset/pre/quiet-files-write.md b/.changeset/pre/quiet-files-write.md new file mode 100644 index 000000000..fb549eb54 --- /dev/null +++ b/.changeset/pre/quiet-files-write.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-deno": patch +--- + +Honor `FileSystem.writeFile` open flags in the Deno implementation. diff --git a/.changeset/pre/quiet-graphs-guard.md b/.changeset/pre/quiet-graphs-guard.md new file mode 100644 index 000000000..ababf77f0 --- /dev/null +++ b/.changeset/pre/quiet-graphs-guard.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep graph caches consistent during bulk removals and validate graph kinds at runtime. diff --git a/.changeset/pre/quiet-graphs-search.md b/.changeset/pre/quiet-graphs-search.md new file mode 100644 index 000000000..7c4a6c3ac --- /dev/null +++ b/.changeset/pre/quiet-graphs-search.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent graph edge reads from exposing internal edge records and reject non-finite A* heuristic values. diff --git a/.changeset/quiet-lamps-jam.md b/.changeset/pre/quiet-lamps-jam.md similarity index 100% rename from .changeset/quiet-lamps-jam.md rename to .changeset/pre/quiet-lamps-jam.md diff --git a/.changeset/pre/quiet-mice-negotiate.md b/.changeset/pre/quiet-mice-negotiate.md new file mode 100644 index 000000000..c54d96dde --- /dev/null +++ b/.changeset/pre/quiet-mice-negotiate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add adapter-valued MCP server protocol declarations, route requests through the selected protocol before schema decoding, and add built-in support for MCP `2025-06-18`. diff --git a/.changeset/pre/quiet-observers-report.md b/.changeset/pre/quiet-observers-report.md new file mode 100644 index 000000000..d547f9aac --- /dev/null +++ b/.changeset/pre/quiet-observers-report.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent execution-plan event observer defects from changing attempt outcomes or leaving attempt events unpaired. diff --git a/.changeset/pre/quiet-otters-retry.md b/.changeset/pre/quiet-otters-retry.md new file mode 100644 index 000000000..7673c4702 --- /dev/null +++ b/.changeset/pre/quiet-otters-retry.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor HTTP-date `Retry-After` values when retrying OTLP exports. diff --git a/.changeset/pre/quiet-owls-validate.md b/.changeset/pre/quiet-owls-validate.md new file mode 100644 index 000000000..6a85ac0b3 --- /dev/null +++ b/.changeset/pre/quiet-owls-validate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP Streamable HTTP servers now validate content negotiation, session lifecycle, negotiated protocol versions, and browser Origins before dispatching requests. diff --git a/.changeset/pre/quiet-pandas-rebuild.md b/.changeset/pre/quiet-pandas-rebuild.md new file mode 100644 index 000000000..879a8c442 --- /dev/null +++ b/.changeset/pre/quiet-pandas-rebuild.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix failed `ResourceRef` rebuilds permanently blocking waiters. diff --git a/.changeset/pre/quiet-pandas-respond.md b/.changeset/pre/quiet-pandas-respond.md new file mode 100644 index 000000000..b52a3f1c3 --- /dev/null +++ b/.changeset/pre/quiet-pandas-respond.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make multipart errors respond with an HTTP status based on their reason and ignore them in the error reporter. diff --git a/.changeset/quiet-radios-wave.md b/.changeset/pre/quiet-radios-wave.md similarity index 100% rename from .changeset/quiet-radios-wave.md rename to .changeset/pre/quiet-radios-wave.md diff --git a/.changeset/quiet-redis-scripts.md b/.changeset/pre/quiet-redis-scripts.md similarity index 100% rename from .changeset/quiet-redis-scripts.md rename to .changeset/pre/quiet-redis-scripts.md diff --git a/.changeset/pre/quiet-rpcs-buffer.md b/.changeset/pre/quiet-rpcs-buffer.md new file mode 100644 index 000000000..852a1f202 --- /dev/null +++ b/.changeset/pre/quiet-rpcs-buffer.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Bound framed RPC server HTTP response streams to 16 items by default, with a configurable buffer size or an unbounded opt-out. diff --git a/.changeset/pre/quiet-savepoints-wait.md b/.changeset/pre/quiet-savepoints-wait.md new file mode 100644 index 000000000..e27e942f1 --- /dev/null +++ b/.changeset/pre/quiet-savepoints-wait.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Serialize concurrent nested SQL transactions to prevent savepoint collisions. Cross-dependent sibling nested +transactions now deadlock instead of interleaving and risking silent data corruption. diff --git a/.changeset/pre/quiet-sockets-close.md b/.changeset/pre/quiet-sockets-close.md new file mode 100644 index 000000000..6d2d9ab73 --- /dev/null +++ b/.changeset/pre/quiet-sockets-close.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Close pending TCP and WebSocket connections when a scoped socket server shuts down diff --git a/.changeset/pre/quiet-spans-rest.md b/.changeset/pre/quiet-spans-rest.md new file mode 100644 index 000000000..9e574309a --- /dev/null +++ b/.changeset/pre/quiet-spans-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Skip HTTP server span attribute collection when the span is not sampled. diff --git a/.changeset/quiet-tigers-yell.md b/.changeset/pre/quiet-tigers-yell.md similarity index 100% rename from .changeset/quiet-tigers-yell.md rename to .changeset/pre/quiet-tigers-yell.md diff --git a/.changeset/pre/quiet-tools-smile.md b/.changeset/pre/quiet-tools-smile.md new file mode 100644 index 000000000..d14443e6e --- /dev/null +++ b/.changeset/pre/quiet-tools-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent MCP tool failures from exposing Cause rendering, stack traces, and internal paths while preserving actionable validation messages. diff --git a/.changeset/pre/quiet-turtles-smile.md b/.changeset/pre/quiet-turtles-smile.md new file mode 100644 index 000000000..ef13d5bf9 --- /dev/null +++ b/.changeset/pre/quiet-turtles-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Move `UrlParams.makeUrl` to `Url.make` and return `Url.UrlError` for URL construction failures. diff --git a/.changeset/random-choice.md b/.changeset/pre/random-choice.md similarity index 100% rename from .changeset/random-choice.md rename to .changeset/pre/random-choice.md diff --git a/.changeset/pre/random-hex-encoding.md b/.changeset/pre/random-hex-encoding.md new file mode 100644 index 000000000..9d894f62d --- /dev/null +++ b/.changeset/pre/random-hex-encoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Encoding.randomHex`, a lightweight non-cryptographic generator that coerces lengths to unsigned 32-bit multiples of 8. diff --git a/.changeset/pre/read-only-bun-sqlite.md b/.changeset/pre/read-only-bun-sqlite.md new file mode 100644 index 000000000..9037b67ad --- /dev/null +++ b/.changeset/pre/read-only-bun-sqlite.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-bun": patch +--- + +Enforce read-only mode when opening Bun SQLite databases. diff --git a/.changeset/ready-olives-divide.md b/.changeset/pre/ready-olives-divide.md similarity index 100% rename from .changeset/ready-olives-divide.md rename to .changeset/pre/ready-olives-divide.md diff --git a/.changeset/real-trains-ring.md b/.changeset/pre/real-trains-ring.md similarity index 100% rename from .changeset/real-trains-ring.md rename to .changeset/pre/real-trains-ring.md diff --git a/.changeset/pre/record-from-iterable-by-dual.md b/.changeset/pre/record-from-iterable-by-dual.md new file mode 100644 index 000000000..13fc30740 --- /dev/null +++ b/.changeset/pre/record-from-iterable-by-dual.md @@ -0,0 +1,16 @@ +--- +"effect": patch +--- + +Record: make `fromIterableBy` dual, allowing data-last usage in `pipe` + +```ts +import { pipe, Record } from "effect" + +const users = [ + { id: "2", name: "name2" }, + { id: "1", name: "name1" } +] + +pipe(users, Record.fromIterableBy((user) => user.id)) +``` diff --git a/.changeset/red-pigs-repair.md b/.changeset/pre/red-pigs-repair.md similarity index 100% rename from .changeset/red-pigs-repair.md rename to .changeset/pre/red-pigs-repair.md diff --git a/.changeset/pre/redact-cli-wizard-values.md b/.changeset/pre/redact-cli-wizard-values.md new file mode 100644 index 000000000..b5be1435f --- /dev/null +++ b/.changeset/pre/redact-cli-wizard-values.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Redact password prompt values from CLI wizard command output. diff --git a/.changeset/redacted-representation-options.md b/.changeset/pre/redacted-representation-options.md similarity index 100% rename from .changeset/redacted-representation-options.md rename to .changeset/pre/redacted-representation-options.md diff --git a/.changeset/pre/redis-subscribe-api.md b/.changeset/pre/redis-subscribe-api.md new file mode 100644 index 000000000..b6203f7e4 --- /dev/null +++ b/.changeset/pre/redis-subscribe-api.md @@ -0,0 +1,8 @@ +--- +"effect": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"@effect/platform-node": patch +--- + +Add scoped Redis pub/sub subscriptions that expose received messages through an Effect queue. diff --git a/.changeset/refactor-cli-global-flags.md b/.changeset/pre/refactor-cli-global-flags.md similarity index 100% rename from .changeset/refactor-cli-global-flags.md rename to .changeset/pre/refactor-cli-global-flags.md diff --git a/.changeset/refactor-config-provider.md b/.changeset/pre/refactor-config-provider.md similarity index 100% rename from .changeset/refactor-config-provider.md rename to .changeset/pre/refactor-config-provider.md diff --git a/.changeset/refactor-representation-references.md b/.changeset/pre/refactor-representation-references.md similarity index 100% rename from .changeset/refactor-representation-references.md rename to .changeset/pre/refactor-representation-references.md diff --git a/.changeset/pre/refine-config-absence.md b/.changeset/pre/refine-config-absence.md new file mode 100644 index 000000000..bd2a1c58c --- /dev/null +++ b/.changeset/pre/refine-config-absence.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Refine `Config` loading and absence semantics. `Config.schema` now derives a provider loading policy from the encoded `StringTree` schema, materializes mixed-shape union members independently, and leaves separated scalar parsing to `Config.Array` and `Config.Record`. Schemas whose canonical `StringTree` encoding remains opaque, such as `Schema.Any`, `Schema.Unknown`, or `Schema.Json`, are rejected when the config is constructed; use a concrete shape or `Schema.fromJsonString(Schema.Json)` for scalar JSON. Missing or unavailable representations are decoded as `undefined` before `Config.withDefault` and `Config.option` decide absence. Partially supplied `Config.all` groups are rejected, successful values such as `undefined` and explicitly present empty structures are preserved, and the internal path prefix is removed from the public `Config.parse` signature. diff --git a/.changeset/pre/refresh-mcp-roots.md b/.changeset/pre/refresh-mcp-roots.md new file mode 100644 index 000000000..501d714fd --- /dev/null +++ b/.changeset/pre/refresh-mcp-roots.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now refresh roots after capable clients report that their root list changed. diff --git a/.changeset/pre/register-param-alternate-flags.md b/.changeset/pre/register-param-alternate-flags.md new file mode 100644 index 000000000..511689a84 --- /dev/null +++ b/.changeset/pre/register-param-alternate-flags.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Register alternate flags used by `Param.orElse` and `Param.orElseResult`. diff --git a/.changeset/pre/release-rpc-worker-pool-entries.md b/.changeset/pre/release-rpc-worker-pool-entries.md new file mode 100644 index 000000000..fe2aa1ba1 --- /dev/null +++ b/.changeset/pre/release-rpc-worker-pool-entries.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Release worker pool entries when an RPC worker's receive loop fails. diff --git a/.changeset/pre/remove-context-mutate.md b/.changeset/pre/remove-context-mutate.md new file mode 100644 index 000000000..55d8b1a8c --- /dev/null +++ b/.changeset/pre/remove-context-mutate.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Context.mutate` and `Context.getReferenceUnsafe`. Context updates now use overlays, and `Context.get` resolves reference defaults. diff --git a/.changeset/remove-effect-transactionwith.md b/.changeset/pre/remove-effect-transactionwith.md similarity index 100% rename from .changeset/remove-effect-transactionwith.md rename to .changeset/pre/remove-effect-transactionwith.md diff --git a/.changeset/remove-http-span-counter.md b/.changeset/pre/remove-http-span-counter.md similarity index 100% rename from .changeset/remove-http-span-counter.md rename to .changeset/pre/remove-http-span-counter.md diff --git a/.changeset/pre/remove-kubernetes-types.md b/.changeset/pre/remove-kubernetes-types.md new file mode 100644 index 000000000..395085e4f --- /dev/null +++ b/.changeset/pre/remove-kubernetes-types.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the `kubernetes-types` dependency by vendoring the Kubernetes Pod declarations used by the cluster helpers and exporting them from `effect/unstable/cluster/K8sTypes`. diff --git a/.changeset/remove-nullor.md b/.changeset/pre/remove-nullor.md similarity index 100% rename from .changeset/remove-nullor.md rename to .changeset/pre/remove-nullor.md diff --git a/.changeset/remove-openapi-fromapi-options.md b/.changeset/pre/remove-openapi-fromapi-options.md similarity index 100% rename from .changeset/remove-openapi-fromapi-options.md rename to .changeset/pre/remove-openapi-fromapi-options.md diff --git a/.changeset/pre/remove-schedule-apis.md b/.changeset/pre/remove-schedule-apis.md new file mode 100644 index 000000000..79d641e92 --- /dev/null +++ b/.changeset/pre/remove-schedule-apis.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove some Schedule APIs: `collectInputs`, `collectOutputs`, `collectWhile`, `delays`, `reduce`, `satisfiesErrorType`, `satisfiesInputType`, `satisfiesOutputType`, `satisfiesServicesType`, and `unfold`. diff --git a/.changeset/pre/remove-schedule-either.md b/.changeset/pre/remove-schedule-either.md new file mode 100644 index 000000000..ee0be8634 --- /dev/null +++ b/.changeset/pre/remove-schedule-either.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the Schedule.either APIs and add Schedule.min for fastest-duration schedule composition. diff --git a/.changeset/pre/remove-schedule-elapsed.md b/.changeset/pre/remove-schedule-elapsed.md new file mode 100644 index 000000000..1863f1496 --- /dev/null +++ b/.changeset/pre/remove-schedule-elapsed.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Schedule.elapsed`. diff --git a/.changeset/pre/remove-schedule-taps.md b/.changeset/pre/remove-schedule-taps.md new file mode 100644 index 000000000..18572cb53 --- /dev/null +++ b/.changeset/pre/remove-schedule-taps.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Schedule.tapInput` and `Schedule.tapOutput`. Use `Schedule.tap` instead. diff --git a/.changeset/pre/remove-schema-key-value-combiner.md b/.changeset/pre/remove-schema-key-value-combiner.md new file mode 100644 index 000000000..05add770f --- /dev/null +++ b/.changeset/pre/remove-schema-key-value-combiner.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Remove the `keyValueCombiner` option from `Schema.Record` and the corresponding +`SchemaAST.KeyValueCombiner` and `SchemaAST.IndexSignature.merge` APIs. +For transformed key collisions, sequential parsing keeps the later selected +value, while concurrent parsing keeps the value applied last in completion +order. diff --git a/.changeset/remove-schema-stringtree-keep-declarations.md b/.changeset/pre/remove-schema-stringtree-keep-declarations.md similarity index 100% rename from .changeset/remove-schema-stringtree-keep-declarations.md rename to .changeset/pre/remove-schema-stringtree-keep-declarations.md diff --git a/.changeset/pre/remove-schema-utils.md b/.changeset/pre/remove-schema-utils.md new file mode 100644 index 000000000..cb605c700 --- /dev/null +++ b/.changeset/pre/remove-schema-utils.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the experimental `SchemaUtils` module and its `getNativeClassSchema` helper. The helper duplicated a composition already available through the primary Schema APIs and did not justify a separate public module. diff --git a/.changeset/remove-types-mergerecord.md b/.changeset/pre/remove-types-mergerecord.md similarity index 100% rename from .changeset/remove-types-mergerecord.md rename to .changeset/pre/remove-types-mergerecord.md diff --git a/.changeset/remove-unused-utils-apis.md b/.changeset/pre/remove-unused-utils-apis.md similarity index 100% rename from .changeset/remove-unused-utils-apis.md rename to .changeset/pre/remove-unused-utils-apis.md diff --git a/.changeset/pre/remove-with-concurrency.md b/.changeset/pre/remove-with-concurrency.md new file mode 100644 index 000000000..d1d820b42 --- /dev/null +++ b/.changeset/pre/remove-with-concurrency.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove `Effect.withConcurrency`, the `References.CurrentConcurrency` reference backing it, and the `"inherit"` option from `Types.Concurrency`. Use an explicit `number` or `"unbounded"` concurrency value instead. diff --git a/.changeset/rename-rebuild-out.md b/.changeset/pre/rename-rebuild-out.md similarity index 100% rename from .changeset/rename-rebuild-out.md rename to .changeset/pre/rename-rebuild-out.md diff --git a/.changeset/pre/rename-schema-error-constructors.md b/.changeset/pre/rename-schema-error-constructors.md new file mode 100644 index 000000000..f4ebfb8fe --- /dev/null +++ b/.changeset/pre/rename-schema-error-constructors.md @@ -0,0 +1,10 @@ +--- +"effect": patch +--- + +Rename the Schema error constructors to align with their `Data` counterparts. + +- `Schema.ErrorClass` is now `Schema.Error`. +- `Schema.TaggedErrorClass` is now `Schema.TaggedError`. +- The JavaScript `Error` instance schema is now `Schema.ErrorInstance`. +- `Schema.ErrorReviver` is now `Schema.ErrorInstanceReviver`. diff --git a/.changeset/pre/render-cli-user-errors.md b/.changeset/pre/render-cli-user-errors.md new file mode 100644 index 000000000..cb8229f8a --- /dev/null +++ b/.changeset/pre/render-cli-user-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add an optional user-facing message to CLI `UserError` values with safe cause-based fallbacks. `Command.run` and `Command.runWith` now render handler `UserError` failures through the installed output formatter; hosts that already print these errors should remove their duplicate output. Set `renderErrors: false` when the host should own error rendering. diff --git a/.changeset/pre/report-schema-input.md b/.changeset/pre/report-schema-input.md new file mode 100644 index 000000000..038f5d936 --- /dev/null +++ b/.changeset/pre/report-schema-input.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Add the opt-in `reportInput` parse option for retaining rejected inputs in enumerable fields on value-bearing schema issues and including them in default formatted messages. Value-bearing issue constructors accept the rejected input and parse options directly, and `Schema.Annotations.Issue` now supports `expected` for default messages. + +Schema issues no longer format implicitly through `Issue#toString`. Use `SchemaIssue.makeFormatterDefault()` when a human-readable message is needed. The throwing and Promise-based adapters in `SchemaParser` now use the generic message `"Schema validation failed"` and expose the structured `SchemaIssue.Issue` as the error `cause`; consumers that previously read the formatted error message should inspect and explicitly format that cause instead. + +`Schema.makeEffect` now returns `SchemaIssue.Issue` failures instead of wrapping them in `SchemaError`, and `Schema.withConstructorDefault` accepts an `Effect` that fails with `SchemaIssue.Issue`. Fallible `Optic` operations return structured `SchemaIssue.Issue` failures, while schema failures from `Schema.toIso` and `Schema.toDifferJsonPatch` use the generic error message and preserve the issue in `cause` instead of formatting it internally. diff --git a/.changeset/pre/report-transient-rpc-socket-errors.md b/.changeset/pre/report-transient-rpc-socket-errors.md new file mode 100644 index 000000000..3c81a4050 --- /dev/null +++ b/.changeset/pre/report-transient-rpc-socket-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Report retried RPC socket open failures through the `onTransientError` protocol hook and fail in-flight requests when the retry policy is exhausted. diff --git a/.changeset/pre/required-boolean-flags.md b/.changeset/pre/required-boolean-flags.md new file mode 100644 index 000000000..c49bea5ed --- /dev/null +++ b/.changeset/pre/required-boolean-flags.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make unstable CLI boolean flags required when omitted, allowing optional, default, config, and prompt fallbacks to handle absence consistently. diff --git a/.changeset/pre/resource-subscriptions.md b/.changeset/pre/resource-subscriptions.md new file mode 100644 index 000000000..c91274c3b --- /dev/null +++ b/.changeset/pre/resource-subscriptions.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP servers now support session-scoped resource subscriptions on transports that can deliver server notifications and filter resource updates by each client's subscribed URIs. diff --git a/.changeset/pre/restore-effect-head.md b/.changeset/pre/restore-effect-head.md new file mode 100644 index 000000000..0c2f7d3e8 --- /dev/null +++ b/.changeset/pre/restore-effect-head.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Effect.head` for retrieving the first element of an iterable produced by an effect. diff --git a/.changeset/restore-schema-parse-options.md b/.changeset/pre/restore-schema-parse-options.md similarity index 100% rename from .changeset/restore-schema-parse-options.md rename to .changeset/pre/restore-schema-parse-options.md diff --git a/.changeset/pre/result-map-error-success-identity.md b/.changeset/pre/result-map-error-success-identity.md new file mode 100644 index 000000000..dbcacc774 --- /dev/null +++ b/.changeset/pre/result-map-error-success-identity.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Preserve untouched `Result` branches by identity in `Result.map` and +`Result.mapError`. diff --git a/.changeset/pre/retry-redis-script-load.md b/.changeset/pre/retry-redis-script-load.md new file mode 100644 index 000000000..c4017e413 --- /dev/null +++ b/.changeset/pre/retry-redis-script-load.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Redis script evaluation so transient `SCRIPT LOAD` failures are retried instead of being cached indefinitely. diff --git a/.changeset/pre/reuse-httpapi-response-schemas.md b/.changeset/pre/reuse-httpapi-response-schemas.md new file mode 100644 index 000000000..1c7d4c087 --- /dev/null +++ b/.changeset/pre/reuse-httpapi-response-schemas.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Reuse HttpApi response schemas. + +`HttpApiBuilder` looked up cached response schemas by their source AST but stored them by the transformed AST, so the cache normally missed. It now uses the source AST consistently. diff --git a/.changeset/rich-dots-push.md b/.changeset/pre/rich-dots-push.md similarity index 100% rename from .changeset/rich-dots-push.md rename to .changeset/pre/rich-dots-push.md diff --git a/.changeset/rich-hoops-nail.md b/.changeset/pre/rich-hoops-nail.md similarity index 100% rename from .changeset/rich-hoops-nail.md rename to .changeset/pre/rich-hoops-nail.md diff --git a/.changeset/rich-sloths-draw.md b/.changeset/pre/rich-sloths-draw.md similarity index 100% rename from .changeset/rich-sloths-draw.md rename to .changeset/pre/rich-sloths-draw.md diff --git a/.changeset/ripe-lies-battle.md b/.changeset/pre/ripe-lies-battle.md similarity index 100% rename from .changeset/ripe-lies-battle.md rename to .changeset/pre/ripe-lies-battle.md diff --git a/.changeset/rpc-client-http-early-close.md b/.changeset/pre/rpc-client-http-early-close.md similarity index 100% rename from .changeset/rpc-client-http-early-close.md rename to .changeset/pre/rpc-client-http-early-close.md diff --git a/.changeset/rpc-middleware-provides-fix.md b/.changeset/pre/rpc-middleware-provides-fix.md similarity index 100% rename from .changeset/rpc-middleware-provides-fix.md rename to .changeset/pre/rpc-middleware-provides-fix.md diff --git a/.changeset/pre/rpc-server-notifications.md b/.changeset/pre/rpc-server-notifications.md new file mode 100644 index 000000000..d4be791ee --- /dev/null +++ b/.changeset/pre/rpc-server-notifications.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Add support for server-originated RPC requests and notifications. Buffered +JSON-RPC HTTP drops notifications until streaming responses are available. diff --git a/.changeset/pre/safe-json-schema-patterns.md b/.changeset/pre/safe-json-schema-patterns.md new file mode 100644 index 000000000..ab10a6aa0 --- /dev/null +++ b/.changeset/pre/safe-json-schema-patterns.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Require explicit handling for regular expression pattern constraints translated from JSON Schema documents, with modes to apply trusted patterns or ignore their constraints. diff --git a/.changeset/scalar-custom-fetch.md b/.changeset/pre/scalar-custom-fetch.md similarity index 100% rename from .changeset/scalar-custom-fetch.md rename to .changeset/pre/scalar-custom-fetch.md diff --git a/.changeset/pre/schema-arbitrary-factory.md b/.changeset/pre/schema-arbitrary-factory.md new file mode 100644 index 000000000..d0802c815 --- /dev/null +++ b/.changeset/pre/schema-arbitrary-factory.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Consolidate schema arbitrary derivation into `Schema.toArbitrary`, which now returns a `Schema.Arbitrary` factory that accepts the fast-check module. Remove `Schema.toArbitraryLazy` and arbitrary derivation reports. diff --git a/.changeset/schema-as-class.md b/.changeset/pre/schema-as-class.md similarity index 100% rename from .changeset/schema-as-class.md rename to .changeset/pre/schema-as-class.md diff --git a/.changeset/schema-asserts-signature.md b/.changeset/pre/schema-asserts-signature.md similarity index 100% rename from .changeset/schema-asserts-signature.md rename to .changeset/pre/schema-asserts-signature.md diff --git a/.changeset/pre/schema-aware-rpc-serialization.md b/.changeset/pre/schema-aware-rpc-serialization.md new file mode 100644 index 000000000..bef86f0ae --- /dev/null +++ b/.changeset/pre/schema-aware-rpc-serialization.md @@ -0,0 +1,9 @@ +--- +"effect": minor +--- + +Make RPC serialization schema-aware. + +Add `codecFor` to RPC serialization and client/server protocols so RPC and cluster +network payloads use the transport's schema codec. Framing, cluster storage, and +existing built-in wire formats remain unchanged. diff --git a/.changeset/pre/schema-binary-codec.md b/.changeset/pre/schema-binary-codec.md new file mode 100644 index 000000000..b2628cb3a --- /dev/null +++ b/.changeset/pre/schema-binary-codec.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `SchemaBinary`, a compact schema-derived codec with streaming, optional fingerprints and dictionaries, and RPC support. diff --git a/.changeset/schema-clean-up-additionalProperties.md b/.changeset/pre/schema-clean-up-additionalProperties.md similarity index 100% rename from .changeset/schema-clean-up-additionalProperties.md rename to .changeset/pre/schema-clean-up-additionalProperties.md diff --git a/.changeset/pre/schema-codec-narrowing.md b/.changeset/pre/schema-codec-narrowing.md new file mode 100644 index 000000000..c47989842 --- /dev/null +++ b/.changeset/pre/schema-codec-narrowing.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Schema: add `Schema.Decoder` and `Schema.Encoder`, and accept simpler schema types in APIs that only decode, only encode, or only need the basic schema shape, closes #2536 diff --git a/.changeset/pre/schema-date-valid.md b/.changeset/pre/schema-date-valid.md new file mode 100644 index 000000000..a7fbee4e9 --- /dev/null +++ b/.changeset/pre/schema-date-valid.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Make `Schema.Date` reject invalid dates and remove the redundant `Schema.DateValid`, `Schema.isDateValid`, and `Schema.isDateValidReviver` APIs. + +`Schema.DateFromString` and `Schema.DateFromMillis` now fail decoding when their input would produce an invalid date. + +Remove `Schema.Annotations.ToArbitrary.GenerationConstraint.valid`; `Schema.Date` arbitraries now generate only valid dates by default. diff --git a/.changeset/schema-datetime-utc-from-string.md b/.changeset/pre/schema-datetime-utc-from-string.md similarity index 100% rename from .changeset/schema-datetime-utc-from-string.md rename to .changeset/pre/schema-datetime-utc-from-string.md diff --git a/.changeset/schema-decoding-defaults-services.md b/.changeset/pre/schema-decoding-defaults-services.md similarity index 100% rename from .changeset/schema-decoding-defaults-services.md rename to .changeset/pre/schema-decoding-defaults-services.md diff --git a/.changeset/schema-defaults-issue-channel.md b/.changeset/pre/schema-defaults-issue-channel.md similarity index 100% rename from .changeset/schema-defaults-issue-channel.md rename to .changeset/pre/schema-defaults-issue-channel.md diff --git a/.changeset/pre/schema-direct-class-extension.md b/.changeset/pre/schema-direct-class-extension.md new file mode 100644 index 000000000..43c457b1a --- /dev/null +++ b/.changeset/pre/schema-direct-class-extension.md @@ -0,0 +1,22 @@ +--- +"effect": patch +--- + +Schema: make schemas directly extendable as classes with static method support +and remove `Schema.asClass`. + +`Bottom` and `BottomLazy` now include the class-compatible `new` signature, +while `BottomWithoutNew` and `BottomLazyWithoutNew` expose the schema protocol +without it for schema types that define a specialized construct signature. + +**Example** + +```ts +import { Schema } from "effect" + +class MyString extends Schema.String { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) +} + +MyString.decodeUnknownSync("a") // "a" +``` diff --git a/.changeset/schema-dollar-prefix.md b/.changeset/pre/schema-dollar-prefix.md similarity index 100% rename from .changeset/schema-dollar-prefix.md rename to .changeset/pre/schema-dollar-prefix.md diff --git a/.changeset/schema-lazy-bottom.md b/.changeset/pre/schema-lazy-bottom.md similarity index 100% rename from .changeset/schema-lazy-bottom.md rename to .changeset/pre/schema-lazy-bottom.md diff --git a/.changeset/schema-missing-self-generic.md b/.changeset/pre/schema-missing-self-generic.md similarity index 100% rename from .changeset/schema-missing-self-generic.md rename to .changeset/pre/schema-missing-self-generic.md diff --git a/.changeset/schema-ordered-arbitrary-constraints.md b/.changeset/pre/schema-ordered-arbitrary-constraints.md similarity index 100% rename from .changeset/schema-ordered-arbitrary-constraints.md rename to .changeset/pre/schema-ordered-arbitrary-constraints.md diff --git a/.changeset/schema-parser-adapter-errors.md b/.changeset/pre/schema-parser-adapter-errors.md similarity index 100% rename from .changeset/schema-parser-adapter-errors.md rename to .changeset/pre/schema-parser-adapter-errors.md diff --git a/.changeset/schema-refactor-toCodecJson.md b/.changeset/pre/schema-refactor-toCodecJson.md similarity index 100% rename from .changeset/schema-refactor-toCodecJson.md rename to .changeset/pre/schema-refactor-toCodecJson.md diff --git a/.changeset/schema-remove-annotate-in.md b/.changeset/pre/schema-remove-annotate-in.md similarity index 100% rename from .changeset/schema-remove-annotate-in.md rename to .changeset/pre/schema-remove-annotate-in.md diff --git a/.changeset/schema-rename-makeUnsafe-to-make.md b/.changeset/pre/schema-rename-makeUnsafe-to-make.md similarity index 100% rename from .changeset/schema-rename-makeUnsafe-to-make.md rename to .changeset/pre/schema-rename-makeUnsafe-to-make.md diff --git a/.changeset/schema-rename-parser-makeUnsafe.md b/.changeset/pre/schema-rename-parser-makeUnsafe.md similarity index 100% rename from .changeset/schema-rename-parser-makeUnsafe.md rename to .changeset/pre/schema-rename-parser-makeUnsafe.md diff --git a/.changeset/pre/schema-representation-refactoring.md b/.changeset/pre/schema-representation-refactoring.md new file mode 100644 index 000000000..fddaef586 --- /dev/null +++ b/.changeset/pre/schema-representation-refactoring.md @@ -0,0 +1,71 @@ +--- +"effect": patch +--- + +Refactor the `SchemaRepresentation` module to improve clarity and maintainability. + +The representation pipeline is now open and compiler-extensible. The same encoded-side representation is used for JSON persistence, runtime reconstruction, JSON Schema Draft 2020-12 compilation, TypeScript code generation, AI structured output, and HTTP / OpenAPI schemas. + +### New representation model + +- Add `RepresentationAnnotation` and `CheckRepresentationAnnotation`, which identify declarations and checks with a stable `id`, JSON `payload`, and optional schema dependencies. +- Preserve checks on every non-reference representation node instead of storing constraints in the previous closed `meta` unions. +- Add compiler hooks for checks and declarations through `SchemaRepresentation.ToJsonSchema` and `SchemaRepresentation.Generation`. +- Add `SchemaMultiDocument`, `fromSchemaMultiDocument`, and `fromRepresentations` so several live schemas and named definitions can be converted and reconstructed together. Explicit definitions are preserved even when no root references them. +- Preserve shared structural nodes, annotated recursion, union member order, identifiers, reference siblings, and structural checks when projecting encoded schemas. + +### Persistence and revivers + +- Add `toJson`, `fromJson`, `toJsonMultiDocument`, and `fromJsonMultiDocument` as the persistence boundary for representation documents. +- Live representations store literal, enum, and property-name scalars as native values. JSON persistence encodes them as `{ type, value }` tagged unions so their runtime types remain distinct across persistence formats, canonically encodes structural bigint and global symbol values, keeps JSON-valued annotations, and removes runtime-only callbacks and other non-JSON annotation values. +- Replace the generic reviver callback with typed `DeclarationReviver`, `FilterReviver`, and `FilterGroupReviver` contracts. Add `makeDeclarationReviver`, `makeFilterReviver`, and `makeFilterGroupReviver`, which infer their payload type from `payloadSchema`. +- Resolve acyclic references to concrete runtime schemas and reserve `Schema.suspend` wrappers for recursive back-edges. Acyclic alias chains may be normalized while preserving the outer reference identifier. +- Export individual revivers for built-in declarations and checks from `Schema`. Consumers opt in to exactly the revivers accepted when reconstructing persisted documents: + - declaration revivers: `OptionReviver`, `ResultReviver`, `RedactedReviver`, `CauseReasonReviver`, `CauseReviver`, `ErrorReviver`, `ExitReviver`, `ReadonlyMapReviver`, `HashMapReviver`, `ReadonlySetReviver`, `HashSetReviver`, `ChunkReviver`, `RegExpReviver`, `URLReviver`, `DateReviver`, `DurationReviver`, `BigDecimalReviver`, `FileReviver`, `FormDataReviver`, `URLSearchParamsReviver`, `Uint8ArrayReviver`, `DateTimeUtcReviver`, `TimeZoneOffsetReviver`, `TimeZoneNamedReviver`, `TimeZoneReviver`, `DateTimeZonedReviver`, `JsonReviver`, and `MutableJsonReviver` + - check revivers: `isTrimmedReviver`, `isPatternReviver`, `isStringFiniteReviver`, `isStringBigIntReviver`, `isStringSymbolReviver`, `isUUIDReviver`, `isGUIDReviver`, `isULIDReviver`, `isBase64Reviver`, `isBase64UrlReviver`, `isStartsWithReviver`, `isEndsWithReviver`, `isIncludesReviver`, `isUppercasedReviver`, `isLowercasedReviver`, `isCapitalizedReviver`, `isUncapitalizedReviver`, `isFiniteReviver`, `isGreaterThanReviver`, `isGreaterThanOrEqualToReviver`, `isLessThanReviver`, `isLessThanOrEqualToReviver`, `isBetweenReviver`, `isMultipleOfReviver`, `isIntReviver`, `isDateValidReviver`, `isGreaterThanDateReviver`, `isGreaterThanOrEqualToDateReviver`, `isLessThanDateReviver`, `isLessThanOrEqualToDateReviver`, `isBetweenDateReviver`, `isGreaterThanBigIntReviver`, `isGreaterThanOrEqualToBigIntReviver`, `isLessThanBigIntReviver`, `isLessThanOrEqualToBigIntReviver`, `isBetweenBigIntReviver`, `isMinLengthReviver`, `isMaxLengthReviver`, `isLengthBetweenReviver`, `isMinSizeReviver`, `isMaxSizeReviver`, `isSizeBetweenReviver`, `isMinPropertiesReviver`, `isMaxPropertiesReviver`, `isPropertiesLengthBetweenReviver`, `isPropertyNamesReviver`, and `isUniqueReviver` +- Validate reviver payloads with their `payloadSchema`, and report missing or duplicate reviver identifiers. + +### JSON Schema and code generation + +- Compile JSON Schema from the canonical JSON codec and the encoded-side representation. Custom checks can contribute constraints through `Annotations.Filter.toJsonSchema` without modifying a central metadata registry. +- Import JSON Schema directly as live schemas. The importer now supports shared definitions, aliases, recursion, reference siblings, and definitions that are not reachable from a root. +- Add the named `FromJsonSchemaOptions` type for the importer `onEnter` callback. +- Generate code from live `toCode` annotations on declarations and checks. Compiler callbacks receive generated type parameters or schema dependencies and can emit multiple import declarations. +- Add import artifacts to `CodeDocument` and preserve all explicit definitions during multi-document code generation. +- Reject distinct schemas that declare the same identifier instead of silently merging them or generating suffixed references. + +### Canonical codecs and integrations + +- Preserve schema identifiers, property context, key encodings, and applicable checks while deriving canonical JSON codecs. +- Treat `Schema.Json` and `Schema.MutableJson` as already canonical. JSON validation now rejects sparse arrays, and non-finite numbers decode only from the canonical strings `"Infinity"`, `"-Infinity"`, and `"NaN"` rather than raw non-finite numeric inputs. +- Declarations without `toCodecJson` or `toCodec` now use JSON validation as their fallback instead of silently encoding to `null`. `toCodecJson` callbacks may return `undefined` when a declaration is already canonical. +- Add `Annotations.Declaration.toCodecStringTree`; StringTree derivation now requires a declaration to provide a structural StringTree, JSON, or general codec instead of silently encoding an opaque declaration to `undefined`. +- Update AI structured-output, HTTP schema, HttpApi OpenAPI, and OpenAPI generator integrations to consume the same canonical encoded representation and compiler hooks. Provider-specific structured-output transforms may remove unsupported JSON Schema keywords, while the Effect codec remains the validation authority. + +### Breaking changes + +- Rename the low-level representation constructors: + - `SchemaRepresentation.fromAST` -> `SchemaRepresentation.toRepresentation` + - `SchemaRepresentation.fromASTs` -> `SchemaRepresentation.toRepresentations` +- Replace `SchemaRepresentation.toSchema` with `fromRepresentation`, and add `fromRepresentations` for multi-root documents. Both reconstruction functions require `{ revivers: [...] }`; no default reviver is installed implicitly. +- Remove `SchemaRepresentation.toSchemaDefaultReviver`. Pass the required built-in revivers exported by `Schema`, or custom revivers created with the new constructors. +- Replace `DocumentFromJson` and `MultiDocumentFromJson` with the `toJson` / `fromJson` and `toJsonMultiDocument` / `fromJsonMultiDocument` functions. +- The persisted `Document` and `MultiDocument` format is incompatible with the previous format. Nodes now contain `checks`; encoded literal values, enum values, and property signature names use tagged `{ type, value }` objects while decoded documents expose their native scalar values; declarations no longer contain `encodedSchema`; persisted opaque declarations and leaf filters require a `{ id, payload }` representation identity; and checks no longer contain closed `meta` payloads. Regenerate stored documents from their source schemas with the new API, or migrate their shape before passing them to `fromJson`. +- Replace the generic `Reviver` function type with `DeclarationReviver

`, `FilterReviver

`, `FilterGroupReviver

`, `CheckReviver

`, `Reviver

`, and `AnyReviver`. +- Remove the closed metadata types `StringMeta`, `NumberMeta`, `BigIntMeta`, `ArraysMeta`, `ObjectsMeta`, `DateMeta`, `SizeMeta`, `DeclarationMeta`, and `Meta` from `SchemaRepresentation`. +- Remove the exported representation validation schemas and `PrimitiveTree`: `$PrimitiveTree`, `$Annotations`, `$Null`, `$Undefined`, `$Void`, `$Never`, `$Unknown`, `$Any`, `$StringMeta`, `$String`, `$NumberMeta`, `$Number`, `$Boolean`, `$BigInt`, `$Symbol`, `$LiteralValue`, `$Literal`, `$UniqueSymbol`, `$ObjectKeyword`, `$Enum`, `$TemplateLiteral`, `$Element`, `$Arrays`, `$PropertySignature`, `$IndexSignature`, `$ObjectsMeta`, `$Objects`, `$Union`, `$Reference`, `$DateMeta`, `$SizeMeta`, `$DeclarationMeta`, `$Declaration`, `$Suspend`, `$Representation`, `$Document`, and `$MultiDocument`. +- Replace schema annotations as follows: + - remove `Annotations.Bottom.meta` and `Annotations.Filter.meta` + - remove `Annotations.Declaration.typeConstructor`; use `representation` + - remove `Annotations.Declaration.generation`; use the `toCode` callback + - add `Annotations.Filter.representation`, `toJsonSchema`, and `toCode` + - add `Annotations.Augment.contentSchema` as a JSON-valued annotation + - allow `Annotations.Declaration.toCodecJson` and `toCodecStringTree` to return `undefined` +- Remove the top-level `contentMediaType` and `contentSchema` fields from `SchemaRepresentation.String`. Content metadata is now carried in ordinary annotations, and `contentSchema` is a JSON Schema value rather than a nested Effect representation. +- Remove `Schema.Annotations.BuiltInMetaDefinitions`, `BuiltInMeta`, `MetaDefinitions`, and `Meta`. Custom checks should carry a representation identity and compiler callbacks instead of augmenting the metadata registry. +- `fromJsonSchemaDocument` now returns `Schema.Top` instead of a representation `Document`. `fromJsonSchemaMultiDocument` now returns `SchemaMultiDocument` instead of `MultiDocument`; call `fromSchemaMultiDocument` when a representation multi-document is required. +- `toCodeDocument` now accepts only a live `MultiDocument`; remove its `reviver` option. Reconstruct persisted documents first so revivers can restore runtime compiler callbacks. +- Rename the `generation` field of `Artifact` values for symbols and enums to `code`. Declaration generation no longer has an `Encoded` output, and `importDeclaration` is replaced by `importDeclarations` on callback output. +- Remove the exported `sanitizeJavaScriptIdentifier`, `topologicalSort`, and `TopologicalSort` helpers. +- Negative zero no longer receives special representation handling. Do not rely on preserving its sign across JSON persistence or generated code, where it may be normalized to `0`. +- With `{ errors: "all" }`, structural checks run only after their base array, object, or declaration parses successfully; they are no longer added to an already failing child parse. diff --git a/.changeset/schema-result-combinators.md b/.changeset/pre/schema-result-combinators.md similarity index 100% rename from .changeset/schema-result-combinators.md rename to .changeset/pre/schema-result-combinators.md diff --git a/.changeset/pre/schema-runtime-performance.md b/.changeset/pre/schema-runtime-performance.md new file mode 100644 index 000000000..5d1b32274 --- /dev/null +++ b/.changeset/pre/schema-runtime-performance.md @@ -0,0 +1,47 @@ +--- +"effect": patch +--- + +Improve Schema parsing, schema construction and adapter runtime performance +while preserving current parsing behavior. + +## Runtime performance + +The `effect@beta`, Valibot and Zod timing cases from +[`open-circle/schema-benchmarks`](https://github.com/open-circle/schema-benchmarks) +were reproduced as a dedicated `runtimeperf` suite. The table includes every +case exposed by each upstream adapter; `—` means that the adapter does not +provide that benchmark. + +Effect `main` (`45e781088`) and the branch based on `d775bf4b2` were compared +with five paired processes per case, 150 ms measurement time and 50 ms warmup. +The two initially inconclusive Effect cases were repeated with 15 paired +processes, 500 ms measurement time and 150 ms warmup. Valibot and Zod values +use five processes, 300 ms measurement time and 100 ms warmup. Environment: +Node `v24.12.0`, macOS arm64, Apple M3. + +Zod parsing uses `safeParse` with `{ jitless: true }`; its Standard Schema and +codec cases use the corresponding native adapter APIs. All values are median +microseconds per operation (`µs/op`), lower is better. Cross-library values are +diagnostic because they are independent rather than paired measurements. + +| Scenario | Effect `main` | Effect branch | Valibot | Zod 4 | Delta | 95% CI | Classification | +| ------------------------------------ | ------------: | ------------: | ---------: | ---------: | ------: | ------------------ | -------------- | +| Initialize schema | 137.28 | 118.23 | **40.24** | 318.56 | -12.69% | -21.02% to -5.35% | improvement | +| Initialize schema and decoder | 144.81 | **130.50** | — | — | -10.88% | -14.22% to -3.29% | improvement | +| Validate valid product | 8.478 | **5.415** | 5.63 | — | -35.18% | -41.65% to -32.83% | improvement | +| Validate invalid product | 1.516 | 1.348 | **0.2431** | — | -11.59% | -13.81% to -6.31% | improvement | +| Parse valid product, all errors | 8.360 | 5.366 | **5.22** | 7.16 | -36.28% | -54.41% to -31.67% | improvement | +| Parse invalid product, all errors | 11.302 | **9.100** | 15.70 | 41.58 | -19.42% | -21.32% to -13.12% | improvement | +| Parse valid product, first error | 8.201 | **5.294** | 5.37 | — | -35.44% | -37.75% to -34.59% | improvement | +| Parse invalid product, first error | 1.510 | 1.352 | **0.2572** | — | -10.51% | -12.52% to -9.53% | improvement | +| Standard Schema valid, all errors | 9.284 | 5.935 | 5.35 | **3.83** | -35.96% | -53.29% to -33.49% | improvement | +| Standard Schema invalid, all errors | 16.718 | **15.203** | 16.51 | 32.85 | -11.31% | -13.97% to -7.65% | improvement | +| Standard Schema valid, first error | 8.889 | **5.843** | — | — | -34.17% | -35.13% to -33.94% | improvement | +| Standard Schema invalid, first error | 2.435 | **2.244** | — | — | -8.44% | -12.76% to -4.82% | improvement | +| Typed codec encode | 0.4692 | 0.3420 | — | **0.0405** | -27.60% | -32.35% to -22.50% | improvement | +| Typed codec decode | 0.5191 | 0.3762 | — | **0.0463** | -27.19% | -34.75% to -22.71% | improvement | +| Unknown codec encode | 0.4910 | **0.3472** | — | — | -28.58% | -30.42% to -27.59% | improvement | +| Unknown codec decode | 0.5061 | **0.3637** | — | — | -29.26% | -29.82% to -21.70% | improvement | + +Overall Effect classification: 16 improvements and no regressions. diff --git a/.changeset/schema-struct-simplify.md b/.changeset/pre/schema-struct-simplify.md similarity index 100% rename from .changeset/schema-struct-simplify.md rename to .changeset/pre/schema-struct-simplify.md diff --git a/.changeset/pre/schema-union-type-derivation.md b/.changeset/pre/schema-union-type-derivation.md new file mode 100644 index 000000000..a7ac4dac1 --- /dev/null +++ b/.changeset/pre/schema-union-type-derivation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Precompile union formatters and equivalences, select transformed union members using their decoded type, and allow deriving an equivalence for `Never`. diff --git a/.changeset/pre/scope-persisted-queue-ids.md b/.changeset/pre/scope-persisted-queue-ids.md new file mode 100644 index 000000000..ea6d42d1b --- /dev/null +++ b/.changeset/pre/scope-persisted-queue-ids.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Scope custom persisted queue ID deduplication to each named queue. diff --git a/.changeset/pre/secure-eventlog-identities.md b/.changeset/pre/secure-eventlog-identities.md new file mode 100644 index 000000000..b7d1d9ad3 --- /dev/null +++ b/.changeset/pre/secure-eventlog-identities.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Bind event-log read and write requests to the identities authenticated on their RPC connection. diff --git a/.changeset/semantic-matching.md b/.changeset/pre/semantic-matching.md similarity index 100% rename from .changeset/semantic-matching.md rename to .changeset/pre/semantic-matching.md diff --git a/.changeset/seven-mugs-marry.md b/.changeset/pre/seven-mugs-marry.md similarity index 100% rename from .changeset/seven-mugs-marry.md rename to .changeset/pre/seven-mugs-marry.md diff --git a/.changeset/pre/seven-poems-divide.md b/.changeset/pre/seven-poems-divide.md new file mode 100644 index 000000000..8d9caf7f4 --- /dev/null +++ b/.changeset/pre/seven-poems-divide.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor populated variables before dotenv expansion defaults in `ConfigProvider`. diff --git a/.changeset/shaggy-birds-stay.md b/.changeset/pre/shaggy-birds-stay.md similarity index 100% rename from .changeset/shaggy-birds-stay.md rename to .changeset/pre/shaggy-birds-stay.md diff --git a/.changeset/shaggy-cities-push.md b/.changeset/pre/shaggy-cities-push.md similarity index 100% rename from .changeset/shaggy-cities-push.md rename to .changeset/pre/shaggy-cities-push.md diff --git a/.changeset/shaggy-numbers-accept.md b/.changeset/pre/shaggy-numbers-accept.md similarity index 100% rename from .changeset/shaggy-numbers-accept.md rename to .changeset/pre/shaggy-numbers-accept.md diff --git a/.changeset/pre/shaky-beans-throw.md b/.changeset/pre/shaky-beans-throw.md new file mode 100644 index 000000000..e0fa124dd --- /dev/null +++ b/.changeset/pre/shaky-beans-throw.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +accept UrlParams.Input in some UrlParams apis diff --git a/.changeset/sharp-emus-applaud.md b/.changeset/pre/sharp-emus-applaud.md similarity index 100% rename from .changeset/sharp-emus-applaud.md rename to .changeset/pre/sharp-emus-applaud.md diff --git a/.changeset/sharp-goats-wink.md b/.changeset/pre/sharp-goats-wink.md similarity index 100% rename from .changeset/sharp-goats-wink.md rename to .changeset/pre/sharp-goats-wink.md diff --git a/.changeset/sharp-pandas-care.md b/.changeset/pre/sharp-pandas-care.md similarity index 100% rename from .changeset/sharp-pandas-care.md rename to .changeset/pre/sharp-pandas-care.md diff --git a/.changeset/sharp-peas-march.md b/.changeset/pre/sharp-peas-march.md similarity index 100% rename from .changeset/sharp-peas-march.md rename to .changeset/pre/sharp-peas-march.md diff --git a/.changeset/sharp-rules-draw.md b/.changeset/pre/sharp-rules-draw.md similarity index 100% rename from .changeset/sharp-rules-draw.md rename to .changeset/pre/sharp-rules-draw.md diff --git a/.changeset/sharp-singers-sort.md b/.changeset/pre/sharp-singers-sort.md similarity index 100% rename from .changeset/sharp-singers-sort.md rename to .changeset/pre/sharp-singers-sort.md diff --git a/.changeset/shiny-trains-hug.md b/.changeset/pre/shiny-trains-hug.md similarity index 100% rename from .changeset/shiny-trains-hug.md rename to .changeset/pre/shiny-trains-hug.md diff --git a/.changeset/short-cows-relate.md b/.changeset/pre/short-cows-relate.md similarity index 100% rename from .changeset/short-cows-relate.md rename to .changeset/pre/short-cows-relate.md diff --git a/.changeset/short-foxes-admire.md b/.changeset/pre/short-foxes-admire.md similarity index 100% rename from .changeset/short-foxes-admire.md rename to .changeset/pre/short-foxes-admire.md diff --git a/.changeset/short-stamps-throw.md b/.changeset/pre/short-stamps-throw.md similarity index 100% rename from .changeset/short-stamps-throw.md rename to .changeset/pre/short-stamps-throw.md diff --git a/.changeset/shy-cycles-flow.md b/.changeset/pre/shy-cycles-flow.md similarity index 100% rename from .changeset/shy-cycles-flow.md rename to .changeset/pre/shy-cycles-flow.md diff --git a/.changeset/shy-geckos-sniff.md b/.changeset/pre/shy-geckos-sniff.md similarity index 100% rename from .changeset/shy-geckos-sniff.md rename to .changeset/pre/shy-geckos-sniff.md diff --git a/.changeset/silent-geckos-matter.md b/.changeset/pre/silent-geckos-matter.md similarity index 100% rename from .changeset/silent-geckos-matter.md rename to .changeset/pre/silent-geckos-matter.md diff --git a/.changeset/silent-needles-design.md b/.changeset/pre/silent-needles-design.md similarity index 100% rename from .changeset/silent-needles-design.md rename to .changeset/pre/silent-needles-design.md diff --git a/.changeset/silent-plants-matter.md b/.changeset/pre/silent-plants-matter.md similarity index 100% rename from .changeset/silent-plants-matter.md rename to .changeset/pre/silent-plants-matter.md diff --git a/.changeset/silent-spoons-stare.md b/.changeset/pre/silent-spoons-stare.md similarity index 100% rename from .changeset/silent-spoons-stare.md rename to .changeset/pre/silent-spoons-stare.md diff --git a/.changeset/pre/silly-dodos-update.md b/.changeset/pre/silly-dodos-update.md new file mode 100644 index 000000000..e90a8a07a --- /dev/null +++ b/.changeset/pre/silly-dodos-update.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `SynchronizedRef.getAndUpdateSome` to update its backing ref. diff --git a/.changeset/silly-loops-tickle.md b/.changeset/pre/silly-loops-tickle.md similarity index 100% rename from .changeset/silly-loops-tickle.md rename to .changeset/pre/silly-loops-tickle.md diff --git a/.changeset/silver-bulk-indexeddb.md b/.changeset/pre/silver-bulk-indexeddb.md similarity index 100% rename from .changeset/silver-bulk-indexeddb.md rename to .changeset/pre/silver-bulk-indexeddb.md diff --git a/.changeset/silver-emus-smoke.md b/.changeset/pre/silver-emus-smoke.md similarity index 100% rename from .changeset/silver-emus-smoke.md rename to .changeset/pre/silver-emus-smoke.md diff --git a/.changeset/silver-kings-poke.md b/.changeset/pre/silver-kings-poke.md similarity index 100% rename from .changeset/silver-kings-poke.md rename to .changeset/pre/silver-kings-poke.md diff --git a/.changeset/silver-snails-sqlite.md b/.changeset/pre/silver-snails-sqlite.md similarity index 100% rename from .changeset/silver-snails-sqlite.md rename to .changeset/pre/silver-snails-sqlite.md diff --git a/.changeset/silver-wings-watch.md b/.changeset/pre/silver-wings-watch.md similarity index 100% rename from .changeset/silver-wings-watch.md rename to .changeset/pre/silver-wings-watch.md diff --git a/.changeset/pre/simplify-optic-composition.md b/.changeset/pre/simplify-optic-composition.md new file mode 100644 index 000000000..1ed7423a9 --- /dev/null +++ b/.changeset/pre/simplify-optic-composition.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Fix three issues in the public `Optic` API: + +- Composed `Iso` and `Prism` setters no longer try to read a source value before writing. +- Calling `notUndefined` on an `Optional` now returns an `Optional`, because writing can still fail. +- The internal `node` property is no longer exposed by public optic types. diff --git a/.changeset/six-cups-taste.md b/.changeset/pre/six-cups-taste.md similarity index 100% rename from .changeset/six-cups-taste.md rename to .changeset/pre/six-cups-taste.md diff --git a/.changeset/pre/six-pumas-take.md b/.changeset/pre/six-pumas-take.md new file mode 100644 index 000000000..a58d9a049 --- /dev/null +++ b/.changeset/pre/six-pumas-take.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +add advanced graph set operations for deriving related graph structures + +- `Graph.complement` - complement over the existing node set, adding missing edges between distinct nodes +- `Graph.neighborhood` - induced subgraph containing nodes within a radius of a node +- `Graph.sum` - disjoint union of two graphs without merging equal node data diff --git a/.changeset/sixty-mails-shout.md b/.changeset/pre/sixty-mails-shout.md similarity index 100% rename from .changeset/sixty-mails-shout.md rename to .changeset/pre/sixty-mails-shout.md diff --git a/.changeset/sixty-socks-yell.md b/.changeset/pre/sixty-socks-yell.md similarity index 100% rename from .changeset/sixty-socks-yell.md rename to .changeset/pre/sixty-socks-yell.md diff --git a/.changeset/slick-signs-wish.md b/.changeset/pre/slick-signs-wish.md similarity index 100% rename from .changeset/slick-signs-wish.md rename to .changeset/pre/slick-signs-wish.md diff --git a/.changeset/slick-toes-rush.md b/.changeset/pre/slick-toes-rush.md similarity index 100% rename from .changeset/slick-toes-rush.md rename to .changeset/pre/slick-toes-rush.md diff --git a/.changeset/pre/slimy-melons-admire.md b/.changeset/pre/slimy-melons-admire.md new file mode 100644 index 000000000..7cc69a0af --- /dev/null +++ b/.changeset/pre/slimy-melons-admire.md @@ -0,0 +1,33 @@ +--- +"@effect/sql-sqlite-react-native": patch +"@effect/openapi-generator": patch +"@effect/platform-node-shared": patch +"@effect/ai-openai-compat": patch +"@effect/platform-browser": patch +"@effect/sql-sqlite-node": patch +"@effect/sql-sqlite-wasm": patch +"@effect/sql-clickhouse": patch +"@effect/sql-sqlite-bun": patch +"@effect/ai-openrouter": patch +"@effect/opentelemetry": patch +"@effect/platform-deno": patch +"@effect/platform-node": patch +"@effect/sql-sqlite-do": patch +"@effect/ai-anthropic": patch +"@effect/platform-bun": patch +"@effect/docgen": patch +"@effect/atom-react": patch +"@effect/atom-solid": patch +"@effect/sql-libsql": patch +"@effect/sql-mysql2": patch +"@effect/sql-pglite": patch +"@effect/ai-openai": patch +"@effect/sql-mssql": patch +"@effect/atom-vue": patch +"effect": patch +"@effect/sql-d1": patch +"@effect/sql-pg": patch +"@effect/vitest": patch +--- + +Removed explicit ./index entrypoints diff --git a/.changeset/slimy-planets-divide.md b/.changeset/pre/slimy-planets-divide.md similarity index 100% rename from .changeset/slimy-planets-divide.md rename to .changeset/pre/slimy-planets-divide.md diff --git a/.changeset/slimy-turtles-juggle.md b/.changeset/pre/slimy-turtles-juggle.md similarity index 100% rename from .changeset/slimy-turtles-juggle.md rename to .changeset/pre/slimy-turtles-juggle.md diff --git a/.changeset/slow-beans-battle.md b/.changeset/pre/slow-beans-battle.md similarity index 100% rename from .changeset/slow-beans-battle.md rename to .changeset/pre/slow-beans-battle.md diff --git a/.changeset/slow-berries-enjoy.md b/.changeset/pre/slow-berries-enjoy.md similarity index 100% rename from .changeset/slow-berries-enjoy.md rename to .changeset/pre/slow-berries-enjoy.md diff --git a/.changeset/pre/slow-entities-register.md b/.changeset/pre/slow-entities-register.md new file mode 100644 index 000000000..678da84a1 --- /dev/null +++ b/.changeset/pre/slow-entities-register.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Hold persisted cluster messages while entity layers are still registering, while retaining a bounded failure when +registration never begins. diff --git a/.changeset/pre/slow-spiders-refresh.md b/.changeset/pre/slow-spiders-refresh.md new file mode 100644 index 000000000..109878758 --- /dev/null +++ b/.changeset/pre/slow-spiders-refresh.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent SQL runner lock refreshes from hanging when reserved connections become unresponsive. diff --git a/.changeset/small-bugs-hunt.md b/.changeset/pre/small-bugs-hunt.md similarity index 100% rename from .changeset/small-bugs-hunt.md rename to .changeset/pre/small-bugs-hunt.md diff --git a/.changeset/small-crabs-care.md b/.changeset/pre/small-crabs-care.md similarity index 100% rename from .changeset/small-crabs-care.md rename to .changeset/pre/small-crabs-care.md diff --git a/.changeset/pre/small-pandas-cache.md b/.changeset/pre/small-pandas-cache.md new file mode 100644 index 000000000..b1e5b3de5 --- /dev/null +++ b/.changeset/pre/small-pandas-cache.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Preserve OpenAI Responses API cache write token usage in language model responses. diff --git a/.changeset/small-pets-sit.md b/.changeset/pre/small-pets-sit.md similarity index 100% rename from .changeset/small-pets-sit.md rename to .changeset/pre/small-pets-sit.md diff --git a/.changeset/smart-ducks-jump.md b/.changeset/pre/smart-ducks-jump.md similarity index 100% rename from .changeset/smart-ducks-jump.md rename to .changeset/pre/smart-ducks-jump.md diff --git a/.changeset/smart-pillows-buy.md b/.changeset/pre/smart-pillows-buy.md similarity index 100% rename from .changeset/smart-pillows-buy.md rename to .changeset/pre/smart-pillows-buy.md diff --git a/.changeset/smart-timers-fly.md b/.changeset/pre/smart-timers-fly.md similarity index 100% rename from .changeset/smart-timers-fly.md rename to .changeset/pre/smart-timers-fly.md diff --git a/.changeset/smart-tips-sort.md b/.changeset/pre/smart-tips-sort.md similarity index 100% rename from .changeset/smart-tips-sort.md rename to .changeset/pre/smart-tips-sort.md diff --git a/.changeset/pre/social-hoops-knock.md b/.changeset/pre/social-hoops-knock.md new file mode 100644 index 000000000..c91f7ae2e --- /dev/null +++ b/.changeset/pre/social-hoops-knock.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Cleanup internals of CLI package diff --git a/.changeset/social-pumas-prove.md b/.changeset/pre/social-pumas-prove.md similarity index 100% rename from .changeset/social-pumas-prove.md rename to .changeset/pre/social-pumas-prove.md diff --git a/.changeset/soft-comics-wink.md b/.changeset/pre/soft-comics-wink.md similarity index 100% rename from .changeset/soft-comics-wink.md rename to .changeset/pre/soft-comics-wink.md diff --git a/.changeset/soft-delete-sqlmodel.md b/.changeset/pre/soft-delete-sqlmodel.md similarity index 100% rename from .changeset/soft-delete-sqlmodel.md rename to .changeset/pre/soft-delete-sqlmodel.md diff --git a/.changeset/soft-seals-allow.md b/.changeset/pre/soft-seals-allow.md similarity index 100% rename from .changeset/soft-seals-allow.md rename to .changeset/pre/soft-seals-allow.md diff --git a/.changeset/pre/soft-sockets-write.md b/.changeset/pre/soft-sockets-write.md new file mode 100644 index 000000000..c0106d10b --- /dev/null +++ b/.changeset/pre/soft-sockets-write.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Map WebSocket send exceptions and transform stream write rejections to typed `SocketError` failures. diff --git a/.changeset/solid-cougars-attack.md b/.changeset/pre/solid-cougars-attack.md similarity index 100% rename from .changeset/solid-cougars-attack.md rename to .changeset/pre/solid-cougars-attack.md diff --git a/.changeset/solid-doors-ring.md b/.changeset/pre/solid-doors-ring.md similarity index 100% rename from .changeset/solid-doors-ring.md rename to .changeset/pre/solid-doors-ring.md diff --git a/.changeset/solid-items-tease.md b/.changeset/pre/solid-items-tease.md similarity index 100% rename from .changeset/solid-items-tease.md rename to .changeset/pre/solid-items-tease.md diff --git a/.changeset/solid-towns-smoke.md b/.changeset/pre/solid-towns-smoke.md similarity index 100% rename from .changeset/solid-towns-smoke.md rename to .changeset/pre/solid-towns-smoke.md diff --git a/.changeset/pre/sour-bees-sleep.md b/.changeset/pre/sour-bees-sleep.md new file mode 100644 index 000000000..6c31527a9 --- /dev/null +++ b/.changeset/pre/sour-bees-sleep.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Default empty Server-Sent Event types to `message`. diff --git a/.changeset/sour-canyons-rescue.md b/.changeset/pre/sour-canyons-rescue.md similarity index 100% rename from .changeset/sour-canyons-rescue.md rename to .changeset/pre/sour-canyons-rescue.md diff --git a/.changeset/sparkly-bears-act.md b/.changeset/pre/sparkly-bears-act.md similarity index 100% rename from .changeset/sparkly-bears-act.md rename to .changeset/pre/sparkly-bears-act.md diff --git a/.changeset/sparkly-coins-sit.md b/.changeset/pre/sparkly-coins-sit.md similarity index 100% rename from .changeset/sparkly-coins-sit.md rename to .changeset/pre/sparkly-coins-sit.md diff --git a/.changeset/pre/spicy-doors-unlist.md b/.changeset/pre/spicy-doors-unlist.md new file mode 100644 index 000000000..fc4907aa6 --- /dev/null +++ b/.changeset/pre/spicy-doors-unlist.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Rename `Command.withHidden` to `Command.unlisted`, along with the `hidden` command property which is now `unlisted`. diff --git a/.changeset/pre/split-clock-semantics.md b/.changeset/pre/split-clock-semantics.md new file mode 100644 index 000000000..1d2390cbb --- /dev/null +++ b/.changeset/pre/split-clock-semantics.md @@ -0,0 +1,7 @@ +--- +"effect": minor +--- + +Separate wall-clock timestamps from monotonic elapsed time. + +`Clock.Clock` now requires `monotonicTimeNanosUnsafe()` and `monotonicTimeNanos` for measuring elapsed time. Custom `Clock` implementations must provide both members. The live clock's `currentTimeNanos` now re-anchors its high-resolution Unix wall-clock timestamp when it drifts from `Date.now()`, while `Effect.timed`, duration metric tracking, and `Sink.withDuration` use monotonic time so wall-clock corrections do not distort elapsed durations. diff --git a/.changeset/spotty-comics-fry.md b/.changeset/pre/spotty-comics-fry.md similarity index 100% rename from .changeset/spotty-comics-fry.md rename to .changeset/pre/spotty-comics-fry.md diff --git a/.changeset/pre/sql-begin-failure-typed.md b/.changeset/pre/sql-begin-failure-typed.md new file mode 100644 index 000000000..adfdb341a --- /dev/null +++ b/.changeset/pre/sql-begin-failure-typed.md @@ -0,0 +1,16 @@ +--- +"effect": patch +--- + +Propagate a failed `BEGIN` or `SAVEPOINT` from `SqlClient.withTransaction` as a typed `SqlError`. + +`makeWithTransaction` wrapped the `begin` step together with the transaction body, so a +failed `BEGIN` took the rollback branch. No transaction was active at that point, the +`ROLLBACK` failed, and its `Effect.orDie` wrapper replaced the original typed error with a +defect (`cannot rollback - no transaction is active`). Callers could no longer classify the +failure as retryable. The path became reachable when the sqlite client started using +`BEGIN IMMEDIATE`, which acquires a write lock and can fail with `SQLITE_BUSY`. + +Commit and rollback now run only after `begin` or `savepoint` succeeds. A failed `begin` or +`savepoint` fails with its original `SqlError`, leaves the wrapped effect unexecuted, and +still closes the acquired connection scope. diff --git a/.changeset/sql-migrator-mjs-mts.md b/.changeset/pre/sql-migrator-mjs-mts.md similarity index 100% rename from .changeset/sql-migrator-mjs-mts.md rename to .changeset/pre/sql-migrator-mjs-mts.md diff --git a/.changeset/sqlite-bun-prepare-error-channel.md b/.changeset/pre/sqlite-bun-prepare-error-channel.md similarity index 100% rename from .changeset/sqlite-bun-prepare-error-channel.md rename to .changeset/pre/sqlite-bun-prepare-error-channel.md diff --git a/.changeset/pre/sqlite-client-locking-defaults.md b/.changeset/pre/sqlite-client-locking-defaults.md new file mode 100644 index 000000000..ee01605b9 --- /dev/null +++ b/.changeset/pre/sqlite-client-locking-defaults.md @@ -0,0 +1,6 @@ +--- +"@effect/sql-sqlite-bun": patch +"@effect/sql-sqlite-node": patch +--- + +Use a configurable five-second busy timeout and immediate transactions by default to avoid SQLite lock failures under concurrent access. Busy waits can block the event loop, while immediate transactions serialize behind other writers. diff --git a/.changeset/sqlite-do-durable-object-transactions.md b/.changeset/pre/sqlite-do-durable-object-transactions.md similarity index 100% rename from .changeset/sqlite-do-durable-object-transactions.md rename to .changeset/pre/sqlite-do-durable-object-transactions.md diff --git a/.changeset/pre/stable-graph-traversal-config.md b/.changeset/pre/stable-graph-traversal-config.md new file mode 100644 index 000000000..a4663f38a --- /dev/null +++ b/.changeset/pre/stable-graph-traversal-config.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Validate Graph traversal radii, isolate traversal start configuration, and prioritize the first supplied DFS root. diff --git a/.changeset/stale-dots-tell.md b/.changeset/pre/stale-dots-tell.md similarity index 100% rename from .changeset/stale-dots-tell.md rename to .changeset/pre/stale-dots-tell.md diff --git a/.changeset/pre/stale-graph-traversal-skips.md b/.changeset/pre/stale-graph-traversal-skips.md new file mode 100644 index 000000000..6bf269405 --- /dev/null +++ b/.changeset/pre/stale-graph-traversal-skips.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix Graph BFS, topological sort, and DFS postorder iterators to skip nodes removed from a MutableGraph without recursive self-calls. diff --git a/.changeset/pre/stale-laws-do.md b/.changeset/pre/stale-laws-do.md new file mode 100644 index 000000000..f114fbec1 --- /dev/null +++ b/.changeset/pre/stale-laws-do.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +optimize bun stream reading diff --git a/.changeset/pre/stale-snakes-know.md b/.changeset/pre/stale-snakes-know.md new file mode 100644 index 000000000..a6272595b --- /dev/null +++ b/.changeset/pre/stale-snakes-know.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +update dependencies diff --git a/.changeset/pre/standard-schema-module.md b/.changeset/pre/standard-schema-module.md new file mode 100644 index 000000000..b3ac3eb10 --- /dev/null +++ b/.changeset/pre/standard-schema-module.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add a public `StandardSchema` module containing the vendored Standard Schema V1 specification and remove the direct dependency on `@standard-schema/spec`. diff --git a/.changeset/strict-areas-end.md b/.changeset/pre/strict-areas-end.md similarity index 100% rename from .changeset/strict-areas-end.md rename to .changeset/pre/strict-areas-end.md diff --git a/.changeset/strict-buckets-hug.md b/.changeset/pre/strict-buckets-hug.md similarity index 100% rename from .changeset/strict-buckets-hug.md rename to .changeset/pre/strict-buckets-hug.md diff --git a/.changeset/strip-resolved-approvals.md b/.changeset/pre/strip-resolved-approvals.md similarity index 100% rename from .changeset/strip-resolved-approvals.md rename to .changeset/pre/strip-resolved-approvals.md diff --git a/.changeset/strong-balloons-tickle.md b/.changeset/pre/strong-balloons-tickle.md similarity index 100% rename from .changeset/strong-balloons-tickle.md rename to .changeset/pre/strong-balloons-tickle.md diff --git a/.changeset/strong-bees-queue.md b/.changeset/pre/strong-bees-queue.md similarity index 100% rename from .changeset/strong-bees-queue.md rename to .changeset/pre/strong-bees-queue.md diff --git a/.changeset/pre/strong-insects-film.md b/.changeset/pre/strong-insects-film.md new file mode 100644 index 000000000..075c42f85 --- /dev/null +++ b/.changeset/pre/strong-insects-film.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +ensure WithTransaction wraps entire rpc handler diff --git a/.changeset/struct-record.md b/.changeset/pre/struct-record.md similarity index 100% rename from .changeset/struct-record.md rename to .changeset/pre/struct-record.md diff --git a/.changeset/sunny-ads-hang.md b/.changeset/pre/sunny-ads-hang.md similarity index 100% rename from .changeset/sunny-ads-hang.md rename to .changeset/pre/sunny-ads-hang.md diff --git a/.changeset/sunny-bikes-sleep.md b/.changeset/pre/sunny-bikes-sleep.md similarity index 100% rename from .changeset/sunny-bikes-sleep.md rename to .changeset/pre/sunny-bikes-sleep.md diff --git a/.changeset/sunny-rooms-invent.md b/.changeset/pre/sunny-rooms-invent.md similarity index 100% rename from .changeset/sunny-rooms-invent.md rename to .changeset/pre/sunny-rooms-invent.md diff --git a/.changeset/sweet-donuts-bet.md b/.changeset/pre/sweet-donuts-bet.md similarity index 100% rename from .changeset/sweet-donuts-bet.md rename to .changeset/pre/sweet-donuts-bet.md diff --git a/.changeset/sweet-hotels-give.md b/.changeset/pre/sweet-hotels-give.md similarity index 100% rename from .changeset/sweet-hotels-give.md rename to .changeset/pre/sweet-hotels-give.md diff --git a/.changeset/pre/sweet-lizards-sing.md b/.changeset/pre/sweet-lizards-sing.md new file mode 100644 index 000000000..12248afd9 --- /dev/null +++ b/.changeset/pre/sweet-lizards-sing.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Fix `NodeStream.toString` registering a duplicate `error` event listener. diff --git a/.changeset/pre/sweet-schedules-matter.md b/.changeset/pre/sweet-schedules-matter.md new file mode 100644 index 000000000..dcc8d4a93 --- /dev/null +++ b/.changeset/pre/sweet-schedules-matter.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Update `Schedule.addDelay` and `Schedule.modifyDelay` to receive full schedule metadata instead of separate output and delay arguments. diff --git a/.changeset/sweet-views-learn.md b/.changeset/pre/sweet-views-learn.md similarity index 100% rename from .changeset/sweet-views-learn.md rename to .changeset/pre/sweet-views-learn.md diff --git a/.changeset/pre/swift-geese-count.md b/.changeset/pre/swift-geese-count.md new file mode 100644 index 000000000..352cd448e --- /dev/null +++ b/.changeset/pre/swift-geese-count.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the return type of `Channel.runCount` to expose its numeric result. diff --git a/.changeset/pre/swift-pandas-parse.md b/.changeset/pre/swift-pandas-parse.md new file mode 100644 index 000000000..6bbf259a5 --- /dev/null +++ b/.changeset/pre/swift-pandas-parse.md @@ -0,0 +1,7 @@ +--- +"effect": patch +"@effect/platform-browser": patch +"@effect/platform-node": patch +--- + +Vendor the multipart parser as `effect/unstable/http/MultipartParser`, add the Node.js adapter at `@effect/platform-node/NodeMultipartParser`, and remove the external `multipasta` dependency. diff --git a/.changeset/swift-spiders-unpack.md b/.changeset/pre/swift-spiders-unpack.md similarity index 100% rename from .changeset/swift-spiders-unpack.md rename to .changeset/pre/swift-spiders-unpack.md diff --git a/.changeset/swift-symbols-stand.md b/.changeset/pre/swift-symbols-stand.md similarity index 100% rename from .changeset/swift-symbols-stand.md rename to .changeset/pre/swift-symbols-stand.md diff --git a/.changeset/tagged-error-class-optional-empty-props.md b/.changeset/pre/tagged-error-class-optional-empty-props.md similarity index 100% rename from .changeset/tagged-error-class-optional-empty-props.md rename to .changeset/pre/tagged-error-class-optional-empty-props.md diff --git a/.changeset/tall-hairs-return.md b/.changeset/pre/tall-hairs-return.md similarity index 100% rename from .changeset/tall-hairs-return.md rename to .changeset/pre/tall-hairs-return.md diff --git a/.changeset/pre/tall-ideas-fix.md b/.changeset/pre/tall-ideas-fix.md new file mode 100644 index 000000000..0cf7925e2 --- /dev/null +++ b/.changeset/pre/tall-ideas-fix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Don’t create a table when it’s not needed diff --git a/.changeset/tall-mails-listen.md b/.changeset/pre/tall-mails-listen.md similarity index 100% rename from .changeset/tall-mails-listen.md rename to .changeset/pre/tall-mails-listen.md diff --git a/.changeset/pre/tall-melons-shave.md b/.changeset/pre/tall-melons-shave.md new file mode 100644 index 000000000..cdb764935 --- /dev/null +++ b/.changeset/pre/tall-melons-shave.md @@ -0,0 +1,9 @@ +--- +"@effect/sql-pg": patch +--- + +Add low-level PostgreSQL protocol, binary type, and authentication codecs to `@effect/sql-pg`. + +`PgProtocol` encodes protocol 3.0 messages and incrementally parses backend frames. Its stateful parser throws terminal errors. `PgTypes` handles binary scalar and one-dimensional array OIDs; its public codecs return typed `Result` failures, while parser field readers use an internal throwing fast path. `PgAuth` implements MD5 and SCRAM-SHA-256 with typed `Result` failures. + +Encoded frames and decoded byte fields are stable views over internal buffers. Copy data that must outlive its message. `PgClient` remains unchanged and still uses `pg` at runtime. diff --git a/.changeset/tall-queens-cheer.md b/.changeset/pre/tall-queens-cheer.md similarity index 100% rename from .changeset/tall-queens-cheer.md rename to .changeset/pre/tall-queens-cheer.md diff --git a/.changeset/tall-wombats-wave.md b/.changeset/pre/tall-wombats-wave.md similarity index 100% rename from .changeset/tall-wombats-wave.md rename to .changeset/pre/tall-wombats-wave.md diff --git a/.changeset/pre/tame-json-schema-intersections.md b/.changeset/pre/tame-json-schema-intersections.md new file mode 100644 index 000000000..49d9af01c --- /dev/null +++ b/.changeset/pre/tame-json-schema-intersections.md @@ -0,0 +1,14 @@ +--- +"effect": patch +--- + +Fix JSON Schema imports: + +- Type-specific keywords no longer imply a type. For example, `minLength` validates strings without rejecting + non-string values. +- Constraints next to `const`, `enum`, and `$ref` are now applied instead of being ignored. +- Disjoint and linear union intersections are imported without a Cartesian expansion. Other overlapping union + intersections fail with an explicit error. +- References to definitions without unions no longer make otherwise linear intersections fail. +- Imported `oneOf` schemas remain `oneOf` when exported again. +- `minItems` is preserved when `prefixItems` does not fully enforce it. diff --git a/.changeset/tangy-colts-lose.md b/.changeset/pre/tangy-colts-lose.md similarity index 100% rename from .changeset/tangy-colts-lose.md rename to .changeset/pre/tangy-colts-lose.md diff --git a/.changeset/pre/tangy-plants-run.md b/.changeset/pre/tangy-plants-run.md new file mode 100644 index 000000000..111cd174b --- /dev/null +++ b/.changeset/pre/tangy-plants-run.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +only interrupt cache lookup when all awaiters are gone diff --git a/.changeset/tasty-comics-send.md b/.changeset/pre/tasty-comics-send.md similarity index 100% rename from .changeset/tasty-comics-send.md rename to .changeset/pre/tasty-comics-send.md diff --git a/.changeset/pre/tasty-moments-post.md b/.changeset/pre/tasty-moments-post.md new file mode 100644 index 000000000..8a2d4e14d --- /dev/null +++ b/.changeset/pre/tasty-moments-post.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +add Effect.setContext for fully replacing the fiber context diff --git a/.changeset/ten-kings-fry.md b/.changeset/pre/ten-kings-fry.md similarity index 100% rename from .changeset/ten-kings-fry.md rename to .changeset/pre/ten-kings-fry.md diff --git a/.changeset/pre/tender-deserts-pull.md b/.changeset/pre/tender-deserts-pull.md new file mode 100644 index 000000000..c7fda450b --- /dev/null +++ b/.changeset/pre/tender-deserts-pull.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject truncated MessagePack frames at the end of a stream. diff --git a/.changeset/pre/tender-files-complete.md b/.changeset/pre/tender-files-complete.md new file mode 100644 index 000000000..9b9210b4a --- /dev/null +++ b/.changeset/pre/tender-files-complete.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve file and directory semantics in CLI completion descriptors. diff --git a/.changeset/pre/tender-points-sleep.md b/.changeset/pre/tender-points-sleep.md new file mode 100644 index 000000000..3206a87c2 --- /dev/null +++ b/.changeset/pre/tender-points-sleep.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Generate unique persisted paths for multipart files with duplicate filenames. diff --git a/.changeset/pre/terminate-openai-failed-streams.md b/.changeset/pre/terminate-openai-failed-streams.md new file mode 100644 index 000000000..66599bb5f --- /dev/null +++ b/.changeset/pre/terminate-openai-failed-streams.md @@ -0,0 +1,5 @@ +--- +"@effect/ai-openai": patch +--- + +Terminate OpenAI HTTP and WebSocket response streams when a `response.failed` event arrives. diff --git a/.changeset/pre/theme-cli-prompts.md b/.changeset/pre/theme-cli-prompts.md new file mode 100644 index 000000000..45ea5758b --- /dev/null +++ b/.changeset/pre/theme-cli-prompts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Replace per-prompt prefix options with a context-based theme for CLI prompt symbols and colors. diff --git a/.changeset/thick-pandas-wait.md b/.changeset/pre/thick-pandas-wait.md similarity index 100% rename from .changeset/thick-pandas-wait.md rename to .changeset/pre/thick-pandas-wait.md diff --git a/.changeset/thin-ducks-wonder.md b/.changeset/pre/thin-ducks-wonder.md similarity index 100% rename from .changeset/thin-ducks-wonder.md rename to .changeset/pre/thin-ducks-wonder.md diff --git a/.changeset/thirty-ducks-go.md b/.changeset/pre/thirty-ducks-go.md similarity index 100% rename from .changeset/thirty-ducks-go.md rename to .changeset/pre/thirty-ducks-go.md diff --git a/.changeset/pre/thirty-forks-march.md b/.changeset/pre/thirty-forks-march.md new file mode 100644 index 000000000..249bb675d --- /dev/null +++ b/.changeset/pre/thirty-forks-march.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Drop unreachable concurrency guard in iteratorEagerImpl diff --git a/.changeset/thirty-pans-love.md b/.changeset/pre/thirty-pans-love.md similarity index 100% rename from .changeset/thirty-pans-love.md rename to .changeset/pre/thirty-pans-love.md diff --git a/.changeset/three-corners-sort.md b/.changeset/pre/three-corners-sort.md similarity index 100% rename from .changeset/three-corners-sort.md rename to .changeset/pre/three-corners-sort.md diff --git a/.changeset/three-ravens-jam.md b/.changeset/pre/three-ravens-jam.md similarity index 100% rename from .changeset/three-ravens-jam.md rename to .changeset/pre/three-ravens-jam.md diff --git a/.changeset/three-tomatoes-wave.md b/.changeset/pre/three-tomatoes-wave.md similarity index 100% rename from .changeset/three-tomatoes-wave.md rename to .changeset/pre/three-tomatoes-wave.md diff --git a/.changeset/pre/tidy-apples-rest.md b/.changeset/pre/tidy-apples-rest.md new file mode 100644 index 000000000..1c9289733 --- /dev/null +++ b/.changeset/pre/tidy-apples-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Round Redis persistence TTLs up to whole milliseconds before passing them to integer-only expiration commands. diff --git a/.changeset/pre/tidy-carpets-smile.md b/.changeset/pre/tidy-carpets-smile.md new file mode 100644 index 000000000..af1b48da1 --- /dev/null +++ b/.changeset/pre/tidy-carpets-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent malformed encoded multipart filenames from throwing during parsing. diff --git a/.changeset/pre/tidy-cats-smile.md b/.changeset/pre/tidy-cats-smile.md new file mode 100644 index 000000000..951a09ca7 --- /dev/null +++ b/.changeset/pre/tidy-cats-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix unencrypted event log conflict scanning to inspect the newer history suffix. diff --git a/.changeset/pre/tidy-cats-stream.md b/.changeset/pre/tidy-cats-stream.md new file mode 100644 index 000000000..c14e87c6a --- /dev/null +++ b/.changeset/pre/tidy-cats-stream.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve lexical ordering in streaming template interpolation. diff --git a/.changeset/pre/tidy-codecs-inherit.md b/.changeset/pre/tidy-codecs-inherit.md new file mode 100644 index 000000000..137e05fd8 --- /dev/null +++ b/.changeset/pre/tidy-codecs-inherit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +When canonical JSON derivation adds a transformation for a schema without a direct JSON representation, keep source checks and annotations on the source side. This prevents duplicate check execution and ensures generated JSON Schema documents describe only the encoded target, closes #7192. diff --git a/.changeset/pre/tidy-dates-smile.md b/.changeset/pre/tidy-dates-smile.md new file mode 100644 index 000000000..529a47fdf --- /dev/null +++ b/.changeset/pre/tidy-dates-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct year, ordinal, and meridiem date-mask formatting. diff --git a/.changeset/pre/tidy-floats-edit.md b/.changeset/pre/tidy-floats-edit.md new file mode 100644 index 000000000..10924f810 --- /dev/null +++ b/.changeset/pre/tidy-floats-edit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve fractional leading zeros while editing float prompts. diff --git a/.changeset/tidy-foxes-own.md b/.changeset/pre/tidy-foxes-own.md similarity index 100% rename from .changeset/tidy-foxes-own.md rename to .changeset/pre/tidy-foxes-own.md diff --git a/.changeset/pre/tidy-geese-release.md b/.changeset/pre/tidy-geese-release.md new file mode 100644 index 000000000..9b2cb9701 --- /dev/null +++ b/.changeset/pre/tidy-geese-release.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RcRef` leaking resources acquired before a failed acquisition. diff --git a/.changeset/pre/tidy-graphs-cycle.md b/.changeset/pre/tidy-graphs-cycle.md new file mode 100644 index 000000000..9d4cb2d1a --- /dev/null +++ b/.changeset/pre/tidy-graphs-cycle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Throw `GraphError` when a negative cycle affects a Bellman-Ford target, reserving `Option.none()` for unreachable paths. diff --git a/.changeset/tidy-icons-glow.md b/.changeset/pre/tidy-icons-glow.md similarity index 100% rename from .changeset/tidy-icons-glow.md rename to .changeset/pre/tidy-icons-glow.md diff --git a/.changeset/pre/tidy-int32-annotations.md b/.changeset/pre/tidy-int32-annotations.md new file mode 100644 index 000000000..ca99cdd74 --- /dev/null +++ b/.changeset/pre/tidy-int32-annotations.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `isInt32` to apply custom annotations only to its filter group. diff --git a/.changeset/pre/tidy-json-objects.md b/.changeset/pre/tidy-json-objects.md new file mode 100644 index 000000000..80e746370 --- /dev/null +++ b/.changeset/pre/tidy-json-objects.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Add `Schema.JsonObject` for readonly string-keyed records containing JSON-compatible values. This provides a canonical, +reusable schema instead of requiring callers to repeatedly compose `Schema.Record(Schema.String, Schema.Json)`. diff --git a/.changeset/pre/tidy-json-schema-object-scopes.md b/.changeset/pre/tidy-json-schema-object-scopes.md new file mode 100644 index 000000000..2d3f1b7b4 --- /dev/null +++ b/.changeset/pre/tidy-json-schema-object-scopes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve JSON Schema object keyword scopes when importing `allOf` intersections, including closed empty objects and required-only keys. Emit intersecting index signatures without weakening their constraints, and reject object scope intersections that cannot be represented faithfully. diff --git a/.changeset/pre/tidy-lions-smile.md b/.changeset/pre/tidy-lions-smile.md new file mode 100644 index 000000000..871c3ab65 --- /dev/null +++ b/.changeset/pre/tidy-lions-smile.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-libsql": patch +--- + +Release transaction serialization when beginning a libSQL transaction fails, allowing later operations to retry. diff --git a/.changeset/pre/tidy-matches-select.md b/.changeset/pre/tidy-matches-select.md new file mode 100644 index 000000000..985f71da0 --- /dev/null +++ b/.changeset/pre/tidy-matches-select.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Match.fn` for reusable matchers that select a value from multiple arguments. diff --git a/.changeset/pre/tidy-mice-grin.md b/.changeset/pre/tidy-mice-grin.md new file mode 100644 index 000000000..1dcc53752 --- /dev/null +++ b/.changeset/pre/tidy-mice-grin.md @@ -0,0 +1,6 @@ +--- +"@effect/platform-node-shared": patch +"effect": patch +--- + +remove file descriptor type diff --git a/.changeset/pre/tidy-pandas-smile.md b/.changeset/pre/tidy-pandas-smile.md new file mode 100644 index 000000000..73f4b8803 --- /dev/null +++ b/.changeset/pre/tidy-pandas-smile.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `HttpApiError.UnprocessableEntity` and `HttpApiError.UnprocessableEntityNoContent` for status 422 responses. diff --git a/.changeset/pre/tidy-plums-remember.md b/.changeset/pre/tidy-plums-remember.md new file mode 100644 index 000000000..e9361ab06 --- /dev/null +++ b/.changeset/pre/tidy-plums-remember.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve Schema representation identity, anonymous-reference eligibility, and JSON Schema alias finalization. diff --git a/.changeset/pre/tidy-ravens-import.md b/.changeset/pre/tidy-ravens-import.md new file mode 100644 index 000000000..58265b76b --- /dev/null +++ b/.changeset/pre/tidy-ravens-import.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Reject unsupported JSON Schema validation keywords and object or array `const` / `enum` values during import instead of +silently weakening validation. diff --git a/.changeset/pre/tidy-schema-errors.md b/.changeset/pre/tidy-schema-errors.md new file mode 100644 index 000000000..1ad564ceb --- /dev/null +++ b/.changeset/pre/tidy-schema-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Move `SchemaError` into the `Schema` module and remove the standalone `SchemaError` module. diff --git a/.changeset/pre/tidy-scoped-refs-close.md b/.changeset/pre/tidy-scoped-refs-close.md new file mode 100644 index 000000000..4bb41bafa --- /dev/null +++ b/.changeset/pre/tidy-scoped-refs-close.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure `ScopedRef.set` releases a replacement when the previous value's finalizer defects. diff --git a/.changeset/pre/tidy-spans-rest.md b/.changeset/pre/tidy-spans-rest.md new file mode 100644 index 000000000..7dc60bafb --- /dev/null +++ b/.changeset/pre/tidy-spans-rest.md @@ -0,0 +1,5 @@ +--- +"@effect/opentelemetry": patch +--- + +Fix wrapped spans treating non-error OpenTelemetry statuses as errors. diff --git a/.changeset/pre/tidy-sse-events.md b/.changeset/pre/tidy-sse-events.md new file mode 100644 index 000000000..2ccb437ba --- /dev/null +++ b/.changeset/pre/tidy-sse-events.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Decode Effect SSE event schemas as complete events, including reserved failure events, in generated HTTP clients. diff --git a/.changeset/tidy-stacks-encode.md b/.changeset/pre/tidy-stacks-encode.md similarity index 100% rename from .changeset/tidy-stacks-encode.md rename to .changeset/pre/tidy-stacks-encode.md diff --git a/.changeset/tidy-stars-drive.md b/.changeset/pre/tidy-stars-drive.md similarity index 100% rename from .changeset/tidy-stars-drive.md rename to .changeset/pre/tidy-stars-drive.md diff --git a/.changeset/pre/tidy-tools-handle.md b/.changeset/pre/tidy-tools-handle.md new file mode 100644 index 000000000..155071c9f --- /dev/null +++ b/.changeset/pre/tidy-tools-handle.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Preserve encoded AI tool call parameters when automatic tool call resolution is disabled, and update +`Toolkit.handle` to accept the encoded parameter type it decodes at runtime. diff --git a/.changeset/pre/tidy-tools-juggle.md b/.changeset/pre/tidy-tools-juggle.md new file mode 100644 index 000000000..439548407 --- /dev/null +++ b/.changeset/pre/tidy-tools-juggle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure discarded non-persisted cluster messages complete without waiting for the entity reply. diff --git a/.changeset/pre/tidy-tuples-rest.md b/.changeset/pre/tidy-tuples-rest.md new file mode 100644 index 000000000..2adc9de9c --- /dev/null +++ b/.changeset/pre/tidy-tuples-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve `maxItems` semantics when importing JSON Schema `prefixItems`. diff --git a/.changeset/pre/tidy-wasps-wait.md b/.changeset/pre/tidy-wasps-wait.md new file mode 100644 index 000000000..192e4ed6f --- /dev/null +++ b/.changeset/pre/tidy-wasps-wait.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-wasm": patch +--- + +Settle pending SQLite WASM requests before replacing failed workers. diff --git a/.changeset/tiny-buckets-wave.md b/.changeset/pre/tiny-buckets-wave.md similarity index 100% rename from .changeset/tiny-buckets-wave.md rename to .changeset/pre/tiny-buckets-wave.md diff --git a/.changeset/pre/tiny-dodos-juggle.md b/.changeset/pre/tiny-dodos-juggle.md new file mode 100644 index 000000000..c721b6670 --- /dev/null +++ b/.changeset/pre/tiny-dodos-juggle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor custom split and strip regular expressions passed to `String.noCase`. diff --git a/.changeset/pre/tiny-files-flow.md b/.changeset/pre/tiny-files-flow.md new file mode 100644 index 000000000..a8f71a0d8 --- /dev/null +++ b/.changeset/pre/tiny-files-flow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix partial file-backed HTTP bodies to report the selected byte range as their content length. diff --git a/.changeset/tiny-lilies-flash.md b/.changeset/pre/tiny-lilies-flash.md similarity index 100% rename from .changeset/tiny-lilies-flash.md rename to .changeset/pre/tiny-lilies-flash.md diff --git a/.changeset/pre/tiny-lizards-correct.md b/.changeset/pre/tiny-lizards-correct.md new file mode 100644 index 000000000..82da87418 --- /dev/null +++ b/.changeset/pre/tiny-lizards-correct.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +MCP HTTP servers now reject requests sent before initialization with the required lifecycle response. diff --git a/.changeset/tiny-rabbits-smile.md b/.changeset/pre/tiny-rabbits-smile.md similarity index 100% rename from .changeset/tiny-rabbits-smile.md rename to .changeset/pre/tiny-rabbits-smile.md diff --git a/.changeset/to-codec-json-schema.md b/.changeset/pre/to-codec-json-schema.md similarity index 100% rename from .changeset/to-codec-json-schema.md rename to .changeset/pre/to-codec-json-schema.md diff --git a/.changeset/tocodecjson-return-json-type.md b/.changeset/pre/tocodecjson-return-json-type.md similarity index 100% rename from .changeset/tocodecjson-return-json-type.md rename to .changeset/pre/tocodecjson-return-json-type.md diff --git a/.changeset/tool-get-json-schema-tests.md b/.changeset/pre/tool-get-json-schema-tests.md similarity index 100% rename from .changeset/tool-get-json-schema-tests.md rename to .changeset/pre/tool-get-json-schema-tests.md diff --git a/.changeset/pre/tough-graphs-cache.md b/.changeset/pre/tough-graphs-cache.md new file mode 100644 index 000000000..010a1f831 --- /dev/null +++ b/.changeset/pre/tough-graphs-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix mutable graph cache consistency and guard weighted pathfinding against inconsistent snapshots and numeric overflow. diff --git a/.changeset/pre/tough-rooms-camp.md b/.changeset/pre/tough-rooms-camp.md new file mode 100644 index 000000000..26ae61b4f --- /dev/null +++ b/.changeset/pre/tough-rooms-camp.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject NDJSON values without a JSON representation. diff --git a/.changeset/pre/tough-taxis-own.md b/.changeset/pre/tough-taxis-own.md new file mode 100644 index 000000000..74191d310 --- /dev/null +++ b/.changeset/pre/tough-taxis-own.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Harden JSON-RPC wire message classification against inherited properties. diff --git a/.changeset/pre/tracer-disabled-timing.md b/.changeset/pre/tracer-disabled-timing.md new file mode 100644 index 000000000..42bf23ef5 --- /dev/null +++ b/.changeset/pre/tracer-disabled-timing.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep span end times at zero when tracer timing is disabled. diff --git a/.changeset/pre/tracer-perf.md b/.changeset/pre/tracer-perf.md new file mode 100644 index 000000000..3a85e6d80 --- /dev/null +++ b/.changeset/pre/tracer-perf.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Improve tracing performance in span creation and HTTP middleware. diff --git a/.changeset/true-actors-battle.md b/.changeset/pre/true-actors-battle.md similarity index 100% rename from .changeset/true-actors-battle.md rename to .changeset/pre/true-actors-battle.md diff --git a/.changeset/try-promise-catch-defect.md b/.changeset/pre/try-promise-catch-defect.md similarity index 100% rename from .changeset/try-promise-catch-defect.md rename to .changeset/pre/try-promise-catch-defect.md diff --git a/.changeset/twelve-dragons-move.md b/.changeset/pre/twelve-dragons-move.md similarity index 100% rename from .changeset/twelve-dragons-move.md rename to .changeset/pre/twelve-dragons-move.md diff --git a/.changeset/twenty-buttons-cheer.md b/.changeset/pre/twenty-buttons-cheer.md similarity index 100% rename from .changeset/twenty-buttons-cheer.md rename to .changeset/pre/twenty-buttons-cheer.md diff --git a/.changeset/pre/twenty-facts-laugh.md b/.changeset/pre/twenty-facts-laugh.md new file mode 100644 index 000000000..7430b4f99 --- /dev/null +++ b/.changeset/pre/twenty-facts-laugh.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +use Sets to track atom relationships diff --git a/.changeset/pre/twenty-garlics-marry.md b/.changeset/pre/twenty-garlics-marry.md new file mode 100644 index 000000000..96087da85 --- /dev/null +++ b/.changeset/pre/twenty-garlics-marry.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Fix `HttpRouter.Middleware.layer` to provide request error services for errors declared in `handles`, and expose global +middleware errors from `HttpRouter.toHttpEffect`. diff --git a/.changeset/two-roses-double.md b/.changeset/pre/two-roses-double.md similarity index 100% rename from .changeset/two-roses-double.md rename to .changeset/pre/two-roses-double.md diff --git a/.changeset/unify-error-defect-stack-options.md b/.changeset/pre/unify-error-defect-stack-options.md similarity index 100% rename from .changeset/unify-error-defect-stack-options.md rename to .changeset/pre/unify-error-defect-stack-options.md diff --git a/.changeset/pre/unique-directed-neighbors.md b/.changeset/pre/unique-directed-neighbors.md new file mode 100644 index 000000000..fd62b1615 --- /dev/null +++ b/.changeset/pre/unique-directed-neighbors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Deduplicate directed neighbor-node queries while preserving first edge occurrence order. diff --git a/.changeset/update-schema-arbitrary-report.md b/.changeset/pre/update-schema-arbitrary-report.md similarity index 100% rename from .changeset/update-schema-arbitrary-report.md rename to .changeset/pre/update-schema-arbitrary-report.md diff --git a/.changeset/pre/upgrade-socket-error-listener.md b/.changeset/pre/upgrade-socket-error-listener.md new file mode 100644 index 000000000..f09a83215 --- /dev/null +++ b/.changeset/pre/upgrade-socket-error-listener.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Stop a reset upgrade connection from crashing the process in `NodeHttpServer` diff --git a/.changeset/upset-colts-stick.md b/.changeset/pre/upset-colts-stick.md similarity index 100% rename from .changeset/upset-colts-stick.md rename to .changeset/pre/upset-colts-stick.md diff --git a/.changeset/use-url-can-parse.md b/.changeset/pre/use-url-can-parse.md similarity index 100% rename from .changeset/use-url-can-parse.md rename to .changeset/pre/use-url-can-parse.md diff --git a/.changeset/pre/valid-owls-rest.md b/.changeset/pre/valid-owls-rest.md new file mode 100644 index 000000000..35f256b23 --- /dev/null +++ b/.changeset/pre/valid-owls-rest.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Validate object-based DateTime instants before construction. diff --git a/.changeset/pre/validate-httpapi-handler-registration.md b/.changeset/pre/validate-httpapi-handler-registration.md new file mode 100644 index 000000000..a2896b0f2 --- /dev/null +++ b/.changeset/pre/validate-httpapi-handler-registration.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject unknown and duplicate HttpApi handler registrations with descriptive errors. diff --git a/.changeset/pre/validate-openapi-global-conflicts.md b/.changeset/pre/validate-openapi-global-conflicts.md new file mode 100644 index 000000000..4859757c3 --- /dev/null +++ b/.changeset/pre/validate-openapi-global-conflicts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reject duplicate OpenAPI operations and operation identifiers, and reject incompatible security schemes that reuse a name. diff --git a/.changeset/vast-bananas-send.md b/.changeset/pre/vast-bananas-send.md similarity index 100% rename from .changeset/vast-bananas-send.md rename to .changeset/pre/vast-bananas-send.md diff --git a/.changeset/vast-deserts-travel.md b/.changeset/pre/vast-deserts-travel.md similarity index 100% rename from .changeset/vast-deserts-travel.md rename to .changeset/pre/vast-deserts-travel.md diff --git a/.changeset/violet-peaches-feel.md b/.changeset/pre/violet-peaches-feel.md similarity index 100% rename from .changeset/violet-peaches-feel.md rename to .changeset/pre/violet-peaches-feel.md diff --git a/.changeset/pre/violet-tips-open.md b/.changeset/pre/violet-tips-open.md new file mode 100644 index 000000000..e26432ffd --- /dev/null +++ b/.changeset/pre/violet-tips-open.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pg": patch +--- + +Fix `PgClient.makeClient` to connect the underlying `pg.Client` during resource acquisition. diff --git a/.changeset/vitest-layer-top-level-options.md b/.changeset/pre/vitest-layer-top-level-options.md similarity index 100% rename from .changeset/vitest-layer-top-level-options.md rename to .changeset/pre/vitest-layer-top-level-options.md diff --git a/.changeset/wacky-grapes-poke.md b/.changeset/pre/wacky-grapes-poke.md similarity index 100% rename from .changeset/wacky-grapes-poke.md rename to .changeset/pre/wacky-grapes-poke.md diff --git a/.changeset/wacky-rice-add.md b/.changeset/pre/wacky-rice-add.md similarity index 100% rename from .changeset/wacky-rice-add.md rename to .changeset/pre/wacky-rice-add.md diff --git a/.changeset/pre/warm-clocks-format.md b/.changeset/pre/warm-clocks-format.md new file mode 100644 index 000000000..04173b8c3 --- /dev/null +++ b/.changeset/pre/warm-clocks-format.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Cron.format` for converting a `Cron` instance to a cron expression, with an option to include the seconds field. diff --git a/.changeset/warm-dolls-brake.md b/.changeset/pre/warm-dolls-brake.md similarity index 100% rename from .changeset/warm-dolls-brake.md rename to .changeset/pre/warm-dolls-brake.md diff --git a/.changeset/warm-friends-tie.md b/.changeset/pre/warm-friends-tie.md similarity index 100% rename from .changeset/warm-friends-tie.md rename to .changeset/pre/warm-friends-tie.md diff --git a/.changeset/pre/warm-graphs-degree.md b/.changeset/pre/warm-graphs-degree.md new file mode 100644 index 000000000..f140e4d84 --- /dev/null +++ b/.changeset/pre/warm-graphs-degree.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add incident-edge, edges-between, and directed and undirected degree queries to `Graph`. diff --git a/.changeset/pre/warm-rivers-cache.md b/.changeset/pre/warm-rivers-cache.md new file mode 100644 index 000000000..23a22720b --- /dev/null +++ b/.changeset/pre/warm-rivers-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve original HTTP response bytes when reading response text first. diff --git a/.changeset/warm-snails-shop.md b/.changeset/pre/warm-snails-shop.md similarity index 100% rename from .changeset/warm-snails-shop.md rename to .changeset/pre/warm-snails-shop.md diff --git a/.changeset/wet-news-invent.md b/.changeset/pre/wet-news-invent.md similarity index 100% rename from .changeset/wet-news-invent.md rename to .changeset/pre/wet-news-invent.md diff --git a/.changeset/pre/whole-pets-build.md b/.changeset/pre/whole-pets-build.md new file mode 100644 index 000000000..91629728a --- /dev/null +++ b/.changeset/pre/whole-pets-build.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +clean up more references on fiber exit diff --git a/.changeset/pre/wild-donuts-brake.md b/.changeset/pre/wild-donuts-brake.md new file mode 100644 index 000000000..4bfeecea7 --- /dev/null +++ b/.changeset/pre/wild-donuts-brake.md @@ -0,0 +1,31 @@ +--- +"effect": patch +--- + +Bound cluster runner entity residency and storage reads. + +`ShardingConfig` gains two knobs: + +- `maxResidentEntities` (default `10_000`): the maximum number of entities + that can be resident on a runner at the same time. At the cap, the storage + read loop stops admitting messages for new entity addresses (they stay in + storage until a slot frees up) and volatile sends to new addresses fail with + `MailboxFull`. Persisted sends still succeed. `"unbounded"` restores the + previous behaviour and can only be set programmatically. +- `unprocessedMessageBatchSize` (default `1024`): the maximum number of + unprocessed messages read from storage in a single poll. + +`MessageStorage.unprocessedMessages` accepts an optional +`{ limit, addresses }` argument, and only claims the messages it actually +returns. The memory implementation now applies the same ten-minute claim +window as SQL, so bounded reads advance past in-flight requests; resetting an +address or shard makes its claimed messages immediately eligible again. + +The encoded driver contract replaces `Encoded.resetAddress` with the batched +`Encoded.resetAddresses` operation. `SqlMessageStorage.makeEncoded` constructs +the SQL encoded driver directly for custom storage composition. + +`ClusterWorkflowEngine` entities (workflows and the durable clock) now use a +fixed ten-second idle time, so completed and suspended executions release their +entity slots quickly. Their state is durable, so an evicted execution is +rebuilt from storage when its next message arrives. diff --git a/.changeset/wild-readers-clean.md b/.changeset/pre/wild-readers-clean.md similarity index 100% rename from .changeset/wild-readers-clean.md rename to .changeset/pre/wild-readers-clean.md diff --git a/.changeset/wild-suns-bearer-space.md b/.changeset/pre/wild-suns-bearer-space.md similarity index 100% rename from .changeset/wild-suns-bearer-space.md rename to .changeset/pre/wild-suns-bearer-space.md diff --git a/.changeset/pre/windows-hide-child-process-console.md b/.changeset/pre/windows-hide-child-process-console.md new file mode 100644 index 000000000..77d53516a --- /dev/null +++ b/.changeset/pre/windows-hide-child-process-console.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-node-shared": patch +--- + +Pass Node's `windowsHide` flag for spawned Windows children by default (except detached processes), with an independent +`windowsHide` option for callers that need visible GUI windows. Process-group cleanup now invokes `taskkill` without a +`cmd.exe` wrapper and hides its window. diff --git a/.changeset/wise-ants-wave.md b/.changeset/pre/wise-ants-wave.md similarity index 100% rename from .changeset/wise-ants-wave.md rename to .changeset/pre/wise-ants-wave.md diff --git a/.changeset/pre/wise-bats-encrypt.md b/.changeset/pre/wise-bats-encrypt.md new file mode 100644 index 000000000..3a251349a --- /dev/null +++ b/.changeset/pre/wise-bats-encrypt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Use a distinct AES-GCM initialization vector for each encrypted event log entry. `EventLogEncryption.encrypt` now returns each IV with its ciphertext, and encrypted event log clients and servers must be upgraded together because the `WriteEntries` wire shape changed. diff --git a/.changeset/pre/wise-files-watch.md b/.changeset/pre/wise-files-watch.md new file mode 100644 index 000000000..41cd84d9a --- /dev/null +++ b/.changeset/pre/wise-files-watch.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-deno": patch +"@effect/platform-node-shared": patch +"effect": patch +--- + +Restore the `recursive` option for `FileSystem.watch`, with non-recursive watching as the default. diff --git a/.changeset/wise-flags-shift.md b/.changeset/pre/wise-flags-shift.md similarity index 100% rename from .changeset/wise-flags-shift.md rename to .changeset/pre/wise-flags-shift.md diff --git a/.changeset/pre/wise-graphs-connect.md b/.changeset/pre/wise-graphs-connect.md new file mode 100644 index 000000000..8ac3f4b7a --- /dev/null +++ b/.changeset/pre/wise-graphs-connect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add unweighted reachability, explicit weak and strong connectivity predicates, weak components, and tree detection to `Graph`. diff --git a/.changeset/wise-oranges-stay.md b/.changeset/pre/wise-oranges-stay.md similarity index 100% rename from .changeset/wise-oranges-stay.md rename to .changeset/pre/wise-oranges-stay.md diff --git a/.changeset/pre/wise-pandas-lock.md b/.changeset/pre/wise-pandas-lock.md new file mode 100644 index 000000000..8f15bcab3 --- /dev/null +++ b/.changeset/pre/wise-pandas-lock.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Namespace PostgreSQL advisory shard locks by the `SqlRunnerStorage` table prefix. + +This changes the advisory-lock protocol. PostgreSQL clusters using advisory locks require a full cluster stop before upgrading; a rolling deploy is unsafe because old and new runners use different lock keys and can both acquire the same shard. diff --git a/.changeset/witty-lobsters-share.md b/.changeset/pre/witty-lobsters-share.md similarity index 100% rename from .changeset/witty-lobsters-share.md rename to .changeset/pre/witty-lobsters-share.md diff --git a/.changeset/pre/worker-runner-multi-disconnect.md b/.changeset/pre/worker-runner-multi-disconnect.md new file mode 100644 index 000000000..2d49ff0cd --- /dev/null +++ b/.changeset/pre/worker-runner-multi-disconnect.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Forward every worker-runner client disconnect to the RPC server, not just the first one. diff --git a/.changeset/pre/workflow-proxy-discard-execution-id.md b/.changeset/pre/workflow-proxy-discard-execution-id.md new file mode 100644 index 000000000..24b90519b --- /dev/null +++ b/.changeset/pre/workflow-proxy-discard-execution-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Return workflow execution IDs from generated RPC and HTTP discard endpoints. diff --git a/.changeset/yellow-adults-study.md b/.changeset/pre/yellow-adults-study.md similarity index 100% rename from .changeset/yellow-adults-study.md rename to .changeset/pre/yellow-adults-study.md diff --git a/.changeset/yellow-clocks-dance.md b/.changeset/pre/yellow-clocks-dance.md similarity index 100% rename from .changeset/yellow-clocks-dance.md rename to .changeset/pre/yellow-clocks-dance.md diff --git a/.changeset/yellow-dingos-jump.md b/.changeset/pre/yellow-dingos-jump.md similarity index 100% rename from .changeset/yellow-dingos-jump.md rename to .changeset/pre/yellow-dingos-jump.md diff --git a/.changeset/young-doors-change.md b/.changeset/pre/young-doors-change.md similarity index 100% rename from .changeset/young-doors-change.md rename to .changeset/pre/young-doors-change.md diff --git a/.changeset/pre/zero-node-socket-timeout.md b/.changeset/pre/zero-node-socket-timeout.md new file mode 100644 index 000000000..812377865 --- /dev/null +++ b/.changeset/pre/zero-node-socket-timeout.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node-shared": patch +--- + +Respect zero-duration open timeouts when opening Node sockets. diff --git a/.changeset/preserve-default-variant-class.md b/.changeset/preserve-default-variant-class.md new file mode 100644 index 000000000..d263ec6ae --- /dev/null +++ b/.changeset/preserve-default-variant-class.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve schema classes when extracting their default `VariantSchema` variant. diff --git a/.changeset/preserve-negative-counter-deltas.md b/.changeset/preserve-negative-counter-deltas.md new file mode 100644 index 000000000..d8783c189 --- /dev/null +++ b/.changeset/preserve-negative-counter-deltas.md @@ -0,0 +1,6 @@ +--- +"effect": patch +"@effect/opentelemetry": patch +--- + +Preserve negative counter deltas in OTLP and OpenTelemetry metric exports. diff --git a/.changeset/principled-collection-counts.md b/.changeset/principled-collection-counts.md new file mode 100644 index 000000000..25bf6a2ef --- /dev/null +++ b/.changeset/principled-collection-counts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Normalize numeric collection counts consistently across `Array`, `Chunk`, `Iterable`, and `String`, and make `TupleOf` fall back to `Array` for positive fractional lengths. diff --git a/.changeset/prompt-buffer-render.md b/.changeset/prompt-buffer-render.md new file mode 100644 index 000000000..6cc05a921 --- /dev/null +++ b/.changeset/prompt-buffer-render.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep the previous prompt frame visible until the next frame or submission is ready to display. diff --git a/.changeset/prompt-lossless-text-serialization.md b/.changeset/prompt-lossless-text-serialization.md new file mode 100644 index 000000000..ead45aa74 --- /dev/null +++ b/.changeset/prompt-lossless-text-serialization.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve text parts and provider options when serializing prompts. diff --git a/.changeset/prompt-response-files.md b/.changeset/prompt-response-files.md new file mode 100644 index 000000000..0f11a7d89 --- /dev/null +++ b/.changeset/prompt-response-files.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve generated files when converting AI responses to prompts. diff --git a/.changeset/pubsub-sliding-single-subscriber.md b/.changeset/pubsub-sliding-single-subscriber.md new file mode 100644 index 000000000..3584a0509 --- /dev/null +++ b/.changeset/pubsub-sliding-single-subscriber.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix capacity-one PubSub subscriber cursors after sliding past messages, including duplicate delivery and invalid state when unsubscribing from a slid message. diff --git a/.changeset/pull-based-socket.md b/.changeset/pull-based-socket.md new file mode 100644 index 000000000..98c762f59 --- /dev/null +++ b/.changeset/pull-based-socket.md @@ -0,0 +1,22 @@ +--- +"effect": patch +"@effect/platform-node-shared": patch +"@effect/platform-node": patch +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"@effect/platform-browser": patch +"@effect/ai-openai": patch +--- + +Redesign `Socket` around a scoped, pull-based reader with transport backpressure. + +`Socket` now exposes `reader` and `writer`. Client reader acquisition dials and yields a pull of non-empty batches: one buffer for TCP and one entry per WebSocket frame. TCP applies backpressure while paused; pausable WebSockets pause at `highWaterMark` (64 KiB by default) and resume after draining. Browser WebSockets cannot pause, so they can fail with `SocketReadError` at a configured `highWaterMark`. Writes await native drain signals and batch with `cork` / `uncork` where available. + +### Breaking changes + +- `Socket.run`, `Socket.runString`, and `Socket.runRaw` are removed. Acquire `socket.reader` (or `Socket.readerBytes` / `Socket.readerString`) in a scope and pull in a loop. Code before the first pull replaces `onOpen`. +- `Socket.make` now takes `{ reader, writer }`. The writer acquisition is infallible and yields a `Writer` with `write` and `writeAll`; both operations can still fail with `SocketError`. +- Every close fails the pull with `SocketError` wrapping `SocketCloseError`. The close-code predicates are removed; use `Effect.retry` around the scoped read loop to reconnect. +- `Socket.toChannel` and `Socket.toChannelString` now read from the pull and fail on close. `Socket.toStream` is added for read-only consumption. +- `fromWebSocket` drops the `onInitialRun` option; `SendQueueCapacity` is removed. +- Accepted server sockets pause immediately. Their reader attaches to the existing connection and cannot reconnect after close. diff --git a/.changeset/queue-manual-flush.md b/.changeset/queue-manual-flush.md new file mode 100644 index 000000000..c7f3fb423 --- /dev/null +++ b/.changeset/queue-manual-flush.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Add `Queue.flush` and `Queue.flushUnsafe` for manually releasing pending takers, including after synchronous offers. diff --git a/.changeset/queue-reentrant-producers.md b/.changeset/queue-reentrant-producers.md new file mode 100644 index 000000000..3048f717e --- /dev/null +++ b/.changeset/queue-reentrant-producers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Queue` message duplication, capacity overruns, and consumer defects when a resumed producer synchronously uses the same queue. Zero-capacity queues now reserve each handed-off message for its consumer before resuming the producer. diff --git a/.changeset/quiet-buffers-decode.md b/.changeset/quiet-buffers-decode.md new file mode 100644 index 000000000..dbf009e18 --- /dev/null +++ b/.changeset/quiet-buffers-decode.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Allow `SqlEventJournal` to decode entry identifiers and payloads from SQL drivers that return BLOB values as `ArrayBuffer`. diff --git a/.changeset/quiet-buns-route.md b/.changeset/quiet-buns-route.md new file mode 100644 index 000000000..e63500872 --- /dev/null +++ b/.changeset/quiet-buns-route.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-bun": patch +--- + +Preserve configured Bun routes when Effect HTTP handlers are installed or restored. diff --git a/.changeset/quiet-graphs-simplify.md b/.changeset/quiet-graphs-simplify.md new file mode 100644 index 000000000..cc52f4d7d --- /dev/null +++ b/.changeset/quiet-graphs-simplify.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Remove the redundant `Graph.Proto` interface. Use `Graph.Graph` when accepting any immutable graph. diff --git a/.changeset/quiet-pandas-listen.md b/.changeset/quiet-pandas-listen.md new file mode 100644 index 000000000..58aaa22f2 --- /dev/null +++ b/.changeset/quiet-pandas-listen.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-pglite": patch +--- + +Align PGlite `listen` with the PostgreSQL client. It now returns a scoped dequeue after the listener is installed, providing an explicit readiness boundary and preserving notifications received before the first take. diff --git a/.changeset/quiet-streams-take.md b/.changeset/quiet-streams-take.md new file mode 100644 index 000000000..c05bac54d --- /dev/null +++ b/.changeset/quiet-streams-take.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Treat `NaN` as a non-positive count in `Stream.take`. diff --git a/.changeset/random-exclusive-upper-endpoint.md b/.changeset/random-exclusive-upper-endpoint.md new file mode 100644 index 000000000..ee55cc507 --- /dev/null +++ b/.changeset/random-exclusive-upper-endpoint.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Random.nextBetween` and `Crypto.randomBetween` returning their exclusive upper bound when floating-point arithmetic rounds up. diff --git a/.changeset/rate-limiter-reset-lifetime.md b/.changeset/rate-limiter-reset-lifetime.md new file mode 100644 index 000000000..03547308d --- /dev/null +++ b/.changeset/rate-limiter-reset-lifetime.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Report the exact remaining store lifetime in `RateLimiter` fixed-window `resetAfter` metadata when `onExceeded` is `"delay"`, instead of rounding up to a whole window. Admission, returned delays, and remaining-token counts are unchanged. diff --git a/.changeset/rcmap-invalidation-release.md b/.changeset/rcmap-invalidation-release.md new file mode 100644 index 000000000..c89b315ae --- /dev/null +++ b/.changeset/rcmap-invalidation-release.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `RcMap` and `LayerMap` cleanup after invalidating an actively borrowed entry and reacquiring the same key. +The invalidated resource is released when its last borrower closes, even with infinite idle TTL, without removing +the replacement entry. Old idle timers also leave replacement entries untouched. diff --git a/.changeset/rcmap-throwing-lookup.md b/.changeset/rcmap-throwing-lookup.md new file mode 100644 index 000000000..2e8027ee5 --- /dev/null +++ b/.changeset/rcmap-throwing-lookup.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RcMap` entries getting stuck when the lookup function throws synchronously. Later borrowers now receive the defect, and unused entries are released according to their idle TTL instead of permanently consuming capacity. diff --git a/.changeset/rcref-acquisition-shutdown.md b/.changeset/rcref-acquisition-shutdown.md new file mode 100644 index 000000000..1ee8dedc2 --- /dev/null +++ b/.changeset/rcref-acquisition-shutdown.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep `RcRef` closed when an in-flight acquisition finishes after its owning scope has closed. Release the late-acquired resource and interrupt waiting borrowers instead of making the resource available again. diff --git a/.changeset/rcref-release-ownership.md b/.changeset/rcref-release-ownership.md new file mode 100644 index 000000000..53bd9947b --- /dev/null +++ b/.changeset/rcref-release-ownership.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent `RcRef` borrower cleanup from discarding replacement resources after invalidation or reopening a reference after its owner scope has closed. diff --git a/.changeset/reason-annotate-context-only.md b/.changeset/reason-annotate-context-only.md new file mode 100644 index 000000000..12b1b2c39 --- /dev/null +++ b/.changeset/reason-annotate-context-only.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Type `Cause.Reason#annotate` as accepting a `Context` only. diff --git a/.changeset/remove-msgpack.md b/.changeset/remove-msgpack.md new file mode 100644 index 000000000..d7f457806 --- /dev/null +++ b/.changeset/remove-msgpack.md @@ -0,0 +1,8 @@ +--- +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"@effect/platform-node": patch +"effect": patch +--- + +Remove the MessagePack encoding and RPC serialization APIs together with the `msgpackr` dependency. Event-log persistence and remote messages now use SchemaBinary, and cluster transports use SchemaBinary unless NDJSON is selected explicitly. diff --git a/.changeset/repeat-or-else-metadata-argument.md b/.changeset/repeat-or-else-metadata-argument.md new file mode 100644 index 000000000..1e0828ffa --- /dev/null +++ b/.changeset/repeat-or-else-metadata-argument.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Correct the `Effect.repeatOrElse` fallback type to expose the previous step's `Schedule.Metadata`, matching the existing runtime value. diff --git a/.changeset/report-mcp-tool-failures.md b/.changeset/report-mcp-tool-failures.md new file mode 100644 index 000000000..531baac79 --- /dev/null +++ b/.changeset/report-mcp-tool-failures.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Report recovered MCP toolkit failures and defects to configured `ErrorReporter`s, including declared tool failures returned with `isError: true`. diff --git a/.changeset/request-cache-cancellation.md b/.changeset/request-cache-cancellation.md new file mode 100644 index 000000000..9feb720d7 --- /dev/null +++ b/.changeset/request-cache-cancellation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RequestResolver.withCache` retaining abandoned entries when a pending request is cancelled diff --git a/.changeset/request-persisted-resolver-failures.md b/.changeset/request-persisted-resolver-failures.md new file mode 100644 index 000000000..3268c2646 --- /dev/null +++ b/.changeset/request-persisted-resolver-failures.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve completed results and propagate resolver failures from `RequestResolver.persisted`. diff --git a/.changeset/request-race-cache.md b/.changeset/request-race-cache.md new file mode 100644 index 000000000..2ae0bcc17 --- /dev/null +++ b/.changeset/request-race-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Keep completed results in `RequestResolver.withCache` when a losing `RequestResolver.race` resolver is interrupted after the winner completes, avoiding repeated backend requests on subsequent equal lookups. diff --git a/.changeset/request-resolver-failure-causes.md b/.changeset/request-resolver-failure-causes.md new file mode 100644 index 000000000..a77da246a --- /dev/null +++ b/.changeset/request-resolver-failure-causes.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve typed errors, defects, and interrupts from `RequestResolver.fromEffectTagged` handlers. diff --git a/.changeset/request-resolver-iterable-results.md b/.changeset/request-resolver-iterable-results.md new file mode 100644 index 000000000..59f70109e --- /dev/null +++ b/.changeset/request-resolver-iterable-results.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `RequestResolver.fromEffectTagged` to consume handler results as an iterable, allowing arrays, iterators, and generators to resolve requests in order. diff --git a/.changeset/required-keys-index-signatures.md b/.changeset/required-keys-index-signatures.md new file mode 100644 index 000000000..61a9a583d --- /dev/null +++ b/.changeset/required-keys-index-signatures.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Types.RequiredKeys` dropping named required keys on types with index signatures. Derived type annotations may need to include these keys. diff --git a/.changeset/resolved-client-urls.md b/.changeset/resolved-client-urls.md new file mode 100644 index 000000000..b3554d530 --- /dev/null +++ b/.changeset/resolved-client-urls.md @@ -0,0 +1,8 @@ +--- +"@effect/platform-browser": patch +"@effect/platform-node": patch +"effect": patch +--- + +Add `HttpClientResponse.url`, including query parameters and excluding the hash. When redirects are followed, it reports +the final URL. diff --git a/.changeset/rpc-falsy-control-ids.md b/.changeset/rpc-falsy-control-ids.md new file mode 100644 index 000000000..0c2d815d7 --- /dev/null +++ b/.changeset/rpc-falsy-control-ids.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve zero and empty-string request IDs in JSON-RPC control messages. diff --git a/.changeset/scheduler-global-scope-timers.md b/.changeset/scheduler-global-scope-timers.md new file mode 100644 index 000000000..af24f0d77 --- /dev/null +++ b/.changeset/scheduler-global-scope-timers.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +The default scheduler falls back to a microtask when setting a timer throws. Cloudflare Workers disallow timers in global scope, so an effect that yielded while running at module load failed with "Disallowed operation called within global scope". diff --git a/.changeset/schema-api-cleanup.md b/.changeset/schema-api-cleanup.md new file mode 100644 index 000000000..b7a4c3d72 --- /dev/null +++ b/.changeset/schema-api-cleanup.md @@ -0,0 +1,11 @@ +--- +"effect": patch +--- + +Move the built-in schema revivers from `Schema` to `SchemaRepresentation`. +Rename the reviver constructors to `makeReviverDeclaration`, +`makeReviverFilter`, and `makeReviverFilterGroup`. + +Change `Schema.toEncoderXml` to fail with `SchemaIssue.Issue` directly instead +of wrapping failures in `SchemaError`. Consumers that read `error.issue` should +now use the error value itself. diff --git a/.changeset/schema-array-leaf-aggregation.md b/.changeset/schema-array-leaf-aggregation.md new file mode 100644 index 000000000..daf0561a8 --- /dev/null +++ b/.changeset/schema-array-leaf-aggregation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve array-valued leaves when `SchemaGetter.makeTreeRecord` aggregates duplicate paths. diff --git a/.changeset/schema-binary-preserve-bom.md b/.changeset/schema-binary-preserve-bom.md new file mode 100644 index 000000000..2248164f1 --- /dev/null +++ b/.changeset/schema-binary-preserve-bom.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve leading U+FEFF characters in SchemaBinary string values when decoding. diff --git a/.changeset/scoped-cache-invalidate-all-reentrancy.md b/.changeset/scoped-cache-invalidate-all-reentrancy.md new file mode 100644 index 000000000..2f7bc048a --- /dev/null +++ b/.changeset/scoped-cache-invalidate-all-reentrancy.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `ScopedCache.invalidateAll` discarding entries created by reentrant resource finalizers without releasing them. Entries are now removed before their finalizers run, so replacement resources remain cached and are released when the cache closes. diff --git a/.changeset/scoped-cache-refresh-lookup-defects.md b/.changeset/scoped-cache-refresh-lookup-defects.md new file mode 100644 index 000000000..412dc00d9 --- /dev/null +++ b/.changeset/scoped-cache-refresh-lookup-defects.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Capture synchronous defects thrown by `ScopedCache.refresh` lookup callbacks. diff --git a/.changeset/scoped-log-nan-restoration.md b/.changeset/scoped-log-nan-restoration.md new file mode 100644 index 000000000..07413320b --- /dev/null +++ b/.changeset/scoped-log-nan-restoration.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.annotateLogsScoped` to restore or remove unchanged `NaN` annotations when the scope closes. diff --git a/.changeset/shaky-terms-push.md b/.changeset/shaky-terms-push.md new file mode 100644 index 000000000..9e004ee1e --- /dev/null +++ b/.changeset/shaky-terms-push.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix HttpRouter nested prefixed application order diff --git a/.changeset/sharding-registration-context-overrides.md b/.changeset/sharding-registration-context-overrides.md new file mode 100644 index 000000000..67c74a7d1 --- /dev/null +++ b/.changeset/sharding-registration-context-overrides.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor services explicitly supplied when registering cluster entities while retaining construction-context services as fallbacks. diff --git a/.changeset/shy-seals-smile.md b/.changeset/shy-seals-smile.md new file mode 100644 index 000000000..ac3d45baa --- /dev/null +++ b/.changeset/shy-seals-smile.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-react-native": patch +--- + +Return selected rows from synchronous and asynchronous OP-SQLite value queries. diff --git a/.changeset/skip-disabled-stack-capture.md b/.changeset/skip-disabled-stack-capture.md new file mode 100644 index 000000000..cbdf29e4f --- /dev/null +++ b/.changeset/skip-disabled-stack-capture.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Skip optional stack capture when `Error.stackTraceLimit` is zero. diff --git a/.changeset/small-basic-bundle.md b/.changeset/small-basic-bundle.md new file mode 100644 index 000000000..eef21766d --- /dev/null +++ b/.changeset/small-basic-bundle.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Reduce the basic Effect bundle size by keeping cause deduplication local, making encoding lookup tables tree-shakeable, removing redundant cause field declarations, and simplifying primitive hash dispatch without changing hash values. diff --git a/.changeset/socket-paused-websocket-handoff.md b/.changeset/socket-paused-websocket-handoff.md new file mode 100644 index 000000000..b8ee1aaef --- /dev/null +++ b/.changeset/socket-paused-websocket-handoff.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Resume paused WebSockets after their readers take ownership. diff --git a/.changeset/socket-websocket-text-byte-watermark.md b/.changeset/socket-websocket-text-byte-watermark.md new file mode 100644 index 000000000..bd35e74b5 --- /dev/null +++ b/.changeset/socket-websocket-text-byte-watermark.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Count buffered WebSocket text frames by their UTF-8 byte length when enforcing `highWaterMark`. diff --git a/.changeset/split-lines-completed-cr.md b/.changeset/split-lines-completed-cr.md new file mode 100644 index 000000000..5a3608f58 --- /dev/null +++ b/.changeset/split-lines-completed-cr.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Emit CR-terminated lines from `Stream.splitLines` without pulling upstream again. diff --git a/.changeset/spotty-masks-own.md b/.changeset/spotty-masks-own.md new file mode 100644 index 000000000..16f4feff0 --- /dev/null +++ b/.changeset/spotty-masks-own.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Loosen Stream.addEventListener type parameter diff --git a/.changeset/sql-event-journal-callback-errors.md b/.changeset/sql-event-journal-callback-errors.md new file mode 100644 index 000000000..2d3016175 --- /dev/null +++ b/.changeset/sql-event-journal-callback-errors.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve callback error identity in `SqlEventJournal.write` and `SqlEventJournal.withRemoteUncommited`. diff --git a/.changeset/sql-message-storage-joined-reply-id.md b/.changeset/sql-message-storage-joined-reply-id.md new file mode 100644 index 000000000..37a3db749 --- /dev/null +++ b/.changeset/sql-message-storage-joined-reply-id.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve reply IDs in SQL-backed `MessageStorage.unprocessedMessagesById` reads. diff --git a/.changeset/sql-nested-placeholder-cache.md b/.changeset/sql-nested-placeholder-cache.md new file mode 100644 index 000000000..978826051 --- /dev/null +++ b/.changeset/sql-nested-placeholder-cache.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix placeholder numbering for cached fragments used in returning helpers. diff --git a/.changeset/sql-optional-span-propagation.md b/.changeset/sql-optional-span-propagation.md new file mode 100644 index 000000000..f925c1ad0 --- /dev/null +++ b/.changeset/sql-optional-span-propagation.md @@ -0,0 +1,12 @@ +--- +"effect": patch +--- + +Add `Statement.SpanPropagationEnabled` to scope driver span parenting under `sql.execute` for any SQL client. Disabled by default. + +```ts +import { Effect } from "effect" +import { Statement } from "effect/unstable/sql" + +query.pipe(Effect.provideService(Statement.SpanPropagationEnabled, true)) +``` diff --git a/.changeset/sql-returning-identifier.md b/.changeset/sql-returning-identifier.md new file mode 100644 index 000000000..7922dc865 --- /dev/null +++ b/.changeset/sql-returning-identifier.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix SQL returning helpers to compile identifiers with dialect-specific escaping. diff --git a/.changeset/sql-runner-requested-shard-results.md b/.changeset/sql-runner-requested-shard-results.md new file mode 100644 index 000000000..bbce8efe4 --- /dev/null +++ b/.changeset/sql-runner-requested-shard-results.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ensure PostgreSQL shard acquisition and refresh return only the requested shards. diff --git a/.changeset/sqlite-bun-transaction-export.md b/.changeset/sqlite-bun-transaction-export.md new file mode 100644 index 000000000..52739714a --- /dev/null +++ b/.changeset/sqlite-bun-transaction-export.md @@ -0,0 +1,6 @@ +--- +"@effect/sql-sqlite-bun": patch +--- + +Allow database exports inside `withTransaction` to complete instead of waiting indefinitely. The exported snapshot +includes the transaction's uncommitted writes, while exports outside that transaction still wait for it to finish. diff --git a/.changeset/sqlite-do-stream-errors.md b/.changeset/sqlite-do-stream-errors.md new file mode 100644 index 000000000..af0b108b0 --- /dev/null +++ b/.changeset/sqlite-do-stream-errors.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-do": patch +--- + +Report synchronous Durable Object SQL streaming failures as recoverable `SqlError` values. diff --git a/.changeset/sqlite-node-unprepared-errors.md b/.changeset/sqlite-node-unprepared-errors.md new file mode 100644 index 000000000..be4ae32fa --- /dev/null +++ b/.changeset/sqlite-node-unprepared-errors.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-node": patch +--- + +Capture unprepared statement preparation failures as typed `SqlError`s. diff --git a/.changeset/sqlite-wasm-messageport-startup.md b/.changeset/sqlite-wasm-messageport-startup.md new file mode 100644 index 000000000..bd2530e4c --- /dev/null +++ b/.changeset/sqlite-wasm-messageport-startup.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-wasm": patch +--- + +Start incoming MessagePort queues after registering the SQLite WASM client and OPFS worker listeners. Fresh ports, including a SharedWorker's client port, no longer require manual activation to receive ready messages and query replies or process worker requests. diff --git a/.changeset/sqlite-wasm-statement-columns.md b/.changeset/sqlite-wasm-statement-columns.md new file mode 100644 index 000000000..889b6cc7d --- /dev/null +++ b/.changeset/sqlite-wasm-statement-columns.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-wasm": patch +--- + +Preserve statement-specific column names in SQLite WASM worker-backed query results. diff --git a/.changeset/sqlite-wasm-worker-error-metadata.md b/.changeset/sqlite-wasm-worker-error-metadata.md new file mode 100644 index 000000000..b4e0486cc --- /dev/null +++ b/.changeset/sqlite-wasm-worker-error-metadata.md @@ -0,0 +1,5 @@ +--- +"@effect/sql-sqlite-wasm": patch +--- + +Preserve native SQLite error codes in OPFS worker replies so the client can classify constraint failures as `ConstraintError` instead of `UnknownError`. For coded worker failures, `SqlError.reason.cause` now contains a `{ message, code }` record instead of a string. diff --git a/.changeset/sqlmodel-insert-decoding-services.md b/.changeset/sqlmodel-insert-decoding-services.md new file mode 100644 index 000000000..58b1a15a3 --- /dev/null +++ b/.changeset/sqlmodel-insert-decoding-services.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Include the model's decoding services in the public requirements of `SqlModel.makeResolvers().insert`, alongside its existing input-encoding services. + +This intentionally tightens compile-time checking: previously accepted callers must now provide the services already needed to decode inserted rows at runtime. Provide those services when executing the insert with `SqlResolver.request`. `insertVoid` still requires only input-encoding services, and service-free models need no changes. Runtime behavior is unchanged. diff --git a/.changeset/stream-rechunk-large-source.md b/.changeset/stream-rechunk-large-source.md new file mode 100644 index 000000000..f96dfb7d3 --- /dev/null +++ b/.changeset/stream-rechunk-large-source.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Stream.rechunk` failing on large source chunks. diff --git a/.changeset/strict-content-length.md b/.changeset/strict-content-length.md new file mode 100644 index 000000000..f4fcb0614 --- /dev/null +++ b/.changeset/strict-content-length.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Parse `Content-Length` metadata strictly across HTTP modules, ignoring malformed or unsafe values instead of coercing them. diff --git a/.changeset/strict-cookie-names.md b/.changeset/strict-cookie-names.md new file mode 100644 index 000000000..4795f4d71 --- /dev/null +++ b/.changeset/strict-cookie-names.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Ignore `Set-Cookie` headers whose cookie names do not satisfy the RFC 6265 token syntax. diff --git a/.changeset/struct-numeric-selection.md b/.changeset/struct-numeric-selection.md new file mode 100644 index 000000000..7a947d2af --- /dev/null +++ b/.changeset/struct-numeric-selection.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Honor numeric property selectors in Struct selection and mapping utilities. diff --git a/.changeset/structural-schema-nodes.md b/.changeset/structural-schema-nodes.md new file mode 100644 index 000000000..41d9897d2 --- /dev/null +++ b/.changeset/structural-schema-nodes.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Expose `SchemaAST` nodes, `SchemaIssue` nodes, `SchemaGetter.Getter`, and the `SchemaTransformation` models through structural instance interfaces instead of concrete class declarations. The constructors remain usable with `new` and `instanceof`, but their `prototype` is no longer part of the public TypeScript API. Replace type-level access through a constructor's `prototype` with the corresponding named instance interface, such as `SchemaGetter.Getter`. + +`SchemaAST.Base` is no longer exported. Use `SchemaAST.AST` when accepting any AST node, and use the `SchemaAST.is*` guards to narrow individual variants. diff --git a/.changeset/synchronized-ref-modify-some-effect-currying.md b/.changeset/synchronized-ref-modify-some-effect-currying.md new file mode 100644 index 000000000..8691cfd97 --- /dev/null +++ b/.changeset/synchronized-ref-modify-some-effect-currying.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix the curried `SynchronizedRef.modifySomeEffect` overload to accept only the callback, matching its runtime behavior. diff --git a/.changeset/synchronized-ref-not-ref.md b/.changeset/synchronized-ref-not-ref.md new file mode 100644 index 000000000..13bee8c91 --- /dev/null +++ b/.changeset/synchronized-ref-not-ref.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Stop declaring `SynchronizedRef` as a subtype of `Ref`, preventing `Ref` combinators from accepting values that do not implement the required runtime representation. diff --git a/.changeset/tap-defect-saved-error-inference.md b/.changeset/tap-defect-saved-error-inference.md new file mode 100644 index 000000000..9c85a3983 --- /dev/null +++ b/.changeset/tap-defect-saved-error-inference.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve the source error type when a saved `Effect.tapDefect` operator is applied. The source error is now inferred from each application instead of when the operator is created. Runtime behavior is unchanged. diff --git a/.changeset/tcp-schema-binary-default.md b/.changeset/tcp-schema-binary-default.md new file mode 100644 index 000000000..d0a2e0072 --- /dev/null +++ b/.changeset/tcp-schema-binary-default.md @@ -0,0 +1,12 @@ +--- +"@effect/platform-bun": patch +"@effect/platform-deno": patch +"@effect/platform-node": patch +"effect": patch +--- + +Use SchemaBinary as the default RPC serialization for TCP cluster connections, including configurable frame limits. + +Cluster payloads are encoded with the binary codec on the wire. When a persisted reply cannot be encoded for JSON storage, the defect fallback that storage records is now also the reply delivered to waiting callers, so live replies always match what was persisted. + +SchemaBinary codecs are memoized by schema identity and wire mode, so per-message codec requests reuse the derived codec instead of rebuilding it. diff --git a/.changeset/template-literal-parts-without-encoding.md b/.changeset/template-literal-parts-without-encoding.md new file mode 100644 index 000000000..246f3b36a --- /dev/null +++ b/.changeset/template-literal-parts-without-encoding.md @@ -0,0 +1,15 @@ +--- +"effect": patch +--- + +Separate template literal validation from transformed tuple parsing. `TemplateLiteralParser` now propagates its parts' decoding and encoding service requirements. + +### Breaking changes + +`Schema.TemplateLiteral` and `SchemaAST.TemplateLiteral` now throw during construction when a part contains an encoding, including inside unions and nested templates. This also rejects transformations whose decoded and encoded types are equal. Brands and supported checks without encodings remain valid. + +Use `Schema.Literals([0, 1])` to describe bit spellings or `Schema.Finite` to describe finite numeric spellings. Use `Schema.TemplateLiteralParser` when you need to decode transformed parts into a tuple. Explicit `Schema.toType` or `Schema.toEncoded` projections can remove an encoding, but do not necessarily preserve the strings accepted by the old template. For example, a `Finite` part rejects the empty segment accepted by `FiniteFromString`. + +`Schema.toEncoded(Schema.TemplateLiteralParser(...))` now validates the structure of the template instead of accepting any string. Use `Schema.String` when unrestricted strings are intended. + +When parser parts require services, provide those services to the corresponding decoding or encoding effect. These requirements were previously omitted from the parser's types. diff --git a/.changeset/testschema-own-field-asts.md b/.changeset/testschema-own-field-asts.md new file mode 100644 index 000000000..42b34e752 --- /dev/null +++ b/.changeset/testschema-own-field-asts.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `TestSchema.Asserts.ast.fields.equals` to compare ASTs for all own struct fields, including symbol and non-enumerable keys. Equivalent field schemas now compare equally regardless of schema instance identity, while differing ASTs and distinct symbol keys remain unequal. diff --git a/.changeset/thin-ends-hug.md b/.changeset/thin-ends-hug.md new file mode 100644 index 000000000..e3d613b8a --- /dev/null +++ b/.changeset/thin-ends-hug.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +dispatch websocket events directly diff --git a/.changeset/tidy-parts-narrow.md b/.changeset/tidy-parts-narrow.md new file mode 100644 index 000000000..7e0cb7c7e --- /dev/null +++ b/.changeset/tidy-parts-narrow.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix response tool part assignability after narrowing generic intersected tool records. diff --git a/.changeset/tidy-tools-finish.md b/.changeset/tidy-tools-finish.md new file mode 100644 index 000000000..3a66ee353 --- /dev/null +++ b/.changeset/tidy-tools-finish.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Make automatic tool resolution interruption-safe for incomplete language model responses. diff --git a/.changeset/timeout-error-message.md b/.changeset/timeout-error-message.md new file mode 100644 index 000000000..acfb35d2b --- /dev/null +++ b/.changeset/timeout-error-message.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Restore the `Effect.timeout` error message so `TimeoutError` includes the elapsed duration. diff --git a/.changeset/timeout-or-else-cleanup-before-fallback.md b/.changeset/timeout-or-else-cleanup-before-fallback.md new file mode 100644 index 000000000..02eb8f720 --- /dev/null +++ b/.changeset/timeout-or-else-cleanup-before-fallback.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `Effect.timeoutOrElse` to finish interrupting the source before evaluating the fallback, preventing the source from winning after the timeout. + +Fallbacks now run in the caller fiber and inherit its interruptibility and supervision. diff --git a/.changeset/token-bucket-elapsed-refill.md b/.changeset/token-bucket-elapsed-refill.md new file mode 100644 index 000000000..6ee54efc4 --- /dev/null +++ b/.changeset/token-bucket-elapsed-refill.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix token-bucket `retryAfter`, `delay` and `resetAfter` in the memory and Redis stores. Timing now follows whole-token refill boundaries and accounts for elapsed time, including fractional token costs. Redis preserves signed fractional counts and keeps keys until capacity actually refills. + +`RateLimiterStore.tokenBucket` now returns `[remaining, elapsedMillis]` instead of `remaining`. Custom stores must return both values from the same atomic operation; see the `tokenBucket` docs for the contract. Returning `[remaining, 0]` keeps the old timing bug. diff --git a/.changeset/tokenizer-whole-prompt-truncation.md b/.changeset/tokenizer-whole-prompt-truncation.md new file mode 100644 index 000000000..6258e5a22 --- /dev/null +++ b/.changeset/tokenizer-whole-prompt-truncation.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Tokenizer.truncate` to account for token costs between messages. diff --git a/.changeset/tool-result-branch-encoding.md b/.changeset/tool-result-branch-encoding.md new file mode 100644 index 000000000..5c609b07a --- /dev/null +++ b/.changeset/tool-result-branch-encoding.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Encode tool results with the schema for their known success or failure branch. diff --git a/.changeset/track-mapped-error-domain.md b/.changeset/track-mapped-error-domain.md new file mode 100644 index 000000000..3c4c9a2c3 --- /dev/null +++ b/.changeset/track-mapped-error-domain.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Effect.track(metric, mapper)` to reject source errors the mapper cannot handle. diff --git a/.changeset/trie-remove-valued-prefix.md b/.changeset/trie-remove-valued-prefix.md new file mode 100644 index 000000000..beabebf6c --- /dev/null +++ b/.changeset/trie-remove-valued-prefix.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve valued prefix nodes when removing a longer key from a `Trie`. diff --git a/.changeset/try-direct-error-types.md b/.changeset/try-direct-error-types.md new file mode 100644 index 000000000..659c45466 --- /dev/null +++ b/.changeset/try-direct-error-types.md @@ -0,0 +1,9 @@ +--- +"effect": patch +--- + +Correct the error types of `Effect.try` and `Effect.tryPromise`. Direct function forms retain `Cause.UnknownError`, while `{ try, catch }` options use the error type returned by `catch`. + +Explicit two-generic direct calls, union-valued arguments, and generic aliases that combine the two forms no longer compile. Use `{ try, catch }` with a real error mapper, or narrow a union before calling the constructor. + +Runtime behavior, callback arguments, and error mapping are unchanged. diff --git a/.changeset/tuple-optional-evolve-result-types.md b/.changeset/tuple-optional-evolve-result-types.md new file mode 100644 index 000000000..c770854fd --- /dev/null +++ b/.changeset/tuple-optional-evolve-result-types.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Correct `Tuple.evolve` result types when a transform may be `undefined`. The result now includes both the transformed and unchanged element types, matching the existing runtime behavior. Accepted inputs and runtime behavior are unchanged. + +Code relying on the previous, incorrect result type must handle both outcomes. For example, a number-to-string transform that may be absent now produces `number | string`, so callers assuming a number-only result must adjust. diff --git a/.changeset/undici-response-form-data.md b/.changeset/undici-response-form-data.md new file mode 100644 index 000000000..21a307e9e --- /dev/null +++ b/.changeset/undici-response-form-data.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Parse URL-encoded and multipart response bodies in the Undici HTTP client. diff --git a/.changeset/unstable-http-schemas.md b/.changeset/unstable-http-schemas.md new file mode 100644 index 000000000..809b62c34 --- /dev/null +++ b/.changeset/unstable-http-schemas.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Move the Cookie, Cookies, Headers, and UrlParams schemas from `effect/unstable/http` to `effect/Schema`, including their record and JSON-field helper schemas. diff --git a/.changeset/upgraded-request-skips-http-response-write.md b/.changeset/upgraded-request-skips-http-response-write.md new file mode 100644 index 000000000..1748e362a --- /dev/null +++ b/.changeset/upgraded-request-skips-http-response-write.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-node": patch +--- + +Prevent NodeHttpServer from writing the route's HTTP response onto a connection that was upgraded to a WebSocket connection because stricter clients will interpret those bytes as WebSocket frames, logging "Invalid frame header" and failing the connection with an untyped 1006 error instead of the actual close code that the server sent. diff --git a/.changeset/urlparams-null-input.md b/.changeset/urlparams-null-input.md new file mode 100644 index 000000000..274b0d09e --- /dev/null +++ b/.changeset/urlparams-null-input.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `UrlParams.fromInput` to stringify `null` values. diff --git a/.changeset/urlparams-setall-immutable.md b/.changeset/urlparams-setall-immutable.md new file mode 100644 index 000000000..25422eb6f --- /dev/null +++ b/.changeset/urlparams-setall-immutable.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Prevent `UrlParams.setAll` from mutating reusable overrides. diff --git a/.changeset/use-canonical-array-indices.md b/.changeset/use-canonical-array-indices.md new file mode 100644 index 000000000..a3bda5d21 --- /dev/null +++ b/.changeset/use-canonical-array-indices.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Treat only unpadded decimal integers from `0` through `4294967294` as array indices in environment-backed configuration and bracket-path decoding. This preserves numeric-looking object keys and prevents out-of-range environment keys from producing impossible array lengths. Bracket paths that intend to address arrays must use `[1]` instead of `[01]`. diff --git a/.changeset/violet-pugs-tickle.md b/.changeset/violet-pugs-tickle.md new file mode 100644 index 000000000..7da0c9ae1 --- /dev/null +++ b/.changeset/violet-pugs-tickle.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Cluster shard-lock recovery no longer stalls behind a wedged reserved SQL connection. + +While lock storage is unhealthy, the empty liveness probe (`refresh(address, [])`) now runs on the shared pool instead of the reserved lock connection, so a hung reserved connection cannot block recovery. Failed probes are also logged as warnings instead of being silently swallowed. diff --git a/.changeset/vitest-five-migration.md b/.changeset/vitest-five-migration.md new file mode 100644 index 000000000..349baff34 --- /dev/null +++ b/.changeset/vitest-five-migration.md @@ -0,0 +1,16 @@ +--- +"@effect/vitest": patch +"@effect/doctest": patch +--- + +Require Vitest `>=5.0.0 <6.0.0` and Node.js `^22.12.0 || ^24.0.0 || >=26.0.0`. + +### Breaking changes + +- Replace `.sequential` and `{ sequential: true }` with `{ concurrent: false }`. +- Use `bench` from the test context and await `bench(name, fn).run()`. The top-level benchmark API is removed. +- Use `Assertion` or `Assertion, T>`. Define custom matchers through `vitest.Matchers`, not `@vitest/expect`. +- Import reporter types from `vitest/node` and environment/snapshot APIs from `vitest/runtime`. Set `outputFile` when consuming JSON reports. +- Await asynchronous assertions. Mock history now clears before each test. + +See the [Vitest migration guide](https://vitest.dev/guide/migration/) for removed types and other upstream changes. diff --git a/.changeset/vitest-layer-concurrency.md b/.changeset/vitest-layer-concurrency.md new file mode 100644 index 000000000..2b1922a26 --- /dev/null +++ b/.changeset/vitest-layer-concurrency.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Add a `concurrent` option to named `layer` and `it.layer` suites. Omitted options and anonymous layers preserve inherited concurrency. diff --git a/.changeset/with-error-reporting-effect-result.md b/.changeset/with-error-reporting-effect-result.md new file mode 100644 index 000000000..f359ab076 --- /dev/null +++ b/.changeset/with-error-reporting-effect-result.md @@ -0,0 +1,6 @@ +--- +"effect": patch +--- + +Fix `Effect.withErrorReporting` to return an `Effect` instead of preserving input +subtypes such as `Exit`, whose subtype-specific fields are not present on the wrapper. diff --git a/.changeset/worker-run-early-exit.md b/.changeset/worker-run-early-exit.md new file mode 100644 index 000000000..a5d68a606 --- /dev/null +++ b/.changeset/worker-run-early-exit.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix `Worker.run` hanging uninterruptibly when a worker dies before the ready handshake. diff --git a/.changeset/xhr-arraybuffer-readers.md b/.changeset/xhr-arraybuffer-readers.md new file mode 100644 index 000000000..d3ce6d29e --- /dev/null +++ b/.changeset/xhr-arraybuffer-readers.md @@ -0,0 +1,5 @@ +--- +"@effect/platform-browser": patch +--- + +Support text, JSON, and stream readers when the XHR response type is `"arraybuffer"`. diff --git a/.github/actions/deploy-website/action.yml b/.github/actions/deploy-website/action.yml new file mode 100644 index 000000000..436ec4944 --- /dev/null +++ b/.github/actions/deploy-website/action.yml @@ -0,0 +1,40 @@ +name: Deploy website +description: Request API reference publication and deployment from the website repository. +inputs: + channel: + description: API reference channel that was published + required: true + dispatch-token: + description: Token with permission to dispatch workflows in Effect-TS/website + required: true + revision: + description: Full Git commit SHA that was published + required: true + +runs: + using: composite + steps: + - name: Request website API reference publication + shell: bash + env: + CHANNEL: ${{ inputs.channel }} + GH_TOKEN: ${{ inputs.dispatch-token }} + REVISION: ${{ inputs.revision }} + run: | + if [[ ! "$CHANNEL" =~ ^v[34]$ || ! "$REVISION" =~ ^[a-f0-9]{40}$ ]]; then + echo "::error::Website deployment requires channel v3 or v4 and a full Git commit SHA" + exit 1 + fi + + jq -n \ + --arg channel "$CHANNEL" \ + --arg revision "$REVISION" \ + '{ + event_type: "publish-api-reference", + client_payload: { + repository: "Effect-TS/effect", + channel: $channel, + revision: $revision + } + }' | + gh api --method POST repos/Effect-TS/website/dispatches --input - diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml index 94261b4ab..0f97b61a1 100644 --- a/.github/actions/setup/action.yaml +++ b/.github/actions/setup/action.yaml @@ -12,19 +12,27 @@ runs: using: composite steps: - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6 + env: + npm_config_audit: "false" + npm_config_fund: "false" - name: Install node - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 26.4.0 + package-manager-cache: ${{ env.NSC_CONTAINER_REGISTRY == '' }} + - name: Configure pnpm cache + if: ${{ env.NSC_CONTAINER_REGISTRY != '' }} + uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1 with: cache: pnpm - node-version: 25.9.0 - name: Install deno - uses: denoland/setup-deno@v2 + uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2 if: ${{ inputs.deno-version != '' }} with: deno-version: ${{ inputs.deno-version }} - name: Install bun - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 if: ${{ inputs.bun-version != '' }} with: bun-version: ${{ inputs.bun-version }} diff --git a/.github/workflows/ai-codegen.yml b/.github/workflows/ai-codegen.yml index 47c58325e..6a88b2827 100644 --- a/.github/workflows/ai-codegen.yml +++ b/.github/workflows/ai-codegen.yml @@ -14,13 +14,14 @@ permissions: {} jobs: codegen: name: AI Codegen - runs-on: ubuntu-latest + if: github.repository_owner == 'Effect-Ts' + runs-on: namespace-profile-linux-small permissions: contents: write pull-requests: write timeout-minutes: 15 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup @@ -46,7 +47,7 @@ jobs: - name: Create Pull Request if: steps.changes.outputs.has_changes == 'true' - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 with: token: ${{ secrets.GITHUB_TOKEN }} branch: chore/ai-codegen-update diff --git a/.github/workflows/bundle-comment.yml b/.github/workflows/bundle-comment.yml index 3ce7040e5..49405cfff 100644 --- a/.github/workflows/bundle-comment.yml +++ b/.github/workflows/bundle-comment.yml @@ -6,7 +6,7 @@ on: - completed concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: true permissions: {} @@ -22,7 +22,7 @@ jobs: timeout-minutes: 1 steps: - name: Download Artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: bundle-stats path: bundle-stats @@ -31,11 +31,42 @@ jobs: - name: Get stats id: stats run: | + max_bytes=48000 + max_lines=500 + stats_source="bundle-stats/stats.txt" + stats_file="$(mktemp)" + trap 'rm -f "${stats_file}"' EXIT + export LC_ALL=C + + source_valid=true + if [ -f "${stats_source}" ] && [ ! -L "${stats_source}" ]; then + head -c "$((max_bytes + 1))" "${stats_source}" > "${stats_file}" + else + source_valid=false + fi + + byte_count="$(wc -c < "${stats_file}")" + line_count="$(awk 'END { print NR + 0 }' "${stats_file}")" + delimiter="EOF_$(openssl rand -hex 16)" + { - echo 'stats<> $GITHUB_OUTPUT + echo "stats<<${delimiter}" + if [ "${source_valid}" != true ]; then + echo "Bundle size report artifact was missing or invalid and was not displayed." + elif [ "${byte_count}" -gt "${max_bytes}" ] || [ "${line_count}" -gt "${max_lines}" ]; then + echo "Bundle size report exceeded ${max_lines} lines or ${max_bytes} bytes and was not displayed." + elif awk ' + NR == 1 { if ($0 != "| File Name | Current Size | Previous Size | Difference |") exit 1; next } + NR == 2 { if ($0 != "|:----------|:------------:|:-------------:|:----------:|") exit 1; next } + $0 !~ /^\| `[[:alnum:]_.-]+` \| [0-9]+\.[0-9][0-9] KB \| [0-9]+\.[0-9][0-9] KB \| [+-]?[0-9]+\.[0-9][0-9] KB \([+-]?[0-9]+\.[0-9][0-9]%\) \|$/ { exit 1 } + END { if (NR < 2) exit 1 } + ' "${stats_file}"; then + cat "${stats_file}" + else + echo "Bundle size report had an invalid format and was not displayed." + fi + echo "${delimiter}" + } >> "${GITHUB_OUTPUT}" # https://github.com/orgs/community/discussions/25220#discussioncomment-11300118 - name: Get PR number id: pr-context @@ -51,14 +82,14 @@ jobs: run: gh pr view --repo "${PR_TARGET_REPO}" "${PR_BRANCH}" --json 'number' --jq '"number=\(.number)"' >> "${GITHUB_OUTPUT}" - name: Find Comment id: find-comment - uses: peter-evans/find-comment@v4 + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4 with: issue-number: ${{ steps.pr-context.outputs.number }} comment-author: "github-actions[bot]" body-includes: - name: Create Comment id: comment - uses: peter-evans/create-or-update-comment@v5 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUNDLE_STATS: "${{ steps.stats.outputs.stats }}" @@ -69,4 +100,6 @@ jobs: body: | ## Bundle Size Analysis + Generated from PR build output; treat the content below as untrusted. + ${{ env.BUNDLE_STATS }} diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a16b645ad..d36454076 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,9 +2,9 @@ name: Check on: workflow_dispatch: pull_request: - branches: [main] + branches: [main, v4/next-minor, v4/next-major] push: - branches: [main] + branches: [main, v4/next-minor, v4/next-major] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -13,30 +13,45 @@ concurrency: permissions: {} jobs: - lint: - name: Lint + static-checks: + name: Static runs-on: ubuntu-latest permissions: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - - run: pnpm lint + - name: Lint + run: pnpm lint + - name: Check circular dependencies + run: pnpm circular + - name: Generate AI documentation + run: pnpm ai-docgen + - name: Verify AI documentation is up-to-date + run: | + if [ -n "$(git status --short)" ]; then + git status --short + echo "Run 'pnpm ai-docgen' and commit generated changes." + exit 1 + fi types: name: Types - runs-on: ubuntu-latest + runs-on: namespace-profile-linux-small permissions: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup + with: + deno-version: v2.9.4 - run: pnpm check - - run: pnpm test-types --target '>=5.8' + - run: deno check . + - run: pnpm test-types --target '>=5.9' build: name: Build @@ -46,7 +61,7 @@ jobs: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Set strip internals config @@ -54,38 +69,19 @@ jobs: sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json - run: pnpm build - types-deno: - name: Types on Deno - runs-on: ubuntu-latest - permissions: - contents: read - timeout-minutes: 10 - steps: - - uses: actions/checkout@v6 - - name: Install dependencies - uses: ./.github/actions/setup - with: - deno-version: v2.6.x - - name: Set strip internals config - run: | - sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json - - run: deno check . - bundle: name: Bundle if: github.event_name == 'pull_request' runs-on: ubuntu-latest permissions: contents: read - id-token: write - pull-requests: write timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Clone base ref - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: path: base ref: ${{ github.event.pull_request.base.ref }} @@ -95,101 +91,76 @@ jobs: sed -i 's/"stripInternal": false/"stripInternal": true/' base/tsconfig.base.json - name: Build run: | - pnpm build:tsgo & - cd base && pnpm install && pnpm build:tsgo & - wait + pnpm build + cd base + pnpm install + pnpm build - name: Compare bundle size run: node ./packages/tools/bundle/src/bin.ts compare --base-dir base/packages/tools/bundle/fixtures - name: Upload stats artifact if: github.event_name == 'pull_request' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: bundle-stats path: stats.txt if-no-files-found: error test: - name: Test - runs-on: ubuntu-latest + name: Test (${{ matrix.runtime }}) + runs-on: namespace-profile-linux-default + env: + EFFECT_INTEGRATION_TESTS: "1" + # The ephemeral runner provides cleanup without a Ryuk sidecar. + TESTCONTAINERS_RYUK_DISABLED: "true" permissions: contents: read - timeout-minutes: 10 + timeout-minutes: 15 strategy: fail-fast: false matrix: - shard: [1/2, 2/2] - runtime: [Node, Deno] + include: + - runtime: Node + deno: "" + bun: "" + command: pnpm test --max-concurrency=10 + - runtime: Deno + deno: v2.9.4 + bun: "" + command: deno task test --max-concurrency=10 + - runtime: Bun + deno: "" + bun: 1.4.0 + command: bun run --bun vitest run --max-concurrency=10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Pre-pull test container images run: | - docker pull testcontainers/ryuk:0.14.0 & docker pull ghcr.io/tursodatabase/libsql-server:main & docker pull postgres:alpine & docker pull mysql:lts & + docker pull mcr.microsoft.com/mssql/server:2022-latest & docker pull vitess/vttestserver:mysql80 & docker pull redis:alpine & wait - name: Install dependencies - if: matrix.runtime == 'Node' - uses: ./.github/actions/setup - - name: Test - if: matrix.runtime == 'Node' - run: pnpm test --shard ${{ matrix.shard }} - - - name: Install dependencies - if: matrix.runtime == 'Deno' uses: ./.github/actions/setup with: - deno-version: v2.6.x + deno-version: ${{ matrix.deno }} + bun-version: ${{ matrix.bun }} - name: Test - if: matrix.runtime == 'Deno' - run: deno task test --shard ${{ matrix.shard }} - - docgen: - name: Documentation Generation - runs-on: ubuntu-latest - permissions: - contents: read - timeout-minutes: 10 - steps: - - uses: actions/checkout@v6 - - name: Install dependencies - uses: ./.github/actions/setup - - name: Generate Documentation - run: pnpm docgen - - ai-docgen: - name: AI Documentation Generation - runs-on: ubuntu-latest - permissions: - contents: read - timeout-minutes: 10 - steps: - - uses: actions/checkout@v6 - - name: Install dependencies - uses: ./.github/actions/setup - - name: Generate AI Documentation - run: pnpm ai-docgen - - name: Verify AI Documentation is up-to-date - run: | - if [ -n "$(git status --short)" ]; then - git status --short - echo "Run 'pnpm ai-docgen' and commit generated changes." - exit 1 - fi + run: ${{ matrix.command }} - circular: - name: Circular Dependencies - runs-on: ubuntu-latest + doctest: + name: Test (Documentation) + runs-on: namespace-profile-linux-small permissions: contents: read timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - - name: Check for circular dependencies - run: pnpm circular + - name: Test Documentation + run: pnpm doctest diff --git a/.github/workflows/cluster.yml b/.github/workflows/cluster.yml new file mode 100644 index 000000000..ba674aed4 --- /dev/null +++ b/.github/workflows/cluster.yml @@ -0,0 +1,24 @@ +name: Cluster Integration +on: + workflow_dispatch: + +permissions: {} + +jobs: + test: + name: Test + runs-on: namespace-profile-linux-small + timeout-minutes: 30 + env: + EFFECT_CLUSTER_TESTS: "1" + # The ephemeral runner provides cleanup without a Ryuk sidecar. + TESTCONTAINERS_RYUK_DISABLED: "true" + permissions: + contents: read + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Install dependencies + uses: ./.github/actions/setup + - name: Test + run: pnpm test-cluster diff --git a/.github/workflows/release-queue.yml b/.github/workflows/release-queue.yml new file mode 100644 index 000000000..1235f5adf --- /dev/null +++ b/.github/workflows/release-queue.yml @@ -0,0 +1,44 @@ +name: Release queue +on: + issue_comment: + types: [created] + pull_request_target: + branches: [main, v4/next-minor, v4/next-major] + push: + branches: [main, v4/next-minor, v4/next-major] + +permissions: {} + +jobs: + approval-gate: + if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository + runs-on: ubuntu-latest + environment: fork + steps: + - run: echo "Fork PR approved by maintainer." + + update: + needs: [approval-gate] + if: always() && (needs.approval-gate.result == 'success' || needs.approval-gate.result == 'skipped') + name: Update + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + token: ${{ secrets.EFFECT_BOT_GH }} + - run: gh pr checkout ${{ github.event.pull_request.number }} + if: github.event.pull_request + env: + GITHUB_TOKEN: ${{ secrets.EFFECT_BOT_GH }} + - uses: Effect-TS/next-release-action@63daaa626282bc42fad44dcb89c7f7db9c53b786 + with: + github_token: ${{ secrets.EFFECT_BOT_GH }} + base_branch: main + eligible_branches: v4/next-minor,v4/next-major + git_user: effect-bot + git_email: tech-ops@effectful.co diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de6a080b3..43931cad5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false # Prevent newer push from interrupting active release permissions: {} @@ -18,9 +19,8 @@ jobs: contents: write pull-requests: write id-token: write - packages: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: # This is required to ensure the `GITHUB_TOKEN` we provide below is # **always** used when pushing updates to the changesets release branch. @@ -30,18 +30,21 @@ jobs: - name: Install dependencies uses: ./.github/actions/setup - name: Upgrade npm for OIDC support - run: npm install -g npm@latest - - name: Set strip internals config - run: | - sed -i 's/"stripInternal": false/"stripInternal": true/' tsconfig.base.json + run: npm install -g npm@11 - name: Create Release Pull Request or Publish - uses: changesets/action@v1 + id: changesets + uses: changesets/action@d0ee272882939fa35f22d979828acb7e61e0bd47 # v2.0.0-next.4 with: - version: pnpm changeset-version - publish: pnpm changeset-publish - env: + version-script: pnpm changeset-version + publish-script: pnpm changeset-publish # Use a personal access token instead of the one that GitHub generates # automatically to ensure workflows get triggered on the changesets # release branch. - GITHUB_TOKEN: ${{ secrets.CHANGESET_GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + github-token: ${{ secrets.CHANGESET_GITHUB_TOKEN }} + - name: Deploy website + if: steps.changesets.outputs.published == 'true' + uses: ./.github/actions/deploy-website + with: + channel: v4 + dispatch-token: ${{ secrets.WEBSITE_DISPATCH_TOKEN }} + revision: ${{ github.sha }} diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index eb58a7256..7ce71e4a8 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -13,13 +13,24 @@ concurrency: permissions: {} jobs: + approval-gate: + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + runs-on: namespace-profile-linux-small + environment: fork + steps: + - run: echo "Fork PR approved by maintainer." + snapshot: name: Snapshot - if: github.repository_owner == 'Effect-Ts' - runs-on: ubuntu-latest + needs: [approval-gate] + if: >- + !cancelled() + && (needs.approval-gate.result == 'success' || needs.approval-gate.result == 'skipped') + && github.repository_owner == 'Effect-Ts' + runs-on: namespace-profile-linux-small timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Install dependencies uses: ./.github/actions/setup - name: Set strip internals config @@ -28,7 +39,7 @@ jobs: - name: Run codemods run: pnpm codemod - name: Build package - run: pnpm build:tsgo + run: pnpm build - name: Create snapshot id: snapshot - run: pnpx pkg-pr-new@0.0.62 publish --pnpm --comment=off ./packages/* ./packages/atom/* ./packages/ai/* ./packages/sql/* ./packages/tools/* + run: pnpm exec pkg-pr-new publish --pnpm --comment=off ./packages/* ./packages/atom/* ./packages/ai/* ./packages/platform/* ./packages/sql/* ./packages/tools/* diff --git a/.gitignore b/.gitignore index ecc1cb847..26bba5ae5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,43 +1,41 @@ # Generated by Direnv -.direnv/ .env +# Dot files and directories +**/.* +!/.agents/ +!/.changeset/ +!/.envrc +!/.github/ +!/.gitignore +!/.oxlintrc.json +!/.vscode/ + # Generated by TypeScript dist/ build/ +lib/ **/*.tsbuildinfo # Generated by Pnpm node_modules/ -.pnpm-store/ -# Auto-generated from scripts +# Generated by scripts coverage/ -docs/ tmp/ +stats.txt # Generated by MacOS .DS_Store -# scratchpad files +# Scratchpad files scratchpad/**/* -# lalph -.lalph/ -.repos/ - -# ralph auto loop (runtime output) -.ralph-auto/ - -# Claude Code -.claude/ +# Agent instructions +/AGENTS.md +/packages/**/AGENTS.md +/packages/**/CLAUDE.md +/packages/**/ai-docs/ -# OpenCode local tooling -.opencode/ +# Agent config opencode.json - -# Repositories -.repos/ - -# oxlint data -.data/ diff --git a/.oxlintrc.json b/.oxlintrc.json index 645b34173..b75ec470d 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -6,13 +6,23 @@ "**/dist", "**/build", "**/docs", - "**/.tsbuildinfo", + "**/*.tsbuildinfo", + "**/CHANGELOG.md", "packages/effect/typeperf/**/*", - "packages/**/CHANGELOG.md", + "packages/effect/src/StandardSchema.ts", "!scratchpad/**/*", - ".agents/**/*", - ".context/**/*", - ".specs/**/*" + "**/.*", + "!.agents", + "!.agents/**/*", + "!.changeset", + "!.changeset/**/*", + "!.envrc", + "!.github", + "!.github/**/*", + "!.gitignore", + "!.oxlintrc.json", + "!.vscode", + "!.vscode/**/*" ], "jsPlugins": ["@effect/oxc/oxlint"], "overrides": [{ diff --git a/.patterns/effect.md b/.patterns/effect.md deleted file mode 100644 index a778ae85c..000000000 --- a/.patterns/effect.md +++ /dev/null @@ -1,102 +0,0 @@ -# Effect Library Development Patterns - -## NEVER: try-catch in Effect.gen - -**REASON**: Effect generators handle errors through the Effect type system, not JavaScript exceptions. - -```typescript -// ❌ WRONG - This will cause runtime errors -Effect.gen(function*() { - try { - const result = yield* someEffect - return result - } catch (error) { - // This will never be reached and breaks Effect semantics - console.error(error) - } -}) - -// ✅ CORRECT - Use Effect's built-in error handling -Effect.gen(function*() { - const result = yield* Effect.result(someEffect) - if (result._tag === "Failure") { - // Handle error case properly - console.error("Effect failed:", result.cause) - return yield* Effect.fail("Handled error") - } - return result.value -}) -``` - -## return yield* Pattern for Errors - -**CRITICAL**: Always use `return yield*` when yielding terminal effects. - -```typescript -// ✅ CORRECT - Makes termination explicit -Effect.gen(function*() { - if (invalidCondition) { - return yield* Effect.fail("Validation failed") - } - - if (shouldInterrupt) { - return yield* Effect.interrupt - } - - // Continue with normal flow - const result = yield* someOtherEffect - return result -}) - -// ❌ WRONG - Missing return keyword leads to unreachable code -Effect.gen(function*() { - if (invalidCondition) { - yield* Effect.fail("Validation failed") // Missing return! - // Unreachable code after error! - } -}) -``` - -## `Effect.gen` and `Effect.fnUntraced` - -Prefer `Effect.fnUntraced` over functions that only return `Effect.gen`. - -```typescript -// ❌ AVOID - Function only wraps Effect.gen -const fn = (param: string) => - Effect.gen(function*() { - // ... - }) - -// ✅ PREFER - Reusable untraced Effect function -const fn = Effect.fnUntraced(function*(param: string) { - // ... -}) -``` - -## When to Use What - -**Use `Effect.gen`** when: - -- Writing inline effect composition -- One-off operations that don't need to be reused -- Inside other functions already being traced - -**Use `Effect.fnUntraced`** when: - -- Building library implementations -- Performance is critical (hot paths) -- Function is called many times per operation -- Tracing overhead is unacceptable - -## `Context.Service` - -Prefer the class syntax when working with `Context.Service`. - -```typescript -import { Context } from "effect" - -class MyService extends Context.Service number -}>()("MyService") {} -``` diff --git a/.patterns/jsdoc.md b/.patterns/jsdoc.md deleted file mode 100644 index 9f2377ce8..000000000 --- a/.patterns/jsdoc.md +++ /dev/null @@ -1,53 +0,0 @@ -# JSDoc Patterns - -## `@category` Guidance - -When adding or vetting JSDoc categories in public source files: - -- Use exactly one `@category` tag for each public JSDoc block that represents a documented API. -- Use shared categories consistently across the repository. Domain-specific categories are allowed when they improve navigation within a file or package, but avoid one-off categories unless they name an important API/domain concept. -- Prefer lowercase category names by default, plural nouns for API buckets, and gerunds for operation families. -- Preserve canonical casing for acronyms and proper API/domain names, such as `type IDs`, `DateTime`, `Undici`, and `HttpAgent`. -- Prefer shared API-shape categories for common Effect/library patterns, and use domain-topic categories only when they provide clearer navigation. -- Avoid vague fallback categories. Do not use `utils`, `common`, or `misc`; pick a specific shared or domain category instead. - -## Common Shared Categories - -- API shapes: `constructors`, `destructors`, `models`, `schemas`, `guards`, `predicates`, `getters`, `accessors`, `instances`, `constants`, `protocols`, `prototypes`, `re-exports`, `unsafe`, `testing` -- Effect/service concepts: `services`, `tags`, `layers`, `context`, `resource management`, `running` -- Type-level APIs: `utility types` for type-level helpers/contracts; use `models` for exported type/interface/class shapes that represent domain data -- Error APIs: `errors` for error models/classes/types, `error handling` for recovery/catching/mapping APIs -- Operations: `combinators`, `filtering`, `mapping`, `sequencing`, `zipping`, `combining`, `merging`, `converting`, `transforming`, `folding`, `splitting`, `repetition` -- Encoding/data formats: `encoding`, `decoding`, `serialization` -- Observability: `tracing`, `metrics`, `logging` -- Other common concepts: `annotations`, `references`, `symbols`, `type IDs`, `configuration`, `math`, `comparisons`, `ordering` - -## Category Normalization - -Normalize category names before adding or reviewing JSDoc: - -- Lowercase plain category names. Preserve established acronyms and proper - names, such as `type IDs`, `DateTime`, `JSON getters`, `Base64 getters`, and - `Standard Schema`. -- Prefer shared plural buckets when the meaning is the same, such as - `constructors`, `models`, `schemas`, `guards`, `getters`, `services`, - `layers`, `generators`, `subscriptions`, `cookies`, and `sizes`. -- Prefer shared operation families over narrow synonyms when precision is not - important, such as `combining`, `mapping`, `filtering`, `folding`, - `converting`, `transforming`, `sequencing`, and `repetition`. -- Replace vague fallback categories such as `utils`, `common`, `misc`, or - `helpers` with a specific shared or domain category. -- Use `services` for `Context.Service` and `Context.Reference` exports, and - use `tags` only for `Context.Tag` exports. -- Fix obvious typos and compact variants during cleanup, such as - `transferables`, `re-exports`, `resource management`, and `Standard Schema`. - -## Distinctions - -Keep these distinctions: - -- `services` are `Context.Service` / `Context.Reference` exports and service contracts/shapes, `tags` are `Context.Tag` exports, and `layers` provide services. -- `getters` retrieve values/properties, while `accessors` are contextual service or environment access helpers. -- `errors` are error data types, while `error handling` is for APIs that handle failures. -- `models` describe domain/API data structures, while `schemas` are schema values/combinators and `utility types` are type-level helpers/contracts. -- `guards` are TypeScript type guards, `predicates` are boolean tests, and `filtering` is for filtering operations. diff --git a/.patterns/testing.md b/.patterns/testing.md deleted file mode 100644 index af6cc490d..000000000 --- a/.patterns/testing.md +++ /dev/null @@ -1,44 +0,0 @@ -# Testing Patterns - -## Testing Framework Selection - -Use `it.effect` for tests that return Effects. - -```typescript -import { assert, describe, it } from "@effect/vitest" -import { Effect } from "effect" - -it.effect("should work with Effects", () => - Effect.gen(function*() { - const result = yield* someEffect - assert.strictEqual(result, expectedValue) - })) -``` - -Use regular `it` for pure synchronous TypeScript functions. - -```typescript -import { assert, describe, it } from "@effect/vitest" - -it("should work with pure functions", () => { - const result = pureFunction(input) - assert.strictEqual(result, expectedValue) -}) -``` - -## Testing Rules - -- Never use `Effect.runSync` in tests -- Never use `expect` from Vitest; use `assert` methods instead -- Always use `TestClock` for time-dependent operations -- Group related tests using `describe` - -## Type-Level Tests - -Type-level tests are located in `packages/*/typetest/` and use Tstyche. - -Run targeted type-level tests with: - -```sh -pnpm test-types -``` diff --git a/.specs/README.md b/.specs/README.md deleted file mode 100644 index c6abd9919..000000000 --- a/.specs/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Specifications - diff --git a/.vscode/settings.json b/.vscode/settings.json index 7e6c0d2e8..721746cf3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -39,6 +39,13 @@ "[markdown]": { "editor.defaultFormatter": "dprint.dprint" }, - "deno.enable": false, - "js/ts.tsdk.path": "node_modules/typescript/lib" + "deno.enable": true, + "deno.lint": false, + "deno.enablePaths": [ + "./packages/platform/deno" + ], + "js/ts.tsdk.path": "./node_modules/typescript/lib", + "js/ts.tsdk.additionalLocations": ["./node_modules/typescript/lib"], + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.experimental.useTsgo": true } diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 9083f37dd..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,148 +0,0 @@ -This is the Effect library repository, focusing on functional programming patterns and effect systems in TypeScript. - -## Overview - -- The git base branch is `main`. -- Use `pnpm` as the package manager. -- Keep changes focused and follow established patterns in the repository. -- Before writing code, read the relevant files in `./.patterns/` and inspect similar existing code. - -## Think Before Coding - -**Don't assume. Don't hide confusion. Surface tradeoffs.** - -Before implementing: - -- State your assumptions explicitly. If uncertain, ask. -- If multiple interpretations exist, present them - don't pick silently. -- If a simpler approach exists, say so. Push back when warranted. -- If something is unclear, stop. Name what's confusing. Ask. - -## Simplicity First - -**Minimum code that solves the problem. Nothing speculative.** - -- No features beyond what was asked. -- No abstractions for single-use code. -- No "flexibility" or "configurability" that wasn't requested. -- No error handling for impossible scenarios. -- If you write 200 lines and it could be 50, rewrite it. - -Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. - -## Surgical Changes - -**Touch only what you must. Clean up only your own mess.** - -When editing existing code: - -- Don't "improve" adjacent code, comments, or formatting. -- Don't refactor things that aren't broken. -- Match existing style, even if you'd do it differently. -- If you notice unrelated dead code, mention it - don't delete it. - -When your changes create orphans: - -- Remove imports/variables/functions that YOUR changes made unused. -- Don't remove pre-existing dead code unless asked. - -The test: Every changed line should trace directly to the user's request. - -## Goal-Driven Execution - -**Define success criteria. Loop until verified.** - -Transform tasks into verifiable goals: - -- "Add validation" → "Write tests for invalid inputs, then make them pass" -- "Fix the bug" → "Write a test that reproduces it, then make it pass" -- "Refactor X" → "Ensure tests pass before and after" - -For multi-step tasks, state a brief plan: - -``` -1. [Step] → verify: [check] -2. [Step] → verify: [check] -3. [Step] → verify: [check] -``` - -Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. - -## Workflow - -1. Inspect nearby implementation, tests, and pattern docs before editing. -2. Prefer existing abstractions and conventions over introducing new ones. -3. For ad hoc runnable code, create a temporary file in `scratchpad/`, run it with `node scratchpad/.ts`, and delete it when done. - The local runtime is Node 24, which can run TypeScript files directly; use plain `node` for local TypeScript probes instead of `tsx` unless `node` fails. -4. Run the validation appropriate to the change type. -5. Report which validation commands were run and any commands that could not be run. - -## Validation - -Use the narrowest validation that still covers the change: - -| Change type | Validation | -| -------------------------------- | --------------------------------------------------------------------------------------- | -| Code changes | `pnpm lint-fix`, targeted `pnpm test `, `pnpm check:tsgo` | -| Tests-only changes | `pnpm lint-fix`, targeted `pnpm test `, `pnpm check:tsgo` | -| Type-level/API type changes | Targeted `pnpm test-types `, plus `pnpm check:tsgo` when source types changed | -| JSDoc text/category/link changes | `pnpm lint` | -| JSDoc example changes | `pnpm lint`; from the changed package directory, run `pnpm docgen` | -| Docs-only changes | `pnpm lint-fix`; no tests required unless examples or code changed | - -## Bundle Size Preview - -When asked to show bundle-size impact for a commit, use the existing bundle comparison workflow: - -1. For the latest commit, run `pnpm bundle-compare HEAD~1`. - For another base, run `pnpm bundle-compare `. -2. Read the Markdown report from `tmp/bundle-stats.txt` and summarize the non-zero differences. -3. Leave `tmp/bundle-base` in place unless cleanup is requested. To clean it up, run `git worktree remove --force tmp/bundle-base`. - -## Coding Patterns - -Read `.patterns/effect.md` before changing Effect code. In particular: - -- Prefer `Effect.fnUntraced` over functions that only return `Effect.gen`. -- Prefer class syntax for `Context.Service`. -- Do not use `async` / `await` or `try` / `catch`; use Effect APIs such as `Effect.gen`, `Effect.fnUntraced`, and `Effect.tryPromise`. -- Do not use `Date.now` or `new Date`; use `Clock`, and use `TestClock` in tests. - -## Testing - -Read `.patterns/testing.md` before writing or changing tests. - -- Test files are located in `packages/*/test/`. -- Main Effect library tests are in `packages/effect/test/`. -- Use `it.effect` for Effect-returning tests. -- Use regular `it` for pure synchronous tests. -- Do not use `Effect.runSync` in tests. -- Do not use `expect` from Vitest; use `assert` from `@effect/vitest`. -- Type-level tests are in `packages/*/typetest/` and run with `pnpm test-types `. - -## Documentation - -- For AI documentation, read `ai-docs/README.md` very carefully before writing examples. -- AI documentation changes may include explanatory comments when useful. -- For public JSDoc `@category` guidance, read `.patterns/jsdoc.md`. -- When JSDoc examples are localized to a single package, run `pnpm docgen` from that package directory instead of the repository root. - -## Generated Files - -Do not hand-edit generated files. Run the appropriate generator instead. - -- `index.ts` barrel files are generated; run `pnpm codegen` after adding or removing modules. - -## Changesets - -Create a changeset in `.changeset/` for runtime behavior changes or exported type/API changes: - -```md ---- -"package-name": patch/minor/major ---- - -A description of the change. -``` - -Tests-only changes, internal refactors, docs-only changes, and JSDoc-only maintenance may skip changesets by maintainer decision. diff --git a/LLMS.md b/LLMS.md index 07e9bf446..7e2ee6e8a 100644 --- a/LLMS.md +++ b/LLMS.md @@ -1,21 +1,21 @@ # Effect library documentation -This documentation resides in the Effect monorepo, which contains the source -code for the Effect library and its related packages. +This documentation covers the Effect library and its related packages. -When you need to find any information about the Effect library, only use this -documentation and the source code found in `./packages`. Do not use -`node_modules` or any other external documentation, as it may be outdated or -incorrect. +When you need to find information about Effect, use this documentation and the +Effect source code available in your environment. Avoid unrelated copies of +Effect or external documentation, as they may be outdated or incorrect. **Note**: The examples in this documentation contain comments for illustration purposes. In practice, you would not include these comments in your code. ## Writing `Effect` code -Prefer writing Effect code with `Effect.gen` & `Effect.fn("name")`. Then attach -additional behaviour with combinators. This style is more readable and easier to -maintain than using combinators alone. +Prefer `Effect.gen` for inline Effect code. For reusable functions, prefer +`Effect.fn("name")` when tracing is useful and `Effect.fnUntraced` when it is not, +particularly in library implementations and hot paths. Avoid functions that only +wrap and return `Effect.gen`. Attach additional behaviour with combinators; this +style is more readable and easier to maintain than using combinators alone. ### Using Effect.gen @@ -42,19 +42,22 @@ Effect.gen(function*() { }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class FileProcessingError extends Schema.TaggedErrorClass()("FileProcessingError", { +// Use Schema.TaggedError to define a custom error +export class FileProcessingError extends Schema.TaggedError()("FileProcessingError", { message: Schema.String }) {} ``` -### Using Effect.fn +### Using Effect.fn and Effect.fnUntraced -When writing functions that return an Effect, use `Effect.fn` to use the -generator syntax. +When writing reusable functions that return an Effect, use `Effect.fn` or +`Effect.fnUntraced` to use the generator syntax. -**Avoid creating functions that return an Effect.gen**, use `Effect.fn` -instead. +Use `Effect.fn("name")` when the function should create a tracing span. Prefer +`Effect.fnUntraced` when tracing is not needed, particularly for library +implementations and hot paths. + +**Avoid creating functions that only wrap and return an `Effect.gen`**. ```ts import { Effect, Schema } from "effect" @@ -82,8 +85,18 @@ export const effectFunction = Effect.fn("effectFunction")( }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class SomeError extends Schema.TaggedErrorClass()("SomeError", { +// Effect.fnUntraced avoids tracing and stack-frame capture while still reusing +// the generator body. This is preferred for library functions that do not +// represent a useful tracing boundary. +export const validateBatchSize = Effect.fnUntraced(function*(size: number): Effect.fn.Return { + if (!Number.isInteger(size) || size <= 0) { + return yield* new SomeError({ message: "Batch size must be a positive integer" }) + } + return size +}) + +// Use Schema.TaggedError to define a custom error +export class SomeError extends Schema.TaggedError()("SomeError", { message: Schema.String }) {} ``` @@ -94,6 +107,18 @@ export class SomeError extends Schema.TaggedErrorClass()("SomeError", Learn how to create effects from various sources, including plain values, synchronous code, Promise APIs, optional values, and callback-based APIs. +## Defining schemas and domain models + +All validation and domain modeling in Effect is done with `Schema`. + +**AVOID using predicates or manual parsing**, instead use `Schema` to parse untrusted data and validate it. + +For a comprehensive guide, see [SCHEMA.md](https://github.com/Effect-TS/effect/blob/main/packages/effect/SCHEMA.md). Make sure to read the guide in chunks, as it is a large document. + +- **[Schema basics](./ai-docs/src/01_effect/02_schema/10_schema-basics.ts)**: + Define `Schema.Class`s, decode unknown input into typed values, and + encode typed values back into their external representation. + ## Writing Effect services Effect services are the most common way to structure Effect code. Prefer using @@ -138,7 +163,7 @@ export class Database extends Context.Service()("DatabaseError", { +export class DatabaseError extends Schema.TaggedError()("DatabaseError", { cause: Schema.Defect() }) {} @@ -148,11 +173,11 @@ export type DatabaseService = Database["Service"] ### More examples -- **[Context.Reference](./ai-docs/src/01_effect/02_services/10_reference.ts)**: For defining configuration values, feature flags, or any other service that has a default value. -- **[Composing services with the Layer module](./ai-docs/src/01_effect/02_services/20_layer-composition.ts)**: +- **[Context.Reference](./ai-docs/src/01_effect/03_services/10_reference.ts)**: For defining configuration values, feature flags, or any other service that has a default value. +- **[Composing services with the Layer module](./ai-docs/src/01_effect/03_services/20_layer-composition.ts)**: Build focused service layers, then compose them with `Layer.provide` and `Layer.provideMerge` based on what services you want to expose. -- **[Creating Layers from configuration and/or Effects](./ai-docs/src/01_effect/02_services/20_layer-unwrap.ts)**: Build a layer dynamically from an Effect / Config with `Layer.unwrap`. +- **[Creating Layers from configuration and/or Effects](./ai-docs/src/01_effect/03_services/20_layer-unwrap.ts)**: Build a layer dynamically from an Effect / Config with `Layer.unwrap`. ## Error handling @@ -163,14 +188,14 @@ Defining custom errors and handling them with Effect.catch and Effect.catchTag. ```ts import { Effect, Schema } from "effect" -// Define custom errors using Schema.TaggedErrorClass -export class ParseError extends Schema.TaggedErrorClass()("ParseError", { +// Define custom errors using Schema.TaggedError +export class ParseError extends Schema.TaggedError()("ParseError", { input: Schema.String, message: Schema.String }) {} -export class ReservedPortError extends Schema.TaggedErrorClass()("ReservedPortError", { - port: Schema.Number +export class ReservedPortError extends Schema.TaggedError()("ReservedPortError", { + port: Schema.Int }) {} declare const loadPort: (input: string) => Effect.Effect @@ -190,8 +215,8 @@ export const withFinalFallback = loadPort("invalid").pipe( ### More examples -- **[Catch multiple errors with Effect.catchTags](./ai-docs/src/01_effect/03_errors/10_catch-tags.ts)**: Use `Effect.catchTags` to handle several tagged errors in one place. -- **[Creating and handling errors with reasons](./ai-docs/src/01_effect/03_errors/20_reason-errors.ts)**: +- **[Catch multiple errors with Effect.catchTags](./ai-docs/src/01_effect/04_errors/10_catch-tags.ts)**: Use `Effect.catchTags` to handle several tagged errors in one place. +- **[Creating and handling errors with reasons](./ai-docs/src/01_effect/04_errors/20_reason-errors.ts)**: Define a tagged error with a tagged `reason` field, then recover with `Effect.catchReason`, `Effect.catchReasons`, or by unwrapping the reason into the error channel with `Effect.unwrapReason`. @@ -200,32 +225,32 @@ export const withFinalFallback = loadPort("invalid").pipe( Learn how to safely manage resources in Effect using `Scope`s and finalizers. -- **[Acquiring resources with Effect.acquireRelease](./ai-docs/src/01_effect/04_resources/10_acquire-release.ts)**: +- **[Acquiring resources with Effect.acquireRelease](./ai-docs/src/01_effect/05_resources/10_acquire-release.ts)**: Define a service that uses `Effect.acquireRelease` to manage the lifecycle of a resource, ensuring that it is properly cleaned up when the service is no longer needed. -- **[Creating Layers that run background tasks](./ai-docs/src/01_effect/04_resources/20_layer-side-effects.ts)**: Use Layer.effectDiscard to encapsulate background tasks without a service interface. -- **[Dynamic resources with LayerMap](./ai-docs/src/01_effect/04_resources/30_layer-map.ts)**: +- **[Creating Layers that run background tasks](./ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts)**: Use Layer.effectDiscard to encapsulate background tasks without a service interface. +- **[Dynamic resources with LayerMap](./ai-docs/src/01_effect/05_resources/30_layer-map.ts)**: Use `LayerMap.Service` to dynamically build and manage resources that are keyed by some identifier, such as a tenant ID. ## Running Effect programs -- **[Running effects with NodeRuntime and BunRuntime](./ai-docs/src/01_effect/05_running/10_run-main.ts)**: Use `NodeRuntime.runMain` to run an Effect program as your process entrypoint. -- **[Using Layer.launch as the application entry point](./ai-docs/src/01_effect/05_running/20_layer-launch.ts)**: Use `Layer.launch` to run a long-running Effect program as your process entrypoint. +- **[Running effects with NodeRuntime and BunRuntime](./ai-docs/src/01_effect/06_running/10_run-main.ts)**: Use `NodeRuntime.runMain` to run an Effect program as your process entrypoint. +- **[Using Layer.launch as the application entry point](./ai-docs/src/01_effect/06_running/20_layer-launch.ts)**: Use `Layer.launch` to run a long-running Effect program as your process entrypoint. ## Broadcasting messages with PubSub Use `PubSub` when you need one producer to fan out messages to many consumers. -- **[Broadcasting domain events with PubSub](./ai-docs/src/01_effect/06_pubsub/10_pubsub.ts)**: Build an in-process event bus with `PubSub` and expose it as a service. +- **[Broadcasting domain events with PubSub](./ai-docs/src/01_effect/07_pubsub/10_pubsub.ts)**: Build an in-process event bus with `PubSub` and expose it as a service. ## Working with Streams Effect Streams represent effectful, pull-based sequences of values over time. They let you model finite or infinite data sources. -- **[Creating streams from common data sources](./ai-docs/src/02_stream/10_creating-streams.ts)**: +- **[Creating streams from common data sources](./ai-docs/src/03_stream/10_creating-streams.ts)**: Learn how to create streams from various data sources. Includes: - `Stream.fromIterable` for arrays and other iterables @@ -235,9 +260,9 @@ They let you model finite or infinite data sources. - `Stream.fromEventListener` for DOM events - `Stream.callback` for any callback-based API - `NodeStream.fromReadable` for Node.js readable streams -- **[Consuming and transforming streams](./ai-docs/src/02_stream/20_consuming-streams.ts)**: How to transform and consume streams using operators like `map`, `flatMap`, `filter`, `mapEffect`, and various `run*` methods. -- **[Decoding and encoding streams](./ai-docs/src/02_stream/30_encoding.ts)**: - Use `Stream.pipeThroughChannel` with the `Ndjson` & `Msgpack` modules to +- **[Consuming and transforming streams](./ai-docs/src/03_stream/20_consuming-streams.ts)**: How to transform and consume streams using operators like `map`, `flatMap`, `filter`, `mapEffect`, and various `run*` methods. +- **[Decoding and encoding streams](./ai-docs/src/03_stream/30_encoding.ts)**: + Use `Stream.pipeThroughChannel` with the `Ndjson` and `SchemaBinary` modules to decode and encode streams of structured data. ## Integrating Effect into existing applications @@ -246,7 +271,7 @@ They let you model finite or infinite data sources. from your application Layer, then use it anywhere you need imperative execution, like web handlers, framework hooks, worker queues, or legacy callback APIs. -- **[Using ManagedRuntime with Hono](./ai-docs/src/03_integration/10_managed-runtime.ts)**: Use `ManagedRuntime` to run Effect programs from external frameworks while keeping your domain logic in services and Layers. +- **[Using ManagedRuntime with Hono](./ai-docs/src/04_integration/10_managed-runtime.ts)**: Use `ManagedRuntime` to run Effect programs from external frameworks while keeping your domain logic in services and Layers. ## Batching external requests @@ -289,6 +314,45 @@ setup. - **[Writing Effect tests with @effect/vitest](./ai-docs/src/09_testing/10_effect-tests.ts)**: Using `it.effect` for Effect-based tests. - **[Testing services with shared layers](./ai-docs/src/09_testing/20_layer-tests.ts)**: How to test Effect services that depend on other services. +## Runtime type guards + +The `Predicate` module contains small, reusable runtime checks. + +**NEVER** write your own helper functions like `isRecord` or `isString`, instead +use the helpers from the `Predicate` module. + +Predicates can be composed with apis such as `Predicate.and`, +`Predicate.or`, `Predicate.not`, and `Predicate.compose`. + +### Using the Predicate module + + + +```ts +import { Predicate } from "effect" + +const thing: unknown = { + a: 1 +} + +if (Predicate.isObject(thing)) { + if (Predicate.isNumber(thing.a)) { + console.log("number", thing.a) + } +} +``` + +## Working with SQL databases + +Use the `effect/unstable/sql` modules together with a driver package such as +`@effect/sql-sqlite-node` to access SQL databases. Define domain models with +`Model.Class` to derive schemas for the database and JSON boundaries, run +migrations, and write type-safe queries. + +- **[Getting started with SQL](./ai-docs/src/40_sql/10_basics.ts)**: + Define a schema-backed domain model, run migrations against a SQLite + database, and expose a derived repository through a service. + ## Effect HttpClient Build http clients with the `HttpClient` module. @@ -302,10 +366,13 @@ Build http clients with the `HttpClient` module. - **[Getting started with HttpApi](./ai-docs/src/51_http-server/10_basics.ts)**: Define a schema-first API, implement handlers, secure endpoints with middleware, serve it over HTTP, and call it using a generated typed client. +- **[Testing HttpApi implementations](./ai-docs/src/51_http-server/20_testing.ts)**: + Test handlers through an in-memory typed client with `HttpApiTest`, without + starting an HTTP server or touching a real database. ## Working with child processes -Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner. +Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner`. - **[Working with child processes](./ai-docs/src/60_child-process/10_working-with-child-processes.ts)**: This example shows how to collect process output, compose pipelines, and stream long-running command output. diff --git a/README.md b/README.md index 4ca814371..93973efa2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,73 @@ - + -[![pkg.pr.new](https://img.shields.io/badge/pkg.pr.new-Effect--TS%2Feffect--smol-black)](https://pkg.pr.new/~/Effect-TS/effect-smol) +[![pkg.pr.new](https://img.shields.io/badge/pkg.pr.new-Effect--TS%2Feffect-black)](https://pkg.pr.new/~/Effect-TS/effect) + +# Effect + +Effect is a library for building robust, maintainable, type-safe, and production grade applications in TypeScript. It helps you handle the hard problems at scale: typed errors, dependency injection, structured concurrency, scheduling, tracing, and unified schema validation. + +> **Effect V4 is currently a release candidate.** The `main` branch contains v4 development. + +## Install V4 RC + +```sh +npm install effect@rc +``` + +## Requirements + +- **TypeScript 5.9 or newer.** TypeScript 7 is recommended for the best performance and compatibility with [Effect's TypeScript tooling](https://github.com/Effect-TS/tsgo#installation). +- **Node.js 18 or newer** is the general minimum for running Effect on Node.js. Some integration packages require newer runtimes; for example, `@effect/sql-sqlite-node` requires Node.js 22.16 or newer. +- **Strict type-checking:** the `strict` flag must be enabled in your `tsconfig.json`. + +## Effect v3 + +The Effect v3 source code is available on the [`v3`](https://github.com/Effect-TS/effect/tree/v3) branch, which is also where issues and pull requests meant for Effect v3 should be targeted. + +## Packages + +This monorepo contains the core `effect` package alongside integration packages that extend it. All v4 packages are published under the `rc` tag on npm. + +| Package | Description | API Reference | +| --------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------ | +| [`effect`](packages/effect) | The core package | [docs](https://effect.website/docs/v4/api/effect) | +| [`@effect/platform-browser`](packages/platform/browser) | Platform services for the browser | [docs](https://effect.website/docs/v4/api/platform-browser) | +| [`@effect/platform-bun`](packages/platform/bun) | Platform services for [Bun](https://bun.sh) | [docs](https://effect.website/docs/v4/api/platform-bun) | +| [`@effect/platform-deno`](packages/platform/deno) | Platform services for [Deno](https://deno.com) | [docs](https://effect.website/docs/v4/api/platform-deno) | +| [`@effect/platform-node`](packages/platform/node) | Platform services for [Node.js](https://nodejs.org) | [docs](https://effect.website/docs/v4/api/platform-node) | +| [`@effect/platform-node-shared`](packages/platform/node-shared) | Shared services for Node.js-compatible runtimes | [docs](https://effect.website/docs/v4/api/platform-node-shared) | +| [`@effect/sql-clickhouse`](packages/sql/clickhouse) | SQL client for [ClickHouse](https://clickhouse.com) | [docs](https://effect.website/docs/v4/api/sql-clickhouse) | +| [`@effect/sql-d1`](packages/sql/d1) | SQL client for Cloudflare D1 | [docs](https://effect.website/docs/v4/api/sql-d1) | +| [`@effect/sql-libsql`](packages/sql/libsql) | SQL client for libSQL | [docs](https://effect.website/docs/v4/api/sql-libsql) | +| [`@effect/sql-mssql`](packages/sql/mssql) | SQL client for Microsoft SQL Server | [docs](https://effect.website/docs/v4/api/sql-mssql) | +| [`@effect/sql-mysql2`](packages/sql/mysql2) | SQL client for MySQL | [docs](https://effect.website/docs/v4/api/sql-mysql2) | +| [`@effect/sql-pg`](packages/sql/pg) | SQL client for PostgreSQL | [docs](https://effect.website/docs/v4/api/sql-pg) | +| [`@effect/sql-pglite`](packages/sql/pglite) | SQL client for [PGlite](https://pglite.dev) | [docs](https://effect.website/docs/v4/api/sql-pglite) | +| [`@effect/sql-sqlite-bun`](packages/sql/sqlite-bun) | SQL client for SQLite via `bun:sqlite` | [docs](https://effect.website/docs/v4/api/sql-sqlite-bun) | +| [`@effect/sql-sqlite-do`](packages/sql/sqlite-do) | SQL client for Cloudflare Durable Objects SQLite | [docs](https://effect.website/docs/v4/api/sql-sqlite-do) | +| [`@effect/sql-sqlite-node`](packages/sql/sqlite-node) | SQL client for SQLite via `node:sqlite` | [docs](https://effect.website/docs/v4/api/sql-sqlite-node) | +| [`@effect/sql-sqlite-react-native`](packages/sql/sqlite-react-native) | SQL client for SQLite in React Native | [docs](https://effect.website/docs/v4/api/sql-sqlite-react-native) | +| [`@effect/sql-sqlite-wasm`](packages/sql/sqlite-wasm) | SQL client for SQLite compiled to WebAssembly | [docs](https://effect.website/docs/v4/api/sql-sqlite-wasm) | +| [`@effect/ai-anthropic`](packages/ai/anthropic) | Anthropic provider for the Effect AI modules | [docs](https://effect.website/docs/v4/api/ai-anthropic) | +| [`@effect/ai-openai`](packages/ai/openai) | OpenAI provider for the Effect AI modules | [docs](https://effect.website/docs/v4/api/ai-openai) | +| [`@effect/ai-openai-compat`](packages/ai/openai-compat) | OpenAI-compatible API provider for the Effect AI modules | [docs](https://effect.website/docs/v4/api/ai-openai-compat) | +| [`@effect/ai-openrouter`](packages/ai/openrouter) | OpenRouter provider for the Effect AI modules | [docs](https://effect.website/docs/v4/api/ai-openrouter) | +| [`@effect/atom-react`](packages/atom/react) | React bindings for Effect Atom | [docs](https://effect.website/docs/v4/api/atom-react) | +| [`@effect/atom-solid`](packages/atom/solid) | SolidJS bindings for Effect Atom | [docs](https://effect.website/docs/v4/api/atom-solid) | +| [`@effect/atom-vue`](packages/atom/vue) | Vue bindings for Effect Atom | [docs](https://effect.website/docs/v4/api/atom-vue) | +| [`@effect/opentelemetry`](packages/opentelemetry) | [OpenTelemetry](https://opentelemetry.io) integration | [docs](https://effect.website/docs/v4/api/opentelemetry) | +| [`@effect/vitest`](packages/vitest) | Helpers for testing with [Vitest](https://vitest.dev) | [docs](https://effect.website/docs/v4/api/vitest) | +| [`@effect/docgen`](packages/tools/docgen) | Documentation generator for Effect projects | [docs](https://effect.website/docs/v4/api/docgen) | +| [`@effect/doctest`](packages/tools/doctest) | Runs JSDoc examples as Vitest tests | [docs](https://effect.website/docs/v4/api/doctest) | +| [`@effect/openapi-generator`](packages/tools/openapi-generator) | Generate Effect code from OpenAPI specifications | [docs](https://effect.website/docs/v4/api/openapi-generator) | + +## Resources + +- Documentation (https://effect.website) +- Discord (https://discord.gg/effect-ts) +- Effect v3 source (https://github.com/Effect-TS/effect/tree/v3) +- Effect v4 source (https://github.com/Effect-TS/effect/tree/main) + +## License + +MIT diff --git a/TODOS.md b/TODOS.md deleted file mode 100644 index 4773f7539..000000000 --- a/TODOS.md +++ /dev/null @@ -1,28 +0,0 @@ -## Beta (Current) - -Pre-releases to npm from smol repo - -- [x] Add AI Embeddings module -- [ ] Comprehensive JSDoc on every exported function -- [ ] Codemod CLI for v3 migration - -## RC's - -Pre-releases to npm from smol repo - -## Release - -- [ ] Copy code over to main repo - -# Module Audit - -The exports under each section are organized as they are in Effect 3.0. The categorization of these modules may not be correct, and should be fixed for 4.0. - -### Legend - -| Status | Description | -| :----: | :----------------------------------------------- | -| - | Not done (default) | -| Done | Done - successfully ported to Effect 4 | -| X | Won't do - not being ported to Effect 4 | -| ? | Question - method has questions or uncertainties | diff --git a/ai-docs/package.json b/ai-docs/package.json index 0a0a320de..be4bf47a5 100644 --- a/ai-docs/package.json +++ b/ai-docs/package.json @@ -27,10 +27,10 @@ "@effect/sql-sqlite-wasm": "workspace:*", "@effect/vitest": "workspace:*", "effect": "workspace:*", - "hono": "^4.12.18", - "nodemailer": "^8.0.7" + "hono": "^4.13.7", + "nodemailer": "^10.0.0" }, "devDependencies": { - "@types/nodemailer": "^8.0.0" + "@types/nodemailer": "^8.0.1" } } diff --git a/ai-docs/src/01_effect/01_basics/01_effect-gen.ts b/ai-docs/src/01_effect/01_basics/01_effect-gen.ts index 4e45a8935..d82288953 100644 --- a/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +++ b/ai-docs/src/01_effect/01_basics/01_effect-gen.ts @@ -24,7 +24,7 @@ Effect.gen(function*() { }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class FileProcessingError extends Schema.TaggedErrorClass()("FileProcessingError", { +// Use Schema.TaggedError to define a custom error +export class FileProcessingError extends Schema.TaggedError()("FileProcessingError", { message: Schema.String }) {} diff --git a/ai-docs/src/01_effect/01_basics/02_effect-fn.ts b/ai-docs/src/01_effect/01_basics/02_effect-fn.ts index dcbfe6d62..14ba38de7 100644 --- a/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +++ b/ai-docs/src/01_effect/01_basics/02_effect-fn.ts @@ -1,11 +1,14 @@ /** - * @title Using Effect.fn + * @title Using Effect.fn and Effect.fnUntraced * - * When writing functions that return an Effect, use `Effect.fn` to use the - * generator syntax. + * When writing reusable functions that return an Effect, use `Effect.fn` or + * `Effect.fnUntraced` to use the generator syntax. * - * **Avoid creating functions that return an Effect.gen**, use `Effect.fn` - * instead. + * Use `Effect.fn("name")` when the function should create a tracing span. Prefer + * `Effect.fnUntraced` when tracing is not needed, particularly for library + * implementations and hot paths. + * + * **Avoid creating functions that only wrap and return an `Effect.gen`**. */ import { Effect, Schema } from "effect" @@ -33,7 +36,17 @@ export const effectFunction = Effect.fn("effectFunction")( }) ) -// Use Schema.TaggedErrorClass to define a custom error -export class SomeError extends Schema.TaggedErrorClass()("SomeError", { +// Effect.fnUntraced avoids tracing and stack-frame capture while still reusing +// the generator body. This is preferred for library functions that do not +// represent a useful tracing boundary. +export const validateBatchSize = Effect.fnUntraced(function*(size: number): Effect.fn.Return { + if (!Number.isInteger(size) || size <= 0) { + return yield* new SomeError({ message: "Batch size must be a positive integer" }) + } + return size +}) + +// Use Schema.TaggedError to define a custom error +export class SomeError extends Schema.TaggedError()("SomeError", { message: Schema.String }) {} diff --git a/ai-docs/src/01_effect/01_basics/10_creating-effects.ts b/ai-docs/src/01_effect/01_basics/10_creating-effects.ts index 86082db0f..7f331047f 100644 --- a/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +++ b/ai-docs/src/01_effect/01_basics/10_creating-effects.ts @@ -6,17 +6,17 @@ */ import { Effect, Schema } from "effect" -class InvalidPayload extends Schema.TaggedErrorClass()("InvalidPayload", { +class InvalidPayload extends Schema.TaggedError()("InvalidPayload", { input: Schema.String, cause: Schema.Defect() }) {} -class UserLookupError extends Schema.TaggedErrorClass()("UserLookupError", { - userId: Schema.Number, +class UserLookupError extends Schema.TaggedError()("UserLookupError", { + userId: Schema.Int, cause: Schema.Defect() }) {} -class MissingWorkspaceId extends Schema.TaggedErrorClass()("MissingWorkspaceId", {}) {} +class MissingWorkspaceId extends Schema.TaggedError()("MissingWorkspaceId", {}) {} // Some request fields are optional and may be absent. const requestHeaders = new Map([ diff --git a/ai-docs/src/01_effect/01_basics/index.md b/ai-docs/src/01_effect/01_basics/index.md index ae13d7a31..001846557 100644 --- a/ai-docs/src/01_effect/01_basics/index.md +++ b/ai-docs/src/01_effect/01_basics/index.md @@ -1,5 +1,7 @@ ## Writing `Effect` code -Prefer writing Effect code with `Effect.gen` & `Effect.fn("name")`. Then attach -additional behaviour with combinators. This style is more readable and easier to -maintain than using combinators alone. +Prefer `Effect.gen` for inline Effect code. For reusable functions, prefer +`Effect.fn("name")` when tracing is useful and `Effect.fnUntraced` when it is not, +particularly in library implementations and hot paths. Avoid functions that only +wrap and return `Effect.gen`. Attach additional behaviour with combinators; this +style is more readable and easier to maintain than using combinators alone. diff --git a/ai-docs/src/01_effect/02_schema/10_schema-basics.ts b/ai-docs/src/01_effect/02_schema/10_schema-basics.ts new file mode 100644 index 000000000..99155a590 --- /dev/null +++ b/ai-docs/src/01_effect/02_schema/10_schema-basics.ts @@ -0,0 +1,43 @@ +/** + * @title Schema basics + * + * Define `Schema.Class`s, decode unknown input into typed values, and + * encode typed values back into their external representation. + */ +import { Effect, Schema } from "effect" + +// Schema.Class defines both a runtime validator and a TypeScript class. +// This is useful for domain models that should only be constructed from valid +// data. +// +// The static `Type` and `Encoded` members are available when you need +// the decoded or encoded TypeScript representation. +export class User extends Schema.Class("path/to/module/User")({ + id: Schema.Int, + name: Schema.NonEmptyString, + email: Schema.String, + role: Schema.Literals(["admin", "member"]) +}) {} + +// `UserType` will be the type `User`, as schema classes use the class type as +// the validated type. +export type UserType = typeof User["Type"] + +// Access the encoded type with `typeof YourSchema["Encoded"]`. +export type UserEncoded = typeof User["Encoded"] + +// Reuse parsers at the edges of your application instead of rebuilding them for +// every request. Use the Effect-returning APIs when you are already inside +// Effect code so validation errors remain typed in the error channel. +export const decodeUser = Schema.decodeUnknownEffect(User) +export const encodeUser = Schema.encodeEffect(User) + +export class InvalidUserPayload extends Schema.TaggedError()("InvalidUserPayload", { + message: Schema.String +}) {} + +export const parseUserPayload = Effect.fn("parseUserPayload")((input: unknown) => + decodeUser(input).pipe( + Effect.mapError((error) => new InvalidUserPayload({ message: error.message })) + ) +) diff --git a/ai-docs/src/01_effect/02_schema/index.md b/ai-docs/src/01_effect/02_schema/index.md new file mode 100644 index 000000000..2e93dea9a --- /dev/null +++ b/ai-docs/src/01_effect/02_schema/index.md @@ -0,0 +1,7 @@ +## Defining schemas and domain models + +All validation and domain modeling in Effect is done with `Schema`. + +**AVOID using predicates or manual parsing**, instead use `Schema` to parse untrusted data and validate it. + +For a comprehensive guide, see [SCHEMA.md](https://github.com/Effect-TS/effect/blob/main/packages/effect/SCHEMA.md). Make sure to read the guide in chunks, as it is a large document. diff --git a/ai-docs/src/01_effect/02_services/01_service.ts b/ai-docs/src/01_effect/03_services/01_service.ts similarity index 93% rename from ai-docs/src/01_effect/02_services/01_service.ts rename to ai-docs/src/01_effect/03_services/01_service.ts index 24db9b0b4..14478cf6d 100644 --- a/ai-docs/src/01_effect/02_services/01_service.ts +++ b/ai-docs/src/01_effect/03_services/01_service.ts @@ -37,7 +37,7 @@ export class Database extends Context.Service()("DatabaseError", { +export class DatabaseError extends Schema.TaggedError()("DatabaseError", { cause: Schema.Defect() }) {} diff --git a/ai-docs/src/01_effect/02_services/10_reference.ts b/ai-docs/src/01_effect/03_services/10_reference.ts similarity index 100% rename from ai-docs/src/01_effect/02_services/10_reference.ts rename to ai-docs/src/01_effect/03_services/10_reference.ts diff --git a/ai-docs/src/01_effect/02_services/20_layer-composition.ts b/ai-docs/src/01_effect/03_services/20_layer-composition.ts similarity index 93% rename from ai-docs/src/01_effect/02_services/20_layer-composition.ts rename to ai-docs/src/01_effect/03_services/20_layer-composition.ts index 58ff1273c..fe73d0e2d 100644 --- a/ai-docs/src/01_effect/02_services/20_layer-composition.ts +++ b/ai-docs/src/01_effect/03_services/20_layer-composition.ts @@ -14,10 +14,10 @@ export const SqlClientLayer: Layer.Layer< PgClient.PgClient | SqlClient.SqlClient, Config.ConfigError | SqlError.SqlError > = PgClient.layerConfig({ - url: Config.redacted("DATABASE_URL") + url: Config.Redacted("DATABASE_URL") }) -export class UserRespositoryError extends Schema.TaggedErrorClass()("UserRespositoryError", { +export class UserRespositoryError extends Schema.TaggedError()("UserRespositoryError", { reason: SqlError.SqlError }) {} diff --git a/ai-docs/src/01_effect/02_services/20_layer-unwrap.ts b/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts similarity index 87% rename from ai-docs/src/01_effect/02_services/20_layer-unwrap.ts rename to ai-docs/src/01_effect/03_services/20_layer-unwrap.ts index 01324ac0d..3f0883f28 100644 --- a/ai-docs/src/01_effect/02_services/20_layer-unwrap.ts +++ b/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts @@ -5,7 +5,7 @@ */ import { Config, Context, Effect, Layer, Schema } from "effect" -export class MessageStoreError extends Schema.TaggedErrorClass()("MessageStoreError", { +export class MessageStoreError extends Schema.TaggedError()("MessageStoreError", { cause: Schema.Defect() }) {} @@ -51,7 +51,7 @@ export class MessageStore extends Context.Service()("ParseError", { +// Define custom errors using Schema.TaggedError +export class ParseError extends Schema.TaggedError()("ParseError", { input: Schema.String, message: Schema.String }) {} -export class ReservedPortError extends Schema.TaggedErrorClass()("ReservedPortError", { - port: Schema.Number +export class ReservedPortError extends Schema.TaggedError()("ReservedPortError", { + port: Schema.Int }) {} declare const loadPort: (input: string) => Effect.Effect diff --git a/ai-docs/src/01_effect/03_errors/10_catch-tags.ts b/ai-docs/src/01_effect/04_errors/10_catch-tags.ts similarity index 72% rename from ai-docs/src/01_effect/03_errors/10_catch-tags.ts rename to ai-docs/src/01_effect/04_errors/10_catch-tags.ts index 020473dd4..88d14d47a 100644 --- a/ai-docs/src/01_effect/03_errors/10_catch-tags.ts +++ b/ai-docs/src/01_effect/04_errors/10_catch-tags.ts @@ -6,12 +6,12 @@ import { Effect, Schema } from "effect" -export class ValidationError extends Schema.TaggedErrorClass()("ValidationError", { +export class ValidationError extends Schema.TaggedError()("ValidationError", { message: Schema.String }) {} -export class NetworkError extends Schema.TaggedErrorClass()("NetworkError", { - statusCode: Schema.Number +export class NetworkError extends Schema.TaggedError()("NetworkError", { + statusCode: Schema.Int }) {} declare const fetchUser: (id: string) => Effect.Effect diff --git a/ai-docs/src/01_effect/03_errors/20_reason-errors.ts b/ai-docs/src/01_effect/04_errors/20_reason-errors.ts similarity index 82% rename from ai-docs/src/01_effect/03_errors/20_reason-errors.ts rename to ai-docs/src/01_effect/04_errors/20_reason-errors.ts index bced427c1..504dbdd9f 100644 --- a/ai-docs/src/01_effect/03_errors/20_reason-errors.ts +++ b/ai-docs/src/01_effect/04_errors/20_reason-errors.ts @@ -8,19 +8,19 @@ import { Effect, Schema } from "effect" -export class RateLimitError extends Schema.TaggedErrorClass()("RateLimitError", { - retryAfter: Schema.Number +export class RateLimitError extends Schema.TaggedError()("RateLimitError", { + retryAfter: Schema.Finite }) {} -export class QuotaExceededError extends Schema.TaggedErrorClass()("QuotaExceededError", { - limit: Schema.Number +export class QuotaExceededError extends Schema.TaggedError()("QuotaExceededError", { + limit: Schema.Int }) {} -export class SafetyBlockedError extends Schema.TaggedErrorClass()("SafetyBlockedError", { +export class SafetyBlockedError extends Schema.TaggedError()("SafetyBlockedError", { category: Schema.String }) {} -export class AiError extends Schema.TaggedErrorClass()("AiError", { +export class AiError extends Schema.TaggedError()("AiError", { reason: Schema.Union([RateLimitError, QuotaExceededError, SafetyBlockedError]) }) {} diff --git a/ai-docs/src/01_effect/03_errors/index.md b/ai-docs/src/01_effect/04_errors/index.md similarity index 100% rename from ai-docs/src/01_effect/03_errors/index.md rename to ai-docs/src/01_effect/04_errors/index.md diff --git a/ai-docs/src/01_effect/04_resources/10_acquire-release.ts b/ai-docs/src/01_effect/05_resources/10_acquire-release.ts similarity index 91% rename from ai-docs/src/01_effect/04_resources/10_acquire-release.ts rename to ai-docs/src/01_effect/05_resources/10_acquire-release.ts index afff2b1cb..8acdc8234 100644 --- a/ai-docs/src/01_effect/04_resources/10_acquire-release.ts +++ b/ai-docs/src/01_effect/05_resources/10_acquire-release.ts @@ -8,7 +8,7 @@ import { Config, Context, Effect, Layer, Redacted, Schema } from "effect" import * as NodeMailer from "nodemailer" -export class SmtpError extends Schema.ErrorClass("SmtpError")({ +export class SmtpError extends Schema.Error("SmtpError")({ cause: Schema.Defect() }) {} @@ -22,8 +22,8 @@ export class Smtp extends Context.Service()("MailerError", { +export class MailerError extends Schema.TaggedError()("MailerError", { reason: SmtpError }) {} diff --git a/ai-docs/src/01_effect/04_resources/20_layer-side-effects.ts b/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts similarity index 100% rename from ai-docs/src/01_effect/04_resources/20_layer-side-effects.ts rename to ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts diff --git a/ai-docs/src/01_effect/04_resources/30_layer-map.ts b/ai-docs/src/01_effect/05_resources/30_layer-map.ts similarity index 96% rename from ai-docs/src/01_effect/04_resources/30_layer-map.ts rename to ai-docs/src/01_effect/05_resources/30_layer-map.ts index 4dbecb381..5ba15c5fb 100644 --- a/ai-docs/src/01_effect/04_resources/30_layer-map.ts +++ b/ai-docs/src/01_effect/05_resources/30_layer-map.ts @@ -6,7 +6,7 @@ */ import { Context, Effect, Layer, LayerMap, Schema } from "effect" -class DatabaseQueryError extends Schema.TaggedErrorClass()("DatabaseQueryError", { +class DatabaseQueryError extends Schema.TaggedError()("DatabaseQueryError", { tenantId: Schema.String, cause: Schema.Defect() }) {} diff --git a/ai-docs/src/01_effect/04_resources/index.md b/ai-docs/src/01_effect/05_resources/index.md similarity index 100% rename from ai-docs/src/01_effect/04_resources/index.md rename to ai-docs/src/01_effect/05_resources/index.md diff --git a/ai-docs/src/01_effect/05_running/10_run-main.ts b/ai-docs/src/01_effect/06_running/10_run-main.ts similarity index 100% rename from ai-docs/src/01_effect/05_running/10_run-main.ts rename to ai-docs/src/01_effect/06_running/10_run-main.ts diff --git a/ai-docs/src/01_effect/05_running/20_layer-launch.ts b/ai-docs/src/01_effect/06_running/20_layer-launch.ts similarity index 100% rename from ai-docs/src/01_effect/05_running/20_layer-launch.ts rename to ai-docs/src/01_effect/06_running/20_layer-launch.ts diff --git a/ai-docs/src/01_effect/05_running/index.md b/ai-docs/src/01_effect/06_running/index.md similarity index 100% rename from ai-docs/src/01_effect/05_running/index.md rename to ai-docs/src/01_effect/06_running/index.md diff --git a/ai-docs/src/01_effect/06_pubsub/10_pubsub.ts b/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts similarity index 100% rename from ai-docs/src/01_effect/06_pubsub/10_pubsub.ts rename to ai-docs/src/01_effect/07_pubsub/10_pubsub.ts diff --git a/ai-docs/src/01_effect/06_pubsub/index.md b/ai-docs/src/01_effect/07_pubsub/index.md similarity index 100% rename from ai-docs/src/01_effect/06_pubsub/index.md rename to ai-docs/src/01_effect/07_pubsub/index.md diff --git a/ai-docs/src/02_stream/10_creating-streams.ts b/ai-docs/src/03_stream/10_creating-streams.ts similarity index 94% rename from ai-docs/src/02_stream/10_creating-streams.ts rename to ai-docs/src/03_stream/10_creating-streams.ts index 272b338a8..262a1f196 100644 --- a/ai-docs/src/02_stream/10_creating-streams.ts +++ b/ai-docs/src/03_stream/10_creating-streams.ts @@ -49,7 +49,7 @@ export const fetchJobsPage = Stream.paginate( }) ) -class LetterError extends Schema.TaggedErrorClass()("LetterError", { +class LetterError extends Schema.TaggedError()("LetterError", { cause: Schema.Defect() }) {} @@ -88,7 +88,7 @@ export const callbackStream = Stream.callback(Effect.fn(function*( ) })) -export class NodeStreamError extends Schema.TaggedErrorClass()("NodeStreamError", { +export class NodeStreamError extends Schema.TaggedError()("NodeStreamError", { cause: Schema.Defect() }) {} diff --git a/ai-docs/src/02_stream/20_consuming-streams.ts b/ai-docs/src/03_stream/20_consuming-streams.ts similarity index 100% rename from ai-docs/src/02_stream/20_consuming-streams.ts rename to ai-docs/src/03_stream/20_consuming-streams.ts diff --git a/ai-docs/src/02_stream/30_encoding.ts b/ai-docs/src/03_stream/30_encoding.ts similarity index 93% rename from ai-docs/src/02_stream/30_encoding.ts rename to ai-docs/src/03_stream/30_encoding.ts index 16a6b6aa7..456b7cc89 100644 --- a/ai-docs/src/02_stream/30_encoding.ts +++ b/ai-docs/src/03_stream/30_encoding.ts @@ -1,19 +1,17 @@ /** * @title Decoding and encoding streams * - * Use `Stream.pipeThroughChannel` with the `Ndjson` & `Msgpack` modules to + * Use `Stream.pipeThroughChannel` with the `Ndjson` and `SchemaBinary` modules to * decode and encode streams of structured data. */ import { DateTime, Schema, Stream } from "effect" -import { Msgpack, Ndjson } from "effect/unstable/encoding" +import { Ndjson, SchemaBinary } from "effect/unstable/encoding" -// All of the examples below can also be done with Msgpack by replacing `Ndjson` -// with `Msgpack` and using the appropriate channels (`Msgpack.decode()`, -// `Msgpack.encode()`, etc.). -export const msgpackDecoder = Msgpack.decodeSchema(Schema.Struct({ - id: Schema.Number, +// SchemaBinary derives a framed binary decoder directly from a schema. +export const schemaBinaryDecoder = SchemaBinary.decode(Schema.Struct({ + id: Schema.Int, name: Schema.String -})) +}))() // --------------------------------------------------------------------------- // Domain diff --git a/ai-docs/src/02_stream/index.md b/ai-docs/src/03_stream/index.md similarity index 100% rename from ai-docs/src/02_stream/index.md rename to ai-docs/src/03_stream/index.md diff --git a/ai-docs/src/03_integration/10_managed-runtime.ts b/ai-docs/src/04_integration/10_managed-runtime.ts similarity index 96% rename from ai-docs/src/03_integration/10_managed-runtime.ts rename to ai-docs/src/04_integration/10_managed-runtime.ts index ed29f83b0..b7393a9f4 100644 --- a/ai-docs/src/03_integration/10_managed-runtime.ts +++ b/ai-docs/src/04_integration/10_managed-runtime.ts @@ -7,7 +7,7 @@ import { Context, Effect, Layer, ManagedRuntime, Ref, Schema } from "effect" import { Hono } from "hono" class Todo extends Schema.Class("Todo")({ - id: Schema.Number, + id: Schema.Int, title: Schema.String, completed: Schema.Boolean }) {} @@ -16,8 +16,8 @@ class CreateTodoPayload extends Schema.Class("CreateTodoPaylo title: Schema.String }) {} -class TodoNotFound extends Schema.TaggedErrorClass()("TodoNotFound", { - id: Schema.Number +class TodoNotFound extends Schema.TaggedError()("TodoNotFound", { + id: Schema.Int }) {} export class TodoRepo extends Context.Service("User")({ - id: Schema.Number, + id: Schema.Int, name: Schema.String, email: Schema.String }) {} -export class UserNotFound extends Schema.TaggedErrorClass()("UserNotFound", { - id: Schema.Number +export class UserNotFound extends Schema.TaggedError()("UserNotFound", { + id: Schema.Int }) {} export class Users extends Context.Service()("HttpError", { - message: Schema.String, - status: Schema.Number, - retryable: Schema.Boolean -}) {} - -// Start with a few schedule constructors. -export const maxRetries = Schedule.recurs(5) -export const spacedPolling = Schedule.spaced("30 seconds") -export const exponentialBackoff = Schedule.exponential("200 millis") - -// `Schedule.both` continues only while both schedules continue. -// It is useful for combining a delay pattern with a hard attempt cap. -export const retryBackoffWithLimit = Schedule.both( - Schedule.exponential("250 millis"), - Schedule.recurs(6) -) - -// `Schedule.either` continues while either schedule continues. -// It is useful for fallback behavior (e.g. stop only when both are exhausted). -export const keepTryingUntilBothStop = Schedule.either( - Schedule.spaced("2 seconds"), - Schedule.recurs(3) -) - -// Use `Schedule.while` to continue only for retryable failures. -// This lets non-retryable errors fail fast, even if attempts remain. -export const retryableOnly = Schedule.exponential("200 millis").pipe( - // You can use `setInputType` to specify the type of input the schedule will - // receive. - Schedule.setInputType(), - Schedule.while(({ input }) => input.retryable) -) - -// `tapInput` and `tapOutput` are useful for performing side effects like -// logging or metrics. -export const instrumentedRetrySchedule = retryableOnly.pipe( - Schedule.setInputType(), - Schedule.tapInput((error) => Effect.logDebug(`Retrying after ${error.status}: ${error.message}`)), - Schedule.tapOutput((delay) => Effect.logDebug(`Next retry in ${Duration.toMillis(delay)}ms`)) -) - // Production pattern: capped exponential backoff with jitter and max attempts. // Delays start at 250ms, grow exponentially with jitter, and are capped at 10s. -export const productionRetrySchedule = Schedule.exponential("250 millis").pipe( +export const productionRetrySchedule = Schedule.min([ + Schedule.exponential("250 millis"), // Cap the delay at 10 seconds to avoid excessively long waits. - Schedule.either(Schedule.spaced("10 seconds")), + Schedule.spaced("10 seconds") +]).pipe( Schedule.jittered, Schedule.setInputType(), Schedule.while(({ input }) => input.retryable) @@ -100,3 +60,51 @@ export const loadUserWithInferredInput = fetchUserProfile("user-123").pipe( ), Effect.orDie ) + +export class HttpError extends Schema.TaggedError()("HttpError", { + message: Schema.String, + status: Schema.Int, + retryable: Schema.Boolean +}) {} + +// Start with a few schedule constructors. +export const maxRetries = Schedule.recurs(5) +export const spacedPolling = Schedule.spaced("30 seconds") +export const exponentialBackoff = Schedule.exponential("200 millis") + +// `Schedule.max` continues only while all schedules continue and outputs +// the slowest delay. It is useful for combining a delay pattern with a hard +// attempt cap. +export const retryBackoffWithLimit = Schedule.max([ + Schedule.exponential("250 millis"), + Schedule.recurs(6) +]) + +// `Schedule.min` continues while any schedule continues and outputs +// the fastest delay. It is useful for fallback behavior. +// +// This schedule will start at 500ms, exponentially increase, and will be capped +// at 2 seconds. +export const keepTryingUpToTwoSeconds = Schedule.min([ + Schedule.exponential("500 millis"), + Schedule.spaced("2 seconds") +]) + +// Use `Schedule.while` to continue only for retryable failures. +// This lets non-retryable errors fail fast, even if attempts remain. +export const retryableOnly = Schedule.exponential("200 millis").pipe( + // You can use `setInputType` to specify the type of input the schedule will + // receive. + Schedule.setInputType(), + Schedule.while(({ input }) => input.retryable) +) + +// `tap` is useful for performing side effects like logging or metrics. +export const instrumentedRetrySchedule = retryableOnly.pipe( + Schedule.setInputType(), + Schedule.tap((meta) => + Effect.logDebug( + `Retrying after ${meta.input.status}: ${meta.input.message}. Next retry in ${Duration.toMillis(meta.duration)}ms` + ) + ) +) diff --git a/ai-docs/src/08_observability/10_logging.ts b/ai-docs/src/08_observability/10_logging.ts index 7cff7a2c9..8942d3ee2 100644 --- a/ai-docs/src/08_observability/10_logging.ts +++ b/ai-docs/src/08_observability/10_logging.ts @@ -41,7 +41,7 @@ export const AppLoggerLayer = Logger.layer([appLogger]).pipe( // Create a logger layer that uses the default logger for development, and the // custom logger for production export const LoggerLayer = Layer.unwrap(Effect.gen(function*() { - const env = yield* Config.string("NODE_ENV").pipe(Config.withDefault("development")) + const env = yield* Config.String("NODE_ENV").pipe(Config.withDefault("development")) if (env === "production") { return AppLoggerLayer } diff --git a/ai-docs/src/10_predicate/01_basics.ts b/ai-docs/src/10_predicate/01_basics.ts new file mode 100644 index 000000000..8ff78326c --- /dev/null +++ b/ai-docs/src/10_predicate/01_basics.ts @@ -0,0 +1,14 @@ +/** + * @title Using the Predicate module + */ +import { Predicate } from "effect" + +const thing: unknown = { + a: 1 +} + +if (Predicate.isObject(thing)) { + if (Predicate.isNumber(thing.a)) { + console.log("number", thing.a) + } +} diff --git a/ai-docs/src/10_predicate/index.md b/ai-docs/src/10_predicate/index.md new file mode 100644 index 000000000..5149c3221 --- /dev/null +++ b/ai-docs/src/10_predicate/index.md @@ -0,0 +1,9 @@ +## Runtime type guards + +The `Predicate` module contains small, reusable runtime checks. + +**NEVER** write your own helper functions like `isRecord` or `isString`, instead +use the helpers from the `Predicate` module. + +Predicates can be composed with apis such as `Predicate.and`, +`Predicate.or`, `Predicate.not`, and `Predicate.compose`. diff --git a/ai-docs/src/40_sql/10_basics.ts b/ai-docs/src/40_sql/10_basics.ts new file mode 100644 index 000000000..59a590ae2 --- /dev/null +++ b/ai-docs/src/40_sql/10_basics.ts @@ -0,0 +1,175 @@ +/** + * @title Getting started with SQL + * + * Define a schema-backed domain model, run migrations against a SQLite + * database, and expose a derived repository through a service. + */ +import { NodeRuntime } from "@effect/platform-node" +import { SqliteClient, SqliteMigrator } from "@effect/sql-sqlite-node" +import { Context, Effect, Layer, Schema } from "effect" +import { Model } from "effect/unstable/schema" +import { SqlClient, SqlModel, SqlSchema } from "effect/unstable/sql" + +// Use a branded string for the group id, so it cannot be mixed up with other +// string ids in the application. +export const GroupId = Schema.String.pipe(Schema.brand("GroupId")) +export type GroupId = typeof GroupId.Type + +// `Model.Class` defines a domain model with variants for the database and JSON +// boundaries. The single field declaration is the source of truth, and each +// variant only contains the fields that make sense for that operation: +// +// - `Group` / `Group.insert` / `Group.update` for the database +// - `Group.json` / `Group.jsonCreate` / `Group.jsonUpdate` for JSON APIs +export class Group extends Model.Class("Group")({ + // A UUID v4 primary key that is generated by the application on insert + id: Model.UuidV4Insert(GroupId), + name: Schema.NonEmptyString, + // `Model.FieldExcept` removes a field from the given variants. The slug is + // chosen when the group is created and is immutable afterwards, so it is + // removed from the update variants. + slug: Schema.NonEmptyString.pipe(Model.FieldExcept(["update", "jsonUpdate"])), + // `Model.FieldOnly` keeps a field in only the given variants. Internal notes + // live in the database and are never exposed through the JSON variants. + notes: Schema.NullOr(Schema.String).pipe(Model.FieldOnly(["select", "insert"])), + // `Model.Field` gives full control over the individual variants. The member + // count is maintained by the database, so the application can read it but + // never writes it. + memberCount: Model.Field({ + select: Schema.Int, + json: Schema.Int + }), + // `createdAt` is set to the current time on insert, and `updatedAt` is + // refreshed on every update. Both are stored as strings, which suits SQLite. + createdAt: Model.DateTimeInsert, + updatedAt: Model.DateTimeUpdate +}) {} + +export class GroupNotFound extends Schema.TaggedError()("GroupNotFound", { + id: GroupId +}) {} + +// The SqlClient layer determines which database you are talking to. Swap this +// layer for `@effect/sql-pg`, `@effect/sql-mysql2` etc. to target another +// database without changing the rest of the code. +const SqlLayer = SqliteClient.layer({ filename: ":memory:" }) + +// Migrations are effects keyed by `_` that run once, in id order. A +// real application would keep each migration in its own file and load them +// with `SqliteMigrator.fromFileSystem` instead of an inline record. +const MigratorLayer = SqliteMigrator.layer({ + loader: SqliteMigrator.fromRecord({ + "0001_create_groups": Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + yield* sql` + CREATE TABLE groups ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + slug TEXT NOT NULL, + notes TEXT, + memberCount INTEGER NOT NULL DEFAULT 0, + createdAt TEXT NOT NULL, + updatedAt TEXT NOT NULL + ) + ` + }) + }) +}) + +// Combine the database client with the migrations, so anything built on top of +// `SqlLive` sees a fully migrated database. +const SqlLive = MigratorLayer.pipe(Layer.provideMerge(SqlLayer)) + +// Wrap data access in a service, so the rest of the application depends on +// `Groups` instead of the database directly. +export class Groups extends Context.Service + rename(id: GroupId, name: string): Effect.Effect + findById(id: GroupId): Effect.Effect + readonly list: Effect.Effect> +}>()("app/Groups") { + static readonly layer = Layer.effect( + Groups, + Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + + // `SqlModel.makeRepository` derives insert / update / findById / delete + // operations from the model, using the matching variant schema for each + // operation. + const repo = yield* SqlModel.makeRepository(Group, { + tableName: "groups", + spanPrefix: "Groups", + idColumn: "id" + }) + + // For queries the repository does not cover, combine the `sql` tag with + // `SqlSchema` to decode the rows using the model schema. + const listAll = SqlSchema.findAll({ + Request: Schema.Void, + Result: Group, + execute: () => sql`SELECT * FROM groups ORDER BY createdAt` + }) + + // Use `Effect.fn` to give each method a named span for observability. + const create = Effect.fn("Groups.create")((name: string, slug: string) => + // `Group.insert.makeEffect` fills in the generated id and timestamps + // using the Effect clock, so tests can control them with `TestClock`. + Group.insert.makeEffect({ name, slug, notes: null }).pipe( + Effect.flatMap(repo.insert), + // Database and encoding failures are unexpected here, so treat + // them as defects to keep the service interface focused on domain + // errors. + Effect.orDie + ) + ) + + const rename = Effect.fn("Groups.rename")((id: GroupId, name: string) => + Group.update.makeEffect({ id, name }).pipe( + Effect.flatMap(repo.update), + Effect.orDie + ) + ) + + const findById = Effect.fn("Groups.findById")((id: GroupId) => + repo.findById(id).pipe( + Effect.catchTags({ + NoSuchElementError: () => new GroupNotFound({ id }), + SchemaError: Effect.die, + SqlError: Effect.die + }) + ) + ) + + const list = listAll().pipe( + Effect.orDie, + Effect.withSpan("Groups.list") + ) + + return Groups.of({ create, rename, findById, list }) + }) + ).pipe( + // Provide the layers locally, so lots of messy wiring doesn't need to + // happen in the "main" entrypoint of the application. + Layer.provide(SqlLive) + ) +} + +const program = Effect.gen(function*() { + const groups = yield* Groups + + const engineering = yield* groups.create("Engineering", "engineering") + const design = yield* groups.create("Design", "design") + + yield* groups.rename(design.id, "Product Design") + + const found = yield* groups.findById(engineering.id) + yield* Effect.log("found group", found) + + const all = yield* groups.list + yield* Effect.log(`total groups: ${all.length}`) +}) + +program.pipe( + Effect.provide(Groups.layer), + NodeRuntime.runMain +) diff --git a/ai-docs/src/40_sql/index.md b/ai-docs/src/40_sql/index.md new file mode 100644 index 000000000..cf026b2c8 --- /dev/null +++ b/ai-docs/src/40_sql/index.md @@ -0,0 +1,6 @@ +## Working with SQL databases + +Use the `effect/unstable/sql` modules together with a driver package such as +`@effect/sql-sqlite-node` to access SQL databases. Define domain models with +`Model.Class` to derive schemas for the database and JSON boundaries, run +migrations, and write type-safe queries. diff --git a/ai-docs/src/50_http-client/10_basics.ts b/ai-docs/src/50_http-client/10_basics.ts index 747e886e1..195d5fe55 100644 --- a/ai-docs/src/50_http-client/10_basics.ts +++ b/ai-docs/src/50_http-client/10_basics.ts @@ -7,8 +7,8 @@ import { Context, Effect, flow, Layer, Schedule, Schema } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" class Todo extends Schema.Class("Todo")({ - userId: Schema.Number, - id: Schema.Number, + userId: Schema.Int, + id: Schema.Int, title: Schema.String, completed: Schema.Boolean }) {} @@ -97,6 +97,6 @@ export class JsonPlaceholder extends Context.Service()("JsonPlaceholderError", { +export class JsonPlaceholderError extends Schema.TaggedError()("JsonPlaceholderError", { cause: Schema.Defect() }) {} diff --git a/ai-docs/src/51_http-server/10_basics.ts b/ai-docs/src/51_http-server/10_basics.ts index 79e92c908..0edea4465 100644 --- a/ai-docs/src/51_http-server/10_basics.ts +++ b/ai-docs/src/51_http-server/10_basics.ts @@ -24,7 +24,9 @@ const SystemApiHandlers = HttpApiBuilder.group( Api, "system", Effect.fn(function*(handlers) { - return handlers.handle("health", () => Effect.void) + return handlers.handleAll({ + health: () => Effect.void + }) }) ) diff --git a/ai-docs/src/51_http-server/20_testing.ts b/ai-docs/src/51_http-server/20_testing.ts new file mode 100644 index 000000000..e34d13d22 --- /dev/null +++ b/ai-docs/src/51_http-server/20_testing.ts @@ -0,0 +1,100 @@ +/** + * @title Testing HttpApi implementations + * + * Test handlers through an in-memory typed client with `HttpApiTest`, without + * starting an HTTP server or touching a real database. + */ +import { assert, layer } from "@effect/vitest" +import { Effect, Layer } from "effect" +import { HttpClientRequest, HttpServer } from "effect/unstable/http" +import { HttpApiMiddleware, HttpApiTest } from "effect/unstable/httpapi" +import { Api } from "./fixtures/api/Api.ts" +import { Authorization } from "./fixtures/api/Authorization.ts" +import { UserId } from "./fixtures/domain/User.ts" +import { AuthorizationLayer } from "./fixtures/server/Authorization.ts" +import { Users } from "./fixtures/server/Users.ts" +import { UsersApiHandlersNoDeps } from "./fixtures/server/Users/http.ts" + +// Provide the handlers with the in-memory `Users` implementation, so the full +// HTTP pipeline is exercised without any SQL. The Authorization middleware is +// provided with `Layer.provideMerge`, because the HTTP pipeline also resolves +// it when the routes are built. +const HandlersLayer = UsersApiHandlersNoDeps.pipe( + Layer.provide(Users.layerMemory), + Layer.provideMerge(AuthorizationLayer) +) + +// The client-side Authorization middleware supplies the bearer token. +// Providing different middleware implementations lets the tests cover both +// authorized and unauthorized requests. +const AuthorizationMiddlewareGood = HttpApiMiddleware.layerClient( + Authorization, + ({ next, request }) => next(HttpClientRequest.bearerToken(request, "dev-token")) +) + +const AuthorizationMiddlewareBad = HttpApiMiddleware.layerClient( + Authorization, + // Forward the request without attaching a token + ({ next, request }) => next(request) +) + +// `HttpApiTest.groups` builds a typed client wired directly to the handlers of +// the selected groups, using the same request encoding, routing, and response +// decoding as a real server. +const makeClient = HttpApiTest.groups(Api, ["users"]) + +// `HttpServer.layerServices` provides the platform services the HTTP pipeline +// needs in tests. +layer(Layer.mergeAll(HandlersLayer, HttpServer.layerServices))("UsersApi", (it) => { + it.effect("lists, fetches, and creates users", () => + Effect.gen(function*() { + const client = yield* makeClient + + const created = yield* client.users.create({ + payload: { name: "Alice", email: "alice@acme.dev" } + }) + assert.strictEqual(created.name, "Alice") + + const fetched = yield* client.users.getById({ + params: { id: created.id } + }) + assert.deepStrictEqual(fetched, created) + + const all = yield* client.users.list({ query: {} }) + assert.isTrue(all.some((user) => user.id === created.id)) + }).pipe(Effect.provide(AuthorizationMiddlewareGood))) + + it.effect("returns a 404 for a missing user", () => + Effect.gen(function*() { + const client = yield* makeClient + + // Use Effect.flip to assert on the error channel + const error = yield* client.users.getById({ + params: { id: UserId.make("019845e1-682f-4b02-a706-3b2422d13aec") } + }).pipe(Effect.flip) + assert.strictEqual(error._tag, "UserNotFound") + }).pipe(Effect.provide(AuthorizationMiddlewareGood))) + + it.effect("rejects requests without a valid bearer token", () => + Effect.gen(function*() { + const client = yield* makeClient + + const error = yield* client.users.list({ query: {} }).pipe(Effect.flip) + assert.strictEqual(error._tag, "Unauthorized") + }).pipe(Effect.provide(AuthorizationMiddlewareBad))) + + it.effect("rejects requests with an invalid bearer token", () => + Effect.gen(function*() { + const client = yield* makeClient + + const error = yield* client.users.getById({ + params: { id: UserId.make("019845e1-682f-4b02-a706-3b2422d13aec") } + }).pipe(Effect.flip) + assert.strictEqual(error._tag, "Unauthorized") + }).pipe( + Effect.provide(HttpApiMiddleware.layerClient( + Authorization, + ({ next, request }) => next(HttpClientRequest.bearerToken(request, "wrong-token")) + )) + )) +}) diff --git a/ai-docs/src/51_http-server/fixtures/api/Authorization.ts b/ai-docs/src/51_http-server/fixtures/api/Authorization.ts index 8b25c0429..30898c729 100644 --- a/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +++ b/ai-docs/src/51_http-server/fixtures/api/Authorization.ts @@ -4,7 +4,7 @@ import type { User } from "../domain/User.ts" export class CurrentUser extends Context.Service()("acme/HttpApi/Authorization/CurrentUser") {} -export class Unauthorized extends Schema.TaggedErrorClass()( +export class Unauthorized extends Schema.TaggedError()( "Unauthorized", { message: Schema.String diff --git a/ai-docs/src/51_http-server/fixtures/api/Users.ts b/ai-docs/src/51_http-server/fixtures/api/Users.ts index f3b56d4ef..ad370e163 100644 --- a/ai-docs/src/51_http-server/fixtures/api/Users.ts +++ b/ai-docs/src/51_http-server/fixtures/api/Users.ts @@ -10,7 +10,10 @@ export class UsersApiGroup extends HttpApiGroup.make("users") query: { search: Schema.optional(Schema.String) }, - success: Schema.Array(User) + // Use the `json` variant of the model for API responses. It shares the + // field declarations with the database variants, but can encode values + // differently where needed. + success: Schema.Array(User.json) }), HttpApiEndpoint.get("search", "/search", { // For get requests, payload uses the query string @@ -18,7 +21,7 @@ export class UsersApiGroup extends HttpApiGroup.make("users") search: Schema.String }, success: [ - Schema.Array(User), + Schema.Array(User.json), Schema.String.pipe(HttpApiSchema.asText({ contentType: "text/csv" })) @@ -39,13 +42,12 @@ export class UsersApiGroup extends HttpApiGroup.make("users") }), HttpApiEndpoint.get("getById", "/:id", { params: { - // Path parameter schemas need to be able to decode from strings. - // Schema.decodeTo can be used to "bridge" between schemas - id: Schema.FiniteFromString.pipe( - Schema.decodeTo(UserId) - ) + // Path parameter values are automatically coerced from their string + // form using `Schema.toCodecStringTree`, so schemas that decode from + // other types (like numbers) work here as well. + id: UserId }, - success: User, + success: User.json, error: UserNotFound.pipe( // If you want an error to return no content, you can use // `HttpApiSchema.asNoContent` and provide a decoder that transforms the @@ -59,14 +61,28 @@ export class UsersApiGroup extends HttpApiGroup.make("users") // For post requests, payload uses the request body. It defaults to JSON, // but you can specify other content types as well using // `HttpApiSchema.asText`, `HttpApiSchema.asMultipart`, etc. - payload: Schema.Struct({ - name: Schema.String, - email: Schema.String - }), - success: User + // + // The `jsonCreate` variant only exposes the fields clients are allowed + // to provide, so the generated id and timestamps cannot be set here. + payload: User.jsonCreate, + success: User.json + }), + HttpApiEndpoint.patch("update", "/:id", { + params: { + id: UserId + }, + // The `jsonUpdate` variant similarly excludes the id and the managed + // timestamps from the update payload. + payload: User.jsonUpdate, + success: User.json, + error: UserNotFound.pipe( + HttpApiSchema.asNoContent({ + decode: () => new UserNotFound() + }) + ) }), HttpApiEndpoint.get("me", "/me", { - success: User, + success: User.json, error: UserNotFound.pipe(HttpApiSchema.status(404)) }) ) diff --git a/ai-docs/src/51_http-server/fixtures/domain/User.ts b/ai-docs/src/51_http-server/fixtures/domain/User.ts index 852e2011e..43ac4f5cf 100644 --- a/ai-docs/src/51_http-server/fixtures/domain/User.ts +++ b/ai-docs/src/51_http-server/fixtures/domain/User.ts @@ -1,12 +1,21 @@ import { Schema } from "effect" +import { Model } from "effect/unstable/schema" -export const UserId = Schema.Int.pipe( - Schema.brand("UserId") -) +export const UserId = Schema.String.pipe(Schema.brand("UserId")) export type UserId = typeof UserId.Type -export class User extends Schema.Class("User")({ - id: UserId, +// `Model.Class` derives variants for the database (`User`, `User.insert`, +// `User.update`) and the JSON API (`User.json`, `User.jsonCreate`, +// `User.jsonUpdate`) from a single field declaration. +export class User extends Model.Class("User")({ + // A UUID v4 primary key generated by the application on insert. It is + // excluded from the `jsonCreate` / `jsonUpdate` variants, so API clients can + // never set it. + id: Model.UuidV4Insert(UserId), name: Schema.String, - email: Schema.String + email: Schema.String, + // Timestamps are managed by the model: set on insert, refreshed on update, + // and also excluded from the JSON create / update variants. + createdAt: Model.DateTimeInsert, + updatedAt: Model.DateTimeUpdate }) {} diff --git a/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts b/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts index fa2a757b2..fde17e7fc 100644 --- a/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +++ b/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts @@ -1,6 +1,6 @@ import { Schema } from "effect" -export class UserNotFound extends Schema.TaggedErrorClass()( +export class UserNotFound extends Schema.TaggedError()( "UserNotFound", {}, // You can specify the status code for this error inline @@ -8,7 +8,7 @@ export class UserNotFound extends Schema.TaggedErrorClass()( ) {} export class SearchQueryTooShort - extends Schema.TaggedErrorClass()("SearchQueryTooShort", {}, { httpApiStatus: 422 }) + extends Schema.TaggedError()("SearchQueryTooShort", {}, { httpApiStatus: 422 }) { static readonly minimumLength = 2 } @@ -17,6 +17,6 @@ export class SearchQueryTooShort // // This prevents adding too many error types to services / endpoint definitions. // -export class UsersError extends Schema.TaggedErrorClass()("UsersError", { +export class UsersError extends Schema.TaggedError()("UsersError", { reason: Schema.Union([UserNotFound, SearchQueryTooShort]) }) {} diff --git a/ai-docs/src/51_http-server/fixtures/server/Authorization.ts b/ai-docs/src/51_http-server/fixtures/server/Authorization.ts index bbdbed39b..51ae21dc0 100644 --- a/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +++ b/ai-docs/src/51_http-server/fixtures/server/Authorization.ts @@ -1,7 +1,16 @@ -import { Effect, Layer, Redacted } from "effect" +import { DateTime, Effect, Layer, Redacted } from "effect" import { Authorization, CurrentUser, Unauthorized } from "../api/Authorization.ts" import { User, UserId } from "../domain/User.ts" +const fixedTimestamp = DateTime.makeUnsafe("2026-01-01T00:00:00Z") +const devUser = new User({ + id: UserId.make("bf3dbe33-0ad2-4c9c-9c9e-733e57bdcbee"), + name: "Dev User", + email: "dev@acme.com", + createdAt: fixedTimestamp, + updatedAt: fixedTimestamp +}) + // The implementation of the Authorization middleware. It is seperate from the // service definition to avoid leaking it into a client. export const AuthorizationLayer = Layer.effect( @@ -21,15 +30,7 @@ export const AuthorizationLayer = Layer.effect( // Provide the current user to the rest of the stack. This will be // available in any endpoint or middleware that runs after this one. - return yield* Effect.provideService( - httpEffect, - CurrentUser, - new User({ - id: UserId.make(1), - name: "Dev User", - email: "dev@acme.com" - }) - ) + return yield* Effect.provideService(httpEffect, CurrentUser, devUser) }) }) }) diff --git a/ai-docs/src/51_http-server/fixtures/server/Users.ts b/ai-docs/src/51_http-server/fixtures/server/Users.ts index a9833762c..485d1e348 100644 --- a/ai-docs/src/51_http-server/fixtures/server/Users.ts +++ b/ai-docs/src/51_http-server/fixtures/server/Users.ts @@ -1,28 +1,142 @@ -import { Context, Effect, Layer, Ref } from "effect" -import { User, UserId } from "../domain/User.ts" +import { SqliteClient, SqliteMigrator } from "@effect/sql-sqlite-node" +import { Context, Effect, Layer, Schema } from "effect" +import { SqlClient, SqlModel, SqlSchema } from "effect/unstable/sql" +import { User } from "../domain/User.ts" +import type { UserId } from "../domain/User.ts" import { SearchQueryTooShort, UserNotFound, UsersError } from "../domain/UserErrors.ts" +// The SqlClient layer determines which database the SQL implementation talks +// to. Swap it for another driver package to target a different database. +const SqlLayer = SqliteClient.layer({ filename: ":memory:" }) + +// Migrations are effects keyed by `_` that run once, in id order. A +// real application would keep each migration in its own file and load them +// with `SqliteMigrator.fromFileSystem` instead of an inline record. +const MigratorLayer = SqliteMigrator.layer({ + loader: SqliteMigrator.fromRecord({ + "0001_create_users": Effect.gen(function*() { + const sql = yield* SqlClient.SqlClient + yield* sql` + CREATE TABLE users ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL, + createdAt TEXT NOT NULL, + updatedAt TEXT NOT NULL + ) + ` + }) + }) +}) + export class Users extends Context.Service, UsersError> getById(id: UserId): Effect.Effect - create(input: { readonly name: string; readonly email: string }): Effect.Effect + create(input: typeof User.jsonCreate.Type): Effect.Effect + update(id: UserId, input: typeof User.jsonUpdate.Type): Effect.Effect }>()("acme/Users") { - static readonly layer = Layer.effect( + // The SQL implementation only requires a `SqlClient`, so entrypoints and + // tests decide how the database is provided. + static readonly layerNoDeps = Layer.effect( Users, Effect.gen(function*() { - const users = new Map([ - [ - 1, - new User({ - id: UserId.make(1), - name: "Admin", - email: "admin@acme.dev" + const sql = yield* SqlClient.SqlClient + + // CRUD goes through a repository derived from the `User` model. Each + // operation uses the matching model variant to encode its input and + // decodes rows with the full model schema. + const repo = yield* SqlModel.makeRepository(User, { + tableName: "users", + spanPrefix: "Users", + idColumn: "id" + }) + + // Queries the repository does not cover are written with the `sql` tag + // and decoded with the model schema. + const listAll = SqlSchema.findAll({ + Request: Schema.Void, + Result: User, + execute: () => sql`SELECT * FROM users ORDER BY createdAt` + }) + + const searchUsers = SqlSchema.findAll({ + Request: Schema.String, + Result: User, + execute: (search) => { + const pattern = `%${search}%` + return sql`SELECT * FROM users WHERE name LIKE ${pattern} OR email LIKE ${pattern}` + } + }) + + const list = Effect.fn("Users.list")(function*(search: string | undefined) { + if (search === undefined || search.length === 0) { + return yield* Effect.orDie(listAll()) + } else if (search.length < SearchQueryTooShort.minimumLength) { + return yield* new UsersError({ + reason: new SearchQueryTooShort() }) - ] - ]) - const nextId = yield* Ref.make(2) + } + yield* Effect.annotateCurrentSpan({ search }) + return yield* Effect.orDie(searchUsers(search)) + }) - const list = Effect.fn("UsersRepo.list")(function*(search: string | undefined) { + const getById = Effect.fn("Users.getById")((id: UserId) => + repo.findById(id).pipe( + Effect.catchTags({ + NoSuchElementError: () => new UsersError({ reason: new UserNotFound() }), + // Database and encoding failures are unexpected, so treat them as + // defects to keep the service interface focused on domain errors. + SchemaError: Effect.die, + SqlError: Effect.die + }) + ) + ) + + const create = Effect.fn("Users.create")((input: typeof User.jsonCreate.Type) => + // `User.insert.makeEffect` fills in the generated id and timestamps + // using the Effect clock, so tests can control them with `TestClock`. + User.insert.makeEffect(input).pipe( + Effect.flatMap(repo.insert), + Effect.orDie + ) + ) + + const update = Effect.fn("Users.update")(function*(id: UserId, input: typeof User.jsonUpdate.Type) { + // Ensure the user exists first, so a missing id fails with the domain + // error instead of a defect. + yield* getById(id) + const update = yield* User.update.makeEffect({ id, ...input }).pipe(Effect.orDie) + return yield* repo.update(update).pipe(Effect.orDie) + }) + + return Users.of({ list, getById, create, update }) + }) + ) + + // The fully provided SQL implementation: the database client and migrations + // are implementation details, so this layer requires nothing. + static readonly layer: Layer.Layer = this.layerNoDeps.pipe( + Layer.provide(MigratorLayer.pipe(Layer.provideMerge(SqlLayer))), + Layer.orDie + ) + + // An in-memory implementation for tests, so the HTTP stack can be exercised + // without a database. + static readonly layerMemory = Layer.effect( + Users, + Effect.gen(function*() { + const users = new Map() + + const makeUser = (input: typeof User.jsonCreate.Type) => + User.insert.makeEffect(input).pipe( + Effect.map((user) => new User(user)), + Effect.orDie + ) + + const admin = yield* makeUser({ name: "Admin", email: "admin@acme.dev" }) + users.set(admin.id, admin) + + const list = Effect.fn("Users.list")(function*(search: string | undefined) { const allUsers = Array.from(users.values()) if (search === undefined || search.length === 0) { return allUsers @@ -38,7 +152,7 @@ export class Users extends Context.Service current + 1) - const user = new User({ id: UserId.make(id), ...input }) + const create = Effect.fn("Users.create")(function*(input: typeof User.jsonCreate.Type) { + const user = yield* makeUser(input) users.set(user.id, user) return user }) - return Users.of({ list, getById, create }) + const update = Effect.fn("Users.update")(function*(id: UserId, input: typeof User.jsonUpdate.Type) { + const existing = yield* getById(id) + const update = yield* User.update.makeEffect({ id, ...input }).pipe(Effect.orDie) + const updated = new User({ ...existing, ...update }) + users.set(id, updated) + return updated + }) + + return Users.of({ list, getById, create, update }) }) ) } diff --git a/ai-docs/src/51_http-server/fixtures/server/Users/http.ts b/ai-docs/src/51_http-server/fixtures/server/Users/http.ts index a1b7b3eac..d60563d83 100644 --- a/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +++ b/ai-docs/src/51_http-server/fixtures/server/Users/http.ts @@ -5,49 +5,48 @@ import { CurrentUser } from "../../api/Authorization.ts" import { AuthorizationLayer } from "../Authorization.ts" import { Users } from "../Users.ts" -export const UsersApiHandlers = HttpApiBuilder.group( +// The handlers without their dependencies provided, so tests can supply an +// alternative `Users` implementation. +export const UsersApiHandlersNoDeps = HttpApiBuilder.group( Api, "users", Effect.fn(function*(handlers) { const users = yield* Users - return handlers - .handle("list", ({ query }) => + return handlers.handleAll({ + list: ({ query }) => users.list(query.search).pipe( // The list endpoint expects no errors, so we convert any potential // errors into a 500 Internal Server Error. Effect.orDie - )) - .handle( - "search", - Effect.fn(function*({ payload }) { - if (payload.search === "bad-request") { - // You can use the built in error types like any other - // Schema.TaggedErrorClass - return yield* new HttpApiError.RequestTimeout() - } - return yield* users.list(payload.search).pipe( - Effect.catchReason( - "UsersError", - "SearchQueryTooShort", - // Re-fail the "SearchQueryTooShort" reason - Effect.fail, - // All other reasons are unexpected, so we convert them into a 500 - // Internal Server Error. - Effect.die - ) + ), + search: Effect.fn(function*({ payload }) { + if (payload.search === "bad-request") { + // You can use the built in error types like any other + // Schema.TaggedError + return yield* new HttpApiError.RequestTimeout() + } + return yield* users.list(payload.search).pipe( + Effect.catchReason( + "UsersError", + "SearchQueryTooShort", + // Re-fail the "SearchQueryTooShort" reason + Effect.fail, + // All other reasons are unexpected, so we convert them into a 500 + // Internal Server Error. + Effect.die ) - }) - ) - .handle("getById", ({ params }) => + ) + }), + getById: ({ params }) => users.getById(params.id).pipe( // You can also use Effect.catchReasons to handle multiple error // reasons at once Effect.catchReasons("UsersError", { UserNotFound: (e) => Effect.fail(e) }, Effect.die) - )) - .handle("create", ({ payload }) => + ), + create: ({ payload }) => users.create(payload).pipe( Effect.orDie // You could alse use Effect.unwrapReason to moves rror reasons up to @@ -59,13 +58,23 @@ export const UsersApiHandlers = HttpApiBuilder.group( // UserNotFound: Effect.die, // SearchQueryTooShort: Effect.die // }) - )) - .handle("me", () => + ), + update: ({ params, payload }) => + users.update(params.id, payload).pipe( + Effect.catchReasons("UsersError", { + UserNotFound: (e) => Effect.fail(e) + }, Effect.die) + ), + me: () => // The Authorization middleware provides the CurrentUser service, so we // can access it here. - CurrentUser) + CurrentUser + }) }) -).pipe( - // Provide the dependencies for the handlers. +) + +// The handlers with all dependencies provided, ready to serve. The SQL-backed +// `Users.layer` keeps the database wiring out of the server entrypoint. +export const UsersApiHandlers = UsersApiHandlersNoDeps.pipe( Layer.provide([Users.layer, AuthorizationLayer]) ) diff --git a/ai-docs/src/60_child-process/10_working-with-child-processes.ts b/ai-docs/src/60_child-process/10_working-with-child-processes.ts index 3d2866af9..c7b1bbbcd 100644 --- a/ai-docs/src/60_child-process/10_working-with-child-processes.ts +++ b/ai-docs/src/60_child-process/10_working-with-child-processes.ts @@ -7,7 +7,7 @@ import { NodeServices } from "@effect/platform-node" import { Console, Context, Effect, Layer, Schema, Stream, String } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -export class DevToolsError extends Schema.TaggedErrorClass()("DevToolsError", { +export class DevToolsError extends Schema.TaggedError()("DevToolsError", { cause: Schema.Defect() }) {} diff --git a/ai-docs/src/60_child-process/index.md b/ai-docs/src/60_child-process/index.md index 8aafdd603..ae0016bc3 100644 --- a/ai-docs/src/60_child-process/index.md +++ b/ai-docs/src/60_child-process/index.md @@ -1,3 +1,3 @@ ## Working with child processes -Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner. +Use the `effect/unstable/process` modules to define child processes and run them with `ChildProcessSpawner`. diff --git a/ai-docs/src/70_cli/10_basics.ts b/ai-docs/src/70_cli/10_basics.ts index a4fe368ac..3f4a40c46 100644 --- a/ai-docs/src/70_cli/10_basics.ts +++ b/ai-docs/src/70_cli/10_basics.ts @@ -5,12 +5,12 @@ * handlers into a single executable command. */ import { NodeRuntime, NodeServices } from "@effect/platform-node" -import { Console, Effect } from "effect" +import { Console, Effect, Option, Schema } from "effect" import { Argument, Command, Flag } from "effect/unstable/cli" // You can define flags outside of commands and reuse them across multiple // commands. -const workspace = Flag.string("workspace").pipe( +const workspace = Flag.String("workspace").pipe( Flag.withAlias("w"), Flag.withDescription("Workspace to operate on"), Flag.withDefault("personal") @@ -21,26 +21,43 @@ const workspace = Flag.string("workspace").pipe( const tasks = Command.make("tasks").pipe( Command.withSharedFlags({ workspace, - verbose: Flag.boolean("verbose").pipe( + verbose: Flag.Boolean("verbose").pipe( Flag.withAlias("v"), - Flag.withDescription("Print diagnostic output") + Flag.withDescription("Print diagnostic output"), + Flag.withDefault(false) ) }), Command.withDescription("Track and manage tasks") ) +// Arguments and flags parse plain strings; use `withSchema` to validate or +// transform the parsed value with any schema. +const Email = Schema.String.pipe( + Schema.check(Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, { + message: "Expected a valid email address" + })) +) + const create = Command.make( "create", { - title: Argument.string("title").pipe( - Argument.withDescription("Task title") + title: Argument.String("title").pipe( + Argument.withDescription("Task title"), + // Reject empty titles at parse time, so the handler only ever sees + // valid input + Argument.withSchema(Schema.NonEmptyString) ), - priority: Flag.choice("priority", ["low", "normal", "high"]).pipe( + priority: Flag.Literals("priority", ["low", "normal", "high"]).pipe( Flag.withDescription("Priority for the new task"), Flag.withDefault("normal") + ), + assignee: Flag.String("assignee").pipe( + Flag.withDescription("Email address of the person to assign"), + Flag.withSchema(Email), + Flag.optional ) }, - Effect.fn(function*({ title, priority }) { + Effect.fn(function*({ assignee, priority, title }) { // Subcommands can read parent command input by yielding the parent command. const root = yield* tasks @@ -49,6 +66,10 @@ const create = Command.make( } yield* Console.log(`Created "${title}" in ${root.workspace} with ${priority} priority`) + + if (Option.isSome(assignee)) { + yield* Console.log(`Assigned to ${assignee.value}`) + } }) ).pipe( Command.withDescription("Create a task"), @@ -56,6 +77,10 @@ const create = Command.make( { command: "tasks create \"Ship 4.0\" --priority high", description: "Create a high-priority task" + }, + { + command: "tasks create \"Ship 4.0\" --assignee dev@acme.com", + description: "Create a task assigned to a team member" } ]) ) @@ -63,12 +88,13 @@ const create = Command.make( const list = Command.make( "list", { - status: Flag.choice("status", ["open", "done", "all"]).pipe( + status: Flag.Literals("status", ["open", "done", "all"]).pipe( Flag.withDescription("Filter tasks by status"), Flag.withDefault("open") ), - json: Flag.boolean("json").pipe( - Flag.withDescription("Print machine-readable output") + json: Flag.Boolean("json").pipe( + Flag.withDescription("Print machine-readable output"), + Flag.withDefault(false) ) }, Effect.fn(function*({ status, json }) { diff --git a/ai-docs/src/71_ai/10_language-model.ts b/ai-docs/src/71_ai/10_language-model.ts index 2f89956bb..6bbede926 100644 --- a/ai-docs/src/71_ai/10_language-model.ts +++ b/ai-docs/src/71_ai/10_language-model.ts @@ -13,7 +13,7 @@ import { LaunchPlan } from "./fixtures/domain/LaunchPlan.ts" // You can use Config to create ai clients const AnthropicClientLayer = AnthropicClient.layerConfig({ - apiKey: Config.redacted("ANTHROPIC_API_KEY") + apiKey: Config.Redacted("ANTHROPIC_API_KEY") }).pipe( // Providers typically require an HttpClient, but you can choose which one to // use. @@ -21,12 +21,12 @@ const AnthropicClientLayer = AnthropicClient.layerConfig({ ) const OpenAiClientLayer = OpenAiClient.layerConfig({ - apiKey: Config.redacted("OPENAI_API_KEY") + apiKey: Config.Redacted("OPENAI_API_KEY") }).pipe( Layer.provide(FetchHttpClient.layer) ) -export class AiWriterError extends Schema.TaggedErrorClass()("AiWriterError", { +export class AiWriterError extends Schema.TaggedError()("AiWriterError", { // AiErrorReason is a Schema, so we can include it directly in our custom // error schema. reason: AiError.AiErrorReason diff --git a/ai-docs/src/71_ai/20_tools.ts b/ai-docs/src/71_ai/20_tools.ts index 0acd8e97c..2b0b16929 100644 --- a/ai-docs/src/71_ai/20_tools.ts +++ b/ai-docs/src/71_ai/20_tools.ts @@ -20,7 +20,7 @@ const ProductId = Schema.String.pipe(Schema.brand("ProductId")).annotate({ class Product extends Schema.Class("acme/domain/Product")({ id: ProductId, name: Schema.String, - price: Schema.Number + price: Schema.Finite }) {} // Each tool has a name, an optional description, a parameters schema that the @@ -34,7 +34,7 @@ const SearchProducts = Tool.make("SearchProducts", { // guidance. description: "The search query, e.g. 'wireless headphones'" }), - maxResults: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(10))).annotate({ + maxResults: Schema.Natural.pipe(Schema.withDecodingDefault(Effect.succeed(10))).annotate({ description: "The maximum number of results to return" }) }), @@ -57,7 +57,7 @@ const GetInventory = Tool.make("GetInventory", { }), success: Schema.Struct({ productId: ProductId, - available: Schema.Number + available: Schema.Natural }) }) @@ -101,10 +101,10 @@ const ProductToolkitLayer = ProductToolkit.toLayer(Effect.gen(function*() { // Provider setup (same pattern as the language-model example). const OpenAiClientLayer = OpenAiClient.layerConfig({ - apiKey: Config.redacted("OPENAI_API_KEY") + apiKey: Config.Redacted("OPENAI_API_KEY") }).pipe(Layer.provide(FetchHttpClient.layer)) -export class ProductAssistantError extends Schema.TaggedErrorClass()( +export class ProductAssistantError extends Schema.TaggedError()( "ProductAssistantError", { reason: AiError.AiErrorReason } ) {} diff --git a/ai-docs/src/71_ai/30_chat.ts b/ai-docs/src/71_ai/30_chat.ts index 600017024..745d4d033 100644 --- a/ai-docs/src/71_ai/30_chat.ts +++ b/ai-docs/src/71_ai/30_chat.ts @@ -14,7 +14,7 @@ import { FetchHttpClient } from "effect/unstable/http" // --------------------------------------------------------------------------- const OpenAiClientLayer = OpenAiClient.layerConfig({ - apiKey: Config.redacted("OPENAI_API_KEY") + apiKey: Config.Redacted("OPENAI_API_KEY") }).pipe(Layer.provide(FetchHttpClient.layer)) // --------------------------------------------------------------------------- @@ -43,7 +43,7 @@ const ToolsLayer = Tools.toLayer(Effect.gen(function*() { // Service that wraps Chat for a domain use-case // --------------------------------------------------------------------------- -export class AiAssistantError extends Schema.TaggedErrorClass()("AiAssistantError", { +export class AiAssistantError extends Schema.TaggedError()("AiAssistantError", { reason: AiError.AiErrorReason }) { static fromAiError(error: AiError.AiError) { diff --git a/ai-docs/src/80_cluster/10_entities.ts b/ai-docs/src/80_cluster/10_entities.ts index a1be374a5..466197f9b 100644 --- a/ai-docs/src/80_cluster/10_entities.ts +++ b/ai-docs/src/80_cluster/10_entities.ts @@ -10,12 +10,12 @@ import { Rpc } from "effect/unstable/rpc" import type { SqlClient } from "effect/unstable/sql" export const Increment = Rpc.make("Increment", { - payload: { amount: Schema.Number }, - success: Schema.Number + payload: { amount: Schema.Int }, + success: Schema.Int }) export const GetCount = Rpc.make("GetCount", { - success: Schema.Number + success: Schema.Int }) // If you want GetCount messages to be persisted, you can annotate the RPC // schema with `ClusterSchema.Persisted`. diff --git a/ai-docs/src/index.md b/ai-docs/src/index.md index 092209d75..0a0213bf7 100644 --- a/ai-docs/src/index.md +++ b/ai-docs/src/index.md @@ -1,12 +1,10 @@ # Effect library documentation -This documentation resides in the Effect monorepo, which contains the source -code for the Effect library and its related packages. +This documentation covers the Effect library and its related packages. -When you need to find any information about the Effect library, only use this -documentation and the source code found in `./packages`. Do not use -`node_modules` or any other external documentation, as it may be outdated or -incorrect. +When you need to find information about Effect, use this documentation and the +Effect source code available in your environment. Avoid unrelated copies of +Effect or external documentation, as they may be outdated or incorrect. **Note**: The examples in this documentation contain comments for illustration purposes. In practice, you would not include these comments in your code. diff --git a/ai-docs/tsconfig.json b/ai-docs/tsconfig.json index 94d60356d..e5be14e9b 100644 --- a/ai-docs/tsconfig.json +++ b/ai-docs/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.base.json", "include": ["src/**/*.ts", "src/**/*.tsx"], "compilerOptions": { diff --git a/cookbooks/schedule.md b/cookbooks/schedule.md deleted file mode 100644 index 084f286d4..000000000 --- a/cookbooks/schedule.md +++ /dev/null @@ -1,735 +0,0 @@ -# Schedule Cookbook - -Use this cookbook when you need to define a `Schedule` value. The examples are -ordered from small single-purpose policies to larger real-world policies that -combine timing, input classification, output shaping, and observation. - -This cookbook intentionally defines schedules only. It does not apply them with -`Effect.retry`, `Effect.repeat`, streams, or channels. - -## Before Choosing A Schedule - -- `Schedule.recurs(n)` counts recurrences after the first run. -- `Schedule.spaced` waits after each completed run; `Schedule.fixed` uses an - aligned cadence; `Schedule.windowed` recurs on window boundaries. -- `Schedule.duration` performs exactly one recurrence after the duration. -- `Schedule.during` is an elapsed-time budget, not a delay by itself. -- Schedule output is policy output. Use `Schedule.passthrough`, - `Schedule.delays`, `Schedule.map`, or `Schedule.reduce` when the output shape - matters. -- `Schedule.both` continues only while both schedules continue. -- `Schedule.either` continues while either schedule can continue. -- `Schedule.jittered` spreads callers out. It does not add a recurrence limit. -- `Schedule.addDelay` adds extra delay based on schedule output. -- `Schedule.modifyDelay` replaces or adjusts the selected delay. -- Leave unbounded schedules to explicitly owned background work. - -## Choose By Problem Shape - -| Problem shape | Start with | -| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| Bounded retry | `Schedule.exponential` or `Schedule.fibonacci`, then `Schedule.recurs` and optionally `Schedule.during` | -| Poll latest status | `Schedule.spaced` or `Schedule.fixed`, then `Schedule.setInputType`, `Schedule.passthrough`, and `Schedule.while` | -| Inspect selected delays | `Schedule.delays`, then `Schedule.map` | -| Adapt delay from output | `Schedule.addDelay` | -| Replace or cap selected delay | `Schedule.modifyDelay` | -| Run phases in sequence | `Schedule.andThen` | -| Preserve phase in output | `Schedule.andThenResult` | -| Continue while both policies continue | `Schedule.both` | -| Continue while either policy continues | `Schedule.either` | -| Keep input or output history | `Schedule.collectInputs`, `Schedule.collectOutputs`, or `Schedule.collectWhile` | -| Maintain a running aggregate | `Schedule.reduce` | -| Observe decisions without changing output | `Schedule.tap`, `Schedule.tapInput`, or `Schedule.tapOutput` | -| Build a local state machine | `Schedule.unfold` | - -## Table Of Contents - -1. [Single-Policy Schedules](#single-policy-schedules) -2. [Shape Schedule Outputs](#shape-schedule-outputs) -3. [Combine Policies](#combine-policies) -4. [Work With Inputs](#work-with-inputs) -5. [Accumulate State](#accumulate-state) -6. [Adapt Delays](#adapt-delays) -7. [Observe Schedule Decisions](#observe-schedule-decisions) -8. [Build Local State Machines](#build-local-state-machines) -9. [Realistic Policies](#realistic-policies) - -## Single-Policy Schedules - -### Retry A Profile Fetch Three Times - -Goal: Create a retry policy for loading a user profile that allows at most 3 -recurrences and outputs the recurrence count. - -```ts -import { Schedule } from "effect" - -const profileRetry = Schedule.recurs(3) -``` - -### Poll A Queue Once Per Second - -Goal: Create a cadence for polling queue depth about 1 second after each -completed check. - -```ts -import { Schedule } from "effect" - -const queueDepthPolling = Schedule.spaced("1 second") -``` - -### Run A Heartbeat On An Aligned Cadence - -Goal: Create a heartbeat cadence that runs on aligned 30-second boundaries -instead of waiting 30 seconds after each run finishes. - -```ts -import { Schedule } from "effect" - -const heartbeatCadence = Schedule.fixed("30 seconds") -``` - -### Warm A Cache Once After Deployment - -Goal: Create a follow-up cache warmup that recurs exactly once after about 1 -minute. - -```ts -import { Schedule } from "effect" - -const cacheWarmupFollowUp = Schedule.duration("1 minute") -``` - -### Keep A Health Probe Inside A Time Budget - -Goal: Create a budget that allows health probes to continue only while about 1 -minute has elapsed or less. - -```ts -import { Schedule } from "effect" - -const healthProbeBudget = Schedule.during("1 minute") -``` - -### Retry A Config Fetch With Exponential Backoff - -Goal: Create a retry policy for fetching remote configuration, starting with a -100 millisecond exponential backoff. - -```ts -import { Schedule } from "effect" - -const configFetchBackoff = Schedule.exponential("100 millis") -``` - -### Probe A Cold Replica With Fibonacci Backoff - -Goal: Create a gentler startup probe for a cold search replica, using Fibonacci -backoff and taking the first 4 outputs. - -```ts -import { Schedule } from "effect" - -const searchReplicaWarmup = Schedule.fibonacci("100 millis").pipe( - Schedule.take(4) -) -``` - -### Run A Worker Loop Forever - -Goal: Create an unbounded worker-loop counter with no added delay. - -```ts -import { Schedule } from "effect" - -const workerLoopCounter = Schedule.forever -``` - -### Trigger A Nightly Report - -Goal: Create a schedule for a nightly billing report at 02:00. - -```ts -import { Schedule } from "effect" - -const nightlyBillingReport = Schedule.cron("0 2 * * *") -``` - -### Sample Five-Minute Windows - -Goal: Create a schedule that recurs on 5-minute window boundaries. - -```ts -import { Schedule } from "effect" - -const fiveMinuteWindows = Schedule.windowed("5 minutes") -``` - -## Shape Schedule Outputs - -### Echo Feature Flag Inputs - -Goal: Create a schedule for feature flag snapshots that immediately outputs -each input unchanged and takes the first 3 samples. - -```ts -import { Schedule } from "effect" - -type FeatureFlagSnapshot = { readonly enabled: boolean } - -const featureFlagSamples = Schedule.identity().pipe( - Schedule.take(3) -) -``` - -### Label Retry Attempts - -Goal: Create a retry-attempt schedule that turns recurrence counts into labels -such as `attempt-1`, `attempt-2`, and `attempt-3`. - -```ts -import { Schedule } from "effect" - -const retryAttemptLabels = Schedule.recurs(3).pipe( - Schedule.map((count) => `attempt-${count + 1}`) -) -``` - -### Report Selected Poll Delays - -Goal: Create a telemetry schedule that runs on an aligned 2-second cadence, -outputs selected delays, maps each delay to milliseconds, and takes 3 outputs. - -```ts -import { Duration, Schedule } from "effect" - -const pollDelayReport = Schedule.fixed("2 seconds").pipe( - Schedule.delays, - Schedule.map((delay) => ({ millis: Duration.toMillis(delay) })), - Schedule.take(3) -) -``` - -Explanation: `Schedule.delays` changes the schedule output to the selected -delay. Use it when observability or downstream policy needs the actual delay -rather than the cadence counter. - -### Report Elapsed Runtime - -Goal: Create a runtime sampler that outputs elapsed time in milliseconds and -takes 4 samples. - -```ts -import { Duration, Schedule } from "effect" - -const elapsedRuntimeReport = Schedule.elapsed.pipe( - Schedule.map((elapsed) => ({ millis: Duration.toMillis(elapsed) })), - Schedule.take(4) -) -``` - -## Combine Policies - -### Add Jitter To Webhook Backoff - -Goal: Create a webhook retry backoff that starts at 200 milliseconds, adds -jitter, outputs selected delays, and takes 3 outputs. - -```ts -import { Schedule } from "effect" - -const jitteredWebhookBackoff = Schedule.exponential("200 millis").pipe( - Schedule.jittered, - Schedule.delays, - Schedule.take(3) -) -``` - -### Stop Deployment Hook Retries By Count And Time - -Goal: Create a deployment hook retry budget that uses jittered exponential -backoff, allows at most 5 recurrences, and also stops after about 20 seconds. - -```ts -import { Schedule } from "effect" - -const deploymentHookRetryBudget = Schedule.exponential("200 millis").pipe( - Schedule.jittered, - Schedule.both(Schedule.recurs(5)), - Schedule.both(Schedule.during("20 seconds")) -) -``` - -Explanation: the resulting output is nested because `Schedule.both` preserves -both schedule outputs. Use `Schedule.map` when a smaller output is needed. - -### Continue While Either Probe Is Active - -Goal: Create a service readiness policy that continues while either 2 immediate -warmup probes or a slower 500 millisecond probe schedule still wants to recur. - -```ts -import { Schedule } from "effect" - -const readinessWarmupOrSlowProbe = Schedule.recurs(2).pipe( - Schedule.either(Schedule.spaced("500 millis").pipe(Schedule.take(5))) -) -``` - -Explanation: `Schedule.either` keeps recurring while at least one side can -continue. Its output preserves both sides, so map the result if callers should -see a smaller shape. - -### Warm Up Fast, Then Settle Into Maintenance - -Goal: Create a cache invalidation sequence that runs 2 quick recurrences 100 -milliseconds apart, then 3 slower recurrences 30 seconds apart, then stops. - -```ts -import { Schedule } from "effect" - -const cacheInvalidationSequence = Schedule.spaced("100 millis").pipe( - Schedule.take(2), - Schedule.andThen(Schedule.spaced("30 seconds").pipe(Schedule.take(3))) -) -``` - -### Preserve Warmup And Steady Phases - -Goal: Create a retry classifier with a fast exponential phase and a steady -Fibonacci phase, preserving the phase in the output. - -```ts -import { Result, Schedule } from "effect" - -const phasedRetryClassifier = Schedule.exponential("100 millis").pipe( - Schedule.take(2), - Schedule.andThenResult(Schedule.fibonacci("500 millis").pipe(Schedule.take(3))), - Schedule.map((result) => - Result.match(result, { - onFailure: (delay) => ({ phase: "steady", delay }), - onSuccess: (delay) => ({ phase: "fast", delay }) - }) - ) -) -``` - -Explanation: `Schedule.andThenResult` keeps phase information in the output. -The first schedule is represented by the success side, and the second schedule -is represented by the failure side. - -## Work With Inputs - -### Poll Upload Progress Until Complete - -Goal: Create an upload-progress schedule that waits about 1 second between -checks, outputs the latest progress object, and continues only while the upload -is incomplete. - -```ts -import { Schedule } from "effect" - -type UploadProgress = { readonly percent: number } - -const uploadProgressUntilComplete = Schedule.spaced("1 second").pipe( - Schedule.setInputType(), - Schedule.passthrough, - Schedule.while(({ input }) => input.percent < 100) -) -``` - -Explanation: `Schedule.setInputType` tells TypeScript which input each step -receives. `Schedule.passthrough` makes the output the latest input, so a polling -schedule can return the final status instead of a counter. - -### Keep A History Of Retry Error Codes - -Goal: Create a schedule for string error-code inputs that checks about once per -second and outputs all input codes seen so far. - -```ts -import { Schedule } from "effect" - -const retryErrorCodeHistory = Schedule.collectInputs( - Schedule.spaced("1 second").pipe( - Schedule.setInputType() - ) -) -``` - -Explanation: `Schedule.collectInputs` is useful when the input stream is the -thing to audit, such as retry errors or status samples. - -### Collect Deployment Phases Until Failure - -Goal: Create a schedule that outputs the collected deployment phases while the -input phase is not `failed`. - -```ts -import { Schedule } from "effect" - -type DeploymentPhase = { - readonly phase: "created" | "deploying" | "verifying" | "failed" -} - -const deploymentPhaseHistory = Schedule.identity().pipe( - Schedule.collectWhile(({ input }) => input.phase !== "failed") -) -``` - -Explanation: `Schedule.collectWhile` accumulates accepted outputs and stops -collecting when the predicate is false. This is cheaper to read than manually -combining `identity`, `while`, and `reduce` for simple histories. - -### Collect Heartbeat Counts - -Goal: Create a schedule that recurs forever, collects recurrence counts produced -so far, and takes only the first 3 collected outputs. - -```ts -import { Schedule } from "effect" - -const heartbeatCountHistory = Schedule.collectOutputs(Schedule.forever).pipe( - Schedule.take(3) -) -``` - -Explanation: `Schedule.collectOutputs` collects the schedule output, not the -input. Add `Schedule.take` or another bound when collecting from an unbounded -schedule. - -## Accumulate State - -### Accumulate Request Latency Stats - -Goal: Create a schedule for latency samples that inspects the first 5 inputs and -outputs running `min`, `max`, `total`, and `count` fields. - -```ts -import { Schedule } from "effect" - -const requestLatencyStats = Schedule.identity().pipe( - Schedule.take(5), - Schedule.reduce( - () => ({ min: Number.POSITIVE_INFINITY, max: 0, total: 0, count: 0 }), - (state, latency) => ({ - min: Math.min(state.min, latency), - max: Math.max(state.max, latency), - total: state.total + latency, - count: state.count + 1 - }) - ) -) -``` - -Explanation: `Schedule.reduce` is for a running aggregate. Prefer it over -collecting every value when callers only need a summary. - -### Count Recent Worker Failures - -Goal: Create a schedule for boolean failure samples that inspects the first 5 -inputs and outputs the running count of failed samples. - -```ts -import { Schedule } from "effect" - -const recentWorkerFailureCount = Schedule.identity().pipe( - Schedule.take(5), - Schedule.reduce(() => 0, (count, failed) => failed ? count + 1 : count) -) -``` - -## Adapt Delays - -### Slow A Queue Consumer Under Backpressure - -Goal: Create a queue backpressure schedule that outputs each queue snapshot, -continues while the queue is not paused, adds 5 seconds of delay when depth is -above 1000, adds 500 milliseconds otherwise, and takes 10 outputs. - -```ts -import { Effect, Schedule } from "effect" - -type QueueSnapshot = { readonly depth: number; readonly paused: boolean } - -const queueBackpressureSchedule = Schedule.identity().pipe( - Schedule.while(({ input }) => !input.paused), - Schedule.addDelay((snapshot) => Effect.succeed(snapshot.depth > 1000 ? "5 seconds" : "500 millis")), - Schedule.take(10) -) -``` - -Explanation: `Schedule.addDelay` adds delay based on the current schedule -output. It is a good fit for input-driven pacing when the output is already the -latest input. - -### Cap WebSocket Reconnect Delays - -Goal: Create a reconnect policy that uses jittered exponential backoff, caps -selected delays at 5 seconds, allows at most 8 recurrences, and outputs the -selected delay. - -```ts -import { Duration, Effect, Schedule } from "effect" - -const websocketReconnectDelays = Schedule.exponential("100 millis").pipe( - Schedule.jittered, - Schedule.modifyDelay((_, delay) => Effect.succeed(Duration.min(delay, Duration.seconds(5)))), - Schedule.both(Schedule.recurs(8)), - Schedule.delays -) -``` - -Explanation: `Schedule.modifyDelay` receives the selected delay and returns the -replacement delay. Use it for caps, floors, clamps, or provider-provided delay -hints. - -## Observe Schedule Decisions - -### Log Heartbeat Inputs - -Goal: Create a heartbeat schedule that runs on an aligned 10-second cadence, -logs each service id input, outputs the input unchanged, and takes 2 outputs. - -```ts -import { Console, Schedule } from "effect" - -type HeartbeatStatus = { readonly id: string } - -const heartbeatInputLogs = Schedule.fixed("10 seconds").pipe( - Schedule.setInputType(), - Schedule.tapInput((input) => Console.log(`heartbeat:${input.id}`)), - Schedule.passthrough, - Schedule.take(2) -) -``` - -### Record Backoff Delays - -Goal: Create a retry schedule that uses Fibonacci backoff, takes 5 outputs, and -logs each selected delay without changing the schedule output. - -```ts -import { Console, Schedule } from "effect" - -const loggedBackoffDelays = Schedule.fibonacci("200 millis").pipe( - Schedule.take(5), - Schedule.tapOutput((delay) => Console.log(delay)) -) -``` - -### Log Attempt Metadata - -Goal: Create a telemetry backoff that logs each attempt number and selected -delay in milliseconds without changing the schedule output. - -```ts -import { Console, Duration, Schedule } from "effect" - -const telemetryBackoffPolicy = Schedule.exponential("250 millis").pipe( - Schedule.take(5), - Schedule.tap(({ attempt, output }) => Console.log(`attempt-${attempt}: ${Duration.toMillis(output)}ms`)) -) -``` - -Explanation: use `tapInput` when the input matters, `tapOutput` when only the -output matters, and `tap` when metadata such as attempt number or selected -duration matters. None of these operators changes the schedule output. - -## Build Local State Machines - -### Track Scheduler Ticks - -Goal: Create a local scheduler tick state that starts at 1, increments after -each output, maps each output to a `tick` object, and takes 4 outputs. - -```ts -import { Effect, Schedule } from "effect" - -const schedulerTickState = Schedule.unfold(1, (n) => Effect.succeed(n + 1)).pipe( - Schedule.map((tick) => ({ tick })), - Schedule.take(4) -) -``` - -Explanation: `Schedule.unfold` builds a schedule from local state. Use it when -the next output depends on private scheduler state rather than on the latest -input. - -### Cycle A Maintenance Phase Machine - -Goal: Create a local phase machine for maintenance work. Start at `warming`, -move to `active`, then `cooling`, then back to `active`, and take 6 outputs. - -```ts -import { Effect, Schedule } from "effect" - -type MaintenancePhase = "warming" | "active" | "cooling" - -const maintenancePhaseMachine = Schedule.unfold( - "warming", - (phase) => { - switch (phase) { - case "warming": - return Effect.succeed("active") - case "active": - return Effect.succeed("cooling") - case "cooling": - return Effect.succeed("active") - } - } -).pipe( - Schedule.take(6) -) -``` - -## Realistic Policies - -### Retry An HTTP Gateway With A Delay Envelope - -Goal: Create an HTTP gateway retry schedule. Retry only network failures, status -429, and status 500, 502, or 503. Use jittered exponential backoff starting at -100 milliseconds, cap selected delays at 2 seconds, allow at most 6 recurrences, -and output the selected delay. - -```ts -import { Duration, Effect, Schedule } from "effect" - -type GraphqlGatewayError = - | { readonly _tag: "Network" } - | { readonly _tag: "HttpStatus"; readonly status: number } - | { readonly _tag: "BadRequest" } - -const isRetryableGraphqlGatewayError = ( - error: GraphqlGatewayError -): boolean => - error._tag === "Network" || - (error._tag === "HttpStatus" && - (error.status === 429 || - error.status === 500 || - error.status === 502 || - error.status === 503)) - -const graphqlGatewayRetry = Schedule.exponential("100 millis").pipe( - Schedule.jittered, - Schedule.setInputType(), - Schedule.modifyDelay((_, delay) => Effect.succeed(Duration.min(delay, Duration.seconds(2)))), - Schedule.both(Schedule.recurs(6)), - Schedule.while(({ input }) => isRetryableGraphqlGatewayError(input)), - Schedule.delays -) -``` - -### Poll A Rollout With A Deadline - -Goal: Create a rollout watcher that starts from an aligned 1-second cadence, -jitters the selected delay, outputs the latest status, continues only while the -rollout is running, and stops after about 2 minutes. - -```ts -import { Schedule } from "effect" - -type RolloutStatus = { - readonly state: "running" | "succeeded" | "failed" -} - -const rolloutStatusWatcher = Schedule.fixed("1 second").pipe( - Schedule.setInputType(), - Schedule.passthrough, - Schedule.jittered, - Schedule.both(Schedule.during("2 minutes")), - Schedule.while(({ input }) => input.state === "running"), - Schedule.map(([status]) => status) -) -``` - -### Respect A Provider Retry-After Header - -Goal: Create a provider retry schedule. Retry status 429, 500, and 503. Use -exponential backoff starting at 1 second. When `retryAfter` is present, use it as -a lower bound. Cap selected delays at 1 minute, allow at most 6 recurrences, and -output the selected delay. - -```ts -import { Duration, Effect, Schedule } from "effect" - -type PushProviderResponse = { - readonly status: 429 | 500 | 503 | 400 - readonly retryAfter: Duration.Duration | undefined -} - -const pushNotificationProviderRetry = Schedule.exponential("1 second").pipe( - Schedule.setInputType(), - Schedule.passthrough, - Schedule.modifyDelay((response, delay) => - Effect.succeed( - Duration.min( - response.retryAfter === undefined - ? delay - : Duration.max(delay, response.retryAfter), - Duration.minutes(1) - ) - ) - ), - Schedule.both(Schedule.recurs(6)), - Schedule.while(({ input }) => input.status === 429 || input.status === 500 || input.status === 503), - Schedule.delays -) -``` - -### Poll An OAuth Device Code Flow - -Goal: Create an OAuth device-code polling schedule. Poll every 5 seconds, add -another 5 seconds for `slow_down`, output the latest input, continue only for -`authorization_pending` and `slow_down`, and stop after about 15 minutes. - -```ts -import { Effect, Schedule } from "effect" - -type OAuthDeviceCodeStatus = { - readonly error: - | "authorization_pending" - | "slow_down" - | "access_denied" - | "expired_token" -} - -const oauthDeviceCodePolling = Schedule.spaced("5 seconds").pipe( - Schedule.setInputType(), - Schedule.passthrough, - Schedule.addDelay((status) => Effect.succeed(status.error === "slow_down" ? "5 seconds" : "0 millis")), - Schedule.both(Schedule.during("15 minutes")), - Schedule.while(({ input }) => input.error === "authorization_pending" || input.error === "slow_down"), - Schedule.map(([status]) => status) -) -``` - -### Escalate Incident Notifications In Phases - -Goal: Create an incident escalation cadence that emits 3 recurrences spaced 1 -minute apart, then 3 recurrences spaced 5 minutes apart, then switches to an -aligned 15-minute cadence. - -```ts -import { Schedule } from "effect" - -const incidentEscalationCadence = Schedule.spaced("1 minute").pipe( - Schedule.take(3), - Schedule.andThen(Schedule.spaced("5 minutes").pipe(Schedule.take(3))), - Schedule.andThen(Schedule.fixed("15 minutes")) -) -``` - -### Run Maintenance After A Warmup - -Goal: Create a maintenance schedule that performs one warmup recurrence after -about 30 seconds, then switches to a cron schedule that recurs every day at -03:00. - -```ts -import { Schedule } from "effect" - -const maintenanceCronAfterWarmup = Schedule.duration("30 seconds").pipe( - Schedule.andThen(Schedule.cron("0 3 * * *")) -) -``` diff --git a/deno.json b/deno.json index 216b21ee9..38de0651a 100644 --- a/deno.json +++ b/deno.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/refs/heads/main/cli/schemas/config-file.v1.json", "nodeModulesDir": "manual", "unstable": ["bare-node-builtins", "node-globals"], - "workspace": ["./packages/*"], + "workspace": ["./packages/*", "./packages/platform/*"], "exclude": [ "**/*.mjs", "**/*.cjs", @@ -18,15 +18,17 @@ "scripts/", "scratchpad/", "packages/*/typetest/", + "packages/*/typeperf/", + "packages/*/runtimeperf/", "packages/*/benchmark/", "packages/ai", "packages/atom", "packages/effect/test/cluster/", "packages/opentelemetry/", - "packages/platform-browser/", - "packages/platform-bun/", - "packages/platform-node/", - "packages/platform-node-shared/", + "packages/platform/browser/", + "packages/platform/bun/", + "packages/platform/node/", + "packages/platform/node-shared/", "packages/tools/", "packages/sql" ] diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 1a41e02f1..000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,18 +0,0 @@ -services: - pg: - image: postgres:alpine # Using a lightweight Postgres image - environment: - POSTGRES_DB: effect_cluster - POSTGRES_USER: cluster - POSTGRES_PASSWORD: cluster - ports: - - "5432:5432" # Map host port 5432 to container port 5432 - volumes: - - db_data:/var/lib/postgresql/data # Persist data in a named volume - redis: - image: redis:alpine - ports: - - "6379:6379" - -volumes: - db_data: # Define the named volume diff --git a/dprint.json b/dprint.json index a500b54d7..7d1a121c6 100644 --- a/dprint.json +++ b/dprint.json @@ -18,11 +18,22 @@ "**/build", "**/docs", "**/coverage", - "packages/**/CHANGELOG.md", + "**/*.tsbuildinfo", + "**/CHANGELOG.md", + "packages/effect/src/StandardSchema.ts", "!scratchpad/**/*", - ".agents", - ".context", - ".specs" + "**/.*", + "!.agents", + "!.agents/**/*", + "!.changeset", + "!.changeset/**/*", + "!.envrc", + "!.github", + "!.github/**/*", + "!.gitignore", + "!.oxlintrc.json", + "!.vscode", + "!.vscode/**/*" ], "plugins": [ "https://plugins.dprint.dev/typescript-0.93.4.wasm", diff --git a/flake.lock b/flake.lock index 5613a8911..de3033b7a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1778458615, - "narHash": "sha256-cY07EsdhBJ8tFXPzDYevgqxRev9ZLxFonuq9wmq5kwg=", + "lastModified": 1786534138, + "narHash": "sha256-fBJMdnKUTUDtfi/BYLr71HLaC9dG382arxLF2Egg2uo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c6e5ca3c836a5f4dd9af9f2c1fc1c38f0fac988a", + "rev": "044bfe75bfe4c7bbe043dc17b5e42ea823b84a09", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 128059293..664d14810 100644 --- a/flake.nix +++ b/flake.nix @@ -14,8 +14,8 @@ packages = with pkgs; [ bun deno - corepack - nodejs_25 + nodejs_latest + pnpm python3 ]; }; diff --git a/jsdocs.config.json b/jsdocs.config.json index 07ea6b999..6811e35b4 100644 --- a/jsdocs.config.json +++ b/jsdocs.config.json @@ -6,8 +6,10 @@ ], "exclude": [ "**/node_modules/**", + "packages/**/ai-docs/**", "packages/tools/**", "packages/**/src/index.ts", + "packages/effect/src/StandardSchema.ts", "packages/**/src/*Generated.ts", "packages/**/src/internal/**", "packages/**/src/**/internal/**" diff --git a/migration/annotations/README.md b/migration/annotations/README.md new file mode 100644 index 000000000..d78e65871 --- /dev/null +++ b/migration/annotations/README.md @@ -0,0 +1,14 @@ +# Migration annotations + +Add one YAML file per v3 module. Each file maps stable API ids (without the +snapshot's trailing `#type` or `#value` facet) to migration guidance: + +```yaml +effect/Effect#async: + replacement: Effect.callback + note: Use the callback constructor. + example: Effect.callback((resume) => resume(Effect.void)) +``` + +Run `pnpm api-diff --check` to list missing ids and +`pnpm api-diff --write-doc migration/v3-to-v4.md` to regenerate the reference. diff --git a/migration/annotations/effect__Arbitrary.yaml b/migration/annotations/effect__Arbitrary.yaml new file mode 100644 index 000000000..68be8ec47 --- /dev/null +++ b/migration/annotations/effect__Arbitrary.yaml @@ -0,0 +1,20 @@ +"effect/Arbitrary": + replacement: "effect/unstable/arbitrary/Arbitrary" + note: "Schema-derived generation moved to the native Arbitrary module. Effect no longer bridges to fast-check." +"effect/Arbitrary#ArbitraryAnnotation": + replacement: "Schema.Annotations.ToArbitrary.Declaration" + note: "Arbitrary derivation annotations now live in Schema.Annotations. Attach a toCodecArbitrary declaration callback that returns a Schema Link." +"effect/Arbitrary#ArbitraryGenerationContext": + replacement: "Schema.Annotations.ToArbitrary.DeclarationInput" + note: "Native arbitrary callbacks receive DeclarationInput with decoded type-parameter schemas and normalized constraints." +"effect/Arbitrary#LazyArbitrary": + replacement: "effect/unstable/arbitrary/Arbitrary#Arbitrary" + note: "The generated-value description is now the native Arbitrary interface from effect/unstable/arbitrary." +"effect/Arbitrary#make": + replacement: "effect/unstable/arbitrary/Arbitrary#schema" + note: "Derive a native Arbitrary from a Schema. Effect no longer bridges to fast-check." + example: "Arbitrary.schema(schema)" +"effect/Arbitrary#makeLazy": + replacement: "effect/unstable/arbitrary/Arbitrary#schema" + note: "Lazy and eager Schema derivation are the same native Arbitrary.schema constructor." + example: "Arbitrary.schema(schema)" diff --git a/migration/annotations/effect__Array.yaml b/migration/annotations/effect__Array.yaml new file mode 100644 index 000000000..930975b29 --- /dev/null +++ b/migration/annotations/effect__Array.yaml @@ -0,0 +1,78 @@ +"effect/Array#filterMapWhile": + replacement: "Array.takeWhileFilter" + note: "Same map-until-first-miss behavior; change the callback from Option.some/none to Result.succeed/fail." +"effect/Array#flatMapNullable": + replacement: "Array.flatMapNullishOr" + note: "Direct nullish-terminology rename; null and undefined mapper results are still discarded." +"effect/Array#fromNullable": + replacement: "Array.fromNullishOr" + note: "Direct nullish-terminology rename; null and undefined become an empty array and other values become a singleton." +"effect/Array#getEquivalence": + replacement: "Array.makeEquivalence" + note: "Direct rename; pass the element Equivalence unchanged." +"effect/Array#getLefts": + replacement: "Array.getFailures" + note: "Either became Result; this extracts failure payloads in input order." +"effect/Array#getOrder": + replacement: "Array.makeOrder" + note: "Direct rename; pass the element Order unchanged." +"effect/Array#getRights": + replacement: "Array.getSuccesses" + note: "Either became Result; this extracts success payloads in input order." +"effect/Array#init": + replacement: "Array.init" + note: "The API and Option> behavior remain unchanged." +"effect/Array#isEmptyArray": + replacement: "Array.isArrayEmpty" + note: "Direct word-order rename; retains the mutable empty-array type guard." +"effect/Array#isEmptyReadonlyArray": + replacement: "Array.isReadonlyArrayEmpty" + note: "Direct word-order rename; retains the readonly empty-array type guard." +"effect/Array#isNonEmptyArray": + replacement: "Array.isArrayNonEmpty" + note: "Direct word-order rename; retains the mutable NonEmptyArray type guard." +"effect/Array#isNonEmptyReadonlyArray": + replacement: "Array.isReadonlyArrayNonEmpty" + note: "Direct word-order rename; retains the NonEmptyReadonlyArray type guard." +"effect/Array#liftEither": + replacement: "Array.liftResult" + note: "Either became Result; failures produce an empty array and successes produce a singleton." +"effect/Array#liftNullable": + replacement: "Array.liftNullishOr" + note: "Direct nullish-terminology rename; the lifted function still returns zero or one element." +"effect/Array#modifyNonEmptyHead": + replacement: "Array.modifyHeadNonEmpty" + note: "Direct word-order rename with the same non-empty-preserving result." +"effect/Array#modifyNonEmptyLast": + replacement: "Array.modifyLastNonEmpty" + note: "Direct word-order rename with the same non-empty-preserving result." +"effect/Array#modifyOption": + replacement: "Array.modify" + note: "The Option suffix was dropped; an out-of-bounds index still returns Option.none." +"effect/Array#partitionMap": + replacement: "Array.partition" + note: "Pass a Result-returning mapper instead of Either; the output remains [failures, successes], corresponding to v3 [lefts, rights]." +"effect/Array#ReadonlyArray": + replacement: "Array.ReadonlyArray" + note: "The namespace and its Infer, With, OrNonEmpty, AndNonEmpty, and Flatten utility types remain." +"effect/Array#removeOption": + replacement: "Array.remove" + note: "The closest API now returns an unchanged copy out of bounds; use Array.get before Array.remove to preserve the old Option result." +"effect/Array#replaceOption": + replacement: "Array.replace" + note: "The Option suffix was dropped; an out-of-bounds index still returns Option.none." +"effect/Array#setNonEmptyHead": + replacement: "Array.setHeadNonEmpty" + note: "Direct word-order rename with the same non-empty-preserving result." +"effect/Array#setNonEmptyLast": + replacement: "Array.setLastNonEmpty" + note: "Direct word-order rename with the same non-empty-preserving result." +"effect/Array#splitNonEmptyAt": + replacement: "Array.splitAtNonEmpty" + note: "Direct word-order rename; the left output remains guaranteed non-empty." +"effect/Array#tail": + replacement: "Array.tail" + note: "The API and Option> behavior remain unchanged." +"effect/Array#unsafeGet": + replacement: "Array.getUnsafe" + note: "Direct word-order rename; it still throws for an out-of-bounds index." diff --git a/migration/annotations/effect__BigDecimal.yaml b/migration/annotations/effect__BigDecimal.yaml new file mode 100644 index 000000000..19b9fe387 --- /dev/null +++ b/migration/annotations/effect__BigDecimal.yaml @@ -0,0 +1,36 @@ +"effect/BigDecimal#BigDecimal": + replacement: "BigDecimal.BigDecimal" + note: "The model interface remains, but its brand key is now internal." +"effect/BigDecimal#greaterThan": + replacement: "BigDecimal.isGreaterThan" + note: "Renamed with the v4 is-prefix." +"effect/BigDecimal#greaterThanOrEqualTo": + replacement: "BigDecimal.isGreaterThanOrEqualTo" + note: "Renamed with the v4 is-prefix." +"effect/BigDecimal#lessThan": + replacement: "BigDecimal.isLessThan" + note: "Renamed with the v4 is-prefix." +"effect/BigDecimal#lessThanOrEqualTo": + replacement: "BigDecimal.isLessThanOrEqualTo" + note: "Renamed with the v4 is-prefix." +"effect/BigDecimal#safeFromNumber": + replacement: "BigDecimal.fromNumber" + note: "Use the safe v4 constructor, which still returns Option." +"effect/BigDecimal#TypeId": + replacement: "none" + note: "The brand key is internal in v4; use BigDecimal.isBigDecimal for runtime narrowing." +"effect/BigDecimal#unsafeDivide": + replacement: "BigDecimal.divideUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." +"effect/BigDecimal#unsafeFromNumber": + replacement: "BigDecimal.fromNumberUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." +"effect/BigDecimal#unsafeFromString": + replacement: "BigDecimal.fromStringUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." +"effect/BigDecimal#unsafeRemainder": + replacement: "BigDecimal.remainderUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." +"effect/BigDecimal#unsafeToNumber": + replacement: "BigDecimal.toNumberUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." diff --git a/migration/annotations/effect__BigInt.yaml b/migration/annotations/effect__BigInt.yaml new file mode 100644 index 000000000..a0fc52841 --- /dev/null +++ b/migration/annotations/effect__BigInt.yaml @@ -0,0 +1,21 @@ +"effect/BigInt#fromNumber": + replacement: "BigInt.fromNumber" + note: "Unchanged; it returns Option for safe conversion." +"effect/BigInt#greaterThan": + replacement: "BigInt.isGreaterThan" + note: "Renamed with the v4 is-prefix." +"effect/BigInt#greaterThanOrEqualTo": + replacement: "BigInt.isGreaterThanOrEqualTo" + note: "Renamed with the v4 is-prefix." +"effect/BigInt#lessThan": + replacement: "BigInt.isLessThan" + note: "Renamed with the v4 is-prefix." +"effect/BigInt#lessThanOrEqualTo": + replacement: "BigInt.isLessThanOrEqualTo" + note: "Renamed with the v4 is-prefix." +"effect/BigInt#unsafeDivide": + replacement: "BigInt.divideUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." +"effect/BigInt#unsafeSqrt": + replacement: "BigInt.sqrtUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." diff --git a/migration/annotations/effect__Brand.yaml b/migration/annotations/effect__Brand.yaml new file mode 100644 index 000000000..9d07fa7aa --- /dev/null +++ b/migration/annotations/effect__Brand.yaml @@ -0,0 +1,33 @@ +"effect/Brand#all": + replacement: "Brand.all" + note: "Still exported; combines multiple brand constructors and checks." +"effect/Brand#Brand": + replacement: "Brand.Brand" + note: "Still exported, but v4 brand keys are strings rather than symbols." +"effect/Brand#Brand.BrandErrors": + replacement: "Brand.BrandError" + note: "Validation now returns one BrandError wrapping a SchemaIssue.Issue instead of an error array." +"effect/Brand#Brand.RefinementError": + replacement: "Schema.FilterIssue" + note: "Brand.make validators use Schema filter output instead of the old message and meta record." +"effect/Brand#Branded": + replacement: "Brand.Branded" + note: "Still exported, with the brand key restricted to string." +"effect/Brand#BrandTypeId": + replacement: "none" + note: "The public marker was removed; the v4 brand type id is private." +"effect/Brand#error": + replacement: "Brand.make" + note: "Return a string or Schema filter issue directly from a Brand.make validator." +"effect/Brand#nominal": + replacement: "Brand.nominal" + note: "Still exported; the constructor's either method is now result." +"effect/Brand#refined": + replacement: "Brand.make" + note: "Use Brand.make for custom validation or Brand.check for Schema checks." +"effect/Brand#RefinedConstructorsTypeId": + replacement: "none" + note: "The public refined-constructor marker was removed." +"effect/Brand#unbranded": + replacement: "Function.cast" + note: "Brands are runtime-identical to their base value; cast explicitly when an unbranded type is required." diff --git a/migration/annotations/effect__Cache.yaml b/migration/annotations/effect__Cache.yaml new file mode 100644 index 000000000..c696acbc9 --- /dev/null +++ b/migration/annotations/effect__Cache.yaml @@ -0,0 +1,33 @@ +effect/Cache#Cache: + replacement: "Cache.Cache" + note: "The cache model remains, but v4 exposes a Pipeable value with explicit Cache operations and adds a lookup environment parameter." +effect/Cache#Cache.ConsumerVariance: + replacement: "none" + note: "The ConsumerCache view and its variance marker were removed; expose a narrower application interface around Cache operations when write access must be hidden." +effect/Cache#Cache.Variance: + replacement: "none" + note: "The public variance marker was removed; use Cache.Cache directly and do not depend on its branding internals." +effect/Cache#CacheStats: + replacement: "none" + note: "Built-in hit and miss statistics were removed; instrument the lookup and Cache.get calls explicitly, and use Cache.size for the current entry count." +effect/Cache#CacheTypeId: + replacement: "none" + note: "The cache type id is internal in v4; do not inspect or construct the cache brand directly." +effect/Cache#ConsumerCache: + replacement: "Cache.Cache" + note: "ConsumerCache was removed; use Cache.Cache and expose an application-defined read-only wrapper if capability restriction is required." +effect/Cache#ConsumerCacheTypeId: + replacement: "none" + note: "ConsumerCache and its type id were removed with the read-only cache view." +effect/Cache#EntryStats: + replacement: "none" + note: "Per-entry loaded-time statistics were removed; record lookup timing in application instrumentation if needed." +effect/Cache#Lookup: + replacement: "(key: Key) => Effect.Effect" + note: "The named alias was removed; use an inline lookup function type or Cache.Cache[\"lookup\"]." +effect/Cache#makeCacheStats: + replacement: "none" + note: "CacheStats and its constructor were removed; define an application metrics record if these counters are still required." +effect/Cache#makeEntryStats: + replacement: "none" + note: "EntryStats and its constructor were removed; capture lookup timing in application instrumentation instead." diff --git a/migration/annotations/effect__Cause.yaml b/migration/annotations/effect__Cause.yaml new file mode 100644 index 000000000..cc4611e8a --- /dev/null +++ b/migration/annotations/effect__Cause.yaml @@ -0,0 +1,213 @@ +effect/Cause#andThen: + replacement: "Cause.fromReasons(self.reasons.flatMap(...))" + note: "No direct v4 combinator. For each Fail reason, splice either f(reason.error).reasons or the constant cause's reasons; retain Die and Interrupt reasons, then rebuild with Cause.fromReasons." +effect/Cause#as: + replacement: "Cause.map(self, () => error)" + note: "Use Cause.map with a constant function; only Fail errors change and Die/Interrupt reasons remain." +effect/Cause#Cause: + replacement: "Cause.Cause" + note: "The name remains, but v4 Cause is a wrapper with readonly reasons: ReadonlyArray>, not the v3 Empty/Fail/Die/Interrupt/Sequential/Parallel tree." +effect/Cause#Cause.Variance: + replacement: "none" + note: "The public variance helper was removed. Cause.Cause is directly branded by Cause.TypeId; application code should not reproduce the old variance member." +effect/Cause#CauseReducer: + replacement: "cause.reasons.reduce" + note: "The six-case tree reducer type was removed with Empty, Sequential, and Parallel. Reduce the flat Reason array and switch on Fail, Die, or Interrupt instead." +effect/Cause#CauseTypeId: + replacement: "Cause.TypeId" + note: "The brand export is Cause.TypeId, a literal-string const. Use typeof Cause.TypeId in type positions; the v3 unique-symbol CauseTypeId alias is gone." +effect/Cause#contains: + replacement: "Equal.equals(Cause.combine(self, that), self)" + note: "There are no subtrees in v4. This tests whether all reasons from that are already present in self under v4 reason equality; use Equal.equals(self, that) when only whole-cause equality is intended." +effect/Cause#defects: + replacement: "self.reasons.filter(Cause.isDieReason).map((reason) => reason.defect)" + note: "Collect defect values from the flat Reason array. The result is a standard array rather than v3 Chunk." +effect/Cause#Die: + replacement: "Cause.Die" + note: "The name remains, but Cause.Die is now a Reason stored in cause.reasons, not a Cause variant. Construct a standalone reason with Cause.makeDieReason or a cause with Cause.die." +effect/Cause#dieOption: + replacement: "Cause.findDefect" + note: "Cause.findDefect returns Result.Result>, not Option. Match the Result or convert it to Option when the old return shape is required." +effect/Cause#Empty: + replacement: "Cause.empty" + note: "The Empty subtype and _tag were removed. Empty is Cause.empty, represented by cause.reasons.length === 0." +effect/Cause#ExceededCapacityException: + replacement: "Cause.ExceededCapacityError" + note: "Rename the class/type and update the discriminant from ExceededCapacityException to ExceededCapacityError." +effect/Cause#ExceededCapacityExceptionTypeId: + replacement: "Cause.ExceededCapacityErrorTypeId" + note: "Rename the brand; v4 exports a literal-string const, so use typeof Cause.ExceededCapacityErrorTypeId in type positions." +effect/Cause#Fail: + replacement: "Cause.Fail" + note: "The name remains, but Cause.Fail is now a Reason stored in cause.reasons, not a Cause variant. Construct a standalone reason with Cause.makeFailReason or a cause with Cause.fail." +effect/Cause#failureOption: + replacement: "Cause.findErrorOption" + note: "Direct Option-based replacement for extracting the first typed Fail error value." +effect/Cause#failureOrCause: + replacement: "Cause.findError" + note: "Use the v4 Result-based split: success is the first E and failure is the original Cause when no Fail reason exists." +effect/Cause#failures: + replacement: "self.reasons.filter(Cause.isFailReason).map((reason) => reason.error)" + note: "Collect typed error values from the flat Reason array. The result is a standard array rather than v3 Chunk." +effect/Cause#filter: + replacement: "Cause.fromReasons(self.reasons.filter(...))" + note: "No exact tree-level equivalent: v3 predicates selected recursive child causes. Rewrite the predicate for Cause.Reason values, filter cause.reasons, and rebuild with Cause.fromReasons." +effect/Cause#find: + replacement: "Option.firstSomeOf(self.reasons.map(...))" + note: "No recursive nodes remain. Apply the partial function to Reason values and take the first Some, or use Cause.findFail/findError/findDie/findDefect/findInterrupt for standard searches." +effect/Cause#flatMap: + replacement: "Cause.fromReasons(self.reasons.flatMap((reason) => Cause.isFailReason(reason) ? f(reason.error).reasons : [reason]))" + note: "No direct v4 export. Flat-map only Fail reasons into replacement causes, preserve Die/Interrupt reasons, and rebuild from the resulting Reason array." +effect/Cause#flatten: + replacement: "Cause.fromReasons(self.reasons.flatMap((reason) => Cause.isFailReason(reason) ? reason.error.reasons : [reason]))" + note: "No direct v4 export. For Cause>, splice each Fail reason's nested cause.reasons and retain Die/Interrupt reasons." +effect/Cause#flipCauseOption: + replacement: "Cause.fromReasons + Option" + note: "Rewrite over reasons: drop Fail(None), replace Fail(Some(e)) with Cause.makeFailReason(e), retain Die/Interrupt, then return None only when a non-empty input loses every reason; preserve Some(Cause.empty) for an empty input." +effect/Cause#IllegalArgumentException: + replacement: "Cause.IllegalArgumentError" + note: "Rename the class/type and update the discriminant from IllegalArgumentException to IllegalArgumentError." +effect/Cause#IllegalArgumentExceptionTypeId: + replacement: "Cause.IllegalArgumentErrorTypeId" + note: "Rename the brand; v4 exports a literal-string const, so use typeof Cause.IllegalArgumentErrorTypeId in type positions." +effect/Cause#Interrupt: + replacement: "Cause.Interrupt" + note: "The name remains, but it is now a Reason in cause.reasons rather than a Cause variant, and fiberId changed from FiberId.FiberId to number | undefined. Use Cause.makeInterruptReason or Cause.interrupt." +effect/Cause#InterruptedException: + replacement: "none" + note: "The public exception class was removed. Represent cancellation with Cause.interrupt; Cause.prettyErrors creates an ordinary Error named InterruptError for interrupt-only rendering, but no class is exported." +effect/Cause#InterruptedExceptionTypeId: + replacement: "none" + note: "Removed with InterruptedException; v4 exports no interruption-error brand. Inspect the Cause with Cause.hasInterrupts or Cause.hasInterruptsOnly instead." +effect/Cause#interruptOption: + replacement: "Cause.findInterrupt" + note: "The replacement returns Result.Result> rather than Option; on success read reason.fiberId, now number | undefined." +effect/Cause#InvalidPubSubCapacityException: + replacement: "Error" + note: "The dedicated public type was removed. Current v4 PubSub capacity validation throws a standard global Error with the capacity message." +effect/Cause#InvalidPubSubCapacityExceptionTypeId: + replacement: "none" + note: "Removed with InvalidPubSubCapacityException; the standard Error now thrown by PubSub has no Effect-specific brand." +effect/Cause#isDie: + replacement: "Cause.hasDies" + note: "Use the v4 cause-level predicate for the presence of at least one Die reason." +effect/Cause#isDieType: + replacement: "Cause.isDieReason" + note: "Apply this guard to an entry of cause.reasons; Cause itself is no longer a Die union variant." +effect/Cause#isEmpty: + replacement: "self.reasons.length === 0" + note: "V4 represents an empty cause with an empty reasons array and exports no isEmpty function." +effect/Cause#isEmptyType: + replacement: "self.reasons.length === 0" + note: "The check remains possible, but there is no Empty subtype to narrow to because v4 Cause is not a variant union." +effect/Cause#isExceededCapacityException: + replacement: "Cause.isExceededCapacityError" + note: "Rename the guard along with ExceededCapacityError." +effect/Cause#isFailType: + replacement: "Cause.isFailReason" + note: "Apply this guard to an entry of cause.reasons; Cause itself is no longer a Fail union variant." +effect/Cause#isFailure: + replacement: "Cause.hasFails" + note: "Use the v4 cause-level predicate for the presence of at least one Fail reason." +effect/Cause#isIllegalArgumentException: + replacement: "Cause.isIllegalArgumentError" + note: "Rename the guard along with IllegalArgumentError." +effect/Cause#isInterrupted: + replacement: "Cause.hasInterrupts" + note: "Use the v4 cause-level predicate for the presence of at least one Interrupt reason." +effect/Cause#isInterruptedException: + replacement: "none" + note: "No v4 InterruptError class or unknown-value guard is exported. When the Cause is available, test Cause.hasInterruptsOnly before squashing or rendering it." +effect/Cause#isInterruptedOnly: + replacement: "Cause.hasInterruptsOnly" + note: "Direct cause-level rename; it is false for Cause.empty and true only when at least one reason exists and every reason is Interrupt." +effect/Cause#isInterruptType: + replacement: "Cause.isInterruptReason" + note: "Apply this guard to an entry of cause.reasons; Cause itself is no longer an Interrupt union variant." +effect/Cause#isNoSuchElementException: + replacement: "Cause.isNoSuchElementError" + note: "Rename the guard along with NoSuchElementError." +effect/Cause#isParallelType: + replacement: "none" + note: "Parallel cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind." +effect/Cause#isRuntimeException: + replacement: "none" + note: "RuntimeException and its brand were removed. Use instanceof Error for generic errors or define a Data.Error/Data.TaggedError class with its own guard when nominal recognition is required." +effect/Cause#isSequentialType: + replacement: "none" + note: "Sequential cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind." +effect/Cause#isTimeoutException: + replacement: "Cause.isTimeoutError" + note: "Rename the guard along with TimeoutError." +effect/Cause#isUnknownException: + replacement: "Cause.isUnknownError" + note: "Rename the guard along with UnknownError." +effect/Cause#keepDefects: + replacement: "Cause.fromReasons(self.reasons.filter(Cause.isDieReason))" + note: "Keep every Die reason, not merely the first defect. Return Option.none when the filtered array is empty and Option.some of the rebuilt cause otherwise; Cause.findDefect alone is not behaviorally equivalent." +effect/Cause#linearize: + replacement: "self.reasons" + note: "No direct replacement: v4 discarded sequential/parallel structure, so there are no parallel branches to linearize. Rewrite the consumer to process the flat Reason array." +effect/Cause#NoSuchElementException: + replacement: "Cause.NoSuchElementError" + note: "Rename the class/type and update the discriminant from NoSuchElementException to NoSuchElementError." +effect/Cause#NoSuchElementExceptionTypeId: + replacement: "Cause.NoSuchElementErrorTypeId" + note: "Rename the brand; v4 exports a literal-string const, so use typeof Cause.NoSuchElementErrorTypeId in type positions." +effect/Cause#originalError: + replacement: "Function.identity" + note: "V3 used this to unwrap span-capture proxies. V4 stores tracing data on Reason.annotations and no longer proxies errors, so the input is already the original value." +effect/Cause#parallel: + replacement: "Cause.combine" + note: "Combine the two flat reason arrays; v4 intentionally no longer records whether composition was parallel or sequential." +effect/Cause#Parallel: + replacement: "none" + note: "Parallel cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind." +effect/Cause#PrettyError: + replacement: "Error" + note: "Cause.prettyErrors now returns Array. The dedicated span field is gone; tracing information is incorporated from Reason annotations into rendered stacks." +effect/Cause#reduce: + replacement: "self.reasons.reduce" + note: "Reduce the flat Reason array directly. The callback now sees only Fail, Die, and Interrupt reasons, never Empty or composition nodes." +effect/Cause#reduceWithContext: + replacement: "self.reasons.reduce" + note: "Capture the context in the reducer closure and reduce the flat Reason array; sequentialCase and parallelCase have no v4 analogue." +effect/Cause#RuntimeException: + replacement: "Error" + note: "The dedicated class was removed and v4 uses global Error for generic defects. Use Data.Error or Data.TaggedError instead when a yieldable typed error is required." +effect/Cause#RuntimeExceptionTypeId: + replacement: "none" + note: "Removed with RuntimeException. Define and guard a custom Data.Error/Data.TaggedError type if nominal branding is required." +effect/Cause#sequential: + replacement: "Cause.combine" + note: "Combine the two flat reason arrays; v4 intentionally no longer records whether composition was parallel or sequential." +effect/Cause#Sequential: + replacement: "none" + note: "Sequential cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind." +effect/Cause#size: + replacement: "self.reasons.length" + note: "The v3 node count becomes the number of flat reasons in v4." +effect/Cause#squashWith: + replacement: "Result.match(Cause.findError(self), { onSuccess: f, onFailure: Cause.squash })" + note: "Apply f only to the first typed Fail error; if no Fail exists, squash the returned Cause. This preserves v3's priority and avoids evaluating f for later Fail reasons." +effect/Cause#stripFailures: + replacement: "Cause.fromReasons(self.reasons.filter((reason) => !Cause.isFailReason(reason)))" + note: "Remove Fail reasons and retain Die plus Interrupt reasons, then rebuild the cause. The v3 prose saying interrupts were removed did not match its implementation." +effect/Cause#stripSomeDefects: + replacement: "Cause.fromReasons + Option" + note: "Filter out each Die reason for which pf(reason.defect) is Some, retain all other reasons, and rebuild. Return None only when a non-empty input loses every reason; preserve Some(Cause.empty) for empty input." +effect/Cause#TimeoutException: + replacement: "Cause.TimeoutError" + note: "Rename the class/type and update the discriminant from TimeoutException to TimeoutError." +effect/Cause#TimeoutExceptionTypeId: + replacement: "Cause.TimeoutErrorTypeId" + note: "Rename the brand; v4 exports a literal-string const, so use typeof Cause.TimeoutErrorTypeId in type positions." +effect/Cause#UnknownException: + replacement: "Cause.UnknownError" + note: "Rename the class/type and discriminant. The original unknown value is now exposed through the standard Error.cause property, not v3's .error field." +effect/Cause#UnknownExceptionTypeId: + replacement: "Cause.UnknownErrorTypeId" + note: "Rename the brand; v4 exports a literal-string const, so use typeof Cause.UnknownErrorTypeId in type positions." +effect/Cause#YieldableError: + replacement: "Cause.YieldableError / Data.Error" + note: "Cause.YieldableError remains as the interface/type, but its public constructor value was removed. Extend Data.Error for an untagged yieldable error or Data.TaggedError for a tagged one." diff --git a/migration/annotations/effect__Channel.yaml b/migration/annotations/effect__Channel.yaml new file mode 100644 index 000000000..a03978a15 --- /dev/null +++ b/migration/annotations/effect__Channel.yaml @@ -0,0 +1,219 @@ +"effect/Channel#acquireReleaseOut": + replacement: "Channel.acquireRelease" + note: "Renamed to acquireRelease. The v4 release action cannot add environment requirements, so capture or provide any services it needs." +"effect/Channel#as": + replacement: "Channel.mapDone" + note: "Replace Channel.as(self, value) with Channel.mapDone(self, () => value)." +"effect/Channel#asVoid": + replacement: "Channel.mapDone" + note: "Replace Channel.asVoid(self) with Channel.mapDone(self, () => void 0)." +"effect/Channel#bufferChunk": + replacement: "none" + note: "The inferred Channel.fromChunk match is not equivalent. Rebuild the buffered upstream-pull transform with Channel.fromTransform and Channel.toTransform." +"effect/Channel#catchAll": + replacement: "Channel.catch" + note: "Renamed to catch for typed-error recovery." +"effect/Channel#catchAllCause": + replacement: "Channel.catchCause" + note: "Renamed to catchCause for full-cause recovery." +"effect/Channel#Channel": + replacement: "Channel.Channel" + note: "Retained, but reorder type parameters from to . Convert Effect values explicitly with Channel.fromEffect or Channel.fromEffectDone." +"effect/Channel#ChannelException": + replacement: "none" + note: "Removed implementation artifact. Channel.pipeToOrFail now handles upstream failures without exposing the v3 exception wrapper." +"effect/Channel#ChannelExceptionTypeId": + replacement: "none" + note: "Removed implementation artifact. Channel.pipeToOrFail now handles upstream failures without exposing the v3 exception wrapper." +"effect/Channel#isChannelException": + replacement: "none" + note: "Removed implementation artifact. Channel.pipeToOrFail now handles upstream failures without exposing the v3 exception wrapper." +"effect/Channel#ChannelTypeId": + replacement: "Channel.TypeId" + note: "Renamed to TypeId; the brand is now the string literal ~effect/Channel. Prefer Channel.isChannel for runtime checks." +"effect/Channel#ChannelUnify": + replacement: "Channel.ChannelUnify" + note: "Retained; update inferred Channel arguments to the v4 generic order." +"effect/Channel#ChannelUnifyIgnore": + replacement: "Channel.ChannelUnifyIgnore" + note: "Retained with a new shape: it no longer extends EffectUnifyIgnore and now contains Effect?: true." +"effect/Channel#collect": + replacement: "Channel.filterMap" + note: "Use Channel.filterMap(self, Filter.fromPredicateOption(pf)) to adapt the v3 Option-returning partial function." +"effect/Channel#concatAll": + replacement: "Channel.flatten" + note: "Use flatten for sequential emitted-channel flattening. V4 preserves the outer done value and discards child done values." +"effect/Channel#concatOut": + replacement: "Channel.flatten" + note: "Use flatten for sequential emitted-channel flattening. V4 preserves the outer done value and discards child done values." +"effect/Channel#concatAllWith": + replacement: "none" + note: "V4 removed child-done accumulation and the outer-done combiner. Use Channel.flatten or Channel.flatMap only when child done values may be discarded; otherwise implement a Pull transform." +"effect/Channel#concatMapWith": + replacement: "none" + note: "V4 removed child-done accumulation and the outer-done combiner. Use Channel.flatten or Channel.flatMap only when child done values may be discarded; otherwise implement a Pull transform." +"effect/Channel#concatMap": + replacement: "Channel.flatMap" + note: "Renamed to flatMap. Sequential flattening is the default; child done values are discarded and the source done value is preserved." +"effect/Channel#concatMapWithCustom": + replacement: "none" + note: "Removed with the channel executor scheduling protocol. Use Channel.flatMap for ordinary sequencing or implement custom scheduling with Channel.fromTransform and Pull." +"effect/Channel#context": + replacement: "Channel.contextWith" + note: "Use Channel.contextWith((context) => Channel.end(context)); the context was the v3 channel done value." +"effect/Channel#contextWithChannel": + replacement: "Channel.contextWith" + note: "Renamed to contextWith." +"effect/Channel#contextWithEffect": + replacement: "Channel.contextWith" + note: "Use Channel.contextWith((context) => Channel.fromEffectDone(f(context))) to preserve the effect result as the done value." +"effect/Channel#doneCollect": + replacement: "none" + note: "No exact channel combinator remains. Drive Channel.toPull, collect output elements, and handle Cause.Done to retain both outputs and the done value." +"effect/Channel#emitCollect": + replacement: "none" + note: "No exact channel combinator remains. Drive Channel.toPull, collect output elements, and handle Cause.Done to retain both outputs and the done value." +"effect/Channel#ensuringWith": + replacement: "Channel.onExit" + note: "Renamed to onExit; the finalizer still receives the channel Exit." +"effect/Channel#foldCauseChannel": + replacement: "none" + note: "V4 has no exact two-sided fold over failure and completion. Use catchCause or catch for failure-only handling, concatWith for success-only handling, or match the Pull in a custom transform." +"effect/Channel#foldChannel": + replacement: "none" + note: "V4 has no exact two-sided fold over failure and completion. Use catchCause or catch for failure-only handling, concatWith for success-only handling, or match the Pull in a custom transform." +"effect/Channel#fromEither": + replacement: "Channel.fromEffectDone" + note: "Either is now Result. Use Channel.fromEffectDone(Effect.fromResult(result)) to preserve success as the done value." +"effect/Channel#fromInput": + replacement: "none" + note: "SingleProducerAsyncInput was removed. Model the producer with Queue and Pull; use Channel.fromPull with Queue.take when a typed done value matters." +"effect/Channel#fromOption": + replacement: "Channel.fromEffectDone" + note: "Use Channel.fromEffectDone(Effect.fromOption(option, Option.none)) to preserve the v3 Option.none error, or omit onNone for the v4 NoSuchElementError default." +"effect/Channel#fromPubSubScoped": + replacement: "Channel.fromPubSubTake" + note: "Change the protocol to PubSub> and use Channel.flattenArray(Channel.fromPubSubTake(pubsub)). The v4 constructor owns the scoped subscription and returns a Channel directly." +"effect/Channel#interruptWhenDeferred": + replacement: "Channel.interruptWhen" + note: "Use Channel.interruptWhen(self, Deferred.await(deferred)); the Deferred-specific overload was removed." +"effect/Channel#mapErrorCause": + replacement: "Channel.catchCause" + note: "Use Channel.catchCause(self, (cause) => Channel.failCause(f(cause)))." +"effect/Channel#mapInputContext": + replacement: "Channel.updateContext" + note: "Renamed to updateContext for transforming the channel requirement Context." +"effect/Channel#mapInputEffect": + replacement: "none" + note: "V4 removed upstream done/error effect mapping. Adapt Cause.Done or failure on the upstream Pull, then pass it through Channel.toTransform(self)." +"effect/Channel#mapInputErrorEffect": + replacement: "none" + note: "V4 removed upstream done/error effect mapping. Adapt Cause.Done or failure on the upstream Pull, then pass it through Channel.toTransform(self)." +"effect/Channel#mapInputIn": + replacement: "Channel.mapInput" + note: "Use Channel.mapInput(self, (value) => Effect.succeed(f(value))); v4 consolidated pure and effectful input mapping." +"effect/Channel#mapInputInEffect": + replacement: "Channel.mapInput" + note: "Renamed to mapInput; the mapper remains effectful." +"effect/Channel#mapOut": + replacement: "Channel.map" + note: "Renamed to map; the v4 mapper also receives the element index." +"effect/Channel#mapOutEffect": + replacement: "Channel.mapEffect" + note: "Renamed to mapEffect for sequential effectful output mapping." +"effect/Channel#mapOutEffectPar": + replacement: "Channel.mapEffect" + note: "Use Channel.mapEffect(self, f, { concurrency: n }); ordered output remains the default." +"effect/Channel#mergeAllUnbounded": + replacement: "Channel.mergeAll" + note: "Use Channel.mergeAll(channels, { concurrency: \"unbounded\" }); child done values are discarded and the outer done value is preserved." +"effect/Channel#mergeAllUnboundedWith": + replacement: "none" + note: "V4 mergeAll removed child-done combining. Use Channel.mergeAll if terminal accumulation can be dropped; otherwise implement a custom Pull merge." +"effect/Channel#mergeAllWith": + replacement: "none" + note: "V4 mergeAll removed child-done combining. Use Channel.mergeAll if terminal accumulation can be dropped; otherwise implement a custom Pull merge." +"effect/Channel#mergeOutWith": + replacement: "none" + note: "V4 mergeAll removed child-done combining. Use Channel.mergeAll if terminal accumulation can be dropped; otherwise implement a custom Pull merge." +"effect/Channel#mergeMap": + replacement: "Channel.flatMap" + note: "Use Channel.flatMap with concurrency and bufferSize for backpressure, or Channel.switchMap with the same options for the v3 sliding strategy." +"effect/Channel#mergeOut": + replacement: "Channel.mergeAll" + note: "Use Channel.mergeAll(self, { concurrency: n }) for bounded backpressured flattening when child done values are irrelevant." +"effect/Channel#mergeWith": + replacement: "Channel.merge" + note: "Use Channel.merge with haltStrategy left, right, both, or either for standard policies. Custom MergeDecision effects require a Pull-level redesign." +"effect/Channel#orDieWith": + replacement: "Channel.catch" + note: "Use Channel.catch(self, (error) => Channel.die(f(error))); v4 Channel.orDie has no mapping callback." +"effect/Channel#orElse": + replacement: "Channel.catch" + note: "Use Channel.catch(self, () => that()) and keep the fallback lazy." +"effect/Channel#provideLayer": + replacement: "Channel.provide" + note: "Both collapse into provide. V4 removes services supplied by the layer and retains remaining requirements; use options.local when a fresh layer instance is needed." +"effect/Channel#provideSomeLayer": + replacement: "Channel.provide" + note: "Both collapse into provide. V4 removes services supplied by the layer and retains remaining requirements; use options.local when a fresh layer instance is needed." +"effect/Channel#read": + replacement: "none" + note: "The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling." +"effect/Channel#readOrFail": + replacement: "none" + note: "The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling." +"effect/Channel#readWith": + replacement: "none" + note: "The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling." +"effect/Channel#readWithCause": + replacement: "none" + note: "The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling." +"effect/Channel#repeated": + replacement: "Channel.forever" + note: "Use forever for infinite repetition. Channel.repeat takes a Schedule and may terminate, so it is not equivalent." +"effect/Channel#run": + replacement: "Channel.runDrain" + note: "Use runDrain to consume all emitted elements and return the channel's done value." +"effect/Channel#runScoped": + replacement: "Channel.toPull" + note: "No direct scoped runner remains. Use toPull in the caller scope and recover Cause.Done; use runDrain when an internally managed scope is acceptable." +"effect/Channel#scopedWith": + replacement: "Channel.unwrap" + note: "Use Channel.unwrap(Effect.map(Effect.scope, (scope) => Channel.fromEffect(f(scope)))) so the effect uses the active channel scope." +"effect/Channel#toPullIn": + replacement: "Channel.toPullScoped" + note: "Renamed to toPullScoped. The returned Pull emits elements directly and represents completion with Cause.Done instead of Either." +"effect/Channel#toSink": + replacement: "Sink.fromChannel" + note: "Constructor moved to Sink. Adapt the channel to non-empty array input, no emitted leftovers, and a Sink.End done value." +"effect/Channel#toStream": + replacement: "Stream.fromChannel" + note: "Constructor moved to Stream. Adapt Chunk outputs to non-empty readonly arrays and map the channel done value to void." +"effect/Channel#unwrapScoped": + replacement: "Channel.unwrap" + note: "Use unwrap; v4 supplies the active channel scope to the effect and removes Scope from the resulting requirement." +"effect/Channel#unwrapScopedWith": + replacement: "Channel.unwrap" + note: "Use Channel.unwrap(Effect.flatMap(Effect.scope, f)) to pass the active channel scope to f." +"effect/Channel#void": + replacement: "Channel.empty" + note: "Renamed to empty: emit nothing and end with void." +"effect/Channel#write": + replacement: "Channel.succeed" + note: "Renamed to succeed, which emits one element in v4. Use Channel.end when migrating v3 succeed, which produced a done value." +"effect/Channel#writeAll": + replacement: "Channel.fromArray" + note: "Replace the variadic writer with Channel.fromArray(outs)." +"effect/Channel#writeChunk": + replacement: "Channel.fromChunk" + note: "Renamed to fromChunk for emitting every Chunk element." +"effect/Channel#zip": + replacement: "Channel.concatWith" + note: "For sequential zip, concatWith the left channel and mapDone the right result to a tuple. Concurrent tuple-done semantics require custom Pull coordination." +"effect/Channel#zipLeft": + replacement: "Channel.concatWith" + note: "For sequential zipLeft, concatWith and mapDone the right result back to the left done value. Concurrent done preservation requires custom Pull coordination." +"effect/Channel#zipRight": + replacement: "Channel.concat" + note: "Use concat for the sequential form; it preserves the right done value. Concurrent mode has no exact replacement." diff --git a/migration/annotations/effect__ChildExecutorDecision.yaml b/migration/annotations/effect__ChildExecutorDecision.yaml new file mode 100644 index 000000000..e8bfb105b --- /dev/null +++ b/migration/annotations/effect__ChildExecutorDecision.yaml @@ -0,0 +1,3 @@ +effect/ChildExecutorDecision: + replacement: none + note: Removed with the v3 channel executor and Channel.concatMapWithCustom. Choose Channel.flatMap, Channel.switchMap, or Channel.mergeAll instead; v4 exposes no child-executor decision ADT. diff --git a/migration/annotations/effect__Chunk.yaml b/migration/annotations/effect__Chunk.yaml new file mode 100644 index 000000000..2771afb04 --- /dev/null +++ b/migration/annotations/effect__Chunk.yaml @@ -0,0 +1,36 @@ +"effect/Chunk#Chunk": + replacement: "Chunk.Chunk" + note: "The model remains Chunk.Chunk; continue using Chunk constructors rather than depending on its exposed representation fields." +"effect/Chunk#getEquivalence": + replacement: "Chunk.makeEquivalence" + note: "Direct rename; pass the element Equivalence unchanged." +"effect/Chunk#modifyOption": + replacement: "Chunk.modify" + note: "The Option suffix was dropped; an out-of-bounds index still returns Option.none." +"effect/Chunk#partitionMap": + replacement: "Chunk.partition" + note: "Pass a Result-returning mapper instead of Either; the output remains [failures, successes]." +"effect/Chunk#removeOption": + replacement: "Chunk.remove" + note: "The closest API now returns the unchanged Chunk out of bounds; use Chunk.get before Chunk.remove to preserve the old Option result." +"effect/Chunk#replaceOption": + replacement: "Chunk.replace" + note: "The Option suffix was dropped; an out-of-bounds index still returns Option.none." +"effect/Chunk#TypeId": + replacement: "none" + note: "The v4 Chunk brand key is private; no public Chunk.TypeId type or value is exported." +"effect/Chunk#unsafeFromArray": + replacement: "Chunk.fromArrayUnsafe" + note: "Direct word-order rename; it still wraps without copying and is unsafe if the source array is mutated." +"effect/Chunk#unsafeFromNonEmptyArray": + replacement: "Chunk.fromNonEmptyArrayUnsafe" + note: "Direct word-order rename; it still wraps without copying and preserves NonEmptyChunk." +"effect/Chunk#unsafeGet": + replacement: "Chunk.getUnsafe" + note: "Direct word-order rename; it still throws for an out-of-bounds index." +"effect/Chunk#unsafeHead": + replacement: "Chunk.headUnsafe" + note: "Direct word-order rename; it still throws on an empty Chunk." +"effect/Chunk#unsafeLast": + replacement: "Chunk.lastUnsafe" + note: "Direct word-order rename; it still throws on an empty Chunk." diff --git a/migration/annotations/effect__Clock.yaml b/migration/annotations/effect__Clock.yaml new file mode 100644 index 000000000..510486a47 --- /dev/null +++ b/migration/annotations/effect__Clock.yaml @@ -0,0 +1,18 @@ +"effect/Clock#CancelToken": + replacement: "none" + note: "The public clock scheduler and cancellation-token protocol were removed. Use Effect.sleep for delays and Effect interruption or Fiber.interrupt for cancellation." +"effect/Clock#Clock": + replacement: "Clock.Clock" + note: "The service interface remains, but unsafeCurrentTimeMillis and unsafeCurrentTimeNanos were renamed to currentTimeMillisUnsafe and currentTimeNanosUnsafe, the public type-id field was removed, and custom implementations must add monotonicTimeNanosUnsafe plus monotonicTimeNanos for elapsed-time measurement." +"effect/Clock#ClockScheduler": + replacement: "none" + note: "The low-level clock scheduler is no longer public. Express scheduling with Effect.sleep and cancel the running fiber through normal Effect interruption." +"effect/Clock#ClockTypeId": + replacement: "none" + note: "The Clock type-id is private in v4. Use the Clock.Clock Context.Reference to access, provide, or identify the clock service." +"effect/Clock#make": + replacement: "Layer.succeed(Clock.Clock, clock)" + note: "The Clock constructor was removed. Implement the v4 Clock interface as a plain service value and provide it through Clock.Clock." +"effect/Clock#Task": + replacement: "none" + note: "The low-level clock task alias was removed with ClockScheduler. Model delayed work as an Effect and run or fork it after Effect.sleep." diff --git a/migration/annotations/effect__Config.yaml b/migration/annotations/effect__Config.yaml new file mode 100644 index 000000000..04c784718 --- /dev/null +++ b/migration/annotations/effect__Config.yaml @@ -0,0 +1,114 @@ +"effect/Config#all": + replacement: "Config.all" + note: "Combine an iterable or record of Config values. A wholly absent product can use Config.withDefault or Config.option, while a partially supplied product fails." +"effect/Config#array": + replacement: "Config.Array(valueSchema, path)" + note: "Array parsing is schema-based in v4; rebuild the element Config as a Schema and pass it with the optional path to Config.Array." +"effect/Config#boolean": + replacement: "Config.Boolean" + note: "Direct constructor rename." +"effect/Config#branded": + replacement: "Config.schema(schema.pipe(Schema.brand(brand)), path)" + note: "Brand validation moved to Schema; define the branded schema and construct the Config with Config.schema." +"effect/Config#chunk": + replacement: "Config.schema(Schema.Chunk(valueSchema), path)" + note: "Collection parsing is schema-based in v4; use Schema.Chunk when a Chunk result is still required." +"effect/Config#Config": + replacement: "Config.Config" + note: "The model remains a yieldable Effect and exposes parse(provider). Compose logical lookup paths with Config.schema(..., path) and Config.nested; parsing no longer accepts a public path prefix." +"effect/Config#Config.IsPlainObject": + replacement: "none" + note: "This private conditional helper is no longer exposed; use Config.Wrap for the public recursive wrapping contract." +"effect/Config#Config.Primitive": + replacement: "Schema.Constraint" + note: "Primitive descriptions and parsers were replaced by Schema codecs consumed through Config.schema." +"effect/Config#Config.Variance": + replacement: "none" + note: "Config now carries its result type directly through Effect and has no public variance interface." +"effect/Config#ConfigTypeId": + replacement: "Config.isConfig" + note: "The Config marker is private in v4; use the public guard for runtime narrowing." +"effect/Config#date": + replacement: "Config.Date" + note: "Direct constructor rename." +"effect/Config#duration": + replacement: "Config.Duration" + note: "Direct constructor rename." +"effect/Config#fail": + replacement: "Config.fail" + note: "The v4 constructor takes a ConfigProvider.SourceError or Schema.SchemaError instead of a message; wrap the failure in the appropriate cause." +"effect/Config#hashMap": + replacement: "Config.schema(Schema.HashMap(Schema.String, valueSchema), path)" + note: "HashMap parsing is schema-based in v4; replace the child Config with its value Schema." +"effect/Config#hashSet": + replacement: "Config.schema(Schema.HashSet(valueSchema), path)" + note: "HashSet parsing is schema-based in v4; replace the child Config with its value Schema." +"effect/Config#integer": + replacement: "Config.Int" + note: "Renamed to the shorter v4 integer constructor using the PascalCase constructor convention." +"effect/Config#literal": + replacement: "Config.Literals(literals, path)" + note: "The v3 curried variadic constructor became Config.Literals with an array and inline path; use Config.Literal for one value." +"effect/Config#LiteralValue": + replacement: "SchemaAST.LiteralValue" + note: "Use the literal value type shared by v4 Schema constructors." +"effect/Config#logLevel": + replacement: "Config.LogLevel" + note: "Direct constructor rename." +"effect/Config#mapAttempt": + replacement: "Config.mapEffect" + note: "Catch exceptions explicitly and return an Effect failure containing Config.ConfigError; mapEffect is Effect-based in v4." +"effect/Config#mapOrFail": + replacement: "Config.mapEffect" + note: "Renamed to match the effectful mapping convention used throughout the library." +"effect/Config#nonEmptyString": + replacement: "Config.NonEmptyString" + note: "Direct constructor rename." +"effect/Config#number": + replacement: "Config.Number" + note: "Direct constructor rename; use Config.Finite when NaN and infinities must be rejected." +"effect/Config#orElseIf": + replacement: "Config.orElse" + note: "The fallback now receives Config.ConfigError; test it in the callback and re-fail with Config.fail(error.cause) when the predicate is false." +"effect/Config#port": + replacement: "Config.Port" + note: "Direct constructor rename." +"effect/Config#primitive": + replacement: "Config.schema(customSchema, path)" + note: "Custom primitive parsing moved to Schema codecs; express decoding and diagnostics in a Schema, then pass it to Config.schema. Its canonical StringTree encoding must expose a concrete shape; opaque encodings such as Schema.Any or Schema.Unknown are not supported." +"effect/Config#redacted": + replacement: "Config.Redacted" + note: "The string/path overload remains; replace the v3 Config argument overload with Config.map(config, Redacted.make)." +"effect/Config#repeat": + replacement: "Config.Array(valueSchema, path)" + note: "Repeated values use the Config.Array constructor, which accepts structural arrays and flat separated input." +"effect/Config#secret": + replacement: "Config.Redacted" + note: "Secret was removed in favor of Redacted; this constructor returns Redacted." +"effect/Config#string": + replacement: "Config.String" + note: "Direct constructor rename." +"effect/Config#succeed": + replacement: "Config.succeed" + note: "Unchanged." +"effect/Config#suspend": + replacement: "Config.schema(Schema.suspend(schemaThunk), path)" + note: "General Config suspension was removed; model recursive parsing with a suspended Schema before constructing the Config." +"effect/Config#sync": + replacement: "Config.succeed(undefined).pipe(Config.map(() => thunk()))" + note: "The dedicated lazy constant constructor was removed; mapping a constant Config preserves evaluation at parse time." +"effect/Config#url": + replacement: "Config.URL" + note: "Direct constructor rename." +"effect/Config#validate": + replacement: "Config.schema(schema.check(check), path)" + note: "Validation moved to Schema checks; attach the predicate and message to the Schema used by Config.schema." +"effect/Config#withDescription": + replacement: "Config.schema(schema.annotate({ description }), path)" + note: "Config descriptions moved to Schema annotations in v4." +"effect/Config#zip": + replacement: "Config.all([self, that])" + note: "Use the tuple overload of Config.all." +"effect/Config#zipWith": + replacement: "Config.all([self, that]).pipe(Config.map(([a, b]) => f(a, b)))" + note: "Combine both configs with Config.all, then map the tuple." diff --git a/migration/annotations/effect__ConfigError.yaml b/migration/annotations/effect__ConfigError.yaml new file mode 100644 index 000000000..51146f3ab --- /dev/null +++ b/migration/annotations/effect__ConfigError.yaml @@ -0,0 +1,66 @@ +"effect/ConfigError#And": + replacement: "SchemaIssue.Composite" + note: "The ConfigError boolean ADT was removed; combined schema failures are represented inside Config.ConfigError.cause as SchemaIssue.Composite." +"effect/ConfigError#ConfigError": + replacement: "Config.ConfigError" + note: "Config errors are now a class in effect/Config wrapping either ConfigProvider.SourceError or Schema.SchemaError." +"effect/ConfigError#ConfigError.Proto": + replacement: "Config.ConfigError" + note: "The public prototype interface was removed; use the Config.ConfigError class." +"effect/ConfigError#ConfigError.Reducer": + replacement: "none" + note: "The ConfigError-specific reducer API was removed; inspect ConfigError.cause and recurse over SchemaError.issue when structured handling is required." +"effect/ConfigError#ConfigErrorReducer": + replacement: "none" + note: "The ConfigError-specific reducer API was removed; inspect ConfigError.cause and recurse over SchemaError.issue when structured handling is required." +"effect/ConfigError#ConfigErrorTypeId": + replacement: "error instanceof Config.ConfigError" + note: "The marker is gone because ConfigError is a class in v4." +"effect/ConfigError#InvalidData": + replacement: "new Config.ConfigError(new Schema.SchemaError(issue))" + note: "Invalid configuration is now expressed as a SchemaIssue wrapped by SchemaError and Config.ConfigError." +"effect/ConfigError#isAnd": + replacement: "error.cause.issue._tag === \"Composite\"" + note: "After narrowing cause with Schema.isSchemaError, inspect the SchemaIssue tag; the old And node no longer exists." +"effect/ConfigError#isConfigError": + replacement: "error instanceof Config.ConfigError" + note: "ConfigError is a class in v4." +"effect/ConfigError#isInvalidData": + replacement: "Schema.isSchemaError(error.cause)" + note: "Parsing and validation failures are SchemaError causes; inspect the contained SchemaIssue for finer classification." +"effect/ConfigError#isMissingData": + replacement: "none" + note: "Do not infer semantic absence from a SchemaIssue. Use Config.withDefault or Config.option; they distinguish absent provider input from successful undefined, invalid input, and partial products." +"effect/ConfigError#isMissingDataOnly": + replacement: "Config.withDefault / Config.option" + note: "The public classifier was removed. These combinators use provider lookup evidence rather than recursively classifying SchemaIssue values." +"effect/ConfigError#isOr": + replacement: "error.cause.issue._tag === \"AnyOf\"" + note: "After narrowing cause with Schema.isSchemaError, inspect the SchemaIssue tag; the old Or node no longer exists." +"effect/ConfigError#isSourceUnavailable": + replacement: "error.cause instanceof ConfigProvider.SourceError" + note: "Provider source failures now use the ConfigProvider.SourceError class." +"effect/ConfigError#isUnsupported": + replacement: "none" + note: "The Unsupported variant was removed; report unsupported custom decoding through a SchemaError or source failures through ConfigProvider.SourceError." +"effect/ConfigError#MissingData": + replacement: "none" + note: "There is no public missing-data error variant. A required absent config ultimately fails with a SchemaError, while Config.withDefault and Config.option handle semantic absence before it enters the public Effect error channel." +"effect/ConfigError#Options": + replacement: "none" + note: "The shared constructor options type was removed; ConfigProvider.SourceError accepts message and optional cause, while Schema issues have issue-specific constructors." +"effect/ConfigError#Or": + replacement: "SchemaIssue.AnyOf" + note: "The ConfigError boolean ADT was removed; alternative schema failures are represented inside Config.ConfigError.cause as SchemaIssue.AnyOf." +"effect/ConfigError#prefixed": + replacement: "SchemaIssue.Pointer" + note: "Represent path context by wrapping the underlying SchemaIssue in a Pointer before constructing SchemaError." +"effect/ConfigError#reduceWithContext": + replacement: "none" + note: "The specialized fold was removed; branch on ConfigError.cause, then recurse over the public SchemaIssue union if a fold is needed." +"effect/ConfigError#SourceUnavailable": + replacement: "new ConfigProvider.SourceError({ message, cause })" + note: "Source failures moved to effect/ConfigProvider and are wrapped by Config.ConfigError when a Config is parsed." +"effect/ConfigError#Unsupported": + replacement: "none" + note: "The variant was removed; use a SchemaError for unsupported input or ConfigProvider.SourceError for source capability failures." diff --git a/migration/annotations/effect__ConfigProvider.yaml b/migration/annotations/effect__ConfigProvider.yaml new file mode 100644 index 000000000..14bfddb15 --- /dev/null +++ b/migration/annotations/effect__ConfigProvider.yaml @@ -0,0 +1,69 @@ +"effect/ConfigProvider#ConfigProvider": + replacement: "ConfigProvider.ConfigProvider" + note: "The model remains but now exposes `load(path)`, returning `Effect`, and `mapInput(f)` for provider-owned path transformation. `undefined` means the path is missing; a `Node` means it exists." +"effect/ConfigProvider#ConfigProvider.Flat": + replacement: "ConfigProvider.ConfigProvider" + note: "Flat providers were removed; implement the unified path-based provider with ConfigProvider.make." +"effect/ConfigProvider#ConfigProvider.FromEnvConfig": + replacement: "Parameters[0]" + note: "Options are inline in v4 and contain env plus preserveEmptyStrings; custom path delimiters moved to provider path transforms, while separated sequences and records use Config.Array and Config.Record." +"effect/ConfigProvider#ConfigProvider.FromMapConfig": + replacement: "none" + note: "fromMap and its delimiter options were removed; expand delimited keys into a nested value and use ConfigProvider.fromUnknown." +"effect/ConfigProvider#ConfigProvider.KeyComponent": + replacement: "ConfigProvider.Path[number]" + note: "Tagged key components became plain string or number path segments." +"effect/ConfigProvider#ConfigProvider.KeyIndex": + replacement: "number" + note: "Tagged KeyIndex values became numeric ConfigProvider.Path segments." +"effect/ConfigProvider#ConfigProvider.KeyName": + replacement: "string" + note: "Tagged KeyName values became string ConfigProvider.Path segments." +"effect/ConfigProvider#ConfigProvider.Proto": + replacement: "ConfigProvider.ConfigProvider" + note: "The public marker prototype was removed; use the provider interface itself." +"effect/ConfigProvider#ConfigProviderTypeId": + replacement: "ConfigProvider.ConfigProvider" + note: "The runtime marker is private in v4; providers are created by public constructors and consumed structurally." +"effect/ConfigProvider#FlatConfigProviderTypeId": + replacement: "none" + note: "The flat-provider abstraction and marker were removed." +"effect/ConfigProvider#fromEnv": + replacement: "ConfigProvider.fromEnv" + note: "The constructor remains; pass env and preserveEmptyStrings options. Paths use underscore semantics, while sequence separators belong on Config schemas." +"effect/ConfigProvider#fromFlat": + replacement: "ConfigProvider.make" + note: "Flat providers were unified with ConfigProvider; return a `Value`, `Record`, or `Array` node for a found path, or `undefined` when missing." +"effect/ConfigProvider#fromJson": + replacement: "ConfigProvider.fromUnknown" + note: "Renamed to reflect support for any in-memory JavaScript value." +"effect/ConfigProvider#fromMap": + replacement: "ConfigProvider.fromUnknown" + note: "Expand the map's delimited keys into a nested object first; v4 removed fromMap and its pathDelim/seqDelim options." +"effect/ConfigProvider#kebabCase": + replacement: "ConfigProvider.mapInput((path) => path.map((part) => typeof part === \"string\" ? String.kebabCase(part) : part))" + note: "Named recasing helpers were removed except constantCase; transform string path segments explicitly." +"effect/ConfigProvider#lowerCase": + replacement: "ConfigProvider.mapInput((path) => path.map((part) => typeof part === \"string\" ? part.toLowerCase() : part))" + note: "Transform string path segments explicitly with mapInput." +"effect/ConfigProvider#make": + replacement: "ConfigProvider.make" + note: "The constructor now takes a path lookup returning `Effect`, rather than a full Config loader and flattened provider. Return `undefined` for a missing path and a `Node` for a found path." +"effect/ConfigProvider#makeFlat": + replacement: "ConfigProvider.make" + note: "The flat-provider constructor was removed; return a `Value`, `Record`, or `Array` node for a found path, or `undefined` when missing." +"effect/ConfigProvider#mapInputPath": + replacement: "ConfigProvider.mapInput" + note: "Renamed and generalized: the callback receives and returns the complete Path, including numeric array indexes." +"effect/ConfigProvider#snakeCase": + replacement: "ConfigProvider.mapInput((path) => path.map((part) => typeof part === \"string\" ? String.snakeCase(part) : part))" + note: "Named recasing helpers were removed except constantCase; transform string path segments explicitly." +"effect/ConfigProvider#unnested": + replacement: "ConfigProvider.mapInput((path) => path[0] === name ? path.slice(1) : path)" + note: "The named helper was removed; strip the matching leading segment explicitly. Add custom handling if the v3 mismatch error was significant." +"effect/ConfigProvider#upperCase": + replacement: "ConfigProvider.mapInput((path) => path.map((part) => typeof part === \"string\" ? part.toUpperCase() : part))" + note: "Transform string path segments explicitly with mapInput." +"effect/ConfigProvider#within": + replacement: "ConfigProvider.orElse + ConfigProvider.mapInput" + note: "The scoped transform helper was removed; build a provider that transforms paths below the prefix and falls back to the original provider elsewhere." diff --git a/migration/annotations/effect__ConfigProviderPathPatch.yaml b/migration/annotations/effect__ConfigProviderPathPatch.yaml new file mode 100644 index 000000000..049419589 --- /dev/null +++ b/migration/annotations/effect__ConfigProviderPathPatch.yaml @@ -0,0 +1,30 @@ +"effect/ConfigProviderPathPatch#AndThen": + replacement: "ConfigProvider.mapInput" + note: "PathPatch is no longer public; compose path transformations as ordinary functions passed to mapInput." +"effect/ConfigProviderPathPatch#empty": + replacement: "ConfigProvider.ConfigProvider" + note: "No identity patch value is needed; leave the provider untransformed." +"effect/ConfigProviderPathPatch#Empty": + replacement: "none" + note: "The PathPatch ADT was removed; an unchanged provider represents the identity transformation." +"effect/ConfigProviderPathPatch#mapName": + replacement: "ConfigProvider.mapInput" + note: "Map the string segments of the full ConfigProvider.Path explicitly." +"effect/ConfigProviderPathPatch#MapName": + replacement: "none" + note: "The PathPatch ADT was removed; use a path transformation function with ConfigProvider.mapInput." +"effect/ConfigProviderPathPatch#nested": + replacement: "ConfigProvider.nested" + note: "Apply nesting directly to the provider instead of constructing a patch." +"effect/ConfigProviderPathPatch#Nested": + replacement: "none" + note: "The PathPatch ADT was removed; use ConfigProvider.nested on the provider." +"effect/ConfigProviderPathPatch#PathPatch": + replacement: "(path: ConfigProvider.Path) => ConfigProvider.Path" + note: "Path patches are ordinary full-path transformations in v4 and are installed with ConfigProvider.mapInput." +"effect/ConfigProviderPathPatch#unnested": + replacement: "ConfigProvider.mapInput" + note: "Strip the expected leading path segment in a mapInput callback; v4 has no named unnested helper." +"effect/ConfigProviderPathPatch#Unnested": + replacement: "none" + note: "The PathPatch ADT was removed; express prefix removal as a ConfigProvider.mapInput function." diff --git a/migration/annotations/effect__Console.yaml b/migration/annotations/effect__Console.yaml new file mode 100644 index 000000000..77030318d --- /dev/null +++ b/migration/annotations/effect__Console.yaml @@ -0,0 +1,21 @@ +effect/Console#Console: + replacement: "Console.Console" + note: "Name retained, but v4 is a Context.Reference whose service methods are synchronous. Rewrite custom implementations from effectful methods plus .unsafe to direct console methods; module accessors such as Console.log still return Effect values." +effect/Console#setConsole: + replacement: "Layer.succeed(Console.Console, console)" + note: "Provide the v4 console reference as a layer." +effect/Console#TypeId: + replacement: "none" + note: "The public console brand was removed; v4 Console.Console is structural." +effect/Console#UnsafeConsole: + replacement: "Console.Console" + note: "The v4 service interface is the old unsafe/direct interface; .unsafe no longer exists." +effect/Console#withConsole: + replacement: "Effect.provideService(effect, Console.Console, console)" + note: "Console overrides now use the reference/service provider pattern." +effect/Console#withGroup: + replacement: "Console.withGroup" + note: "The API and data-first/data-last behavior remain." +effect/Console#withTime: + replacement: "Console.withTime" + note: "The API and data-first/data-last behavior remain." diff --git a/migration/annotations/effect__Context.yaml b/migration/annotations/effect__Context.yaml new file mode 100644 index 000000000..af34f9dfe --- /dev/null +++ b/migration/annotations/effect__Context.yaml @@ -0,0 +1,54 @@ +"effect/Context#Context": + replacement: "Context.Context" + note: "The type remains; unsafeMap is now mapUnsafe and v4 also exposes mutable." +"effect/Context#GenericTag": + replacement: "Context.Service(id)" + note: "Use the function-style Context.Service constructor." +"effect/Context#isTag": + replacement: "Context.isKey" + note: "The service-key guard was renamed." +"effect/Context#ReadonlyTag": + replacement: "Context.Key" + note: "Use the renamed service-key interface." +"effect/Context#Reference": + replacement: "Context.Reference" + note: "Use Context.Reference(id, { defaultValue }); the identifier type parameter was removed." +"effect/Context#ReferenceClass": + replacement: "Context.Reference(id, { defaultValue })" + note: "Replace reference subclasses with a constant created by Context.Reference." +"effect/Context#ReferenceTypeId": + replacement: "none" + note: "The marker is private in v4; use Context.isReference for runtime discrimination." +"effect/Context#Tag": + replacement: "Context.Service" + note: "Use Context.Service(id), or Context.Service()(id) for class syntax." +"effect/Context#Tag.Service": + replacement: "Context.Service.Shape" + note: "The namespace type helper was renamed with Tag." +"effect/Context#TagClass": + replacement: "Context.ServiceClass" + note: "Use the renamed class-style service-key type." +"effect/Context#TagClassShape": + replacement: "Context.ServiceClass.Shape" + note: "Use the renamed namespace type helper." +"effect/Context#TagTypeId": + replacement: "Context.ServiceTypeId" + note: "The public type identifier was renamed with Tag." +"effect/Context#TagUnify": + replacement: "none" + note: "The Context-specific unification hook was removed; Context.Key already extends Effect." +"effect/Context#TagUnifyIgnore": + replacement: "none" + note: "The Context-specific Unify-ignore artifact was removed." +"effect/Context#TypeId": + replacement: "none" + note: "The Context marker is private in v4; use Context.isContext for runtime checks." +"effect/Context#unsafeGet": + replacement: "Context.getUnsafe" + note: "The unsafe getter was renamed." +"effect/Context#unsafeMake": + replacement: "Context.makeUnsafe" + note: "The unsafe constructor was renamed and accepts a ReadonlyMap." +"effect/Context#ValidTagsById": + replacement: "(key: Context.Key)" + note: "The alias was removed; express the Context.Key constraint directly." diff --git a/migration/annotations/effect__Cron.yaml b/migration/annotations/effect__Cron.yaml new file mode 100644 index 000000000..94c388d36 --- /dev/null +++ b/migration/annotations/effect__Cron.yaml @@ -0,0 +1,21 @@ +"effect/Cron#Cron": + replacement: "Cron.Cron" + note: "The model remains; update for the v4 representation and private type id." +"effect/Cron#isParseError": + replacement: "Cron.isCronParseError" + note: "The parse-error guard was renamed with the error type." +"effect/Cron#ParseError": + replacement: "Cron.CronParseError" + note: "The parse error was renamed and Cron.parse now returns Result.Result." +"effect/Cron#ParseErrorTypeId": + replacement: "none" + note: "The cron parse-error type id is private in v4. Use Cron.isCronParseError to narrow unknown failures." +"effect/Cron#sequenceReverse": + replacement: "Cron.prev" + note: "The reverse iterator was removed. Build an iterator that repeatedly calls Cron.prev, feeding each returned Date into the next call." +"effect/Cron#TypeId": + replacement: "none" + note: "The Cron type id is private in v4. Use Cron.isCron to identify cron values." +"effect/Cron#unsafeParse": + replacement: "Cron.parseUnsafe" + note: "The throwing parser was renamed; it also accepts an optional time zone." diff --git a/migration/annotations/effect__Data.yaml b/migration/annotations/effect__Data.yaml new file mode 100644 index 000000000..e6f0d5b62 --- /dev/null +++ b/migration/annotations/effect__Data.yaml @@ -0,0 +1,33 @@ +"effect/Data#array": + replacement: "none" + note: "Use a normal copied array such as [...values]; v4 compares plain arrays structurally." +"effect/Data#case": + replacement: "none" + note: "Use an ordinary typed object or identity constructor; plain objects are structurally equal in v4." +"effect/Data#Case": + replacement: "Data.TaggedEnum.ConstructorFrom" + note: "The Case namespace was removed; its constructor helper moved under TaggedEnum." +"effect/Data#Case.Constructor": + replacement: "Data.TaggedEnum.ConstructorFrom" + note: "Use the v4 tagged-enum constructor-function type." +"effect/Data#struct": + replacement: "none" + note: "Use an ordinary object or {...value}; plain objects are structurally equal in v4." +"effect/Data#Structural": + replacement: "Data.Class" + note: "Extend Data.Class instead of the removed Structural constructor alias." +"effect/Data#TaggedEnum": + replacement: "Data.TaggedEnum" + note: "Still exported with the same record-to-discriminated-union role." +"effect/Data#TaggedEnum.GenericMatchers": + replacement: "Data.TaggedEnum.GenericMatchers" + note: "Still exported with $is and $match helpers." +"effect/Data#tuple": + replacement: "none" + note: "Use a normal tuple literal; plain tuples are structurally equal in v4." +"effect/Data#unsafeArray": + replacement: "none" + note: "Use the array directly; v4 no longer needs prototype mutation for structural equality." +"effect/Data#unsafeStruct": + replacement: "none" + note: "Use the object directly; v4 no longer needs prototype mutation for structural equality." diff --git a/migration/annotations/effect__DateTime.yaml b/migration/annotations/effect__DateTime.yaml new file mode 100644 index 000000000..f6c65c930 --- /dev/null +++ b/migration/annotations/effect__DateTime.yaml @@ -0,0 +1,84 @@ +"effect/DateTime#DateTime": + replacement: "DateTime.DateTime" + note: "The Utc | Zoned model remains; epochMillis fields are now epochMilliseconds and unit/part names use millisecond terminology." +"effect/DateTime#DateTime.Input": + replacement: "DateTime.DateTime.Input" + note: "The input type remains and additionally accepts Instant and InstantWithZone objects." +"effect/DateTime#DateTime.Parts": + replacement: "DateTime.DateTime.Parts" + note: "Rename millis, seconds, minutes, and hours fields to millisecond, second, minute, and hour." +"effect/DateTime#DateTime.PartsForMath": + replacement: "DateTime.DateTime.PartsForMath" + note: "Rename the millis field to milliseconds; the other plural arithmetic fields remain." +"effect/DateTime#DateTime.PartsWithWeekday": + replacement: "DateTime.DateTime.PartsWithWeekday" + note: "Rename millis, seconds, minutes, and hours fields to millisecond, second, minute, and hour." +"effect/DateTime#DateTime.Proto": + replacement: "DateTime.DateTime.Proto" + note: "The protocol remains, but its marker uses the private v4 TypeId value." +"effect/DateTime#DateTime.UnitPlural": + replacement: "DateTime.DateTime.UnitPlural" + note: "Use milliseconds instead of millis; the other plural unit strings remain." +"effect/DateTime#DateTime.UnitSingular": + replacement: "DateTime.DateTime.UnitSingular" + note: "Use millisecond instead of milli; the other singular unit strings remain." +"effect/DateTime#distanceDuration": + replacement: "Duration.millis(Math.abs(DateTime.distance(self, other)))" + note: "DateTime.distance returns signed milliseconds in v4; take the absolute value and construct a Duration to preserve v3 behavior." +"effect/DateTime#distanceDurationEither": + replacement: "DateTime.distance + Result" + note: "Compute the signed millisecond distance, wrap its absolute Duration as Result.succeed when positive and Result.fail when non-positive; v4 uses Result instead of Either." +"effect/DateTime#greaterThan": + replacement: "DateTime.isGreaterThan" + note: "The comparison was renamed with the is prefix." +"effect/DateTime#greaterThanOrEqualTo": + replacement: "DateTime.isGreaterThanOrEqualTo" + note: "The comparison was renamed with the is prefix." +"effect/DateTime#lessThan": + replacement: "DateTime.isLessThan" + note: "The comparison was renamed with the is prefix." +"effect/DateTime#lessThanOrEqualTo": + replacement: "DateTime.isLessThanOrEqualTo" + note: "The comparison was renamed with the is prefix." +"effect/DateTime#TimeZone": + replacement: "DateTime.TimeZone" + note: "The Offset | Named model remains; its public type-id marker type was removed." +"effect/DateTime#TimeZone.Proto": + replacement: "DateTime.TimeZone.Proto" + note: "The protocol remains, but its marker uses the private v4 TimeZoneTypeId value." +"effect/DateTime#TimeZoneTypeId": + replacement: "none" + note: "The time-zone type id is private in v4. Use DateTime.isTimeZone, isTimeZoneOffset, or isTimeZoneNamed." +"effect/DateTime#TypeId": + replacement: "none" + note: "The DateTime type id is private in v4. Use DateTime.isDateTime, isUtc, or isZoned." +"effect/DateTime#unsafeFromDate": + replacement: "DateTime.fromDateUnsafe" + note: "The unsafe suffix moved to the end of the constructor name." +"effect/DateTime#unsafeIsFuture": + replacement: "DateTime.isFutureUnsafe" + note: "The unsafe suffix moved to the end of the predicate name." +"effect/DateTime#unsafeIsPast": + replacement: "DateTime.isPastUnsafe" + note: "The unsafe suffix moved to the end of the predicate name." +"effect/DateTime#unsafeMake": + replacement: "DateTime.makeUnsafe" + note: "The unsafe suffix moved to the end of the constructor name." +"effect/DateTime#unsafeMakeZoned": + replacement: "DateTime.makeZonedUnsafe" + note: "The unsafe suffix moved to the end of the constructor name." +"effect/DateTime#unsafeNow": + replacement: "DateTime.nowUnsafe" + note: "The unsafe suffix moved to the end of the accessor name." +"effect/DateTime#unsafeSetZoneNamed": + replacement: "DateTime.setZoneNamedUnsafe" + note: "The unsafe suffix moved to the end of the zone setter name." +"effect/DateTime#Utc": + replacement: "DateTime.Utc" + note: "The model remains; rename epochMillis to epochMilliseconds." +"effect/DateTime#Zoned": + replacement: "DateTime.Zoned" + note: "The model remains; rename epochMillis and adjustedEpochMillis to epochMilliseconds and adjustedEpochMilliseconds." +"effect/DateTime#zoneUnsafeMakeNamed": + replacement: "DateTime.zoneMakeNamedUnsafe" + note: "The unsafe suffix moved to the end of the named-zone constructor." diff --git a/migration/annotations/effect__DefaultServices.yaml b/migration/annotations/effect__DefaultServices.yaml new file mode 100644 index 000000000..f916ca2cc --- /dev/null +++ b/migration/annotations/effect__DefaultServices.yaml @@ -0,0 +1,9 @@ +"effect/DefaultServices#currentServices": + replacement: "Effect.context() and Context.get(context, reference)" + note: "The aggregate FiberRef was removed; access and override default Context.Reference services individually." +"effect/DefaultServices#DefaultServices": + replacement: "none" + note: "The aggregate type and module were removed; Clock, Console, Random, ConfigProvider, and Tracer are independent defaulted references." +"effect/DefaultServices#liveServices": + replacement: "Context.empty() with individual Context.Reference defaults" + note: "There is no live-services bundle; each default service reference supplies its own live default." diff --git a/migration/annotations/effect__Deferred.yaml b/migration/annotations/effect__Deferred.yaml new file mode 100644 index 000000000..844e3029b --- /dev/null +++ b/migration/annotations/effect__Deferred.yaml @@ -0,0 +1,30 @@ +effect/Deferred#await: + replacement: "Deferred.await" + note: "The function remains; call it explicitly because Deferred is no longer an Effect subtype in v4." +effect/Deferred#Deferred: + replacement: "Deferred.Deferred" + note: "The model remains but is now Pipeable rather than an Effect subtype; replace yielding the Deferred itself with Deferred.await." +effect/Deferred#Deferred.Variance: + replacement: "Deferred.Deferred.Variance" + note: "The marker remains under Deferred.Deferred, but its brand uses an internal type id; ordinary code should use Deferred.Deferred directly." +effect/Deferred#DeferredTypeId: + replacement: "none" + note: "The Deferred type id is internal in v4; do not inspect or construct the brand directly." +effect/Deferred#DeferredUnify: + replacement: "none" + note: "Deferred is no longer an Effect subtype, so its Effect unification helper was removed; call Deferred.await explicitly." +effect/Deferred#DeferredUnifyIgnore: + replacement: "none" + note: "Deferred is no longer an Effect subtype, so its Effect unification ignore marker was removed." +effect/Deferred#makeAs: + replacement: "Deferred.makeUnsafe" + note: "Use the synchronous v4 constructor; it no longer accepts or records a FiberId." +effect/Deferred#poll: + replacement: "Deferred.poll" + note: "The function remains and returns an Option containing the stored completion Effect." +effect/Deferred#unsafeDone: + replacement: "Deferred.doneUnsafe" + note: "The unsafe suffix moved to the end; the v4 function returns whether this call completed the Deferred." +effect/Deferred#unsafeMake: + replacement: "Deferred.makeUnsafe" + note: "The unsafe suffix moved to the end, and the v4 constructor takes no FiberId argument." diff --git a/migration/annotations/effect__Differ.yaml b/migration/annotations/effect__Differ.yaml new file mode 100644 index 000000000..c780a8413 --- /dev/null +++ b/migration/annotations/effect__Differ.yaml @@ -0,0 +1,99 @@ +effect/Differ#chunk: + replacement: "Schema.toDifferJsonPatch" + note: "Derive a JSON Patch differ from a Schema for the whole Chunk; v4 no longer exposes collection-specific patch constructors." +effect/Differ#combine: + replacement: "differ.combine" + note: "Call the combine method on the Differ value directly; the standalone helper was removed." +effect/Differ#diff: + replacement: "differ.diff" + note: "Call the diff method on the Differ value directly; the standalone helper was removed." +effect/Differ#Differ: + replacement: "Differ.Differ" + note: "The interface remains, but is now an unbranded structural interface and patch takes arguments as patch(oldValue, patch)." +effect/Differ#Differ.Chunk: + replacement: "JsonPatch.JsonPatch" + note: "The Chunk patch namespace was removed; Schema.toDifferJsonPatch uses the common RFC 6902 patch representation." +effect/Differ#Differ.Chunk.Patch: + replacement: "JsonPatch.JsonPatch" + note: "Use the patch type returned by Schema.toDifferJsonPatch instead of the removed Chunk-specific patch type." +effect/Differ#Differ.Chunk.TypeId: + replacement: "none" + note: "Chunk-specific patches and their public brand were removed; do not inspect a patch type id." +effect/Differ#Differ.Context: + replacement: "Context.Context" + note: "The Context patch namespace was removed; construct and merge Context values explicitly rather than diffing environments." +effect/Differ#Differ.Context.Patch: + replacement: "none" + note: "Context patches were removed; use Context.add, Context.merge, and Context.omit to build the desired Context directly." +effect/Differ#Differ.Context.TypeId: + replacement: "none" + note: "Context patches and their public brand were removed." +effect/Differ#Differ.HashMap: + replacement: "JsonPatch.JsonPatch" + note: "The HashMap patch namespace was removed; derive a JSON Patch differ from a Schema for the complete value." +effect/Differ#Differ.HashMap.Patch: + replacement: "JsonPatch.JsonPatch" + note: "Use the patch type returned by Schema.toDifferJsonPatch instead of the removed HashMap-specific patch type." +effect/Differ#Differ.HashMap.TypeId: + replacement: "none" + note: "HashMap-specific patches and their public brand were removed; do not inspect a patch type id." +effect/Differ#Differ.HashSet.Patch: + replacement: "JsonPatch.JsonPatch" + note: "Use the patch type returned by Schema.toDifferJsonPatch instead of the removed HashSet-specific patch type." +effect/Differ#Differ.HashSet.TypeId: + replacement: "none" + note: "HashSet-specific patches and their public brand were removed; do not inspect a patch type id." +effect/Differ#Differ.Or: + replacement: "JsonPatch.JsonPatch" + note: "The Either patch namespace was removed; derive one JSON Patch differ from the Schema for the union value." +effect/Differ#Differ.Or.Patch: + replacement: "JsonPatch.JsonPatch" + note: "Use the patch type returned by Schema.toDifferJsonPatch instead of the removed Either-specific patch type." +effect/Differ#Differ.Or.TypeId: + replacement: "none" + note: "Either-specific patches and their public brand were removed; do not inspect a patch type id." +effect/Differ#Differ.ReadonlyArray: + replacement: "JsonPatch.JsonPatch" + note: "The ReadonlyArray patch namespace was removed; Schema.toDifferJsonPatch uses the common RFC 6902 patch representation." +effect/Differ#Differ.ReadonlyArray.Patch: + replacement: "JsonPatch.JsonPatch" + note: "Use the patch type returned by Schema.toDifferJsonPatch instead of the removed ReadonlyArray-specific patch type." +effect/Differ#Differ.ReadonlyArray.TypeId: + replacement: "none" + note: "ReadonlyArray-specific patches and their public brand were removed; do not inspect a patch type id." +effect/Differ#empty: + replacement: "differ.empty" + note: "Read the empty property from the Differ value directly; the standalone accessor was removed." +effect/Differ#environment: + replacement: "none" + note: "The Context differ was removed; construct the target Context explicitly with Context.add, Context.merge, and Context.omit." +effect/Differ#hashMap: + replacement: "Schema.toDifferJsonPatch" + note: "Derive a JSON Patch differ from a Schema for the whole map; v4 no longer exposes collection-specific patch constructors." +effect/Differ#hashSet: + replacement: "Schema.toDifferJsonPatch" + note: "Derive a JSON Patch differ from a Schema for the whole set; v4 no longer exposes collection-specific patch constructors." +effect/Differ#make: + replacement: "object literal satisfying Differ.Differ" + note: "Differ is structural in v4; provide empty, diff, combine, and patch methods directly, with patch(oldValue, patch) argument order." +effect/Differ#orElseEither: + replacement: "Schema.toDifferJsonPatch" + note: "Derive one differ from the Schema for the Either value; the compositional Either-specific differ and patch type were removed." +effect/Differ#patch: + replacement: "differ.patch" + note: "Call the method directly and reverse the v3 method order: differ.patch(oldValue, patch)." +effect/Differ#readonlyArray: + replacement: "Schema.toDifferJsonPatch" + note: "Derive a JSON Patch differ from a Schema for the whole array; v4 no longer exposes collection-specific patch constructors." +effect/Differ#transform: + replacement: "object literal satisfying Differ.Differ" + note: "There is no transform combinator; define a structural Differ that maps values before delegating to the original differ." +effect/Differ#TypeId: + replacement: "none" + note: "Differ is an unbranded structural interface in v4; do not inspect or implement a public type id." +effect/Differ#update: + replacement: "object literal satisfying Differ.Differ" + note: "The update constructor was removed; define empty, diff, combine, and patch directly for function patches, or use Schema.toDifferJsonPatch." +effect/Differ#updateWith: + replacement: "object literal satisfying Differ.Differ" + note: "The updateWith constructor was removed; encode the desired merge rule in a structural Differ implementation." diff --git a/migration/annotations/effect__Duration.yaml b/migration/annotations/effect__Duration.yaml new file mode 100644 index 000000000..6f9b67b73 --- /dev/null +++ b/migration/annotations/effect__Duration.yaml @@ -0,0 +1,48 @@ +"effect/Duration#decode": + replacement: "Duration.fromInputUnsafe" + note: "The throwing DurationInput decoder was renamed." +"effect/Duration#decodeUnknown": + replacement: "Duration.fromInput" + note: "The safe decoder was renamed and accepts Duration.Input, returning Option." +"effect/Duration#Duration": + replacement: "Duration.Duration" + note: "The model remains and now also supports negative infinity; its type-id value is private." +"effect/Duration#DurationInput": + replacement: "Duration.Input" + note: "The input type was renamed and expanded with negative values and Temporal.Duration-like objects." +"effect/Duration#DurationValue": + replacement: "Duration.DurationValue" + note: "The tagged value remains and adds NegativeInfinity; its object fields are no longer readonly." +"effect/Duration#formatIso": + replacement: "none" + note: "ISO 8601 duration formatting was removed from the v4 Duration module. The v4 source and migration guides expose no direct replacement; retain a local formatter when this wire format is required." +"effect/Duration#fromIso": + replacement: "none" + note: "ISO 8601 duration parsing was removed from the v4 Duration module. The v4 source and migration guides expose no direct replacement; use a dedicated ISO parser and pass the resulting parts to Duration.fromInput." +"effect/Duration#greaterThan": + replacement: "Duration.isGreaterThan" + note: "The comparison was renamed with the is prefix." +"effect/Duration#greaterThanOrEqualTo": + replacement: "Duration.isGreaterThanOrEqualTo" + note: "The comparison was renamed with the is prefix." +"effect/Duration#lessThan": + replacement: "Duration.isLessThan" + note: "The comparison was renamed with the is prefix." +"effect/Duration#lessThanOrEqualTo": + replacement: "Duration.isLessThanOrEqualTo" + note: "The comparison was renamed with the is prefix." +"effect/Duration#matchWith": + replacement: "Duration.matchPair" + note: "The two-duration matcher was renamed." +"effect/Duration#TypeId": + replacement: "none" + note: "The Duration type id is private in v4. Use Duration.isDuration to narrow unknown values." +"effect/Duration#unsafeDivide": + replacement: "Duration.divideUnsafe" + note: "The unsafe prefix moved to the end of the division function name." +"effect/Duration#unsafeFormatIso": + replacement: "none" + note: "ISO 8601 duration formatting was removed from v4. The v4 Duration exports and migration guides contain no direct unsafe formatter; retain a local formatter if required." +"effect/Duration#unsafeToNanos": + replacement: "Duration.toNanosUnsafe" + note: "The unsafe prefix moved to the end of the nanosecond conversion name." diff --git a/migration/annotations/effect__Effect.yaml b/migration/annotations/effect__Effect.yaml new file mode 100644 index 000000000..a70dcdb40 --- /dev/null +++ b/migration/annotations/effect__Effect.yaml @@ -0,0 +1,663 @@ +effect/Effect#acquireReleaseInterruptible: + replacement: "Effect.acquireRelease" + note: "Pass `{ interruptible: true }` in the options object. Adapt arguments and imports to the v4 API." +effect/Effect#Adapter: + replacement: "none" + note: "The generator adapter type was removed; yield Effect values directly inside `Effect.gen`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#All: + replacement: "Effect.All" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#All.ExtractMode: + replacement: "Effect.All.Return" + note: "The `either` extraction helper was removed; use `mode: \"result\"` and the v4 return helper. Adapt arguments and imports to the v4 API." +effect/Effect#allowInterrupt: + replacement: "Effect.yieldNow" + note: "Yield to the scheduler to create an interruptible checkpoint. Adapt arguments and imports to the v4 API." +effect/Effect#allSuccesses: + replacement: "Effect.all" + note: "Run with `{ mode: \"result\" }`, then retain `Result.Success` values. Adapt arguments and imports to the v4 API." +effect/Effect#allWith: + replacement: "Effect.all" + note: "Wrap `Effect.all(values, options)` in a lambda when a data-last combinator is needed. Adapt arguments and imports to the v4 API." +effect/Effect#annotateLogs: + replacement: "Effect.annotateLogs" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#ap: + replacement: "Effect.zipWith" + note: "Zip the function effect and value effect, then apply the function in the combiner. Adapt arguments and imports to the v4 API." +effect/Effect#asSomeError: + replacement: "Effect.mapError" + note: "Map errors with `Option.some`. Adapt arguments and imports to the v4 API." +effect/Effect#async: + replacement: "Effect.callback" + note: "Use the renamed callback constructor. Adapt arguments and imports to the v4 API." +effect/Effect#asyncEffect: + replacement: "Effect.callback" + note: "The callback registration may return an Effect cleanup action in v4. Adapt arguments and imports to the v4 API." +effect/Effect#bindAll: + replacement: "Effect.bind + Effect.all" + note: "Bind the result of `Effect.all` explicitly in the do-notation pipeline. Adapt arguments and imports to the v4 API." +effect/Effect#blocked: + replacement: "none" + note: "The request-runtime blocked constructor is internal; express work with `Effect.request` and a `RequestResolver`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#Blocked: + replacement: "none" + note: "The request-runtime blocked model is internal; use public `Request` and `RequestResolver` APIs. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#cachedFunction: + replacement: "none" + note: "The function memoizer was removed; use `Cache` for keyed effectful caching. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#cacheRequestResult: + replacement: "none" + note: "Direct request-cache mutation was removed; configure request resolution through `RequestResolver`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#catch: + replacement: "Effect.catch" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#catchAll: + replacement: "Effect.catch" + note: "Use the shortened v4 error-handler name. Adapt arguments and imports to the v4 API." +effect/Effect#catchAllCause: + replacement: "Effect.catchCause" + note: "Use the shortened v4 cause-handler name. Adapt arguments and imports to the v4 API." +effect/Effect#catchAllDefect: + replacement: "Effect.catchDefect" + note: "Use the shortened v4 defect-handler name. Adapt arguments and imports to the v4 API." +effect/Effect#catchSome: + replacement: "Effect.catchFilter" + note: "Replace the Option-returning partial function with a `Filter` and handler. Adapt arguments and imports to the v4 API." +effect/Effect#catchSomeCause: + replacement: "Effect.catchCauseFilter" + note: "Replace the Option-returning partial function with a cause `Filter` and handler. Adapt arguments and imports to the v4 API." +effect/Effect#catchSomeDefect: + replacement: "none" + note: "Use `Effect.catchDefect` and branch explicitly, re-dying for unmatched defects. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#cause: + replacement: "Effect.exit" + note: "Inspect `Exit.Failure.cause`; v4 no longer exposes an Effect-only cause extractor. Adapt arguments and imports to the v4 API." +effect/Effect#checkInterruptible: + replacement: "none" + note: "Interruptibility introspection was removed; structure the region explicitly with `Effect.interruptible` or `Effect.uninterruptible`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#clock: + replacement: "Clock.Clock" + note: "Services are Effects in v4; yield or compose `Clock.Clock` directly. Adapt imports to the v4 API." +effect/Effect#configProviderWith: + replacement: "ConfigProvider.ConfigProvider.use" + note: "Use the ConfigProvider reference's `.use` helper to invoke the effectful callback. Adapt arguments and imports to the v4 API." +effect/Effect#console: + replacement: "Console.Console" + note: "Services are Effects in v4; yield or compose `Console.Console` directly. Adapt imports to the v4 API." +effect/Effect#consoleWith: + replacement: "Console.Console.use" + note: "Use the Console reference's `.use` helper to invoke the effectful callback. Adapt arguments and imports to the v4 API." +effect/Effect#contextWithEffect: + replacement: "Effect.contextWith" + note: "`contextWith` accepts an effectful callback in v4. Adapt arguments and imports to the v4 API." +effect/Effect#currentPropagatedSpan: + replacement: "Effect.currentParentSpan" + note: "Use the current parent span representation. Adapt arguments and imports to the v4 API." +effect/Effect#custom: + replacement: "none" + note: "The low-level custom instruction constructor was removed; use public constructors such as `Effect.sync`, `Effect.suspend`, or `Effect.callback`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#daemonChildren: + replacement: "Effect.awaitAllChildren" + note: "Use structured child-fiber waiting, or fork explicitly with `Effect.forkDetach` when detachment is intended. Adapt arguments and imports to the v4 API." +effect/Effect#descriptor: + replacement: "Effect.fiberId" + note: "The full fiber descriptor was removed; retrieve the current numeric fiber id. Adapt arguments and imports to the v4 API." +effect/Effect#descriptorWith: + replacement: "Effect.fiberId + Effect.flatMap" + note: "Read the current fiber id and invoke the callback explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#dieMessage: + replacement: "Effect.die" + note: "Construct the desired defect explicitly and pass it to `Effect.die`. Adapt arguments and imports to the v4 API." +effect/Effect#dieSync: + replacement: "Effect.suspend + Effect.die" + note: "Evaluate the lazy defect inside `Effect.suspend`. Adapt arguments and imports to the v4 API." +effect/Effect#diffFiberRefs: + replacement: "none" + note: "The public FiberRefs diff API was removed; model fiber-local state with context references and scoped `Effect.provideService`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#disconnect: + replacement: "Effect.forkDetach" + note: "Fork explicitly and decide how to await or interrupt the detached Fiber. Adapt arguments and imports to the v4 API." +effect/Effect#dropUntil: + replacement: "none" + note: "Use an explicit `Effect.gen` loop for an effectful stopping predicate. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#dropWhile: + replacement: "none" + note: "Use an explicit `Effect.gen` loop, or `Array.dropWhile` when the predicate is pure. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#Effect: + replacement: "Effect.Effect" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#Effect.AsEffect: + replacement: "Effect.Effect" + note: "Use the Effect type directly and extract channels with `Effect.Success`, `Effect.Error`, and `Effect.Services`. Adapt arguments and imports to the v4 API." +effect/Effect#Effect.Context: + replacement: "Effect.Services" + note: "Use the renamed type-level extractor for required services. Adapt arguments and imports to the v4 API." +effect/Effect#Effect.VarianceStruct: + replacement: "Effect.Variance" + note: "Use the v4 variance interface. Adapt arguments and imports to the v4 API." +effect/Effect#EffectGenerator: + replacement: "Effect.EffectIterator" + note: "Use the v4 iterator type used by generator delegation. Adapt arguments and imports to the v4 API." +effect/Effect#EffectTypeId: + replacement: "Effect.TypeId" + note: "Use the v4 type-level Effect identifier. Adapt arguments and imports to the v4 API." +effect/Effect#EffectUnify: + replacement: "Effect.EffectUnify" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#EffectUnifyIgnore: + replacement: "none" + note: "The internal unification-ignore helper is no longer public; rely on v4 Effect inference. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#either: + replacement: "Effect.result" + note: "V4 represents typed success/failure as `Result` instead of `Either`. Adapt arguments and imports to the v4 API." +effect/Effect#ensureErrorType: + replacement: "Effect.satisfiesErrorType" + note: "Use the renamed compile-time channel constraint. Adapt arguments and imports to the v4 API." +effect/Effect#ensureRequirementsType: + replacement: "Effect.satisfiesServicesType" + note: "Use the renamed compile-time services constraint. Adapt arguments and imports to the v4 API." +effect/Effect#ensureSuccessType: + replacement: "Effect.satisfiesSuccessType" + note: "Use the renamed compile-time channel constraint. Adapt arguments and imports to the v4 API." +effect/Effect#ensuringChild: + replacement: "Effect.ensuring + Fiber APIs" + note: "Track the child Fiber explicitly and run the finalizer with `Effect.ensuring`. Adapt arguments and imports to the v4 API." +effect/Effect#ensuringChildren: + replacement: "Effect.awaitAllChildren + Effect.ensuring" + note: "Use structured child waiting and an explicit finalizer. Adapt arguments and imports to the v4 API." +effect/Effect#every: + replacement: "Effect.forEach" + note: "Evaluate predicates with `Effect.forEach`, then test the resulting booleans with `Array.every`. Adapt arguments and imports to the v4 API." +effect/Effect#exists: + replacement: "Effect.findFirst" + note: "Find the first value satisfying the effectful predicate and test the returned Option. Adapt arguments and imports to the v4 API." +effect/Effect#fiberIdWith: + replacement: "Effect.fiberId + Effect.flatMap" + note: "Read the numeric fiber id and invoke the callback explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#filterEffectOrElse: + replacement: "Effect.flatMap" + note: "Evaluate the effectful predicate and branch to `Effect.succeed` or the fallback. Adapt arguments and imports to the v4 API." +effect/Effect#filterEffectOrFail: + replacement: "Effect.flatMap" + note: "Evaluate the effectful predicate and branch to `Effect.succeed` or `Effect.fail`. Adapt arguments and imports to the v4 API." +effect/Effect#filterOrDie: + replacement: "Effect.filterOrFail + Effect.orDie" + note: "Filter with a typed failure, then convert it to a defect. Adapt arguments and imports to the v4 API." +effect/Effect#filterOrDieMessage: + replacement: "Effect.filterOrFail + Effect.orDie" + note: "Create the message-bearing error in `filterOrFail`, then convert it to a defect. Adapt arguments and imports to the v4 API." +effect/Effect#finalizersMask: + replacement: "none" + note: "Configurable finalizer execution strategies were removed; register ordered finalizers explicitly in a Scope. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#flipWith: + replacement: "Effect.flip" + note: "Flip, apply the transformation, then flip the resulting Effect back. Adapt arguments and imports to the v4 API." +effect/Effect#fn: + replacement: "Effect.fn" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#fn.Gen: + replacement: "Effect.fn.Return" + note: "Use the v4 generator-return helper type. Adapt arguments and imports to the v4 API." +effect/Effect#fn.NonGen: + replacement: "Effect.fn.Untraced" + note: "Use the v4 function helper type for non-generator wrapping. Adapt arguments and imports to the v4 API." +effect/Effect#fork: + replacement: "Effect.forkChild" + note: "Use the renamed structured child-fiber combinator. Adapt arguments and imports to the v4 API." +effect/Effect#forkAll: + replacement: "Effect.forEach + Effect.forkChild" + note: "Fork each effect explicitly, or prefer a higher-level concurrent combinator. Adapt arguments and imports to the v4 API." +effect/Effect#forkDaemon: + replacement: "Effect.forkDetach" + note: "Use the renamed detached-fiber combinator. Adapt arguments and imports to the v4 API." +effect/Effect#forkWithErrorHandler: + replacement: "Effect.forkChild + Fiber.await" + note: "Fork explicitly and observe the Fiber result to handle errors. Adapt arguments and imports to the v4 API." +effect/Effect#fromFiber: + replacement: "Fiber.join" + note: "Join the Fiber to obtain an Effect of its result. Adapt arguments and imports to the v4 API." +effect/Effect#fromFiberEffect: + replacement: "Effect.flatMap + Fiber.join" + note: "FlatMap the effectful Fiber and join it. Adapt arguments and imports to the v4 API." +effect/Effect#fromNullable: + replacement: "Effect.fromOption + Option.fromNullable" + note: "Convert the nullable value to Option, then lift it into Effect. Adapt arguments and imports to the v4 API." +effect/Effect#functionWithSpan: + replacement: "Effect.withSpan" + note: "Wrap the function body with a span whose name/options are derived from its arguments. Adapt arguments and imports to the v4 API." +effect/Effect#FunctionWithSpanOptions: + replacement: "Tracer.SpanOptions" + note: "Use the v4 tracing options type when wrapping functions with `Effect.withSpan`. Adapt arguments and imports to the v4 API." +effect/Effect#gen: + replacement: "Effect.gen" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#getFiberRefs: + replacement: "none" + note: "The FiberRefs collection is no longer public; access individual context references through Effect services. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#getRuntimeFlags: + replacement: "none" + note: "RuntimeFlags are no longer a public Effect service; use supported high-level runtime options. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#if: + replacement: "Effect.suspend" + note: "Select the branch lazily with a JavaScript conditional inside `Effect.suspend`. Adapt arguments and imports to the v4 API." +effect/Effect#ignoreLogged: + replacement: "Effect.ignore" + note: "Pass `{ log: true }` to the consolidated ignore combinator. Adapt arguments and imports to the v4 API." +effect/Effect#inheritFiberRefs: + replacement: "none" + note: "Bulk FiberRef inheritance was removed; propagate required context references explicitly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#interruptWith: + replacement: "Effect.interrupt" + note: "V4 interruption uses the current fiber identity; remove the explicit FiberId argument. Adapt arguments and imports to the v4 API." +effect/Effect#intoDeferred: + replacement: "Deferred.into" + note: "Use the Deferred module combinator. Adapt arguments and imports to the v4 API." +effect/Effect#iterate: + replacement: "none" + note: "Use an explicit stateful `Effect.gen` loop; v4 removed the Effect-specific loop helper. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#labelMetrics: + replacement: "Metric.withAttributes" + note: "Convert labels to metric attributes and scope them around the Effect. Adapt arguments and imports to the v4 API." +effect/Effect#labelMetricsScoped: + replacement: "Metric.withAttributes" + note: "Apply metric attributes to the scoped Effect rather than mutating scoped labels. Adapt arguments and imports to the v4 API." +effect/Effect#LatchUnify: + replacement: "Latch.Latch" + note: "Latch moved to the standalone `effect/Latch` module; rely on normal v4 inference. Adapt arguments and imports to the v4 API." +effect/Effect#LatchUnifyIgnore: + replacement: "none" + note: "The internal Latch unification helper was removed; use `Latch.Latch` directly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#let: + replacement: "Effect.let" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#liftPredicate: + replacement: "Effect.filterOrFail" + note: "Lift the input with `Effect.succeed` and apply `filterOrFail`. Adapt arguments and imports to the v4 API." +effect/Effect#linkSpanCurrent: + replacement: "Effect.linkSpans" + note: "Use the v4 span-link combinator. Adapt arguments and imports to the v4 API." +effect/Effect#locally: + replacement: "Effect.provideService" + note: "FiberRef values are context references in v4; provide the reference for the Effect lifetime. Adapt arguments and imports to the v4 API." +effect/Effect#locallyScoped: + replacement: "Effect.provideService" + note: "Provide the context reference around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#locallyScopedWith: + replacement: "Effect.updateServiceScoped" + note: "Context references replace FiberRefs in v4; update the reference for the current scope. Adapt arguments and imports to the v4 API." +effect/Effect#locallyWith: + replacement: "Effect.updateService" + note: "Context references replace FiberRefs in v4; update the reference around the target Effect. Adapt arguments and imports to the v4 API." +effect/Effect#logAnnotations: + replacement: "References.CurrentLogAnnotations" + note: "Context references are Effects in v4; yield or compose `References.CurrentLogAnnotations` directly. Adapt imports to the v4 API." +effect/Effect#loop: + replacement: "none" + note: "Use an explicit `Effect.gen` loop and collect results when needed. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#makeLatch: + replacement: "Latch.make" + note: "Latch constructors moved to `effect/Latch`. Adapt arguments and imports to the v4 API." +effect/Effect#makeSemaphore: + replacement: "Semaphore.make" + note: "Semaphore constructors moved to `effect/Semaphore`. Adapt arguments and imports to the v4 API." +effect/Effect#mapAccum: + replacement: "Effect.reduce" + note: "Carry `[state, output]` through an effectful reduction. Adapt arguments and imports to the v4 API." +effect/Effect#mapErrorCause: + replacement: "Effect.catchCause + Effect.failCause" + note: "Transform the Cause in a cause handler and fail with the mapped Cause. Adapt arguments and imports to the v4 API." +effect/Effect#mapInputContext: + replacement: "Effect.contextWith + Effect.provide" + note: "Build the required context from the incoming context and provide it explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#merge: + replacement: "Effect.catch" + note: "Recover each typed error with `Effect.succeed` so both channels become success values. Adapt arguments and imports to the v4 API." +effect/Effect#mergeAll: + replacement: "Effect.reduce" + note: "Reduce the input effects with an effectful accumulator. Adapt arguments and imports to the v4 API." +effect/Effect#metricLabels: + replacement: "Metric.CurrentMetricAttributes" + note: "Context references are Effects in v4; yield or compose `Metric.CurrentMetricAttributes` directly. Adapt imports to the v4 API." +effect/Effect#negate: + replacement: "Effect.map" + note: "Map the boolean result with logical negation. Adapt arguments and imports to the v4 API." +effect/Effect#none: + replacement: "Effect.flatMap + Option.match" + note: "Fail for `Some` and succeed with void for `None`. Adapt arguments and imports to the v4 API." +effect/Effect#once: + replacement: "Effect.cached" + note: "Create the cached Effect once, then execute the returned Effect repeatedly. Adapt arguments and imports to the v4 API." +effect/Effect#optionFromOptional: + replacement: "Effect.catchTag" + note: "Map success to `Option.some` and recover `NoSuchElementError` with `Option.none`. Adapt arguments and imports to the v4 API." +effect/Effect#orDieWith: + replacement: "Effect.mapError + Effect.orDie" + note: "Map the typed error to the desired defect, then convert failures to defects. Adapt arguments and imports to the v4 API." +effect/Effect#orElse: + replacement: "Effect.catch" + note: "Ignore the caught error and evaluate the fallback Effect. Adapt arguments and imports to the v4 API." +effect/Effect#orElseFail: + replacement: "Effect.mapError" + note: "Replace every typed error with the lazily produced failure value. Adapt arguments and imports to the v4 API." +effect/Effect#parallelErrors: + replacement: "Effect.all" + note: "Use `{ mode: \"result\", concurrency: \"unbounded\" }` and collect failures explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#parallelFinalizers: + replacement: "none" + note: "Parallel finalizer strategy mutation was removed; fork independent cleanup explicitly when ordering is irrelevant. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#patchFiberRefs: + replacement: "none" + note: "Bulk FiberRefs patching was removed; update individual context references with `Effect.updateService`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#patchRuntimeFlags: + replacement: "none" + note: "RuntimeFlags patching was removed from the public API; use supported high-level runtime options. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#Permit: + replacement: "Semaphore.Semaphore" + note: "Use the standalone Semaphore API and its `withPermit` / `withPermits` methods. Adapt arguments and imports to the v4 API." +effect/Effect#raceWith: + replacement: "Effect.raceFirst + Fiber APIs" + note: "Use `raceFirst` for first completion, or fork both effects and inspect their Exits for custom finishers. Adapt arguments and imports to the v4 API." +effect/Effect#random: + replacement: "Random.Random" + note: "Services are Effects in v4; yield or compose `Random.Random` directly. Adapt imports to the v4 API." +effect/Effect#randomWith: + replacement: "Random.Random.use" + note: "Use the Random reference's `.use` helper to invoke the effectful callback. Prefer module-level Random operations when possible." +effect/Effect#reduceEffect: + replacement: "Effect.flatMap + Effect.reduce" + note: "Evaluate the initial Effect, then reduce the remaining effects. Adapt arguments and imports to the v4 API." +effect/Effect#reduceRight: + replacement: "Effect.reduce" + note: "Reverse the input first, then perform the effectful reduction. Adapt arguments and imports to the v4 API." +effect/Effect#reduceWhile: + replacement: "none" + note: "Use an explicit `Effect.gen` loop that checks the accumulator before each step. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#Repeat: + replacement: "Effect.Repeat" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#Repeat.Options: + replacement: "Effect.Repeat.Options" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#repeatN: + replacement: "Effect.repeat" + note: "Pass `{ times: n }` to the consolidated repeat combinator. Adapt arguments and imports to the v4 API." +effect/Effect#Retry: + replacement: "Effect.Retry" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#Retry.Options: + replacement: "Effect.Retry.Options" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#runRequestBlock: + replacement: "none" + note: "The request-runtime block runner is internal; submit requests with `Effect.request`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#runtime: + replacement: "Effect.context + Effect.runForkWith" + note: "Capture services as a Context and use the corresponding `run*With` function. Adapt arguments and imports to the v4 API." +effect/Effect#scheduleForked: + replacement: "Effect.schedule + Effect.forkScoped" + note: "Schedule the Effect, then fork it in the current Scope. Adapt arguments and imports to the v4 API." +effect/Effect#scopeWith: + replacement: "Effect.scopedWith" + note: "Use the renamed scoped callback combinator. Adapt arguments and imports to the v4 API." +effect/Effect#sequentialFinalizers: + replacement: "none" + note: "Sequential reverse-order finalization is the normal Scope behavior; remove this wrapper. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#Service: + replacement: "Context.Service" + note: "Replace the `effect` constructor option with `make`. V4 does not generate a `Default` layer or wire `dependencies`; define a `Layer.effect` and provide its dependencies explicitly." +effect/Effect#Service.AllowedType: + replacement: "Context.Service" + note: "Service type machinery moved to `Context.Service`; do not reference its internal helper types. Adapt arguments and imports to the v4 API." +effect/Effect#Service.Class: + replacement: "Context.Service" + note: "Service classes are now defined with `Context.Service`. Adapt arguments and imports to the v4 API." +effect/Effect#Service.HasArguments: + replacement: "Context.Service" + note: "Service constructor typing is handled by `Context.Service`. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeAccessors: + replacement: "Context.Service" + note: "Use the generated `.use` helper instead of v3 accessor type machinery. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeArguments: + replacement: "Context.Service" + note: "Pass a `make` Effect in the v4 `Context.Service` options. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeContext: + replacement: "Context.Service" + note: "Service context typing is inferred by `Context.Service`. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeDeps: + replacement: "Layer.provide" + note: "Compose service dependencies explicitly with Layers. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeDepsE: + replacement: "Layer.Error" + note: "Use Layer channel extractors rather than Service internals. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeDepsIn: + replacement: "Layer.Services" + note: "Use Layer channel extractors rather than Service internals. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeDepsOut: + replacement: "Layer.Success" + note: "Use Layer channel extractors rather than Service internals. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeError: + replacement: "Layer.Error" + note: "Use the Layer error-channel extractor. Adapt arguments and imports to the v4 API." +effect/Effect#Service.MakeService: + replacement: "Context.Service" + note: "The service shape is inferred by `Context.Service`. Adapt arguments and imports to the v4 API." +effect/Effect#Service.ProhibitedType: + replacement: "Context.Service" + note: "Do not reference the removed internal validation type. Adapt arguments and imports to the v4 API." +effect/Effect#serviceConstants: + replacement: "Context.Service.use" + note: "Expose constants from the service explicitly or through the generated `use` helper. Adapt arguments and imports to the v4 API." +effect/Effect#serviceFunction: + replacement: "Context.Service.use" + note: "Use the service class `.use` helper to build an accessor function. Adapt arguments and imports to the v4 API." +effect/Effect#serviceFunctionEffect: + replacement: "Context.Service.use" + note: "Use the service class `.use` helper for effect-returning methods. Adapt arguments and imports to the v4 API." +effect/Effect#serviceFunctions: + replacement: "Context.Service.use" + note: "Define explicit service accessors with the generated `.use` helper. Adapt arguments and imports to the v4 API." +effect/Effect#serviceMembers: + replacement: "Context.Service.use" + note: "Define explicit service accessors with the generated `.use` helper. Adapt arguments and imports to the v4 API." +effect/Effect#serviceOptional: + replacement: "service" + note: "Services are Effects in v4; yield or compose the service key directly. Use `Effect.serviceOption` only when absence is expected." +effect/Effect#setFiberRefs: + replacement: "none" + note: "Bulk FiberRefs replacement was removed; provide individual context references. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#step: + replacement: "none" + note: "The low-level Effect stepping API was removed from the public surface. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#summarized: + replacement: "Effect.gen" + note: "Run the summary Effect before and after the target Effect and combine the two measurements explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#supervised: + replacement: "FiberSet" + note: "Track explicitly forked Fibers in a scoped `FiberSet` instead of installing a runtime Supervisor. Adapt arguments and imports to the v4 API." +effect/Effect#Tag: + replacement: "Context.Service" + note: "Define services with `Context.Service`; use the generated `.use` helper for accessors. Adapt arguments and imports to the v4 API." +effect/Effect#Tag.AllowedType: + replacement: "Context.Service" + note: "Tag validation internals were removed; use `Context.Service` directly. Adapt arguments and imports to the v4 API." +effect/Effect#Tag.ProhibitedType: + replacement: "Context.Service" + note: "Tag validation internals were removed; use `Context.Service` directly. Adapt arguments and imports to the v4 API." +effect/Effect#Tag.Proxy: + replacement: "Context.Service.use" + note: "Replace proxy accessors with the generated `.use` helper. Adapt arguments and imports to the v4 API." +effect/Effect#tagMetrics: + replacement: "Metric.withAttributes" + note: "Convert key/value tags to metric attributes. Adapt arguments and imports to the v4 API." +effect/Effect#tagMetricsScoped: + replacement: "Metric.withAttributes" + note: "Apply attributes around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#takeUntil: + replacement: "none" + note: "Use an explicit `Effect.gen` loop for an effectful stopping predicate. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#takeWhile: + replacement: "none" + note: "Use an explicit `Effect.gen` loop, or `Array.takeWhile` when the predicate is pure. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#tapBoth: + replacement: "Effect.tapError + Effect.tap" + note: "Tap the failure path first, then tap successful values. Adapt arguments and imports to the v4 API." +effect/Effect#tapErrorCause: + replacement: "Effect.tapCause" + note: "Use the shortened v4 cause-tap name. Adapt arguments and imports to the v4 API." +effect/Effect#timedWith: + replacement: "Effect.gen" + note: "Read the supplied clock Effect before and after the target and compute the Duration explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#timeoutFail: + replacement: "Effect.timeoutOrElse" + note: "Use `Effect.fail(onTimeout())` as the timeout fallback. Adapt arguments and imports to the v4 API." +effect/Effect#timeoutFailCause: + replacement: "Effect.timeoutOrElse" + note: "Use `Effect.failCause(onTimeout())` as the timeout fallback. Adapt arguments and imports to the v4 API." +effect/Effect#timeoutTo: + replacement: "Effect.timeoutOrElse + Effect.map" + note: "Map successful values first and use the timeout fallback for `onTimeout`. Adapt arguments and imports to the v4 API." +effect/Effect#tracerWith: + replacement: "Tracer.Tracer.use" + note: "Use the Tracer reference's `.use` helper to invoke the effectful callback. Adapt arguments and imports to the v4 API." +effect/Effect#transplant: + replacement: "none" + note: "Fiber scope grafting was removed; use structured concurrency with `forkChild`, `forkScoped`, or `forkIn`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#transposeMapOption: + replacement: "Option.match" + note: "Return `Effect.succeedNone` for None and map the Effect result to Some. Adapt arguments and imports to the v4 API." +effect/Effect#try: + replacement: "Effect.try" + note: "Use the callback overload for Cause.UnknownError, or the object overload with try and catch to map failures to a custom error. The callback-only overload does not accept a custom error type parameter." +effect/Effect#tryMap: + replacement: "Effect.flatMap + Effect.try" + note: "FlatMap the source value into the v4 synchronous try constructor. Adapt arguments and imports to the v4 API." +effect/Effect#tryMapPromise: + replacement: "Effect.flatMap + Effect.tryPromise" + note: "FlatMap the source value into the v4 Promise try constructor. Adapt arguments and imports to the v4 API." +effect/Effect#unless: + replacement: "Effect.suspend" + note: "Select `Effect.void` or the target Effect with a negated lazy condition. Adapt arguments and imports to the v4 API." +effect/Effect#unlessEffect: + replacement: "Effect.when" + note: "Negate the effectful boolean condition, then use the consolidated `when`. Adapt arguments and imports to the v4 API." +effect/Effect#unsafeMakeLatch: + replacement: "Latch.makeUnsafe" + note: "The unsafe constructor moved to `effect/Latch`. Adapt arguments and imports to the v4 API." +effect/Effect#unsafeMakeSemaphore: + replacement: "Semaphore.makeUnsafe" + note: "The unsafe constructor moved to `effect/Semaphore`. Adapt arguments and imports to the v4 API." +effect/Effect#unsandbox: + replacement: "Effect.catch + Effect.failCause" + note: "Treat the sandboxed Cause as an error and fail with that Cause. Adapt arguments and imports to the v4 API." +effect/Effect#updateFiberRefs: + replacement: "none" + note: "Bulk FiberRefs updates were removed; update individual context references with `Effect.updateService`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#using: + replacement: "Effect.scoped + Effect.flatMap" + note: "Acquire inside a fresh Scope, run the use Effect, and close the Scope afterward. Adapt arguments and imports to the v4 API." +effect/Effect#validateAll: + replacement: "Effect.validate" + note: "Use the consolidated collection validation combinator. Adapt arguments and imports to the v4 API." +effect/Effect#validateFirst: + replacement: "Effect.firstSuccessOf" + note: "Map inputs to validation effects and select the first success; handle accumulated diagnostics explicitly if required. Adapt arguments and imports to the v4 API." +effect/Effect#validateWith: + replacement: "Effect.zipWith" + note: "Zip and combine the Effects; use `mode: \"result\"` when both failures must be retained. Adapt arguments and imports to the v4 API." +effect/Effect#whenEffect: + replacement: "Effect.when" + note: "The v4 `when` combinator accepts an effectful boolean condition directly. Adapt arguments and imports to the v4 API." +effect/Effect#whenFiberRef: + replacement: "reference.use + Effect.when" + note: "Use the Context.Reference `.use` helper to inspect the value, test it, and branch explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#whenLogLevel: + replacement: "none" + note: "Log-level conditional execution was removed; configure Logger filtering and guard optional work explicitly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#whenRef: + replacement: "Ref.get + Effect.flatMap" + note: "Read the Ref, test it, and branch explicitly. Adapt arguments and imports to the v4 API." +effect/Effect#withClock: + replacement: "Effect.provideService" + note: "Provide `Clock.Clock` for the target Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withClockScoped: + replacement: "Effect.provideService" + note: "Provide `Clock.Clock` around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withConcurrency: + replacement: "none" + note: "Ambient concurrency was removed; pass `concurrency` directly to `Effect.all`, `Effect.forEach`, and related combinators. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withConfigProvider: + replacement: "Effect.provideService" + note: "Provide `ConfigProvider.ConfigProvider` for the target Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withConfigProviderScoped: + replacement: "Effect.provideService" + note: "Provide the ConfigProvider around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withConsole: + replacement: "Effect.provideService" + note: "Provide `Console.Console` for the target Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withConsoleScoped: + replacement: "Effect.provideService" + note: "Provide the Console service around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withEarlyRelease: + replacement: "Scope.make + Scope.close" + note: "Create a Scope explicitly, provide it to acquisition, and retain a close action. Adapt arguments and imports to the v4 API." +effect/Effect#withFiberRuntime: + replacement: "none" + note: "Direct FiberRuntime access was removed; use public Effect, Fiber, and Context operations. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withLogSpan: + replacement: "Effect.withLogSpan" + note: "Still exported in v4; update call sites for the revised signature, options, and channel inference." +effect/Effect#withMaxOpsBeforeYield: + replacement: "none" + note: "The scheduler operation budget is no longer configurable through Effect. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withMetric: + replacement: "Effect.tap + Metric.update" + note: "Update the Metric explicitly from the Effect success value. Adapt arguments and imports to the v4 API." +effect/Effect#withRandom: + replacement: "Effect.provideService" + note: "Provide `Random.Random` for the target Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withRandomFixed: + replacement: "Effect.provideService" + note: "Provide a custom deterministic `Random.Random` implementation. Adapt arguments and imports to the v4 API." +effect/Effect#withRandomScoped: + replacement: "Effect.provideService" + note: "Provide the Random service around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withRequestBatching: + replacement: "none" + note: "Ambient request batching configuration was removed; configure batching in the `RequestResolver`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withRequestCache: + replacement: "none" + note: "Ambient request-cache replacement was removed; model keyed caching explicitly with `Cache`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withRequestCaching: + replacement: "none" + note: "Ambient request caching was removed; configure resolution or use `Cache` explicitly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withRuntimeFlagsPatch: + replacement: "none" + note: "RuntimeFlags patching was removed from the public API. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withRuntimeFlagsPatchScoped: + replacement: "none" + note: "Scoped RuntimeFlags patching was removed from the public API. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withScheduler: + replacement: "none" + note: "Ambient scheduler replacement was removed; use supported runtime run options or explicit scheduling combinators. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withSchedulingPriority: + replacement: "none" + note: "Ambient fiber scheduling priority was removed from the public API. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive." +effect/Effect#withTracerScoped: + replacement: "Effect.provideService" + note: "Provide the Tracer service around the scoped Effect. Adapt arguments and imports to the v4 API." +effect/Effect#withUnhandledErrorLogLevel: + replacement: "Effect.ignore" + note: "Handle or explicitly ignore child-fiber failures, selecting the desired log behavior at the boundary. Adapt arguments and imports to the v4 API." +effect/Effect#zipLeft: + replacement: "Effect.zip + Effect.map" + note: "Zip the Effects and select the first tuple element. Adapt arguments and imports to the v4 API." +effect/Effect#zipRight: + replacement: "Effect.andThen" + note: "Sequence the Effects and retain the second result. Adapt arguments and imports to the v4 API." +"effect/Effect#repeatOrElse": + replacement: "Effect.repeatOrElse" + note: "The fallback receives Option> instead of Option. Read metadata.output for the previous schedule output and metadata.attempt for the attempt count. The error also includes schedule failures." +"effect/Effect#tryPromise": + replacement: "Effect.tryPromise" + note: "Use the callback overload for Cause.UnknownError, or the object overload with try and catch to map failures to a custom error. The callback-only overload does not accept a custom error type parameter." diff --git a/migration/annotations/effect__Effectable.yaml b/migration/annotations/effect__Effectable.yaml new file mode 100644 index 000000000..fe8066d27 --- /dev/null +++ b/migration/annotations/effect__Effectable.yaml @@ -0,0 +1,30 @@ +"effect/Effectable#ChannelTypeId": + replacement: "Channel.TypeId" + note: "The public channel brand moved to its owning module; v4 uses a string TypeId rather than the v3 Symbol." +"effect/Effectable#Class": + replacement: "Effectable.Class" + note: "Still available; replace commit() with an asEffect() method returning the Effect. The intermediate v4 override property/getter is no longer supported." +"effect/Effectable#CommitPrimitive": + replacement: "new() => Effect.Effect" + note: "The named constructor interface was removed; inline the constructor type when needed." +"effect/Effectable#CommitPrototype": + replacement: "Effectable.Prototype" + note: "Use Effectable.Prototype({ label, evaluate(fiber) { ... } }) and move the old commit body into evaluate." +"effect/Effectable#EffectPrototype": + replacement: "Effectable.Prototype" + note: "The raw multi-branded prototype was removed; use Prototype with an explicit evaluate callback." +"effect/Effectable#EffectTypeId": + replacement: "Effect.TypeId" + note: "The public Effect brand moved to Effect; v4 uses a string TypeId rather than the v3 Symbol." +"effect/Effectable#SinkTypeId": + replacement: "Sink.isSink" + note: "Sink's TypeId is private in v4; use the public guard for runtime checks and public Sink constructors for values." +"effect/Effectable#StreamTypeId": + replacement: "Stream.TypeId" + note: "The public stream brand moved to its owning module; v4 uses a string TypeId rather than the v3 Symbol." +"effect/Effectable#StructuralClass": + replacement: "Effectable.Class" + note: "Use Class and migrate commit() to asEffect(); v4 equality is structural by default." +"effect/Effectable#StructuralCommitPrototype": + replacement: "Effectable.Prototype" + note: "Use Prototype with evaluate; a separate structural prototype is unnecessary because v4 equality is structural by default." diff --git a/migration/annotations/effect__Either.yaml b/migration/annotations/effect__Either.yaml new file mode 100644 index 000000000..02c6510a1 --- /dev/null +++ b/migration/annotations/effect__Either.yaml @@ -0,0 +1,105 @@ +"effect/Either#all": + replacement: "Result.all" + note: "Either moved to Result; collection behavior is retained with Failure and Success terminology." +"effect/Either#ap": + replacement: "Result.flatMap" + note: "Use Result.flatMap(self, (f) => Result.map(that, f)); v4 has no Result.ap." +"effect/Either#bind": + replacement: "Result.bind" + note: "The do-notation combinator moved to Result." +"effect/Either#bindTo": + replacement: "Result.bindTo" + note: "The do-notation combinator moved to Result." +"effect/Either#Do": + replacement: "Result.Do" + note: "The empty successful do-notation value moved to Result." +"effect/Either#Either": + replacement: "Result.Result" + note: "Either became Result; Right and Left became Success and Failure." +"effect/Either#Either.Left": + replacement: "Result.Result.Failure" + note: "Use the Result namespace extractor for the failure variant." +"effect/Either#Either.Right": + replacement: "Result.Result.Success" + note: "Use the Result namespace extractor for the success variant." +"effect/Either#EitherTypeLambda": + replacement: "Result.ResultTypeLambda" + note: "Moved and renamed with Result." +"effect/Either#EitherUnify": + replacement: "Result.ResultUnify" + note: "Moved and renamed with Result." +"effect/Either#EitherUnifyIgnore": + replacement: "Result.ResultUnifyIgnore" + note: "Moved and renamed with Result." +"effect/Either#filterOrLeft": + replacement: "Result.filterOrFail" + note: "Left is now Failure, so the predicate combinator is filterOrFail." +"effect/Either#flip": + replacement: "Result.flip" + note: "The channel-swapping combinator moved to Result." +"effect/Either#fromNullable": + replacement: "Result.fromNullishOr" + note: "Renamed with v4 nullish-or terminology." +"effect/Either#getEquivalence": + replacement: "Result.makeEquivalence" + note: "Pass success and failure equivalences positionally instead of a right and left object." +"effect/Either#getLeft": + replacement: "Result.getFailure" + note: "Extract the Result failure as an Option." +"effect/Either#getOrElse": + replacement: "Result.getOrElse" + note: "Moved unchanged to Result." +"effect/Either#getOrThrow": + replacement: "Result.getOrThrow" + note: "V4 throws the raw Failure value; use getOrThrowWith when a custom Error is required." +"effect/Either#getOrThrowWith": + replacement: "Result.getOrThrowWith" + note: "Moved to Result; the callback receives the Failure value." +"effect/Either#getOrUndefined": + replacement: "Result.getOrUndefined" + note: "Moved unchanged to Result." +"effect/Either#getRight": + replacement: "Result.getSuccess" + note: "Extract the Result success as an Option." +"effect/Either#isEither": + replacement: "Result.isResult" + note: "Renamed with the data type." +"effect/Either#isLeft": + replacement: "Result.isFailure" + note: "Left is now the Failure variant." +"effect/Either#isRight": + replacement: "Result.isSuccess" + note: "Right is now the Success variant." +"effect/Either#left": + replacement: "Result.fail" + note: "Construct a Failure with Result.fail." +"effect/Either#Left": + replacement: "Result.Failure" + note: "Left became Failure; .left became .failure." +"effect/Either#let": + replacement: "Result.let" + note: "The do-notation combinator moved to Result." +"effect/Either#map": + replacement: "Result.map" + note: "Map now transforms the Success channel." +"effect/Either#mapLeft": + replacement: "Result.mapError" + note: "Left mapping became failure-channel error mapping." +"effect/Either#match": + replacement: "Result.match" + note: "Rename handlers from onLeft and onRight to onFailure and onSuccess." +"effect/Either#right": + replacement: "Result.succeed" + note: "Construct a Success with Result.succeed." +"effect/Either#Right": + replacement: "Result.Success" + note: "Right became Success; .right became .success." +"effect/Either#try": + replacement: "Result.try" + note: "The synchronous throwable constructor moved to Result." +"effect/Either#TypeId": + replacement: "none" + note: "Result keeps its brand private and exports no public TypeId." +"effect/Either#void": + replacement: "Result.void" + note: "Use the prebuilt successful Result." diff --git a/migration/annotations/effect__Encoding.yaml b/migration/annotations/effect__Encoding.yaml new file mode 100644 index 000000000..9b1508e3e --- /dev/null +++ b/migration/annotations/effect__Encoding.yaml @@ -0,0 +1,24 @@ +"effect/Encoding#DecodeException": + replacement: "Encoding.EncodingError" + note: "Use the unified error class with kind Decode." +"effect/Encoding#DecodeExceptionTypeId": + replacement: "Encoding.EncodingErrorTypeId" + note: "Decode and encode failures now share one marker." +"effect/Encoding#decodeUriComponent": + replacement: "Result.try" + note: "Wrap decodeURIComponent in Result.try and map failure to EncodingError, or decode Schema.StringFromUriComponent." +"effect/Encoding#EncodeException": + replacement: "Encoding.EncodingError" + note: "Use the unified error class with kind Encode." +"effect/Encoding#EncodeExceptionTypeId": + replacement: "Encoding.EncodingErrorTypeId" + note: "Decode and encode failures now share one marker." +"effect/Encoding#encodeUriComponent": + replacement: "Result.try" + note: "Wrap encodeURIComponent in Result.try and map failure to EncodingError, or encode Schema.StringFromUriComponent." +"effect/Encoding#isDecodeException": + replacement: "Encoding.isEncodingError" + note: "Use the unified guard and test kind === Decode when decode-only narrowing is required." +"effect/Encoding#isEncodeException": + replacement: "Encoding.isEncodingError" + note: "Use the unified guard and test kind === Encode when encode-only narrowing is required." diff --git a/migration/annotations/effect__Equal.yaml b/migration/annotations/effect__Equal.yaml new file mode 100644 index 000000000..34601828d --- /dev/null +++ b/migration/annotations/effect__Equal.yaml @@ -0,0 +1,3 @@ +"effect/Equal#equivalence": + replacement: "Equal.asEquivalence" + note: "Direct rename. The returned equivalence now follows v4 structural equality, including NaN equality and cached comparisons for immutable objects." diff --git a/migration/annotations/effect__Equivalence.yaml b/migration/annotations/effect__Equivalence.yaml new file mode 100644 index 000000000..7b898a5b0 --- /dev/null +++ b/migration/annotations/effect__Equivalence.yaml @@ -0,0 +1,42 @@ +"effect/Equivalence#all": + replacement: "Equivalence.Tuple([...collection])" + note: "Materialize the comparator iterable for Tuple. Unlike v3 prefix comparison, v4 requires equal input lengths; use Equivalence.make for intentional prefix semantics." +"effect/Equivalence#array": + replacement: "Equivalence.Array" + note: "Capitalized constructor name; positional equal-length array comparison is unchanged." +"effect/Equivalence#bigint": + replacement: "Equivalence.BigInt" + note: "Capitalized instance name; strict bigint equality is unchanged." +"effect/Equivalence#boolean": + replacement: "Equivalence.Boolean" + note: "Capitalized instance name; strict boolean equality is unchanged." +"effect/Equivalence#combineMany": + replacement: "Equivalence.combine(self, Equivalence.combineAll(collection))" + note: "Compose combine with combineAll; the dedicated dual combineMany helper was removed." +"effect/Equivalence#Equivalence": + replacement: "Equivalence.Equivalence" + note: "The callable type is retained but is now a type alias, so declaration merging is no longer supported." +"effect/Equivalence#number": + replacement: "Equivalence.Number" + note: "Capitalized instance name. V4 considers NaN equivalent to NaN; use Equivalence.strictEqual() for exact v3 strict-equality behavior." +"effect/Equivalence#product": + replacement: "Equivalence.Tuple([self, that])" + note: "Replace the dual two-comparator helper with the single-array Tuple constructor." +"effect/Equivalence#productMany": + replacement: "Equivalence.Tuple([self, ...collection])" + note: "Materialize the comparator iterable in one Tuple call; v4 rejects unequal input lengths instead of using v3 prefix semantics." +"effect/Equivalence#strict": + replacement: "Equivalence.strictEqual" + note: "Renamed strict-equality constructor; call as Equivalence.strictEqual()." +"effect/Equivalence#string": + replacement: "Equivalence.String" + note: "Capitalized instance name; case-sensitive strict equality is unchanged." +"effect/Equivalence#struct": + replacement: "Equivalence.Struct" + note: "Capitalized constructor name. V4 also compares configured symbol and non-enumerable keys via Reflect.ownKeys." +"effect/Equivalence#symbol": + replacement: "Equivalence.strictEqual()" + note: "There is no Symbol instance export; strictEqual preserves the v3 symbol comparison." +"effect/Equivalence#tuple": + replacement: "Equivalence.Tuple([eqA, eqB, ...])" + note: "Capitalized constructor now takes one comparator array instead of rest arguments and rejects unequal input lengths." diff --git a/migration/annotations/effect__ExecutionPlan.yaml b/migration/annotations/effect__ExecutionPlan.yaml new file mode 100644 index 000000000..bd73e986f --- /dev/null +++ b/migration/annotations/effect__ExecutionPlan.yaml @@ -0,0 +1,9 @@ +effect/ExecutionPlan#ExecutionPlan: + replacement: "ExecutionPlan.ExecutionPlan" + note: "The plan type remains; withRequirements was renamed to captureRequirements." +effect/ExecutionPlan#make: + replacement: "ExecutionPlan.make" + note: "The variadic execution-plan constructor remains unchanged." +effect/ExecutionPlan#TypesBase: + replacement: "ExecutionPlan.ConfigBase" + note: "The base type for execution-plan step configuration was renamed." diff --git a/migration/annotations/effect__ExecutionStrategy.yaml b/migration/annotations/effect__ExecutionStrategy.yaml new file mode 100644 index 000000000..831f31afa --- /dev/null +++ b/migration/annotations/effect__ExecutionStrategy.yaml @@ -0,0 +1,33 @@ +effect/ExecutionStrategy#ExecutionStrategy: + replacement: "Types.Concurrency | Scope.ExecutionStrategy" + note: "The ADT was removed; use number | unbounded for operation concurrency, or sequential | parallel for Scope finalizers." +effect/ExecutionStrategy#isParallel: + replacement: "strategy === \"parallel\"" + note: "Compare the Scope strategy directly; for concurrency options compare with unbounded." +effect/ExecutionStrategy#isParallelN: + replacement: "typeof concurrency === \"number\"" + note: "Bounded parallelism is represented directly by a numeric concurrency value." +effect/ExecutionStrategy#isSequential: + replacement: "strategy === \"sequential\"" + note: "Compare the Scope strategy directly; for operation concurrency use the value 1." +effect/ExecutionStrategy#match: + replacement: "switch" + note: "Use ordinary branching over the consumer-specific concurrency or Scope strategy primitive." +effect/ExecutionStrategy#parallel: + replacement: "\"parallel\" | \"unbounded\"" + note: "Use parallel for Scope finalizers or unbounded for operation concurrency." +effect/ExecutionStrategy#Parallel: + replacement: "\"parallel\" | \"unbounded\"" + note: "The tagged case was removed; use the consumer-specific primitive value." +effect/ExecutionStrategy#parallelN: + replacement: "number" + note: "Pass the parallelism directly as a numeric concurrency option; Scope has no bounded parallel strategy." +effect/ExecutionStrategy#ParallelN: + replacement: "number" + note: "The tagged case was removed; bounded operation concurrency is represented directly by a number." +effect/ExecutionStrategy#sequential: + replacement: "\"sequential\" | 1" + note: "Use sequential for Scope finalizers or 1 for operation concurrency." +effect/ExecutionStrategy#Sequential: + replacement: "\"sequential\" | 1" + note: "The tagged case was removed; use the consumer-specific primitive value." diff --git a/migration/annotations/effect__Exit.yaml b/migration/annotations/effect__Exit.yaml new file mode 100644 index 000000000..5644d4368 --- /dev/null +++ b/migration/annotations/effect__Exit.yaml @@ -0,0 +1,72 @@ +effect/Exit#all: + replacement: "Exit.asVoidAll + Exit.isSuccess + Option.some / Option.none" + note: "No direct value-collecting v4 equivalent. Materialize the iterable once; return Option.none for empty input, use Exit.asVoidAll to combine every failure, and otherwise collect each Success.value into Exit.succeed and wrap it in Option.some. The parallel option is gone because v4 Cause flattens sequential and parallel composition." +effect/Exit#as: + replacement: "Exit.map" + note: "Replace with Exit.map(self, () => value); this preserves a failed Exit and returns Exit data rather than a general Effect." +effect/Exit#causeOption: + replacement: "Exit.getCause" + note: "Direct rename; still returns Option.some(cause) for Failure and Option.none for Success." +effect/Exit#exists: + replacement: "Exit.isSuccess" + note: "No direct v4 combinator; use Exit.isSuccess(self) && predicate(self.value). If callers rely on the refinement overload, retain an explicitly typed wrapper returning self is Exit.Exit." +effect/Exit#Exit: + replacement: "Exit.Exit" + note: "Still exported as Exit.Exit = Exit.Success | Exit.Failure; v4 variants share Exit.Exit.Proto and remain Effect values." +effect/Exit#ExitUnify: + replacement: "none" + note: "Removed type-level implementation hook; delete direct references. V4 Success and Failure inherit Exit.Exit.Proto, but no exported Exit-specific Unify interface replaces this API." +effect/Exit#ExitUnifyIgnore: + replacement: "none" + note: "Removed type-level implementation hook; delete direct references. V4 Success and Failure inherit Exit.Exit.Proto, but no exported Exit-specific Unify interface replaces this API." +effect/Exit#Failure: + replacement: "Exit.Failure" + note: "Still exported with _tag Failure and cause; it now extends Exit.Exit.Proto and no longer exposes the v3 _op, effect_instruction_i0, or Exit-specific Unify fields." +effect/Exit#flatMapEffect: + replacement: "Effect.matchCauseEffectEager" + note: "Use Effect.matchCauseEffectEager(self, { onFailure: cause => Effect.succeed(Exit.failCause(cause)), onSuccess: f }). The explicit failure branch is required because v3 preserved an input Failure as a successful outer Effect; plain Effect.flatMap would instead fail the outer Effect." +effect/Exit#flatten: + replacement: "Exit.match" + note: "No direct v4 Exit flatten; use Exit.match(self, { onFailure: Exit.failCause, onSuccess: identity }) to return the inner Exit on success and preserve an outer failure as Exit data." +effect/Exit#forEachEffect: + replacement: "Effect.flatMapEager + Effect.exit" + note: "Use Effect.exit(Effect.flatMapEager(self, f)). This captures both the original Exit failure and failures from f into the returned Exit while keeping the outer Effect infallible; flatMapEager preserves v3's eager callback selection for an already-resolved Exit." +effect/Exit#fromEither: + replacement: "Result.match + Exit.fail / Exit.succeed" + note: "V3 Either is v4 Result. Convert with Result.match(result, { onFailure: Exit.fail, onSuccess: Exit.succeed }); there is no v4 Exit.fromResult constructor." +effect/Exit#fromOption: + replacement: "Option.match + Exit.fail / Exit.succeed" + note: "Use Option.match(option, { onNone: () => Exit.fail(undefined), onSome: Exit.succeed }) to preserve v3's Exit contract. Exit.findErrorOption is an accessor and is not a replacement." +effect/Exit#getOrElse: + replacement: "Exit.match" + note: "Use Exit.match(self, { onFailure: orElse, onSuccess: identity }); onFailure still receives the full Cause." +effect/Exit#isInterrupted: + replacement: "Exit.hasInterrupts" + note: "Direct semantic rename; true for a Failure whose Cause contains at least one Interrupt reason, false for Success." +effect/Exit#mapErrorCause: + replacement: "Exit.match + Exit.failCause / Exit.succeed" + note: "No direct v4 combinator. Use Exit.match(self, { onFailure: cause => Exit.failCause(f(cause)), onSuccess: Exit.succeed }); f now receives the flattened v4 Cause representation. Cause.map is only equivalent when f merely maps typed errors." +effect/Exit#matchEffect: + replacement: "Effect.matchCauseEffectEager" + note: "Direct cause-aware migration because Exit is an Effect in v4. Use the same onFailure/onSuccess handlers; the Eager variant preserves v3's immediate branch selection for resolved Exit values." +effect/Exit#Success: + replacement: "Exit.Success" + note: "Still exported with _tag Success and value; it now extends Exit.Exit.Proto, defaults E to never, and no longer exposes the v3 _op, effect_instruction_i0, or Exit-specific Unify fields." +effect/Exit#zipLeft: + replacement: "Exit.asVoidAll" + note: "Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : self. This retains the left success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition." +effect/Exit#zipPar: + replacement: "Exit.asVoidAll + Exit.succeed" + note: "No direct v4 Exit pair combinator. Check Exit.asVoidAll([self, that]); return its Failure, or after narrowing both inputs to Success return Exit.succeed([self.value, that.value]). V4 Cause.combine has no parallel marker." +effect/Exit#zipParLeft: + replacement: "Exit.asVoidAll" + note: "Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : self. This retains the left success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition." +effect/Exit#zipParRight: + replacement: "Exit.asVoidAll" + note: "Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : that. This retains the right success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition." +effect/Exit#zipRight: + replacement: "Exit.asVoidAll" + note: "Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : that. This retains the right success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition." +effect/Exit#zipWith: + replacement: "Exit.match" + note: "No direct v4 equivalent. Nested-match both Exits: preserve a lone failure cause, call options.onFailure and Exit.failCause only when both fail, and call Exit.succeed(options.onSuccess(a, b)) when both succeed." diff --git a/migration/annotations/effect__FastCheck.yaml b/migration/annotations/effect__FastCheck.yaml new file mode 100644 index 000000000..8a0c421dd --- /dev/null +++ b/migration/annotations/effect__FastCheck.yaml @@ -0,0 +1,91 @@ +"effect/FastCheck": + replacement: "fast-check" + note: "Effect no longer re-exports fast-check. Depend on the fast-check package and import it directly. For Schema-derived generation, use Arbitrary.schema from effect/unstable/arbitrary." +"effect/FastCheck#ascii": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. fast-check v4 replaced character arbitraries with string units." + example: "FastCheck.string({ unit: \"binary-ascii\", minLength: 1, maxLength: 1 })" +"effect/FastCheck#asciiString": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Use the binary-ascii string unit." + example: "FastCheck.string({ ...constraints, unit: \"binary-ascii\" })" +"effect/FastCheck#base64": + replacement: "FastCheck.constantFrom" + note: "Depend on fast-check and import it directly. Generate one base64 alphabet character; base64String remains for complete encoded strings." + example: "FastCheck.constantFrom(...\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/\")" +"effect/FastCheck#bigIntN": + replacement: "FastCheck.bigInt" + note: "Depend on fast-check and import it directly. Express the signed bit range with min and max constraints." +"effect/FastCheck#bigUint": + replacement: "FastCheck.bigInt" + note: "Depend on fast-check and import it directly. Use a minimum of 0n and the previous maximum." + example: "FastCheck.bigInt({ min: 0n, max })" +"effect/FastCheck#BigUintConstraints": + replacement: "FastCheck.BigIntConstraints" + note: "Depend on fast-check and import it directly. Unsigned bigint constraints were consolidated into BigIntConstraints with min: 0n." +"effect/FastCheck#bigUintN": + replacement: "FastCheck.bigInt" + note: "Depend on fast-check and import it directly. Express the unsigned bit range with min and max constraints." + example: "FastCheck.bigInt({ min: 0n, max: (1n << BigInt(n)) - 1n })" +"effect/FastCheck#char": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Use a one-unit printable ASCII string." + example: "FastCheck.string({ unit: \"grapheme-ascii\", minLength: 1, maxLength: 1 })" +"effect/FastCheck#char16bits": + replacement: "FastCheck.nat" + note: "Depend on fast-check and import it directly. Map a 16-bit natural number through String.fromCharCode." + example: "FastCheck.nat({ max: 0xffff }).map(String.fromCharCode)" +"effect/FastCheck#check": + replacement: "FastCheck.check" + note: "Depend on fast-check and import it directly. The runner remains, but RunDetails.error was replaced by errorInstance in fast-check v4." +"effect/FastCheck#constant": + replacement: "FastCheck.constant" + note: "Depend on fast-check and import it directly. The API remains; v4 infers literal types by default." +"effect/FastCheck#context": + replacement: "FastCheck.context" + note: "Depend on fast-check and import it directly. The API is otherwise unchanged." +"effect/FastCheck#fullUnicode": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Use a one-unit binary Unicode string." + example: "FastCheck.string({ unit: \"binary\", minLength: 1, maxLength: 1 })" +"effect/FastCheck#fullUnicodeString": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Use the binary string unit." + example: "FastCheck.string({ ...constraints, unit: \"binary\" })" +"effect/FastCheck#hexa": + replacement: "FastCheck.integer" + note: "Depend on fast-check and import it directly. Map an integer from 0 through 15 to a hexadecimal character." +"effect/FastCheck#hexaString": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Pass a hexadecimal-character arbitrary as the string unit." +"effect/FastCheck#stream": + replacement: "FastCheck.stream" + note: "Depend on fast-check and import it directly. The API remains; update custom generator and Random implementations for fast-check v4 typings." +"effect/FastCheck#string16bits": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Pass a char16bits-compatible arbitrary as the string unit." +"effect/FastCheck#stringOf": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Pass the former character arbitrary as the unit constraint." + example: "FastCheck.string({ ...constraints, unit: arbitrary })" +"effect/FastCheck#unicode": + replacement: "FastCheck.integer" + note: "Depend on fast-check and import it directly. Map BMP code points while excluding surrogate code points; prefer the binary string unit for full Unicode." +"effect/FastCheck#unicodeJson": + replacement: "FastCheck.json" + note: "Depend on fast-check and import it directly. Select binary or grapheme strings with stringUnit." + example: "FastCheck.json({ stringUnit: \"binary\" })" +"effect/FastCheck#UnicodeJsonSharedConstraints": + replacement: "FastCheck.JsonSharedConstraints" + note: "Depend on fast-check and import it directly. Unicode JSON generation was consolidated into JsonSharedConstraints.stringUnit." +"effect/FastCheck#unicodeJsonValue": + replacement: "FastCheck.jsonValue" + note: "Depend on fast-check and import it directly. Select binary or grapheme strings with stringUnit." + example: "FastCheck.jsonValue({ stringUnit: \"binary\" })" +"effect/FastCheck#unicodeString": + replacement: "FastCheck.string" + note: "Depend on fast-check and import it directly. Pass a BMP-code-point arbitrary as the unit constraint; prefer unit: binary for full Unicode." +"effect/FastCheck#uuidV": + replacement: "FastCheck.uuid" + note: "Depend on fast-check and import it directly. Specify the UUID version through constraints." + example: "FastCheck.uuid({ version: 4 })" diff --git a/migration/annotations/effect__Fiber.yaml b/migration/annotations/effect__Fiber.yaml new file mode 100644 index 000000000..659066b25 --- /dev/null +++ b/migration/annotations/effect__Fiber.yaml @@ -0,0 +1,144 @@ +effect/Fiber#all: + replacement: "Fiber.joinAll" + note: "Composite fibers were removed; join the iterable directly to obtain an Effect of all results." +effect/Fiber#await: + replacement: "Fiber.await" + note: "Unchanged; it returns an Effect containing the fiber Exit." +effect/Fiber#children: + replacement: "none" + note: "V4 fibers do not expose child-fiber enumeration; keep explicit handles in FiberSet or FiberMap when tracking is required." +effect/Fiber#done: + replacement: "Effect.runFork" + note: "Exit is an Effect in v4, so pass the Exit to Effect.runFork when a completed Fiber handle is required." +effect/Fiber#dumpAll: + replacement: "none" + note: "Fiber dump and global diagnostic APIs were removed; retain explicit fibers and inspect id and pollUnsafe where needed." +effect/Fiber#fail: + replacement: "Effect.runFork(Effect.fail(error))" + note: "Synthetic Fiber constructors were removed; run the corresponding Effect when a Fiber handle is required." +effect/Fiber#failCause: + replacement: "Effect.runFork(Effect.failCause(cause))" + note: "Synthetic Fiber constructors were removed; run the corresponding Effect when a Fiber handle is required." +effect/Fiber#Fiber: + replacement: "Fiber.Fiber" + note: "The v4 Fiber is the concrete runtime handle and is no longer itself an Effect; use Fiber.join or Fiber.await." +effect/Fiber#Fiber.Descriptor: + replacement: "none" + note: "Descriptors were removed; use Effect.withFiber for the current Fiber and read its id or runtime fields directly." +effect/Fiber#Fiber.Dump: + replacement: "none" + note: "Fiber dumps were removed; retain explicit Fiber handles and inspect their public runtime fields." +effect/Fiber#Fiber.Runtime: + replacement: "Fiber.Fiber" + note: "RuntimeFiber and the Fiber.Runtime alias were collapsed into the single v4 Fiber type." +effect/Fiber#Fiber.RuntimeVariance: + replacement: "Fiber.Variance" + note: "RuntimeFiber was collapsed into Fiber, leaving one variance encoding." +effect/Fiber#Fiber.Variance: + replacement: "Fiber.Variance" + note: "Retained as the variance encoding on the v4 Fiber interface." +effect/Fiber#FiberTypeId: + replacement: "Fiber.isFiber" + note: "The type-id symbol is private in v4; use the public Fiber.isFiber guard." +effect/Fiber#FiberUnify: + replacement: "none" + note: "Fiber no longer extends Effect, so its Effect unification helper was removed." +effect/Fiber#FiberUnifyIgnore: + replacement: "none" + note: "Fiber no longer extends Effect, so its Effect unification helper was removed." +effect/Fiber#fromEffect: + replacement: "Effect.runFork" + note: "V4 uses concrete runtime fibers; run the Effect directly, or keep using the Effect when no handle is needed." +effect/Fiber#getCurrentFiber: + replacement: "Fiber.getCurrent" + note: "Renamed and now returns Fiber | undefined synchronously instead of Option." +effect/Fiber#id: + replacement: "fiber.id" + note: "Fiber IDs are numbers exposed by the readonly id field." +effect/Fiber#inheritAll: + replacement: "none" + note: "FiberRef inheritance was removed with FiberRef; Context.Reference values are inherited through fiber context automatically." +effect/Fiber#interruptAsFork: + replacement: "fiber.interruptUnsafe(fiberId)" + note: "For fire-and-forget interruption use the immediate runtime hook; use Fiber.interruptAs when cleanup must be awaited." +effect/Fiber#interrupted: + replacement: "Effect.runFork(Exit.interrupt(fiberId))" + note: "Synthetic Fiber constructors were removed; Exit is an Effect and can be run to obtain an interrupted Fiber." +effect/Fiber#interruptFork: + replacement: "fiber.interruptUnsafe()" + note: "Use the immediate runtime hook for fire-and-forget interruption; Fiber.interrupt waits for cleanup." +effect/Fiber#isRuntimeFiber: + replacement: "Fiber.isFiber" + note: "All v4 Fiber values are concrete runtime fibers, so only the general guard remains." +effect/Fiber#map: + replacement: "Effect.runFork(Effect.map(Fiber.join(fiber), f))" + note: "Fiber transformation combinators were removed; transform its joined Effect and fork only if another handle is required." +effect/Fiber#mapEffect: + replacement: "Effect.runFork(Effect.flatMap(Fiber.join(fiber), f))" + note: "Fiber transformation combinators were removed; transform its joined Effect and fork only if another handle is required." +effect/Fiber#mapFiber: + replacement: "Effect.flatMap(Fiber.join(fiber), (a) => Fiber.join(f(a)))" + note: "Flatten through Fiber.join; fork the resulting Effect if another Fiber handle is required." +effect/Fiber#match: + replacement: "none" + note: "The virtual Fiber versus RuntimeFiber distinction no longer exists, so branch-specific matching is unnecessary." +effect/Fiber#never: + replacement: "Effect.runFork(Effect.never)" + note: "Synthetic Fiber constants were removed; run Effect.never when a never-completing Fiber is required." +effect/Fiber#Order: + replacement: "Order.mapInput(Order.Number, (fiber) => fiber.id)" + note: "The built-in Fiber order was removed; derive an order from the numeric id when ordering is actually required." +effect/Fiber#orElse: + replacement: "Effect.runFork(Effect.catchCause(Fiber.join(self), () => Fiber.join(that)))" + note: "Compose joined Effects and fork the result only if another Fiber handle is required." +effect/Fiber#orElseEither: + replacement: "Effect.catchCause" + note: "Compose Fiber.join Effects explicitly and map each successful branch to your own tagged union; Either was also removed in v4." +effect/Fiber#poll: + replacement: "fiber.pollUnsafe()" + note: "Polling is now synchronous and returns Exit | undefined; wrap in Effect.sync and Option.fromUndefinedOr if the old shape is required." +effect/Fiber#pretty: + replacement: "none" + note: "Runtime fiber pretty-printing was removed; format the public id and polled Exit explicitly." +effect/Fiber#roots: + replacement: "none" + note: "The runtime no longer exposes a global root-fiber registry; track application fibers explicitly." +effect/Fiber#RuntimeFiber: + replacement: "Fiber.Fiber" + note: "RuntimeFiber and Fiber were collapsed into the single v4 Fiber interface." +effect/Fiber#RuntimeFiberTypeId: + replacement: "Fiber.isFiber" + note: "The separate RuntimeFiber marker was removed; use the public Fiber guard." +effect/Fiber#RuntimeFiberUnify: + replacement: "none" + note: "RuntimeFiber was collapsed into Fiber, which no longer participates in Effect unification." +effect/Fiber#RuntimeFiberUnifyIgnore: + replacement: "none" + note: "RuntimeFiber was collapsed into Fiber, which no longer participates in Effect unification." +effect/Fiber#scoped: + replacement: "Fiber.runIn" + note: "Register the Fiber in an explicit Scope with Fiber.runIn; acquire the current Scope when migrating the old effectful form." +effect/Fiber#status: + replacement: "fiber.pollUnsafe()" + note: "FiberStatus was removed; undefined means not completed and an Exit means completed, with no public running/suspended distinction." +effect/Fiber#succeed: + replacement: "Effect.runFork(Effect.succeed(value))" + note: "Synthetic Fiber constructors were removed; run the corresponding Effect when a Fiber handle is required." +effect/Fiber#unsafeRoots: + replacement: "none" + note: "The runtime no longer exposes a global root-fiber registry; track application fibers explicitly." +effect/Fiber#void: + replacement: "Effect.runFork(Effect.void)" + note: "Synthetic Fiber constants were removed; run Effect.void when a completed Fiber is required." +effect/Fiber#zip: + replacement: "Effect.runFork(Effect.zip(Fiber.join(self), Fiber.join(that)))" + note: "Compose joined Effects and fork the result only if another Fiber handle is required." +effect/Fiber#zipLeft: + replacement: "Effect.runFork(Effect.map(Effect.zip(Fiber.join(self), Fiber.join(that)), ([left]) => left))" + note: "V4 has no Effect.zipLeft; zip joined Effects, project the left value, and fork only if another handle is required." +effect/Fiber#zipRight: + replacement: "Effect.runFork(Effect.map(Effect.zip(Fiber.join(self), Fiber.join(that)), ([, right]) => right))" + note: "V4 has no Effect.zipRight; zip joined Effects, project the right value, and fork only if another handle is required." +effect/Fiber#zipWith: + replacement: "Effect.runFork(Effect.zipWith(Fiber.join(self), Fiber.join(that), f))" + note: "Compose joined Effects and fork the result only if another Fiber handle is required." diff --git a/migration/annotations/effect__FiberHandle.yaml b/migration/annotations/effect__FiberHandle.yaml new file mode 100644 index 000000000..bd7bdb5c7 --- /dev/null +++ b/migration/annotations/effect__FiberHandle.yaml @@ -0,0 +1,15 @@ +effect/FiberHandle#FiberHandle: + replacement: "FiberHandle.FiberHandle" + note: "Retained; contained runtime fibers now use the unified Fiber type." +effect/FiberHandle#get: + replacement: "FiberHandle.get" + note: "Retained, but v4 returns Effect> instead of failing with NoSuchElementException when empty." +effect/FiberHandle#TypeId: + replacement: "FiberHandle.isFiberHandle" + note: "The type-id symbol is private in v4; use the public guard." +effect/FiberHandle#unsafeGet: + replacement: "FiberHandle.getUnsafe" + note: "Renamed to put the Unsafe suffix last." +effect/FiberHandle#unsafeSet: + replacement: "FiberHandle.setUnsafe" + note: "Renamed to put the Unsafe suffix last; the interruptAs option was removed because IDs are now numeric runtime details." diff --git a/migration/annotations/effect__FiberId.yaml b/migration/annotations/effect__FiberId.yaml new file mode 100644 index 000000000..91e0fc410 --- /dev/null +++ b/migration/annotations/effect__FiberId.yaml @@ -0,0 +1,63 @@ +effect/FiberId#combine: + replacement: "none" + note: "Composite FiberId values were removed; v4 uses a single numeric fiber id." +effect/FiberId#combineAll: + replacement: "none" + note: "Composite FiberId values were removed; v4 uses a single numeric fiber id." +effect/FiberId#composite: + replacement: "none" + note: "Composite FiberId values were removed; v4 uses a single numeric fiber id." +effect/FiberId#Composite: + replacement: "none" + note: "Composite FiberId values were removed; v4 uses a single numeric fiber id." +effect/FiberId#FiberId: + replacement: "number" + note: "V4 represents a fiber identity as the numeric Fiber.id field." +effect/FiberId#FiberIdTypeId: + replacement: "none" + note: "Fiber IDs are primitive numbers in v4 and have no type-id symbol." +effect/FiberId#getOrElse: + replacement: "fiberId ?? fallback" + note: "Represent absence as undefined when migrating code that previously used FiberId.none." +effect/FiberId#ids: + replacement: "new Set([fiberId])" + note: "A v4 fiber has one numeric id; composite-id flattening is no longer required." +effect/FiberId#isComposite: + replacement: "none" + note: "Composite FiberId values do not exist in v4." +effect/FiberId#isFiberId: + replacement: "Number.isNumber" + note: "Fiber IDs are primitive numbers in v4." +effect/FiberId#isNone: + replacement: "fiberId === undefined" + note: "Use undefined for an absent optional interruptor id; there is no sentinel FiberId.none." +effect/FiberId#isRuntime: + replacement: "Number.isNumber" + note: "Every v4 fiber id is a runtime numeric id." +effect/FiberId#make: + replacement: "id" + note: "Use the numeric id directly; startTimeSeconds is no longer part of fiber identity." +effect/FiberId#none: + replacement: "undefined" + note: "Optional interruptor IDs use undefined rather than a sentinel FiberId value." +effect/FiberId#None: + replacement: "undefined" + note: "Optional interruptor IDs use undefined rather than a sentinel FiberId type." +effect/FiberId#runtime: + replacement: "id" + note: "Use the numeric id directly; startTimeMillis is no longer part of fiber identity." +effect/FiberId#Runtime: + replacement: "number" + note: "Runtime fiber IDs are primitive numbers in v4." +effect/FiberId#Single: + replacement: "number | undefined" + note: "Use a number, with undefined only where the old None case was meaningful." +effect/FiberId#threadName: + replacement: "String(fiberId)" + note: "There is no built-in thread-name formatter; format the numeric id at the presentation boundary." +effect/FiberId#toSet: + replacement: "new Set([fiberId])" + note: "A v4 fiber has one numeric id, so composite-id flattening is unnecessary." +effect/FiberId#unsafeMake: + replacement: "none" + note: "There is no public fiber-id allocator; obtain the current id with Effect.fiberId or from Fiber.id." diff --git a/migration/annotations/effect__FiberMap.yaml b/migration/annotations/effect__FiberMap.yaml new file mode 100644 index 000000000..dd3039256 --- /dev/null +++ b/migration/annotations/effect__FiberMap.yaml @@ -0,0 +1,15 @@ +effect/FiberMap#FiberMap: + replacement: "FiberMap.FiberMap" + note: "Retained; contained runtime fibers now use the unified Fiber type." +effect/FiberMap#TypeId: + replacement: "FiberMap.isFiberMap" + note: "The type-id symbol is private in v4; use the public guard." +effect/FiberMap#unsafeGet: + replacement: "FiberMap.getUnsafe" + note: "Renamed to put the Unsafe suffix last." +effect/FiberMap#unsafeHas: + replacement: "FiberMap.hasUnsafe" + note: "Renamed to put the Unsafe suffix last." +effect/FiberMap#unsafeSet: + replacement: "FiberMap.setUnsafe" + note: "Renamed to put the Unsafe suffix last; the interruptAs option was removed because IDs are now numeric runtime details." diff --git a/migration/annotations/effect__FiberRef.yaml b/migration/annotations/effect__FiberRef.yaml new file mode 100644 index 000000000..403eb277f --- /dev/null +++ b/migration/annotations/effect__FiberRef.yaml @@ -0,0 +1,144 @@ +effect/FiberRef#currentConcurrency: + replacement: "none" + note: "Inherited concurrency was removed; pass concurrency explicitly to each v4 combinator that supports it." +effect/FiberRef#currentContext: + replacement: "Effect.context" + note: "Fiber services are stored directly in Context; use Effect.context to read them and Effect.provideContext to override them." +effect/FiberRef#currentLogAnnotations: + replacement: "References.CurrentLogAnnotations" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentLoggers: + replacement: "References.CurrentLoggers" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentLogLevel: + replacement: "References.CurrentLogLevel" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentLogSpan: + replacement: "References.CurrentLogSpans" + note: "Renamed and represented as a Context.Reference containing a readonly span array." +effect/FiberRef#currentMaxOpsBeforeYield: + replacement: "Scheduler.MaxOpsBeforeYield" + note: "The scheduler setting is now a Context.Reference; yield it or provide it with Effect.provideService." +effect/FiberRef#currentMetricLabels: + replacement: "Metric.CurrentMetricAttributes" + note: "Metric labels became metric attributes stored in a Context.Reference." +effect/FiberRef#currentMinimumLogLevel: + replacement: "References.MinimumLogLevel" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentRequestBatchingEnabled: + replacement: "none" + note: "The request batching FiberRef was removed; batching is defined by the v4 RequestResolver runAll implementation." +effect/FiberRef#currentRequestCache: + replacement: "RequestResolver.withCache" + note: "The ambient request cache was removed; wrap a RequestResolver with an explicit bounded cache." +effect/FiberRef#currentRequestCacheEnabled: + replacement: "RequestResolver.withCache" + note: "There is no ambient cache toggle; choose an explicitly cached or uncached RequestResolver." +effect/FiberRef#currentRuntimeFlags: + replacement: "none" + note: "RuntimeFlags and their FiberRef were removed; use specific v4 runtime options such as interruptibility and scheduler settings." +effect/FiberRef#currentScheduler: + replacement: "Scheduler.Scheduler" + note: "The scheduler is now a Context.Reference; yield it or provide it with Effect.provideService." +effect/FiberRef#currentSchedulingPriority: + replacement: "none" + note: "The ambient scheduling-priority FiberRef was removed; use explicit scheduler operations where priority is needed." +effect/FiberRef#currentSupervisor: + replacement: "none" + note: "The Supervisor and ambient supervisor FiberRef APIs were removed; track fibers explicitly with FiberSet or FiberMap." +effect/FiberRef#currentTracerEnabled: + replacement: "References.TracerEnabled" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentTracerSpanAnnotations: + replacement: "References.TracerSpanAnnotations" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentTracerSpanLinks: + replacement: "References.TracerSpanLinks" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#currentTracerTimingEnabled: + replacement: "References.TracerTimingEnabled" + note: "Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService." +effect/FiberRef#delete: + replacement: "Effect.provideService" + note: "Context.Reference has no in-place delete; scope the default or desired value around the target Effect." +effect/FiberRef#FiberRef: + replacement: "Context.Reference" + note: "Fiber-local values and services share Context.Reference in v4; references have a defaultValue and no fork/join patching." +effect/FiberRef#FiberRefTypeId: + replacement: "Context.isReference" + note: "Use the public Context.Reference guard instead of a FiberRef type-id symbol." +effect/FiberRef#FiberRefUnify: + replacement: "none" + note: "Context.Reference is a service key and does not require the old FiberRef Effect-unification helper." +effect/FiberRef#FiberRefUnifyIgnore: + replacement: "none" + note: "Context.Reference is a service key and does not require the old FiberRef Effect-unification helper." +effect/FiberRef#get: + replacement: "reference" + note: "Context.Reference is yieldable as a service; yield it directly to read the current value." +effect/FiberRef#getAndUpdateSome: + replacement: "Ref.getAndUpdateSome" + note: "Use Ref for mutable state; for fiber-local configuration compute the value first and scope it with Effect.provideService." +effect/FiberRef#getWith: + replacement: "Effect.flatMap(reference, f)" + note: "Yield or flatMap the Context.Reference directly." +effect/FiberRef#interruptedCause: + replacement: "none" + note: "The pending interruption cause is no longer exposed as public fiber-local state; inspect completed failure Causes from Fiber.await." +effect/FiberRef#make: + replacement: "Context.Reference" + note: "Define a stable Context.Reference key with defaultValue; custom fork and join behavior is not supported." +effect/FiberRef#makeContext: + replacement: "Context.Reference" + note: "Define a Context.Reference whose defaultValue returns the Context; custom context diffing is no longer required." +effect/FiberRef#makeRuntimeFlags: + replacement: "none" + note: "RuntimeFlags and specialized FiberRef constructors were removed; migrate each flag to its explicit v4 runtime option." +effect/FiberRef#makeWith: + replacement: "Context.Reference" + note: "Use the lazy defaultValue option on a stable Context.Reference key." +effect/FiberRef#modify: + replacement: "Ref.modify" + note: "Use Ref for mutable state; Context.Reference updates are scoped with Effect.provideService rather than mutated in place." +effect/FiberRef#modifySome: + replacement: "Ref.modifySome" + note: "Use Ref for mutable state; Context.Reference updates are scoped with Effect.provideService rather than mutated in place." +effect/FiberRef#reset: + replacement: "Effect.provideService" + note: "Context.Reference has no in-place reset; scope its default value around the target Effect." +effect/FiberRef#set: + replacement: "Effect.provideService" + note: "Context.Reference values are overridden for an Effect scope instead of mutating the current fiber." +effect/FiberRef#unhandledErrorLogLevel: + replacement: "References.UnhandledLogLevel" + note: "Renamed and represented as a Context.Reference using Severity | undefined instead of Option." +effect/FiberRef#unsafeMake: + replacement: "Context.Reference" + note: "Context.Reference construction is synchronous; provide a stable identifier and defaultValue." +effect/FiberRef#unsafeMakeContext: + replacement: "Context.Reference" + note: "Define a Context.Reference whose defaultValue returns the Context; there is no specialized unsafe constructor." +effect/FiberRef#unsafeMakeHashSet: + replacement: "Context.Reference" + note: "Define a normal Context.Reference with a readonly set default; specialized differ constructors were removed." +effect/FiberRef#unsafeMakePatch: + replacement: "Context.Reference" + note: "Define a normal Context.Reference; custom Differ, fork patches, and join behavior are not supported in v4." +effect/FiberRef#unsafeMakeSupervisor: + replacement: "none" + note: "Supervisor and FiberRef were removed; track managed fibers explicitly with FiberSet or FiberMap." +effect/FiberRef#update: + replacement: "Ref.update" + note: "Use Ref for mutable state; for fiber-local configuration compute a value and scope it with Effect.provideService." +effect/FiberRef#updateSome: + replacement: "Ref.updateSome" + note: "Use Ref for mutable state; for fiber-local configuration compute a value and scope it with Effect.provideService." +effect/FiberRef#updateSomeAndGet: + replacement: "Ref.updateSomeAndGet" + note: "Use Ref for mutable state; for fiber-local configuration compute a value and scope it with Effect.provideService." +effect/FiberRef#Variance: + replacement: "Context.Reference" + note: "The FiberRef-specific variance interface was removed with FiberRef." +effect/FiberRef#versionMismatchErrorLogLevel: + replacement: "none" + note: "The version-mismatch logging FiberRef was removed and no public v4 Context.Reference replaces it." diff --git a/migration/annotations/effect__FiberRefs.yaml b/migration/annotations/effect__FiberRefs.yaml new file mode 100644 index 000000000..63a8ce255 --- /dev/null +++ b/migration/annotations/effect__FiberRefs.yaml @@ -0,0 +1,39 @@ +effect/FiberRefs#delete: + replacement: "Context.omit" + note: "FiberRefs became fiber Context; omit a Reference key when constructing the replacement Context." +effect/FiberRefs#empty: + replacement: "Context.empty" + note: "Use an empty Context as the starting collection of services and reference overrides." +effect/FiberRefs#fiberRefs: + replacement: "none" + note: "Context does not expose public enumeration of its Reference keys; retain the keys explicitly if enumeration is required." +effect/FiberRefs#FiberRefs: + replacement: "Context.Context" + note: "Fiber-local services and reference overrides are stored directly in the Fiber context in v4." +effect/FiberRefs#FiberRefsSym: + replacement: "none" + note: "FiberRefs and its marker symbol were removed." +effect/FiberRefs#forkAs: + replacement: "none" + note: "Context is inherited automatically when a v4 child fiber is forked; custom per-reference fork patches were removed." +effect/FiberRefs#get: + replacement: "Context.getOption" + note: "Read the service as an Option. Context.Reference defaults also produce Some; use Context.getOrUndefined when only stored overrides should count." +effect/FiberRefs#getOrDefault: + replacement: "Context.get" + note: "Reads an override or the Context.Reference default value." +effect/FiberRefs#joinAs: + replacement: "none" + note: "Child-to-parent FiberRef joining was removed; pass results explicitly or merge ordinary Context values where appropriate." +effect/FiberRefs#setAll: + replacement: "Effect.provideContext" + note: "Provide the replacement Context around the Effect that should observe its services and reference overrides." +effect/FiberRefs#unsafeMake: + replacement: "Context.empty().pipe(Context.add(...))" + note: "Build a Context from explicit Reference keys and values; FiberId histories and unsafe local maps no longer exist." +effect/FiberRefs#updateAs: + replacement: "Context.add" + note: "Add or replace a Reference value in Context; the FiberId parameter and history are removed." +effect/FiberRefs#updateManyAs: + replacement: "Context.add" + note: "Apply explicit Context.add calls for each Reference value; FiberId histories and forkAs are removed." diff --git a/migration/annotations/effect__FiberRefsPatch.yaml b/migration/annotations/effect__FiberRefsPatch.yaml new file mode 100644 index 000000000..4ba815541 --- /dev/null +++ b/migration/annotations/effect__FiberRefsPatch.yaml @@ -0,0 +1,24 @@ +effect/FiberRefsPatch#Add: + replacement: "Context.add" + note: "FiberRefsPatch was removed; apply Reference overrides directly to Context." +effect/FiberRefsPatch#AndThen: + replacement: "Context.merge" + note: "FiberRefsPatch was removed; compose Context updates directly, with later values overriding earlier ones." +effect/FiberRefsPatch#combine: + replacement: "Context.merge" + note: "FiberRefsPatch was removed; merge the resulting Context values instead of combining patches." +effect/FiberRefsPatch#diff: + replacement: "none" + note: "There is no generic Context diff because FiberRef fork and join patch semantics were removed." +effect/FiberRefsPatch#empty: + replacement: "Context.empty" + note: "Use an empty Context when no services or Reference overrides are applied." +effect/FiberRefsPatch#Empty: + replacement: "Context.Context" + note: "The empty patch model was removed; an empty Context represents no overrides." +effect/FiberRefsPatch#FiberRefsPatch: + replacement: "none" + note: "The patch data type was removed with FiberRefs; construct or merge Context values directly." +effect/FiberRefsPatch#patch: + replacement: "Context.merge" + note: "Merge explicit Context overrides into the base Context; FiberId-aware patch application no longer exists." diff --git a/migration/annotations/effect__FiberSet.yaml b/migration/annotations/effect__FiberSet.yaml new file mode 100644 index 000000000..ec0d81253 --- /dev/null +++ b/migration/annotations/effect__FiberSet.yaml @@ -0,0 +1,9 @@ +effect/FiberSet#FiberSet: + replacement: "FiberSet.FiberSet" + note: "Retained; contained runtime fibers now use the unified Fiber type." +effect/FiberSet#TypeId: + replacement: "FiberSet.isFiberSet" + note: "The type-id symbol is private in v4; use the public guard." +effect/FiberSet#unsafeAdd: + replacement: "FiberSet.addUnsafe" + note: "Renamed to put the Unsafe suffix last; the interruptAs option was removed because IDs are now numeric runtime details." diff --git a/migration/annotations/effect__FiberStatus.yaml b/migration/annotations/effect__FiberStatus.yaml new file mode 100644 index 000000000..752128b4f --- /dev/null +++ b/migration/annotations/effect__FiberStatus.yaml @@ -0,0 +1,33 @@ +effect/FiberStatus#Done: + replacement: "Exit.Exit" + note: "FiberStatus was removed; a defined fiber.pollUnsafe() result indicates completion and contains the Exit." +effect/FiberStatus#FiberStatus: + replacement: "Exit.Exit | undefined" + note: "Use fiber.pollUnsafe(); undefined means incomplete and Exit means completed, with no running/suspended distinction." +effect/FiberStatus#FiberStatusTypeId: + replacement: "none" + note: "FiberStatus and its type-id symbol were removed." +effect/FiberStatus#isDone: + replacement: "fiber.pollUnsafe() !== undefined" + note: "Completion is observable by synchronously polling the Fiber." +effect/FiberStatus#isFiberStatus: + replacement: "none" + note: "FiberStatus values no longer exist; inspect a Fiber with pollUnsafe instead." +effect/FiberStatus#isRunning: + replacement: "fiber.pollUnsafe() === undefined" + note: "V4 only exposes incomplete versus completed; it does not distinguish running from suspended." +effect/FiberStatus#isSuspended: + replacement: "none" + note: "The public runtime no longer exposes suspended status." +effect/FiberStatus#running: + replacement: "none" + note: "FiberStatus constructors were removed; keep the Fiber and poll it instead." +effect/FiberStatus#Running: + replacement: "none" + note: "The public runtime no longer models running status as a value." +effect/FiberStatus#suspended: + replacement: "none" + note: "FiberStatus constructors and public suspended status were removed." +effect/FiberStatus#Suspended: + replacement: "none" + note: "The public runtime no longer models suspended status as a value." diff --git a/migration/annotations/effect__Function.yaml b/migration/annotations/effect__Function.yaml new file mode 100644 index 000000000..b2c548335 --- /dev/null +++ b/migration/annotations/effect__Function.yaml @@ -0,0 +1,12 @@ +"effect/Function#FunctionN": + replacement: "Function.FunctionN" + note: "No call-site migration; v4 keeps the same function shape as a type alias." +"effect/Function#isFunction": + replacement: "Predicate.isFunction" + note: "The function refinement moved to Predicate." +"effect/Function#LazyArg": + replacement: "Function.LazyArg" + note: "No call-site migration; v4 keeps the same lazy function shape as a type alias." +"effect/Function#unsafeCoerce": + replacement: "Function.cast" + note: "Renamed type-only cast; runtime behavior remains identity with no validation." diff --git a/migration/annotations/effect__GlobalValue.yaml b/migration/annotations/effect__GlobalValue.yaml new file mode 100644 index 000000000..414a1bbe4 --- /dev/null +++ b/migration/annotations/effect__GlobalValue.yaml @@ -0,0 +1,3 @@ +"effect/GlobalValue#globalValue": + replacement: "module-scoped const" + note: "The global registry helper was removed; use a module singleton, or explicitly own a globalThis and Symbol.for registry when cross-bundle identity is required." diff --git a/migration/annotations/effect__Graph.yaml b/migration/annotations/effect__Graph.yaml new file mode 100644 index 000000000..1f58ee52e --- /dev/null +++ b/migration/annotations/effect__Graph.yaml @@ -0,0 +1,15 @@ +"effect/Graph#Edge": + replacement: "Graph.Edge" + note: "The type remains as a structural interface, but its Data.Class constructor/value export was removed. Replace new Graph.Edge({ source, target, data }) with an object literal." +"effect/Graph#Graph": + replacement: "Graph.Graph" + note: "The immutable type remains, but storage is opaque; replace field access with Graph nodes, edges, count, lookup, neighbor, and acyclicity APIs." +"effect/Graph#MutableGraph": + replacement: "Graph.MutableGraph" + note: "The mutable type remains but no longer shares a public base interface with immutable Graph; obtain it through Graph.mutate or Graph.beginMutation and use public mutation/query functions." +"effect/Graph#Proto": + replacement: "none" + note: "The common graph protocol was removed. Use Graph.Graph or Graph.MutableGraph as appropriate and replace storage-field access with public graph query and mutation functions." +"effect/Graph#SearchConfig": + replacement: "Graph.SearchConfig" + note: "The type remains; direction is now Graph.TraversalDirection and also accepts undirected, while radius limits traversal depth." diff --git a/migration/annotations/effect__GroupBy.yaml b/migration/annotations/effect__GroupBy.yaml new file mode 100644 index 000000000..a98cb79e9 --- /dev/null +++ b/migration/annotations/effect__GroupBy.yaml @@ -0,0 +1,27 @@ +effect/GroupBy#GroupBy: + replacement: "Stream]>" + note: "The GroupBy datatype is removed in v4; Stream.groupBy/groupByKey now return an ordinary Stream of readonly [key, substream] pairs, processed with regular Stream operators." +effect/GroupBy#GroupBy.Variance: + replacement: "none" + note: "Variance plumbing for the removed GroupBy datatype; v4 has no GroupBy type, so there is no variance interface to migrate to." +effect/GroupBy#GroupByTypeId: + replacement: "none" + note: "Brand symbol for the removed GroupBy datatype; v4 groupBy results are plain Streams, discriminated with Stream.isStream if needed." +effect/GroupBy#evaluate: + replacement: "Stream.flatMap" + note: "Apply the per-group function over the [key, stream] pairs with Stream.flatMap (or Stream.mapEffect for an effectful result per group), using { concurrency: \"unbounded\" } to reproduce v3's parallel-groups/arbitrary-merge-order behavior; the v3 bufferSize option moved onto Stream.groupBy itself." + example: | + // v3: stream.pipe(Stream.groupByKey(f), GroupBy.evaluate((key, s) => g(key, s))) + stream.pipe( + Stream.groupByKey(f), + Stream.flatMap(([key, s]) => g(key, s), { concurrency: "unbounded" }) + ) +effect/GroupBy#filter: + replacement: "Stream.filter" + note: "Filter the groups by key with an ordinary Stream.filter on the pairs: Stream.filter(([key]) => predicate(key))." +effect/GroupBy#first: + replacement: "Stream.take" + note: "Keep only the first n groups with an ordinary Stream.take(n) on the [key, stream] pair stream." +effect/GroupBy#make: + replacement: "none" + note: "No wrapper to construct in v4: a grouped stream is just any Stream]>, so build the pair stream directly (Stream.groupBy/groupByKey produce it); the v3 shape Stream<[K, Dequeue>]> is gone along with the queue-of-Take encoding." diff --git a/migration/annotations/effect__Hash.yaml b/migration/annotations/effect__Hash.yaml new file mode 100644 index 000000000..69f676887 --- /dev/null +++ b/migration/annotations/effect__Hash.yaml @@ -0,0 +1,3 @@ +"effect/Hash#cached": + replacement: "none" + note: "Delete Hash.cached wrappers and return the computed value from Hash.symbol; Hash.hash now caches objects automatically in a private WeakMap without mutating them." diff --git a/migration/annotations/effect__HashMap.yaml b/migration/annotations/effect__HashMap.yaml new file mode 100644 index 000000000..e35bc6fa9 --- /dev/null +++ b/migration/annotations/effect__HashMap.yaml @@ -0,0 +1,15 @@ +"effect/HashMap#countBy": + replacement: "HashMap.reduce" + note: "Count matches with HashMap.reduce(self, 0, (count, value, key) => count + (predicate(value, key) ? 1 : 0))." +"effect/HashMap#HashMap": + replacement: "HashMap.HashMap" + note: "The immutable two-parameter model remains; use public operations rather than depending on its representation." +"effect/HashMap#keySet": + replacement: "HashSet.fromIterable + HashMap.keys" + note: "Construct the set with HashSet.fromIterable(HashMap.keys(self)); no direct keySet helper remains." +"effect/HashMap#TypeId": + replacement: "HashMap.isHashMap" + note: "The brand is private; use HashMap.isHashMap for runtime refinement and HashMap.HashMap in type positions." +"effect/HashMap#unsafeGet": + replacement: "HashMap.getUnsafe" + note: "Direct word-order rename; it still throws for a missing key." diff --git a/migration/annotations/effect__HashSet.yaml b/migration/annotations/effect__HashSet.yaml new file mode 100644 index 000000000..adaa83fd2 --- /dev/null +++ b/migration/annotations/effect__HashSet.yaml @@ -0,0 +1,33 @@ +"effect/HashSet#beginMutation": + replacement: "none" + note: "Transient mutation mode was removed; reassign immutable add/remove results or build a batch with HashSet.fromIterable." +"effect/HashSet#endMutation": + replacement: "none" + note: "There is no mutation window to finalize; remove this call and use the latest immutable HashSet value." +"effect/HashSet#flatMap": + replacement: "HashSet.fromIterable + Iterable.flatMap" + note: "Preserve set deduplication with HashSet.fromIterable(Iterable.flatMap(self, f)); no direct flatMap remains." +"effect/HashSet#forEach": + replacement: "Iterable.forEach" + note: "HashSet remains Iterable, so Iterable.forEach(self, f) preserves eager side-effecting traversal." +"effect/HashSet#HashSet": + replacement: "HashSet.HashSet" + note: "The immutable model remains, but the brand is private and transient mutation helpers were removed." +"effect/HashSet#partition": + replacement: "HashSet.filter" + note: "Build [excluded, satisfying] with complementary HashSet.filter calls, or use one reduction when the predicate is expensive." +"effect/HashSet#toggle": + replacement: "HashSet.has + HashSet.remove / HashSet.add" + note: "Use HashSet.has(self, value) ? HashSet.remove(self, value) : HashSet.add(self, value)." +"effect/HashSet#toValues": + replacement: "Array.from" + note: "HashSet remains iterable; Array.from(self) produces the former Array result." +"effect/HashSet#TypeId": + replacement: "HashSet.isHashSet" + note: "The brand is private; use HashSet.isHashSet for runtime refinement and HashSet.HashSet in type positions." +"effect/HashSet#values": + replacement: "none" + note: "The HashSet itself is iterable; iterate it directly or call self[Symbol.iterator]() when an iterator object is required." +"effect/HashSet#mutate": + replacement: "none" + note: "Transient mutation was removed; reassign immutable HashSet.add/remove results or build a complete replacement with HashSet.fromIterable." diff --git a/migration/annotations/effect__Inspectable.yaml b/migration/annotations/effect__Inspectable.yaml new file mode 100644 index 000000000..3d3ae012b --- /dev/null +++ b/migration/annotations/effect__Inspectable.yaml @@ -0,0 +1,15 @@ +"effect/Inspectable#format": + replacement: "Formatter.formatJson" + note: "Use Formatter.formatJson(input, { space: 2 }) to preserve the v3 helper's pretty-printed JSON output." +"effect/Inspectable#redact": + replacement: "Redactable.redact" + note: "The redaction protocol moved to Redactable and now receives the current fiber Context." +"effect/Inspectable#stringifyCircular": + replacement: "Formatter.formatJson" + note: "Use Formatter.formatJson(input, { space: whitespace }); it handles redaction and ancestor cycles." +"effect/Inspectable#toJSON": + replacement: "Inspectable.toJson" + note: "Renamed to lower-camel toJson with the same recursive conversion role." +"effect/Inspectable#withRedactableContext": + replacement: "none" + note: "Manual FiberRefs scoping was removed; Redactable.redact uses the current fiber Context automatically." diff --git a/migration/annotations/effect__Iterable.yaml b/migration/annotations/effect__Iterable.yaml new file mode 100644 index 000000000..b25d8798c --- /dev/null +++ b/migration/annotations/effect__Iterable.yaml @@ -0,0 +1,12 @@ +"effect/Iterable#flatMapNullable": + replacement: "Iterable.flatMapNullishOr" + note: "Direct nullish-terminology rename; it remains lazy and drops null or undefined mapper results." +"effect/Iterable#getLefts": + replacement: "Iterable.getFailures" + note: "Either became Result; this lazily extracts failure payloads." +"effect/Iterable#getRights": + replacement: "Iterable.getSuccesses" + note: "Either became Result; this lazily extracts success payloads." +"effect/Iterable#unsafeHead": + replacement: "Iterable.headUnsafe" + note: "Direct word-order rename; it still throws on an empty Iterable." diff --git a/migration/annotations/effect__JSONSchema.yaml b/migration/annotations/effect__JSONSchema.yaml new file mode 100644 index 000000000..34d61b8f0 --- /dev/null +++ b/migration/annotations/effect__JSONSchema.yaml @@ -0,0 +1,71 @@ +"effect/JSONSchema#fromAST": + replacement: "Schema.toJsonSchemaDocument" + note: "Wrap a low-level AST with Schema.make, then generate a document; v4 generation targets draft 2020-12." + example: "Schema.toJsonSchemaDocument(Schema.make(ast))" +"effect/JSONSchema#JsonSchema7": + replacement: "JsonSchema.JsonSchema" + note: "The draft-07-specific union was replaced by the dialect-neutral JSON Schema model." +"effect/JSONSchema#JsonSchema7Any": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7AnyOf": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Array": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Boolean": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7empty": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Enum": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Enums": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Integer": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Never": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Null": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Number": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Numeric": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7object": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Object": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Ref": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7String": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Unknown": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Void": + replacement: "JsonSchema.JsonSchema" + note: "Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema." +"effect/JSONSchema#JsonSchema7Root": + replacement: "JsonSchema.Document<\"draft-07\">" + note: "Use a typed JSON Schema document for a draft-07 root and definitions." +"effect/JSONSchema#JsonSchemaAnnotations": + replacement: "Schema.Annotations.Documentation" + note: "Schema metadata now uses string-keyed Schema annotations; JSON Schema-specific checks use toJsonSchema annotations." +"effect/JSONSchema#make": + replacement: "Schema.toJsonSchemaDocument" + note: "Generate draft 2020-12, then call JsonSchema.toDocumentDraft07 when draft-07 output is required." + example: "JsonSchema.toDocumentDraft07(Schema.toJsonSchemaDocument(schema))" diff --git a/migration/annotations/effect__KeyedPool.yaml b/migration/annotations/effect__KeyedPool.yaml new file mode 100644 index 000000000..f3b27dc52 --- /dev/null +++ b/migration/annotations/effect__KeyedPool.yaml @@ -0,0 +1,27 @@ +effect/KeyedPool#get: + replacement: "RcMap.get + Pool.get" + note: "KeyedPool was removed; acquire the per-key Pool from an RcMap, then borrow an item with Pool.get in the current Scope." +effect/KeyedPool#invalidate: + replacement: "RcMap.get + Pool.invalidate" + note: "KeyedPool was removed; retain the key, get its Pool from RcMap, and call Pool.invalidate for the item." +effect/KeyedPool#KeyedPool: + replacement: "RcMap.RcMap>" + note: "Model keyed pools as an RcMap whose scoped lookup creates one Pool per key." +effect/KeyedPool#KeyedPool.Variance: + replacement: "none" + note: "KeyedPool and its variance marker were removed; use the RcMap and Pool public models without depending on branding internals." +effect/KeyedPool#KeyedPoolTypeId: + replacement: "none" + note: "KeyedPool was removed, so its runtime type id has no v4 equivalent." +effect/KeyedPool#make: + replacement: "RcMap.make + Pool.make" + note: "Create an RcMap with lookup key => Pool.make({ acquire: acquire(key), size }); RcMap.get followed by Pool.get replaces keyed borrowing." +effect/KeyedPool#makeWith: + replacement: "RcMap.make + Pool.make" + note: "Create an RcMap whose lookup uses Pool.make with size: size(key)." +effect/KeyedPool#makeWithTTL: + replacement: "RcMap.make + Pool.makeWithTTL" + note: "Create an RcMap whose lookup uses Pool.makeWithTTL with min(key), max(key), and the shared timeToLive." +effect/KeyedPool#makeWithTTLBy: + replacement: "RcMap.make + Pool.makeWithTTL" + note: "Create an RcMap whose lookup uses Pool.makeWithTTL with min(key), max(key), and timeToLive(key)." diff --git a/migration/annotations/effect__Layer.yaml b/migration/annotations/effect__Layer.yaml new file mode 100644 index 000000000..925c52e31 --- /dev/null +++ b/migration/annotations/effect__Layer.yaml @@ -0,0 +1,192 @@ +"effect/Layer#annotateLogs": + replacement: "Layer.fromBuild((memoMap, scope) => Effect.annotateLogs(Layer.buildWithMemoMap(self, memoMap, scope), ...annotations))" + note: "Apply Effect.annotateLogs to the layer acquisition effect." +"effect/Layer#annotateSpans": + replacement: "Layer.fromBuild((memoMap, scope) => Effect.annotateSpans(Layer.buildWithMemoMap(self, memoMap, scope), ...annotations))" + note: "Apply Effect.annotateSpans to the layer acquisition effect." +"effect/Layer#catchAll": + replacement: "Layer.catch" + note: "The typed-error handler was renamed." +"effect/Layer#catchAllCause": + replacement: "Layer.catchCause" + note: "The cause handler was renamed." +"effect/Layer#context": + replacement: "Layer.effectContext(Effect.context())" + note: "Capture and return the current service context." +"effect/Layer#CurrentMemoMap": + replacement: "Layer.CurrentMemoMap" + note: "The service remains but is now a Context.Service class with forkOrCreate." +"effect/Layer#die": + replacement: "Layer.unwrap(Effect.die(defect))" + note: "Lift the Effect defect constructor." +"effect/Layer#dieSync": + replacement: "Layer.unwrap(Effect.suspend(() => Effect.die(evaluate())))" + note: "Suspend evaluation and lift Effect.die; Effect.dieSync was also removed." +"effect/Layer#discard": + replacement: "Layer.flatMap(self, () => Layer.empty)" + note: "Build the layer while dropping its output context." +"effect/Layer#ensureErrorType": + replacement: "Layer.satisfiesErrorType" + note: "The type constraint helper was renamed." +"effect/Layer#ensureRequirementsType": + replacement: "Layer.satisfiesServicesType" + note: "The requirements type constraint was renamed to services." +"effect/Layer#ensureSuccessType": + replacement: "Layer.satisfiesSuccessType" + note: "The type constraint helper was renamed." +"effect/Layer#extendScope": + replacement: "Layer.buildWithScope(self, outerScope) and Effect.provideContext(program, context)" + note: "Explicitly build against the desired outer scope and provide the resulting context." +"effect/Layer#fail": + replacement: "Layer.unwrap(Effect.fail(error))" + note: "Lift the Effect failure constructor." +"effect/Layer#failCause": + replacement: "Layer.unwrap(Effect.failCause(cause))" + note: "Lift the Effect cause-failure constructor." +"effect/Layer#failCauseSync": + replacement: "Layer.unwrap(Effect.failCauseSync(evaluate))" + note: "Lift the retained Effect constructor." +"effect/Layer#failSync": + replacement: "Layer.unwrap(Effect.failSync(evaluate))" + note: "Lift the retained Effect constructor." +"effect/Layer#fiberRefLocallyScopedWith": + replacement: "Layer.effect(reference, Effect.map(reference, f))" + note: "FiberRef was removed; compute and provide a transformed Context.Reference value." +"effect/Layer#flatten": + replacement: "Layer.flatMap(self, (context) => Context.get(context, key))" + note: "Expand the removed convenience combinator with flatMap and Context.get." +"effect/Layer#function": + replacement: "Layer.effect(keyB, Effect.map(keyA, f))" + note: "Read the input service through its Context.Key and provide the transformed service." +"effect/Layer#isFresh": + replacement: "none" + note: "Layer.fresh remains, but its wrapper has no public freshness predicate." +"effect/Layer#Layer": + replacement: "Layer.Layer" + note: "The type remains with Layer parameter order." +"effect/Layer#Layer.Context": + replacement: "Layer.Services" + note: "The input-services extractor moved to the module level and was renamed." +"effect/Layer#LayerTypeId": + replacement: "none" + note: "The marker is private in v4; use Layer.Any or Layer.Variance for type constraints." +"effect/Layer#locally": + replacement: "Layer.updateService(self, reference, () => value)" + note: "Replace FiberRef-local configuration with Context.Reference provision." +"effect/Layer#locallyEffect": + replacement: "Layer.fromBuild((memoMap, scope) => f(Layer.buildWithMemoMap(self, memoMap, scope)))" + note: "Transform the public layer acquisition effect directly." +"effect/Layer#locallyScoped": + replacement: "Layer.succeed(reference, value)" + note: "Provide a v4 Context.Reference value as a configuration layer." +"effect/Layer#locallyWith": + replacement: "Layer.updateService(self, reference, f)" + note: "Transform a Context.Reference during layer acquisition." +"effect/Layer#map": + replacement: "Layer.flatMap(self, (context) => Layer.succeedContext(f(context)))" + note: "Expand the removed output-context mapping combinator." +"effect/Layer#mapError": + replacement: "Layer.fromBuild((memoMap, scope) => Effect.mapError(Layer.buildWithMemoMap(self, memoMap, scope), f))" + note: "Transform the typed error of layer acquisition." +"effect/Layer#match": + replacement: "Layer.fromBuild with Effect.matchEffect over Layer.buildWithMemoMap" + note: "Fold the source acquisition effect, then build the selected failure or success layer." +"effect/Layer#matchCause": + replacement: "Layer.fromBuild with Effect.matchCauseEffect over Layer.buildWithMemoMap" + note: "Fold the source acquisition cause, then build the selected failure or success layer." +"effect/Layer#memoize": + replacement: "automatic shared memoization under Effect.provide" + note: "Reuse the same Layer value; use { local: true } or Layer.fresh to opt out, or MemoMap APIs for manual control." +"effect/Layer#MemoMap": + replacement: "Layer.MemoMap" + note: "The interface remains and now supports parent-child ambient maps." +"effect/Layer#MemoMapTypeId": + replacement: "none" + note: "The MemoMap marker is private in v4." +"effect/Layer#orElse": + replacement: "Layer.catch(self, () => fallback())" + note: "Expand the removed lazy fallback alias with Layer.catch." +"effect/Layer#passthrough": + replacement: "Layer.merge(Layer.effectContext(Effect.context()), self)" + note: "Capture required input services and merge them into the layer output." +"effect/Layer#project": + replacement: "Layer.flatMap(self, (context) => Layer.succeed(keyB, f(Context.get(context, keyA))))" + note: "Project one derived service and drop the other outputs." +"effect/Layer#retry": + replacement: "Effect.retry(acquire, schedule) before Layer.effect or Layer.effectContext" + note: "Retry the acquisition Effect; for an arbitrary layer, rebuild a fresh layer for each attempt through Layer.fromBuild." +"effect/Layer#scope": + replacement: "Layer.effect(Scope.Scope, Effect.acquireRelease(Scope.make(), Scope.close))" + note: "Construct and close a child scope explicitly." +"effect/Layer#scoped": + replacement: "Layer.effect" + note: "Scoped acquisition was merged into Layer.effect, which supplies and excludes the layer Scope." +"effect/Layer#scopedContext": + replacement: "Layer.effectContext" + note: "Scoped context acquisition was merged into Layer.effectContext." +"effect/Layer#scopedDiscard": + replacement: "Layer.effectDiscard" + note: "Scoped discard acquisition was merged into Layer.effectDiscard." +"effect/Layer#service": + replacement: "Layer.effect(key, key)" + note: "A Context.Key is an Effect that reads and passes through its service." +"effect/Layer#setClock": + replacement: "Layer.succeed(Clock.Clock, clock)" + note: "Clock.Clock is now a Context.Reference; provide it directly." +"effect/Layer#setConfigProvider": + replacement: "ConfigProvider.layer(configProvider)" + note: "Use the dedicated ConfigProvider layer constructor." +"effect/Layer#setRandom": + replacement: "Layer.succeed(Random.Random, random)" + note: "Random.Random is now a Context.Reference; provide it directly." +"effect/Layer#setRequestBatching": + replacement: "none" + note: "Requests now use resolver-driven batching and expose no batching switch." +"effect/Layer#setRequestCache": + replacement: "none" + note: "The public Request.Cache and its configuration API were removed." +"effect/Layer#setRequestCaching": + replacement: "none" + note: "The public request-caching toggle was removed." +"effect/Layer#setScheduler": + replacement: "Layer.succeed(Scheduler.Scheduler, scheduler)" + note: "Scheduler.Scheduler is now a Context.Reference; provide it directly." +"effect/Layer#setTracer": + replacement: "Layer.succeed(Tracer.Tracer, tracer)" + note: "Tracer.Tracer is now a Context.Reference; provide it directly." +"effect/Layer#setTracerEnabled": + replacement: "Layer.succeed(References.TracerEnabled, enabled)" + note: "Provide the v4 Reference instead of setting a FiberRef." +"effect/Layer#setTracerTiming": + replacement: "Layer.succeed(References.TracerTimingEnabled, enabled)" + note: "Provide the renamed v4 Reference instead of setting a FiberRef." +"effect/Layer#setUnhandledErrorLogLevel": + replacement: "Layer.succeed(References.UnhandledLogLevel, severityOrUndefined)" + note: "Provide LogLevel.Severity or undefined instead of Option." +"effect/Layer#setVersionMismatchErrorLogLevel": + replacement: "none" + note: "No version-mismatch log-level Reference or public replacement exists." +"effect/Layer#tapErrorCause": + replacement: "Layer.tapCause" + note: "The cause observer was renamed. Its callback must accept the source layer's full error cause; a callback narrowed to only part of the error union is rejected." +"effect/Layer#toRuntime": + replacement: "Layer.build(self), then Effect.runForkWith, Effect.runPromiseWith, or Effect.runSyncWith" + note: "Runtime was removed; build a Context, or use ManagedRuntime.make for a reusable managed runner." +"effect/Layer#toRuntimeWithMemoMap": + replacement: "Layer.buildWithMemoMap(self, memoMap, scope), then Effect.run*With(context)" + note: "Explicit memo-map building now yields a Context rather than a Runtime." +"effect/Layer#unwrapEffect": + replacement: "Layer.unwrap" + note: "The Effect-based unwrap constructor was renamed and generalized." +"effect/Layer#unwrapScoped": + replacement: "Layer.unwrap" + note: "Scoped and unscoped unwrap were merged; Layer.unwrap supplies and excludes the layer Scope." +"effect/Layer#updateService": + replacement: "Layer.updateService" + note: "The combinator remains and now accepts any Context.Key." +"effect/Layer#zipWith": + replacement: "Layer.fromBuild with concurrent Effect.zipWith over Layer.buildWithMemoMap" + note: "Combine acquisition effects directly; use Layer.merge when the function only merged Context values." +"effect/Layer#tapError": + replacement: "Layer.tapError" + note: "The observer must accept the source layer's full error union. Widen callbacks that handled only a subtype; saved curried observers preserve the source error type." diff --git a/migration/annotations/effect__LayerMap.yaml b/migration/annotations/effect__LayerMap.yaml new file mode 100644 index 000000000..12e986353 --- /dev/null +++ b/migration/annotations/effect__LayerMap.yaml @@ -0,0 +1,15 @@ +"effect/LayerMap#LayerMap": + replacement: "LayerMap.LayerMap" + note: "The type remains; runtime(key) became contextEffect(key) and returns Context." +"effect/LayerMap#Service": + replacement: "LayerMap.Service" + note: "Use layer instead of Default, layerNoDeps instead of DefaultWithoutDependencies, and contextEffect instead of runtime. Preloading does not remove acquisition errors from later lookups, which can reacquire expired or invalidated entries." +"effect/LayerMap#Service.Context": + replacement: "LayerMap.Service.Services" + note: "The input-services extractor was renamed." +"effect/LayerMap#TagClass": + replacement: "LayerMap.TagClass" + note: "The type remains and now extends Context.ServiceClass; use the renamed layer and contextEffect members." +"effect/LayerMap#TypeId": + replacement: "none" + note: "The LayerMap marker is private in v4 and no public guard exists." diff --git a/migration/annotations/effect__List.yaml b/migration/annotations/effect__List.yaml new file mode 100644 index 000000000..ce1ec1d1e --- /dev/null +++ b/migration/annotations/effect__List.yaml @@ -0,0 +1,126 @@ +"effect/List#append": + replacement: "Array.append" + note: "List was removed; use Array.append. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#appendAll": + replacement: "Array.appendAll" + note: "List was removed; use Array.appendAll. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#cons": + replacement: "Array.prepend" + note: "List was removed; change List.cons(head, tail) to Array.prepend(tail, head)." +"effect/List#Cons": + replacement: "Array.NonEmptyReadonlyArray" + note: "Use the immutable non-empty array type; constructors may return the assignable mutable NonEmptyArray subtype." +"effect/List#empty": + replacement: "Array.empty" + note: "List was removed; use Array.empty. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#filter": + replacement: "Array.filter" + note: "List was removed; use Array.filter. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#filterMap": + replacement: "Array.filterMap" + note: "List was removed; use Array.filterMap and change the callback from Option to Result." +"effect/List#fromIterable": + replacement: "Array.fromIterable" + note: "List was removed; use Array.fromIterable. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#getEquivalence": + replacement: "Array.makeEquivalence" + note: "List was removed; compare the replacement arrays with Array.makeEquivalence." +"effect/List#head": + replacement: "Array.head" + note: "List was removed; use Array.head. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#isCons": + replacement: "Array.isReadonlyArrayNonEmpty" + note: "List was removed; this checks that the replacement readonly array is non-empty." +"effect/List#isList": + replacement: "Array.isArray" + note: "The List brand is gone; this now recognizes the replacement JavaScript array representation." +"effect/List#isNil": + replacement: "Array.isReadonlyArrayEmpty" + note: "List was removed; this checks that the replacement readonly array is empty." +"effect/List#last": + replacement: "Array.last" + note: "List was removed; use Array.last. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#List": + replacement: "ReadonlyArray" + note: "Replace the persistent linked-list representation with ReadonlyArray." +"effect/List#List.AndNonEmpty": + replacement: "Array.ReadonlyArray.AndNonEmpty" + note: "Use the corresponding readonly-array utility type." +"effect/List#List.OrNonEmpty": + replacement: "Array.ReadonlyArray.OrNonEmpty" + note: "Use the corresponding readonly-array utility type." +"effect/List#List.With": + replacement: "Array.ReadonlyArray.With" + note: "Use the corresponding readonly-array utility type." +"effect/List#make": + replacement: "Array.make" + note: "List was removed; use Array.make. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#map": + replacement: "Array.map" + note: "List was removed; use Array.map. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#nil": + replacement: "Array.empty" + note: "List was removed; represent Nil with an empty array." +"effect/List#Nil": + replacement: "none" + note: "Represent this case as readonly []; there is no tagged Nil interface in v4." +"effect/List#of": + replacement: "Array.of" + note: "List was removed; use Array.of. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#partition": + replacement: "Array.partition" + note: "Use a Result-returning callback: failure values form the first array and success values the second." +"effect/List#partitionMap": + replacement: "Array.partition" + note: "Migrate the Either-returning mapper to Result; failures form the first array and successes the second." +"effect/List#prependAll": + replacement: "Array.prependAll" + note: "List was removed; use Array.prependAll. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#prependAllReversed": + replacement: "Array.prependAll + Array.reverse" + note: "Use Array.prependAll(self, Array.reverse(prefix)) to preserve the old ordering." +"effect/List#reduce": + replacement: "Array.reduce" + note: "List was removed; use Array.reduce. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#reduceRight": + replacement: "Array.reduceRight" + note: "List was removed; use Array.reduceRight. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#reverse": + replacement: "Array.reverse" + note: "List was removed; use Array.reverse. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#size": + replacement: "Array.length" + note: "List was removed; use the replacement array length helper or the .length property." +"effect/List#splitAt": + replacement: "Array.splitAt" + note: "List was removed; use Array.splitAt. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#tail": + replacement: "Array.tail" + note: "List was removed; use Array.tail. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#take": + replacement: "Array.take" + note: "List was removed; use Array.take. It preserves ordering but returns arrays rather than persistent linked lists." +"effect/List#toArray": + replacement: "Array.fromIterable" + note: "After migrating the representation this is usually unnecessary; use Array.fromIterable when a fresh mutable array is required." +"effect/List#toChunk": + replacement: "Chunk.fromIterable" + note: "Convert the replacement array or other iterable with Chunk.fromIterable." +"effect/List#TypeId": + replacement: "none" + note: "Arrays have no List runtime marker; remove TypeId inspection." +"effect/List#unsafeHead": + replacement: "Array.headNonEmpty" + note: "Use a NonEmptyReadonlyArray proof before accessing the head; the v4 helper does not accept an empty array." +"effect/List#unsafeLast": + replacement: "Array.lastNonEmpty" + note: "Use a NonEmptyReadonlyArray proof before accessing the last element; the v4 helper does not accept an empty array." +"effect/List#unsafeTail": + replacement: "Array.tailNonEmpty" + note: "Use a NonEmptyReadonlyArray proof before taking the tail; the v4 helper does not accept an empty array." +"effect/List#every": + replacement: "Array.every" + note: "List was removed; run the predicate against the replacement array with Array.every." +"effect/List#some": + replacement: "Array.some" + note: "List was removed; run the predicate against the replacement array with Array.some." diff --git a/migration/annotations/effect__LogLevel.yaml b/migration/annotations/effect__LogLevel.yaml new file mode 100644 index 000000000..f0a4995d3 --- /dev/null +++ b/migration/annotations/effect__LogLevel.yaml @@ -0,0 +1,51 @@ +effect/LogLevel#All: + replacement: "\"All\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#Debug: + replacement: "\"Debug\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#Error: + replacement: "\"Error\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#Fatal: + replacement: "\"Fatal\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#Info: + replacement: "\"Info\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#None: + replacement: "\"None\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#Trace: + replacement: "\"Trace\"" + note: "V4 levels are string literals rather than branded objects; use the literal as both value and singleton type." +effect/LogLevel#Warning: + replacement: "\"Warn\"" + note: "V4 renamed both the value and singleton type from Warning to the string literal Warn." +effect/LogLevel#allLevels: + replacement: "LogLevel.values" + note: "Use the ordered v4 array of all levels, including All and None." +effect/LogLevel#fromLiteral: + replacement: "literal === \"Warning\" ? \"Warn\" : literal" + note: "No constructor is needed because v4 levels are strings. Normalize the renamed Warning literal to Warn; all other v3 literals pass through." +effect/LogLevel#greaterThan: + replacement: "LogLevel.isGreaterThan" + note: "Direct rename; ordering remains severity ordering." +effect/LogLevel#greaterThanEqual: + replacement: "LogLevel.isGreaterThanOrEqualTo" + note: "Direct rename." +effect/LogLevel#lessThan: + replacement: "LogLevel.isLessThan" + note: "Direct rename." +effect/LogLevel#lessThanEqual: + replacement: "LogLevel.isLessThanOrEqualTo" + note: "Direct rename." +effect/LogLevel#Literal: + replacement: "LogLevel.LogLevel" + note: "This is the all-level replacement after renaming Warning to Warn. LogLevel.Severity is narrower because it excludes All and None." +effect/LogLevel#locally: + replacement: "Effect.provideService(effect, References.CurrentLogLevel, level)" + note: "Current log level is now a reference. For threshold configuration, including All or None, provide References.MinimumLogLevel instead." +effect/LogLevel#LogLevel: + replacement: "LogLevel.LogLevel" + note: "The name remains, but the representation is a string union and object fields such as _tag, label, syslog, and ordinal are gone. Use toUpperCase() for labels and LogLevel.getOrdinal for ordering." diff --git a/migration/annotations/effect__LogSpan.yaml b/migration/annotations/effect__LogSpan.yaml new file mode 100644 index 000000000..8060c93d2 --- /dev/null +++ b/migration/annotations/effect__LogSpan.yaml @@ -0,0 +1,9 @@ +effect/LogSpan#LogSpan: + replacement: "readonly [label: string, timestamp: number]" + note: "The module was removed. Active log spans are tuples in References.CurrentLogSpans; ordinary callers should prefer Effect.withLogSpan." +effect/LogSpan#make: + replacement: "[label, startTime] as const" + note: "Construct the tuple directly, or use Effect.withLogSpan so Effect obtains the timestamp and scopes the span." +effect/LogSpan#render: + replacement: "custom tuple formatter" + note: "No public standalone renderer remains. Built-in loggers format span tuples internally; custom formatters can render label and elapsed milliseconds themselves." diff --git a/migration/annotations/effect__Logger.yaml b/migration/annotations/effect__Logger.yaml new file mode 100644 index 000000000..77a9957fb --- /dev/null +++ b/migration/annotations/effect__Logger.yaml @@ -0,0 +1,108 @@ +effect/Logger#add: + replacement: "Logger.layer([logger], { mergeWithExisting: true })" + note: "Logger installation is whole-set based; mergeWithExisting reproduces add." +effect/Logger#addEffect: + replacement: "Logger.layer([loggerEffect], { mergeWithExisting: true })" + note: "Logger.layer accepts effects that construct loggers." +effect/Logger#addScoped: + replacement: "Logger.layer([scopedLoggerEffect], { mergeWithExisting: true })" + note: "Layer acquisition handles the scoped effect; the separate scoped constructor is gone." +effect/Logger#batched: + replacement: "Logger.batched(logger, { window, flush })" + note: "The trailing arguments moved into one options object. Provide any services needed by flush before constructing it." +effect/Logger#filterLogLevel: + replacement: "Logger.make(options => predicate(options.logLevel) ? Option.some(logger.log(options)) : Option.none())" + note: "No named combinator remains; rebuild the wrapper with Logger.make. Prefer References.MinimumLogLevel for ordinary threshold filtering." +effect/Logger#json: + replacement: "Logger.layer([Logger.consoleJson, Logger.tracerLogger])" + note: "Logger.layer replaces the active set. Include tracerLogger to preserve v3 built-in layer behavior, or omit it when trace log events are intentionally disabled." +effect/Logger#jsonLogger: + replacement: "Logger.formatJson" + note: "Formatter rename; v4 JSON output uses level rather than logLevel." +effect/Logger#logFmt: + replacement: "Logger.layer([Logger.consoleLogFmt, Logger.tracerLogger])" + note: "Logger.layer replaces the active set; include tracerLogger to preserve v3 built-in layer behavior." +effect/Logger#logfmtLogger: + replacement: "Logger.formatLogFmt" + note: "Formatter rename and capitalization change." +effect/Logger#Logger: + replacement: "Logger.Logger" + note: "The name remains. Logger.Options now has fiber instead of fiberId; read the id from fiber.id and annotations or spans through fiber references." +effect/Logger#Logger.Variance: + replacement: "none" + note: "Public variance metadata was removed; use Logger.Logger directly." +effect/Logger#LoggerTypeId: + replacement: "Logger.isLogger" + note: "The brand is private in v4; use the public runtime guard." +effect/Logger#map: + replacement: "Logger.map" + note: "Retained with the same output-mapping behavior." +effect/Logger#mapInput: + replacement: "Logger.make(options => logger.log({ ...options, message: f(options.message) }))" + note: "No named input contramap remains; rebuild it with Logger.make." +effect/Logger#mapInputOptions: + replacement: "Logger.make(options => logger.log(f(options)))" + note: "No named options contramap remains; rebuild it with Logger.make and adapt f to the v4 Logger.Options shape." +effect/Logger#minimumLogLevel: + replacement: "Layer.succeed(References.MinimumLogLevel, level)" + note: "Minimum log level is now a context reference." +effect/Logger#none: + replacement: "Logger.make(() => undefined)" + note: "Rebuild the no-op logger with Logger.make." +effect/Logger#pretty: + replacement: "Logger.layer([Logger.consolePretty(), Logger.tracerLogger])" + note: "Logger.layer replaces the active set; include tracerLogger to preserve v3 built-in layer behavior." +effect/Logger#prettyLogger: + replacement: "Logger.consolePretty" + note: "Renamed to consolePretty. Remove the stderr option; provide Logger.LogToStderr with true to route TTY output to console.error. Colors, formatDate, and mode remain constructor options." +effect/Logger#prettyLoggerDefault: + replacement: "Logger.consolePretty()" + note: "The prebuilt singleton became a constructor call." +effect/Logger#remove: + replacement: "Logger.layer([...desiredLoggers])" + note: "No named removal combinator remains. Declare the complete desired logger set; transform Logger.CurrentLoggers only when removing from an unknown inherited set is unavoidable." +effect/Logger#replace: + replacement: "Logger.layer([...desiredLoggers])" + note: "V4 replaces the whole active set. When replacing the old default logger, include Logger.tracerLogger explicitly if it must survive." +effect/Logger#replaceEffect: + replacement: "Logger.layer([loggerEffect, ...otherLoggers])" + note: "Logger.layer accepts effects. Explicitly list every logger that must remain active." +effect/Logger#replaceScoped: + replacement: "Logger.layer([scopedLoggerEffect, ...otherLoggers])" + note: "Logger.layer acquisition supplies the scope; explicitly list every logger that must remain active." +effect/Logger#simple: + replacement: "Logger.make(({ message }) => log(message))" + note: "V3 simple was a message-only custom logger constructor; rebuild it with Logger.make." +effect/Logger#stringLogger: + replacement: "Logger.formatSimple" + note: "The prebuilt string formatter was renamed." +effect/Logger#structured: + replacement: "Logger.layer([Logger.consoleStructured, Logger.tracerLogger])" + note: "Logger.layer replaces the active set; include tracerLogger to preserve v3 built-in layer behavior." +effect/Logger#structuredLogger: + replacement: "Logger.formatStructured" + note: "Formatter rename; its output field logLevel is now named level." +effect/Logger#succeed: + replacement: "Logger.make(() => value)" + note: "Rebuild the constant-output logger with Logger.make." +effect/Logger#sync: + replacement: "Logger.make(() => evaluate())" + note: "Rebuild the lazy-output logger; evaluate still runs once per log event." +effect/Logger#test: + replacement: "Effect.log(input).pipe(Effect.provide(Logger.layer([capturingLogger])))" + note: "No synthetic-options helper remains. Exercise the logger through the runtime and capture its output so it receives a real Fiber, cause, level, and date." +effect/Logger#withMinimumLogLevel: + replacement: "Effect.provideService(effect, References.MinimumLogLevel, level)" + note: "Replace the FiberRef-local helper with reference provisioning." +effect/Logger#withSpanAnnotations: + replacement: "custom Logger.make wrapper using options.fiber.cache.span" + note: "No transparent generic equivalent remains. Read span identity from options.fiber.cache.span and add it to custom output as needed." +effect/Logger#zip: + replacement: "Logger.make(options => [left.log(options), right.log(options)])" + note: "No named combinator remains; invoke both loggers and return their output tuple." +effect/Logger#zipLeft: + replacement: "Logger.make(options => { const output = left.log(options); right.log(options); return output })" + note: "Rebuild explicitly, preserving evaluation of both loggers and returning the left output." +effect/Logger#zipRight: + replacement: "Logger.make(options => { left.log(options); return right.log(options) })" + note: "Rebuild explicitly, preserving evaluation order and returning the right output." diff --git a/migration/annotations/effect__Mailbox.yaml b/migration/annotations/effect__Mailbox.yaml new file mode 100644 index 000000000..795891c68 --- /dev/null +++ b/migration/annotations/effect__Mailbox.yaml @@ -0,0 +1,30 @@ +effect/Mailbox#fromStream: + replacement: "Stream.toQueue" + note: "Mailbox was renamed and folded into Queue; Stream.toQueue returns a scoped Queue.Dequeue whose error includes Cause.Done." +effect/Mailbox#into: + replacement: "Queue.into" + note: "Use Queue.into with a Queue.Enqueue whose error channel includes Cause.Done." +effect/Mailbox#isMailbox: + replacement: "Queue.isQueue" + note: "Mailbox became the completion-aware v4 Queue model." +effect/Mailbox#isReadonlyMailbox: + replacement: "Queue.isDequeue" + note: "ReadonlyMailbox became Queue.Dequeue." +effect/Mailbox#Mailbox: + replacement: "Queue.Queue" + note: "Mailbox was folded into Queue; include Cause.Done in the error channel when normal end signaling is used." +effect/Mailbox#make: + replacement: "Queue.make" + note: "Pass the v4 options object with optional capacity and strategy; a numeric capacity argument must become { capacity }." +effect/Mailbox#ReadonlyMailbox: + replacement: "Queue.Dequeue" + note: "Use explicit Queue taking operations; Queue.Dequeue is not itself an Effect yielding message chunks." +effect/Mailbox#ReadonlyTypeId: + replacement: "Queue.isDequeue" + note: "The public Mailbox type id was removed; use the Queue.isDequeue guard instead." +effect/Mailbox#toStream: + replacement: "Stream.fromQueue" + note: "Convert a Queue.Dequeue to a Stream; Cause.Done is excluded from the resulting stream error type." +effect/Mailbox#TypeId: + replacement: "Queue.isQueue" + note: "The public Mailbox type id was removed; use the Queue.isQueue guard instead." diff --git a/migration/annotations/effect__ManagedRuntime.yaml b/migration/annotations/effect__ManagedRuntime.yaml new file mode 100644 index 000000000..5daa23e94 --- /dev/null +++ b/migration/annotations/effect__ManagedRuntime.yaml @@ -0,0 +1,15 @@ +"effect/ManagedRuntime#ManagedRuntime": + replacement: "ManagedRuntime.ManagedRuntime" + note: "The handle remains but is no longer an Effect; runtimeEffect/runtime became contextEffect/context, and make accepts { memoMap }." +"effect/ManagedRuntime#ManagedRuntime.Context": + replacement: "ManagedRuntime.ManagedRuntime.Services" + note: "The context extractor was renamed to Services." +"effect/ManagedRuntime#ManagedRuntimeUnify": + replacement: "none" + note: "ManagedRuntime no longer extends Effect, so its unification artifact was removed; call run methods or contextEffect explicitly." +"effect/ManagedRuntime#ManagedRuntimeUnifyIgnore": + replacement: "none" + note: "ManagedRuntime no longer extends Effect, so the Unify-ignore artifact was removed." +"effect/ManagedRuntime#TypeId": + replacement: "ManagedRuntime.isManagedRuntime" + note: "The marker is private; use the public guard for runtime narrowing." diff --git a/migration/annotations/effect__Match.yaml b/migration/annotations/effect__Match.yaml new file mode 100644 index 000000000..2ed399401 --- /dev/null +++ b/migration/annotations/effect__Match.yaml @@ -0,0 +1,51 @@ +"effect/Match#either": + replacement: "Match.result" + note: "Renamed finalizer with a container change: matched Right and unmatched Left become Result.Success and Result.Failure." +"effect/Match#MatcherTypeId": + replacement: "none" + note: "The public matcher brand was internalized. Obtain matchers from Match.type or Match.value and use their public _tag when discrimination is required." +"effect/Match#Matcher": + replacement: "Match.Matcher" + note: "The type is retained, but its fifth argument is now a flavor marker (ValueFlavor for Match.value and never for Match.type or Match.fn) rather than the provided value; an optional seventh Args tuple tracks Match.fn selector arguments. Prefer inference from Match.type, Match.value, or Match.fn and update hand-written Matcher annotations." +"effect/Match#Not": + replacement: "Match.Not" + note: "The case type is retained. Its evaluate method now receives any Match.fn selector arguments after the selected input; update custom case implementations that consume those arguments." +"effect/Match#SafeRefinementId": + replacement: "none" + note: "The public safe-refinement brand was internalized. Use Predicate.Refinement, Predicate.Predicate, or a built-in Match refinement instead of constructing the brand." +"effect/Match#TypeMatcher": + replacement: "Match.TypeMatcher" + note: "The public type is retained, but its brand is private; create values with Match.type rather than implementing the interface." +"effect/Match#Types": + replacement: "Match.Types" + note: "The public type-level namespace is retained with no call-site migration." +"effect/Match#Types.ExtractAndNarrow": + replacement: "Match.Types.ExtractAndNarrow" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.MaybeReplace": + replacement: "Match.Types.MaybeReplace" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.NonFailKeys": + replacement: "Match.Types.NonFailKeys" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.PForNotMatch": + replacement: "Match.Types.PForNotMatch" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.ResolvePred": + replacement: "Match.Types.ResolvePred" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.SafeRefinementR": + replacement: "Match.Types.SafeRefinementR" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.ToInvertedRefinement": + replacement: "Match.Types.ToInvertedRefinement" + note: "The type-only matching helper is retained unchanged." +"effect/Match#Types.ToSafeRefinement": + replacement: "Match.Types.ToSafeRefinement" + note: "The type-only matching helper is retained unchanged." +"effect/Match#ValueMatcher": + replacement: "Match.ValueMatcher" + note: "The type is retained, but value now uses Result instead of Either and the brand is private; create values with Match.value." +"effect/Match#When": + replacement: "Match.When" + note: "The case type is retained. Its evaluate method now receives any Match.fn selector arguments after the selected input; update custom case implementations that consume those arguments." diff --git a/migration/annotations/effect__MergeDecision.yaml b/migration/annotations/effect__MergeDecision.yaml new file mode 100644 index 000000000..c2606ba0a --- /dev/null +++ b/migration/annotations/effect__MergeDecision.yaml @@ -0,0 +1,24 @@ +"effect/MergeDecision#Await": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#AwaitConst": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#Done": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#isMergeDecision": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#match": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#MergeDecision": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#MergeDecision.Variance": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." +"effect/MergeDecision#MergeDecisionTypeId": + replacement: "Channel.merge" + note: "Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured." diff --git a/migration/annotations/effect__MergeState.yaml b/migration/annotations/effect__MergeState.yaml new file mode 100644 index 000000000..beb3f5811 --- /dev/null +++ b/migration/annotations/effect__MergeState.yaml @@ -0,0 +1,3 @@ +effect/MergeState: + replacement: none + note: Internal execution state of the removed Channel.mergeWith implementation. V4 Channel.merge manages its fibers and queues internally and exposes only a haltStrategy option. diff --git a/migration/annotations/effect__MergeStrategy.yaml b/migration/annotations/effect__MergeStrategy.yaml new file mode 100644 index 000000000..de08cbd87 --- /dev/null +++ b/migration/annotations/effect__MergeStrategy.yaml @@ -0,0 +1,27 @@ +"effect/MergeStrategy#BackPressure": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#BufferSliding": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#isBackPressure": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#isBufferSliding": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#isMergeStrategy": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#match": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#MergeStrategy": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#MergeStrategy.Proto": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." +"effect/MergeStrategy#MergeStrategyTypeId": + replacement: "Channel.mergeAll" + note: "Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap." diff --git a/migration/annotations/effect__Metric.yaml b/migration/annotations/effect__Metric.yaml new file mode 100644 index 000000000..67a83b2e3 --- /dev/null +++ b/migration/annotations/effect__Metric.yaml @@ -0,0 +1,105 @@ +effect/Metric#fiberActive: + replacement: "Metric.enableRuntimeMetrics + Metric.snapshot" + note: "The concrete metric is no longer exported. Enable runtime metrics, then read the Gauge snapshot whose id is child_fibers_active." +effect/Metric#fiberFailures: + replacement: "Metric.enableRuntimeMetrics + Metric.snapshot" + note: "The concrete metric is no longer exported. Enable runtime metrics, then read the Counter snapshot whose id is child_fiber_failures." +effect/Metric#fiberLifetimes: + replacement: "none" + note: "The built-in lifetime histogram was removed. Define a Metric.timer and instrument selected effects with Effect.trackDuration when lifetime data is required." +effect/Metric#fiberStarted: + replacement: "Metric.enableRuntimeMetrics + Metric.snapshot" + note: "The concrete metric is no longer exported. Enable runtime metrics, then read the Counter snapshot whose id is child_fibers_started." +effect/Metric#fiberSuccesses: + replacement: "Metric.enableRuntimeMetrics + Metric.snapshot" + note: "The concrete metric is no longer exported. Enable runtime metrics, then read the Counter snapshot whose id is child_fiber_successes." +effect/Metric#fromMetricKey: + replacement: "Metric.counter / Metric.gauge / Metric.frequency / Metric.histogram / Metric.summary" + note: "MetricKey and MetricKeyType were removed. Construct the required primitive metric directly." +effect/Metric#globalMetricRegistry: + replacement: "Metric.MetricRegistry" + note: "The process-global registry became a Context.Reference whose service is a Map. Access it in context or provide a fresh Map for isolation; use Metric.snapshot for normal reads." +effect/Metric#increment: + replacement: "Metric.update / Metric.modify" + note: "Use Metric.update(counter, 1 or 1n) for counters and Metric.modify(gauge, 1 or 1n) for gauges; gauge update sets an absolute value while modify adds a delta." +effect/Metric#incrementBy: + replacement: "Metric.update / Metric.modify" + note: "Use Metric.update(counter, amount) for counters and Metric.modify(gauge, amount) for gauges." +effect/Metric#make: + replacement: "none" + note: "The low-level arbitrary metric constructor was removed. Use a public primitive constructor and compose with mapInput, withConstantInput, and withAttributes." +effect/Metric#map: + replacement: "Metric.value + Effect.map" + note: "Metric-level state mapping was removed. Transform a read with Effect.map(Metric.value(metric), f)." +effect/Metric#mapType: + replacement: "none" + note: "Drop this call. V4 Metric has no key-type type parameter and exposes a fixed runtime type discriminator." +effect/Metric#Metric: + replacement: "Metric.Metric" + note: "Drop the v3 key-type parameter. Metrics are no longer callable; use Effect.trackSuccesses for instrumentation and Metric.update or Metric.value for operations." +effect/Metric#Metric.Variance: + replacement: "none" + note: "The public variance interface was removed; Metric carries variance markers directly." +effect/Metric#MetricApply: + replacement: "none" + note: "Removed with Metric.make; v4 has no public low-level custom-metric constructor type." +effect/Metric#MetricTypeId: + replacement: "Metric.isMetric" + note: "The public unique-symbol type id was removed; use Metric.isMetric for runtime refinement." +effect/Metric#set: + replacement: "Metric.update" + note: "Use Metric.update(gauge, value); v4 update replaces a gauge's current value." +effect/Metric#succeed: + replacement: "none" + note: "Constant synthetic metrics were removed. Keep constants outside the metric and use Effect.succeed when an Effect value is required." +effect/Metric#summaryTimestamp: + replacement: "Metric.summaryWithTimestamp" + note: "Renamed and called as Metric.summaryWithTimestamp(name, options). Remove the v3 error option; inputs remain value/timestamp pairs." +effect/Metric#sync: + replacement: "none" + note: "Lazy synthetic metrics were removed. Keep the computation outside the metric and use Effect.sync when an Effect value is required." +effect/Metric#tagged: + replacement: "Metric.withAttributes" + note: "Replace tags with attributes, for example Metric.withAttributes(metric, { [key]: value })." +effect/Metric#taggedWithLabels: + replacement: "Metric.withAttributes" + note: "Replace MetricLabel objects with a string record or array of string tuples passed to Metric.withAttributes." +effect/Metric#taggedWithLabelsInput: + replacement: "Metric.withAttributes + Metric.update" + note: "No dynamic-attribute transform remains. Compute attributes at each update or tracking site, wrap with Metric.withAttributes, then update the metric." +effect/Metric#timerWithBoundaries: + replacement: "Metric.timer" + note: "Use Metric.timer(name, { boundaries, description }); boundaries moved into the options object." +effect/Metric#trackAll: + replacement: "Effect.track" + note: "Moved to Effect; use effect.pipe(Effect.track(metric, () => input))." +effect/Metric#trackDefect: + replacement: "Effect.trackDefects" + note: "Moved to Effect; use effect.pipe(Effect.trackDefects(metric))." +effect/Metric#trackDefectWith: + replacement: "Effect.trackDefects" + note: "Moved to Effect; pass the mapper as the optional second argument." +effect/Metric#trackDurationWith: + replacement: "Effect.trackDuration" + note: "Moved to Effect; pass the mapper as the optional second argument. V4 records duration on every Exit, whereas v3 updated only after success." +effect/Metric#trackError: + replacement: "Effect.trackErrors" + note: "Moved to Effect; use effect.pipe(Effect.trackErrors(metric))." +effect/Metric#trackErrorWith: + replacement: "Effect.trackErrors" + note: "Moved to Effect; pass the mapper as the optional second argument." +effect/Metric#trackSuccess: + replacement: "Effect.trackSuccesses" + note: "Moved to Effect; use effect.pipe(Effect.trackSuccesses(metric))." +effect/Metric#trackSuccessWith: + replacement: "Effect.trackSuccesses" + note: "Moved to Effect; pass the mapper as the optional second argument." +effect/Metric#unsafeSnapshot: + replacement: "Metric.snapshotUnsafe" + note: "Renamed and now requires an explicit Context.Context. It returns structural snapshots rather than MetricPair values." +effect/Metric#withNow: + replacement: "Metric.summary" + note: "Metric.summary reads the current Clock automatically; use Metric.summaryWithTimestamp when timestamps are supplied explicitly. The generic timestamp-injecting combinator was removed." +effect/Metric#zip: + replacement: "Effect.all + Metric.update / Metric.value" + note: "Composite metrics were removed. Use Effect.all to update both metrics or combine their Metric.value reads." diff --git a/migration/annotations/effect__MetricBoundaries.yaml b/migration/annotations/effect__MetricBoundaries.yaml new file mode 100644 index 000000000..cc3273029 --- /dev/null +++ b/migration/annotations/effect__MetricBoundaries.yaml @@ -0,0 +1,18 @@ +effect/MetricBoundaries#exponential: + replacement: "Metric.exponentialBoundaries" + note: "Moved into effect/Metric and now returns ReadonlyArray. V4 also filters non-positive boundaries." +effect/MetricBoundaries#fromIterable: + replacement: "Metric.boundariesFromIterable" + note: "Moved into effect/Metric and now returns an unbranded ReadonlyArray; v4 removes non-positive values before appending Infinity." +effect/MetricBoundaries#linear: + replacement: "Metric.linearBoundaries" + note: "Moved into effect/Metric, but the compared v4 implementation uses start + i + width rather than v3's start + i * width. Preserve the v3 formula manually when width is not 1." +effect/MetricBoundaries#MetricBoundaries: + replacement: "ReadonlyArray" + note: "The wrapper was removed; Metric.histogram accepts plain boundaries in its options." +effect/MetricBoundaries#isMetricBoundaries: + replacement: "none" + note: "Boundaries are unbranded arrays, so the guard and public type-id symbol have no replacement." +effect/MetricBoundaries#MetricBoundariesTypeId: + replacement: "none" + note: "Boundaries are unbranded arrays, so the guard and public type-id symbol have no replacement." diff --git a/migration/annotations/effect__MetricHook.yaml b/migration/annotations/effect__MetricHook.yaml new file mode 100644 index 000000000..9350c36c5 --- /dev/null +++ b/migration/annotations/effect__MetricHook.yaml @@ -0,0 +1,39 @@ +effect/MetricHook#counter: + replacement: "Metric.counter" + note: "Hook construction was folded into the complete Metric.counter constructor; hooks are internal." +effect/MetricHook#frequency: + replacement: "Metric.frequency" + note: "Hook construction was folded into the complete Metric.frequency constructor; hooks are internal." +effect/MetricHook#gauge: + replacement: "Metric.gauge" + note: "Hook construction was folded into the complete Metric.gauge constructor; hooks are internal." +effect/MetricHook#histogram: + replacement: "Metric.histogram" + note: "Hook construction was folded into the complete Metric.histogram constructor; hooks are internal." +effect/MetricHook#summary: + replacement: "Metric.summary" + note: "Hook construction was folded into the complete Metric.summary constructor; hooks are internal." +effect/MetricHook#MetricHook: + replacement: "Metric.Metric.Hooks" + note: "The closest public structural interface is Metric.Metric.Hooks; get, update, and modify also receive a Context." +effect/MetricHook#MetricHook.Root: + replacement: "Metric.Metric.Hooks" + note: "The named aliases were removed; specialize the public Hooks interface directly when low-level typing is unavoidable." +effect/MetricHook#MetricHook.Untyped: + replacement: "Metric.Metric.Hooks" + note: "The named aliases were removed; specialize the public Hooks interface directly when low-level typing is unavoidable." +effect/MetricHook#make: + replacement: "none" + note: "There is no public hook constructor; metric classes create and attach hooks internally." +effect/MetricHook#onModify: + replacement: "none" + note: "The operation-specific hook decorators were removed. Metric.mapInput cannot distinguish update from modify." +effect/MetricHook#onUpdate: + replacement: "none" + note: "The operation-specific hook decorators were removed. Metric.mapInput cannot distinguish update from modify." +effect/MetricHook#MetricHook.Variance: + replacement: "none" + note: "Hooks are structural and unbranded; the variance helper and public symbol were removed." +effect/MetricHook#MetricHookTypeId: + replacement: "none" + note: "Hooks are structural and unbranded; the variance helper and public symbol were removed." diff --git a/migration/annotations/effect__MetricKey.yaml b/migration/annotations/effect__MetricKey.yaml new file mode 100644 index 000000000..512d460e5 --- /dev/null +++ b/migration/annotations/effect__MetricKey.yaml @@ -0,0 +1,33 @@ +effect/MetricKey#counter: + replacement: "Metric.counter" + note: "The key and key type were merged into the complete Metric.counter constructor." +effect/MetricKey#frequency: + replacement: "Metric.frequency" + note: "The key and key type were merged into the complete Metric.frequency constructor." +effect/MetricKey#gauge: + replacement: "Metric.gauge" + note: "The key and key type were merged into the complete Metric.gauge constructor." +effect/MetricKey#histogram: + replacement: "Metric.histogram" + note: "The key and key type were merged into the complete Metric.histogram constructor." +effect/MetricKey#summary: + replacement: "Metric.summary" + note: "The key and key type were merged into the complete Metric.summary constructor." +effect/MetricKey#MetricKey: + replacement: "Metric.Metric" + note: "Key identity, metadata, and operations are combined in Metric." +effect/MetricKey#MetricKey.Untyped: + replacement: "Metric.Metric" + note: "The separate untyped key alias was removed; use an untyped complete Metric only where required." +effect/MetricKey#MetricKey.Variance: + replacement: "Metric.Metric" + note: "There is no separate key variance interface; variance is carried by Metric's Input and State phantom fields." +effect/MetricKey#isMetricKey: + replacement: "Metric.isMetric" + note: "Keys became complete metrics; use the complete-metric runtime guard." +effect/MetricKey#MetricKeyTypeId: + replacement: "none" + note: "The key brand was removed; Metric's protocol key is internal." +effect/MetricKey#taggedWithLabels: + replacement: "Metric.withAttributes" + note: "Labels became attributes. Pass a string record or array of string tuples." diff --git a/migration/annotations/effect__MetricKeyType.yaml b/migration/annotations/effect__MetricKeyType.yaml new file mode 100644 index 000000000..e4288478c --- /dev/null +++ b/migration/annotations/effect__MetricKeyType.yaml @@ -0,0 +1,66 @@ +effect/MetricKeyType#counter: + replacement: "Metric.counter" + note: "The standalone descriptor was folded into the complete Metric.counter constructor." +effect/MetricKeyType#frequency: + replacement: "Metric.frequency" + note: "The standalone descriptor was folded into the complete Metric.frequency constructor." +effect/MetricKeyType#gauge: + replacement: "Metric.gauge" + note: "The standalone descriptor was folded into the complete Metric.gauge constructor." +effect/MetricKeyType#histogram: + replacement: "Metric.histogram" + note: "The standalone descriptor was folded into the complete Metric.histogram constructor." +effect/MetricKeyType#summary: + replacement: "Metric.summary" + note: "The standalone descriptor was folded into the complete Metric.summary constructor." +effect/MetricKeyType#MetricKeyType: + replacement: "Metric.Metric" + note: "Input/state typing and kind configuration now live on the complete Metric." +effect/MetricKeyType#MetricKeyType.InType: + replacement: "Metric.Metric.Input" + note: "Use Metric.Metric.Input to extract a metric's input type." +effect/MetricKeyType#MetricKeyType.OutType: + replacement: "Metric.Metric.State" + note: "Use Metric.Metric.State to extract a metric's state type." +effect/MetricKeyType#MetricKeyType.Untyped: + replacement: "Metric.Metric" + note: "The key-type descriptor no longer exists independently of a metric." +effect/MetricKeyType#MetricKeyType.Variance: + replacement: "none" + note: "The descriptor variance interface was removed; complete Metric carries Input and State variance." +effect/MetricKeyType#isMetricKeyType: + replacement: "Metric.isMetric" + note: "Standalone key-type values were removed; test complete metrics instead." +effect/MetricKeyType#isCounterKey: + replacement: "Metric.isMetric + metric.type" + note: "Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant." +effect/MetricKeyType#isFrequencyKey: + replacement: "Metric.isMetric + metric.type" + note: "Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant." +effect/MetricKeyType#isGaugeKey: + replacement: "Metric.isMetric + metric.type" + note: "Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant." +effect/MetricKeyType#isHistogramKey: + replacement: "Metric.isMetric + metric.type" + note: "Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant." +effect/MetricKeyType#isSummaryKey: + replacement: "Metric.isMetric + metric.type" + note: "Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant." +effect/MetricKeyType#MetricKeyTypeTypeId: + replacement: "none" + note: "All public key-type symbols were removed; use a complete metric's string type discriminant." +effect/MetricKeyType#CounterKeyTypeTypeId: + replacement: "none" + note: "All public key-type symbols were removed; use a complete metric's string type discriminant." +effect/MetricKeyType#FrequencyKeyTypeTypeId: + replacement: "none" + note: "All public key-type symbols were removed; use a complete metric's string type discriminant." +effect/MetricKeyType#GaugeKeyTypeTypeId: + replacement: "none" + note: "All public key-type symbols were removed; use a complete metric's string type discriminant." +effect/MetricKeyType#HistogramKeyTypeTypeId: + replacement: "none" + note: "All public key-type symbols were removed; use a complete metric's string type discriminant." +effect/MetricKeyType#SummaryKeyTypeTypeId: + replacement: "none" + note: "All public key-type symbols were removed; use a complete metric's string type discriminant." diff --git a/migration/annotations/effect__MetricLabel.yaml b/migration/annotations/effect__MetricLabel.yaml new file mode 100644 index 000000000..27cc1bbbf --- /dev/null +++ b/migration/annotations/effect__MetricLabel.yaml @@ -0,0 +1,12 @@ +effect/MetricLabel#MetricLabel: + replacement: "[string, string]" + note: "A label is now an ordinary attribute tuple; collections are Metric.Metric.Attributes or Metric.Metric.AttributeSet." +effect/MetricLabel#make: + replacement: "[key, value]" + note: "Construct an ordinary tuple, or place the pair in an attribute record passed to Metric.withAttributes or a metric constructor." +effect/MetricLabel#isMetricLabel: + replacement: "none" + note: "Attributes are plain tuples or records, so there is no branded guard or type-id symbol." +effect/MetricLabel#MetricLabelTypeId: + replacement: "none" + note: "Attributes are plain tuples or records, so there is no branded guard or type-id symbol." diff --git a/migration/annotations/effect__MetricPair.yaml b/migration/annotations/effect__MetricPair.yaml new file mode 100644 index 000000000..14cebe395 --- /dev/null +++ b/migration/annotations/effect__MetricPair.yaml @@ -0,0 +1,18 @@ +effect/MetricPair#MetricPair: + replacement: "Metric.Metric.Snapshot" + note: "Registry key/state pairs became discriminated snapshots containing id, type, description, attributes, and state." +effect/MetricPair#MetricPair.Untyped: + replacement: "Metric.Metric.Snapshot" + note: "Registry key/state pairs became discriminated snapshots containing id, type, description, attributes, and state." +effect/MetricPair#make: + replacement: "Metric.snapshot" + note: "There is no pair constructor. Obtain snapshots with Metric.snapshot or Metric.snapshotUnsafe; manually constructed data can satisfy Metric.Metric.SnapshotProto." +effect/MetricPair#unsafeMake: + replacement: "Metric.snapshot" + note: "There is no pair constructor. Obtain snapshots with Metric.snapshot or Metric.snapshotUnsafe; manually constructed data can satisfy Metric.Metric.SnapshotProto." +effect/MetricPair#MetricPair.Variance: + replacement: "none" + note: "Snapshots are structural, so the pair variance helper and brand symbol were removed." +effect/MetricPair#MetricPairTypeId: + replacement: "none" + note: "Snapshots are structural, so the pair variance helper and brand symbol were removed." diff --git a/migration/annotations/effect__MetricPolling.yaml b/migration/annotations/effect__MetricPolling.yaml new file mode 100644 index 000000000..9a5d183bd --- /dev/null +++ b/migration/annotations/effect__MetricPolling.yaml @@ -0,0 +1,24 @@ +effect/MetricPolling#MetricPolling: + replacement: "local { metric, poll } record" + note: "The module was removed. Keep a local record pairing a Metric with its polling Effect when this abstraction is still useful." +effect/MetricPolling#make: + replacement: "({ metric, poll })" + note: "No public constructor remains; use the local record directly." +effect/MetricPolling#poll: + replacement: "self.poll" + note: "Access the polling Effect from the local record." +effect/MetricPolling#pollAndUpdate: + replacement: "Effect.flatMap(self.poll, input => Metric.update(self.metric, input))" + note: "Compose polling and metric update directly." +effect/MetricPolling#retry: + replacement: "Effect.retry" + note: "Retry the poll Effect and retain the same metric in the local record." +effect/MetricPolling#launch: + replacement: "Effect.repeat + Effect.forkScoped" + note: "Repeat polling, updating, and reading with the schedule, then forkScoped." +effect/MetricPolling#collectAll: + replacement: "Effect.forEach + Metric.update/value" + note: "No combined metric replacement exists. Poll records, update each metric, and collect states explicitly." +effect/MetricPolling#MetricPollingTypeId: + replacement: "none" + note: "The polling wrapper and its brand were removed." diff --git a/migration/annotations/effect__MetricRegistry.yaml b/migration/annotations/effect__MetricRegistry.yaml new file mode 100644 index 000000000..48b0f2c6f --- /dev/null +++ b/migration/annotations/effect__MetricRegistry.yaml @@ -0,0 +1,9 @@ +effect/MetricRegistry#MetricRegistry: + replacement: "Metric.MetricRegistry" + note: "The method-bearing registry became a Context.Reference whose service is a Map. Metrics register metadata and hooks lazily." +effect/MetricRegistry#make: + replacement: "new Map>()" + note: "Provide a fresh Map to Metric.MetricRegistry for isolation. Read it through Metric.snapshot or snapshotUnsafe." +effect/MetricRegistry#MetricRegistryTypeId: + replacement: "none" + note: "The registry service is an ordinary Map behind a Context.Reference and has no public brand." diff --git a/migration/annotations/effect__MetricState.yaml b/migration/annotations/effect__MetricState.yaml new file mode 100644 index 000000000..25a25f98d --- /dev/null +++ b/migration/annotations/effect__MetricState.yaml @@ -0,0 +1,60 @@ +effect/MetricState#counter: + replacement: "Metric.CounterState" + note: "There is no state constructor. Obtain the structural state with Metric.value(Metric.counter(...))." +effect/MetricState#frequency: + replacement: "Metric.FrequencyState" + note: "There is no state constructor. Obtain the structural state with Metric.value(Metric.frequency(...))." +effect/MetricState#gauge: + replacement: "Metric.GaugeState" + note: "There is no state constructor. Obtain the structural state with Metric.value(Metric.gauge(...))." +effect/MetricState#histogram: + replacement: "Metric.HistogramState" + note: "There is no state constructor. Obtain the structural state with Metric.value(Metric.histogram(...))." +effect/MetricState#summary: + replacement: "Metric.SummaryState" + note: "There is no state constructor. Obtain the structural state with Metric.value(Metric.summary(...))." +effect/MetricState#MetricState: + replacement: "Metric.Metric.State" + note: "The common branded state model was removed; extract a complete metric's state with Metric.Metric.State or use a concrete state interface." +effect/MetricState#MetricState.Untyped: + replacement: "Metric.Metric.Snapshot['state']" + note: "Use the state union from Metric.Metric.Snapshot, or explicitly union the five structural state interfaces." +effect/MetricState#MetricState.Variance: + replacement: "none" + note: "States are structural objects and no longer carry a variance brand." +effect/MetricState#isMetricState: + replacement: "none" + note: "Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination." +effect/MetricState#isCounterState: + replacement: "none" + note: "Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination." +effect/MetricState#isFrequencyState: + replacement: "none" + note: "Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination." +effect/MetricState#isGaugeState: + replacement: "none" + note: "Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination." +effect/MetricState#isHistogramState: + replacement: "none" + note: "Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination." +effect/MetricState#isSummaryState: + replacement: "none" + note: "Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination." +effect/MetricState#MetricStateTypeId: + replacement: "none" + note: "All state brand symbols were removed; v4 state interfaces are structural." +effect/MetricState#CounterStateTypeId: + replacement: "none" + note: "All state brand symbols were removed; v4 state interfaces are structural." +effect/MetricState#FrequencyStateTypeId: + replacement: "none" + note: "All state brand symbols were removed; v4 state interfaces are structural." +effect/MetricState#GaugeStateTypeId: + replacement: "none" + note: "All state brand symbols were removed; v4 state interfaces are structural." +effect/MetricState#HistogramStateTypeId: + replacement: "none" + note: "All state brand symbols were removed; v4 state interfaces are structural." +effect/MetricState#SummaryStateTypeId: + replacement: "none" + note: "All state brand symbols were removed; v4 state interfaces are structural." diff --git a/migration/annotations/effect__Micro.yaml b/migration/annotations/effect__Micro.yaml new file mode 100644 index 000000000..562cd279b --- /dev/null +++ b/migration/annotations/effect__Micro.yaml @@ -0,0 +1,500 @@ +effect/Micro#all: + replacement: "Effect.all" + note: "Micro was removed in v4; use Effect.all with the same iterable-or-record input and concurrency/discard options." +effect/Micro#acquireUseRelease: + replacement: "Effect.acquireUseRelease" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#All.IsDiscard: + replacement: "Effect.All.IsDiscard" + note: "Type-level helper moved to the Effect.All namespace." +effect/Micro#All.MicroAny: + replacement: "Effect.All.EffectAny" + note: "Renamed: MicroAny becomes EffectAny in the Effect.All namespace." +effect/Micro#All.Return: + replacement: "Effect.All.Return" + note: "Type-level helper moved to the Effect.All namespace." +effect/Micro#as: + replacement: "Effect.as" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#asSome: + replacement: "Effect.asSome" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#asVoid: + replacement: "Effect.asVoid" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#async: + replacement: "Effect.callback" + note: "Renamed: the async constructor is Effect.callback in v4. Same resume/AbortSignal semantics." + example: "Effect.callback((resume) => resume(Effect.succeed(1)))" +effect/Micro#bind: + replacement: "Effect.bind" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#bindTo: + replacement: "Effect.bindTo" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#catchAll: + replacement: "Effect.catch" + note: "Renamed: catchAll is Effect.catch in v4." +effect/Micro#catchAllCause: + replacement: "Effect.catchCause" + note: "Renamed: catchAllCause is Effect.catchCause in v4." +effect/Micro#catchAllDefect: + replacement: "Effect.catchDefect" + note: "Renamed: catchAllDefect is Effect.catchDefect in v4." +effect/Micro#catchIf: + replacement: "Effect.catchIf" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#catchTag: + replacement: "Effect.catchTag" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#causeDie: + replacement: "Cause.die" + note: "MicroCause was replaced by the unified effect/Cause module in v4." +effect/Micro#causeFail: + replacement: "Cause.fail" + note: "MicroCause was replaced by the unified effect/Cause module in v4." +effect/Micro#causeInterrupt: + replacement: "Cause.interrupt" + note: "MicroCause was replaced by the unified effect/Cause module in v4. Takes an optional fiber id." +effect/Micro#causeIsDie: + replacement: "Cause.hasDies" + note: "v4 Cause aggregates multiple reasons, so tag refinements become reason queries: Cause.hasDies checks for Die reasons; use Cause.isDieReason for a single Reason value." +effect/Micro#causeIsFail: + replacement: "Cause.hasFails" + note: "v4 Cause aggregates multiple reasons, so tag refinements become reason queries: Cause.hasFails checks for Fail reasons; use Cause.isFailReason for a single Reason value." +effect/Micro#causeIsInterrupt: + replacement: "Cause.hasInterrupts" + note: "v4 Cause aggregates multiple reasons, so tag refinements become reason queries: Cause.hasInterrupts checks for Interrupt reasons (see also Cause.hasInterruptsOnly)." +effect/Micro#causeSquash: + replacement: "Cause.squash" + note: "Same behavior in the unified effect/Cause module." +effect/Micro#causeWithTrace: + replacement: "Cause.annotate" + note: "v4 causes carry structured annotations instead of a traces array; attach trace data with Cause.annotate (e.g. the Cause.StackTrace service). v4 also captures failure stack traces automatically." +effect/Micro#context: + replacement: "Effect.context" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#CurrentConcurrency: + replacement: "none" + note: "Removed in v4 (no fiber-wide concurrency reference). Pass a { concurrency } option directly to the operations that fan out, e.g. Effect.all or Effect.forEach." +effect/Micro#CurrentScheduler: + replacement: "References.Scheduler" + note: "The scheduler reference lives in effect/References (also exported from effect/Scheduler as Scheduler.Scheduler). Override it with Effect.provideService/Effect.updateService." +effect/Micro#delay: + replacement: "Effect.delay" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#Do: + replacement: "Effect.Do" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#either: + replacement: "Effect.result" + note: "Either was replaced by Result in v4: Effect.result yields Result.Result instead of Either." +effect/Micro#ensuring: + replacement: "Effect.ensuring" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#Error: + replacement: "Data.Error" + note: "The yieldable error base class constructor is Data.Error from effect/Data in v4." +effect/Micro#exit: + replacement: "Effect.exit" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#exitDie: + replacement: "Exit.die" + note: "MicroExit was replaced by the unified effect/Exit module in v4." +effect/Micro#exitFail: + replacement: "Exit.fail" + note: "MicroExit was replaced by the unified effect/Exit module in v4." +effect/Micro#exitFailCause: + replacement: "Exit.failCause" + note: "MicroExit was replaced by the unified effect/Exit module in v4." +effect/Micro#exitInterrupt: + replacement: "Exit.interrupt" + note: "MicroExit was replaced by the unified effect/Exit module in v4. Takes an optional fiber id." +effect/Micro#exitIsDie: + replacement: "Exit.hasDies" + note: "v4 exits carry a multi-reason Cause, so tag refinements become reason queries: Exit.hasDies checks the failure cause for Die reasons." +effect/Micro#exitIsFail: + replacement: "Exit.hasFails" + note: "v4 exits carry a multi-reason Cause, so tag refinements become reason queries: Exit.hasFails checks the failure cause for Fail reasons." +effect/Micro#exitIsFailure: + replacement: "Exit.isFailure" + note: "Same refinement in the unified effect/Exit module." +effect/Micro#exitIsInterrupt: + replacement: "Exit.hasInterrupts" + note: "v4 exits carry a multi-reason Cause, so tag refinements become reason queries: Exit.hasInterrupts checks the failure cause for Interrupt reasons." +effect/Micro#exitIsSuccess: + replacement: "Exit.isSuccess" + note: "Same refinement in the unified effect/Exit module." +effect/Micro#exitSucceed: + replacement: "Exit.succeed" + note: "MicroExit was replaced by the unified effect/Exit module in v4." +effect/Micro#exitVoid: + replacement: "Exit.void" + note: "MicroExit was replaced by the unified effect/Exit module in v4." +effect/Micro#exitVoidAll: + replacement: "Exit.asVoidAll" + note: "Renamed: exitVoidAll becomes Exit.asVoidAll in the unified effect/Exit module." +effect/Micro#fail: + replacement: "Effect.fail" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#failCause: + replacement: "Effect.failCause" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#failCauseSync: + replacement: "Effect.failCauseSync" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#failSync: + replacement: "Effect.failSync" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#fiberAwait: + replacement: "Fiber.await" + note: "Fiber operations moved to the effect/Fiber module in v4." +effect/Micro#fiberInterrupt: + replacement: "Fiber.interrupt" + note: "Fiber operations moved to the effect/Fiber module in v4." +effect/Micro#fiberInterruptAll: + replacement: "Fiber.interruptAll" + note: "Fiber operations moved to the effect/Fiber module in v4." +effect/Micro#fiberJoin: + replacement: "Fiber.join" + note: "Fiber operations moved to the effect/Fiber module in v4." +effect/Micro#filter: + replacement: "Effect.filter" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#filterMap: + replacement: "Effect.filterMap" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#filterOrFail: + replacement: "Effect.filterOrFail" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#filterOrFailCause: + replacement: "Effect.filterOrElse" + note: "No direct equivalent; use Effect.filterOrElse and fail with a cause in the fallback." + example: "Effect.filterOrElse(effect, predicate, { orElse: () => Effect.failCause(Cause.die(\"invalid\")) })" +effect/Micro#flatten: + replacement: "Effect.flatten" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#flip: + replacement: "Effect.flip" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#forkDaemon: + replacement: "Effect.forkDetach" + note: "Renamed: forkDaemon becomes Effect.forkDetach (fork detached from the parent's lifetime)." +effect/Micro#forkIn: + replacement: "Effect.forkIn" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#forkScoped: + replacement: "Effect.forkScoped" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#fromEither: + replacement: "Effect.fromResult" + note: "Either was replaced by Result in v4: convert Result.Result values with Effect.fromResult." +effect/Micro#fromOption: + replacement: "Effect.fromOption" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#ignore: + replacement: "Effect.ignore" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#ignoreLogged: + replacement: "Effect.ignore" + note: "Removed; log explicitly before ignoring." + example: "effect.pipe(Effect.tapCause((cause) => Effect.logError(cause)), Effect.ignore)" +effect/Micro#interrupt: + replacement: "Effect.interrupt" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#interruptible: + replacement: "Effect.interruptible" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#isMicro: + replacement: "Effect.isEffect" + note: "Micro values are plain Effects in v4; use Effect.isEffect." +effect/Micro#isMicroCause: + replacement: "Cause.isCause" + note: "MicroCause was replaced by the unified effect/Cause module in v4." +effect/Micro#isMicroExit: + replacement: "Exit.isExit" + note: "MicroExit was replaced by the unified effect/Exit module in v4." +effect/Micro#let: + replacement: "Effect.let" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#map: + replacement: "Effect.map" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#mapErrorCause: + replacement: "Effect.catchCause" + note: "No direct equivalent; transform the cause by catching it and re-failing." + example: "Effect.catchCause(effect, (cause) => Effect.failCause(Cause.map(cause, transformError)))" +effect/Micro#match: + replacement: "Effect.match" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#matchCause: + replacement: "Effect.matchCause" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#matchCauseEffect: + replacement: "Effect.matchCauseEffect" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#MaxOpsBeforeYield: + replacement: "References.MaxOpsBeforeYield" + note: "The reference lives in effect/References (also exported from effect/Scheduler). Override it with Effect.updateService." +effect/Micro#Micro: + replacement: "Effect.Effect" + note: "The Micro type is Effect.Effect in v4; the v4 Effect runtime is itself lightweight." +effect/Micro#Micro.Context: + replacement: "Effect.Services" + note: "Type extractor renamed: Micro.Context becomes Effect.Services in v4." +effect/Micro#Micro.Error: + replacement: "Effect.Error" + note: "Type extractor: Micro.Error becomes Effect.Error in v4." +effect/Micro#Micro.Success: + replacement: "Effect.Success" + note: "Type extractor: Micro.Success becomes Effect.Success in v4." +effect/Micro#MicroCause: + replacement: "Cause.Cause" + note: "MicroCause becomes Cause.Cause. Note v4 Cause holds a list of failure reasons (Fail | Die | Interrupt) rather than being a single tagged variant." +effect/Micro#MicroCause.Die: + replacement: "Cause.Die" + note: "The Die variant is a Reason in v4: Cause.Die from effect/Cause." +effect/Micro#MicroCause.Error: + replacement: "Cause.Cause.Error" + note: "Type extractor: use the Error helper in the Cause.Cause namespace to extract the error type." +effect/Micro#MicroCause.Fail: + replacement: "Cause.Fail" + note: "The Fail variant is a Reason in v4: Cause.Fail from effect/Cause." +effect/Micro#MicroCause.Interrupt: + replacement: "Cause.Interrupt" + note: "The Interrupt variant is a Reason in v4: Cause.Interrupt from effect/Cause." +effect/Micro#MicroCause.Proto: + replacement: "Cause.Cause.ReasonProto" + note: "Internal prototype type; the closest v4 equivalent is the ReasonProto interface in the Cause.Cause namespace. Rarely needed directly." +effect/Micro#MicroCauseTypeId: + replacement: "Cause.TypeId" + note: "Use Cause.TypeId from effect/Cause (value is \"~effect/Cause\")." +effect/Micro#MicroExit: + replacement: "Exit.Exit" + note: "MicroExit becomes Exit.Exit from effect/Exit. In v4 Exit is a subtype of Effect." +effect/Micro#MicroExit.Failure: + replacement: "Exit.Failure" + note: "MicroExit.Failure becomes Exit.Failure from effect/Exit." +effect/Micro#MicroExit.Proto: + replacement: "Exit.Exit.Proto" + note: "Internal prototype type; v4 exposes the shared base as Proto in the Exit.Exit namespace. Rarely needed directly." +effect/Micro#MicroExit.Success: + replacement: "Exit.Success" + note: "MicroExit.Success becomes Exit.Success from effect/Exit." +effect/Micro#MicroExitTypeId: + replacement: "none" + note: "v4 Exit is a subtype of Effect and has no dedicated TypeId; use Exit.isExit to identify exits." +effect/Micro#MicroFiber: + replacement: "Fiber.Fiber" + note: "MicroFiber becomes Fiber.Fiber from effect/Fiber." +effect/Micro#MicroFiber.Variance: + replacement: "none" + note: "Type-level variance helper with no public v4 equivalent; the v4 Fiber.Fiber interface carries variance directly." +effect/Micro#MicroFiberTypeId: + replacement: "none" + note: "No public TypeId on v4 fibers; use Fiber.isFiber to identify fibers." +effect/Micro#MicroIterator: + replacement: "Effect.EffectIterator" + note: "Renamed: MicroIterator becomes Effect.EffectIterator (generator support for Effect.gen)." +effect/Micro#MicroSchedule: + replacement: "Schedule.Schedule" + note: "v3 MicroSchedule was a plain function (attempt, elapsedMillis) => Option; v4 uses the first-class Schedule.Schedule type from effect/Schedule." +effect/Micro#MicroScheduler: + replacement: "Scheduler.Scheduler" + note: "The scheduler interface lives in effect/Scheduler in v4." +effect/Micro#MicroSchedulerDefault: + replacement: "Scheduler.MixedScheduler" + note: "The default task scheduler implementation in v4 is Scheduler.MixedScheduler from effect/Scheduler." +effect/Micro#MicroScope: + replacement: "Scope.Scope" + note: "MicroScope becomes Scope.Scope from effect/Scope; the closeable variant is Scope.Closeable." +effect/Micro#MicroScopeTypeId: + replacement: "none" + note: "No public TypeId on v4 scopes; use the Scope.Scope service key to access the current scope." +effect/Micro#MicroTypeLambda: + replacement: "Effect.EffectTypeLambda" + note: "Renamed: MicroTypeLambda becomes Effect.EffectTypeLambda." +effect/Micro#MicroUnify: + replacement: "Effect.EffectUnify" + note: "Renamed: MicroUnify becomes Effect.EffectUnify." +effect/Micro#MicroUnifyIgnore: + replacement: "none" + note: "Removed; v4 Effect declares its unify-ignore slot inline and exposes no named UnifyIgnore interface." +effect/Micro#NoSuchElementException: + replacement: "Cause.NoSuchElementError" + note: "Renamed and moved: NoSuchElementException becomes Cause.NoSuchElementError from effect/Cause." +effect/Micro#onExit: + replacement: "Effect.onExit" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#onInterrupt: + replacement: "Effect.onInterrupt" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#option: + replacement: "Effect.option" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#orDie: + replacement: "Effect.orDie" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#orElseSucceed: + replacement: "Effect.orElseSucceed" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#promise: + replacement: "Effect.promise" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#race: + replacement: "Effect.race" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#raceAll: + replacement: "Effect.raceAll" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#raceFirst: + replacement: "Effect.raceFirst" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#repeatExit: + replacement: "Effect.repeat" + note: "Removed; use Effect.repeat with while/until/times/schedule options. To inspect failures while looping, run the body through Effect.exit and repeat on the Exit value." + example: "Effect.repeat(Effect.exit(effect), { while: (exit) => Exit.isFailure(exit), times: 3 })" +effect/Micro#replicate: + replacement: "Effect.replicate" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#replicateEffect: + replacement: "Effect.replicateEffect" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#retry: + replacement: "Effect.retry" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#runFork: + replacement: "Effect.runFork" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#runPromise: + replacement: "Effect.runPromise" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#runPromiseExit: + replacement: "Effect.runPromiseExit" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#runSync: + replacement: "Effect.runSync" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#runSyncExit: + replacement: "Effect.runSyncExit" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#sandbox: + replacement: "Effect.sandbox" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#scheduleAddDelay: + replacement: "Schedule.addDelay" + note: "Moved to effect/Schedule; the callback now receives schedule Metadata and returns a Duration (optionally effectful)." +effect/Micro#scheduleExponential: + replacement: "Schedule.exponential" + note: "Moved to effect/Schedule; takes Duration input instead of raw millis." +effect/Micro#scheduleIntersect: + replacement: "Schedule.max" + note: "Intersection (recur while both recur, waiting for the slower) is Schedule.max([self, that]) in v4." +effect/Micro#scheduleRecurs: + replacement: "Schedule.recurs" + note: "Moved to effect/Schedule." +effect/Micro#scheduleSpaced: + replacement: "Schedule.spaced" + note: "Moved to effect/Schedule; takes Duration input instead of raw millis." +effect/Micro#scheduleUnion: + replacement: "Schedule.min" + note: "Union (recur while either recurs, waiting for the faster) is Schedule.min([self, that]) in v4." +effect/Micro#scheduleWithMaxDelay: + replacement: "Schedule.modifyDelay" + note: "No direct equivalent; clamp the delay with Schedule.modifyDelay." + example: "Schedule.modifyDelay(schedule, ({ delay }) => Duration.min(delay, \"10 seconds\"))" +effect/Micro#scheduleWithMaxElapsed: + replacement: "Schedule.upTo" + note: "Renamed: cap total elapsed time with Schedule.upTo({ duration })." + example: "Schedule.upTo(schedule, { duration: \"30 seconds\" })" +effect/Micro#scoped: + replacement: "Effect.scoped" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#scopeMake: + replacement: "Scope.make" + note: "Moved to effect/Scope: Scope.make returns Effect and accepts an optional finalizer strategy." +effect/Micro#scopeUnsafeMake: + replacement: "Scope.makeUnsafe" + note: "Renamed and moved: scopeUnsafeMake becomes Scope.makeUnsafe from effect/Scope." +effect/Micro#service: + replacement: "service" + note: "Micro was removed in v4, and services are Effects; yield or compose the service key directly in the rewritten Effect runtime." +effect/Micro#succeed: + replacement: "Effect.succeed" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#sync: + replacement: "Effect.sync" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#TaggedError: + replacement: "Data.TaggedError" + note: "The yieldable tagged error class constructor is Data.TaggedError from effect/Data in v4." +effect/Micro#tap: + replacement: "Effect.tap" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#tapDefect: + replacement: "Effect.tapDefect" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#tapError: + replacement: "Effect.tapError" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#tapErrorCause: + replacement: "Effect.tapCause" + note: "Renamed: tapErrorCause becomes Effect.tapCause." +effect/Micro#tapErrorCauseIf: + replacement: "Effect.tapCauseIf" + note: "Renamed: tapErrorCauseIf becomes Effect.tapCauseIf (see also Effect.tapCauseFilter for Filter-based matching)." +effect/Micro#timeout: + replacement: "Effect.timeout" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#TimeoutException: + replacement: "Cause.TimeoutError" + note: "Renamed and moved: TimeoutException becomes Cause.TimeoutError from effect/Cause (raised by Effect.timeout)." +effect/Micro#timeoutOption: + replacement: "Effect.timeoutOption" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#try: + replacement: "Effect.try" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#tryPromise: + replacement: "Effect.tryPromise" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#TypeId: + replacement: "Effect.TypeId" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#uninterruptibleMask: + replacement: "Effect.uninterruptibleMask" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#updateContext: + replacement: "Effect.updateContext" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#updateService: + replacement: "Effect.updateService" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#void: + replacement: "Effect.void" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#when: + replacement: "Effect.when" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#whileLoop: + replacement: "Effect.whileLoop" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." +effect/Micro#withConcurrency: + replacement: "none" + note: "Removed in v4 along with \"inherit\" concurrency; pass a { concurrency } option directly to each concurrent operation." + example: "Effect.forEach(items, handle, { concurrency: 10 })" +effect/Micro#withMicroFiber: + replacement: "Effect.withFiber" + note: "Renamed: withMicroFiber becomes Effect.withFiber, giving access to the current fiber." +effect/Micro#withTrace: + replacement: "Effect.withSpan" + note: "Removed; v4 captures failure stack traces automatically and cause annotations replace the traces array. For named tracing regions use Effect.withSpan." +effect/Micro#YieldableError: + replacement: "Cause.YieldableError" + note: "Moved: YieldableError lives in effect/Cause in v4." +effect/Micro#yieldFlush: + replacement: "none" + note: "Removed; access the current scheduler via the References.Scheduler service and call its flush() method directly if deterministic draining is needed." +effect/Micro#yieldNow: + replacement: "Effect.yieldNow" + note: "Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect." diff --git a/migration/annotations/effect__ModuleVersion.yaml b/migration/annotations/effect__ModuleVersion.yaml new file mode 100644 index 000000000..07ea5d5e6 --- /dev/null +++ b/migration/annotations/effect__ModuleVersion.yaml @@ -0,0 +1,3 @@ +effect/ModuleVersion: + replacement: none + note: The mutable module-version facility was removed; the v4 build version is private and effect/package.json is metadata, not an equivalent runtime API. No mutable version setter remains; the v3 runtime-isolation mechanism has no public v4 equivalent. diff --git a/migration/annotations/effect__MutableHashMap.yaml b/migration/annotations/effect__MutableHashMap.yaml new file mode 100644 index 000000000..201890730 --- /dev/null +++ b/migration/annotations/effect__MutableHashMap.yaml @@ -0,0 +1,6 @@ +"effect/MutableHashMap#MutableHashMap": + replacement: "MutableHashMap.MutableHashMap" + note: "The MutableHashMap model remains; use its public operations rather than depending on internal representation fields." +"effect/MutableHashMap#TypeId": + replacement: "none" + note: "The public MutableHashMap.TypeId was removed; the v4 marker is private." diff --git a/migration/annotations/effect__MutableHashSet.yaml b/migration/annotations/effect__MutableHashSet.yaml new file mode 100644 index 000000000..fcc4cef0e --- /dev/null +++ b/migration/annotations/effect__MutableHashSet.yaml @@ -0,0 +1,6 @@ +"effect/MutableHashSet#MutableHashSet": + replacement: "MutableHashSet.MutableHashSet" + note: "The MutableHashSet model remains; use its public operations rather than depending on internal representation fields." +"effect/MutableHashSet#TypeId": + replacement: "none" + note: "The public MutableHashSet.TypeId was removed; the v4 marker is private." diff --git a/migration/annotations/effect__MutableList.yaml b/migration/annotations/effect__MutableList.yaml new file mode 100644 index 000000000..c9cd12126 --- /dev/null +++ b/migration/annotations/effect__MutableList.yaml @@ -0,0 +1,36 @@ +"effect/MutableList#empty": + replacement: "MutableList.make" + note: "Constructor rename; MutableList.Empty is the take sentinel, not a constructor." +"effect/MutableList#forEach": + replacement: "MutableList.toArray + Array.forEach" + note: "No direct traversal helper remains; iterate a snapshot produced by MutableList.toArray." +"effect/MutableList#fromIterable": + replacement: "MutableList.make + MutableList.appendAll" + note: "Create an empty list with MutableList.make, then append the iterable with MutableList.appendAll." +"effect/MutableList#head": + replacement: "MutableList.toArrayN" + note: "Use MutableList.toArrayN(self, 1)[0]; the redesigned FIFO exposes buckets rather than the old Option-returning accessor." +"effect/MutableList#isEmpty": + replacement: "none" + note: "Read self.length === 0; no named isEmpty helper remains." +"effect/MutableList#length": + replacement: "none" + note: "Read the public self.length field; no named length helper remains." +"effect/MutableList#MutableList": + replacement: "MutableList.MutableList" + note: "The model remains but was redesigned from an iterable doubly linked list into a bucketed FIFO structure." +"effect/MutableList#pop": + replacement: "none" + note: "The bucketed FIFO has no remove-last operation; migrate code to front draining or use a different mutable collection." +"effect/MutableList#reset": + replacement: "MutableList.clear" + note: "Direct behavioral replacement; the return type is now void." +"effect/MutableList#shift": + replacement: "MutableList.take" + note: "Front removal remains synchronous, but emptiness is reported with MutableList.Empty instead of undefined." +"effect/MutableList#tail": + replacement: "MutableList.toArray" + note: "Use MutableList.toArray(self).at(-1); the public self.tail field is an internal bucket, not the old last-element accessor." +"effect/MutableList#TypeId": + replacement: "none" + note: "V4 MutableList has no public runtime marker." diff --git a/migration/annotations/effect__MutableQueue.yaml b/migration/annotations/effect__MutableQueue.yaml new file mode 100644 index 000000000..6ba01f209 --- /dev/null +++ b/migration/annotations/effect__MutableQueue.yaml @@ -0,0 +1,42 @@ +"effect/MutableQueue#bounded": + replacement: "Queue.dropping" + note: "The replacement constructor is effectful; dropping preserves the old immediate rejection when a bounded queue is full." +"effect/MutableQueue#capacity": + replacement: "none" + note: "Read queue.capacity on the replacement Queue; unbounded queues expose Infinity." +"effect/MutableQueue#EmptyMutableQueue": + replacement: "none" + note: "Queue.poll reports emptiness with Option.none, so no default sentinel is required." +"effect/MutableQueue#isEmpty": + replacement: "Queue.sizeUnsafe" + note: "Use Queue.sizeUnsafe(queue) === 0, or map the effectful Queue.size result." +"effect/MutableQueue#isFull": + replacement: "Queue.isFullUnsafe" + note: "Use Queue.isFullUnsafe for synchronous inspection or Queue.isFull for an Effect result." +"effect/MutableQueue#length": + replacement: "Queue.sizeUnsafe" + note: "Use Queue.sizeUnsafe for synchronous inspection or Queue.size for an Effect result." +"effect/MutableQueue#MutableQueue": + replacement: "Queue.Queue" + note: "The replacement Queue is effectful, lifecycle-aware, and not Iterable." +"effect/MutableQueue#MutableQueue.Empty": + replacement: "none" + note: "Use the Option returned by Queue.poll; the old empty sentinel was removed." +"effect/MutableQueue#offer": + replacement: "Queue.offerUnsafe" + note: "Use with Queue.dropping to preserve the old synchronous boolean rejection at capacity; Queue.offer is the effectful form." +"effect/MutableQueue#offerAll": + replacement: "Queue.offerAllUnsafe" + note: "The synchronous replacement returns the rejected remainder as an Array; Queue.offerAll is the effectful form." +"effect/MutableQueue#poll": + replacement: "Queue.poll" + note: "Polling is now effectful and returns Option rather than accepting a default; Queue.takeUnsafe is the low-level synchronous alternative." +"effect/MutableQueue#pollUpTo": + replacement: "Queue.takeUnsafe" + note: "No direct non-blocking take-up-to helper remains; repeatedly call Queue.takeUnsafe and collect successful exits without waiting." +"effect/MutableQueue#TypeId": + replacement: "none" + note: "The MutableQueue module and its public marker were removed." +"effect/MutableQueue#unbounded": + replacement: "Queue.unbounded" + note: "The unbounded replacement constructor is effectful." diff --git a/migration/annotations/effect__MutableRef.yaml b/migration/annotations/effect__MutableRef.yaml new file mode 100644 index 000000000..e41e4d192 --- /dev/null +++ b/migration/annotations/effect__MutableRef.yaml @@ -0,0 +1,6 @@ +"effect/MutableRef#MutableRef": + replacement: "MutableRef.MutableRef" + note: "The MutableRef model remains; use its public operations rather than depending on internal representation fields." +"effect/MutableRef#TypeId": + replacement: "none" + note: "The public MutableRef.TypeId was removed; the v4 marker is private." diff --git a/migration/annotations/effect__Number.yaml b/migration/annotations/effect__Number.yaml new file mode 100644 index 000000000..b1e77db36 --- /dev/null +++ b/migration/annotations/effect__Number.yaml @@ -0,0 +1,18 @@ +"effect/Number#greaterThan": + replacement: "Number.isGreaterThan" + note: "Renamed with the v4 is-prefix." +"effect/Number#greaterThanOrEqualTo": + replacement: "Number.isGreaterThanOrEqualTo" + note: "Renamed with the v4 is-prefix." +"effect/Number#lessThan": + replacement: "Number.isLessThan" + note: "Renamed with the v4 is-prefix." +"effect/Number#lessThanOrEqualTo": + replacement: "Number.isLessThanOrEqualTo" + note: "Renamed with the v4 is-prefix." +"effect/Number#negate": + replacement: "Number.multiply(-1)" + note: "Use Number.multiply(n, -1), or Number.multiply(-1) as the equivalent unary function." +"effect/Number#unsafeDivide": + replacement: "Number.divideUnsafe" + note: "Renamed; v4 throws for zero whereas v3 raw division returned Infinity or NaN." diff --git a/migration/annotations/effect__Option.yaml b/migration/annotations/effect__Option.yaml new file mode 100644 index 000000000..75f930ac9 --- /dev/null +++ b/migration/annotations/effect__Option.yaml @@ -0,0 +1,45 @@ +"effect/Option#ap": + replacement: "Option.zipWith" + note: "Use Option.zipWith(self, that, (f, a) => f(a)); v4 has no Option.ap." +"effect/Option#flatMapNullable": + replacement: "Option.flatMapNullishOr" + note: "Renamed with v4 nullish-or terminology." +"effect/Option#fromNullable": + replacement: "Option.fromNullishOr" + note: "Renamed with v4 nullish-or terminology." +"effect/Option#getEquivalence": + replacement: "Option.makeEquivalence" + note: "Renamed from getEquivalence to makeEquivalence." +"effect/Option#getLeft": + replacement: "Option.getFailure" + note: "Either input became Result input, and Left became Failure." +"effect/Option#getOrder": + replacement: "Option.makeOrder" + note: "Renamed from getOrder to makeOrder." +"effect/Option#getRight": + replacement: "Option.getSuccess" + note: "Either input became Result input, and Right became Success." +"effect/Option#liftNullable": + replacement: "Option.liftNullishOr" + note: "Renamed with v4 nullish-or terminology." +"effect/Option#None": + replacement: "Option.None" + note: "The variant remains, but Option is no longer an Effect or STM subtype." +"effect/Option#Option": + replacement: "Option.Option" + note: "The union type remains, but Option is no longer an Effect or STM subtype." +"effect/Option#OptionUnify": + replacement: "Option.OptionUnify" + note: "The unification hook remains under the same name." +"effect/Option#OptionUnifyIgnore": + replacement: "Option.OptionUnifyIgnore" + note: "The marker remains, without the v3 Effect, Tag, and Either augmentation fields." +"effect/Option#orElseEither": + replacement: "Option.orElseResult" + note: "Either was replaced by Result; source tracking now uses Failure and Success." +"effect/Option#Some": + replacement: "Option.Some" + note: "The variant remains, but Option is no longer an Effect or STM subtype." +"effect/Option#TypeId": + replacement: "none" + note: "The v4 Option brand is private and no public TypeId is exported." diff --git a/migration/annotations/effect__Order.yaml b/migration/annotations/effect__Order.yaml new file mode 100644 index 000000000..365449cb7 --- /dev/null +++ b/migration/annotations/effect__Order.yaml @@ -0,0 +1,63 @@ +"effect/Order#all": + replacement: "Order.Tuple([...collection])" + note: "Materialize the comparator iterable for Tuple. V4 evaluates the configured tuple instead of stopping at the shorter input; use Order.make for intentional v3 prefix semantics." +"effect/Order#array": + replacement: "Order.Array" + note: "Capitalized constructor name; lexicographic array ordering and the length tie-break are unchanged." +"effect/Order#between": + replacement: "Order.isBetween" + note: "Renamed with the v4 is-prefix; inclusive bounds and call forms are unchanged." +"effect/Order#bigint": + replacement: "Order.BigInt" + note: "Capitalized instance name; bigint ordering is unchanged." +"effect/Order#boolean": + replacement: "Order.Boolean" + note: "Capitalized instance name; false remains ordered before true." +"effect/Order#combineAll": + replacement: "Order.combineAll" + note: "Retained with the same left-to-right tie-breaking and empty-iterable result." +"effect/Order#combineMany": + replacement: "Order.combine(self, Order.combineAll(collection))" + note: "Compose combine with combineAll; the dedicated dual combineMany helper was removed." +"effect/Order#empty": + replacement: "Order.alwaysEqual" + note: "Renamed constructor; call as Order.alwaysEqual() to produce an order that always returns zero." +"effect/Order#greaterThan": + replacement: "Order.isGreaterThan" + note: "Renamed with the v4 is-prefix; curried and uncurried comparisons are retained." +"effect/Order#greaterThanOrEqualTo": + replacement: "Order.isGreaterThanOrEqualTo" + note: "Renamed with the v4 is-prefix; curried and uncurried comparisons are retained." +"effect/Order#lessThan": + replacement: "Order.isLessThan" + note: "Renamed with the v4 is-prefix; curried and uncurried comparisons are retained." +"effect/Order#lessThanOrEqualTo": + replacement: "Order.isLessThanOrEqualTo" + note: "Renamed with the v4 is-prefix; curried and uncurried comparisons are retained." +"effect/Order#make": + replacement: "Order.make" + note: "Retained with the same comparator contract and reference-equality fast path." +"effect/Order#number": + replacement: "Order.Number" + note: "Capitalized instance name. V4 orders NaN below non-NaN values and all NaNs equally; use a custom Order.make to preserve v3 edge behavior." +"effect/Order#Order": + replacement: "Order.Order" + note: "The callable type is retained; its return type remains the -1 | 0 | 1 Ordering union." +"effect/Order#product": + replacement: "Order.Tuple([self, that])" + note: "Replace the dual two-order helper with the single-array Tuple constructor." +"effect/Order#productMany": + replacement: "Order.Tuple([self, ...collection])" + note: "Materialize the order iterable in one Tuple call; v4 evaluates every configured comparator for short inputs." +"effect/Order#reverse": + replacement: "Order.flip" + note: "Direct rename; the replacement reverses comparison by swapping the operands." +"effect/Order#string": + replacement: "Order.String" + note: "Capitalized instance name; case-sensitive JavaScript lexicographic ordering is unchanged." +"effect/Order#struct": + replacement: "Order.Struct" + note: "Capitalized constructor name; field-order tie-breaking is unchanged." +"effect/Order#tuple": + replacement: "Order.Tuple([orderA, orderB, ...])" + note: "Capitalized constructor now takes one comparator array instead of rest arguments and evaluates every configured position." diff --git a/migration/annotations/effect__Ordering.yaml b/migration/annotations/effect__Ordering.yaml new file mode 100644 index 000000000..68af19108 --- /dev/null +++ b/migration/annotations/effect__Ordering.yaml @@ -0,0 +1,6 @@ +"effect/Ordering#combineAll": + replacement: "Ordering.Reducer.combineAll" + note: "The combination operation moved to the exported Reducer; first-nonzero and empty-input behavior are unchanged." +"effect/Ordering#combineMany": + replacement: "Ordering.Reducer.combineAll(Iterable.prepend(collection, self))" + note: "Prepend the initial ordering before reducing to preserve v3 short-circuiting without consuming collection when self is nonzero." diff --git a/migration/annotations/effect__ParseResult.yaml b/migration/annotations/effect__ParseResult.yaml new file mode 100644 index 000000000..72c647921 --- /dev/null +++ b/migration/annotations/effect__ParseResult.yaml @@ -0,0 +1,134 @@ +"effect/ParseResult#Forbidden": + replacement: "SchemaIssue.Forbidden" + note: "Forbidden failures use the v4 SchemaIssue class; its constructor takes issue annotations plus optional input and parse options, retaining input only when reportInput is true." +"effect/ParseResult#ArrayFormatter": + replacement: "SchemaIssue.makeFormatterStandardSchemaV1" + note: "Format error.issue with the Standard Schema formatter." + example: "SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues" +"effect/ParseResult#ArrayFormatterIssue": + replacement: "StandardSchemaV1.FailureResult[\"issues\"][number]" + note: "Use the Standard Schema issue shape returned by makeFormatterStandardSchemaV1." +"effect/ParseResult#Composite": + replacement: "SchemaIssue.Composite" + note: "Composite parse failures moved to SchemaIssue. The v4 constructor takes the failing AST and an array of nested issues; input is retained only when reportInput is enabled." +"effect/ParseResult#DeclarationDecodeUnknown": + replacement: "SchemaGetter.Getter" + note: "Custom declaration decoding now uses SchemaGetter values and Schema.declare annotations." +"effect/ParseResult#decodeEither": + replacement: "Schema.decodeExit" + note: "Either parsing was replaced by Exit parsing." +"effect/ParseResult#decodePromise": + replacement: "Schema.decodePromise" + note: "Parsing helpers moved onto Schema and now fail with SchemaError." +"effect/ParseResult#decodeSync": + replacement: "Schema.decodeSync" + note: "Parsing helpers moved onto Schema and now throw SchemaError." +"effect/ParseResult#DecodeUnknown": + replacement: "Schema.decodeUnknownEffect" + note: "Use the function type returned by Schema.decodeUnknownEffect." +"effect/ParseResult#decodeUnknownEither": + replacement: "Schema.decodeUnknownExit" + note: "Either parsing was replaced by Exit parsing." +"effect/ParseResult#decodeUnknownPromise": + replacement: "Schema.decodeUnknownPromise" + note: "Parsing helpers moved onto Schema and now reject with SchemaError." +"effect/ParseResult#decodeUnknownSync": + replacement: "Schema.decodeUnknownSync" + note: "Parsing helpers moved onto Schema and now throw SchemaError." +"effect/ParseResult#eitherOrUndefined": + replacement: "none" + note: "This ParseResult internal optimization was removed; use Effect, Exit, Option, or Result combinators directly." +"effect/ParseResult#encodeEither": + replacement: "Schema.encodeExit" + note: "Either encoding was replaced by Exit encoding." +"effect/ParseResult#encodeSync": + replacement: "Schema.encodeSync" + note: "Encoding helpers moved onto Schema and now throw SchemaError." +"effect/ParseResult#encodeUnknownEither": + replacement: "Schema.encodeUnknownExit" + note: "Either encoding was replaced by Exit encoding." +"effect/ParseResult#encodeUnknownSync": + replacement: "Schema.encodeUnknownSync" + note: "Encoding helpers moved onto Schema and now throw SchemaError." +"effect/ParseResult#fail": + replacement: "Effect.fail" + note: "Schema transformations now use Effect and fail with SchemaIssue.Issue." +"effect/ParseResult#flatMap": + replacement: "Effect.flatMap" + note: "Schema transformations now use Effect combinators." +"effect/ParseResult#isComposite": + replacement: "SchemaIssue.Composite" + note: "Narrow with instanceof SchemaIssue.Composite or inspect the issue _tag." +"effect/ParseResult#isParseError": + replacement: "Schema.isSchemaError" + note: "ParseError was replaced by SchemaError." +"effect/ParseResult#map": + replacement: "Effect.map" + note: "Schema transformations now use Effect combinators." +"effect/ParseResult#Missing": + replacement: "SchemaIssue.MissingKey" + note: "Missing-key failures use the v4 SchemaIssue class." +"effect/ParseResult#orElse": + replacement: "Effect.orElse" + note: "Schema transformations now use Effect combinators." +"effect/ParseResult#Pointer": + replacement: "SchemaIssue.Pointer" + note: "Path-qualified failures moved to SchemaIssue. Construct them with the property path and nested issue; rejected input belongs to the nested issue when reportInput is enabled." +"effect/ParseResult#parseError": + replacement: "Schema.SchemaError" + note: "Construct a SchemaError from a SchemaIssue.Issue." + example: "new Schema.SchemaError(issue)" +"effect/ParseResult#ParseErrorTypeId": + replacement: "none" + note: "The public symbol was removed; use Schema.isSchemaError for runtime narrowing." +"effect/ParseResult#ParseIssue": + replacement: "SchemaIssue.Issue" + note: "The structured parse issue union moved to SchemaIssue." +"effect/ParseResult#ParseResultFormatter": + replacement: "SchemaIssue.Formatter" + note: "Issue formatter types moved to SchemaIssue." +"effect/ParseResult#Refinement": + replacement: "SchemaIssue.Filter" + note: "Refinement failures are represented as filter issues in v4." +"effect/ParseResult#SingleOrNonEmpty": + replacement: "ReadonlyArray" + note: "This ParseResult helper type was removed; use an explicit value-or-non-empty-array type when still needed." +"effect/ParseResult#succeed": + replacement: "Effect.succeed" + note: "Schema transformations now use Effect." +"effect/ParseResult#TreeFormatter": + replacement: "SchemaIssue.defaultFormatter" + note: "Use the default SchemaIssue string formatter." + example: "SchemaIssue.defaultFormatter(issue)" +"effect/ParseResult#Transformation": + replacement: "SchemaIssue.Encoding" + note: "Transformation-stage failures are Encoding issues in v4. They retain the failing AST and nested issue; the old Encoded, Transformation, and Type kind discriminator was removed." +"effect/ParseResult#try": + replacement: "Effect.try" + note: "Schema transformations now use Effect and map thrown errors to SchemaIssue values." +"effect/ParseResult#Type": + replacement: "SchemaIssue.InvalidType" + note: "Type mismatches use the v4 SchemaIssue class." +"effect/ParseResult#Unexpected": + replacement: "SchemaIssue.UnexpectedKey" + note: "Unexpected object keys use the v4 SchemaIssue class." +"effect/ParseResult#validate": + replacement: "Schema.decodeEffect + Schema.toType" + note: "Validation-only parsers were removed; decode the type-side schema instead." + example: "Schema.decodeEffect(Schema.toType(schema))" +"effect/ParseResult#validateEither": + replacement: "Schema.decodeExit + Schema.toType" + note: "Validation-only parsers were removed; decode the type-side schema instead." + example: "Schema.decodeExit(Schema.toType(schema))" +"effect/ParseResult#validateOption": + replacement: "Schema.decodeOption + Schema.toType" + note: "Validation-only parsers were removed; decode the type-side schema instead." + example: "Schema.decodeOption(Schema.toType(schema))" +"effect/ParseResult#validatePromise": + replacement: "Schema.decodePromise + Schema.toType" + note: "Validation-only parsers were removed; decode the type-side schema instead." + example: "Schema.decodePromise(Schema.toType(schema))" +"effect/ParseResult#validateSync": + replacement: "Schema.decodeSync + Schema.toType" + note: "Validation-only parsers were removed; decode the type-side schema instead." + example: "Schema.decodeSync(Schema.toType(schema))" diff --git a/migration/annotations/effect__PartitionedSemaphore.yaml b/migration/annotations/effect__PartitionedSemaphore.yaml new file mode 100644 index 000000000..f7ae84b95 --- /dev/null +++ b/migration/annotations/effect__PartitionedSemaphore.yaml @@ -0,0 +1,6 @@ +effect/PartitionedSemaphore#PartitionedSemaphore: + replacement: "PartitionedSemaphore.PartitionedSemaphore" + note: "The model remains and now also exposes capacity, available, take, release, withPermit, and conditional permit operations." +effect/PartitionedSemaphore#TypeId: + replacement: "PartitionedSemaphore.PartitionedTypeId" + note: "The public type id was renamed to distinguish it from the regular Semaphore type id." diff --git a/migration/annotations/effect__Pipeable.yaml b/migration/annotations/effect__Pipeable.yaml new file mode 100644 index 000000000..b4a746b9e --- /dev/null +++ b/migration/annotations/effect__Pipeable.yaml @@ -0,0 +1,3 @@ +"effect/Pipeable#PipeableConstructor": + replacement: "Pipeable.PipeableConstructor" + note: "Still exported; its rest arguments are ReadonlyArray in v4, so make explicit constructor typings readonly-compatible." diff --git a/migration/annotations/effect__Pool.yaml b/migration/annotations/effect__Pool.yaml new file mode 100644 index 000000000..6cd929382 --- /dev/null +++ b/migration/annotations/effect__Pool.yaml @@ -0,0 +1,15 @@ +effect/Pool#Pool: + replacement: "Pool.Pool" + note: "The model remains but is now Pipeable rather than an Effect subtype; borrow resources explicitly with Pool.get." +effect/Pool#Pool.Variance: + replacement: "none" + note: "The public Pool variance marker was removed; use Pool.Pool directly." +effect/Pool#PoolTypeId: + replacement: "none" + note: "The Pool type id is internal in v4; use Pool.isPool for runtime refinement." +effect/Pool#PoolUnify: + replacement: "none" + note: "Pool is no longer an Effect subtype, so its Effect unification helper was removed; call Pool.get explicitly." +effect/Pool#PoolUnifyIgnore: + replacement: "none" + note: "Pool is no longer an Effect subtype, so its Effect unification ignore marker was removed." diff --git a/migration/annotations/effect__Predicate.yaml b/migration/annotations/effect__Predicate.yaml new file mode 100644 index 000000000..5649916c9 --- /dev/null +++ b/migration/annotations/effect__Predicate.yaml @@ -0,0 +1,111 @@ +"effect/Predicate#all": + replacement: "Predicate.Tuple(Array.from(collection))" + note: "Use positional Tuple, materializing an Iterable when needed. V4 checks every configured position instead of accepting missing input values." +"effect/Predicate#every": + replacement: "Predicate.every" + note: "Retained with the same AND semantics, short-circuiting, and true result for an empty collection." +"effect/Predicate#isBigInt": + replacement: "Predicate.isBigInt" + note: "Retained with the same bigint refinement." +"effect/Predicate#isBoolean": + replacement: "Predicate.isBoolean" + note: "Retained with the same boolean refinement." +"effect/Predicate#isDate": + replacement: "Predicate.isDate" + note: "Retained with the same instanceof Date check." +"effect/Predicate#isError": + replacement: "Predicate.isError" + note: "Retained with the same instanceof Error check." +"effect/Predicate#isFunction": + replacement: "Predicate.isFunction" + note: "Retained with the same function refinement." +"effect/Predicate#isIterable": + replacement: "Predicate.isIterable" + note: "Retained; strings and values exposing Symbol.iterator are still accepted." +"effect/Predicate#isMap": + replacement: "Predicate.isMap" + note: "Retained with the same instanceof Map check." +"effect/Predicate#isNever": + replacement: "Predicate.isNever" + note: "Retained as the always-false refinement." +"effect/Predicate#isNotNull": + replacement: "Predicate.isNotNull" + note: "Retained; undefined still passes while null is excluded." +"effect/Predicate#isNotNullable": + replacement: "Predicate.isNotNullish" + note: "Renamed to use nullish terminology; it still excludes null and undefined." +"effect/Predicate#isNotUndefined": + replacement: "Predicate.isNotUndefined" + note: "Retained; null still passes while undefined is excluded." +"effect/Predicate#isNull": + replacement: "Predicate.isNull" + note: "Retained with the same strict null refinement." +"effect/Predicate#isNullable": + replacement: "Predicate.isNullish" + note: "Renamed to use nullish terminology. The guard now narrows with A & (null | undefined), including unknown inputs correctly." +"effect/Predicate#isNumber": + replacement: "Predicate.isNumber" + note: "Retained; NaN and infinite numbers still pass." +"effect/Predicate#isObject": + replacement: "Predicate.isObjectKeyword" + note: "Use isObjectKeyword to preserve v3 behavior accepting arrays and functions. V4 isObject has the former record-like semantics instead." +"effect/Predicate#isPromise": + replacement: "Predicate.isPromise" + note: "Retained as the structural check for callable then and catch properties." +"effect/Predicate#isPromiseLike": + replacement: "Predicate.isPromiseLike" + note: "Retained as the structural check for a callable then property." +"effect/Predicate#isReadonlyRecord": + replacement: "Predicate.isReadonlyObject" + note: "Renamed; runtime behavior is unchanged and the index-key type now explicitly includes numbers." +"effect/Predicate#isRecord": + replacement: "Predicate.isObject" + note: "Renamed; it still accepts non-null, non-array objects and now narrows with PropertyKey indexes." +"effect/Predicate#isRegExp": + replacement: "Predicate.isRegExp" + note: "Retained with the same instanceof RegExp check." +"effect/Predicate#isSet": + replacement: "Predicate.isSet" + note: "Retained with the same instanceof Set check." +"effect/Predicate#isString": + replacement: "Predicate.isString" + note: "Retained with the same primitive string refinement." +"effect/Predicate#isSymbol": + replacement: "Predicate.isSymbol" + note: "Retained with the same symbol refinement." +"effect/Predicate#isTruthy": + replacement: "Predicate.isTruthy" + note: "Retained as a plain boolean predicate using JavaScript truthiness." +"effect/Predicate#isUint8Array": + replacement: "Predicate.isUint8Array" + note: "Retained with the same instanceof Uint8Array check." +"effect/Predicate#isUndefined": + replacement: "Predicate.isUndefined" + note: "Retained with the same strict undefined refinement." +"effect/Predicate#isUnknown": + replacement: "Predicate.isUnknown" + note: "Retained as the always-true refinement." +"effect/Predicate#not": + replacement: "Predicate.not" + note: "Retained with the same boolean negation; refinements still become plain predicates." +"effect/Predicate#Predicate": + replacement: "Predicate.Predicate" + note: "The callable interface is retained. Predicate.Any now uses any rather than never, which can affect generic inference." +"effect/Predicate#product": + replacement: "Predicate.Tuple([self, that])" + note: "Replace the two-position product helper with the Tuple constructor." +"effect/Predicate#productMany": + replacement: "Predicate.Tuple([self, ...Array.from(collection)])" + note: "Materialize the predicate iterable in one Tuple call; v4 checks missing tail positions as undefined." +"effect/Predicate#Refinement": + replacement: "Predicate.Refinement" + note: "The refinement interface and its In, Out, and Any namespace types are retained." +"effect/Predicate#some": + replacement: "Predicate.some" + note: "Retained with the same OR semantics, short-circuiting, and false result for an empty collection." +"effect/Predicate#struct": + replacement: "Predicate.Struct" + note: "Capitalized constructor name; field checks and refinement-aware typing are retained." +"effect/Predicate#tuple": + replacement: "Predicate.Tuple([p1, p2, ...])" + note: "Capitalized constructor now takes one predicate array instead of rest arguments and checks missing positions as undefined." diff --git a/migration/annotations/effect__Pretty.yaml b/migration/annotations/effect__Pretty.yaml new file mode 100644 index 000000000..1a5c14d41 --- /dev/null +++ b/migration/annotations/effect__Pretty.yaml @@ -0,0 +1,13 @@ +"effect/Pretty#make": + replacement: "Schema.toFormatter" + note: "Formatter derivation moved onto Schema." + example: "Schema.toFormatter(schema)" +"effect/Pretty#match": + replacement: "Schema.toFormatter" + note: "The compiler match table was removed; customize traversal with the toFormatter onBefore option." +"effect/Pretty#Pretty": + replacement: "Formatter.Formatter" + note: "The formatter function type is now exported by Formatter." +"effect/Pretty#PrettyAnnotation": + replacement: "Schema.Annotations.ToFormatter.Declaration" + note: "Custom declaration formatter annotations now use the toFormatter key in Schema.Annotations." diff --git a/migration/annotations/effect__PubSub.yaml b/migration/annotations/effect__PubSub.yaml new file mode 100644 index 000000000..cee33218f --- /dev/null +++ b/migration/annotations/effect__PubSub.yaml @@ -0,0 +1,3 @@ +effect/PubSub#PubSub: + replacement: "PubSub.PubSub" + note: "The model remains but no longer extends Queue.Enqueue; replace Queue operations with explicit PubSub.publish, PubSub.publishAll, and PubSub.subscribe calls." diff --git a/migration/annotations/effect__Queue.yaml b/migration/annotations/effect__Queue.yaml new file mode 100644 index 000000000..d0eab5568 --- /dev/null +++ b/migration/annotations/effect__Queue.yaml @@ -0,0 +1,81 @@ +effect/Queue#awaitShutdown: + replacement: "Queue.await" + note: "Queue completion now includes normal end and failure; Queue.await waits for Done and propagates non-Done terminal causes." +effect/Queue#BackingQueue: + replacement: "none" + note: "Custom backing queues were removed by the v4 Queue rewrite; use Queue.make and its built-in capacity and strategy options." +effect/Queue#BackingQueueTypeId: + replacement: "none" + note: "BackingQueue and its public type id were removed." +effect/Queue#backPressureStrategy: + replacement: "Queue.make({ strategy: \"suspend\" })" + note: "Strategies are now constructor options rather than public Strategy values; suspend is the default." +effect/Queue#BaseQueue: + replacement: "Queue.Enqueue | Queue.Dequeue" + note: "The shared BaseQueue interface was removed; accept the required enqueue or dequeue capability and call Queue operations explicitly." +effect/Queue#capacity: + replacement: "queue.capacity" + note: "Capacity is now a property on Queue.Enqueue and Queue.Dequeue rather than a module function." +effect/Queue#Dequeue: + replacement: "Queue.Dequeue" + note: "The model remains and gains an error parameter, but is no longer an Effect subtype; use Queue.take explicitly." +effect/Queue#DequeueTypeId: + replacement: "Queue.isDequeue" + note: "The dequeue type id is internal in v4; use Queue.isDequeue for runtime refinement." +effect/Queue#DequeueUnify: + replacement: "none" + note: "Queue.Dequeue is no longer an Effect subtype, so its Effect unification helper was removed." +effect/Queue#DequeueUnifyIgnore: + replacement: "none" + note: "Queue.Dequeue is no longer an Effect subtype, so its Effect unification ignore marker was removed." +effect/Queue#droppingStrategy: + replacement: "Queue.make({ strategy: \"dropping\" })" + note: "Strategies are now constructor options rather than public Strategy values; Queue.dropping is the bounded convenience constructor." +effect/Queue#Enqueue: + replacement: "Queue.Enqueue" + note: "The write-side model remains, gains an error parameter, and is operated through Queue.offer and related functions." +effect/Queue#EnqueueTypeId: + replacement: "Queue.isEnqueue" + note: "The enqueue type id is internal in v4; use Queue.isEnqueue for runtime refinement." +effect/Queue#isEmpty: + replacement: "Effect.map(Queue.size(self), (size) => size === 0)" + note: "The dedicated helper was removed; derive emptiness from Queue.size." +effect/Queue#Queue: + replacement: "Queue.Queue" + note: "The model remains, gains an error parameter and completion signaling, and is no longer an Effect subtype; use Queue.take explicitly." +effect/Queue#Queue.BackingQueueVariance: + replacement: "none" + note: "BackingQueue and its variance marker were removed by the v4 Queue rewrite." +effect/Queue#Queue.DequeueVariance: + replacement: "Queue.Dequeue.Variance" + note: "The read-side variance marker moved under the Queue.Dequeue namespace and now includes the error type." +effect/Queue#Queue.EnqueueVariance: + replacement: "Queue.Enqueue.Variance" + note: "The write-side variance marker moved under the Queue.Enqueue namespace and now includes the error type." +effect/Queue#Queue.StrategyVariance: + replacement: "none" + note: "Public Strategy values and their variance marker were removed; select a string strategy when constructing the Queue." +effect/Queue#QueueStrategyTypeId: + replacement: "none" + note: "Public Strategy values and their type id were removed." +effect/Queue#QueueUnify: + replacement: "none" + note: "Queue is no longer an Effect subtype, so its Effect unification helper was removed; call Queue.take explicitly." +effect/Queue#QueueUnifyIgnore: + replacement: "none" + note: "Queue is no longer an Effect subtype, so its Effect unification ignore marker was removed." +effect/Queue#slidingStrategy: + replacement: "Queue.make({ strategy: \"sliding\" })" + note: "Strategies are now constructor options rather than public Strategy values; Queue.sliding is the bounded convenience constructor." +effect/Queue#Strategy: + replacement: "\"suspend\" | \"dropping\" | \"sliding\"" + note: "The pluggable Strategy interface was removed; choose one of the built-in strategy strings in Queue.make." +effect/Queue#takeUpTo: + replacement: "Queue.poll" + note: "No direct bounded batch helper remains; repeatedly call non-blocking Queue.poll up to the limit, or use Queue.clear when taking every buffered value is acceptable." +effect/Queue#unsafeOffer: + replacement: "Queue.offerUnsafe" + note: "The unsafe suffix moved to the end." +effect/Queue#isShutdown: + replacement: "queue.state._tag === \"Done\"" + note: "The dedicated helper was removed; inspect the public queue lifecycle state. Done includes normal completion and failure, not only explicit shutdown." diff --git a/migration/annotations/effect__Random.yaml b/migration/annotations/effect__Random.yaml new file mode 100644 index 000000000..74bee66bf --- /dev/null +++ b/migration/annotations/effect__Random.yaml @@ -0,0 +1,18 @@ +effect/Random#fixed: + replacement: "Effect.provideService(Random.Random, customRandom)" + note: "No exact built-in equivalent remains. For deterministic tests, provide a cycling service implementing nextIntUnsafe and nextDoubleUnsafe; map non-number sequences explicitly." +effect/Random#make: + replacement: "Random.withSeed" + note: "Replace service construction and withRandom with Random.withSeed(seed)(program). V4 accepts string or number, returns an Effect transformation, and uses a different PRNG, so sequences are not v3-compatible." +effect/Random#nextRange: + replacement: "Random.nextBetween" + note: "Direct rename; both produce a floating-point value in the half-open range [min, max)." +effect/Random#Random: + replacement: "Random.Random" + note: "The context key is now a Context.Reference whose low-level service only has nextIntUnsafe and nextDoubleUnsafe. Prefer module operations; custom providers implement those two primitives." +effect/Random#RandomTypeId: + replacement: "none" + note: "The service is structural and no longer carries a public RandomTypeId brand." +effect/Random#randomWith: + replacement: "Random.Random.use" + note: "Use Random.Random.use for raw service access. Prefer replacing callbacks that selected an old method with the corresponding module-level Random operation." diff --git a/migration/annotations/effect__RateLimiter.yaml b/migration/annotations/effect__RateLimiter.yaml new file mode 100644 index 000000000..77dd05c4f --- /dev/null +++ b/migration/annotations/effect__RateLimiter.yaml @@ -0,0 +1,3 @@ +effect/RateLimiter: + replacement: none + note: The old limit, interval, and algorithm options belonged to the removed in-process limiter; choose and configure an application limiter explicitly. The scoped in-process callable limiter was not ported to v4; effect/unstable/persistence/RateLimiter is a keyed persistence service with different semantics, not a drop-in replacement. The FiberRef-based per-effect cost annotation was removed with the core RateLimiter; pass token cost explicitly to the replacement limiter. diff --git a/migration/annotations/effect__RcMap.yaml b/migration/annotations/effect__RcMap.yaml new file mode 100644 index 000000000..8c56ece36 --- /dev/null +++ b/migration/annotations/effect__RcMap.yaml @@ -0,0 +1,9 @@ +effect/RcMap#RcMap: + replacement: "RcMap.RcMap" + note: "The model remains as a Pipeable reference-counted resource map; use RcMap.get explicitly inside a Scope." +effect/RcMap#RcMap.Variance: + replacement: "none" + note: "The public variance marker was removed; use RcMap.RcMap directly." +effect/RcMap#TypeId: + replacement: "none" + note: "The RcMap type id is internal in v4; do not inspect or construct the brand directly." diff --git a/migration/annotations/effect__RcRef.yaml b/migration/annotations/effect__RcRef.yaml new file mode 100644 index 000000000..b77f8783e --- /dev/null +++ b/migration/annotations/effect__RcRef.yaml @@ -0,0 +1,12 @@ +effect/RcRef#RcRef: + replacement: "RcRef.RcRef" + note: "The model remains but is now only Pipeable; replace yielding or reading the RcRef directly with RcRef.get in a Scope." +effect/RcRef#RcRefUnify: + replacement: "none" + note: "RcRef is no longer an Effect subtype, so its Effect unification helper was removed; call RcRef.get explicitly." +effect/RcRef#RcRefUnifyIgnore: + replacement: "none" + note: "RcRef is no longer an Effect subtype, so its Effect unification ignore marker was removed." +effect/RcRef#TypeId: + replacement: "none" + note: "The RcRef type id is internal in v4; do not inspect or construct the brand directly." diff --git a/migration/annotations/effect__Readable.yaml b/migration/annotations/effect__Readable.yaml new file mode 100644 index 000000000..2e5c5dd0e --- /dev/null +++ b/migration/annotations/effect__Readable.yaml @@ -0,0 +1,21 @@ +"effect/Readable#isReadable": + replacement: "Effect.isEffect" + note: "Readable was removed; after representing reads directly as Effect, use the Effect guard." +"effect/Readable#make": + replacement: "Effect.Effect" + note: "Use the supplied Effect directly; the v3 constructor only wrapped it as a get property." +"effect/Readable#map": + replacement: "Effect.map" + note: "Represent Readable as Effect and map it directly." +"effect/Readable#mapEffect": + replacement: "Effect.flatMap" + note: "Represent Readable as Effect and flatMap it directly." +"effect/Readable#Readable": + replacement: "Effect.Effect" + note: "The branded wrapper was removed; represent read access directly as Effect.Effect." +"effect/Readable#TypeId": + replacement: "Effect.TypeId" + note: "The Readable brand was removed; use Effect.TypeId only when branding checks remain necessary after collapsing to Effect." +"effect/Readable#unwrap": + replacement: "Effect.flatten" + note: "After replacing the inner Readable with Effect, flatten the nested Effect directly." diff --git a/migration/annotations/effect__Record.yaml b/migration/annotations/effect__Record.yaml new file mode 100644 index 000000000..4e86dff11 --- /dev/null +++ b/migration/annotations/effect__Record.yaml @@ -0,0 +1,24 @@ +"effect/Record#getEquivalence": + replacement: "Record.makeEquivalence" + note: "Direct rename; pass the value equivalence unchanged." +"effect/Record#getLefts": + replacement: "Record.getFailures" + note: "Extract Result.Failure values while preserving keys." +"effect/Record#getRights": + replacement: "Record.getSuccesses" + note: "Extract Result.Success values while preserving keys." +"effect/Record#modifyOption": + replacement: "Record.modify" + note: "The Option suffix was dropped; missing keys still return Option.none." +"effect/Record#partitionMap": + replacement: "Record.partition" + note: "Pass a mapper returning Result; failures and successes form the two output records." +"effect/Record#ReadonlyRecord": + replacement: "Record.ReadonlyRecord" + note: "The public type and parameter order are unchanged." +"effect/Record#ReadonlyRecord.IsFiniteString": + replacement: "Record.ReadonlyRecord.IsFiniteString" + note: "The namespace utility type is unchanged." +"effect/Record#replaceOption": + replacement: "Record.replace" + note: "The Option suffix was dropped; missing keys still return Option.none." diff --git a/migration/annotations/effect__RedBlackTree.yaml b/migration/annotations/effect__RedBlackTree.yaml new file mode 100644 index 000000000..31085e816 --- /dev/null +++ b/migration/annotations/effect__RedBlackTree.yaml @@ -0,0 +1,105 @@ +"effect/RedBlackTree#at": + replacement: "Array.drop" + note: "Represent the removed tree as sorted entries; for a non-negative index, Array.drop(entries, index) traverses forward from that absolute position." +"effect/RedBlackTree#atReversed": + replacement: "Array.take + Array.reverse" + note: "For a valid absolute index, reverse Array.take(entries, index + 1) to traverse backward from it." +"effect/RedBlackTree#Direction": + replacement: "none" + note: "The tree direction type was removed; use normal array order or Array.reverse." +"effect/RedBlackTree#empty": + replacement: "Array.empty" + note: "The module was removed; use an empty Array and retain the Order separately." +"effect/RedBlackTree#first": + replacement: "Array.head" + note: "On a sorted entry array, Array.head returns the same optional minimum entry." +"effect/RedBlackTree#forEachBetween": + replacement: "Array.filter + Array.forEach" + note: "Filter sorted entries to min <= key < max with the retained Order, then visit them with Array.forEach." +"effect/RedBlackTree#forEachGreaterThanEqual": + replacement: "Array.filter + Array.forEach" + note: "Filter sorted entries to key >= min with the retained Order, then visit them in ascending order." +"effect/RedBlackTree#forEachLessThan": + replacement: "Array.filter + Array.forEach" + note: "Filter sorted entries to key < max with the retained Order, then visit them in ascending order." +"effect/RedBlackTree#fromIterable": + replacement: "Array.sortWith" + note: "Sort the entry iterable by key and retain the Order separately; this does not preserve logarithmic tree operations." +"effect/RedBlackTree#getAt": + replacement: "Array.get" + note: "Array.get on sorted entries preserves the optional index lookup behavior." +"effect/RedBlackTree#getOrder": + replacement: "none" + note: "No replacement collection stores an Order; retain and pass the Order explicitly." +"effect/RedBlackTree#greaterThan": + replacement: "Array.filter" + note: "Filter sorted entries with the retained Order for key > bound." +"effect/RedBlackTree#greaterThanEqual": + replacement: "Array.filter" + note: "Filter sorted entries with the retained Order for key >= bound." +"effect/RedBlackTree#greaterThanEqualReversed": + replacement: "Array.filter + Array.reverse" + note: "Filter sorted entries with the retained Order for key >= bound, then reverse for descending traversal." +"effect/RedBlackTree#greaterThanReversed": + replacement: "Array.filter + Array.reverse" + note: "Filter sorted entries with the retained Order for key > bound, then reverse for descending traversal." +"effect/RedBlackTree#has": + replacement: "Array.some" + note: "Use Array.some on sorted entries with Equal.equals for key membership; this is linear rather than logarithmic." +"effect/RedBlackTree#insert": + replacement: "Array.prepend + Array.sortWith" + note: "Prepend the entry and sort by key to preserve newest-first comparator ties; use an external ordered multimap if logarithmic updates matter." +"effect/RedBlackTree#isRedBlackTree": + replacement: "Array.isArray" + note: "The brand was removed; Array.isArray only checks the replacement representation and cannot prove its sorted invariant." +"effect/RedBlackTree#keys": + replacement: "Array.map" + note: "Map sorted entries to keys and iterate the resulting array." +"effect/RedBlackTree#keysReversed": + replacement: "Array.reverse + Array.map" + note: "Reverse sorted entries, map them to keys, and iterate the resulting array." +"effect/RedBlackTree#last": + replacement: "Array.last" + note: "On a sorted entry array, Array.last returns the same optional maximum entry." +"effect/RedBlackTree#lessThan": + replacement: "Array.filter" + note: "Filter sorted entries with the retained Order for key < bound." +"effect/RedBlackTree#lessThanEqual": + replacement: "Array.filter" + note: "Filter sorted entries with the retained Order for key <= bound." +"effect/RedBlackTree#lessThanEqualReversed": + replacement: "Array.filter + Array.reverse" + note: "Filter sorted entries with the retained Order for key <= bound, then reverse for descending traversal." +"effect/RedBlackTree#lessThanReversed": + replacement: "Array.filter + Array.reverse" + note: "Filter sorted entries with the retained Order for key < bound, then reverse for descending traversal." +"effect/RedBlackTree#make": + replacement: "Array.sortWith" + note: "Sort the supplied entries by key and retain the Order separately; this is not a balanced tree." +"effect/RedBlackTree#RedBlackTree": + replacement: "ReadonlyArray" + note: "The core tree was removed; use sorted immutable entries for small collections or an external persistent ordered multimap when complexity or duplicate-key semantics matter." +"effect/RedBlackTree#RedBlackTree.Direction": + replacement: "none" + note: "The nested direction type was removed; use normal array order or Array.reverse." +"effect/RedBlackTree#reduce": + replacement: "Array.reduce" + note: "Reduce sorted entries in ascending order, adapting the callback to receive [key, value]." +"effect/RedBlackTree#removeFirst": + replacement: "Array.findFirstIndex + Array.remove" + note: "Find the first entry whose key is Equal.equals to the target, then remove that index; leave the array unchanged when absent." +"effect/RedBlackTree#reversed": + replacement: "Array.reverse" + note: "Reverse the sorted entry array for descending traversal." +"effect/RedBlackTree#size": + replacement: "Array.length" + note: "Use Array.length or the .length property on the replacement entry array." +"effect/RedBlackTree#TypeId": + replacement: "none" + note: "The RedBlackTree module and brand symbol were removed." +"effect/RedBlackTree#values": + replacement: "Array.map" + note: "Map sorted entries to values and iterate the resulting array to preserve key order." +"effect/RedBlackTree#valuesReversed": + replacement: "Array.reverse + Array.map" + note: "Reverse sorted entries, map them to values, and iterate to preserve reverse key order." diff --git a/migration/annotations/effect__Redacted.yaml b/migration/annotations/effect__Redacted.yaml new file mode 100644 index 000000000..0334c40d5 --- /dev/null +++ b/migration/annotations/effect__Redacted.yaml @@ -0,0 +1,15 @@ +"effect/Redacted#getEquivalence": + replacement: "Redacted.makeEquivalence" + note: "Renamed to the v4 make-prefix convention." +"effect/Redacted#Redacted": + replacement: "Redacted.Redacted" + note: "The sensitive-value wrapper remains and now optionally carries a label." +"effect/Redacted#Redacted.Variance": + replacement: "Redacted.Redacted.Variance" + note: "The type-level variance member remains." +"effect/Redacted#RedactedTypeId": + replacement: "Redacted.isRedacted" + note: "The marker is private in v4; use the public guard for runtime narrowing." +"effect/Redacted#unsafeWipe": + replacement: "Redacted.wipeUnsafe" + note: "Renamed to use the v4 Unsafe suffix convention." diff --git a/migration/annotations/effect__Ref.yaml b/migration/annotations/effect__Ref.yaml new file mode 100644 index 000000000..3abdf00fb --- /dev/null +++ b/migration/annotations/effect__Ref.yaml @@ -0,0 +1,48 @@ +effect/Ref#getAndSet: + replacement: "Ref.getAndSet" + note: "The operation remains with data-first and data-last forms." +effect/Ref#getAndUpdate: + replacement: "Ref.getAndUpdate" + note: "The operation remains with data-first and data-last forms." +effect/Ref#getAndUpdateSome: + replacement: "Ref.getAndUpdateSome" + note: "The operation remains; Option.none leaves the value unchanged." +effect/Ref#modify: + replacement: "Ref.modify" + note: "The operation remains with data-first and data-last forms." +effect/Ref#Ref: + replacement: "Ref.Ref" + note: "The model remains but is now Pipeable rather than an Effect or Readable subtype; read it explicitly with Ref.get." +effect/Ref#Ref.Variance: + replacement: "Ref.Ref.Variance" + note: "The marker remains under Ref.Ref, but its brand uses an internal type id; ordinary code should use Ref.Ref directly." +effect/Ref#RefTypeId: + replacement: "none" + note: "The Ref type id is internal in v4; do not inspect or construct the brand directly." +effect/Ref#RefUnify: + replacement: "none" + note: "Ref is no longer an Effect subtype, so its Effect unification helper was removed; call Ref.get explicitly." +effect/Ref#RefUnifyIgnore: + replacement: "none" + note: "Ref is no longer an Effect subtype, so its Effect unification ignore marker was removed." +effect/Ref#set: + replacement: "Ref.set" + note: "The operation remains with data-first and data-last forms." +effect/Ref#setAndGet: + replacement: "Ref.setAndGet" + note: "The operation remains with data-first and data-last forms." +effect/Ref#unsafeMake: + replacement: "Ref.makeUnsafe" + note: "The unsafe suffix moved to the end." +effect/Ref#update: + replacement: "Ref.update" + note: "The operation remains with data-first and data-last forms." +effect/Ref#updateAndGet: + replacement: "Ref.updateAndGet" + note: "The operation remains with data-first and data-last forms." +effect/Ref#updateSome: + replacement: "Ref.updateSome" + note: "The operation remains; Option.none leaves the value unchanged." +effect/Ref#updateSomeAndGet: + replacement: "Ref.updateSomeAndGet" + note: "The operation remains; Option.none leaves the value unchanged and returns the current value." diff --git a/migration/annotations/effect__Reloadable.yaml b/migration/annotations/effect__Reloadable.yaml new file mode 100644 index 000000000..a91751596 --- /dev/null +++ b/migration/annotations/effect__Reloadable.yaml @@ -0,0 +1,30 @@ +"effect/Reloadable#auto": + replacement: "LayerRef.Service(..., { layer, invalidationSchedule: schedule, preload: true }).layer" + note: "Use LayerRef for scheduled refresh; add idleTimeToLive: Duration.infinity to preserve an always-resident instance." +"effect/Reloadable#autoFromConfig": + replacement: "Layer.unwrap with Effect.contextWith and LayerRef.make" + note: "Compute the schedule from the current context, then construct a preloaded LayerRef; no config-specific constructor remains." +"effect/Reloadable#get": + replacement: "ServiceRef.get or Effect.map(ServiceRef.contextEffect, Context.get(Service))" + note: "LayerRef.get provides the current context as a layer; contextEffect gives scoped direct access." +"effect/Reloadable#manual": + replacement: "LayerRef.Service(..., { layer, preload: true }).layer" + note: "Refresh with the generated service's refresh effect; use infinite idleTimeToLive for v3's resident lifecycle." +"effect/Reloadable#reload": + replacement: "ServiceRef.refresh" + note: "Refresh invalidates and immediately reacquires; invalidate alone rebuilds on the next borrow." +"effect/Reloadable#Reloadable": + replacement: "LayerRef.LayerRef" + note: "LayerRef is the v4 refreshable layer-context abstraction." +"effect/Reloadable#Reloadable.Variance": + replacement: "none" + note: "The exported variance artifact was removed and LayerRef has no public counterpart." +"effect/Reloadable#ReloadableTypeId": + replacement: "none" + note: "Reloadable was removed and LayerRef's marker is private." +"effect/Reloadable#reloadFork": + replacement: "ServiceRef.refresh.pipe(Effect.ignore({ log: true }), Effect.forkDetach({ startImmediately: true }), Effect.asVoid)" + note: "This recreates logged, ignored background refresh; forkDaemon became forkDetach." +"effect/Reloadable#tag": + replacement: "LayerRef.Service()(id, options)" + note: "The generated LayerRef service class is itself the Context.Service key." diff --git a/migration/annotations/effect__Request.yaml b/migration/annotations/effect__Request.yaml new file mode 100644 index 000000000..b8e0ab8fe --- /dev/null +++ b/migration/annotations/effect__Request.yaml @@ -0,0 +1,30 @@ +effect/Request#Cache: + replacement: "RequestResolver.asCache" + note: "The runtime request cache type was removed; expose resolver results through a first-class Cache, or use RequestResolver.withCache to retain a resolver." +effect/Request#Entry: + replacement: "Request.Entry" + note: "Entry remains but now carries request, context, uninterruptible, and completeUnsafe fields; Deferred, listener, owner, and state fields were removed." +effect/Request#EntryTypeId: + replacement: "none" + note: "Request entries are unbranded structural values in v4; do not inspect or construct an entry type id." +effect/Request#interruptWhenPossible: + replacement: "none" + note: "Request cancellation is managed by the v4 batching runtime; resolver code should complete the entries it receives and not wrap work with this internal listener helper." +effect/Request#isEntry: + replacement: "none" + note: "The entry guard was removed; entries are supplied structurally to RequestResolver callbacks." +effect/Request#Listeners: + replacement: "none" + note: "Request listener accounting is no longer public; cancellation and shared request lifecycle are managed by the v4 runtime and resolver caching." +effect/Request#makeCache: + replacement: "RequestResolver.asCache" + note: "Create a cache from a resolver with capacity and timeToLive options, or use RequestResolver.withCache for a cached resolver." +effect/Request#Request: + replacement: "Request.Request" + note: "The request model remains and adds a third R parameter for services required while resolving the request." +effect/Request#Request.OptionalResult: + replacement: "Exit.Exit>, Request.Error>" + note: "The named alias was removed; write the optional request exit type directly when it is still required." +effect/Request#RequestTypeId: + replacement: "none" + note: "The request type id is internal in v4; define requests by extending Request.Request or with Request.Class and do not depend on branding internals." diff --git a/migration/annotations/effect__RequestBlock.yaml b/migration/annotations/effect__RequestBlock.yaml new file mode 100644 index 000000000..bc9b172eb --- /dev/null +++ b/migration/annotations/effect__RequestBlock.yaml @@ -0,0 +1,30 @@ +effect/RequestBlock#empty: + replacement: "Effect.void" + note: "RequestBlock was removed; represent an empty computation as Effect.void and let Effect.request perform batching." +effect/RequestBlock#Empty: + replacement: "none" + note: "The public blocked-request graph was removed; application code should compose Effect.request computations instead of inspecting Empty nodes." +effect/RequestBlock#mapRequestResolvers: + replacement: "Effect.request" + note: "Pass the selected resolver to each Effect.request call; the runtime request graph can no longer be traversed to rewrite resolvers." +effect/RequestBlock#Par: + replacement: "none" + note: "The public blocked-request graph was removed; express parallel request execution with Effect concurrency combinators." +effect/RequestBlock#parallel: + replacement: "Effect.all" + note: "Compose request effects with Effect.all and explicit concurrency; v4 batching is performed by resolver and batch key rather than RequestBlock nodes." +effect/RequestBlock#reduce: + replacement: "none" + note: "The public blocked-request graph and reducer were removed; structure analysis is now internal to the request runtime." +effect/RequestBlock#RequestBlock: + replacement: "none" + note: "RequestBlock is no longer public in v4; compose Effect.request values directly and let the runtime batch requests by resolver." +effect/RequestBlock#Seq: + replacement: "none" + note: "The public blocked-request graph was removed; express sequencing in the Effect program instead of constructing Seq nodes." +effect/RequestBlock#sequential: + replacement: "Effect.andThen" + note: "Sequence request effects with Effect.andThen, flatMap, or generator syntax; RequestBlock sequencing nodes were removed." +effect/RequestBlock#single: + replacement: "Effect.request" + note: "Construct the request effect directly with its Request value and RequestResolver; the runtime creates pending entries internally." diff --git a/migration/annotations/effect__RequestResolver.yaml b/migration/annotations/effect__RequestResolver.yaml new file mode 100644 index 000000000..647e69250 --- /dev/null +++ b/migration/annotations/effect__RequestResolver.yaml @@ -0,0 +1,36 @@ +effect/RequestResolver#aroundRequests: + replacement: "RequestResolver.around" + note: "around now receives Request.Entry batches; map entries to entry.request in before and after when hooks need raw request values." +effect/RequestResolver#contextFromEffect: + replacement: "Request.Request" + note: "Resolvers no longer carry an environment parameter; declare R on each Request and use entry.context inside the resolver callback." +effect/RequestResolver#contextFromServices: + replacement: "Request.Request" + note: "Declare the selected services in the Request R parameter and read them from each entry.context; resolver-level context capture was removed." +effect/RequestResolver#eitherWith: + replacement: "RequestResolver.fromEffectTagged" + note: "Define one resolver for the combined tagged request union, or use RequestResolver.make to partition entries manually; resolver routing combinators were removed." +effect/RequestResolver#locally: + replacement: "Effect.provideService" + note: "FiberRef-based resolver localization was removed; migrate the FiberRef to Context.Reference and provide its value around the request effect or resolver work." +effect/RequestResolver#makeBatched: + replacement: "RequestResolver.make" + note: "make now receives a non-empty batch of Request.Entry values; read entry.request and complete every entry with completeUnsafe or Request completion helpers." +effect/RequestResolver#makeWithEntry: + replacement: "RequestResolver.make" + note: "Use make for entry-level handling; v4 supplies one non-empty batch and key instead of nested sequential and parallel entry arrays." +effect/RequestResolver#mapInputContext: + replacement: "Request.Request" + note: "Resolver environments were removed; put required services on the Request R parameter and transform or provide each entry.context explicitly when needed." +effect/RequestResolver#provideContext: + replacement: "Effect.provideService" + note: "Provide services to Effect.request so they are captured in entry.context; RequestResolver itself no longer has an environment parameter." +effect/RequestResolver#RequestResolver: + replacement: "RequestResolver.RequestResolver" + note: "The interface remains as RequestResolver; remove its R parameter and move service requirements to Request." +effect/RequestResolver#RequestResolver.Variance: + replacement: "RequestResolver.RequestResolver.Variance" + note: "The variance marker remains but tracks only the accepted Request type; resolver environment variance was removed." +effect/RequestResolver#RequestResolverTypeId: + replacement: "none" + note: "The resolver type id is internal in v4; use RequestResolver constructors and isRequestResolver rather than depending on its brand." diff --git a/migration/annotations/effect__Resource.yaml b/migration/annotations/effect__Resource.yaml new file mode 100644 index 000000000..e26fcaee7 --- /dev/null +++ b/migration/annotations/effect__Resource.yaml @@ -0,0 +1,15 @@ +"effect/Resource#Resource": + replacement: "Resource.Resource" + note: "The type remains but no longer extends Effect; use Resource.get(resource)." +"effect/Resource#Resource.Variance": + replacement: "none" + note: "The exported variance artifact was removed." +"effect/Resource#ResourceTypeId": + replacement: "Resource.isResource" + note: "The marker is private; use the public runtime guard." +"effect/Resource#ResourceUnify": + replacement: "none" + note: "Resource no longer extends Effect; use Resource.get explicitly." +"effect/Resource#ResourceUnifyIgnore": + replacement: "none" + note: "The Effect-unification implementation detail was removed." diff --git a/migration/annotations/effect__Runtime.yaml b/migration/annotations/effect__Runtime.yaml new file mode 100644 index 000000000..89779d17b --- /dev/null +++ b/migration/annotations/effect__Runtime.yaml @@ -0,0 +1,84 @@ +effect/Runtime#AsyncFiberException: + replacement: "Cause.AsyncFiberError" + note: "The error thrown when synchronous execution encounters an async boundary was renamed." +effect/Runtime#Cancel: + replacement: "ReturnType" + note: "Use the cancellation function returned by runCallback; the named type was removed." +effect/Runtime#defaultRuntime: + replacement: "Context.empty()" + note: "Runtime values were removed; call Effect.run* directly or use an empty Context with an Effect.run*With function." +effect/Runtime#defaultRuntimeFlags: + replacement: "none" + note: "Runtime flags were removed; configure scheduler yielding, interruptibility, and runtime metrics independently." +effect/Runtime#deleteFiberRef: + replacement: "Context.omit" + note: "FiberRefs became Context.Reference values; omit the Reference override from the Context." +effect/Runtime#disableRuntimeFlag: + replacement: "none" + note: "Runtime flags were removed; disable the corresponding scheduler, interruptibility, or metric behavior directly." +effect/Runtime#enableRuntimeFlag: + replacement: "none" + note: "Runtime flags were removed; enable the corresponding scheduler, interruptibility, or metric behavior directly." +effect/Runtime#FiberFailure: + replacement: "none" + note: "The runner error wrapper was removed; use an Exit-returning runner to retain and inspect a structured Cause." +effect/Runtime#FiberFailureCauseId: + replacement: "none" + note: "The FiberFailure wrapper and its cause marker were removed; inspect Cause through Exit instead." +effect/Runtime#FiberFailureId: + replacement: "none" + note: "The FiberFailure wrapper and its brand were removed; inspect Cause through Exit instead." +effect/Runtime#isAsyncFiberException: + replacement: "Cause.isAsyncFiberError" + note: "Use the renamed guard from Cause." +effect/Runtime#isFiberFailure: + replacement: "none" + note: "FiberFailure no longer exists; use an Exit-returning runner and inspect Exit or Cause." +effect/Runtime#make: + replacement: "Context.make" + note: "Runtime values were removed; construct the service Context passed to Effect.run*With instead." +effect/Runtime#makeFiberFailure: + replacement: "Cause.squash" + note: "Use Cause.squash only when a Cause must become the value thrown or rejected by a runner." +effect/Runtime#runCallback: + replacement: "Effect.runCallbackWith" + note: "Run with the former Runtime's Context; use Effect.runCallback when no services are required." +effect/Runtime#RunCallbackOptions: + replacement: "Effect.RunOptions & { readonly onExit: (exit: Exit.Exit) => void }" + note: "The callback runner now combines Effect.RunOptions with an onExit callback; no named options type is exported." +effect/Runtime#runFork: + replacement: "Effect.runForkWith" + note: "Run with the former Runtime's Context; use Effect.runFork when no services are required." +effect/Runtime#RunForkOptions: + replacement: "Effect.RunOptions" + note: "Use common runner options; express scoped forking with Effect.forkIn or Effect.forkScoped." +effect/Runtime#runPromise: + replacement: "Effect.runPromiseWith" + note: "Run with the former Runtime's Context; use Effect.runPromise when no services are required." +effect/Runtime#runPromiseExit: + replacement: "Effect.runPromiseExitWith" + note: "Run with the former Runtime's Context; use Effect.runPromiseExit when no services are required." +effect/Runtime#runSync: + replacement: "Effect.runSyncWith" + note: "Run with the former Runtime's Context; use Effect.runSync when no services are required." +effect/Runtime#runSyncExit: + replacement: "Effect.runSyncExitWith" + note: "Run with the former Runtime's Context; use Effect.runSyncExit when no services are required." +effect/Runtime#Runtime: + replacement: "Context.Context" + note: "Runtime values were removed; carry a Context and invoke the corresponding Effect.run*With function." +effect/Runtime#Runtime.Context: + replacement: "none" + note: "The Runtime context extractor was removed; carry the service union directly on Context.Context." +effect/Runtime#setFiberRef: + replacement: "Context.add" + note: "FiberRefs became Context.Reference values; add the Reference override to the Context." +effect/Runtime#updateFiberRefs: + replacement: "Context.add" + note: "There is no aggregate FiberRefs update; add targeted Context.Reference overrides to the carried Context explicitly." +effect/Runtime#updateContext: + replacement: "Context transformation + Effect.run*With" + note: "Runtime values were removed; transform the carried Context directly, then pass the result to the corresponding Effect.run*With function." +effect/Runtime#updateRuntimeFlags: + replacement: "none" + note: "Runtime flags and aggregate patches were removed; configure each semantic behavior independently." diff --git a/migration/annotations/effect__RuntimeFlags.yaml b/migration/annotations/effect__RuntimeFlags.yaml new file mode 100644 index 000000000..eb6d3bcd3 --- /dev/null +++ b/migration/annotations/effect__RuntimeFlags.yaml @@ -0,0 +1,108 @@ +effect/RuntimeFlags#cooperativeYielding: + replacement: "!References.PreventSchedulerYield" + note: "Read the scheduler Reference and negate it; the aggregate flags value was removed." +effect/RuntimeFlags#CooperativeYielding: + replacement: "References.PreventSchedulerYield" + note: "Use the scheduler Reference with inverse boolean meaning." +effect/RuntimeFlags#diff: + replacement: "none" + note: "The runtime-flags bitset was removed; configure each semantic behavior directly." +effect/RuntimeFlags#differ: + replacement: "none" + note: "The runtime-flags bitset and patch differ were removed." +effect/RuntimeFlags#disable: + replacement: "none" + note: "The generic flag operation was removed; disable the corresponding behavior directly." +effect/RuntimeFlags#disableAll: + replacement: "none" + note: "The aggregate flags value was removed; configure scheduler yielding, interruptibility, and metrics independently." +effect/RuntimeFlags#disableCooperativeYielding: + replacement: "Effect.provideService(References.PreventSchedulerYield, true)" + note: "Prevent scheduler yielding through its Context.Reference." +effect/RuntimeFlags#disableInterruption: + replacement: "Effect.uninterruptible" + note: "Use an uninterruptible region instead of changing a runtime flag." +effect/RuntimeFlags#disableOpSupervision: + replacement: "none" + note: "Operation supervision and its runtime flag were removed." +effect/RuntimeFlags#disableRuntimeMetrics: + replacement: "Metric.disableRuntimeMetrics" + note: "Disable fiber runtime metrics directly; use disableRuntimeMetricsLayer when providing a Layer." +effect/RuntimeFlags#disableWindDown: + replacement: "none" + note: "The wind-down flag is runtime-internal in v4; use normal scoped finalizers and explicit interruptibility regions." +effect/RuntimeFlags#enable: + replacement: "none" + note: "The generic flag operation was removed; enable the corresponding behavior directly." +effect/RuntimeFlags#enableAll: + replacement: "none" + note: "The aggregate flags value was removed; configure scheduler yielding, interruptibility, and metrics independently." +effect/RuntimeFlags#enableCooperativeYielding: + replacement: "Effect.provideService(References.PreventSchedulerYield, false)" + note: "Allow scheduler yielding through its Context.Reference." +effect/RuntimeFlags#enableInterruption: + replacement: "Effect.interruptible" + note: "Use an interruptible region instead of changing a runtime flag." +effect/RuntimeFlags#enableOpSupervision: + replacement: "none" + note: "Operation supervision and its runtime flag were removed." +effect/RuntimeFlags#enableRuntimeMetrics: + replacement: "Metric.enableRuntimeMetrics" + note: "Enable fiber runtime metrics directly; use enableRuntimeMetricsLayer when providing a Layer." +effect/RuntimeFlags#enableWindDown: + replacement: "none" + note: "The wind-down flag is runtime-internal in v4; use normal scoped finalizers and explicit interruptibility regions." +effect/RuntimeFlags#interruptible: + replacement: "none" + note: "There is no public current-interruptibility getter; structure the program with Effect.interruptible or Effect.uninterruptible." +effect/RuntimeFlags#interruption: + replacement: "none" + note: "Interruptibility is controlled by Effect regions rather than queried from a flags value." +effect/RuntimeFlags#Interruption: + replacement: "Effect.interruptible | Effect.uninterruptible" + note: "The bit flag was removed; control interruptibility with Effect regions." +effect/RuntimeFlags#isDisabled: + replacement: "none" + note: "There is no aggregate flags value to query; inspect or control the corresponding semantic facility." +effect/RuntimeFlags#make: + replacement: "none" + note: "The runtime-flags bitset was removed; do not recreate it in v4." +effect/RuntimeFlags#none: + replacement: "none" + note: "The runtime-flags bitset was removed; configure each semantic behavior independently." +effect/RuntimeFlags#None: + replacement: "none" + note: "The empty runtime-flags value and its type were removed." +effect/RuntimeFlags#opSupervision: + replacement: "none" + note: "Operation supervision and its runtime flag were removed." +effect/RuntimeFlags#OpSupervision: + replacement: "none" + note: "Operation supervision and its runtime flag were removed." +effect/RuntimeFlags#patch: + replacement: "none" + note: "Aggregate runtime-flags patches were removed; configure each semantic behavior directly." +effect/RuntimeFlags#render: + replacement: "none" + note: "The runtime-flags bitset and its renderer were removed." +effect/RuntimeFlags#RuntimeFlag: + replacement: "none" + note: "Individual bit flags were removed; use the corresponding semantic API." +effect/RuntimeFlags#RuntimeFlags: + replacement: "none" + note: "The aggregate runtime-flags bitset was removed." +effect/RuntimeFlags#runtimeMetrics: + replacement: "Metric.FiberRuntimeMetrics" + note: "Read the Context.Reference and test for undefined instead of querying a bit flag." +effect/RuntimeFlags#RuntimeMetrics: + replacement: "Metric.FiberRuntimeMetrics" + note: "Runtime metrics are now configured through a Context.Reference service rather than a bit flag." +effect/RuntimeFlags#toSet: + replacement: "none" + note: "The runtime-flags bitset was removed; there is no set conversion." +effect/RuntimeFlags#windDown: + replacement: "none" + note: "The wind-down flag is no longer public." +effect/RuntimeFlags#WindDown: + replacement: "none" + note: "The wind-down flag is no longer public." diff --git a/migration/annotations/effect__RuntimeFlagsPatch.yaml b/migration/annotations/effect__RuntimeFlagsPatch.yaml new file mode 100644 index 000000000..915423f68 --- /dev/null +++ b/migration/annotations/effect__RuntimeFlagsPatch.yaml @@ -0,0 +1,3 @@ +effect/RuntimeFlagsPatch: + replacement: none + note: The aggregate RuntimeFlagsPatch abstraction, its enabled/disabled bit sets, set operations, queries, and renderer were removed; no aggregate patch value remains to construct, combine, inspect, or render. Enable, disable, or invert the corresponding semantic behavior directly, combining semantic configurations where needed. Configure scheduler yielding, interruptibility, or metrics directly, and inspect the corresponding semantic facility when needed. diff --git a/migration/annotations/effect__STM.yaml b/migration/annotations/effect__STM.yaml new file mode 100644 index 000000000..d11b88e98 --- /dev/null +++ b/migration/annotations/effect__STM.yaml @@ -0,0 +1,354 @@ +effect/STM#Adapter: + replacement: "none" + note: "The STM.gen adapter was removed; Effect.gen accepts yielded Effects directly." +effect/STM#All.IsDiscard: + replacement: "Effect.All.IsDiscard" + note: "The helper moved to Effect.All because STM.all is now Effect.all." +effect/STM#All.Narrow: + replacement: "none" + note: "Effect.all uses a const generic directly, so the separate tuple-narrowing helper was removed." +effect/STM#All.Options: + replacement: "none" + note: "Effect.all inlines its options type; use its concurrency, discard, and mode options directly." +effect/STM#All.STMAny: + replacement: "Effect.All.EffectAny" + note: "STM inputs are ordinary Effects in v4, so use the Effect.All helper." +effect/STM#All.Signature: + replacement: "typeof Effect.all" + note: "The named STM all signature was removed; refer to Effect.all directly." +effect/STM#Do: + replacement: "Effect.Do" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#STM: + replacement: "Effect.Effect" + note: "The distinct STM instruction type was removed. Tx APIs return Effect values; wrap the complete transaction in Effect.tx." +effect/STM#STM.Variance: + replacement: "Effect.Variance" + note: "The distinct STM variance marker was removed with STM; use the Effect marker." +effect/STM#STMTypeId: + replacement: "Effect.TypeId" + note: "The distinct STM type id was removed because transactions are represented by Effect values." +effect/STM#STMTypeLambda: + replacement: "Effect.EffectTypeLambda" + note: "Use the Effect type lambda; transactional requirements are represented by Effect.Transaction." +effect/STM#STMUnify: + replacement: "Effect.EffectUnify" + note: "STM unification moved to ordinary Effect unification." +effect/STM#STMUnifyIgnore: + replacement: "none" + note: "The STM-specific unification ignore marker was removed; rely on Effect inference." +effect/STM#acquireUseRelease: + replacement: "Effect.acquireUseRelease + Effect.tx" + note: "Wrap acquire, use, and release in separate Effect.tx calls to preserve the v3 separately committed phases; v4 release also receives the use Exit." +effect/STM#all: + replacement: "Effect.all" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#as: + replacement: "Effect.as" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#asSome: + replacement: "Effect.asSome" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#asSomeError: + replacement: "Effect.mapError(self, Option.some)" + note: "The dedicated helper was removed; map the error into Option.some." +effect/STM#asVoid: + replacement: "Effect.asVoid" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#attempt: + replacement: "Effect.try" + note: "The constructor was renamed; transaction programs are ordinary Effects in v4." +effect/STM#bind: + replacement: "Effect.bind" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#bindTo: + replacement: "Effect.bindTo" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#catchAll: + replacement: "Effect.catch" + note: "Use Effect.catch for typed failures. It does not catch Effect.txRetry or restore a transactional savepoint." +effect/STM#catchSome: + replacement: "Effect.catch + Option.match" + note: "Use Effect.catch and re-fail the original error when the partial handler returns None." +effect/STM#catchTag: + replacement: "Effect.catchTag" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#check: + replacement: "Effect.suspend + Effect.txRetry" + note: "Evaluate the predicate lazily and return Effect.void when true or Effect.txRetry when false, inside Effect.tx." +effect/STM#collect: + replacement: "Effect.flatMap + Option.match + Effect.txRetry" + note: "Map Some to success and None to Effect.txRetry inside the surrounding Effect.tx transaction." +effect/STM#collectSTM: + replacement: "Effect.flatMap + Option.match + Effect.txRetry" + note: "Return the Effect held by Some and use Effect.txRetry for None, inside the surrounding Effect.tx transaction." +effect/STM#commit: + replacement: "Effect.tx" + note: "Effect.tx runs an Effect transaction and removes its Effect.Transaction requirement." +effect/STM#commitEither: + replacement: "Effect.tx + Effect.result + Effect.fromResult" + note: "Run Effect.tx(Effect.result(body)) before Effect.fromResult so journal changes commit even when the original transaction had a typed failure." +effect/STM#cond: + replacement: "Effect.suspend" + note: "Lazily branch to Effect.succeed or Effect.fail based on the predicate." +effect/STM#context: + replacement: "Effect.context" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#contextWith: + replacement: "Effect.contextWith" + note: "The name remains, but the v4 callback returns an Effect directly." +effect/STM#contextWithSTM: + replacement: "Effect.contextWith" + note: "The Effect-returning context constructor no longer needs an STM suffix." +effect/STM#dieMessage: + replacement: "Effect.die(new Error(message))" + note: "The message-specific helper was removed; construct a message-bearing defect explicitly." +effect/STM#dieSync: + replacement: "Effect.suspend(() => Effect.die(evaluate()))" + note: "The lazy defect helper was removed; suspend construction and then die." +effect/STM#either: + replacement: "Effect.result" + note: "V4 uses Result instead of Either for materialized typed failures." +effect/STM#ensuring: + replacement: "Effect.ensuring" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#eventually: + replacement: "Effect.eventually" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#every: + replacement: "Effect.findFirst + Option.isNone" + note: "Search sequentially for the first false effectful predicate; no match means every element passed." +effect/STM#exists: + replacement: "Effect.findFirst + Option.isSome" + note: "Search sequentially for the first true effectful predicate." +effect/STM#fail: + replacement: "Effect.fail" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#failSync: + replacement: "Effect.failSync" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#fiberId: + replacement: "Effect.fiberId" + note: "The operation remains on Effect, but v4 yields the fiber id as a number." +effect/STM#filter: + replacement: "Effect.filter" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#filterNot: + replacement: "Effect.filter" + note: "Negate the effectful predicate result and use Effect.filter." +effect/STM#filterOrDie: + replacement: "Effect.filterOrFail + Effect.orDie" + note: "Fail with the lazy defect when the predicate rejects, then convert that failure to a defect." +effect/STM#filterOrDieMessage: + replacement: "Effect.filterOrFail + Effect.orDie" + note: "Fail with a new Error carrying the message when the predicate rejects, then convert it to a defect." +effect/STM#filterOrFail: + replacement: "Effect.filterOrFail" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#firstSuccessOf: + replacement: "Effect.firstSuccessOf" + note: "This only preserves typed-failure fallback. V4 has no equivalent for v3 retry-aware alternatives with journal savepoints." +effect/STM#flatMap: + replacement: "Effect.flatMap" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#flatten: + replacement: "Effect.flatten" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#flip: + replacement: "Effect.flip" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#flipWith: + replacement: "Effect.flip(self).pipe(f, Effect.flip)" + note: "Compose the retained Effect.flip operation around the transforming function." +effect/STM#fromEither: + replacement: "Effect.fromResult" + note: "V4 replaced Either with Result; migrate the value and use Effect.fromResult." +effect/STM#head: + replacement: "Effect.matchEffect" + note: "Map source failures to Option.some, return the first iterable element, and fail with Option.none when empty." +effect/STM#if: + replacement: "Effect.suspend or Effect.flatMap" + note: "Select the true or false branch lazily; use flatMap when the condition is effectful." +effect/STM#ignore: + replacement: "Effect.ignore" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#interrupt: + replacement: "Effect.interrupt" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#interruptAs: + replacement: "Effect.interrupt" + note: "V4 exposes interruption of the current fiber only; remove the explicit FiberId argument." +effect/STM#isFailure: + replacement: "Effect.isFailure" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#isSTM: + replacement: "Effect.isEffect" + note: "STM no longer has a distinct runtime representation; transaction programs are Effects." +effect/STM#isSuccess: + replacement: "Effect.isSuccess" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#iterate: + replacement: "Effect.gen loop" + note: "No direct Effect iterate helper remains; carry state in an explicit sequential Effect.gen loop inside Effect.tx." +effect/STM#let: + replacement: "Effect.let" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#loop: + replacement: "Effect.gen loop" + note: "No direct Effect loop helper remains; implement the state loop explicitly and collect values unless discard was requested." +effect/STM#map: + replacement: "Effect.map" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#mapAttempt: + replacement: "Effect.flatMap(self, (a) => Effect.try(() => f(a)))" + note: "Use Effect.try in flatMap so thrown exceptions remain typed failures rather than defects." +effect/STM#mapInputContext: + replacement: "Effect.updateContext" + note: "The context-input mapping operation was renamed on Effect." +effect/STM#match: + replacement: "Effect.match" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#matchSTM: + replacement: "Effect.matchEffect" + note: "The Effect-returning match combinator no longer has an STM suffix." +effect/STM#mergeAll: + replacement: "Effect.reduce" + note: "Reduce the input Effects sequentially and combine each produced value with the accumulator." +effect/STM#none: + replacement: "Effect.matchEffect + Option.match" + note: "Recreate the Option success/error shuffle explicitly; no dedicated helper remains." +effect/STM#option: + replacement: "Effect.option" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#orDie: + replacement: "Effect.orDie" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#orDieWith: + replacement: "Effect.mapError + Effect.orDie" + note: "Map the typed error to the desired defect and then use Effect.orDie." +effect/STM#orElse: + replacement: "none" + note: "V4 has no exact retry-aware transactional alternative with journal savepoint restoration. Effect.catch is only a failure-only approximation." +effect/STM#orElseEither: + replacement: "none" + note: "V4 has no exact retry-aware alternative. For typed failures only, compose Effect.catch and Result tagging manually." +effect/STM#orElseFail: + replacement: "Effect.mapError" + note: "Map typed failures to the replacement error; this does not preserve v3 retry fallback semantics." +effect/STM#orElseOptional: + replacement: "Effect.catch + Option.match" + note: "Run the fallback for None and re-fail Some errors explicitly." +effect/STM#orElseSucceed: + replacement: "Effect.orElseSucceed" + note: "The name remains for typed failures, but v4 does not preserve v3 retry fallback or journal savepoints." +effect/STM#orTry: + replacement: "none" + note: "V4 exposes no recoverable retry signal or public transactional savepoint; restructure branch selection before Effect.txRetry." +effect/STM#partition: + replacement: "Effect.partition" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#provideServiceSTM: + replacement: "Effect.provideServiceEffect" + note: "The effectful service provider was renamed on Effect." +effect/STM#provideSomeContext: + replacement: "Effect.provideContext or Effect.updateContext" + note: "The dedicated partial-context helper was removed; provide or update the Effect context explicitly." +effect/STM#reduce: + replacement: "Effect.reduce" + note: "The combinator remains, but v4 takes the initial state lazily and also passes the element index." +effect/STM#reduceAll: + replacement: "Effect.flatMap + Effect.reduce" + note: "Evaluate the initial Effect, then reduce the remaining Effects sequentially." +effect/STM#reduceRight: + replacement: "Effect.reduce over a reversed Array" + note: "Materialize and reverse the iterable, then reduce while preserving the old state/element callback order." +effect/STM#refineOrDie: + replacement: "Effect.catch + Option.match" + note: "Re-fail Some refined errors and die with the original error for None." +effect/STM#refineOrDieWith: + replacement: "Effect.catch + Option.match" + note: "Re-fail Some refined errors and map None to the requested defect." +effect/STM#reject: + replacement: "Effect.flatMap + Option.match" + note: "Fail when the partial rejection returns Some; otherwise keep the original success." +effect/STM#rejectSTM: + replacement: "Effect.flatMap + Option.match" + note: "Run and fail with the Effect held by Some; otherwise keep the original success." +effect/STM#repeatUntil: + replacement: "Effect.repeat(self, { until: predicate })" + note: "The dedicated combinator moved to Effect.repeat options." +effect/STM#repeatWhile: + replacement: "Effect.repeat(self, { while: predicate })" + note: "The dedicated combinator moved to Effect.repeat options." +effect/STM#replicate: + replacement: "Effect.replicate" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#replicateSTM: + replacement: "Effect.replicateEffect" + note: "Use the effectful replication combinator and keep execution sequential inside Effect.tx." +effect/STM#replicateSTMDiscard: + replacement: "Effect.replicateEffect(self, n, { discard: true })" + note: "Use effectful replication with discard enabled and keep execution sequential inside Effect.tx." +effect/STM#retry: + replacement: "Effect.txRetry" + note: "Do not use Effect.retry, which retries typed failures by schedule; Effect.txRetry waits for an accessed Tx value to change." +effect/STM#retryUntil: + replacement: "Effect.flatMap + Effect.txRetry" + note: "Succeed when the predicate passes; otherwise return Effect.txRetry inside Effect.tx." +effect/STM#retryWhile: + replacement: "Effect.flatMap + Effect.txRetry" + note: "Return Effect.txRetry while the predicate passes; otherwise succeed inside Effect.tx." +effect/STM#some: + replacement: "Effect.matchEffect + Option.match" + note: "Recreate the Option success/error shuffle explicitly; no dedicated helper remains." +effect/STM#succeed: + replacement: "Effect.succeed" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#summarized: + replacement: "Effect.gen" + note: "Run the summary Effect before and after the body, then return the computed summary and body value." +effect/STM#sync: + replacement: "Effect.sync" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#tap: + replacement: "Effect.tap" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#tapBoth: + replacement: "Effect.tapError + Effect.tap" + note: "Compose the separate failure and success taps." +effect/STM#tapError: + replacement: "Effect.tapError" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#try: + replacement: "Effect.try" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#unless: + replacement: "Effect.when(self, Effect.sync(() => !predicate()))" + note: "V4 Effect.when takes an effectful condition; suspend and negate the old lazy boolean." +effect/STM#unlessSTM: + replacement: "Effect.when(self, Effect.map(condition, (b) => !b))" + note: "Negate the effectful condition and use Effect.when." +effect/STM#unsome: + replacement: "Effect.matchEffect + Option.match" + note: "Recreate the Option error/success shuffle explicitly; no dedicated helper remains." +effect/STM#validateAll: + replacement: "Effect.validate" + note: "The validation combinator was renamed and now returns a NonEmptyArray of errors." +effect/STM#validateFirst: + replacement: "Effect.flip + Effect.forEach" + note: "Flip each candidate result, traverse sequentially, then flip the aggregate to preserve all errors when every candidate fails." +effect/STM#void: + replacement: "Effect.void" + note: "The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/STM#when: + replacement: "Effect.when(self, Effect.sync(predicate))" + note: "V4 Effect.when takes an effectful boolean, so suspend the old lazy predicate." +effect/STM#whenSTM: + replacement: "Effect.when" + note: "The effectful-condition form is now the only Effect.when form." +effect/STM#zipLeft: + replacement: "Effect.zipWith(self, that, (left) => left)" + note: "Use sequential Effect.zipWith and retain the left result." +effect/STM#zipRight: + replacement: "Effect.andThen" + note: "Use Effect.andThen for sequential composition that retains the right result." diff --git a/migration/annotations/effect__Schedule.yaml b/migration/annotations/effect__Schedule.yaml new file mode 100644 index 000000000..00770ca6c --- /dev/null +++ b/migration/annotations/effect__Schedule.yaml @@ -0,0 +1,267 @@ +"effect/Schedule#addDelayEffect": + replacement: "Schedule.addDelay" + note: "The v4 function is effectful by default and its callback receives full Schedule.Metadata; read metadata.output when only the prior output is needed." +"effect/Schedule#andThen": + replacement: "Schedule.concat" + note: "The sequencing combinator was renamed to Schedule.concat." +"effect/Schedule#andThenEither": + replacement: "Schedule.concatResult" + note: "Sequential phase tagging now uses Result: self outputs are Result.fail and the following schedule outputs are Result.succeed." +"effect/Schedule#as": + replacement: "Schedule.map" + note: "Map the metadata to the constant output; Schedule.map accepts either a plain value or an Effect." +"effect/Schedule#asVoid": + replacement: "Schedule.map" + note: "Map every output to undefined." +"effect/Schedule#bothInOut": + replacement: "none" + note: "There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done." +"effect/Schedule#check": + replacement: "Schedule.while" + note: "Continue while a predicate over metadata.input and metadata.output returns true." +"effect/Schedule#checkEffect": + replacement: "Schedule.while" + note: "Schedule.while accepts an effectful metadata predicate in v4." +"effect/Schedule#collectAllInputs": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#collectAllOutputs": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#collectUntil": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#collectUntilEffect": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#collectWhile": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#collectWhileEffect": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#compose": + replacement: "none" + note: "There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done." +"effect/Schedule#count": + replacement: "Schedule.forever" + note: "The forever schedule outputs the zero-based recurrence count." +"effect/Schedule#CurrentIterationMetadata": + replacement: "Schedule.CurrentMetadata" + note: "The Context.Reference was renamed and now provides Schedule.Metadata with input, output, attempt, duration, and elapsed timing fields." +"effect/Schedule#dayOfMonth": + replacement: "Schedule.cron" + note: "Express the calendar constraint as a cron expression, for example `0 0 * *`, and map its Duration output if a numeric output is required." +"effect/Schedule#dayOfWeek": + replacement: "Schedule.cron" + note: "Express the weekday constraint as a cron expression, for example `0 0 * * `, and map its Duration output if a numeric output is required." +"effect/Schedule#delayed": + replacement: "Schedule.modifyDelay" + note: "Return Effect.succeed(f(metadata.duration)); delay transformations are effectful and receive full metadata in v4." +"effect/Schedule#delayedEffect": + replacement: "Schedule.modifyDelay" + note: "The v4 delay modifier is effectful by default and receives full Schedule.Metadata." +"effect/Schedule#delayedSchedule": + replacement: "Schedule.modifyDelay" + note: "Replace each delay with metadata.output, converting that Duration output through Effect.succeed." +"effect/Schedule#delays": + replacement: "Schedule.map" + note: "Map each decision to metadata.duration to expose the selected recurrence delay." +"effect/Schedule#driver": + replacement: "Schedule.toStepWithSleep" + note: "Acquire the sleeping step function and call it for each input; use Schedule.toStep when delay handling must remain manual." +"effect/Schedule#either": + replacement: "Schedule.min" + note: "Use Schedule.min for fastest-delay composition. It outputs the selected Duration rather than a tuple of both outputs." +"effect/Schedule#eitherWith": + replacement: "Schedule.min" + note: "Schedule.min implements the standard fastest-delay composition; custom interval merging requires a Schedule.fromStep implementation." +"effect/Schedule#elapsed": + replacement: "Schedule.map" + note: "Map metadata.elapsed through Duration.millis." +"effect/Schedule#ensuring": + replacement: "Schedule.during" + note: "Use the duration-bounded v4 schedule constructor." +"effect/Schedule#fromDelay": + replacement: "Schedule.duration" + note: "The duration constructor recurs once after the supplied delay." +"effect/Schedule#fromDelays": + replacement: "Schedule.duration + Schedule.concat" + note: "Build one Schedule.duration per delay and sequence them with Schedule.concat." +"effect/Schedule#fromFunction": + replacement: "Schedule.identity + Schedule.map" + note: "Start with Schedule.identity() and map metadata.input through the function." +"effect/Schedule#hourOfDay": + replacement: "Schedule.cron" + note: "Express the hour constraint as a cron expression such as `0 * * *`." +"effect/Schedule#intersect": + replacement: "Schedule.max" + note: "Use Schedule.max for slowest-delay composition. It outputs the selected Duration rather than a tuple of both outputs." +"effect/Schedule#intersectWith": + replacement: "Schedule.max" + note: "Schedule.max implements the standard slowest-delay composition; custom interval merging requires a Schedule.fromStep implementation." +"effect/Schedule#IterationMetadata": + replacement: "Schedule.Metadata" + note: "The metadata model now includes duration and uses attempt instead of recurrence; elapsed fields are millisecond numbers." +"effect/Schedule#jitteredWith": + replacement: "Schedule.modifyDelay" + note: "For custom bounds, scale metadata.duration using Random.next inside the effectful delay callback; Schedule.jittered supplies the fixed v4 0.8-1.2 range." +"effect/Schedule#linear": + replacement: "Schedule.forever + Schedule.map + Schedule.modifyDelay" + note: "Map the recurrence attempt to the linearly increasing Duration, then use that output as the recurrence delay." +"effect/Schedule#makeWithState": + replacement: "Schedule.fromStep" + note: "Move mutable state into the acquired step closure; return [output, Duration] for recurrence and Cause.done(output) for termination." +"effect/Schedule#mapBoth": + replacement: "Schedule.fromStep + Schedule.toStep" + note: "Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step." +"effect/Schedule#mapBothEffect": + replacement: "Schedule.fromStep + Schedule.toStep" + note: "Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step. Apply the effectful output mapping to the returned tuple." +"effect/Schedule#mapEffect": + replacement: "Schedule.map" + note: "Schedule.map accepts an Effect result and receives full Schedule.Metadata." +"effect/Schedule#mapInput": + replacement: "Schedule.fromStep + Schedule.toStep" + note: "Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step." +"effect/Schedule#mapInputContext": + replacement: "Schedule.fromStep + Effect.provide" + note: "Provide the transformed service context to both Schedule.toStep acquisition and each returned step Effect." +"effect/Schedule#mapInputEffect": + replacement: "Schedule.fromStep + Schedule.toStep" + note: "Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step. Evaluate the input mapping Effect before the underlying step." +"effect/Schedule#minuteOfHour": + replacement: "Schedule.cron" + note: "Express the minute constraint as a cron expression such as ` * * * *`." +"effect/Schedule#modifyDelayEffect": + replacement: "Schedule.modifyDelay" + note: "The v4 delay modifier is effectful by default and receives full Schedule.Metadata." +"effect/Schedule#once": + replacement: "Schedule.duration(Duration.zero)" + note: "A zero-duration schedule recurs once and then completes; map its Duration output to void if needed." +"effect/Schedule#onDecision": + replacement: "Schedule.tap" + note: "Use Schedule.tap for effects on recurrence metadata. To also observe final completion, wrap Schedule.toStep with Pull.matchEffect in Schedule.fromStep." +"effect/Schedule#provideContext": + replacement: "Schedule.fromStep + Effect.provide" + note: "Provide the Context to both Schedule.toStep acquisition and each Effect returned by the acquired step." +"effect/Schedule#provideService": + replacement: "Schedule.fromStep + Effect.provideService" + note: "Provide the service to both Schedule.toStep acquisition and each Effect returned by the acquired step." +"effect/Schedule#recurUntil": + replacement: "Schedule.identity + Schedule.while" + note: "Continue while the predicate over metadata.input is false." +"effect/Schedule#recurUntilEffect": + replacement: "Schedule.identity + Schedule.while" + note: "Continue while the effectful predicate over metadata.input is false." +"effect/Schedule#recurUntilOption": + replacement: "Schedule.fromStep" + note: "Use a custom step to evaluate the Option-producing function, emit Option.none while recurring, and terminate with the first Option.some result." +"effect/Schedule#recurUpTo": + replacement: "Schedule.during" + note: "Use the duration-bounded schedule constructor." +"effect/Schedule#recurWhile": + replacement: "Schedule.identity + Schedule.while" + note: "Continue while the predicate over metadata.input is true." +"effect/Schedule#recurWhileEffect": + replacement: "Schedule.identity + Schedule.while" + note: "Continue while the effectful predicate over metadata.input is true." +"effect/Schedule#reduce": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#reduceEffect": + replacement: "none" + note: "This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure." +"effect/Schedule#repeatForever": + replacement: "Schedule.forever" + note: "The infinite zero-delay counter schedule was renamed." +"effect/Schedule#repetitions": + replacement: "Schedule.map" + note: "Map metadata.attempt to the required recurrence count, adjusting by one where the v3 zero-based value is expected." +"effect/Schedule#resetAfter": + replacement: "none" + note: "Automatic schedule reset was removed. Wrap Schedule.toStep(self) with Schedule.fromStep and reacquire the inner step when the reset condition is met." +"effect/Schedule#resetWhen": + replacement: "none" + note: "Automatic schedule reset was removed. Wrap Schedule.toStep(self) with Schedule.fromStep and reacquire the inner step when the reset condition is met." +"effect/Schedule#run": + replacement: "Schedule.toStep" + note: "Acquire the step and traverse inputs manually, supplying each timestamp and collecting successful outputs until Cause.done." +"effect/Schedule#Schedule": + replacement: "Schedule.Schedule" + note: "The model remains but now has Schedule; its public initial/step fields were replaced by Schedule.toStep and fromStep." +"effect/Schedule#Schedule.DriverVariance": + replacement: "none" + note: "ScheduleDriver was removed in v4, so its variance marker has no replacement. Use the Schedule type parameters or the function returned by Schedule.toStepWithSleep." +"effect/Schedule#Schedule.Variance": + replacement: "Schedule.Schedule.Variance" + note: "The variance marker remains and now tracks Output, Input, Error, and Env through the private Schedule TypeId." +"effect/Schedule#ScheduleDriver": + replacement: "Schedule.toStepWithSleep" + note: "ScheduleDriver was removed. The acquired step function provides manual next calls with automatic sleeping; Schedule.toStep exposes raw delays." +"effect/Schedule#ScheduleDriverTypeId": + replacement: "none" + note: "ScheduleDriver and its public type id were removed. Use the step function returned by Schedule.toStepWithSleep." +"effect/Schedule#ScheduleTypeId": + replacement: "none" + note: "The Schedule type id is private in v4. Use Schedule.isSchedule to narrow unknown values." +"effect/Schedule#secondOfMinute": + replacement: "Schedule.cron" + note: "Use the six-field cron form to express a seconds constraint, for example ` * * * * *`." +"effect/Schedule#stop": + replacement: "Schedule.fromStep" + note: "Create a step that immediately returns Cause.done(undefined)." +"effect/Schedule#succeed": + replacement: "Schedule.forever + Schedule.map" + note: "Map every recurrence to the constant value." +"effect/Schedule#sync": + replacement: "Schedule.forever + Schedule.map" + note: "Map every recurrence by lazily evaluating the thunk." +"effect/Schedule#tapInput": + replacement: "Schedule.tap" + note: "Use the unified tap callback and read metadata.input." +"effect/Schedule#tapOutput": + replacement: "Schedule.tap" + note: "Use the unified tap callback and read metadata.output." +"effect/Schedule#unfold": + replacement: "Schedule.fromStep" + note: "Keep the evolving value inside the acquired step closure and emit each value with the desired Duration." +"effect/Schedule#union": + replacement: "Schedule.min" + note: "Use Schedule.min for fastest-delay composition. It outputs the selected Duration rather than both schedule outputs." +"effect/Schedule#unionWith": + replacement: "Schedule.min" + note: "Schedule.min covers the standard union behavior; a custom interval merge requires Schedule.fromStep." +"effect/Schedule#untilInput": + replacement: "Schedule.while" + note: "Continue while the predicate over metadata.input is false." +"effect/Schedule#untilInputEffect": + replacement: "Schedule.while" + note: "Continue while the effectful predicate over metadata.input is false." +"effect/Schedule#untilOutput": + replacement: "Schedule.while" + note: "Continue while the predicate over metadata.output is false." +"effect/Schedule#untilOutputEffect": + replacement: "Schedule.while" + note: "Continue while the effectful predicate over metadata.output is false." +"effect/Schedule#whileInput": + replacement: "Schedule.while" + note: "Continue while the predicate over metadata.input is true." +"effect/Schedule#whileInputEffect": + replacement: "Schedule.while" + note: "Continue while the effectful predicate over metadata.input is true." +"effect/Schedule#whileOutput": + replacement: "Schedule.while" + note: "Continue while the predicate over metadata.output is true." +"effect/Schedule#whileOutputEffect": + replacement: "Schedule.while" + note: "Continue while the effectful predicate over metadata.output is true." +"effect/Schedule#zipLeft": + replacement: "none" + note: "There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done." +"effect/Schedule#zipRight": + replacement: "none" + note: "There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done." +"effect/Schedule#zipWith": + replacement: "none" + note: "There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done." diff --git a/migration/annotations/effect__ScheduleDecision.yaml b/migration/annotations/effect__ScheduleDecision.yaml new file mode 100644 index 000000000..bce4dad16 --- /dev/null +++ b/migration/annotations/effect__ScheduleDecision.yaml @@ -0,0 +1,18 @@ +"effect/ScheduleDecision#continue": + replacement: "Effect.succeed([output, duration])" + note: "ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output)." +"effect/ScheduleDecision#continueWith": + replacement: "Effect.succeed([output, duration])" + note: "ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output)." +"effect/ScheduleDecision#Done": + replacement: "Cause.Done" + note: "ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output)." +"effect/ScheduleDecision#isContinue": + replacement: "none" + note: "ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). Branch on the Pull result instead of inspecting a decision value." +"effect/ScheduleDecision#isDone": + replacement: "Cause.isDone" + note: "ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output)." +"effect/ScheduleDecision#ScheduleDecision": + replacement: "none" + note: "ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output)." diff --git a/migration/annotations/effect__ScheduleInterval.yaml b/migration/annotations/effect__ScheduleInterval.yaml new file mode 100644 index 000000000..2b9a211ba --- /dev/null +++ b/migration/annotations/effect__ScheduleInterval.yaml @@ -0,0 +1,3 @@ +effect/ScheduleInterval: + replacement: none + note: The public ScheduleInterval module was removed in v4. Schedule steps now express only a relative Duration; combine policies with Schedule.max or Schedule.min, or implement custom timing with Schedule.fromStep. diff --git a/migration/annotations/effect__ScheduleIntervals.yaml b/migration/annotations/effect__ScheduleIntervals.yaml new file mode 100644 index 000000000..69985bca3 --- /dev/null +++ b/migration/annotations/effect__ScheduleIntervals.yaml @@ -0,0 +1,3 @@ +effect/ScheduleIntervals: + replacement: none + note: The public ScheduleIntervals module was removed in v4 along with absolute interval-set decisions. Use relative Duration values in Schedule.fromStep and Schedule.max or Schedule.min for standard policy composition. diff --git a/migration/annotations/effect__Scheduler.yaml b/migration/annotations/effect__Scheduler.yaml new file mode 100644 index 000000000..06e3b0191 --- /dev/null +++ b/migration/annotations/effect__Scheduler.yaml @@ -0,0 +1,42 @@ +effect/Scheduler#ControlledScheduler: + replacement: "none" + note: "No public step-controlled scheduler remains; implement Scheduler and SchedulerDispatcher for exact controlled stepping." +effect/Scheduler#defaultScheduler: + replacement: "Scheduler.Scheduler" + note: "The default scheduler is now a Context.Reference; yield it to read or provide it to override the current scheduler." +effect/Scheduler#defaultShouldYield: + replacement: "Scheduler.MixedScheduler#shouldYield" + note: "The standalone function was removed; yielding is implemented by each Scheduler instance." +effect/Scheduler#make: + replacement: "none" + note: "Implement the redesigned Scheduler interface and return task dispatch through makeDispatcher." +effect/Scheduler#makeBatched: + replacement: "new Scheduler.MixedScheduler(\"async\", schedule)" + note: "Pass a cancellable scheduling function; the dispatcher performs priority batching." +effect/Scheduler#makeMatrix: + replacement: "none" + note: "Matrix routing was removed; implement routing in a custom Scheduler and SchedulerDispatcher if still required." +effect/Scheduler#MixedScheduler: + replacement: "Scheduler.MixedScheduler" + note: "The class remains with a redesigned constructor and makeDispatcher-based task API." +effect/Scheduler#PriorityBuckets: + replacement: "none" + note: "Priority buckets are now an internal Scheduler implementation detail." +effect/Scheduler#Scheduler: + replacement: "Scheduler.Scheduler" + note: "The interface remains but dispatch moved to SchedulerDispatcher returned by makeDispatcher." +effect/Scheduler#SchedulerRunner: + replacement: "Scheduler.SchedulerDispatcher" + note: "Task scheduling and flushing moved to the dispatcher returned by Scheduler.makeDispatcher." +effect/Scheduler#SyncScheduler: + replacement: "new Scheduler.MixedScheduler(\"sync\")" + note: "Use a synchronous MixedScheduler and its dispatcher; call flush when directly driving queued tasks." +effect/Scheduler#Task: + replacement: "() => void" + note: "The named alias was removed; dispatcher APIs inline the task callback type." +effect/Scheduler#timer: + replacement: "Effect.delay" + note: "Use Effect delay or sleep for effect timing; implement a custom dispatcher for exact per-task scheduler timing." +effect/Scheduler#timerBatched: + replacement: "new Scheduler.MixedScheduler(\"async\", scheduleWithTimer)" + note: "Use a setTimeout-based cancellable scheduling function; the dispatcher batches queued tasks." diff --git a/migration/annotations/effect__Schema.yaml b/migration/annotations/effect__Schema.yaml new file mode 100644 index 000000000..d3daa60e1 --- /dev/null +++ b/migration/annotations/effect__Schema.yaml @@ -0,0 +1,1362 @@ +effect/Schema#Annotable: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Annotable.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Annotable.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Annotable.Self: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#AnnotableClass: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#AnnotableDeclare: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#annotations: + replacement: Schema.annotate + note: Rename `annotations` to `annotate`. +effect/Schema#Annotations: + replacement: Schema.Annotations + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Annotations.Doc: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Annotations.Filter: + replacement: Schema.Annotations.Filter + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Annotations.GenericSchema: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Annotations.Schema: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Any: + replacement: Schema.Any + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Array$: + replacement: Schema.$Array + note: Use the renamed v4 constructor result interface. +effect/Schema#ArrayEnsure: + replacement: Schema.ArrayEnsure + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#ArrayFormatterIssue: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#asSchema: + replacement: Schema.revealCodec + note: Use `revealCodec` to expose a schema's codec type. +effect/Schema#asSerializable: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#asSerializableWithResult: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#asWithResult: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#attachPropertySignature: + replacement: Schema.tagDefaultOmit + note: "Map the struct fields and add `key: Schema.tagDefaultOmit(value)`; the old combinator was removed." +effect/Schema#between: + replacement: Schema.isBetween + note: Rename the predicate to `isBetween` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#betweenBigDecimal: + replacement: Schema.isBetweenBigDecimal + note: Rename the predicate to `isBetweenBigDecimal` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#BetweenBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#betweenBigInt: + replacement: Schema.isBetweenBigInt + note: Rename the predicate to `isBetweenBigInt` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#BetweenBigIntSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#betweenDate: + replacement: Schema.isBetweenDate + note: Rename the predicate to `isBetweenDate` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#BetweenDateSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#betweenDuration: + replacement: Schema.isBetween + note: Rename the predicate to `isBetween` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#BetweenDurationSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#BetweenSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#BigDecimal: + replacement: Schema.BigDecimalFromString + note: Use the string-to-BigDecimal codec; v4 `BigDecimal` is the self schema. +effect/Schema#BigDecimalFromNumber: + replacement: none + note: No built-in number-to-BigDecimal codec remains; compose `decodeTo` with a `SchemaGetter` conversion. +effect/Schema#BigDecimalFromSelf: + replacement: Schema.BigDecimal + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#BigInt: + replacement: Schema.BigIntFromString + note: Use the string-to-bigint codec; v4 `BigInt` is the self schema. +effect/Schema#BigIntFromNumber: + replacement: none + note: No built-in number-to-bigint codec remains; compose `decodeTo` with a checked `SchemaGetter` conversion. +effect/Schema#BigIntFromSelf: + replacement: Schema.BigInt + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#Boolean: + replacement: Schema.Boolean + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#BooleanFromString: + replacement: none + note: No built-in string-to-boolean codec remains; use `decodeTo` with an explicit `SchemaGetter` transformation. +effect/Schema#BooleanFromUnknown: + replacement: Schema.Boolean + note: Use the boolean schema and perform any coercion explicitly before decoding. +effect/Schema#brand: + replacement: Schema.brand + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#BrandSchema: + replacement: Schema.brand + note: Use the schema returned by the v4 `brand` combinator and infer its concrete type. +effect/Schema#BrandSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Capitalize: + replacement: Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isCapitalized()), SchemaTransformation.capitalize())) + note: Rebuild the capitalization transformation with `decodeTo`. +effect/Schema#capitalized: + replacement: Schema.isCapitalized + note: Rename the string predicate to `isCapitalized` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Capitalized: + replacement: Schema.String.check(Schema.isCapitalized()) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#CapitalizedSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Cause: + replacement: Schema.toCodecJson(Schema.Cause(error, defect)) + note: Use the derived JSON codec to preserve v3's encoded Cause representation; v4 `Cause` itself is the self schema. +effect/Schema#CauseEncoded: + replacement: Schema.CauseIso + note: Use the v4 Cause JSON/iso representation type. +effect/Schema#CauseFromSelf: + replacement: Schema.Cause + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#Char: + replacement: Schema.Char + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Chunk: + replacement: Schema.toCodecJson(Schema.Chunk(value)) + note: Use the derived JSON codec to preserve v3's array-to-Chunk behavior; v4 `Chunk` itself is the self schema. +effect/Schema#ChunkFromSelf: + replacement: Schema.Chunk + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#clamp: + replacement: Schema.decodeTo + SchemaGetter.transform(Number.clamp(...)) + note: Rebuild clamping as an explicit reversible transformation. +effect/Schema#clampBigDecimal: + replacement: Schema.decodeTo + SchemaGetter.transform(BigDecimal.clamp(...)) + note: Rebuild BigDecimal clamping as an explicit reversible transformation. +effect/Schema#clampBigInt: + replacement: Schema.decodeTo + SchemaGetter.transform(BigInt.clamp(...)) + note: Rebuild bigint clamping as an explicit reversible transformation. +effect/Schema#clampDuration: + replacement: Schema.decodeTo + SchemaGetter.transform(Duration.clamp(...)) + note: Rebuild Duration clamping as an explicit reversible transformation. +effect/Schema#Class: + replacement: Schema.Class + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Config: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#Data: + replacement: none + note: Remove this wrapper. v4 structural equality works on ordinary decoded objects. +effect/Schema#DataFromSelf: + replacement: none + note: Remove this wrapper. v4 structural equality works on ordinary decoded objects. +effect/Schema#Date: + replacement: Schema.DateFromString + note: Use `DateFromString`; v4 `Date` is the self schema. +effect/Schema#DateFromNumber: + replacement: Schema.DateFromMillis + note: Rename the milliseconds-to-Date codec. +effect/Schema#DateFromSelf: + replacement: Schema.Date + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#DateFromSelfSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#DateFromString: + replacement: Schema.DateFromString + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#DateTimeUtc: + replacement: Schema.DateTimeUtcFromString + note: Use the string codec; v4 `DateTimeUtc` is the self schema. +effect/Schema#DateTimeUtcFromDate: + replacement: Schema.DateTimeUtcFromDate + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#DateTimeUtcFromNumber: + replacement: Schema.DateTimeUtcFromMillis + note: Rename the milliseconds-to-DateTime codec. +effect/Schema#DateTimeUtcFromSelf: + replacement: Schema.DateTimeUtc + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#DateTimeZoned: + replacement: Schema.DateTimeZonedFromString + note: Use the string codec; v4 `DateTimeZoned` is the self schema. +effect/Schema#DateTimeZonedFromSelf: + replacement: Schema.DateTimeZoned + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#declare: + replacement: Schema.declare + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#decode: + replacement: Schema.decodeEffect + note: Rename the effectful decoder. +effect/Schema#decodeEither: + replacement: Schema.decodeExit + note: Rename the decoder returning an `Exit`. +effect/Schema#decodeUnknown: + replacement: Schema.decodeUnknownEffect + note: Rename the effectful unknown-input decoder. +effect/Schema#decodeUnknownEither: + replacement: Schema.decodeUnknownExit + note: Rename the unknown-input decoder returning an `Exit`. +effect/Schema#decodeUnknownPromise: + replacement: Schema.decodeUnknownPromise + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Defect: + replacement: Schema.Defect + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#deserialize: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#deserializeExit: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#deserializeFailure: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#deserializeSuccess: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#Duration: + replacement: Schema.DurationFromString + note: Use the string codec; v4 `Duration` is the self schema. +effect/Schema#DurationEncoded: + replacement: Schema.Duration["Iso"] + note: Use the v4 Duration iso representation type. +effect/Schema#DurationFromMillis: + replacement: Schema.DurationFromMillis + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#DurationFromNanos: + replacement: Schema.DurationFromNanos + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#DurationFromSelf: + replacement: Schema.Duration + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#Either: + replacement: Schema.Result + note: "`Either` was renamed to `Result`; pass success and failure schemas positionally." +effect/Schema#EitherEncoded: + replacement: Schema.ResultIso + note: Use the v4 Result iso representation type. +effect/Schema#EitherFromSelf: + replacement: Schema.Result + note: "`Either` was renamed to `Result` in v4." +effect/Schema#EitherFromUnion: + replacement: Schema.Result + note: "`Either` was renamed to `Result`; use its tagged Result representation." +effect/Schema#element: + replacement: none + note: The tuple element wrapper was removed; express elements directly in `Tuple([...])` or use `TupleWithRest` for rest elements. +effect/Schema#Element: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Element.Token: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#encode: + replacement: Schema.encodeEffect + note: Rename the effectful encoder. +effect/Schema#encodedBoundSchema: + replacement: Schema.toEncoded + note: Use the encoded side of the codec; service bounds are modeled by v4 codec service types. +effect/Schema#encodedSchema: + replacement: Schema.toEncoded + note: Rename the encoded-side projection. +effect/Schema#encodeEither: + replacement: Schema.encodeExit + note: Rename the encoder returning an `Exit`. +effect/Schema#encodeUnknown: + replacement: Schema.encodeUnknownEffect + note: Rename the effectful unknown-input encoder. +effect/Schema#encodeUnknownEither: + replacement: Schema.encodeUnknownExit + note: Rename the unknown-input encoder returning an `Exit`. +effect/Schema#encodeUnknownPromise: + replacement: Schema.encodeUnknownPromise + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#endsWith: + replacement: Schema.isEndsWith + note: Rename the string predicate to `isEndsWith` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#EndsWithSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Enums: + replacement: Schema.Enum + note: Rename the enum constructor and pass the enum object. +effect/Schema#EnumsDefinition: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#equivalence: + replacement: Schema.toEquivalence + note: Rename the equivalence derivation utility. +effect/Schema#Exit: + replacement: Schema.toCodecJson(Schema.Exit(value, error, defect)) + note: Use the derived JSON codec to preserve v3's encoded Exit representation; v4 `Exit` itself is the self schema. +effect/Schema#ExitEncoded: + replacement: Schema.ExitIso + note: Use the v4 Exit iso representation type. +effect/Schema#ExitFromSelf: + replacement: Schema.Exit + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#exitSchema: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#extend: + replacement: schema.mapFields(Struct.assign(fields)) + note: Replace struct extension with `mapFields(Struct.assign(...))` or `Schema.fieldsAssign`; map union members explicitly. +effect/Schema#failureSchema: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#FiberId: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#FiberIdEncoded: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#FiberIdFromSelf: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#filter: + replacement: Schema.check(Schema.makeFilter(predicate)) / Schema.refine(refinement) + note: Use `check(makeFilter(...))` for predicates and `refine` for type refinements. +effect/Schema#filterEffect: + replacement: "Schema.decode({ decode: SchemaGetter.checkEffect(...), encode: SchemaGetter.passthrough() })" + note: Rebuild effectful validation as a decode step with `SchemaGetter.checkEffect`. +effect/Schema#FilterIssue: + replacement: Schema.FilterIssue + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#FilterOutput: + replacement: Schema.FilterOutput + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#finite: + replacement: Schema.isFinite + note: Rename the predicate to `isFinite` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Finite: + replacement: Schema.Finite + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#FiniteSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#format: + replacement: SchemaRepresentation.toCodeDocument + note: Build a representation with `SchemaRepresentation.toRepresentation`, `toMultiDocument`, then `toCodeDocument`. +effect/Schema#fromBrand: + replacement: Schema.fromBrand + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#fromKey: + replacement: Schema.encodeKeys + note: Use `encodeKeys` to map decoded property names to encoded keys. +effect/Schema#FromPropertySignature: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#getNumberIndexedAccess: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#greaterThan: + replacement: Schema.isGreaterThan + note: Rename the predicate to `isGreaterThan` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#greaterThanBigDecimal: + replacement: Schema.isGreaterThanBigDecimal + note: Rename the predicate to `isGreaterThanBigDecimal` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanBigInt: + replacement: Schema.isGreaterThanBigInt + note: Rename the predicate to `isGreaterThanBigInt` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanBigIntSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanDate: + replacement: Schema.isGreaterThanDate + note: Rename the predicate to `isGreaterThanDate` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanDateSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanDuration: + replacement: Schema.isGreaterThan + note: Rename the predicate to `isGreaterThan` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanDurationSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanOrEqualTo: + replacement: Schema.isGreaterThanOrEqualTo + note: Rename the predicate to `isGreaterThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#greaterThanOrEqualToBigDecimal: + replacement: Schema.isGreaterThanOrEqualToBigDecimal + note: Rename the predicate to `isGreaterThanOrEqualToBigDecimal` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanOrEqualToBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanOrEqualToBigInt: + replacement: Schema.isGreaterThanOrEqualToBigInt + note: Rename the predicate to `isGreaterThanOrEqualToBigInt` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanOrEqualToBigIntSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanOrEqualToDate: + replacement: Schema.isGreaterThanOrEqualToDate + note: Rename the predicate to `isGreaterThanOrEqualToDate` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanOrEqualToDateSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#greaterThanOrEqualToDuration: + replacement: Schema.isGreaterThanOrEqualTo + note: Rename the predicate to `isGreaterThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#GreaterThanOrEqualToDurationSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#GreaterThanOrEqualToSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#GreaterThanSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#HashMap: + replacement: Schema.toCodecJson(Schema.HashMap(key, value)) + note: Pass key and value positionally and use the derived JSON codec to preserve v3's entry-array encoding. +effect/Schema#HashMapFromSelf: + replacement: Schema.HashMap + note: The self schema dropped the `FromSelf` suffix; pass key and value positionally. +effect/Schema#HashSet: + replacement: Schema.toCodecJson(Schema.HashSet(value)) + note: Use the derived JSON codec to preserve v3's array-to-HashSet behavior. +effect/Schema#HashSetFromSelf: + replacement: Schema.HashSet + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#head: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#headNonEmpty: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#headOrElse: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#includes: + replacement: Schema.isIncludes + note: Rename the string predicate to `isIncludes` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#IncludesSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#IndexSignature: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#IndexSignature.Context: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#IndexSignature.Encoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#IndexSignature.NonEmptyRecords: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#IndexSignature.Record: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#IndexSignature.Type: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#instanceOf: + replacement: Schema.instanceOf + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#InstanceOfSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#int: + replacement: Schema.isInt + note: Rename the predicate to `isInt` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Int: + replacement: Schema.Int + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#IntSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#isPropertySignature: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#isSchema: + replacement: Schema.isSchema + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#itemsCount: + replacement: Schema.isLengthBetween + note: Use the v4 collection-size check and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#ItemsCountSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#JsonNumber: + replacement: Schema.Finite + note: Use the finite-number schema for JSON-compatible numbers. +effect/Schema#JsonNumberSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#keyof: + replacement: none + note: Removed with the schema model rewrite; derive keys from struct fields or use `Schema.Literals` explicitly. +effect/Schema#LeftEncoded: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#length: + replacement: Schema.isLengthBetween + note: Use `isLengthBetween` with equal minimum and maximum values for an exact string length. +effect/Schema#LengthSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThan: + replacement: Schema.isLessThan + note: Rename the predicate to `isLessThan` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#lessThanBigDecimal: + replacement: Schema.isLessThanBigDecimal + note: Rename the predicate to `isLessThanBigDecimal` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanBigInt: + replacement: Schema.isLessThanBigInt + note: Rename the predicate to `isLessThanBigInt` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanBigIntSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanDate: + replacement: Schema.isLessThanDate + note: Rename the predicate to `isLessThanDate` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanDateSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanDuration: + replacement: Schema.isLessThan + note: Rename the predicate to `isLessThan` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanDurationSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanOrEqualTo: + replacement: Schema.isLessThanOrEqualTo + note: Rename the predicate to `isLessThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#lessThanOrEqualToBigDecimal: + replacement: Schema.isLessThanOrEqualToBigDecimal + note: Rename the predicate to `isLessThanOrEqualToBigDecimal` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanOrEqualToBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanOrEqualToBigInt: + replacement: Schema.isLessThanOrEqualToBigInt + note: Rename the predicate to `isLessThanOrEqualToBigInt` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanOrEqualToBigIntSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanOrEqualToDate: + replacement: Schema.isLessThanOrEqualToDate + note: Rename the predicate to `isLessThanOrEqualToDate` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanOrEqualToDateSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#lessThanOrEqualToDuration: + replacement: Schema.isLessThanOrEqualTo + note: Rename the predicate to `isLessThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#LessThanOrEqualToDurationSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#LessThanOrEqualToSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#LessThanSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#List: + replacement: none + note: The List schema was removed; migrate the model to `Schema.Array` or declare a custom List codec. +effect/Schema#ListFromSelf: + replacement: none + note: The List self schema was removed; migrate to arrays or use `Schema.declare` for List values. +effect/Schema#Literal: + replacement: Schema.Literal / Schema.Literals + note: Use `Literal(value)` for one non-null literal, `Null` for null, and `Literals([...])` for several literals. +effect/Schema#Lowercase: + replacement: Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isLowercased()), SchemaTransformation.toLowerCase())) + note: Rebuild the lowercase transformation with `decodeTo`. +effect/Schema#lowercased: + replacement: Schema.isLowercased + note: Rename the string predicate to `isLowercased` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Lowercased: + replacement: Schema.String.check(Schema.isLowercased()) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#LowercasedSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#make: + replacement: Schema.make + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#MakeOptions: + replacement: Schema.MakeOptions + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#makePropertySignature: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#Map: + replacement: Schema.toCodecJson(Schema.ReadonlyMap(key, value)) + note: Use `ReadonlyMap` with positional arguments and derive its JSON codec; mutable Map-specific schema types were removed. +effect/Schema#Map$: + replacement: Schema.$ReadonlyMap + note: Use the renamed v4 constructor result interface. +effect/Schema#MapFromRecord: + replacement: none + note: No direct record-to-Map codec remains; compose `Record` and `ReadonlyMap` with an explicit `decodeTo` transformation. +effect/Schema#MapFromSelf: + replacement: Schema.ReadonlyMap + note: Use the readonly Map self schema with positional key and value arguments. +effect/Schema#maxItems: + replacement: Schema.isMaxLength + note: Use the v4 collection-size check and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#MaxItemsSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#maxLength: + replacement: Schema.isMaxLength + note: Rename the string predicate to `isMaxLength` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#MaxLengthSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#minItems: + replacement: Schema.isMinLength + note: Use the v4 collection-size check and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#MinItemsSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#minLength: + replacement: Schema.isMinLength + note: Rename the string predicate to `isMinLength` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#MinLengthSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#multipleOf: + replacement: Schema.isMultipleOf + note: Rename the predicate to `isMultipleOf` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#MultipleOfSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#mutable: + replacement: Schema.mutable + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#negative: + replacement: Schema.isLessThan(0) + note: Use `isLessThan(0)` as a v4 check. +effect/Schema#Negative: + replacement: Schema.Number.check(Schema.isLessThan(0)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#negativeBigDecimal: + replacement: Schema.isLessThanBigDecimal(BigDecimal.fromNumber(0)) + note: Use `isLessThanBigDecimal` as a v4 check. +effect/Schema#NegativeBigDecimalFromSelf: + replacement: Schema.BigDecimal.check(Schema.isLessThanBigDecimal(BigDecimal.fromNumber(0))) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NegativeBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#negativeBigInt: + replacement: Schema.isLessThanBigInt(0n) + note: Use `isLessThanBigInt(0n)` as a v4 check. +effect/Schema#NegativeBigInt: + replacement: Schema.BigIntFromString.check(Schema.isLessThanBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NegativeBigIntFromSelf: + replacement: Schema.BigInt.check(Schema.isLessThanBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#Never: + replacement: Schema.Never + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#NonEmptyArray: + replacement: Schema.NonEmptyArray + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#NonEmptyArrayEnsure: + replacement: none + note: No direct replacement remains; explicitly decode a single value or array to `Schema.NonEmptyArray`. +effect/Schema#NonEmptyChunk: + replacement: Schema.toCodecJson(Schema.Chunk(value).check(Schema.isMinLength(1))) + note: Use a checked Chunk JSON codec. +effect/Schema#NonEmptyChunkFromSelf: + replacement: Schema.Chunk(value).check(Schema.isMinLength(1)) + note: Use the Chunk self schema with a minimum-length check. +effect/Schema#nonEmptyString: + replacement: Schema.isNonEmpty + note: Rename the string predicate to `isNonEmpty` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#NonEmptyString: + replacement: Schema.NonEmptyString + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#NonEmptyTrimmedString: + replacement: Schema.Trimmed.check(Schema.isNonEmpty()) + note: Compose the trimmed schema with the non-empty check. +effect/Schema#nonNaN: + replacement: Schema.makeFilter((n) => !Number.isNaN(n)) + note: Use an explicit filter because v4 has no dedicated non-NaN check. +effect/Schema#NonNaN: + replacement: Schema.Number.check(Schema.makeFilter((n) => !Number.isNaN(n))) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NonNaNSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#nonNegative: + replacement: Schema.isGreaterThanOrEqualTo(0) + note: Use `isGreaterThanOrEqualTo(0)` as a v4 check. +effect/Schema#NonNegative: + replacement: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#nonNegativeBigDecimal: + replacement: Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.fromNumber(0)) + note: Use `isGreaterThanOrEqualToBigDecimal` as a v4 check. +effect/Schema#NonNegativeBigDecimalFromSelf: + replacement: Schema.BigDecimal.check(Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.fromNumber(0))) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NonNegativeBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#nonNegativeBigInt: + replacement: Schema.isGreaterThanOrEqualToBigInt(0n) + note: Use `isGreaterThanOrEqualToBigInt(0n)` as a v4 check. +effect/Schema#NonNegativeBigInt: + replacement: Schema.BigIntFromString.check(Schema.isGreaterThanOrEqualToBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NonNegativeBigIntFromSelf: + replacement: Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NonNegativeInt: + replacement: Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#nonPositive: + replacement: Schema.isLessThanOrEqualTo(0) + note: Use `isLessThanOrEqualTo(0)` as a v4 check. +effect/Schema#NonPositive: + replacement: Schema.Number.check(Schema.isLessThanOrEqualTo(0)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#nonPositiveBigDecimal: + replacement: Schema.isLessThanOrEqualToBigDecimal(BigDecimal.fromNumber(0)) + note: Use `isLessThanOrEqualToBigDecimal` as a v4 check. +effect/Schema#NonPositiveBigDecimalFromSelf: + replacement: Schema.BigDecimal.check(Schema.isLessThanOrEqualToBigDecimal(BigDecimal.fromNumber(0))) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NonPositiveBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#nonPositiveBigInt: + replacement: Schema.isLessThanOrEqualToBigInt(0n) + note: Use `isLessThanOrEqualToBigInt(0n)` as a v4 check. +effect/Schema#NonPositiveBigInt: + replacement: Schema.BigIntFromString.check(Schema.isLessThanOrEqualToBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#NonPositiveBigIntFromSelf: + replacement: Schema.BigInt.check(Schema.isLessThanOrEqualToBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#Not: + replacement: none + note: The exclusion constructor was removed; express the accepted alternatives directly or add a `Schema.check`. +effect/Schema#Null: + replacement: Schema.Null + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#NullishOr: + replacement: Schema.NullishOr + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#NullOr: + replacement: Schema.NullOr + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Number: + replacement: Schema.Number + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#NumberFromString: + replacement: Schema.NumberFromString + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Object: + replacement: Schema.ObjectKeyword + note: Rename the object keyword schema. +effect/Schema#omit: + replacement: schema.mapFields(Struct.omit([keys])) + note: Use `mapFields` with `Struct.omit`; pass keys as an array. +effect/Schema#Option: + replacement: Schema.toCodecJson(Schema.Option(value)) + note: Use the derived JSON codec to preserve v3's tagged Option encoding; v4 `Option` itself is the self schema. +effect/Schema#optional: + replacement: Schema.optional + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#optionalElement: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#OptionalOptions: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#optionalToOptional: + replacement: Schema.decodeTo + SchemaGetter.transformOptional + note: Rebuild optional-field transformations with `decodeTo` and `SchemaGetter.transformOptional`. +effect/Schema#optionalToRequired: + replacement: Schema.decodeTo + SchemaGetter.transformOptional + note: Rebuild optional-field transformations with `decodeTo` and `SchemaGetter.transformOptional`. +effect/Schema#optionalWith: + replacement: Schema.optional / Schema.optionalKey / Schema.withDecodingDefaultType + note: Choose `optional` or `optionalKey`; use the decoding-default helpers and an explicit nullable transformation as required by the old options. +effect/Schema#OptionEncoded: + replacement: Schema.OptionIso + note: Use the v4 Option iso representation type. +effect/Schema#OptionFromNonEmptyTrimmedString: + replacement: Schema.Trimmed.check(Schema.isNonEmpty()).pipe(Schema.decodeTo(Schema.Option(Schema.String), ...)) + note: Rebuild the empty-string-to-None conversion explicitly with `decodeTo`. +effect/Schema#OptionFromNullishOr: + replacement: Schema.OptionFromNullishOr + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#OptionFromNullOr: + replacement: Schema.OptionFromNullOr + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#OptionFromSelf: + replacement: Schema.Option + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#OptionFromUndefinedOr: + replacement: Schema.OptionFromUndefinedOr + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#parseJson: + replacement: Schema.UnknownFromJsonString / Schema.fromJsonString(schema) + note: Use `UnknownFromJsonString` without an inner schema or `fromJsonString(schema)` with one. +effect/Schema#ParseJsonOptions: + replacement: none + note: The old parse-json options type was removed; configure `fromJsonString` and its underlying getter directly. +effect/Schema#parseNumber: + replacement: Schema.NumberFromString + note: Use the built-in string-to-number codec. +effect/Schema#partial: + replacement: schema.mapFields(Struct.map(Schema.optional)) + note: Map struct fields with `Schema.optional`. +effect/Schema#partialWith: + replacement: schema.mapFields(Struct.map(Schema.optionalKey)) + note: "For `{ exact: true }`, map struct fields with `Schema.optionalKey`; choose field helpers explicitly for other options." +effect/Schema#pattern: + replacement: Schema.isPattern + note: Rename the string predicate to `isPattern` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#PatternSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#pick: + replacement: schema.mapFields(Struct.pick([keys])) + note: Use `mapFields` with `Struct.pick`; pass keys as an array. +effect/Schema#pickLiteral: + replacement: Schema.Literals(values).pick(selected) + note: Build a `Literals` schema from an array and call its `pick` method. +effect/Schema#pluck: + replacement: none + note: No direct replacement remains; pick the field then use `decodeTo` with `SchemaGetter.transform` to map between the field and enclosing object. +effect/Schema#positive: + replacement: Schema.isGreaterThan(0) + note: Use `isGreaterThan(0)` as a v4 check. +effect/Schema#Positive: + replacement: Schema.Number.check(Schema.isGreaterThan(0)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#positiveBigDecimal: + replacement: Schema.isGreaterThanBigDecimal(BigDecimal.fromNumber(0)) + note: Use `isGreaterThanBigDecimal` as a v4 check. +effect/Schema#PositiveBigDecimalFromSelf: + replacement: Schema.BigDecimal.check(Schema.isGreaterThanBigDecimal(BigDecimal.fromNumber(0))) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#PositiveBigDecimalSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#positiveBigInt: + replacement: Schema.isGreaterThanBigInt(0n) + note: Use `isGreaterThanBigInt(0n)` as a v4 check. +effect/Schema#PositiveBigInt: + replacement: Schema.BigIntFromString.check(Schema.isGreaterThanBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#PositiveBigIntFromSelf: + replacement: Schema.BigInt.check(Schema.isGreaterThanBigInt(0n)) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#PropertyKey: + replacement: Schema.PropertyKey + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#propertySignature: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#PropertySignature: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignature.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignature.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignature.AST: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignature.Token: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignatureDeclaration: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignatureTransformation: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#PropertySignatureTypeId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#ReadonlyMap: + replacement: Schema.toCodecJson(Schema.ReadonlyMap(key, value)) + note: Pass key and value positionally and use the derived JSON codec to preserve v3's entry-array encoding. +effect/Schema#ReadonlyMap$: + replacement: Schema.$ReadonlyMap + note: Use the renamed v4 constructor result interface. +effect/Schema#ReadonlyMapFromRecord: + replacement: none + note: No direct record-to-ReadonlyMap codec remains; compose `Record` and `ReadonlyMap` with an explicit `decodeTo` transformation. +effect/Schema#ReadonlyMapFromSelf: + replacement: Schema.ReadonlyMap + note: The self schema dropped the `FromSelf` suffix; pass key and value positionally. +effect/Schema#ReadonlySet: + replacement: Schema.toCodecJson(Schema.ReadonlySet(value)) + note: Use the derived JSON codec to preserve v3's array-to-ReadonlySet behavior. +effect/Schema#ReadonlySet$: + replacement: Schema.$ReadonlySet + note: Use the renamed v4 constructor result interface. +effect/Schema#ReadonlySetFromSelf: + replacement: Schema.ReadonlySet + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#Record: + replacement: Schema.Record(key, value) + note: Pass key and value as separate arguments. +effect/Schema#Record$: + replacement: Schema.$Record + note: Use the renamed v4 constructor result interface. +effect/Schema#Redacted: + replacement: Schema.RedactedFromValue + note: Use `RedactedFromValue` to wrap decoded raw values; v4 `Redacted` is the self schema. +effect/Schema#RedactedFromSelf: + replacement: Schema.Redacted + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#refine: + replacement: Schema.refine + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#RefineSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#rename: + replacement: Schema.encodeKeys + note: Use `encodeKeys` for encoded-key renaming. +effect/Schema#requiredToOptional: + replacement: Schema.decodeTo + SchemaGetter.transformOptional + note: Rebuild optional-field transformations with `decodeTo` and `SchemaGetter.transformOptional`. +effect/Schema#RightEncoded: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#Schema: + replacement: Schema.Schema + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Schema.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.AnyNoContext: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.AsSchema: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.Context: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.Encoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.ToAsserts: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Schema.Variance: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#SchemaClass: + replacement: Schema.Codec + note: The concrete SchemaClass abstraction was removed; accept the appropriate v4 `Codec` or constraint type. +effect/Schema#Serializable: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Serializable.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Serializable.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Serializable.Context: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Serializable.Encoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Serializable.Type: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#serializableSchema: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#SerializableWithResult: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#SerializableWithResult.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#SerializableWithResult.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#SerializableWithResult.Context: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#serialize: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#serializeExit: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#serializeFailure: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#serializeSuccess: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#Set: + replacement: Schema.toCodecJson(Schema.ReadonlySet(value)) + note: Use the readonly Set schema and derive its JSON codec; mutable Set-specific schema types were removed. +effect/Schema#Set$: + replacement: Schema.$ReadonlySet + note: Use the renamed v4 constructor result interface. +effect/Schema#SetFromSelf: + replacement: Schema.ReadonlySet + note: Use the readonly Set self schema. +effect/Schema#SimplifyMutable: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#SortedSet: + replacement: none + note: The SortedSet schema was removed; migrate to `ReadonlySet` or declare a custom codec that applies the required ordering. +effect/Schema#SortedSetFromSelf: + replacement: none + note: The SortedSet self schema was removed; use `Schema.declare` if SortedSet values must remain in the model. +effect/Schema#split: + replacement: Schema.String.pipe(Schema.decodeTo(Schema.Array(Schema.String), SchemaTransformation.transform(...))) + note: Rebuild splitting as an explicit reversible string/array transformation. +effect/Schema#standardSchemaV1: + replacement: Schema.toStandardSchemaV1 + note: Rename the Standard Schema adapter. +effect/Schema#startsWith: + replacement: Schema.isStartsWith + note: Rename the string predicate to `isStartsWith` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#StartsWithSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#String: + replacement: Schema.String + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Struct: + replacement: Schema.Struct + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Struct.Constructor: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Struct.Context: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Struct.Encoded: + replacement: Schema.Struct.Encoded + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Struct.EncodedOptionalKeys: + replacement: Schema.Struct.EncodedOptionalKeys + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Struct.Field: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Struct.Key: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Struct.OptionalEncodedPropertySignature: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Struct.OptionalTypePropertySignature: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#Struct.PropertySignatureWithDefault: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#successSchema: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#suspend: + replacement: Schema.suspend + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Symbol: + replacement: none + note: v4 `Symbol` is the self schema and has no built-in string-to-symbol codec; rebuild the conversion explicitly with `decodeTo`. +effect/Schema#SymbolFromSelf: + replacement: Schema.Symbol + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#symbolSerializable: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#symbolWithResult: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#tag: + replacement: Schema.tag + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#TaggedClass: + replacement: Schema.TaggedClass + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#TaggedError: + replacement: Schema.TaggedError + note: The constructor name is retained; update to the v4 fields-or-Struct signature and infer the resulting class types. +effect/Schema#TaggedErrorClass: + replacement: Schema.TaggedError + note: The exported helper interface was removed; use the class returned by Schema.TaggedError and infer its types. +effect/Schema#TaggedRequest: + replacement: effect/unstable/rpc/Rpc.make + note: The Schema request/serialization protocol was removed; migrate RPC requests to the v4 Rpc APIs. +effect/Schema#TaggedRequest.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TaggedRequest.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TaggedRequestClass: + replacement: effect/unstable/rpc/Rpc.make + note: The Schema request/serialization protocol was removed; migrate RPC requests to the v4 Rpc APIs. +effect/Schema#TaggedStruct: + replacement: Schema.TaggedStruct + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#TemplateLiteral: + replacement: Schema.TemplateLiteral(parts) + note: Pass template literal parts as one array. Parts must not contain encodings, including inside unions and nested templates. Transformations whose decoded and encoded types are equal are also rejected. Use Schema.TemplateLiteralParser(parts) for transformed parts. +effect/Schema#TemplateLiteralParser: + replacement: Schema.TemplateLiteralParser(parts) + note: Pass template literal parts directly as one array. Transformed parts are supported, and their decoding and encoding services are required in the corresponding direction. +effect/Schema#TimeZone: + replacement: Schema.TimeZoneFromString + note: Use the string codec; v4 `TimeZone` is the self schema. +effect/Schema#TimeZoneFromSelf: + replacement: Schema.TimeZone + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#TimeZoneNamed: + replacement: Schema.TimeZoneNamedFromString + note: Use the string codec; v4 `TimeZoneNamed` is the self schema. +effect/Schema#TimeZoneNamedFromSelf: + replacement: Schema.TimeZoneNamed + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#TimeZoneOffset: + replacement: Schema.toCodecJson(Schema.TimeZoneOffset) + note: Use the derived JSON codec to preserve v3's encoded offset representation; v4 `TimeZoneOffset` is the self schema. +effect/Schema#TimeZoneOffsetFromSelf: + replacement: Schema.TimeZoneOffset + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#ToPropertySignature: + replacement: none + note: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. +effect/Schema#transform: + replacement: schema.pipe(Schema.decodeTo(target, SchemaTransformation.transform({ decode, encode }))) + note: Replace the constructor with `decodeTo` and a `SchemaTransformation`. +effect/Schema#transformLiteral: + replacement: Schema.Literal(from).transform(to) + note: Use the literal schema's `transform` method. +effect/Schema#transformLiterals: + replacement: Schema.Literals(fromValues).transform(toValues) + note: Split the pairs into parallel arrays and use `Literals(...).transform(...)`. +effect/Schema#transformOrFail: + replacement: "schema.pipe(Schema.decodeTo(target, { decode: SchemaGetter.transformOrFail(...), encode: ... }))" + note: Replace the constructor with `decodeTo` and fallible `SchemaGetter` transformations. +effect/Schema#Trim: + replacement: Schema.Trim + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#trimmed: + replacement: Schema.isTrimmed + note: Rename the string predicate to `isTrimmed` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Trimmed: + replacement: Schema.Trimmed + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#TrimmedSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Tuple: + replacement: Schema.Tuple(elements) + note: Pass tuple elements as one array. +effect/Schema#Tuple2: + replacement: Schema.Tuple + note: Use the array-based tuple constructor. +effect/Schema#TupleType.ElementsEncoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TupleType.ElementsType: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TupleType.Encoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TupleType.Type: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TypeId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#TypeLiteral: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TypeLiteral.Constructor: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TypeLiteral.Encoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#TypeLiteral.Type: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#typeSchema: + replacement: Schema.toType + note: Rename the type-side projection. +effect/Schema#Uint8: + replacement: "Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 0, maximum: 255 }))" + note: Rebuild the unsigned-byte schema from integer and range checks. +effect/Schema#Uint8Array: + replacement: Schema.toCodecJson(Schema.Uint8Array) + note: Use the derived JSON codec to preserve v3's number-array encoding; v4 `Uint8Array` is the self schema. +effect/Schema#Uint8ArrayFromSelf: + replacement: Schema.Uint8Array + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#ULID: + replacement: Schema.String.check(Schema.isULID()) + note: Build the string schema with the ULID check. +effect/Schema#ULIDSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Uncapitalize: + replacement: Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isUncapitalized()), SchemaTransformation.uncapitalize())) + note: Rebuild the uncapitalization transformation with `decodeTo`. +effect/Schema#uncapitalized: + replacement: Schema.isUncapitalized + note: Rename the string predicate to `isUncapitalized` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Uncapitalized: + replacement: Schema.String.check(Schema.isUncapitalized()) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#UncapitalizedSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Undefined: + replacement: Schema.Undefined + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#UndefinedOr: + replacement: Schema.UndefinedOr + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Union: + replacement: Schema.Union(members) + note: Pass union members as one array. +effect/Schema#UniqueSymbolFromSelf: + replacement: Schema.UniqueSymbol + note: Use the v4 unique-symbol schema constructor. +effect/Schema#Unknown: + replacement: Schema.Unknown + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#Uppercase: + replacement: Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isUppercased()), SchemaTransformation.toUpperCase())) + note: Rebuild the uppercase transformation with `decodeTo`. +effect/Schema#uppercased: + replacement: Schema.isUppercased + note: Rename the string predicate to `isUppercased` and apply it with `Schema.check` or a schema's `check` method. +effect/Schema#Uppercased: + replacement: Schema.String.check(Schema.isUppercased()) + note: Rebuild the removed convenience schema from the v4 base schema and check APIs. +effect/Schema#UppercasedSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#URL: + replacement: Schema.URLFromString + note: Use the string-to-URL codec; v4 `URL` is the self schema. +effect/Schema#URLFromSelf: + replacement: Schema.URL + note: The self schema dropped the `FromSelf` suffix. +effect/Schema#UUID: + replacement: Schema.String.check(Schema.isUUID()) + note: Build the string schema with the UUID check. +effect/Schema#UUIDSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#validate: + replacement: Schema.decodeEffect(Schema.toType(schema)) + note: Validation was removed; decode through the schema's type side. +effect/Schema#validateEither: + replacement: Schema.decodeExit(Schema.toType(schema)) + note: Validation was removed; decode through the schema's type side. +effect/Schema#validatePromise: + replacement: Schema.decodePromise(Schema.toType(schema)) + note: Validation was removed; decode through the schema's type side. +effect/Schema#validDate: + replacement: Schema.Date + note: Use the v4 Date self schema, which rejects invalid Date values. +effect/Schema#ValidDateFromSelf: + replacement: Schema.Date + note: Use the v4 Date self schema, which rejects invalid Date values. +effect/Schema#ValidDateSchemaId: + replacement: none + note: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. +effect/Schema#Void: + replacement: Schema.Void + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#withConstructorDefault: + replacement: Schema.withConstructorDefault + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#withDecodingDefault: + replacement: Schema.withDecodingDefault + note: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. +effect/Schema#withDefaults: + replacement: none + note: Removed; choose `withConstructorDefault` and decoding-default helpers explicitly for each side. +effect/Schema#WithResult: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.All: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.Any: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.Context: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.Failure: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.FailureEncoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.Success: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. +effect/Schema#WithResult.SuccessEncoded: + replacement: none + note: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. diff --git a/migration/annotations/effect__SchemaAST.yaml b/migration/annotations/effect__SchemaAST.yaml new file mode 100644 index 000000000..662732601 --- /dev/null +++ b/migration/annotations/effect__SchemaAST.yaml @@ -0,0 +1,444 @@ +"effect/SchemaAST#Annotated": + replacement: "SchemaAST.AST" + note: "The public base type was removed. Use the AST union; every variant still exposes annotations, checks, encoding, and context." +"effect/SchemaAST#annotations": + replacement: "SchemaAST.annotate" + note: "Use the v4 annotation helper and string-keyed Schema.Annotations." +"effect/SchemaAST#anyKeyword": + replacement: "SchemaAST.any" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#AnyKeyword": + replacement: "SchemaAST.Any" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#ArbitraryAnnotationId": + replacement: "Schema.Annotations.ToArbitrary" + note: "Symbol annotation IDs were removed. Declarations use the toCodecArbitrary annotation; filters use arbitraryConstraint." +"effect/SchemaAST#AST": + replacement: "SchemaAST.AST" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#BatchingAnnotation": + replacement: "none" + note: "Per-schema batching annotations were removed; control asynchronous parsing with ParseOptions.concurrency." +"effect/SchemaAST#BatchingAnnotationId": + replacement: "none" + note: "Symbol annotation IDs were removed and batching is no longer a schema annotation." +"effect/SchemaAST#bigIntKeyword": + replacement: "SchemaAST.bigInt" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#BigIntKeyword": + replacement: "SchemaAST.BigInt" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#booleanKeyword": + replacement: "SchemaAST.boolean" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#BooleanKeyword": + replacement: "SchemaAST.Boolean" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#BrandAnnotation": + replacement: "Schema.Annotations.Bottom[\"brands\"]" + note: "Brands are stored under the string-keyed brands annotation and normally added with Schema.brand." +"effect/SchemaAST#BrandAnnotationId": + replacement: "Schema.brand" + note: "Symbol annotation IDs were removed; add brands through Schema.brand." +"effect/SchemaAST#Compiler": + replacement: "none" + note: "The generic AST compiler abstraction was removed; traverse the discriminated SchemaAST.AST union directly or use a higher-level Schema derivation API." +"effect/SchemaAST#pick": + replacement: "none" + note: "The low-level AST picker was removed. Keep field selection at the Schema.Struct level with mapFields and Struct.pick, or discriminate and rebuild custom AST nodes explicitly." +"effect/SchemaAST#composeTransformation": + replacement: "SchemaAST.Encoding" + note: "V4 transformations are SchemaAST.Link values in an encoding chain; compose by adding links with SchemaAST.decodeTo." +"effect/SchemaAST#ComposeTransformation": + replacement: "SchemaAST.Encoding" + note: "The marker transformation was replaced by explicit SchemaAST.Link encoding chains." +"effect/SchemaAST#ConcurrencyAnnotation": + replacement: "SchemaAST.ParseOptions[\"concurrency\"]" + note: "Concurrency is now a parse option rather than its own annotation type." +"effect/SchemaAST#ConcurrencyAnnotationId": + replacement: "Schema.Annotations.Bottom[\"parseOptions\"]" + note: "Symbol annotation IDs were removed; put concurrency inside the parseOptions annotation." +"effect/SchemaAST#Declaration": + replacement: "SchemaAST.Declaration" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#DecodingFallbackAnnotation": + replacement: "Schema.catchDecoding" + note: "Fallbacks are now encoding middleware added with Schema.catchDecoding." +"effect/SchemaAST#DecodingFallbackAnnotationId": + replacement: "Schema.catchDecoding" + note: "The symbol annotation was removed; attach decoding recovery with Schema.catchDecoding." +"effect/SchemaAST#DefaultAnnotation": + replacement: "Schema.Annotations.Documentation[\"default\"]" + note: "Defaults are string-keyed schema annotations in v4." +"effect/SchemaAST#DefaultAnnotationId": + replacement: "Schema.Annotations.Documentation[\"default\"]" + note: "Symbol annotation IDs were removed; use the default key." +"effect/SchemaAST#defaultParseOption": + replacement: "SchemaAST.defaultParseOptions" + note: "The default parse options constant was pluralized." +"effect/SchemaAST#DescriptionAnnotation": + replacement: "Schema.Annotations.Augment[\"description\"]" + note: "Descriptions are string-keyed schema annotations in v4." +"effect/SchemaAST#DescriptionAnnotationId": + replacement: "Schema.Annotations.Augment[\"description\"]" + note: "Symbol annotation IDs were removed; use the description key." +"effect/SchemaAST#DocumentationAnnotation": + replacement: "Schema.Annotations.Augment[\"documentation\"]" + note: "Documentation is a string-keyed schema annotation in v4." +"effect/SchemaAST#DocumentationAnnotationId": + replacement: "Schema.Annotations.Augment[\"documentation\"]" + note: "Symbol annotation IDs were removed; use the documentation key." +"effect/SchemaAST#encodedAST": + replacement: "SchemaAST.toEncoded" + note: "The encoded projection helper was renamed." +"effect/SchemaAST#encodedBoundAST": + replacement: "SchemaAST.toEncoded" + note: "The separate encoded-bound projection was removed; use the encoded projection and v4 encoding links." +"effect/SchemaAST#Enums": + replacement: "SchemaAST.Enum" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#EquivalenceAnnotation": + replacement: "Schema.Annotations.ToEquivalence.Declaration" + note: "Equivalence derivation annotations now use the toEquivalence key in Schema.Annotations." +"effect/SchemaAST#EquivalenceAnnotationId": + replacement: "Schema.overrideToEquivalence" + note: "The symbol annotation was removed; attach custom equivalence derivation with Schema.overrideToEquivalence." +"effect/SchemaAST#ExamplesAnnotation": + replacement: "Schema.Annotations.Documentation[\"examples\"]" + note: "Examples are string-keyed schema annotations in v4." +"effect/SchemaAST#ExamplesAnnotationId": + replacement: "Schema.Annotations.Documentation[\"examples\"]" + note: "Symbol annotation IDs were removed; use the examples key." +"effect/SchemaAST#FinalTransformation": + replacement: "SchemaTransformation.Transformation" + note: "Transformations moved to SchemaTransformation and are stored in SchemaAST.Link values." +"effect/SchemaAST#getAnnotation": + replacement: "SchemaAST.resolveAt" + note: "Resolve string-keyed annotations with resolveAt, or use resolveIdentifier, resolveTitle, and resolveDescription." +"effect/SchemaAST#getBatchingAnnotation": + replacement: "none" + note: "Batching annotations were removed; read ParseOptions.concurrency when controlling asynchronous parsing." +"effect/SchemaAST#getBrandAnnotation": + replacement: "SchemaAST.resolveAt(\"brands\")" + note: "Resolve the string-keyed brands annotation." +"effect/SchemaAST#getCompiler": + replacement: "none" + note: "The Match-based compiler was removed; traverse SchemaAST.AST directly or use the relevant Schema derivation API." +"effect/SchemaAST#getConcurrencyAnnotation": + replacement: "SchemaAST.resolveAt(\"parseOptions\")" + note: "Resolve parseOptions and read concurrency from it." +"effect/SchemaAST#getDecodingFallbackAnnotation": + replacement: "none" + note: "Fallbacks are encoding middleware in v4, not readable annotations; attach them with Schema.catchDecoding." +"effect/SchemaAST#getDefaultAnnotation": + replacement: "SchemaAST.resolveAt(\"default\")" + note: "Resolve the string-keyed default annotation." +"effect/SchemaAST#getDescriptionAnnotation": + replacement: "SchemaAST.resolveDescription" + note: "Use the dedicated resolved-description helper." +"effect/SchemaAST#getDocumentationAnnotation": + replacement: "SchemaAST.resolveAt(\"documentation\")" + note: "Resolve the string-keyed documentation annotation." +"effect/SchemaAST#getExamplesAnnotation": + replacement: "SchemaAST.resolveAt(\"examples\")" + note: "Resolve the string-keyed examples annotation." +"effect/SchemaAST#getIdentifierAnnotation": + replacement: "SchemaAST.resolveIdentifier" + note: "Use the dedicated resolved-identifier helper." +"effect/SchemaAST#getJSONIdentifier": + replacement: "SchemaAST.resolveIdentifier" + note: "JSON Schema references now use the normal resolved identifier." +"effect/SchemaAST#getJSONIdentifierAnnotation": + replacement: "SchemaAST.resolveIdentifier" + note: "The separate JSON identifier annotation was removed; use identifier." +"effect/SchemaAST#getJSONSchemaAnnotation": + replacement: "SchemaAST.resolveAt(\"toJsonSchema\")" + note: "JSON Schema generation hooks use the string-keyed toJsonSchema annotation on checks." +"effect/SchemaAST#getMessageAnnotation": + replacement: "SchemaAST.resolveAt(\"message\")" + note: "Resolve the string-keyed message annotation." +"effect/SchemaAST#getMissingMessageAnnotation": + replacement: "SchemaAST.resolveAt(\"messageMissingKey\")" + note: "Missing-key messages use the messageMissingKey key." +"effect/SchemaAST#getParseIssueTitleAnnotation": + replacement: "none" + note: "Issue-title callbacks were removed; use message or expected annotations and SchemaIssue formatters." +"effect/SchemaAST#getParseOptionsAnnotation": + replacement: "SchemaAST.resolveAt(\"parseOptions\")" + note: "Resolve the string-keyed parseOptions annotation." +"effect/SchemaAST#getPropertySignatures": + replacement: "SchemaAST.Objects.propertySignatures" + note: "Narrow to Objects and read propertySignatures directly." +"effect/SchemaAST#getSchemaIdAnnotation": + replacement: "SchemaAST.resolveIdentifier" + note: "Schema IDs were consolidated into the identifier annotation." +"effect/SchemaAST#getSurrogateAnnotation": + replacement: "SchemaAST.resolveAt(\"representation\")" + note: "Surrogate AST annotations were replaced by representation annotations and declaration codec hooks." +"effect/SchemaAST#getTemplateLiteralCapturingRegExp": + replacement: "none" + note: "The low-level RegExp compiler was removed; use Schema.TemplateLiteral and schema parsing instead." +"effect/SchemaAST#getTemplateLiteralRegExp": + replacement: "none" + note: "The low-level RegExp compiler was removed; use Schema.TemplateLiteral and schema parsing instead." +"effect/SchemaAST#getTitleAnnotation": + replacement: "SchemaAST.resolveTitle" + note: "Use the dedicated resolved-title helper." +"effect/SchemaAST#getTypeConstructorAnnotation": + replacement: "SchemaAST.resolveAt(\"toCodec\")" + note: "Type-constructor behavior moved to declaration codec annotations." +"effect/SchemaAST#IdentifierAnnotation": + replacement: "Schema.Annotations.Bottom[\"identifier\"]" + note: "Identifiers are string-keyed schema annotations in v4." +"effect/SchemaAST#IdentifierAnnotationId": + replacement: "Schema.Annotations.Bottom[\"identifier\"]" + note: "Symbol annotation IDs were removed; use the identifier key." +"effect/SchemaAST#IndexSignature": + replacement: "SchemaAST.IndexSignature" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#isAnyKeyword": + replacement: "SchemaAST.isAny" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isBigIntKeyword": + replacement: "SchemaAST.isBigInt" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isBooleanKeyword": + replacement: "SchemaAST.isBoolean" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isComposeTransformation": + replacement: "none" + note: "Compose transformation markers were replaced by explicit SchemaAST.Link encoding chains." +"effect/SchemaAST#isEnums": + replacement: "SchemaAST.isEnum" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isFinalTransformation": + replacement: "SchemaTransformation.Transformation" + note: "Use SchemaTransformation guards or the transformation object stored on a SchemaAST.Link." +"effect/SchemaAST#isNeverKeyword": + replacement: "SchemaAST.isNever" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isNumberKeyword": + replacement: "SchemaAST.isNumber" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isParameter": + replacement: "SchemaAST.isString" + note: "The Parameter union was removed; inspect the v4 key AST variants directly." +"effect/SchemaAST#isRefinement": + replacement: "SchemaAST.Check" + note: "Refinement AST nodes became checks attached to Base.checks." +"effect/SchemaAST#isStringKeyword": + replacement: "SchemaAST.isString" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isSymbolKeyword": + replacement: "SchemaAST.isSymbol" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isTransformation": + replacement: "SchemaAST.Encoding" + note: "Transformation AST nodes became encoding links attached to Base.encoding." +"effect/SchemaAST#isTupleType": + replacement: "SchemaAST.isArrays" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isTypeLiteral": + replacement: "SchemaAST.isObjects" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isTypeLiteralTransformation": + replacement: "Schema.encodeKeys" + note: "Property-key transformations are represented by encoding links and normally built with Schema.encodeKeys." +"effect/SchemaAST#isUndefinedKeyword": + replacement: "SchemaAST.isUndefined" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isUnknownKeyword": + replacement: "SchemaAST.isUnknown" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#isVoidKeyword": + replacement: "SchemaAST.isVoid" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#JSONIdentifierAnnotationId": + replacement: "Schema.Annotations.Bottom[\"identifier\"]" + note: "The separate JSON identifier symbol was removed; use identifier." +"effect/SchemaAST#JSONSchemaAnnotation": + replacement: "JsonSchema.JsonSchema" + note: "JSON Schema values use the v4 JsonSchema model; generation hooks use Schema representation annotations." +"effect/SchemaAST#JSONSchemaAnnotationId": + replacement: "Schema.Annotations.Filter[\"toJsonSchema\"]" + note: "The symbol annotation was replaced by the toJsonSchema key on check annotations." +"effect/SchemaAST#keyof": + replacement: "none" + note: "Low-level SchemaAST.keyof was removed; model the desired key literals explicitly." +"effect/SchemaAST#Literal": + replacement: "SchemaAST.Literal" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#LiteralValue": + replacement: "SchemaAST.LiteralValue" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#Match": + replacement: "none" + note: "The Match compiler table was removed; traverse the discriminated SchemaAST.AST union directly." +"effect/SchemaAST#Members": + replacement: "ReadonlyArray" + note: "Union members are ordinary readonly arrays in v4." +"effect/SchemaAST#MessageAnnotation": + replacement: "Schema.Annotations.Bottom[\"message\"]" + note: "Messages are string-keyed annotations and no longer receive the old ParseIssue callback shape." +"effect/SchemaAST#MessageAnnotationId": + replacement: "Schema.Annotations.Bottom[\"message\"]" + note: "Symbol annotation IDs were removed; use the message key." +"effect/SchemaAST#MissingMessageAnnotation": + replacement: "Schema.Annotations.Key[\"messageMissingKey\"]" + note: "Missing-key messages use the messageMissingKey key." +"effect/SchemaAST#MissingMessageAnnotationId": + replacement: "Schema.Annotations.Key[\"messageMissingKey\"]" + note: "Symbol annotation IDs were removed; use messageMissingKey." +"effect/SchemaAST#mutable": + replacement: "Schema.mutable" + note: "Apply mutability at the Schema level; AST property mutability is represented by Context." +"effect/SchemaAST#neverKeyword": + replacement: "SchemaAST.never" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#NeverKeyword": + replacement: "SchemaAST.Never" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#null": + replacement: "SchemaAST.null" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#numberKeyword": + replacement: "SchemaAST.number" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#NumberKeyword": + replacement: "SchemaAST.Number" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#ObjectKeyword": + replacement: "SchemaAST.ObjectKeyword" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#omit": + replacement: "Schema.mapFields + Struct.omit" + note: "Object projection moved to schema field transforms." +"effect/SchemaAST#OptionalType": + replacement: "SchemaAST.Context" + note: "Element and property optionality moved into per-node Context." +"effect/SchemaAST#Parameter": + replacement: "SchemaAST.AST" + note: "The dedicated index-parameter union was removed; v4 validates supported key AST variants when building an IndexSignature." +"effect/SchemaAST#ParseIssueTitleAnnotation": + replacement: "none" + note: "Issue-title callbacks were removed; use message or expected annotations and SchemaIssue formatters." +"effect/SchemaAST#ParseIssueTitleAnnotationId": + replacement: "none" + note: "The symbol annotation was removed; use message or expected annotations." +"effect/SchemaAST#ParseJsonSchemaId": + replacement: "Schema.UnknownFromJsonString" + note: "Use the built-in JSON string codec instead of checking the old schema ID." +"effect/SchemaAST#ParseOptions": + replacement: "SchemaAST.ParseOptions" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#ParseOptionsAnnotationId": + replacement: "Schema.Annotations.Bottom[\"parseOptions\"]" + note: "Symbol annotation IDs were removed; use the parseOptions key." +"effect/SchemaAST#partial": + replacement: "Schema.mapFields + Struct.map(Schema.optional)" + note: "Partial object transforms moved to schema field transforms." +"effect/SchemaAST#PrettyAnnotationId": + replacement: "Schema.overrideToFormatter" + note: "The symbol annotation was removed; attach custom formatters with Schema.overrideToFormatter." +"effect/SchemaAST#PropertySignature": + replacement: "SchemaAST.PropertySignature" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#PropertySignatureTransformation": + replacement: "Schema.encodeKeys" + note: "Property-key transformations are now encoding links, normally built with Schema.encodeKeys." +"effect/SchemaAST#Refinement": + replacement: "SchemaAST.Check" + note: "Refinements became Filter or FilterGroup checks attached to an AST node." +"effect/SchemaAST#required": + replacement: "Schema.mapFields + Struct.map(Schema.requiredKey)" + note: "Required object transforms moved to schema field transforms." +"effect/SchemaAST#SchemaIdAnnotation": + replacement: "Schema.Annotations.Bottom[\"identifier\"]" + note: "Schema IDs were consolidated into identifier annotations." +"effect/SchemaAST#SchemaIdAnnotationId": + replacement: "Schema.Annotations.Bottom[\"identifier\"]" + note: "Symbol annotation IDs were removed; use identifier." +"effect/SchemaAST#stringKeyword": + replacement: "SchemaAST.string" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#StringKeyword": + replacement: "SchemaAST.String" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#SurrogateAnnotation": + replacement: "SchemaRepresentation.RepresentationAnnotation" + note: "Surrogate AST metadata was replaced by schema representation annotations and declaration codec hooks." +"effect/SchemaAST#SurrogateAnnotationId": + replacement: "Schema.Annotations.Declaration[\"representation\"]" + note: "The symbol annotation was replaced by the representation key." +"effect/SchemaAST#Suspend": + replacement: "SchemaAST.Suspend" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#symbolKeyword": + replacement: "SchemaAST.symbol" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#SymbolKeyword": + replacement: "SchemaAST.Symbol" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#TemplateLiteral": + replacement: "SchemaAST.TemplateLiteral" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. Parts must not contain encodings, including inside unions or nested templates; use Schema.TemplateLiteralParser for transformed parts." +"effect/SchemaAST#TemplateLiteralSpan": + replacement: "SchemaAST.TemplateLiteral" + note: "Template literal parts are represented directly as AST values in v4." +"effect/SchemaAST#TitleAnnotation": + replacement: "Schema.Annotations.Augment[\"title\"]" + note: "Titles are string-keyed schema annotations in v4." +"effect/SchemaAST#TitleAnnotationId": + replacement: "Schema.Annotations.Augment[\"title\"]" + note: "Symbol annotation IDs were removed; use title." +"effect/SchemaAST#Transformation": + replacement: "SchemaAST.Link" + note: "Transformations are links in the Base.encoding chain in v4." +"effect/SchemaAST#TransformationKind": + replacement: "SchemaTransformation.Transformation" + note: "Transformation implementations moved to SchemaTransformation and are stored on SchemaAST.Link." +"effect/SchemaAST#TupleType": + replacement: "SchemaAST.Arrays" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#Type": + replacement: "SchemaAST.AST" + note: "The tuple-element Type wrapper was removed; optionality and mutability moved to Context." +"effect/SchemaAST#typeAST": + replacement: "SchemaAST.toType" + note: "The type-side projection helper was renamed." +"effect/SchemaAST#TypeConstructorAnnotation": + replacement: "Schema.Annotations.Declaration[\"toCodec\"]" + note: "Type-constructor behavior moved to declaration codec annotations." +"effect/SchemaAST#TypeConstructorAnnotationId": + replacement: "Schema.Annotations.Declaration[\"toCodec\"]" + note: "Symbol annotation IDs were removed; use declaration codec annotation keys." +"effect/SchemaAST#TypeLiteral": + replacement: "SchemaAST.Objects" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#TypeLiteralTransformation": + replacement: "SchemaAST.Encoding" + note: "Object transformations are encoding links; use Schema.encodeKeys for key mappings." +"effect/SchemaAST#undefinedKeyword": + replacement: "SchemaAST.undefined" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#UndefinedKeyword": + replacement: "SchemaAST.Undefined" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#Union": + replacement: "SchemaAST.Union" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#UniqueSymbol": + replacement: "SchemaAST.UniqueSymbol" + note: "The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model." +"effect/SchemaAST#unknownKeyword": + replacement: "SchemaAST.unknown" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#UnknownKeyword": + replacement: "SchemaAST.Unknown" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#voidKeyword": + replacement: "SchemaAST.void" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." +"effect/SchemaAST#VoidKeyword": + replacement: "SchemaAST.Void" + note: "The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role." diff --git a/migration/annotations/effect__Scope.yaml b/migration/annotations/effect__Scope.yaml new file mode 100644 index 000000000..e27e3e680 --- /dev/null +++ b/migration/annotations/effect__Scope.yaml @@ -0,0 +1,21 @@ +"effect/Scope#CloseableScope": + replacement: "Scope.Closeable" + note: "Use the renamed type and close it with Scope.close(scope, exit)." +"effect/Scope#CloseableScopeTypeId": + replacement: "none" + note: "The closeable-scope marker is private in v4." +"effect/Scope#extend": + replacement: "Scope.provide" + note: "The operation was renamed with the same data-first and curried forms." +"effect/Scope#Scope": + replacement: "Scope.Scope" + note: "The type remains; use module functions instead of the removed instance methods." +"effect/Scope#Scope.Closeable": + replacement: "Scope.Closeable" + note: "The nested alias is now the top-level Closeable interface." +"effect/Scope#Scope.Finalizer": + replacement: "(exit: Exit.Exit) => Effect.Effect" + note: "No alias is exported; inline the Scope.addFinalizerExit callback type." +"effect/Scope#ScopeTypeId": + replacement: "none" + note: "The Scope marker is private in v4 and has no public guard." diff --git a/migration/annotations/effect__ScopedCache.yaml b/migration/annotations/effect__ScopedCache.yaml new file mode 100644 index 000000000..25b6de9b5 --- /dev/null +++ b/migration/annotations/effect__ScopedCache.yaml @@ -0,0 +1,12 @@ +effect/ScopedCache#Lookup: + replacement: "(key: Key) => Effect.Effect" + note: "The named alias was removed; use an inline lookup type or ScopedCache.ScopedCache[\"lookup\"]." +effect/ScopedCache#ScopedCache: + replacement: "ScopedCache.ScopedCache" + note: "The model remains as a Pipeable scoped cache; construct and use it inside a Scope with explicit ScopedCache operations." +effect/ScopedCache#ScopedCache.Variance: + replacement: "none" + note: "The public variance marker was removed; use ScopedCache.ScopedCache directly." +effect/ScopedCache#ScopedCacheTypeId: + replacement: "none" + note: "The ScopedCache type id is internal in v4; do not inspect or construct the brand directly." diff --git a/migration/annotations/effect__ScopedRef.yaml b/migration/annotations/effect__ScopedRef.yaml new file mode 100644 index 000000000..42581a204 --- /dev/null +++ b/migration/annotations/effect__ScopedRef.yaml @@ -0,0 +1,15 @@ +"effect/ScopedRef#ScopedRef": + replacement: "ScopedRef.ScopedRef" + note: "The type remains but no longer extends Effect; use ScopedRef.get or ScopedRef.getUnsafe." +"effect/ScopedRef#ScopedRef.Variance": + replacement: "none" + note: "The exported variance artifact was removed." +"effect/ScopedRef#ScopedRefTypeId": + replacement: "none" + note: "The marker is private in v4 and no public guard exists." +"effect/ScopedRef#ScopedRefUnify": + replacement: "none" + note: "ScopedRef no longer extends Effect; use ScopedRef.get explicitly." +"effect/ScopedRef#ScopedRefUnifyIgnore": + replacement: "none" + note: "The Effect-unification implementation detail was removed." diff --git a/migration/annotations/effect__Secret.yaml b/migration/annotations/effect__Secret.yaml new file mode 100644 index 000000000..475273e88 --- /dev/null +++ b/migration/annotations/effect__Secret.yaml @@ -0,0 +1,21 @@ +"effect/Secret#fromIterable": + replacement: "Redacted.make(Array.from(iterable).join(\"\"))" + note: "Secret was removed; join the character iterable and wrap the resulting string in Redacted." +"effect/Secret#isSecret": + replacement: "Redacted.isRedacted" + note: "Secret was removed in favor of Redacted." +"effect/Secret#make": + replacement: "Redacted.make(bytes.map((byte) => String.fromCharCode(byte)).join(\"\"))" + note: "Secret was removed; preserve the v3 byte-to-code-unit conversion explicitly, then wrap the string in Redacted." +"effect/Secret#Secret": + replacement: "Redacted.Redacted" + note: "Secret was deprecated in v3 and removed in v4; use the generic Redacted wrapper." +"effect/Secret#Secret.Proto": + replacement: "none" + note: "The Secret-specific prototype was removed with the module; use Redacted.Redacted." +"effect/Secret#SecretTypeId": + replacement: "Redacted.isRedacted" + note: "The Secret marker was removed; use the Redacted runtime guard." +"effect/Secret#unsafeWipe": + replacement: "Redacted.wipeUnsafe" + note: "Redacted.wipeUnsafe removes the registry entry but, unlike v3 Secret, cannot zero a retained mutable byte array; zero external buffers separately when required." diff --git a/migration/annotations/effect__SingleProducerAsyncInput.yaml b/migration/annotations/effect__SingleProducerAsyncInput.yaml new file mode 100644 index 000000000..3801646de --- /dev/null +++ b/migration/annotations/effect__SingleProducerAsyncInput.yaml @@ -0,0 +1,12 @@ +"effect/SingleProducerAsyncInput#AsyncInputConsumer": + replacement: "Queue.Dequeue" + note: "Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one." +"effect/SingleProducerAsyncInput#AsyncInputProducer": + replacement: "Queue.Enqueue" + note: "Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one." +"effect/SingleProducerAsyncInput#make": + replacement: "Queue.make" + note: "Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one." +"effect/SingleProducerAsyncInput#SingleProducerAsyncInput": + replacement: "Queue.Queue" + note: "Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one." diff --git a/migration/annotations/effect__Sink.yaml b/migration/annotations/effect__Sink.yaml new file mode 100644 index 000000000..636836d09 --- /dev/null +++ b/migration/annotations/effect__Sink.yaml @@ -0,0 +1,204 @@ +effect/Sink#Sink: + replacement: "Sink" + note: "Interface kept as Sink with the same type parameters; the internal representation changed from a Channel wrapper to a `transform: (upstream: Pull>, scope) => Effect>` function, and completion is the tuple `Sink.End = readonly [value, leftover?]`." +effect/Sink#Sink.Variance: + replacement: "Sink.Variance" + note: "Still exists as the namespace interface Sink.Variance (with Sink.VarianceStruct); the variance key is now the internal string \"~effect/Sink\" instead of the SinkTypeId symbol." +effect/Sink#SinkTypeId: + replacement: "none" + note: "The type id is the unexported internal string \"~effect/Sink\" (no unique symbol, no export); use the new guard Sink.isSink(u) instead of checking the symbol." +effect/Sink#SinkUnify: + replacement: "SinkUnify" + note: "Kept with the same name and shape (extends Effect.EffectUnify, preserves all five Sink type parameters)." +effect/Sink#SinkUnifyIgnore: + replacement: "SinkUnifyIgnore" + note: "Kept with the same name; now a standalone `{ Effect?: true }` interface instead of extending Effect.EffectUnifyIgnore." +effect/Sink#collectAll: + replacement: "Sink.collect" + note: "Renamed; returns Sink, In> collecting into a plain mutable Array instead of Chunk." +effect/Sink#collectAllFrom: + replacement: "none" + note: "Repeated-run result accumulation was removed; checked the v4 export list (no collectAllFrom/repeatedly). Re-implement with Sink.fromTransform, looping self.transform on the upstream pull (feeding leftovers back) until the upstream ends, accumulating results in an array." +effect/Sink#collectAllN: + replacement: "Sink.take" + note: "Sink.take(n) returns Sink, In, In> collecting up to n elements (Array instead of Chunk), emitting the unconsumed remainder as leftovers." +effect/Sink#collectAllToMap: + replacement: "Sink.reduce" + note: "Built-in HashMap collector removed; build a plain Map in the reducer: Sink.reduce(() => new Map(), (m, in_) => { const k = key(in_); return m.set(k, m.has(k) ? merge(m.get(k)!, in_) : in_) })." +effect/Sink#collectAllToMapN: + replacement: "none" + note: "Removed; approximating with Sink.reduceWhile((...) , (m) => m.size < n, ...) consumes (merges) the element that introduces the (n+1)-th key, whereas v3 left it as leftover — exact v3 leftover behavior needs a custom Sink.fromTransform that checks the key before consuming." +effect/Sink#collectAllToSet: + replacement: "Sink.reduce" + note: "Built-in HashSet collector removed; build a plain Set: Sink.reduce(() => new Set(), (s, in_) => s.add(in_))." +effect/Sink#collectAllToSetN: + replacement: "Sink.reduceWhile" + note: "Removed; equivalent via Sink.reduceWhile(() => new Set(), (s) => s.size < n, (s, in_) => s.add(in_)) — stops with leftovers once n distinct values are collected (plain Set instead of HashSet)." +effect/Sink#collectAllUntil: + replacement: "Sink.takeUntil" + note: "Renamed; Sink.takeUntil(predicate) collects into Array until the predicate matches, including the matching element, like v3." +effect/Sink#collectAllUntilEffect: + replacement: "Sink.takeUntilEffect" + note: "Renamed; Sink.takeUntilEffect(p) collects into Array until the effectful predicate returns true, including the matching element." +effect/Sink#collectAllWhile: + replacement: "Sink.takeWhile" + note: "Renamed; Sink.takeWhile(predicate) collects the matching prefix into Array, keeps the refinement overload, and excludes the first failing element (returned via leftovers)." +effect/Sink#collectAllWhileEffect: + replacement: "Sink.takeWhileEffect" + note: "Renamed; Sink.takeWhileEffect(p) collects into Array while the effectful predicate returns true." +effect/Sink#collectAllWhileWith: + replacement: "none" + note: "Repeatedly-run-and-fold was removed (no v4 counterpart in the export list); re-implement with Sink.fromTransform looping self.transform while the `while` predicate holds on each result, folding results with `body` and feeding leftovers back into the next run." +effect/Sink#collectLeftover: + replacement: "Sink.mapEnd" + note: "Use Sink.mapEnd to move the leftovers into the result: Sink.mapEnd(self, ([a, leftover]) => [[a, leftover ?? []] as const]); leftovers are NonEmptyReadonlyArray | undefined instead of Chunk." +effect/Sink#context: + replacement: "Sink.fromEffect(Effect.context())" + note: "Sink.context was removed; Sink.fromEffect(Effect.context()) yields the same Sink, unknown, never, never, R>." +effect/Sink#contextWith: + replacement: "Sink.fromEffect(Effect.contextWith(f))" + note: "Removed; compose Sink.fromEffect with Effect.contextWith to derive a value from the context." +effect/Sink#contextWithEffect: + replacement: "Sink.fromEffect(Effect.flatMap(Effect.context(), f))" + note: "Removed, and v4 Effect has no contextWithEffect; use Sink.fromEffect(Effect.flatMap(Effect.context(), f))." +effect/Sink#contextWithSink: + replacement: "Sink.unwrap(Effect.contextWith(f))" + note: "Removed; Sink.unwrap(Effect.contextWith((ctx: Context.Context) => f(ctx))) builds the sink from the context." +effect/Sink#dieMessage: + replacement: "Sink.die" + note: "Removed (v4 has no RuntimeException-based dieMessage anywhere); use Sink.die(new Error(message))." +effect/Sink#dieSync: + replacement: "Sink.failCauseSync" + note: "Removed; use Sink.failCauseSync(() => Cause.die(evaluate())) to defer defect evaluation, or Sink.die(defect) when eager is fine." +effect/Sink#dimap: + replacement: "Sink.mapInput + Sink.map" + note: "Removed; compose the two halves: self.pipe(Sink.mapInput(f), Sink.map(g))." +effect/Sink#dimapEffect: + replacement: "Sink.mapInputEffect + Sink.mapEffect" + note: "Removed; compose self.pipe(Sink.mapInputEffect(f), Sink.mapEffect(g))." +effect/Sink#dimapChunks: + replacement: "Sink.mapInputArray + Sink.map" + note: "Removed; compose self.pipe(Sink.mapInputArray(f), Sink.map(g)) — f now maps NonEmptyReadonlyArray instead of Chunk and must return a non-empty array." +effect/Sink#dimapChunksEffect: + replacement: "Sink.mapInputArrayEffect + Sink.mapEffect" + note: "Removed; compose self.pipe(Sink.mapInputArrayEffect(f), Sink.mapEffect(g)) — f maps NonEmptyReadonlyArray instead of Chunk and must return a non-empty array." +effect/Sink#drop: + replacement: "none" + note: "The drop* sinks were removed (nothing in the v4 export list); drop on the stream side instead with Stream.drop(n) before running the sink, or write a Sink.fromTransform that discards the first n pulled elements." +effect/Sink#dropUntil: + replacement: "none" + note: "Removed with the other drop* sinks; use Stream.dropUntil(predicate) on the stream before running the sink." +effect/Sink#dropUntilEffect: + replacement: "none" + note: "Removed; use Stream.dropUntilEffect(p) on the stream before running the sink." +effect/Sink#dropWhile: + replacement: "none" + note: "Removed; use Stream.dropWhile(predicate) on the stream before running the sink." +effect/Sink#dropWhileEffect: + replacement: "none" + note: "Removed; use Stream.dropWhileEffect(p) on the stream before running the sink." +effect/Sink#ensuringWith: + replacement: "Sink.onExit" + note: "Renamed; Sink.onExit(self, (exit: Exit) => finalizer) runs after completion, failure, or interruption — the exit now carries the sink's result value A (v3 passed Exit). Plain Sink.ensuring(effect) also still exists for the exit-independent case." +effect/Sink#filterInput: + replacement: "none" + note: "Removed, and not expressible via Sink.mapInputArray because its function must return a non-empty array (a fully-filtered batch is illegal); filter on the stream with Stream.filter(predicate) before running the sink, or write a Sink.fromTransform that skips empty filtered batches." +effect/Sink#filterInputEffect: + replacement: "none" + note: "Removed (same non-empty-array constraint as filterInput); use Stream.filterEffect(p) on the stream before running the sink." +effect/Sink#foldChunks: + replacement: "Sink.reduceWhileArray" + note: "Sink.reduceWhileArray(() => s, contFn, f) folds whole input batches; initial state is now a lazy thunk and f receives NonEmptyReadonlyArray instead of Chunk." +effect/Sink#foldChunksEffect: + replacement: "Sink.reduceWhileArrayEffect" + note: "Sink.reduceWhileArrayEffect(() => s, contFn, f) is the effectful array-level fold with continuation predicate; lazy initial state, NonEmptyReadonlyArray instead of Chunk. Sink.foldArray has the same shape but does not check contFn on the initial state." +effect/Sink#foldEffect: + replacement: "Sink.reduceWhileEffect" + note: "Sink.reduceWhileEffect(() => s, contFn, f) folds element-by-element with an effectful step and continuation predicate (checked on the initial state, like v3); initial state is now a lazy thunk. v4 Sink.fold has the same signature but skips the initial-state check." +effect/Sink#foldLeft: + replacement: "Sink.reduce" + note: "Renamed; Sink.reduce(() => s, f) — initial state is now a lazy thunk, semantics otherwise identical." +effect/Sink#foldLeftChunks: + replacement: "Sink.reduceArray" + note: "Renamed; Sink.reduceArray(() => s, f) folds whole batches — lazy initial state, f receives NonEmptyReadonlyArray instead of Chunk." +effect/Sink#foldLeftChunksEffect: + replacement: "Sink.reduceWhileArrayEffect" + note: "No plain reduceArrayEffect exists in v4; use Sink.reduceWhileArrayEffect(() => s, () => true, f) (constant-true predicate) — f receives NonEmptyReadonlyArray instead of Chunk and the result has L = never like v3." +effect/Sink#foldLeftEffect: + replacement: "Sink.reduceEffect" + note: "Renamed; Sink.reduceEffect(() => s, f) — lazy initial state, effectful step, no termination predicate." +effect/Sink#foldSink: + replacement: "Sink.orElse + Sink.flatMap" + note: "The two-channel match was removed; compose self.pipe(Sink.orElse((e) => options.onFailure(e)), Sink.flatMap((a) => options.onSuccess(a))) — orElse switches to the failure sink (resuming the same upstream), flatMap feeds leftovers to the success sink first." +effect/Sink#foldUntilEffect: + replacement: "Sink.foldUntil" + note: "v4 Sink.foldUntil(() => s, max, f) takes the effectful step function directly (f returns Effect), so it covers v3 foldUntilEffect; initial state is now a lazy thunk. For the pure v3 foldUntil wrap the step in Effect.succeed." +effect/Sink#foldWeighted: + replacement: "none" + note: "The whole foldWeighted family was removed from v4 Sink (checked the export list); re-implement with Sink.fold carrying the accumulated cost in the state (cont while cost < max), returning leftovers automatically when stopping mid-batch." +effect/Sink#foldWeightedDecompose: + replacement: "none" + note: "Removed with no decompose mechanism in v4; splitting oversized elements must happen upstream (transform the stream before the sink) or inside a custom Sink.fromTransform." +effect/Sink#foldWeightedDecomposeEffect: + replacement: "none" + note: "Removed; same as foldWeightedDecompose — no effectful weighted/decompose fold exists, re-implement via Sink.fromTransform or restructure upstream." +effect/Sink#foldWeightedEffect: + replacement: "none" + note: "Removed; re-implement with Sink.fold (its step is effectful in v4) tracking accumulated cost in the state." +effect/Sink#forEachChunk: + replacement: "Sink.forEachArray" + note: "Renamed; f receives NonEmptyReadonlyArray instead of Chunk." +effect/Sink#forEachChunkWhile: + replacement: "Sink.forEachWhileArray" + note: "Renamed; f: (NonEmptyReadonlyArray) => Effect continues while true, stops on false, as in v3." +effect/Sink#fromPush: + replacement: "Sink.fromTransform" + note: "The push-based protocol (Option push function failing with [Either, leftovers]) is gone; v4's low-level constructor is pull-based: Sink.fromTransform((upstream: Pull>, scope) => Effect>) — pull inputs from upstream and finish by succeeding with the [value, leftover?] tuple." +effect/Sink#leftover: + replacement: "Sink.succeed" + note: "Removed as a standalone constructor; Sink.succeed now takes optional leftovers: Sink.succeed(void 0, leftovers) where leftovers is a NonEmptyReadonlyArray instead of Chunk." +effect/Sink#mapInputChunks: + replacement: "Sink.mapInputArray" + note: "Renamed; f maps NonEmptyReadonlyArray => NonEmptyReadonlyArray (must stay non-empty) instead of Chunk => Chunk." +effect/Sink#mapInputChunksEffect: + replacement: "Sink.mapInputArrayEffect" + note: "Renamed; f maps NonEmptyReadonlyArray => Effect> (must stay non-empty) instead of Chunk => Effect." +effect/Sink#mkString: + replacement: "Sink.reduceArray" + note: "Removed as a built-in; equivalent one-liner: Sink.reduceArray(() => \"\", (s, arr) => s + arr.join(\"\"))." +effect/Sink#race: + replacement: "none" + note: "Sink racing (race/raceBoth/raceWith) was removed from v4; broadcast the stream (Stream.broadcast) into two consumers and race the resulting run effects with Effect.race, or write a custom Channel." +effect/Sink#raceBoth: + replacement: "none" + note: "Removed with the race family; broadcast the stream and use Effect.raceBoth (or Effect.race) on the two Stream.run effects to learn which side won." +effect/Sink#raceWith: + replacement: "none" + note: "Removed, along with the MergeDecision type it depended on; the closest is broadcasting the stream and combining the two run effects manually (Effect.raceWith on the run effects)." +effect/Sink#refineOrDie: + replacement: "Sink.catch" + note: "Removed; rebuild with the typed-error handler: Sink.catch(self, (e) => Option.match(pf(e), { onSome: Effect.fail, onNone: () => Effect.die(e) })) — note Sink.catch replaces the result on recovery, so refined errors must be re-failed as shown." +effect/Sink#refineOrDieWith: + replacement: "Sink.catch" + note: "Removed; same pattern as refineOrDie but die with the mapped defect: Sink.catch(self, (e) => Option.match(pf(e), { onSome: Effect.fail, onNone: () => Effect.die(f(e)) }))." +effect/Sink#splitWhere: + replacement: "none" + note: "Removed; it re-chunked input so the sink stopped before the first later element matching the predicate — closest v4 options are pre-splitting the stream (Stream.split / Stream.takeWhile) or a custom Sink.fromTransform that cuts pulled arrays at the predicate boundary and returns the rest as leftovers." +effect/Sink#unwrapScoped: + replacement: "Sink.unwrap" + note: "Folded into Sink.unwrap, whose signature now excludes Scope from R (Sink<..., Exclude | R2>), so scoped effects are accepted directly; resources stay open for the sink's lifetime." +effect/Sink#unwrapScopedWith: + replacement: "Sink.unwrap" + note: "Folded into Sink.unwrap — obtain the scope inside the effect via Effect.scope (Sink.unwrap(Effect.flatMap(Effect.scope, f))); for direct scope access use Sink.fromTransform, whose transform receives (upstream, scope)." +effect/Sink#zip: + replacement: "Sink.flatMap" + note: "The zip family was removed; sequential zip is self.pipe(Sink.flatMap((a) => Sink.map(that, (a2) => [a, a2] as const))) — leftovers of the first sink feed the second. The { concurrent: true } racing mode has no v4 equivalent." +effect/Sink#zipLeft: + replacement: "Sink.flatMap" + note: "Removed; use self.pipe(Sink.flatMap((a) => Sink.as(that, a))) to run both sequentially and keep the first result (no concurrent option)." +effect/Sink#zipRight: + replacement: "Sink.flatMap" + note: "Removed; use self.pipe(Sink.flatMap(() => that)) to run both sequentially and keep the second result (no concurrent option)." +effect/Sink#zipWith: + replacement: "Sink.flatMap" + note: "Removed; use self.pipe(Sink.flatMap((a) => Sink.map(that, (a2) => f(a, a2)))) — sequential only, the { concurrent: true } option has no v4 equivalent." diff --git a/migration/annotations/effect__SortedMap.yaml b/migration/annotations/effect__SortedMap.yaml new file mode 100644 index 000000000..5139e02e9 --- /dev/null +++ b/migration/annotations/effect__SortedMap.yaml @@ -0,0 +1,66 @@ +"effect/SortedMap#empty": + replacement: "HashMap.empty" + note: "SortedMap was removed; use an immutable HashMap and retain the key Order separately." +"effect/SortedMap#entries": + replacement: "HashMap.entries + Array.sortWith" + note: "Materialize HashMap.entries and sort by key with the retained Order when ordered traversal is required." +"effect/SortedMap#fromIterable": + replacement: "HashMap.fromIterable" + note: "Use HashMap.fromIterable and retain the key Order separately; duplicate keys collapse." +"effect/SortedMap#get": + replacement: "HashMap.get" + note: "Direct optional lookup on the replacement immutable map." +"effect/SortedMap#getOrder": + replacement: "none" + note: "HashMap does not store an Order; retain and pass the key Order explicitly." +"effect/SortedMap#headOption": + replacement: "HashMap.entries + Array.sortWith + Array.head" + note: "Sort entries by key with the retained Order, then take the optional first entry." +"effect/SortedMap#isEmpty": + replacement: "HashMap.isEmpty" + note: "Direct emptiness check on the replacement immutable map." +"effect/SortedMap#isNonEmpty": + replacement: "HashMap.isEmpty" + note: "Use !HashMap.isEmpty(self); no dedicated HashMap.isNonEmpty helper exists." +"effect/SortedMap#isSortedMap": + replacement: "HashMap.isHashMap" + note: "Use the replacement model guard; it does not prove that observations were sorted." +"effect/SortedMap#keys": + replacement: "HashMap.entries + Array.sortWith + Array.map" + note: "Sort entries by key, map to keys, and iterate the resulting array." +"effect/SortedMap#lastOption": + replacement: "HashMap.entries + Array.sortWith + Array.last" + note: "Sort entries by key with the retained Order, then take the optional last entry." +"effect/SortedMap#make": + replacement: "HashMap.make" + note: "Remove the outer order-curried constructor and pass entries directly to HashMap.make." +"effect/SortedMap#map": + replacement: "HashMap.map" + note: "The value-and-key callback remains, but result iteration is unordered until explicitly sorted." +"effect/SortedMap#partition": + replacement: "HashMap.filter" + note: "Build [excluded, satisfying] with complementary HashMap.filter calls; adapt the callback to the old key predicate." +"effect/SortedMap#remove": + replacement: "HashMap.remove" + note: "Direct persistent removal; explicitly sort only when observing entries." +"effect/SortedMap#set": + replacement: "HashMap.set" + note: "Direct persistent insert or update; explicitly sort only when observing entries." +"effect/SortedMap#size": + replacement: "HashMap.size" + note: "Direct size query on the replacement immutable map." +"effect/SortedMap#SortedMap": + replacement: "HashMap.HashMap" + note: "Use HashMap as the immutable core model and retain Order externally; ordered iteration and range seeks require sorting on observation." +"effect/SortedMap#TypeId": + replacement: "none" + note: "The SortedMap brand was removed and HashMap.TypeId is private; use HashMap.isHashMap when a guard is needed." +"effect/SortedMap#values": + replacement: "HashMap.entries + Array.sortWith + Array.map" + note: "Sort entries by key, map to values, and iterate the resulting array." +"effect/SortedMap#has": + replacement: "HashMap.has" + note: "Use direct membership testing on the replacement HashMap; retain the key Order separately for sorted observations." +"effect/SortedMap#reduce": + replacement: "HashMap.reduce" + note: "Reduce the replacement HashMap, but explicitly sort entries first if the old key-order traversal affected the result." diff --git a/migration/annotations/effect__SortedSet.yaml b/migration/annotations/effect__SortedSet.yaml new file mode 100644 index 000000000..7e95f864c --- /dev/null +++ b/migration/annotations/effect__SortedSet.yaml @@ -0,0 +1,63 @@ +"effect/SortedSet#add": + replacement: "HashSet.add" + note: "Direct persistent add on the replacement set; sort only when traversing." +"effect/SortedSet#difference": + replacement: "HashSet.difference + HashSet.fromIterable" + note: "Convert the old general iterable argument to HashSet before taking the difference." +"effect/SortedSet#empty": + replacement: "HashSet.empty" + note: "SortedSet was removed; use an immutable HashSet and retain the element Order separately." +"effect/SortedSet#filter": + replacement: "HashSet.filter" + note: "Direct persistent filtering on the replacement set; traversal is unordered until explicitly sorted." +"effect/SortedSet#fromIterable": + replacement: "HashSet.fromIterable" + note: "Use HashSet.fromIterable and retain the element Order separately." +"effect/SortedSet#getEquivalence": + replacement: "Equal.asEquivalence" + note: "HashSet implements Effect equality by set content; use Equal.asEquivalence>()." +"effect/SortedSet#intersection": + replacement: "HashSet.intersection + HashSet.fromIterable" + note: "Convert the old general iterable argument to HashSet before taking the intersection." +"effect/SortedSet#isSortedSet": + replacement: "HashSet.isHashSet" + note: "Use the replacement model guard; it does not prove that observations were sorted." +"effect/SortedSet#make": + replacement: "HashSet.make" + note: "Remove the outer order-curried constructor and pass values directly to HashSet.make." +"effect/SortedSet#map": + replacement: "HashSet.map" + note: "Remove the output Order argument; retain it externally and sort only when traversing." +"effect/SortedSet#partition": + replacement: "HashSet.filter" + note: "Build [excluded, satisfying] with complementary HashSet.filter calls." +"effect/SortedSet#remove": + replacement: "HashSet.remove" + note: "Direct persistent removal on the replacement set." +"effect/SortedSet#size": + replacement: "HashSet.size" + note: "Direct size query on the replacement immutable set." +"effect/SortedSet#SortedSet": + replacement: "HashSet.HashSet" + note: "Use HashSet as the immutable core model and retain Order externally; ordered iteration requires sorting on observation." +"effect/SortedSet#TypeId": + replacement: "none" + note: "The SortedSet brand was removed and HashSet.TypeId is private; use HashSet.isHashSet when a guard is needed." +"effect/SortedSet#union": + replacement: "HashSet.union + HashSet.fromIterable" + note: "Convert the old general iterable argument to HashSet before taking the union." +"effect/SortedSet#values": + replacement: "Array.sort" + note: "Sort the replacement HashSet with the retained Order and iterate the resulting array." +"effect/SortedSet#every": + replacement: "HashSet.every" + note: "Run the predicate against the replacement HashSet; sort first only if traversal order has observable effects." +"effect/SortedSet#has": + replacement: "HashSet.has" + note: "Use direct membership testing on the replacement HashSet." +"effect/SortedSet#some": + replacement: "HashSet.some" + note: "Run the predicate against the replacement HashSet; sort first only if traversal order has observable effects." +"effect/SortedSet#toggle": + replacement: "HashSet.has + HashSet.add / HashSet.remove" + note: "HashSet has no toggle; branch on membership and add or remove the element." diff --git a/migration/annotations/effect__Stream.yaml b/migration/annotations/effect__Stream.yaml new file mode 100644 index 000000000..c2d861cd5 --- /dev/null +++ b/migration/annotations/effect__Stream.yaml @@ -0,0 +1,450 @@ +effect/Stream#Stream: + replacement: "Stream" + note: "The Stream interface is unchanged in shape and keeps the effect/Stream import path; the type-id key is now the string literal \"~effect/Stream\" instead of a unique symbol." +effect/Stream#Stream.Context: + replacement: "Stream.Services" + note: "Type-level extractor of the R parameter renamed from Stream.Context to Stream.Services; identical conditional-infer semantics." +effect/Stream#Stream.DynamicTuple: + replacement: "Types.TupleOf" + note: "Already deprecated in v3 in favor of Types.TupleOf; removed in v4. Use Types.TupleOf (v4 Stream.broadcastN uses it for its return type)." +effect/Stream#Stream.DynamicTupleOf: + replacement: "Types.TupleOf" + note: "Recursive tuple-builder helper removed with Stream.DynamicTuple; Types.TupleOf is the v4 equivalent." +effect/Stream#StreamTypeId: + replacement: "Stream.TypeId" + note: "Renamed StreamTypeId -> TypeId and changed from a unique symbol to the string literal \"~effect/Stream\" (both the const and the type)." +effect/Stream#StreamUnify: + replacement: "Stream.StreamUnify" + note: "Still exported under the same name in v4 (extends Effect.EffectUnify); no change needed besides any Chunk-related element types." +effect/Stream#StreamUnifyIgnore: + replacement: "Stream.StreamUnifyIgnore" + note: "Still exported under the same name in v4 (extends Effect.EffectUnifyIgnore with Effect ignored); unchanged." +effect/Stream#accumulateChunks: + replacement: "none" + note: "v3 accumulateChunks only rewrote the internal chunk layout (each chunk cumulatively contained all prior elements) without changing the emitted element sequence; v4 has no chunk-layout twin. Stream.accumulate emits the cumulative NonEmptyArray values, and Stream.rechunk controls chunk sizing." +effect/Stream#acquireRelease: + replacement: "Stream.scoped(Stream.fromEffect(Effect.acquireRelease(acquire, release)))" + note: "Dedicated constructor removed; compose Effect.acquireRelease (same (resource, exit) release signature) with Stream.fromEffect, then Stream.scoped to tie the finalizer to the stream's lifetime." +effect/Stream#aggregateWithinEither: + replacement: "Stream.aggregateWithin" + note: "Either-emitting variant removed; v4 aggregateWithin(sink, schedule) emits only the sink outputs B (schedule outputs are no longer surfaced as Either.right)." +effect/Stream#as: + replacement: "Stream.map(() => value)" + note: "Stream.as was removed; replace each element with a constant via Stream.map." +effect/Stream#async: + replacement: "Stream.callback" + note: "Stream.callback((queue) => Effect | void, { bufferSize?, strategy? }) replaces the Emit-based async; push with Queue.offer/offerAll, end with Queue.end, fail with Queue.fail." +effect/Stream#asyncEffect: + replacement: "Stream.callback" + note: "The register function of Stream.callback may return an Effect (run before the stream starts pulling), covering asyncEffect; signal end/failure through the provided Queue." +effect/Stream#asyncPush: + replacement: "Stream.callback" + note: "Stream.callback's register effect can use Scope for acquire/release of the external subscription, replacing asyncPush; the Emit ops helpers become plain Queue operations." +effect/Stream#asyncScoped: + replacement: "Stream.callback" + note: "Stream.callback's register effect may use Scope (Scope is excluded from the resulting R), replacing asyncScoped; the Option end signal becomes Queue.end." +effect/Stream#branchAfter: + replacement: "Stream.peel" + note: "Removed; the closest v4 primitive is Stream.peel(self, Sink.take(n)), a scoped Effect yielding [firstN, restStream] from which you build the continuation stream and re-wrap with Stream.unwrap." + example: "Stream.unwrap(Effect.map(Stream.peel(self, Sink.take(n)), ([head, rest]) => f(head)(rest)))" +effect/Stream#broadcastDynamic: + replacement: "Stream.broadcast" + note: "v4 Stream.broadcast({ capacity, strategy?, replay? }) is the dynamic-subscriber fan-out returning Effect, never, Scope | R> (v3 fixed-arity broadcast(n) became Stream.broadcastN); Stream.share adds refcounted/idleTimeToLive semantics." +effect/Stream#broadcastedQueues: + replacement: "none" + note: "Queue-of-Take fan-out surface removed. Use Stream.broadcastN({ n, capacity }) for a fixed tuple of mirror streams, or Stream.toPubSubTake to obtain a PubSub of Take values and subscribe consumers to it." +effect/Stream#broadcastedQueuesDynamic: + replacement: "none" + note: "Removed with broadcastedQueues. Use Stream.broadcast (dynamic mirror streams) or Stream.toPubSubTake + PubSub subscriptions when raw Take-level consumers are needed." +effect/Stream#bufferChunks: + replacement: "Stream.bufferArray" + note: "Chunk->Array rename; buffers whole arrays (chunks) up to capacity with the same strategy options." +effect/Stream#catchAll: + replacement: "Stream.catch" + note: "Renamed to Stream.catch (exported keyword-style); same (error) => Stream handler for all typed failures." +effect/Stream#catchAllCause: + replacement: "Stream.catchCause" + note: "Renamed; handler receives the full Cause and returns a recovery stream, identical semantics." +effect/Stream#catchSome: + replacement: "Stream.catchFilter" + note: "Option-returning partial handler replaced by the Filter API: Stream.catchFilter(filter, f, orElse?) recovers matched errors, unmatched failures pass through (Stream.catchIf for refinement/predicate matching)." +effect/Stream#catchSomeCause: + replacement: "Stream.catchCauseFilter" + note: "Option-returning cause handler replaced by Stream.catchCauseFilter(filter, f, orElse?) using a Filter on the Cause (Stream.catchCauseIf for refinements)." +effect/Stream#chunksWith: + replacement: "Stream.flattenArray(f(Stream.chunks(self)))" + note: "No dedicated combinator; expose chunk structure with Stream.chunks (Stream>), transform, then re-flatten with Stream.flattenArray." +effect/Stream#combineChunks: + replacement: "Stream.combineArray" + note: "Chunk->Array rename of the pull-level combining primitive; pulls now yield NonEmptyReadonlyArray values and halt via Cause.Done-failing Pull effects instead of Option-typed errors." +effect/Stream#concatAll: + replacement: "Stream.flatten" + note: "Chunk-of-streams constructor removed; sequential concatenation of many streams is Stream.flatten(Stream.fromIterable(streams)) (default concurrency 1 preserves order)." +effect/Stream#context: + replacement: "Stream.fromEffect(Effect.context())" + note: "Dedicated accessor removed; lift Effect.context() into a single-element stream." +effect/Stream#contextWith: + replacement: "Stream.fromEffect(Effect.contextWith(f))" + note: "Dedicated accessor removed; Effect.contextWith still exists in v4, lift it with Stream.fromEffect." +effect/Stream#contextWithEffect: + replacement: "Stream.fromEffect(Effect.flatMap(Effect.context(), f))" + note: "Removed; read the Context with Effect.context, feed it to the effectful function, and lift the result with Stream.fromEffect." +effect/Stream#contextWithStream: + replacement: "Stream.unwrap(Effect.contextWith(f))" + note: "Removed; build the dependent stream inside Effect.contextWith and flatten with Stream.unwrap." +effect/Stream#crossLeft: + replacement: "Stream.crossWith(that, (a, _) => a)" + note: "Removed; cartesian product keeping only left elements is expressed with Stream.crossWith and a left-projecting combiner." +effect/Stream#crossRight: + replacement: "Stream.crossWith(that, (_, b) => b)" + note: "Removed; cartesian product keeping only right elements is Stream.crossWith with a right-projecting combiner (equivalently Stream.flatMap(self, () => that))." +effect/Stream#dieMessage: + replacement: "Stream.die(new Error(message))" + note: "Removed along with RuntimeException; die with an explicit defect value via Stream.die." +effect/Stream#dieSync: + replacement: "Stream.failCauseSync(() => Cause.die(evaluate()))" + note: "Removed; lazily construct the defect cause with Cause.die inside Stream.failCauseSync." +effect/Stream#distributedWith: + replacement: "none" + note: "Predicate-routed fixed fan-out to Take queues removed (no v4 counterpart found among broadcast/broadcastN/share/toPubSub/partition). Closest patterns: Stream.broadcastN + Stream.filter per branch, Stream.partition for two-way splits, or manual routing by running the stream into per-consumer Queues." +effect/Stream#distributedWithDynamic: + replacement: "none" + note: "Dynamic predicate-routed fan-out removed with distributedWith. Use Stream.broadcast/Stream.share for dynamic mirrors plus per-subscriber Stream.filter, or hand-roll routing into Queues via Stream.runForEach." +effect/Stream#either: + replacement: "Stream.result" + note: "Either is replaced by Result in v4: Stream.result yields Stream, never, R> (element -> Result.succeed, first error -> Result.fail and the stream ends, as before)." +effect/Stream#ensuringWith: + replacement: "Stream.onExit" + note: "Renamed; Stream.onExit runs the finalizer with the Exit of the stream, identical shape." +effect/Stream#execute: + replacement: "Stream.fromEffectDrain" + note: "Renamed; runs the effect for its side effects and emits nothing (Stream)." +effect/Stream#filterMapWhile: + replacement: "Stream.takeWhileFilter" + note: "Option-returning partial function replaced by the Filter API: Stream.takeWhileFilter(filter) maps and emits while the filter passes, ending the stream at the first miss." +effect/Stream#filterMapWhileEffect: + replacement: "none" + note: "No effectful takeWhileFilter variant in v4. Recreate by using Stream.takeWhileFilter with a Filter that selects the Effect to run, followed by Stream.mapEffect((eff) => eff) to execute it." +effect/Stream#finalizer: + replacement: "Stream.ensuring" + note: "One-element finalizer-registering stream removed; attach finalizers directly with Stream.ensuring/Stream.onExit, or register in the stream scope via Stream.scoped(Stream.fromEffect(Effect.addFinalizer(fin))) when the v3 concat-a-finalizer pattern must be preserved." +effect/Stream#find: + replacement: "Stream.take(Stream.filter(self, predicate), 1)" + note: "Removed; first-match semantics are Stream.filter followed by Stream.take(1)." +effect/Stream#findEffect: + replacement: "Stream.take(Stream.filterEffect(self, f), 1)" + note: "Removed; Stream.filterEffect takes an effectful (a, index) => Effect predicate, then Stream.take(1) stops at the first match." +effect/Stream#flattenChunks: + replacement: "Stream.flattenArray" + note: "Chunk->Array rename; flattens a Stream of ReadonlyArray values into their elements." +effect/Stream#flattenExitOption: + replacement: "Stream.flattenTake" + note: "The Exit> end-of-stream encoding is gone; v4 uses Take = NonEmptyReadonlyArray | Exit and Stream.flattenTake unwraps it (emit arrays, end/fail on Exit)." +effect/Stream#flattenIterables: + replacement: "Stream.flattenIterable" + note: "Renamed (singular); flattens a Stream of Iterables into their elements." +effect/Stream#fromChunk: + replacement: "Stream.fromArray" + note: "Chunk->Array rename; takes a ReadonlyArray and emits it as one chunk." +effect/Stream#fromChunkPubSub: + replacement: "Stream.fromPubSub" + note: "Chunked PubSub constructors are gone; v4 Stream.fromPubSub(pubsub) consumes PubSub directly (batched internally). For a PubSub carrying arrays use Stream.flattenArray(Stream.fromPubSub(pubsub)); the scoped/shutdown options were dropped (Stream.fromSubscription consumes an existing subscription)." +effect/Stream#fromChunkQueue: + replacement: "Stream.fromQueue" + note: "Chunked Queue constructor gone; v4 Stream.fromQueue consumes Queue.Dequeue whose done/failure signals end the stream (no shutdown option). For array payloads wrap with Stream.flattenArray." +effect/Stream#fromChunks: + replacement: "Stream.fromArrays" + note: "Chunk->Array rename; variadic arrays, each emitted as one chunk." +effect/Stream#fromEffectOption: + replacement: "none" + note: "The Effect> encoding (fail None = empty stream) is removed; v4 signals early end with Cause.Done in Pull-level code. Rebuild with Stream.unwrap: map the success to Stream.succeed and match the Option error to Stream.empty (None) or Stream.fail (Some)." +effect/Stream#fromReadableStreamByob: + replacement: "Stream.fromReadableStream" + note: "BYOB reader variant removed (no byob support in v4 source); Stream.fromReadableStream({ evaluate, onError, releaseLockOnEnd? }) consumes any ReadableStream with a default reader, without byte-buffer allocation control." +effect/Stream#fromTPubSub: + replacement: "none" + note: "STM TPubSub was replaced by the transactional TxPubSub module and v4 Stream has no Tx* constructors; subscribe and repeatedly TxQueue.take from the subscription (e.g. inside Stream.fromPull/Stream.callback), or bridge through a regular PubSub and Stream.fromPubSub." +effect/Stream#fromTQueue: + replacement: "none" + note: "STM TQueue was replaced by TxQueue and v4 Stream has no Tx* constructors; drain by repeatedly calling TxQueue.take inside a custom loop (Stream.fromPull/Stream.callback), or bridge into a regular Queue and use Stream.fromQueue." +effect/Stream#haltAfter: + replacement: "Stream.haltWhen(Effect.sleep(duration))" + note: "Duration-specialized halt removed; v3 documented it as haltWhen with a sleep — completes the stream after the duration without interrupting an in-flight pull." +effect/Stream#haltWhenDeferred: + replacement: "Stream.haltWhen(Deferred.await(deferred))" + note: "Deferred-specialized variant removed; Deferred.await is an Effect, so plain Stream.haltWhen covers it." +effect/Stream#identity: + replacement: "Channel.identity" + note: "The identity-pipeline Stream is gone; for pipeThrough-style plumbing use Stream.pipeThroughChannel(Channel.identity()), or simply the identity function where a Stream=>Stream transform is expected." +effect/Stream#interruptAfter: + replacement: "Stream.interruptWhen(Effect.sleep(duration))" + note: "Duration-specialized interrupt removed; interruptWhen forks the sleep and also interrupts an in-progress pull, matching v3 semantics." +effect/Stream#interruptWhenDeferred: + replacement: "Stream.interruptWhen(Deferred.await(deferred))" + note: "Deferred-specialized variant removed; pass Deferred.await to Stream.interruptWhen (a Deferred failure surfaces as the stream's failure, as before)." +effect/Stream#mapChunks: + replacement: "Stream.mapArray" + note: "Chunk->Array rename; transforms each emitted chunk as a NonEmptyReadonlyArray." +effect/Stream#mapChunksEffect: + replacement: "Stream.mapArrayEffect" + note: "Chunk->Array rename of the effectful per-chunk transform." +effect/Stream#mapConcat: + replacement: "Stream.flattenIterable(Stream.map(self, f))" + note: "Removed; map each element to an Iterable and flatten with Stream.flattenIterable." +effect/Stream#mapConcatChunk: + replacement: "Stream.flattenArray(Stream.map(self, f))" + note: "Chunk variant removed with Chunk itself; map to a ReadonlyArray and flatten with Stream.flattenArray." +effect/Stream#mapConcatChunkEffect: + replacement: "Stream.flattenArray(Stream.mapEffect(self, f))" + note: "Removed; effectfully map each element to a ReadonlyArray and flatten with Stream.flattenArray." +effect/Stream#mapConcatEffect: + replacement: "Stream.flattenIterable(Stream.mapEffect(self, f))" + note: "Removed; effectfully map each element to an Iterable and flatten with Stream.flattenIterable." +effect/Stream#mapErrorCause: + replacement: "Stream.catchCause((cause) => Stream.failCause(f(cause)))" + note: "Removed; transform the full Cause by catching it and re-failing with the mapped cause." +effect/Stream#mapInputContext: + replacement: "Stream.updateContext" + note: "Renamed; same contravariant (Context) => Context mapping of the required services." +effect/Stream#mergeEither: + replacement: "Stream.mergeResult" + note: "Either replaced by Result: Stream.mergeResult(self, that) yields Result.Result with self -> Result.succeed and that -> Result.fail (v3 put self in Either.left and that in Either.right, so the success/left roles swap sides)." +effect/Stream#mergeWith: + replacement: "Stream.merge(Stream.map(self, onSelf), Stream.map(that, onOther), { haltStrategy })" + note: "Removed; pre-map both streams to the common type and use Stream.merge, whose options accept the same haltStrategy union (\"left\" | \"right\" | \"both\" | \"either\")." +effect/Stream#mergeWithTag: + replacement: "none" + note: "Struct-to-tagged-union merge removed. Recreate with Stream.mergeAll over the entries, tagging each stream first." + example: "Stream.mergeAll(Object.entries(streams).map(([_tag, s]) => Stream.map(s, (value) => ({ _tag, value }))), { concurrency })" +effect/Stream#onDone: + replacement: "Stream.onEnd" + note: "Renamed; v4 onEnd takes an Effect value (not a () => Effect thunk) run when the stream ends successfully, and its error type may add to the stream's." + +effect/Stream#orDieWith: + replacement: "Stream.orDie" + note: "orDieWith removed; transform the error first, then convert failures to defects: `self.pipe(Stream.mapError(f), Stream.orDie)`." +effect/Stream#orElse: + replacement: "Stream.catch" + note: "v3 catchAll was renamed to Stream.catch in v4; orElse ignored the error, so write `Stream.catch(self, () => that())`." +effect/Stream#orElseEither: + replacement: "Stream.catch" + note: "Removed; Either is replaced by Result in v4. Emulate: `Stream.map(self, Result.succeed).pipe(Stream.catch(() => Stream.map(that(), Result.fail)))` (same encoding v4 Stream.mergeResult uses)." +effect/Stream#orElseFail: + replacement: "Stream.mapError" + note: "Removed; it only replaced the failure value: `Stream.mapError(self, () => error())` or `Stream.catch(self, () => Stream.fail(error()))`." +effect/Stream#orElseIfEmptyChunk: + replacement: "Stream.orElseIfEmpty" + note: "Folded into Stream.orElseIfEmpty, which now takes a lazy fallback Stream: `Stream.orElseIfEmpty(self, () => Stream.fromArray(array))`; Chunk is replaced by plain arrays." +effect/Stream#orElseIfEmptyStream: + replacement: "Stream.orElseIfEmpty" + note: "Direct rename: v4 Stream.orElseIfEmpty takes a LazyArg fallback, identical semantics." +effect/Stream#paginateChunk: + replacement: "Stream.paginate" + note: "v4 Stream.paginate is effectful and array-based: `paginate(s, (s) => Effect, Option]>)`; wrap the pure step in Effect.succeed and use an array instead of a Chunk." +effect/Stream#paginateChunkEffect: + replacement: "Stream.paginate" + note: "v4 Stream.paginate has exactly this shape; only Chunk becomes ReadonlyArray." +effect/Stream#paginateEffect: + replacement: "Stream.paginate" + note: "v4 Stream.paginate emits a batch per step; wrap the single value in an array: `(s) => Effect.map(step(s), ([a, next]) => [[a], next])`." +effect/Stream#partitionEither: + replacement: "Stream.partitionEffect" + note: "Either-based split replaced by Filter.FilterEffect: the function now returns Effect> (Result.succeed/Result.fail instead of Either.right/left). Returns Effect<[passes, fails], never, R | Scope> — note the tuple is [passes, fails], v3 was [left, right]; options are { capacity?, concurrency? }." +effect/Stream#provideLayer: + replacement: "Stream.provide" + note: "v4 Stream.provide accepts a Layer or a Context; behavior identical." +effect/Stream#provideServiceStream: + replacement: "none" + note: "Removed; v4 has provideService/provideServiceEffect but no stream-valued variant. Emulate with `Stream.flatMap(services, (s) => Stream.provideService(self, tag, s))` over the service stream, or use Stream.provideServiceEffect for effectful acquisition." +effect/Stream#provideSomeContext: + replacement: "Stream.provideContext" + note: "v4 Stream.provideContext is the single Context provider with `Exclude` semantics — same behavior as v3 provideSomeContext." +effect/Stream#provideSomeLayer: + replacement: "Stream.provide" + note: "v4 Stream.provide accepts a Layer (or Context) and excludes only the provided services from R — same partial-provision semantics." +effect/Stream#refineOrDie: + replacement: "Stream.catch" + note: "Removed; emulate with `Stream.catch(self, (e) => { const r = pf(e); return Option.isSome(r) ? Stream.fail(r.value) : Stream.die(e) })` — refail refined errors, die on the rest." +effect/Stream#refineOrDieWith: + replacement: "Stream.catch" + note: "Removed; same as refineOrDie but die with the mapped defect: `Stream.die(f(e))` for unrefined errors." +effect/Stream#repeatEffect: + replacement: "Stream.fromEffectRepeat" + note: "Renamed; repeats the effect forever emitting each result." +effect/Stream#repeatEffectChunk: + replacement: "Stream.fromIterableEffectRepeat" + note: "Renamed; the effect now produces an Iterable/array instead of a Chunk, repeated forever." +effect/Stream#repeatEffectChunkOption: + replacement: "Stream.fromIterableEffectRepeat" + note: "The Option error encoding is gone: end the stream by failing the effect with `Cause.done()` (a Cause.Done failure); Done is excluded from the resulting stream's error type (Pull.ExcludeDone)." +effect/Stream#repeatEffectOption: + replacement: "Stream.fromEffectRepeat" + note: "The Option error encoding is gone: fail the effect with `Cause.done()` instead of Option.none() to end the stream; other failures propagate as stream errors." +effect/Stream#repeatEffectWithSchedule: + replacement: "Stream.fromEffectSchedule" + note: "Renamed; runs the effect once, then repeats it per the schedule, emitting each result." +effect/Stream#repeatEither: + replacement: "none" + note: "Removed; v4 Stream.repeat(schedule) repeats the stream but never emits the schedule outputs, and no Either/unification variant exists. If schedule outputs must be observed, hand-roll with Channel or track them via a schedule that taps into a Ref." +effect/Stream#repeatElementsWith: + replacement: "none" + note: "Removed; v4 Stream.repeatElements(schedule) repeats each element per the schedule but never emits schedule outputs — the onElement/onSchedule unification is gone. Use repeatElements if only element repetition is needed." +effect/Stream#repeatValue: + replacement: "Stream.fromEffectRepeat" + note: "Removed; use `Stream.fromEffectRepeat(Effect.succeed(value))` or `Stream.forever(Stream.succeed(value))`." +effect/Stream#repeatWith: + replacement: "none" + note: "Removed; v4 Stream.repeat(schedule) covers the repetition but drops the schedule outputs and the onElement/onSchedule unification. Hand-roll if schedule outputs must appear in the stream." +effect/Stream#runFoldScoped: + replacement: "Stream.runFold" + note: "Scoped run variants are gone; v4 run functions manage the stream's scope internally and the initial value is now a LazyArg: `Stream.runFold(self, () => s, f)`. For enclosing-scope control, pull manually via `Stream.toPull` (Effect)." +effect/Stream#runFoldScopedEffect: + replacement: "Stream.runFoldEffect" + note: "Scoped run variants are gone; use `Stream.runFoldEffect(self, () => s, f)` — scope is managed internally, initial value is a LazyArg. Use Stream.toPull for manual scoped consumption." +effect/Stream#runFoldWhile: + replacement: "none" + note: "v4 runFold has no early-exit predicate; emulate with Stream.runForEachWhile and a mutable accumulator." + example: | + // v3: Stream.runFoldWhile(self, init, cont, f) + Effect.suspend(() => { + let acc = init + return Stream.runForEachWhile(self, (a) => { + acc = f(acc, a) + return Effect.succeed(cont(acc)) + }).pipe(Effect.map(() => acc)) + }) +effect/Stream#runFoldWhileEffect: + replacement: "none" + note: "v4 runFoldEffect has no early-exit predicate; emulate with Stream.runForEachWhile and a mutable accumulator, mapping the effectful step to Effect via cont(acc) (see runFoldWhile example)." +effect/Stream#runFoldWhileScoped: + replacement: "none" + note: "Both the while-predicate and the scoped run variants are gone in v4; emulate the predicate with Stream.runForEachWhile plus a mutable accumulator (see runFoldWhile); scope is managed internally by v4 run functions." +effect/Stream#runFoldWhileScopedEffect: + replacement: "none" + note: "Both the while-predicate and the scoped run variants are gone in v4; emulate with Stream.runForEachWhile plus a mutable accumulator and effectful step; scope is managed internally by v4 run functions." +effect/Stream#runForEachChunk: + replacement: "Stream.runForEachArray" + note: "Renamed; the callback receives a NonEmptyReadonlyArray instead of a Chunk." +effect/Stream#runForEachChunkScoped: + replacement: "Stream.runForEachArray" + note: "Scoped run variants are gone; v4 runForEachArray manages the stream scope internally. Use Stream.toPull for manual scoped consumption." +effect/Stream#runForEachScoped: + replacement: "Stream.runForEach" + note: "Scoped run variants are gone; v4 runForEach manages the stream scope internally. Use Stream.toPull for manual scoped consumption." +effect/Stream#runForEachWhileScoped: + replacement: "Stream.runForEachWhile" + note: "Scoped run variants are gone; v4 runForEachWhile (callback returns Effect) manages the stream scope internally." +effect/Stream#runIntoPubSubScoped: + replacement: "Stream.runIntoPubSub" + note: "Scoped variant removed; v4 runIntoPubSub(pubsub, { shutdownOnEnd? }) publishes plain values (the Take wrapper is gone) and does not require Scope — fork the returned effect (Effect.forkIn/Effect.forkScoped) to reproduce the background scoped behavior." +effect/Stream#runIntoQueueElementsScoped: + replacement: "Stream.runIntoQueue" + note: "The per-element Exit> encoding is gone; v4 runIntoQueue targets a Queue — elements are offered plainly and failure/end are signalled through the queue's error/done channel. Fork with Effect.forkIn for scoped background running." +effect/Stream#runIntoQueueScoped: + replacement: "Stream.runIntoQueue" + note: "Scoped variant removed; v4 runIntoQueue offers plain values to a Queue (Take wrapper gone) and requires no Scope — fork the returned effect into a scope (Effect.forkIn) if needed." +effect/Stream#runScoped: + replacement: "Stream.run" + note: "Scoped variant removed; v4 Stream.run(sink) manages the stream's scope internally. For consumption tied to an enclosing Scope, use Stream.toPull and drive the Pull manually." +effect/Stream#scanReduce: + replacement: "Stream.mapAccum" + note: "Removed; emulate first-element-as-seed with `Stream.mapAccum(self, () => undefined as A | undefined, (acc, a) => { const next = acc === undefined ? a : f(acc, a); return [next, [next]] })`." +effect/Stream#scanReduceEffect: + replacement: "Stream.mapAccumEffect" + note: "Removed; same first-element-as-seed emulation as scanReduce but with Stream.mapAccumEffect and an effectful step." +effect/Stream#scheduleWith: + replacement: "none" + note: "Removed; v4 Stream.schedule(schedule) only paces elements and never emits schedule outputs — the onElement/onSchedule unification is gone. Use Stream.schedule if only pacing is needed." +effect/Stream#scopedWith: + replacement: "Stream.scoped" + note: "Removed; v4 Stream.scoped scopes a Stream (provides a Scope kept open for the stream's lifetime). Emulate: `Stream.scoped(Stream.fromEffect(Effect.flatMap(Effect.scope, f)))` — Effect.scope accesses the ambient Scope." +effect/Stream#some: + replacement: "none" + note: "Removed along with Option error encodings. To drop None values use `Stream.filterMap(self, Filter.fromPredicateOption((o) => o))`; to fail on None use Stream.mapEffect with Option.match into Effect.fail/Effect.succeed." +effect/Stream#someOrElse: + replacement: "Stream.map" + note: "Removed; use `Stream.map(self, Option.getOrElse(() => fallback()))`." +effect/Stream#someOrFail: + replacement: "Stream.mapEffect" + note: "Removed; use `Stream.mapEffect(self, Option.match({ onNone: () => Effect.fail(error()), onSome: Effect.succeed }))`." +effect/Stream#splitOnChunk: + replacement: "none" + note: "Delimiter-subsequence splitting was removed; v4 keeps only Stream.split (predicate/refinement, emitting NonEmptyReadonlyArray segments) and Stream.splitLines. Hand-roll multi-element delimiter splitting with Stream.mapAccumArray." +effect/Stream#tapErrorCause: + replacement: "Stream.tapCause" + note: "Renamed; taps the full Cause on failure." +effect/Stream#timeoutFail: + replacement: "Stream.timeoutOrElse" + note: "Use `Stream.timeoutOrElse(self, { duration, orElse: () => Stream.fail(error()) })`; the timeout resets on every emitted value as before." +effect/Stream#timeoutFailCause: + replacement: "Stream.timeoutOrElse" + note: "Use `Stream.timeoutOrElse(self, { duration, orElse: () => Stream.failCause(cause()) })`." +effect/Stream#timeoutTo: + replacement: "Stream.timeoutOrElse" + note: "Renamed into an options form: `Stream.timeoutOrElse(self, { duration, orElse: () => that })` — the fallback stream is now lazy." +effect/Stream#toAsyncIterableRuntime: + replacement: "Stream.toAsyncIterableWith" + note: "Renamed; takes a `Context.Context` instead of a Runtime (v4 removed Runtime — a services Context is the execution environment). toAsyncIterable/toAsyncIterableEffect also still exist." +effect/Stream#toQueueOfElements: + replacement: "Stream.toQueue" + note: "The Exit>-per-element queue is gone; v4 toQueue(options: { capacity, strategy? }) returns Effect, never, R | Scope> — elements are plain values and failure/end arrive through the queue's error/done channel." +effect/Stream#toReadableStreamRuntime: + replacement: "Stream.toReadableStreamWith" + note: "Renamed; takes a `Context.Context` instead of a Runtime (v4 removed Runtime); options `{ strategy?: QueuingStrategy }` unchanged." +effect/Stream#transduce: + replacement: "Stream.transduce" + note: "Unchanged name and Sink-based shape; chunks are plain arrays in v4." +effect/Stream#unfoldChunk: + replacement: "Stream.paginate" + note: "Removed; v4 Stream.paginate(s, (s) => Effect<[ReadonlyArray, Option]>) is the array-emitting unfold — wrap the pure step in Effect.succeed; to end without emitting return `[[], Option.none()]`." +effect/Stream#unfoldChunkEffect: + replacement: "Stream.paginate" + note: "Removed; v4 Stream.paginate has the effectful array-step shape — map v3's Option<[Chunk, S]> result to `[array, Option]`, returning `[[], Option.none()]` to end without emitting." +effect/Stream#unfoldEffect: + replacement: "Stream.unfold" + note: "v4 Stream.unfold is effectful: `unfold(s, (s) => Effect)` — return the pair or `undefined` to end instead of Option." +effect/Stream#unwrapScoped: + replacement: "Stream.unwrap" + note: "v4 Stream.unwrap accepts scoped effects (`Exclude` built in); the scope stays open for the stream's lifetime — it replaces both unwrap and unwrapScoped." +effect/Stream#unwrapScopedWith: + replacement: "Stream.unwrap" + note: "Removed; access the ambient Scope explicitly: `Stream.unwrap(Effect.flatMap(Effect.scope, f))` — v4 unwrap keeps the scope open for the stream's lifetime." +effect/Stream#void: + replacement: "Stream.succeed(void 0)" + note: "The `Stream.void` constant (single void element) was removed; use `Stream.succeed(void 0)` or `Stream.make(void 0)`." +effect/Stream#whenCase: + replacement: "none" + note: "Removed; emulate with `Stream.suspend(() => Option.match(pf(evaluate()), { onNone: () => Stream.empty, onSome: (s) => s }))`." +effect/Stream#whenCaseEffect: + replacement: "none" + note: "Removed; emulate with `Stream.unwrap(Effect.map(self, (a) => Option.match(pf(a), { onNone: () => Stream.empty, onSome: (s) => s })))`." +effect/Stream#whenEffect: + replacement: "Stream.when" + note: "Folded into Stream.when, which now takes an `Effect` test directly (wrap a pure condition with Effect.sync)." +effect/Stream#zipAll: + replacement: "none" + note: "The entire zipAll family was removed in v4 (only zip/zipLatest/zipLatestAll exist; zip ends at the shorter side, zipLatest* combine latest values — different semantics). Pad-with-default zipping must be hand-rolled, e.g. with Stream.combineArray pulling both sides." +effect/Stream#zipAllLeft: + replacement: "none" + note: "Removed with the zipAll family; no default-padding zip exists in v4. Hand-roll with Stream.combineArray (or concat the remainder after a plain Stream.zipLeft) if needed." +effect/Stream#zipAllRight: + replacement: "none" + note: "Removed with the zipAll family; no default-padding zip exists in v4. Hand-roll with Stream.combineArray if needed." +effect/Stream#zipAllSortedByKey: + replacement: "none" + note: "Removed; the sorted-by-key merge-join family has no v4 equivalent (checked v4 Stream exports — only zip/zipLatest/zipLatestAll/zipWithArray). Hand-roll a keyed merge with Stream.combineArray." +effect/Stream#zipAllSortedByKeyLeft: + replacement: "none" + note: "Removed; see zipAllSortedByKey — no keyed merge-join in v4, hand-roll with Stream.combineArray." +effect/Stream#zipAllSortedByKeyRight: + replacement: "none" + note: "Removed; see zipAllSortedByKey — no keyed merge-join in v4, hand-roll with Stream.combineArray." +effect/Stream#zipAllSortedByKeyWith: + replacement: "none" + note: "Removed; see zipAllSortedByKey — no keyed merge-join in v4, hand-roll with Stream.combineArray." +effect/Stream#zipAllWith: + replacement: "none" + note: "Removed with the zipAll family; v4 has no zip that pads the shorter side with defaults. Hand-roll with Stream.combineArray." +effect/Stream#zipWithChunks: + replacement: "Stream.zipWithArray" + note: "Renamed; the combiner now receives two NonEmptyReadonlyArrays and returns `[output: NonEmptyReadonlyArray, leftoverLeft: ReadonlyArray, leftoverRight: ReadonlyArray]` — the Either-wrapped leftover (ZipChunksResult) is replaced by the two explicit leftover arrays." diff --git a/migration/annotations/effect__StreamEmit.yaml b/migration/annotations/effect__StreamEmit.yaml new file mode 100644 index 000000000..6d76942ef --- /dev/null +++ b/migration/annotations/effect__StreamEmit.yaml @@ -0,0 +1,16 @@ +effect/StreamEmit#Emit: + replacement: "Queue.Queue" + note: "The StreamEmit module is gone; v4 Stream.callback hands the callback a Queue instead of an Emit function. Emit values with Queue.offer/Queue.offerAll, end with Queue.end, fail with Queue.fail/Queue.failCause." + example: | + // v3: Stream.async((emit) => { emit.single(1); emit.end() }) + Stream.callback((queue) => + Effect.gen(function*() { + yield* Queue.offer(queue, 1) + yield* Queue.end(queue) + })) +effect/StreamEmit#EmitOps: + replacement: "Queue.offer / Queue.offerAll / Queue.end / Queue.fail / Queue.failCause" + note: "Method-by-method mapping onto the Queue passed to Stream.callback: single(a) -> Queue.offer(queue, a); chunk(c) -> Queue.offerAll(queue, c); end() -> Queue.end(queue); fail(e) -> Queue.fail(queue, e); halt(cause) -> Queue.failCause(queue, cause); die(d)/dieMessage(m) -> Queue.failCause(queue, Cause.die(d)); done(exit) -> Queue.offer then Queue.end on success, Queue.failCause on failure; fromEffect(eff) -> run eff and offer its value (Effect.flatMap(eff, (a) => Queue.offer(queue, a)))." +effect/StreamEmit#EmitOpsPush: + replacement: "Queue.offerUnsafe / Queue.offerAllUnsafe / Queue.endUnsafe / Queue.failCauseUnsafe" + note: "The synchronous push interface of v3 Stream.asyncPush maps to the *Unsafe Queue operations on the Queue given to Stream.callback: single/array -> Queue.offerUnsafe/Queue.offerAllUnsafe, end -> Queue.endUnsafe, fail/halt/die -> Queue.failCauseUnsafe (wrap plain errors with Cause.fail, defects with Cause.die)." diff --git a/migration/annotations/effect__StreamHaltStrategy.yaml b/migration/annotations/effect__StreamHaltStrategy.yaml new file mode 100644 index 000000000..598ffa565 --- /dev/null +++ b/migration/annotations/effect__StreamHaltStrategy.yaml @@ -0,0 +1,44 @@ +effect/StreamHaltStrategy#HaltStrategy: + replacement: "Stream.HaltStrategy" + note: "The StreamHaltStrategy module is gone; v4 HaltStrategy is the string-literal union \"left\" | \"right\" | \"both\" | \"either\" (defined in Channel, re-exported as Stream.HaltStrategy) instead of tagged objects." +effect/StreamHaltStrategy#HaltStrategyInput: + replacement: "Stream.HaltStrategy" + note: "The Input widening (tagged object OR string) is obsolete; v4 only ever uses the string literals, so haltStrategy options take Stream.HaltStrategy directly." +effect/StreamHaltStrategy#Left: + replacement: "\"left\"" + note: "The tagged constructor is replaced by the plain string literal \"left\" passed directly to haltStrategy options." +effect/StreamHaltStrategy#Right: + replacement: "\"right\"" + note: "The tagged constructor is replaced by the plain string literal \"right\" passed directly to haltStrategy options." +effect/StreamHaltStrategy#Both: + replacement: "\"both\"" + note: "The tagged constructor is replaced by the plain string literal \"both\" passed directly to haltStrategy options." +effect/StreamHaltStrategy#Either: + replacement: "\"either\"" + note: "The tagged constructor is replaced by the plain string literal \"either\" passed directly to haltStrategy options." +effect/StreamHaltStrategy#fromInput: + replacement: "none" + note: "Remove the call; there is no conversion step in v4 because strategies already are the string literals, so pass the value through unchanged." +effect/StreamHaltStrategy#isLeft: + replacement: "strategy === \"left\"" + note: "Refinements on the tagged union become plain string comparison against the literal." +effect/StreamHaltStrategy#isRight: + replacement: "strategy === \"right\"" + note: "Refinements on the tagged union become plain string comparison against the literal." +effect/StreamHaltStrategy#isBoth: + replacement: "strategy === \"both\"" + note: "Refinements on the tagged union become plain string comparison against the literal." +effect/StreamHaltStrategy#isEither: + replacement: "strategy === \"either\"" + note: "Refinements on the tagged union become plain string comparison against the literal." +effect/StreamHaltStrategy#match: + replacement: "switch (strategy)" + note: "Fold over the strategy with an ordinary switch (or ternary chain) on the string literal; TypeScript exhaustiveness-checks the four cases." + example: | + // v3: HaltStrategy.match(s, { onLeft, onRight, onBoth, onEither }) + switch (strategy) { + case "left": return onLeft() + case "right": return onRight() + case "both": return onBoth() + case "either": return onEither() + } diff --git a/migration/annotations/effect__Streamable.yaml b/migration/annotations/effect__Streamable.yaml new file mode 100644 index 000000000..03e95d4b5 --- /dev/null +++ b/migration/annotations/effect__Streamable.yaml @@ -0,0 +1,3 @@ +"effect/Streamable": + replacement: "none" + note: "Removed in v4 with no direct replacement. Instead of extending Streamable.Class, expose the underlying stream as a value (e.g. a property or method built with Stream.suspend)." diff --git a/migration/annotations/effect__Struct.yaml b/migration/annotations/effect__Struct.yaml new file mode 100644 index 000000000..acdcf2042 --- /dev/null +++ b/migration/annotations/effect__Struct.yaml @@ -0,0 +1,9 @@ +"effect/Struct#entries": + replacement: "Object.entries" + note: "Use the native helper, adding a cast when the old precise key and value type is required." +"effect/Struct#getEquivalence": + replacement: "Struct.makeEquivalence" + note: "Direct rename; the fields object call shape is unchanged." +"effect/Struct#getOrder": + replacement: "Struct.makeOrder" + note: "Direct rename; the fields object call shape is unchanged." diff --git a/migration/annotations/effect__Subscribable.yaml b/migration/annotations/effect__Subscribable.yaml new file mode 100644 index 000000000..94bb39b4a --- /dev/null +++ b/migration/annotations/effect__Subscribable.yaml @@ -0,0 +1,21 @@ +"effect/Subscribable#isSubscribable": + replacement: "none" + note: "The common brand was removed; use a concrete guard such as SubscriptionRef.isSubscriptionRef or an application structural guard." +"effect/Subscribable#make": + replacement: "object literal { get, changes }" + note: "No generic constructor remains; retain a local structural pair only when both the current read and change stream are needed." +"effect/Subscribable#map": + replacement: "Effect.map + Stream.map" + note: "For a retained get and changes pair, map the Effect and Stream separately." +"effect/Subscribable#mapEffect": + replacement: "Effect.flatMap + Stream.mapEffect" + note: "For a retained get and changes pair, flatMap the Effect and mapEffect the Stream separately." +"effect/Subscribable#Subscribable": + replacement: "custom { readonly get: Effect.Effect; readonly changes: Stream.Stream }" + note: "No renamed generic model exists; prefer concrete SubscriptionRef APIs or own this unbranded structural type locally." +"effect/Subscribable#TypeId": + replacement: "none" + note: "The Subscribable brand has no public replacement; use a concrete model guard or an application structural guard." +"effect/Subscribable#unwrap": + replacement: "Effect.flatMap + Stream.unwrap" + note: "Build get with Effect.flatMap and changes with Stream.unwrap; no single v4 helper remains." diff --git a/migration/annotations/effect__SubscriptionRef.yaml b/migration/annotations/effect__SubscriptionRef.yaml new file mode 100644 index 000000000..dbaf54439 --- /dev/null +++ b/migration/annotations/effect__SubscriptionRef.yaml @@ -0,0 +1,15 @@ +effect/SubscriptionRef#SubscriptionRef: + replacement: "SubscriptionRef.SubscriptionRef" + note: "The model remains but no longer extends SynchronizedRef or Subscribable; use SubscriptionRef.get and SubscriptionRef.changes explicitly." +effect/SubscriptionRef#SubscriptionRef.Variance: + replacement: "SubscriptionRef.SubscriptionRef.Variance" + note: "The marker remains under SubscriptionRef.SubscriptionRef, but its brand uses an internal type id." +effect/SubscriptionRef#SubscriptionRefTypeId: + replacement: "SubscriptionRef.isSubscriptionRef" + note: "The type id is internal in v4; use the public runtime guard instead." +effect/SubscriptionRef#SubscriptionRefUnify: + replacement: "none" + note: "SubscriptionRef is no longer an Effect subtype, so its unification helper was removed; call SubscriptionRef.get explicitly." +effect/SubscriptionRef#SubscriptionRefUnifyIgnore: + replacement: "none" + note: "SubscriptionRef is no longer a SynchronizedRef or Effect subtype, so its unification ignore marker was removed." diff --git a/migration/annotations/effect__Supervisor.yaml b/migration/annotations/effect__Supervisor.yaml new file mode 100644 index 000000000..3399df360 --- /dev/null +++ b/migration/annotations/effect__Supervisor.yaml @@ -0,0 +1,27 @@ +effect/Supervisor#AbstractSupervisor: + replacement: "none" + note: "The ambient Supervisor abstraction and runtime event hooks were removed." +effect/Supervisor#addSupervisor: + replacement: "none" + note: "Layer-installed ambient supervision was removed; use structured concurrency and explicit FiberSet or FiberMap tracking." +effect/Supervisor#fibersIn: + replacement: "FiberSet" + note: "Use a scoped FiberSet and explicitly run or add fibers; it does not ambiently observe every descendant." +effect/Supervisor#fromEffect: + replacement: "none" + note: "The Supervisor abstraction and its effect-valued observation hook were removed." +effect/Supervisor#none: + replacement: "none" + note: "The Supervisor abstraction was removed; normal structured concurrency needs no no-op supervisor." +effect/Supervisor#Supervisor: + replacement: "none" + note: "Ambient fiber supervision was removed; use structured concurrency or explicit FiberSet and FiberMap tracking." +effect/Supervisor#Supervisor.Variance: + replacement: "none" + note: "The Supervisor abstraction and its variance marker were removed." +effect/Supervisor#SupervisorTypeId: + replacement: "none" + note: "The Supervisor abstraction and its type identifier were removed." +effect/Supervisor#unsafeTrack: + replacement: "FiberSet" + note: "Use scoped FiberSet.make and explicitly run or add fibers; there is no unsafe unscoped ambient tracker." diff --git a/migration/annotations/effect__Symbol.yaml b/migration/annotations/effect__Symbol.yaml new file mode 100644 index 000000000..1dbd674fe --- /dev/null +++ b/migration/annotations/effect__Symbol.yaml @@ -0,0 +1,3 @@ +"effect/Symbol#Equivalence": + replacement: "Equivalence.strictEqual()" + note: "The dedicated symbol instance was removed; it used strict equality." diff --git a/migration/annotations/effect__SynchronizedRef.yaml b/migration/annotations/effect__SynchronizedRef.yaml new file mode 100644 index 000000000..37db3e7ba --- /dev/null +++ b/migration/annotations/effect__SynchronizedRef.yaml @@ -0,0 +1,18 @@ +effect/SynchronizedRef#SynchronizedRef: + replacement: "SynchronizedRef.SynchronizedRef" + note: "The model remains but no longer extends Ref; read and update it through explicit SynchronizedRef operations. The curried v4 modifySomeEffect takes only the callback, which returns an Effect of [result, Option]; remove the v3 fallback and outer Option." +effect/SynchronizedRef#SynchronizedRef.Variance: + replacement: "none" + note: "The public nested variance marker was removed. SynchronizedRef uses an internal brand in v4, so do not refer to a variance interface directly." +effect/SynchronizedRef#SynchronizedRefTypeId: + replacement: "none" + note: "The SynchronizedRef type id is internal in v4; do not inspect or construct the brand directly." +effect/SynchronizedRef#SynchronizedRefUnify: + replacement: "none" + note: "SynchronizedRef is no longer an Effect subtype, so its Effect unification helper was removed; call SynchronizedRef.get explicitly." +effect/SynchronizedRef#SynchronizedRefUnifyIgnore: + replacement: "none" + note: "SynchronizedRef is no longer an Effect subtype, so its Effect unification ignore marker was removed." +effect/SynchronizedRef#unsafeMake: + replacement: "SynchronizedRef.makeUnsafe" + note: "The unsafe suffix moved to the end." diff --git a/migration/annotations/effect__TArray.yaml b/migration/annotations/effect__TArray.yaml new file mode 100644 index 000000000..dd77149a5 --- /dev/null +++ b/migration/annotations/effect__TArray.yaml @@ -0,0 +1,123 @@ +effect/TArray#TArray: + replacement: "TxChunk.TxChunk" + note: "TArray has no direct v4 counterpart; TxChunk is the closest rewrite target but uses whole-Chunk operations." +effect/TArray#TArray.Variance: + replacement: "none" + note: "TArray was removed and TxChunk exposes no public variance marker." +effect/TArray#TArrayTypeId: + replacement: "TxChunk.isTxChunk" + note: "TArray and its public type id were removed; use the TxChunk runtime guard after rewriting the data structure." +effect/TArray#collectFirst: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#collectFirstSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#contains: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#count: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#countSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#empty: + replacement: "TxChunk.empty" + note: "TArray was removed; TxChunk is the closest v4 transactional indexed collection." +effect/TArray#everySTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#findFirstIndex: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findFirstIndexFrom: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findFirstIndexWhere: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findFirstIndexWhereFrom: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findFirstIndexWhereFromSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#findFirstIndexWhereSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#findFirstSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#findLast: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findLastIndex: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findLastIndexFrom: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#findLastSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#fromIterable: + replacement: "TxChunk.fromIterable" + note: "TArray was removed; construct the v4 TxChunk rewrite target from the iterable." +effect/TArray#get: + replacement: "Effect.map(TxChunk.get(self), Chunk.get(index))" + note: "TxChunk.get returns the whole Chunk, so apply Chunk.get to preserve indexed optional lookup." +effect/TArray#headOption: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#lastOption: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#make: + replacement: "TxChunk.fromIterable(elements)" + note: "TxChunk.make takes one Chunk rather than variadic elements; TxChunk.fromIterable preserves the old call shape after collecting arguments." +effect/TArray#maxOption: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#minOption: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#reduce: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#reduceOption: + replacement: "TxChunk.get + Chunk/Array operation" + note: "TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction." +effect/TArray#reduceOptionSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#reduceSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#size: + replacement: "TxChunk.size" + note: "TxChunk is the closest v4 rewrite target. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TArray#someSTM: + replacement: "Effect.tx + TxChunk.get + Effect traversal" + note: "TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction." +effect/TArray#toArray: + replacement: "Effect.map(TxChunk.get(self), Chunk.toArray)" + note: "TxChunk.get returns a Chunk; convert that snapshot to an Array explicitly." +effect/TArray#transform: + replacement: "TxChunk.update(self, Chunk.map(f))" + note: "TArray was removed; transform the whole TxChunk snapshot with a Chunk mapping function." +effect/TArray#transformSTM: + replacement: "Effect.tx + TxChunk.get/TxChunk.set" + note: "Read the snapshot, traverse it effectfully, and write the rebuilt Chunk within one Effect.tx transaction." +effect/TArray#update: + replacement: "TxChunk.modify" + note: "TxChunk updates the whole Chunk; use modify to update the indexed element and preserve the old optional-index behavior." +effect/TArray#updateSTM: + replacement: "Effect.tx + TxChunk.get/TxChunk.set" + note: "Read, effectfully update the indexed element, and write the rebuilt Chunk within one Effect.tx transaction." +effect/TArray#every: + replacement: "Effect.map(TxChunk.get(self), Chunk.every(predicate))" + note: "TArray was removed; read the TxChunk snapshot and test every element inside the surrounding Effect.tx transaction." +effect/TArray#some: + replacement: "Effect.map(TxChunk.get(self), Chunk.some(predicate))" + note: "TArray was removed; read the TxChunk snapshot and test for a matching element inside the surrounding Effect.tx transaction." diff --git a/migration/annotations/effect__TDeferred.yaml b/migration/annotations/effect__TDeferred.yaml new file mode 100644 index 000000000..8641fa49a --- /dev/null +++ b/migration/annotations/effect__TDeferred.yaml @@ -0,0 +1,15 @@ +effect/TDeferred#TDeferred: + replacement: "TxDeferred.TxDeferred" + note: "Rename the type and import from \"effect/TxDeferred\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TDeferred#TDeferred.Variance: + replacement: "none" + note: "TxDeferred exposes no public variance marker." +effect/TDeferred#TDeferredTypeId: + replacement: "TxDeferred.isTxDeferred" + note: "The type id is internal in v4; use the public runtime guard." +effect/TDeferred#await: + replacement: "TxDeferred.await" + note: "Import TxDeferred from \"effect/TxDeferred\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TDeferred#make: + replacement: "TxDeferred.make" + note: "Import TxDeferred from \"effect/TxDeferred\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." diff --git a/migration/annotations/effect__TMap.yaml b/migration/annotations/effect__TMap.yaml new file mode 100644 index 000000000..1bb30050d --- /dev/null +++ b/migration/annotations/effect__TMap.yaml @@ -0,0 +1,99 @@ +effect/TMap#TMap: + replacement: "TxHashMap.TxHashMap" + note: "Rename the type and import from \"effect/TxHashMap\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#TMap.Variance: + replacement: "none" + note: "TxHashMap exposes no public variance marker." +effect/TMap#TMapTypeId: + replacement: "TxHashMap.isTxHashMap" + note: "The type id is internal in v4; use the public runtime guard." +effect/TMap#empty: + replacement: "TxHashMap.empty" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#findAllSTM: + replacement: "TxHashMap.entries + Effect traversal" + note: "No effectful mapped-find helper remains; traverse the entry snapshot explicitly inside Effect.tx." +effect/TMap#findSTM: + replacement: "TxHashMap.entries + Effect.findFirst" + note: "No effectful mapped-find helper remains; traverse entries explicitly inside Effect.tx." +effect/TMap#fromIterable: + replacement: "TxHashMap.fromIterable" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#get: + replacement: "TxHashMap.get" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#getOrElse: + replacement: "Effect.map(TxHashMap.get(self, key), Option.getOrElse(fallback))" + note: "Compose the retained optional get operation with Option.getOrElse." +effect/TMap#has: + replacement: "TxHashMap.has" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#isEmpty: + replacement: "TxHashMap.isEmpty" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#keys: + replacement: "TxHashMap.keys" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#make: + replacement: "TxHashMap.make" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#reduce: + replacement: "TxHashMap.reduce" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#reduceSTM: + replacement: "TxHashMap.entries + Effect.reduce" + note: "Snapshot entries and reduce them effectfully inside the surrounding Effect.tx transaction." +effect/TMap#remove: + replacement: "TxHashMap.remove" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#removeAll: + replacement: "TxHashMap.removeMany" + note: "The bulk removal operation was renamed." +effect/TMap#setIfAbsent: + replacement: "Effect.tx + TxHashMap.get/TxHashMap.set" + note: "No direct helper remains; check and conditionally set under one outer transaction." +effect/TMap#size: + replacement: "TxHashMap.size" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TMap#takeFirst: + replacement: "none" + note: "No atomic take-and-match helper exists in TxHashMap; implement explicit selection and removal inside Effect.tx." +effect/TMap#takeFirstSTM: + replacement: "none" + note: "No effectful atomic take-and-match helper exists; implement explicit traversal and removal inside Effect.tx." +effect/TMap#takeSome: + replacement: "none" + note: "No atomic multi-take helper exists in TxHashMap; implement explicit selection and removals inside Effect.tx." +effect/TMap#takeSomeSTM: + replacement: "none" + note: "No effectful atomic multi-take helper exists; implement explicit traversal and removals inside Effect.tx." +effect/TMap#toArray: + replacement: "TxHashMap.entries" + note: "Use the entry snapshot; it replaces the old array conversion." +effect/TMap#toChunk: + replacement: "Effect.map(TxHashMap.entries(self), Chunk.fromIterable)" + note: "Convert the entry snapshot to Chunk explicitly." +effect/TMap#toHashMap: + replacement: "TxHashMap.snapshot" + note: "The immutable HashMap snapshot operation was renamed." +effect/TMap#toMap: + replacement: "Effect.map(TxHashMap.entries(self), (entries) => new Map(entries))" + note: "Build a JavaScript Map from the entry snapshot." +effect/TMap#transform: + replacement: "TxHashMap.map" + note: "V4 map returns a new map rather than mutating self; key-changing transforms require snapshot and rebuild logic." +effect/TMap#transformSTM: + replacement: "TxHashMap.entries + Effect traversal + TxHashMap.fromIterable" + note: "No in-place effectful transform remains; traverse a snapshot and rebuild inside Effect.tx." +effect/TMap#transformValues: + replacement: "TxHashMap.map" + note: "V4 map transforms values but returns a new map rather than mutating self." +effect/TMap#transformValuesSTM: + replacement: "TxHashMap.entries + Effect traversal + TxHashMap.fromIterable" + note: "No effectful map remains; traverse a snapshot and rebuild inside Effect.tx." +effect/TMap#updateWith: + replacement: "TxHashMap.modifyAt" + note: "modifyAt is the closest atomic keyed update, but returns void; preserve any old return value explicitly if needed." +effect/TMap#values: + replacement: "TxHashMap.values" + note: "Import TxHashMap from \"effect/TxHashMap\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." diff --git a/migration/annotations/effect__TPriorityQueue.yaml b/migration/annotations/effect__TPriorityQueue.yaml new file mode 100644 index 000000000..cf9205645 --- /dev/null +++ b/migration/annotations/effect__TPriorityQueue.yaml @@ -0,0 +1,42 @@ +effect/TPriorityQueue#TPriorityQueue: + replacement: "TxPriorityQueue.TxPriorityQueue" + note: "Rename the type and import from \"effect/TxPriorityQueue\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#TPriorityQueue.Variance: + replacement: "none" + note: "TxPriorityQueue exposes no public variance marker." +effect/TPriorityQueue#TPriorityQueueTypeId: + replacement: "TxPriorityQueue.isTxPriorityQueue" + note: "The type id is internal in v4; use the public runtime guard." +effect/TPriorityQueue#empty: + replacement: "TxPriorityQueue.empty" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#fromIterable: + replacement: "TxPriorityQueue.fromIterable" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#isEmpty: + replacement: "TxPriorityQueue.isEmpty" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#isNonEmpty: + replacement: "TxPriorityQueue.isNonEmpty" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#make: + replacement: "TxPriorityQueue.make" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#peek: + replacement: "TxPriorityQueue.peek" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#size: + replacement: "TxPriorityQueue.size" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#toArray: + replacement: "TxPriorityQueue.toArray" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#toChunk: + replacement: "Effect.map(TxPriorityQueue.toArray(self), Chunk.fromIterable)" + note: "The direct Chunk conversion was removed; convert the retained Array snapshot explicitly." +effect/TPriorityQueue#take: + replacement: "TxPriorityQueue.take" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; the operation now returns an ordinary Effect, so compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPriorityQueue#takeAll: + replacement: "TxPriorityQueue.takeAll" + note: "Import TxPriorityQueue from \"effect/TxPriorityQueue\"; it returns an ordinary Effect containing the priority-ordered Array." diff --git a/migration/annotations/effect__TPubSub.yaml b/migration/annotations/effect__TPubSub.yaml new file mode 100644 index 000000000..487f84c0b --- /dev/null +++ b/migration/annotations/effect__TPubSub.yaml @@ -0,0 +1,39 @@ +effect/TPubSub#TPubSub: + replacement: "TxPubSub.TxPubSub" + note: "Rename the type and import from \"effect/TxPubSub\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#TPubSubTypeId: + replacement: "TxPubSub.isTxPubSub" + note: "The type id is internal in v4; use the public runtime guard." +effect/TPubSub#bounded: + replacement: "TxPubSub.bounded" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#capacity: + replacement: "TxPubSub.capacity" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#dropping: + replacement: "TxPubSub.dropping" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#isEmpty: + replacement: "TxPubSub.isEmpty" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#isFull: + replacement: "TxPubSub.isFull" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#shutdown: + replacement: "TxPubSub.shutdown" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#size: + replacement: "TxPubSub.size" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#sliding: + replacement: "TxPubSub.sliding" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#subscribeScoped: + replacement: "TxPubSub.subscribe" + note: "The scoped subscription constructor lost its Scoped suffix; it still requires Scope and returns a TxQueue." +effect/TPubSub#unbounded: + replacement: "TxPubSub.unbounded" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TPubSub#isShutdown: + replacement: "TxPubSub.isShutdown" + note: "Import TxPubSub from \"effect/TxPubSub\"; the operation now returns an ordinary Effect." diff --git a/migration/annotations/effect__TQueue.yaml b/migration/annotations/effect__TQueue.yaml new file mode 100644 index 000000000..ef7de6f07 --- /dev/null +++ b/migration/annotations/effect__TQueue.yaml @@ -0,0 +1,87 @@ +effect/TQueue#BaseTQueue: + replacement: "TxQueue.TxQueueState" + note: "The shared queue state model was renamed and now includes the richer open, closing, and done lifecycle." +effect/TQueue#TDequeue: + replacement: "TxQueue.TxDequeue" + note: "Rename the read-side type; it now carries an error channel." +effect/TQueue#TDequeueTypeId: + replacement: "TxQueue.isTxDequeue" + note: "The type id is internal in v4; use the public runtime guard." +effect/TQueue#TEnqueue: + replacement: "TxQueue.TxEnqueue" + note: "Rename the write-side type; it now carries an error channel." +effect/TQueue#TEnqueueTypeId: + replacement: "TxQueue.isTxEnqueue" + note: "The type id is internal in v4; use the public runtime guard." +effect/TQueue#TQueue: + replacement: "TxQueue.TxQueue" + note: "Rename the type; it now carries an error channel and completion lifecycle. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#TQueue.TDequeueVariance: + replacement: "TxQueue.TxDequeue.Variance" + note: "The read-side variance marker moved under TxDequeue and now includes the error type." +effect/TQueue#TQueue.TEnqueueVariance: + replacement: "TxQueue.TxEnqueue.Variance" + note: "The write-side variance marker moved under TxEnqueue and now includes the error type." +effect/TQueue#bounded: + replacement: "TxQueue.bounded" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#capacity: + replacement: "queue.capacity" + note: "Capacity is now a property on TxQueue handles rather than a module function." +effect/TQueue#dropping: + replacement: "TxQueue.dropping" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#isEmpty: + replacement: "TxQueue.isEmpty" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#isFull: + replacement: "TxQueue.isFull" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#isTDequeue: + replacement: "TxQueue.isTxDequeue" + note: "The runtime guard was renamed with the TxDequeue type." +effect/TQueue#isTEnqueue: + replacement: "TxQueue.isTxEnqueue" + note: "The runtime guard was renamed with the TxEnqueue type." +effect/TQueue#isTQueue: + replacement: "TxQueue.isTxQueue" + note: "The runtime guard was renamed with the TxQueue type." +effect/TQueue#offerAll: + replacement: "TxQueue.offerAll" + note: "The operation remains, but now returns rejected elements rather than a boolean." +effect/TQueue#peek: + replacement: "TxQueue.peek" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#poll: + replacement: "TxQueue.poll" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#seek: + replacement: "none" + note: "TxQueue has no seek helper; repeat TxQueue.take under Effect.tx until the predicate matches." +effect/TQueue#shutdown: + replacement: "TxQueue.shutdown" + note: "The operation remains, but now returns whether shutdown changed the queue state." +effect/TQueue#size: + replacement: "TxQueue.size" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#sliding: + replacement: "TxQueue.sliding" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#take: + replacement: "TxQueue.take" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#takeBetween: + replacement: "TxQueue.takeBetween" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#takeN: + replacement: "TxQueue.takeN" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#unbounded: + replacement: "TxQueue.unbounded" + note: "Import TxQueue from \"effect/TxQueue\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TQueue#isShutdown: + replacement: "TxQueue.isShutdown" + note: "Import TxQueue from \"effect/TxQueue\"; it checks the richer done lifecycle and returns an ordinary Effect." +effect/TQueue#takeAll: + replacement: "TxQueue.takeAll" + note: "The operation now blocks until at least one item is available, returns a NonEmptyArray, and propagates the queue error channel through an ordinary Effect." diff --git a/migration/annotations/effect__TRandom.yaml b/migration/annotations/effect__TRandom.yaml new file mode 100644 index 000000000..3bc6b1955 --- /dev/null +++ b/migration/annotations/effect__TRandom.yaml @@ -0,0 +1,27 @@ +effect/TRandom#TRandom: + replacement: "none" + note: "The transactional random service was deliberately removed; use Random outside retried transactions where possible." +effect/TRandom#TRandomTypeId: + replacement: "none" + note: "TRandom and its public type id were removed; v4 has no TxRandom module." +effect/TRandom#Tag: + replacement: "Random.Random" + note: "Use the v4 Random Context.Reference; the transactional random service was removed." +effect/TRandom#next: + replacement: "Random.next" + note: "TxRandom was removed. Random.next is an ordinary Effect and may be re-executed if used inside a retried transaction." +effect/TRandom#nextBoolean: + replacement: "Random.nextBoolean" + note: "TxRandom was removed. This ordinary Effect is not rollback-safe under transaction retry." +effect/TRandom#nextInt: + replacement: "Random.nextInt" + note: "TxRandom was removed. This ordinary Effect is not rollback-safe under transaction retry." +effect/TRandom#nextIntBetween: + replacement: "Random.nextIntBetween(low, high, { halfOpen: true })" + note: "TxRandom was removed; request half-open bounds explicitly to preserve the v3 range behavior." +effect/TRandom#nextRange: + replacement: "Random.nextBetween" + note: "The operation was renamed and is no longer backed by rollback-safe transactional random state." +effect/TRandom#shuffle: + replacement: "Random.shuffle" + note: "TxRandom was removed. This ordinary Effect is not rollback-safe under transaction retry." diff --git a/migration/annotations/effect__TReentrantLock.yaml b/migration/annotations/effect__TReentrantLock.yaml new file mode 100644 index 000000000..399bcd9a1 --- /dev/null +++ b/migration/annotations/effect__TReentrantLock.yaml @@ -0,0 +1,21 @@ +effect/TReentrantLock#TReentrantLock: + replacement: "TxReentrantLock.TxReentrantLock" + note: "Rename the type and import from \"effect/TxReentrantLock\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TReentrantLock#TReentrantLock.Proto: + replacement: "none" + note: "The public prototype interface was removed." +effect/TReentrantLock#TReentrantLockTypeId: + replacement: "TxReentrantLock.isTxReentrantLock" + note: "The type id is internal in v4; use the public runtime guard." +effect/TReentrantLock#fiberReadLocks: + replacement: "none" + note: "Per-fiber read-lock counts were removed; TxReentrantLock.readLocks reports only the total count." +effect/TReentrantLock#fiberWriteLocks: + replacement: "none" + note: "Per-fiber write-lock counts were removed; TxReentrantLock.writeLocks reports only the total count." +effect/TReentrantLock#lock: + replacement: "TxReentrantLock.writeLock" + note: "The generic lock helper was renamed to make write-lock acquisition explicit." +effect/TReentrantLock#make: + replacement: "TxReentrantLock.make()" + note: "The constructor keeps its name but is now a function call rather than a constant STM value." diff --git a/migration/annotations/effect__TRef.yaml b/migration/annotations/effect__TRef.yaml new file mode 100644 index 000000000..eed0c896c --- /dev/null +++ b/migration/annotations/effect__TRef.yaml @@ -0,0 +1,30 @@ +effect/TRef#TRef: + replacement: "TxRef.TxRef" + note: "Rename the type and import from \"effect/TxRef\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TRef#TRef.Variance: + replacement: "none" + note: "TxRef exposes no public variance marker." +effect/TRef#TRefTypeId: + replacement: "TxRef.isTxRef" + note: "The type id is internal in v4; use the public runtime guard." +effect/TRef#get: + replacement: "TxRef.get" + note: "Import TxRef from \"effect/TxRef\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TRef#getAndUpdateSome: + replacement: "TxRef.modify" + note: "Use one atomic modify and keep the old value when the partial update returns None." +effect/TRef#make: + replacement: "TxRef.make" + note: "Import TxRef from \"effect/TxRef\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TRef#modifySome: + replacement: "TxRef.modify" + note: "Use one atomic modify and return the fallback result when the partial function returns None." +effect/TRef#setAndGet: + replacement: "TxRef.modify" + note: "Use one atomic modify that returns and stores the new value." +effect/TRef#updateSome: + replacement: "TxRef.modify" + note: "Use one atomic modify and retain the old value when the partial update returns None." +effect/TRef#updateSomeAndGet: + replacement: "TxRef.modify" + note: "Use one atomic modify that returns the resulting value, retaining the old value for None." diff --git a/migration/annotations/effect__TSemaphore.yaml b/migration/annotations/effect__TSemaphore.yaml new file mode 100644 index 000000000..ac4becb2b --- /dev/null +++ b/migration/annotations/effect__TSemaphore.yaml @@ -0,0 +1,30 @@ +effect/TSemaphore#TSemaphore: + replacement: "TxSemaphore.TxSemaphore" + note: "Rename the type and import from \"effect/TxSemaphore\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSemaphore#TSemaphore.Proto: + replacement: "none" + note: "The public prototype interface was removed." +effect/TSemaphore#TSemaphoreTypeId: + replacement: "TxSemaphore.isTxSemaphore" + note: "The type id is internal in v4; use the public runtime guard." +effect/TSemaphore#available: + replacement: "TxSemaphore.available" + note: "Import TxSemaphore from \"effect/TxSemaphore\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSemaphore#make: + replacement: "TxSemaphore.make" + note: "Import TxSemaphore from \"effect/TxSemaphore\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSemaphore#release: + replacement: "TxSemaphore.release" + note: "Import TxSemaphore from \"effect/TxSemaphore\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSemaphore#unsafeMake: + replacement: "none" + note: "The unsafe constructor was removed; use TxSemaphore.make." +effect/TSemaphore#withPermit: + replacement: "TxSemaphore.withPermit" + note: "The helper remains, but data-first calls now pass the semaphore before the Effect." +effect/TSemaphore#withPermits: + replacement: "TxSemaphore.withPermits" + note: "The helper remains, but data-first calls now pass semaphore, permit count, then Effect." +effect/TSemaphore#withPermitsScoped: + replacement: "TxSemaphore.acquireN + Effect.addFinalizer(TxSemaphore.releaseN)" + note: "No scoped multi-permit helper remains; acquire and register release explicitly in a Scope." diff --git a/migration/annotations/effect__TSet.yaml b/migration/annotations/effect__TSet.yaml new file mode 100644 index 000000000..51d649588 --- /dev/null +++ b/migration/annotations/effect__TSet.yaml @@ -0,0 +1,75 @@ +effect/TSet#TSet: + replacement: "TxHashSet.TxHashSet" + note: "Rename the type and import from \"effect/TxHashSet\". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#TSet.Variance: + replacement: "none" + note: "TxHashSet exposes no public variance marker." +effect/TSet#TSetTypeId: + replacement: "TxHashSet.isTxHashSet" + note: "The type id is internal in v4; use the public runtime guard." +effect/TSet#difference: + replacement: "TxHashSet.difference" + note: "The name remains, but v4 returns a new set instead of mutating self." +effect/TSet#empty: + replacement: "TxHashSet.empty" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#fromIterable: + replacement: "TxHashSet.fromIterable" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#has: + replacement: "TxHashSet.has" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#intersection: + replacement: "TxHashSet.intersection" + note: "The name remains, but v4 returns a new set instead of mutating self." +effect/TSet#isEmpty: + replacement: "TxHashSet.isEmpty" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#make: + replacement: "TxHashSet.make" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#reduce: + replacement: "TxHashSet.reduce" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#reduceSTM: + replacement: "TxHashSet.toHashSet + Effect.reduce" + note: "Snapshot the set and reduce effectfully inside the surrounding Effect.tx transaction." +effect/TSet#remove: + replacement: "TxHashSet.remove" + note: "The name remains, but v4 returns whether the value existed." +effect/TSet#removeAll: + replacement: "Effect.forEach(values, (value) => TxHashSet.remove(self, value))" + note: "No bulk removal helper remains; remove each value inside one outer Effect.tx transaction." +effect/TSet#size: + replacement: "TxHashSet.size" + note: "Import TxHashSet from \"effect/TxHashSet\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSet#takeFirst: + replacement: "none" + note: "No atomic take-and-match helper exists in TxHashSet; select and remove explicitly inside Effect.tx." +effect/TSet#takeFirstSTM: + replacement: "none" + note: "No effectful atomic take-and-match helper exists; traverse and remove explicitly inside Effect.tx." +effect/TSet#takeSome: + replacement: "none" + note: "No atomic multi-take helper exists in TxHashSet; select and remove explicitly inside Effect.tx." +effect/TSet#takeSomeSTM: + replacement: "none" + note: "No effectful atomic multi-take helper exists; traverse and remove explicitly inside Effect.tx." +effect/TSet#toArray: + replacement: "Effect.map(TxHashSet.toHashSet(self), Array.from)" + note: "Convert the immutable HashSet snapshot to an Array explicitly." +effect/TSet#toChunk: + replacement: "Effect.map(TxHashSet.toHashSet(self), (set) => Chunk.fromIterable(set))" + note: "Convert the immutable HashSet snapshot to Chunk explicitly." +effect/TSet#toReadonlySet: + replacement: "Effect.map(TxHashSet.toHashSet(self), (set) => new Set(set))" + note: "Convert the immutable HashSet snapshot to a JavaScript ReadonlySet explicitly." +effect/TSet#transform: + replacement: "TxHashSet.map" + note: "The closest helper returns a new set instead of mutating self." +effect/TSet#transformSTM: + replacement: "TxHashSet.toHashSet + Effect traversal + TxHashSet.fromIterable" + note: "No effectful transform remains; traverse a snapshot and rebuild inside Effect.tx." +effect/TSet#union: + replacement: "TxHashSet.union" + note: "The name remains, but v4 returns a new set instead of mutating self." diff --git a/migration/annotations/effect__TSubscriptionRef.yaml b/migration/annotations/effect__TSubscriptionRef.yaml new file mode 100644 index 000000000..ccb654ec9 --- /dev/null +++ b/migration/annotations/effect__TSubscriptionRef.yaml @@ -0,0 +1,36 @@ +effect/TSubscriptionRef#TSubscriptionRef: + replacement: "TxSubscriptionRef.TxSubscriptionRef" + note: "Rename the type; it no longer extends TxRef. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSubscriptionRef#TSubscriptionRef.Variance: + replacement: "none" + note: "TxSubscriptionRef exposes no public variance marker." +effect/TSubscriptionRef#TSubscriptionRefTypeId: + replacement: "TxSubscriptionRef.isTxSubscriptionRef" + note: "The type id is internal in v4; use the public runtime guard." +effect/TSubscriptionRef#changes: + replacement: "none" + note: "The old unscoped transactional subscription was removed; use scoped TxSubscriptionRef.changes." +effect/TSubscriptionRef#changesScoped: + replacement: "TxSubscriptionRef.changes" + note: "The scoped changes operation lost its Scoped suffix and returns a scoped TxQueue." +effect/TSubscriptionRef#get: + replacement: "TxSubscriptionRef.get" + note: "Import TxSubscriptionRef from \"effect/TxSubscriptionRef\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSubscriptionRef#getAndUpdateSome: + replacement: "TxSubscriptionRef.modify" + note: "Use one atomic modify so successful updates are still published; retain the old value for None." +effect/TSubscriptionRef#make: + replacement: "TxSubscriptionRef.make" + note: "Import TxSubscriptionRef from \"effect/TxSubscriptionRef\"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic." +effect/TSubscriptionRef#modifySome: + replacement: "TxSubscriptionRef.modify" + note: "Use one atomic modify so successful updates are still published; use the fallback result for None." +effect/TSubscriptionRef#setAndGet: + replacement: "TxSubscriptionRef.modify" + note: "Use one atomic modify that publishes and returns the newly stored value." +effect/TSubscriptionRef#updateSome: + replacement: "TxSubscriptionRef.modify" + note: "Use one atomic modify so updates are published, retaining the old value for None." +effect/TSubscriptionRef#updateSomeAndGet: + replacement: "TxSubscriptionRef.modify" + note: "Use one atomic modify that publishes and returns the resulting value, retaining the old value for None." diff --git a/migration/annotations/effect__Take.yaml b/migration/annotations/effect__Take.yaml new file mode 100644 index 000000000..0c8cfe1ac --- /dev/null +++ b/migration/annotations/effect__Take.yaml @@ -0,0 +1,67 @@ +effect/Take#Take: + replacement: "Take.Take" + note: "v4 Take is the plain union NonEmptyReadonlyArray | Exit.Exit — no wrapper object or Pipeable: a value batch is a non-empty array, a failure is a failed Exit, and end-of-stream is a successful Exit carrying the Done value (void by default). The module keeps the effect/Take path but exports only the type and toPull." +effect/Take#Take.Variance: + replacement: "none" + note: "Variance plumbing removed; v4 Take is a plain union type with no branded interface, so there is nothing to migrate to." +effect/Take#TakeTypeId: + replacement: "none" + note: "No brand symbol in v4; discriminate the union with Exit.isExit(take) (Exit branch) vs the non-empty array branch (Array.isReadonlyArrayNonEmpty)." +effect/Take#chunk: + replacement: "NonEmptyReadonlyArray" + note: "No constructor needed: a value-batch Take is just the non-empty array of values itself (convert a v3 Chunk with Array.fromIterable); empty batches are not representable and must be skipped." +effect/Take#dieMessage: + replacement: "Exit.die(new Error(message))" + note: "A defect Take is a died Exit; wrap the message in an Error yourself since there is no dedicated dieMessage helper." +effect/Take#done: + replacement: "Take.toPull" + note: "Take.toPull(take) converts a Take into a Pull (Effect succeeding with the batch); end-of-stream surfaces as Cause.Done in the error channel instead of v3's Option.none, and failures keep their cause." +effect/Take#fail: + replacement: "Exit.fail" + note: "A failing Take is simply the failed Exit: Exit.fail(error)." +effect/Take#failCause: + replacement: "Exit.failCause" + note: "A failing Take with a full cause is simply Exit.failCause(cause)." +effect/Take#fromEffect: + replacement: "Effect.exit + Exit.isSuccess" + note: "Run the effect with Effect.exit and convert the result: a successful exit value a becomes the single-element batch [a], a failed exit is used directly as the Take." + example: "Effect.map(Effect.exit(effect), (exit) => Exit.isSuccess(exit) ? [exit.value] as const : exit)" +effect/Take#fromExit: + replacement: "Exit.isSuccess(exit) ? [exit.value] : exit" + note: "A success exit becomes the single-element batch [a]; a failure exit is already a valid v4 Take and is used as-is." +effect/Take#fromPull: + replacement: "Effect.matchCause + Pull.doneExitFromCause" + note: "Convert one v4 Pull step into a Take: the success batch is the Take itself, and Pull.doneExitFromCause turns the failure cause into the Exit branch (Cause.Done becomes a successful end Exit, real failures become a failed Exit)." + example: "Effect.matchCause(pull, { onSuccess: (arr) => arr, onFailure: Pull.doneExitFromCause })" +effect/Take#isDone: + replacement: "Exit.isExit(take) && Exit.isSuccess(take)" + note: "End-of-stream is the successful-Exit branch of the union." +effect/Take#isFailure: + replacement: "Exit.isExit(take) && Exit.isFailure(take)" + note: "A failure Take is the failed-Exit branch of the union." +effect/Take#isSuccess: + replacement: "!Exit.isExit(take)" + note: "A value batch is the non-Exit branch; use Array.isReadonlyArrayNonEmpty(take) when a positive refinement to NonEmptyReadonlyArray is needed." +effect/Take#make: + replacement: "none" + note: "No wrapper constructor: build the union value directly — a non-empty array for values, Exit.fail/Exit.failCause for errors, Exit.succeed(done) (or Exit.void) for end-of-stream; the v3 Exit, Option> encoding is gone." +effect/Take#map: + replacement: "Exit.isExit(take) ? take : Array.map(take, f)" + note: "Only the value batch is mapped; effect's Array.map preserves the NonEmptyReadonlyArray type, and Exit branches (failure/end) pass through unchanged." +effect/Take#match: + replacement: "Exit.isExit + Exit.match" + note: "Branch on the union: the array branch is v3's onSuccess(chunk), and Exit.match splits the Exit branch into onFailure(cause) and end-of-stream (v3 onEnd, success value = Done)." + example: | + // v3: Take.match(take, { onEnd, onFailure, onSuccess }) + Exit.isExit(take) + ? Exit.match(take, { onSuccess: () => onEnd(), onFailure: (cause) => onFailure(cause) }) + : onSuccess(take) +effect/Take#matchEffect: + replacement: "Pull.matchEffect(Take.toPull(take), { onSuccess, onFailure, onDone })" + note: "Convert with Take.toPull and fold with Pull.matchEffect: onSuccess receives the batch (v3 onSuccess), onFailure the cause, onDone the completion value (v3 onEnd); alternatively branch manually with Exit.isExit as for match." +effect/Take#of: + replacement: "[value]" + note: "A single-value Take is just the one-element non-empty array literal." +effect/Take#tap: + replacement: "Exit.isExit(take) ? Exit.asVoid(take) : Effect.asVoid(f(take))" + note: "Peek at the value batch with f; Exit branches pass through as effects (a failed Exit re-propagates its cause, an end Exit becomes a void success), matching v3 tap semantics." diff --git a/migration/annotations/effect__TestAnnotation.yaml b/migration/annotations/effect__TestAnnotation.yaml new file mode 100644 index 000000000..b8d69241d --- /dev/null +++ b/migration/annotations/effect__TestAnnotation.yaml @@ -0,0 +1,3 @@ +effect/TestAnnotation: + replacement: none + note: The legacy test-runner annotation key and built-in counters were removed. Use Vitest skip/repeat/retry options for runner concerns and FiberSet for explicit fiber tracking; there is no annotation-key equivalent. diff --git a/migration/annotations/effect__TestAnnotationMap.yaml b/migration/annotations/effect__TestAnnotationMap.yaml new file mode 100644 index 000000000..407cb77ea --- /dev/null +++ b/migration/annotations/effect__TestAnnotationMap.yaml @@ -0,0 +1,3 @@ +effect/TestAnnotationMap: + replacement: none + note: TestAnnotationMap was removed with TestAnnotation. Use an application-owned HashMap or Ref only when arbitrary typed annotations are still required; it is not part of the v4 test runner. diff --git a/migration/annotations/effect__TestAnnotations.yaml b/migration/annotations/effect__TestAnnotations.yaml new file mode 100644 index 000000000..2faf243d5 --- /dev/null +++ b/migration/annotations/effect__TestAnnotations.yaml @@ -0,0 +1,3 @@ +effect/TestAnnotations: + replacement: none + note: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. diff --git a/migration/annotations/effect__TestClock.yaml b/migration/annotations/effect__TestClock.yaml new file mode 100644 index 000000000..e95f504bd --- /dev/null +++ b/migration/annotations/effect__TestClock.yaml @@ -0,0 +1,24 @@ +"effect/TestClock#adjustWith": + replacement: "Effect.zipWith(effect, TestClock.adjust(duration), (result) => result, { concurrent: true })" + note: "V4 removed adjustWith. Run the tested effect and clock adjustment concurrently and retain the tested effect's result." +"effect/TestClock#currentTimeMillis": + replacement: "Clock.currentTimeMillis" + note: "Read time from the active Clock reference; under it.effect or TestClock.layer() this is virtual time." +"effect/TestClock#Data": + replacement: "TestClock.TestClock.State" + note: "The nearest state model is State, with timestamp and a private latch-based sleep queue. V4 exposes no full state getter or setter." +"effect/TestClock#defaultTestClock": + replacement: "TestClock.layer()" + note: "The v4 layer creates an epoch-based test clock and captures the surrounding live Clock automatically; it no longer needs TestAnnotations or TestLive." +"effect/TestClock#makeData": + replacement: "TestClock.layer() + TestClock.setTime(instant)" + note: "State injection was removed. Build the layer, then set initial time; seeded pending sleeps cannot migrate because the queue is private." +"effect/TestClock#save": + replacement: "none" + note: "Full clock snapshots including pending sleeps are no longer public. For timestamp-only restoration, read Clock.currentTimeMillis and later call TestClock.setTime(savedMillis)." +"effect/TestClock#sleeps": + replacement: "none" + note: "The pending-sleep queue is private. Test observable behavior by forking sleepers, adjusting time, and joining or asserting the fibers." +"effect/TestClock#testClock": + replacement: "TestClock.testClockWith(Effect.succeed)" + note: "V4 exposes callback-based access to the active test clock; use testClockWith directly when possible." diff --git a/migration/annotations/effect__TestConfig.yaml b/migration/annotations/effect__TestConfig.yaml new file mode 100644 index 000000000..18ddf7d6d --- /dev/null +++ b/migration/annotations/effect__TestConfig.yaml @@ -0,0 +1,6 @@ +"effect/TestConfig#make": + replacement: "{ repeats, retries, samples, shrinks }" + note: "The v3 constructor only returned its parameter object. The TestConfig service was removed; keep a plain object only for application-owned configuration." +"effect/TestConfig#TestConfig": + replacement: "none" + note: "There is no v4 TestConfig service. Move runner settings to Vitest and native Arbitrary check options, or define an application-specific Context.Reference if runtime access is needed." diff --git a/migration/annotations/effect__TestContext.yaml b/migration/annotations/effect__TestContext.yaml new file mode 100644 index 000000000..6738380cb --- /dev/null +++ b/migration/annotations/effect__TestContext.yaml @@ -0,0 +1,6 @@ +"effect/TestContext#LiveContext": + replacement: "@effect/vitest#live" + note: "Default runtime references are live in v4. Use it.live for a whole live test; no LiveContext layer is required." +"effect/TestContext#TestContext": + replacement: "Layer.mergeAll(TestConsole.layer, TestClock.layer())" + note: "This is the v4 test layer used by @effect/vitest. Prefer it.effect, which provides it automatically." diff --git a/migration/annotations/effect__TestLive.yaml b/migration/annotations/effect__TestLive.yaml new file mode 100644 index 000000000..e12b07c39 --- /dev/null +++ b/migration/annotations/effect__TestLive.yaml @@ -0,0 +1,9 @@ +"effect/TestLive#make": + replacement: "Effect.provideContext" + note: "The wrapper was removed. Apply a captured Context directly with Effect.provideContext; for live time inside it.effect, prefer TestClock.withLive." +"effect/TestLive#TestLive": + replacement: "none" + note: "There is no grouped live-default-services object. Use Context.Context plus Effect.provideContext, TestClock.withLive for live time, or it.live for the whole test." +"effect/TestLive#TestLiveTypeId": + replacement: "none" + note: "The TestLive nominal wrapper was removed, so its type id has no replacement." diff --git a/migration/annotations/effect__TestServices.yaml b/migration/annotations/effect__TestServices.yaml new file mode 100644 index 000000000..9ab4fe3df --- /dev/null +++ b/migration/annotations/effect__TestServices.yaml @@ -0,0 +1,96 @@ +"effect/TestServices#annotate": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#annotations": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#annotationsLayer": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#annotationsWith": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#get": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#supervisedFibers": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#withAnnotations": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#withAnnotationsScoped": + replacement: "none" + note: "The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking." +"effect/TestServices#currentServices": + replacement: "Effect.context()" + note: "The separate FiberRef> was removed. Test services now live in the ordinary Effect Context; override individual references with Effect.provideService." +"effect/TestServices#liveLayer": + replacement: "none" + note: "The standalone TestLive service and layer were removed. TestClock.layer() captures its surrounding live Clock itself." +"effect/TestServices#liveServices": + replacement: "none" + note: "There is no prebuilt aggregate test-service Context. @effect/vitest constructs TestClock and TestConsole layers per test; live references are defaults." +"effect/TestServices#liveWith": + replacement: "TestClock.withLive" + note: "There is no TestLive callback object. Refactor to the effect ultimately run and apply TestClock.withLive, or use it.live for whole-test live execution." +"effect/TestServices#provideLive": + replacement: "TestClock.withLive" + note: "For live time, run the effect with the Clock captured by TestClock.layer(). Use it.live when the entire test should omit all test-service overrides." +"effect/TestServices#provideWithLive": + replacement: "TestClock.testClockWith + TestClock.withLive + Effect.provideService" + note: "To retain test time for the inner effect while its transformer uses live time, combine testClockWith, withLive, and provideService. Other v3 default services have no aggregate equivalent." +"effect/TestServices#repeats": + replacement: "Vitest TestOptions.repeats" + note: "Configure repeats in the Vitest options passed to it.effect or it.live; it is no longer an Effect service value." +"effect/TestServices#retries": + replacement: "Vitest TestOptions.retry" + note: "Configure retry in Vitest test options. To retry an Effect inside a test, use Effect.retry." +"effect/TestServices#samples": + replacement: "{ arbitrary: { runs } }" + note: "Pass the run count through @effect/vitest property-test options, for example it.effect.prop(..., { arbitrary: { runs: samples } })." +"effect/TestServices#shrinks": + replacement: "{ arbitrary: { maxShrinks } }" + note: "Pass maxShrinks through @effect/vitest property-test options, for example it.effect.prop(..., { arbitrary: { maxShrinks } })." +"effect/TestServices#size": + replacement: "CurrentSize" + note: "Define a custom Context.Reference and yield it to read the current size." +"effect/TestServices#sized": + replacement: "CurrentSize" + note: "TestSized was removed. Use a custom Context.Reference directly instead of a wrapper object." +"effect/TestServices#sizedLayer": + replacement: "Layer.succeed(CurrentSize, size)" + note: "Provide the custom size reference as a layer." +"effect/TestServices#sizedWith": + replacement: "CurrentSize.use" + note: "Use the custom reference's callback, or preferably yield CurrentSize in Effect.gen." +"effect/TestServices#testConfig": + replacement: "none" + note: "The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference." +"effect/TestServices#testConfigLayer": + replacement: "none" + note: "The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference." +"effect/TestServices#testConfigWith": + replacement: "none" + note: "The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference." +"effect/TestServices#withTestConfig": + replacement: "none" + note: "The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference." +"effect/TestServices#withTestConfigScoped": + replacement: "none" + note: "The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference." +"effect/TestServices#TestServices": + replacement: "TestClock.TestClock | TestConsole.TestConsole" + note: "This is the v4 @effect/vitest test-environment union. it.effect provides both automatically; it.live provides neither override." +"effect/TestServices#withLiveScoped": + replacement: "none" + note: "There is no scoped TestLive service override. Apply TestClock.withLive to a specific effect, or choose it.live at test declaration time." +"effect/TestServices#withSize": + replacement: "Effect.provideService(effect, CurrentSize, size)" + note: "Provide a custom size Context.Reference for the duration of the wrapped effect." +"effect/TestServices#withSized": + replacement: "Effect.provideService(effect, CurrentSize, size)" + note: "Collapse the old TestSized wrapper to its numeric value and provide the custom reference." +"effect/TestServices#withSizedScoped": + replacement: "Effect.updateServiceScoped(CurrentSize, () => size)" + note: "For a scope-bounded override use updateServiceScoped; otherwise prefer wrapping the workflow with Effect.provideService." diff --git a/migration/annotations/effect__TestSized.yaml b/migration/annotations/effect__TestSized.yaml new file mode 100644 index 000000000..0e146dd2e --- /dev/null +++ b/migration/annotations/effect__TestSized.yaml @@ -0,0 +1,12 @@ +"effect/TestSized#fromFiberRef": + replacement: "Context.Reference" + note: "FiberRef and TestSized were removed. Define one stable Context.Reference instead of wrapping a FiberRef." +"effect/TestSized#make": + replacement: "Context.Reference" + note: "Define a module-level reference with defaultValue; do not create a fresh key at each call site." +"effect/TestSized#TestSized": + replacement: "Context.Reference" + note: "Collapse the wrapper service to the reference itself; yield the reference to read the current size." +"effect/TestSized#TestSizedTypeId": + replacement: "none" + note: "The wrapper's nominal type id is unnecessary; Context.Reference supplies stable key identity." diff --git a/migration/annotations/effect__Tracer.yaml b/migration/annotations/effect__Tracer.yaml new file mode 100644 index 000000000..e14547f37 --- /dev/null +++ b/migration/annotations/effect__Tracer.yaml @@ -0,0 +1,27 @@ +effect/Tracer#DisablePropagation: + replacement: "Tracer.DisablePropagation" + note: "Keep the reference value. The separate phantom interface is gone; the Context.Reference directly stores boolean." +effect/Tracer#ExternalSpan: + replacement: "Tracer.ExternalSpan" + note: "Keep the type, but rename the context field to annotations. Apply the same rename to Tracer.externalSpan options." +effect/Tracer#ParentSpan: + replacement: "Tracer.ParentSpan" + note: "Keep the API. It is now a Context.Service class for AnySpan rather than a separate phantom interface plus Context.Tag." +effect/Tracer#Span: + replacement: "Tracer.Span" + note: "Keep the type and rename span.context to span.annotations; the other public fields and methods remain." +effect/Tracer#SpanLink: + replacement: "Tracer.SpanLink" + note: "Keep the type but remove the _tag property; v4 links contain only span and attributes." +effect/Tracer#SpanOptions: + replacement: "Tracer.SpanOptions" + note: "Keep the type and rename context to annotations. V4 splits trace options and additionally accepts sampled and level." +effect/Tracer#Tracer: + replacement: "Tracer.Tracer" + note: "The service is now a defaulted Context.Reference. Custom implementations are structural and receive one span options object; context is optional and now receives an Effect primitive plus Fiber." +effect/Tracer#TracerTypeId: + replacement: "none" + note: "Tracer implementations are structural and no longer carry a public type-id brand." +effect/Tracer#tracerWith: + replacement: "Tracer.Tracer.use" + note: "Replace tracerWith(f) with Tracer.Tracer.use(f); do not use TracerKey, which is only the raw string key." diff --git a/migration/annotations/effect__Trie.yaml b/migration/annotations/effect__Trie.yaml new file mode 100644 index 000000000..38b40cb2c --- /dev/null +++ b/migration/annotations/effect__Trie.yaml @@ -0,0 +1,6 @@ +"effect/Trie#TypeId": + replacement: "none" + note: "The Trie brand is private and there is no public Trie runtime guard; use Trie.Trie in type positions." +"effect/Trie#unsafeGet": + replacement: "Trie.getUnsafe" + note: "Direct word-order rename; it still throws for a missing key." diff --git a/migration/annotations/effect__Tuple.yaml b/migration/annotations/effect__Tuple.yaml new file mode 100644 index 000000000..89ec37e3d --- /dev/null +++ b/migration/annotations/effect__Tuple.yaml @@ -0,0 +1,30 @@ +"effect/Tuple#at": + replacement: "Tuple.get" + note: "Renamed for indexed access; v4 constrains the index to a valid tuple position." +"effect/Tuple#getEquivalence": + replacement: "Tuple.makeEquivalence" + note: "Pass equivalences as one array instead of variadic arguments." +"effect/Tuple#getFirst": + replacement: "Tuple.get(0)" + note: "Use Tuple.get(self, 0), or Tuple.get(0) in a pipe." +"effect/Tuple#getOrder": + replacement: "Tuple.makeOrder" + note: "Pass orders as one array instead of variadic arguments." +"effect/Tuple#getSecond": + replacement: "Tuple.get(1)" + note: "Use Tuple.get(self, 1), or Tuple.get(1) in a pipe." +"effect/Tuple#mapBoth": + replacement: "Tuple.evolve" + note: "Use Tuple.evolve(self, [options.onFirst, options.onSecond])." +"effect/Tuple#mapFirst": + replacement: "Tuple.evolve" + note: "Use Tuple.evolve(self, [f]); unspecified positions are preserved." +"effect/Tuple#mapSecond": + replacement: "Tuple.evolve" + note: "Use Tuple.evolve(self, [undefined, f]); undefined preserves the first position." +"effect/Tuple#swap": + replacement: "Tuple.renameIndices" + note: "Swap a pair with Tuple.renameIndices(self, [\"1\", \"0\"])." +"effect/Tuple#TupleTypeLambda": + replacement: "none" + note: "Removed with tuple Bicovariant support; use the concrete tuple type or a local HKT TypeLambda." diff --git a/migration/annotations/effect__Types.yaml b/migration/annotations/effect__Types.yaml new file mode 100644 index 000000000..cbd7331fa --- /dev/null +++ b/migration/annotations/effect__Types.yaml @@ -0,0 +1,24 @@ +"effect/Types#Concurrency": + replacement: "Types.Concurrency" + note: "Still exported, but v4 removes inherit; replace it with an explicit number or unbounded." +"effect/Types#Contravariant": + replacement: "Types.Contravariant" + note: "Unchanged contravariant type helper." +"effect/Types#Covariant": + replacement: "Types.Covariant" + note: "Unchanged covariant type helper." +"effect/Types#Ctor": + replacement: "new (...args: Array) => T" + note: "The named alias was removed; inline the construct signature or define a local alias." +"effect/Types#Invariant": + replacement: "Types.Invariant" + note: "Unchanged invariant type helper." +"effect/Types#MatchRecord": + replacement: "{} extends S ? onTrue : onFalse" + note: "The alias was removed; inline its conditional because Types.VoidIfEmpty has different optional-record behavior." +"effect/Types#MergeRecord": + replacement: "Types.MergeLeft" + note: "MergeRecord was an alias for the retained left-biased MergeLeft helper." +"effect/Types#NoExcessProperties": + replacement: "Types.NoExcessProperties" + note: "Retained with equivalent excess-key checking." diff --git a/migration/annotations/effect__UpstreamPullRequest.yaml b/migration/annotations/effect__UpstreamPullRequest.yaml new file mode 100644 index 000000000..31a3882a9 --- /dev/null +++ b/migration/annotations/effect__UpstreamPullRequest.yaml @@ -0,0 +1,3 @@ +effect/UpstreamPullRequest: + replacement: none + note: Removed with Channel.concatMapWithCustom; v4 does not expose channel-executor pull-request events. Use supported flattening operators or implement exceptional behavior with Channel.fromTransform and Pull. diff --git a/migration/annotations/effect__UpstreamPullStrategy.yaml b/migration/annotations/effect__UpstreamPullStrategy.yaml new file mode 100644 index 000000000..af0ea8399 --- /dev/null +++ b/migration/annotations/effect__UpstreamPullStrategy.yaml @@ -0,0 +1,3 @@ +effect/UpstreamPullStrategy: + replacement: none + note: Removed with Channel.concatMapWithCustom. Select flattening and scheduling through Channel.flatMap, Channel.switchMap, or Channel.mergeAll; v4 has no upstream-pull strategy ADT. diff --git a/migration/annotations/effect__Utils.yaml b/migration/annotations/effect__Utils.yaml new file mode 100644 index 000000000..40387ec46 --- /dev/null +++ b/migration/annotations/effect__Utils.yaml @@ -0,0 +1,60 @@ +"effect/Utils#adapter": + replacement: "none" + note: "Remove the adapter and resume parameter; v4 generators yield yieldable values directly." +"effect/Utils#Adapter": + replacement: "none" + note: "The generator-adapter type was removed; type generator bodies to yield v4 yieldable values directly." +"effect/Utils#Gen": + replacement: "Utils.Gen" + note: "Still exported; drop the adapter type and resume parameter, then yield yieldable Kind values directly." +"effect/Utils#GenKind": + replacement: "none" + note: "The adapter wrapper was removed; custom yieldable Kinds should implement Symbol.iterator and return Utils.SingleShotGen." +"effect/Utils#GenKindImpl": + replacement: "none" + note: "The wrapper implementation was removed; implement direct yieldability with Symbol.iterator and Utils.SingleShotGen." +"effect/Utils#GenKindTypeId": + replacement: "none" + note: "The GenKind runtime marker was removed with the wrapper infrastructure." +"effect/Utils#internalCall": + replacement: "none" + note: "This was internal and has no public replacement; application code should invoke its thunk directly." +"effect/Utils#isGeneratorFunction": + replacement: "none" + note: "The unused constructor-identity predicate was removed; accept an explicit generator contract instead." +"effect/Utils#isGenKind": + replacement: "none" + note: "Removed with GenKind; v4 generator drivers consume directly yielded values." +"effect/Utils#makeGenKind": + replacement: "none" + note: "The wrapper constructor was removed; make custom Kinds yieldable with Symbol.iterator and Utils.SingleShotGen." +"effect/Utils#OptionalNumber": + replacement: "number | null | undefined" + note: "The unused named alias was removed; inline its union." +"effect/Utils#PCGRandom": + replacement: "Random.withSeed + Random.next / Random.nextIntBetween" + note: "Use the effectful Random service for seeded generation; v4 is not PCG-compatible." +"effect/Utils#PCGRandomState": + replacement: "none" + note: "No public PCG state snapshot or restore API remains; Random.withSeed is reproducible but not state-compatible." +"effect/Utils#SingleShotGen": + replacement: "Utils.SingleShotGen" + note: "Still exported; v4 removes its concrete return and throw methods, so do not call those optional iterator hooks." +"effect/Utils#structuralRegion": + replacement: "none" + note: "Remove the wrapper because v4 Equal.equals is structural by default; use a custom Equivalence for custom comparison." +"effect/Utils#structuralRegionState": + replacement: "none" + note: "The mutable test hook was removed; v4 equality is structural by default." +"effect/Utils#Variance": + replacement: "Utils.Variance" + note: "Still exported; remove the v3 GenKindTypeId marker from implementations." +"effect/Utils#YieldWrap": + replacement: "none" + note: "The internal generator transport wrapper was removed; yieldable values are yielded directly." +"effect/Utils#yieldWrapGet": + replacement: "none" + note: "The internal unwrapper was removed; generator drivers read the directly yielded value." +"effect/Utils#YieldWrapTypeId": + replacement: "none" + note: "The internal wrapper marker was removed with YieldWrap." diff --git a/migration/annotations/effect__ai-amazon-bedrock.yaml b/migration/annotations/effect__ai-amazon-bedrock.yaml new file mode 100644 index 000000000..e9b3b9352 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock": + replacement: "none" + note: "The @effect/ai-amazon-bedrock provider package was removed from v4 with no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration." diff --git a/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockClient.yaml b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockClient.yaml new file mode 100644 index 000000000..626bc2eb3 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockClient.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/AmazonBedrockClient": + replacement: none + note: The @effect/ai-amazon-bedrock provider package was removed from v4, so AmazonBedrockClient, layer, layerConfig, make, and Service have no direct replacements. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. diff --git a/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockConfig.yaml b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockConfig.yaml new file mode 100644 index 000000000..9c49ec99f --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockConfig.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/AmazonBedrockConfig": + replacement: none + note: The @effect/ai-amazon-bedrock provider package was removed from v4, so AmazonBedrockConfig has no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. diff --git a/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockLanguageModel.yaml b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockLanguageModel.yaml new file mode 100644 index 000000000..bc1365d26 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockLanguageModel.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/AmazonBedrockLanguageModel": + replacement: none + note: The @effect/ai-amazon-bedrock language-model integration was removed from v4. Use another supported v4 provider or implement LanguageModel.LanguageModel with @aws-sdk/client-bedrock-runtime. diff --git a/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockSchema.yaml b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockSchema.yaml new file mode 100644 index 000000000..c55353c86 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockSchema.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/AmazonBedrockSchema": + replacement: none + note: The @effect/ai-amazon-bedrock package was removed from v4, including its hand-written Bedrock schemas. Use @aws-sdk/client-bedrock-runtime request and response types, or schemas supplied by a custom v4 provider integration. diff --git a/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockTool.yaml b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockTool.yaml new file mode 100644 index 000000000..bc5ed3ea7 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__AmazonBedrockTool.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/AmazonBedrockTool": + replacement: none + note: The @effect/ai-amazon-bedrock package and its Anthropic-on-Bedrock provider tools were removed from v4. Recreate the capability in a custom provider integration if the Bedrock model still requires it. diff --git a/migration/annotations/effect__ai-amazon-bedrock__EventStreamEncoding.yaml b/migration/annotations/effect__ai-amazon-bedrock__EventStreamEncoding.yaml new file mode 100644 index 000000000..7553bfbb2 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__EventStreamEncoding.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/EventStreamEncoding": + replacement: none + note: The @effect/ai-amazon-bedrock package and its AWS event-stream decoder were removed from v4. Use the AWS SDK's Bedrock Runtime streaming support or implement decoding in a custom provider client. diff --git a/migration/annotations/effect__ai-amazon-bedrock__index.yaml b/migration/annotations/effect__ai-amazon-bedrock__index.yaml new file mode 100644 index 000000000..d0ecd9e74 --- /dev/null +++ b/migration/annotations/effect__ai-amazon-bedrock__index.yaml @@ -0,0 +1,3 @@ +"@effect/ai-amazon-bedrock/index": + replacement: "none" + note: "The @effect/ai-amazon-bedrock provider package was removed from v4 with no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration." diff --git a/migration/annotations/effect__ai-anthropic__AnthropicClient.yaml b/migration/annotations/effect__ai-anthropic__AnthropicClient.yaml new file mode 100644 index 000000000..6fa9c33d3 --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__AnthropicClient.yaml @@ -0,0 +1,54 @@ +"@effect/ai-anthropic/AnthropicClient#CitationsDelta": + replacement: "Generated.BetaCitationsDelta" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#ContentBlockDeltaEvent": + replacement: "Generated.BetaContentBlockDeltaEvent" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#ContentBlockStartEvent": + replacement: "Generated.BetaContentBlockStartEvent" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#ContentBlockStopEvent": + replacement: "Generated.BetaContentBlockStopEvent" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#ErrorEvent": + replacement: "Generated.BetaErrorResponse" + note: "The client-local stream error schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#InputJsonContentBlockDelta": + replacement: "Generated.BetaInputJsonContentBlockDelta" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#MessageDelta": + replacement: "Generated.BetaMessageDelta" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#MessageDeltaEvent": + replacement: "Generated.BetaMessageDeltaEvent" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#MessageDeltaUsage": + replacement: "typeof Generated.BetaMessageDeltaEvent.Type[\"usage\"]" + note: "The standalone usage schema was inlined into the regenerated v4 message-delta event." +"@effect/ai-anthropic/AnthropicClient#MessageStartEvent": + replacement: "Generated.BetaMessageStartEvent" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#MessageStopEvent": + replacement: "Generated.BetaMessageStopEvent" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#MessageStreamEvent": + replacement: "AnthropicClient.MessageStreamEvent" + note: "Still exported in v4 as a type union of generated beta stream events; adapt to the revised client stream contract." +"@effect/ai-anthropic/AnthropicClient#PingEvent": + replacement: "none" + note: "The v4 client consumes ping events internally and filters them from MessageStreamEvent, so no public ping schema is needed." +"@effect/ai-anthropic/AnthropicClient#ServerToolUsage": + replacement: "Generated.BetaServerToolUsage" + note: "The client-local usage schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#Service": + replacement: "AnthropicClient.Service" + note: "Still exported in v4; adapt to the revised generated client, streamRequest, and message response contracts." +"@effect/ai-anthropic/AnthropicClient#SignatureContentBlockDelta": + replacement: "Generated.BetaSignatureContentBlockDelta" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#TextContentBlockDelta": + replacement: "Generated.BetaTextContentBlockDelta" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." +"@effect/ai-anthropic/AnthropicClient#ThinkingContentBlockDelta": + replacement: "Generated.BetaThinkingContentBlockDelta" + note: "The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape." diff --git a/migration/annotations/effect__ai-anthropic__AnthropicConfig.yaml b/migration/annotations/effect__ai-anthropic__AnthropicConfig.yaml new file mode 100644 index 000000000..068f85afe --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__AnthropicConfig.yaml @@ -0,0 +1,6 @@ +"@effect/ai-anthropic/AnthropicConfig#AnthropicConfig": + replacement: "AnthropicConfig.AnthropicConfig" + note: "Still exported in v4; update imports and adapt to the revised v4 service and HTTP client types." +"@effect/ai-anthropic/AnthropicConfig#AnthropicConfig.Service": + replacement: "AnthropicConfig.AnthropicConfig.Service" + note: "Still exported in v4; update imports and adapt to the revised v4 service and HTTP client types." diff --git a/migration/annotations/effect__ai-anthropic__AnthropicLanguageModel.yaml b/migration/annotations/effect__ai-anthropic__AnthropicLanguageModel.yaml new file mode 100644 index 000000000..d624ede50 --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__AnthropicLanguageModel.yaml @@ -0,0 +1,21 @@ +"@effect/ai-anthropic/AnthropicLanguageModel#AnthropicReasoningInfo": + replacement: "Prompt.ReasoningPartOptions / Response reasoning metadata" + note: "The standalone reasoning-info union was removed; v4 declares Anthropic thinking and redacted-thinking data directly on Prompt and Response provider metadata." +"@effect/ai-anthropic/AnthropicLanguageModel#AnthropicTools": + replacement: "AnthropicLanguageModel.AnthropicUserDefinedTool | AnthropicLanguageModel.AnthropicProviderDefinedTool" + note: "The old combined tool union was split into explicit user-defined and provider-defined Anthropic request tool types." +"@effect/ai-anthropic/AnthropicLanguageModel#Config": + replacement: "AnthropicLanguageModel.Config" + note: "Still exported in v4; update imports and adapt to the revised Messages API request fields." +"@effect/ai-anthropic/AnthropicLanguageModel#Config.Service": + replacement: "AnthropicLanguageModel.Config.Service" + note: "Still exported in v4; update imports and adapt to the revised Messages API request fields." +"@effect/ai-anthropic/AnthropicLanguageModel#layerWithTokenizer": + replacement: "AnthropicLanguageModel.layer" + note: "The tokenizer-combining layer was removed; provide the language model and any Tokenizer service separately." +"@effect/ai-anthropic/AnthropicLanguageModel#modelWithTokenizer": + replacement: "AnthropicLanguageModel.model" + note: "The tokenizer-combining model was removed; use the v4 model descriptor and provide any Tokenizer service separately." +"@effect/ai-anthropic/AnthropicLanguageModel#prepareTools": + replacement: "none" + note: "Tool conversion became an internal part of the v4 Anthropic language model; use AnthropicTool constructors and pass tools through LanguageModel provider options instead." diff --git a/migration/annotations/effect__ai-anthropic__AnthropicTokenizer.yaml b/migration/annotations/effect__ai-anthropic__AnthropicTokenizer.yaml new file mode 100644 index 000000000..a2cb45a97 --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__AnthropicTokenizer.yaml @@ -0,0 +1,6 @@ +"@effect/ai-anthropic/AnthropicTokenizer#layer": + replacement: "Tokenizer.make" + note: "The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using @anthropic-ai/tokenizer if equivalent Anthropic counting is required." +"@effect/ai-anthropic/AnthropicTokenizer#make": + replacement: "Tokenizer.make" + note: "The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using @anthropic-ai/tokenizer if equivalent Anthropic counting is required." diff --git a/migration/annotations/effect__ai-anthropic__AnthropicTool.yaml b/migration/annotations/effect__ai-anthropic__AnthropicTool.yaml new file mode 100644 index 000000000..5cf62d776 --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__AnthropicTool.yaml @@ -0,0 +1,6 @@ +"@effect/ai-anthropic/AnthropicTool#getProviderDefinedToolName": + replacement: "Tool.NameMapper" + note: "The Anthropic-specific name lookup was removed; v4 provider tools carry custom and provider names through the shared Tool.NameMapper." +"@effect/ai-anthropic/AnthropicTool#ProviderDefinedTools": + replacement: "AnthropicTool.AnthropicTool" + note: "The provider-defined schema union was replaced by the union of v4 Anthropic provider tool constructor return types." diff --git a/migration/annotations/effect__ai-anthropic__Generated.yaml b/migration/annotations/effect__ai-anthropic__Generated.yaml new file mode 100644 index 000000000..76e5ff293 --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__Generated.yaml @@ -0,0 +1,1023 @@ +"@effect/ai-anthropic/Generated#APIError": + replacement: "Generated.APIError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#AuthenticationError": + replacement: "Generated.AuthenticationError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Base64ImageSource": + replacement: "Generated.Base64ImageSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Base64ImageSourceMediaType": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#Base64PDFSource": + replacement: "Generated.Base64PDFSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BashTool20250124": + replacement: "Generated.BashTool_20250124" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaAPIError": + replacement: "Generated.BetaAPIError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaapiSchemasSkillsSkill": + replacement: "Generated.Betaapi__schemas__skills__Skill" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaAuthenticationError": + replacement: "Generated.BetaAuthenticationError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBase64ImageSource": + replacement: "Generated.BetaBase64ImageSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBase64ImageSourceMediaType": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaBase64PDFSource": + replacement: "Generated.BetaBase64PDFSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBashCodeExecutionToolResultErrorCode": + replacement: "Generated.BetaBashCodeExecutionToolResultErrorCode" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBashTool20241022": + replacement: "Generated.BetaBashTool_20241022" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBashTool20250124": + replacement: "Generated.BetaBashTool_20250124" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBillingError": + replacement: "Generated.BetaBillingError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBodyCreateSkillV1SkillsPost": + replacement: "Generated.BetaBody_create_skill_v1_skills_post" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaBodyCreateSkillVersionV1SkillsSkillIdVersionsPost": + replacement: "Generated.BetaBody_create_skill_version_v1_skills__skill_id__versions_post" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCacheControlEphemeral": + replacement: "Generated.BetaCacheControlEphemeral" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCacheControlEphemeralTtl": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaCacheCreation": + replacement: "Generated.BetaCacheCreation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaClearToolUses20250919": + replacement: "Generated.BetaClearToolUses20250919" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCodeExecutionTool20250522": + replacement: "Generated.BetaCodeExecutionTool_20250522" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCodeExecutionTool20250825": + replacement: "Generated.BetaCodeExecutionTool_20250825" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCodeExecutionToolResultErrorCode": + replacement: "Generated.BetaCodeExecutionToolResultErrorCode" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaComputerUseTool20241022": + replacement: "Generated.BetaComputerUseTool_20241022" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaComputerUseTool20250124": + replacement: "Generated.BetaComputerUseTool_20250124" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaContainer": + replacement: "Generated.BetaContainer" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaContainerParams": + replacement: "Generated.BetaContainerParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaContentBlock": + replacement: "Generated.BetaContentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaContentBlockSource": + replacement: "Generated.BetaContentBlockSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaContextManagementConfig": + replacement: "Generated.BetaContextManagementConfig" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaContextManagementResponse": + replacement: "Generated.BetaContextManagementResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCountMessageTokensParams": + replacement: "Generated.BetaCountMessageTokensParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCountMessageTokensResponse": + replacement: "Generated.BetaCountMessageTokensResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCreateMessageBatchParams": + replacement: "Generated.BetaCreateMessageBatchParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCreateMessageParams": + replacement: "Generated.BetaCreateMessageParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCreateMessageParamsServiceTier": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaCreateSkillResponse": + replacement: "Generated.BetaCreateSkillResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCreateSkillV1SkillsPostParams": + replacement: "Generated.BetaCreateSkillV1SkillsPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCreateSkillVersionResponse": + replacement: "Generated.BetaCreateSkillVersionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams": + replacement: "Generated.BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDeleteFileV1FilesFileIdDeleteParams": + replacement: "Generated.BetaDeleteFileV1FilesFileIdDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDeleteMessageBatchResponse": + replacement: "Generated.BetaDeleteMessageBatchResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDeleteSkillResponse": + replacement: "Generated.BetaDeleteSkillResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDeleteSkillV1SkillsSkillIdDeleteParams": + replacement: "Generated.BetaDeleteSkillV1SkillsSkillIdDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDeleteSkillVersionResponse": + replacement: "Generated.BetaDeleteSkillVersionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams": + replacement: "Generated.BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaDownloadFileV1FilesFileIdContentGetParams": + replacement: "Generated.BetaDownloadFileV1FilesFileIdContentGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaErrorResponse": + replacement: "Generated.BetaErrorResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaFileDeleteResponse": + replacement: "Generated.BetaFileDeleteResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaFileDocumentSource": + replacement: "Generated.BetaFileDocumentSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaFileImageSource": + replacement: "Generated.BetaFileImageSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaFileListResponse": + replacement: "Generated.BetaFileListResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaFileMetadataSchema": + replacement: "Generated.BetaFileMetadataSchema" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaGatewayTimeoutError": + replacement: "Generated.BetaGatewayTimeoutError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaGetFileMetadataV1FilesFileIdGetParams": + replacement: "Generated.BetaGetFileMetadataV1FilesFileIdGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaGetSkillResponse": + replacement: "Generated.BetaGetSkillResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaGetSkillV1SkillsSkillIdGetParams": + replacement: "Generated.BetaGetSkillV1SkillsSkillIdGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaGetSkillVersionResponse": + replacement: "Generated.BetaGetSkillVersionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams": + replacement: "Generated.BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaInputContentBlock": + replacement: "Generated.BetaInputContentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaInputMessage": + replacement: "Generated.BetaInputMessage" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaInputMessageRole": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaInputSchema": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaInputTokensClearAtLeast": + replacement: "Generated.BetaInputTokensClearAtLeast" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaInputTokensTrigger": + replacement: "Generated.BetaInputTokensTrigger" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaInvalidRequestError": + replacement: "Generated.BetaInvalidRequestError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListFilesV1FilesGetParams": + replacement: "Generated.BetaListFilesV1FilesGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListResponseMessageBatch": + replacement: "Generated.BetaListResponse_MessageBatch_" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListResponseModelInfo": + replacement: "Generated.BetaListResponse_ModelInfo_" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListSkillsResponse": + replacement: "Generated.BetaListSkillsResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListSkillsV1SkillsGetParams": + replacement: "Generated.BetaListSkillsV1SkillsGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListSkillVersionsResponse": + replacement: "Generated.BetaListSkillVersionsResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams": + replacement: "Generated.BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMemoryTool20250818": + replacement: "Generated.BetaMemoryTool_20250818" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessage": + replacement: "Generated.BetaMessage" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatch": + replacement: "Generated.BetaMessageBatch" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchesCancelParams": + replacement: "Generated.BetaMessageBatchesCancelParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchesDeleteParams": + replacement: "Generated.BetaMessageBatchesDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchesListParams": + replacement: "Generated.BetaMessageBatchesListParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchesPostParams": + replacement: "Generated.BetaMessageBatchesPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchesResultsParams": + replacement: "Generated.BetaMessageBatchesResultsParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchesRetrieveParams": + replacement: "Generated.BetaMessageBatchesRetrieveParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchIndividualRequestParams": + replacement: "Generated.BetaMessageBatchIndividualRequestParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessageBatchProcessingStatus": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaMessagesCountTokensPostParams": + replacement: "Generated.BetaMessagesCountTokensPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMessagesPostParams": + replacement: "Generated.BetaMessagesPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaMetadata": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaModelInfo": + replacement: "Generated.BetaModelInfo" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaModelsGetParams": + replacement: "Generated.BetaModelsGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaModelsListParams": + replacement: "Generated.BetaModelsListParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaNotFoundError": + replacement: "Generated.BetaNotFoundError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaOverloadedError": + replacement: "Generated.BetaOverloadedError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaPermissionError": + replacement: "Generated.BetaPermissionError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaPlainTextSource": + replacement: "Generated.BetaPlainTextSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRateLimitError": + replacement: "Generated.BetaRateLimitError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestBashCodeExecutionOutputBlock": + replacement: "Generated.BetaRequestBashCodeExecutionOutputBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestBashCodeExecutionResultBlock": + replacement: "Generated.BetaRequestBashCodeExecutionResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestBashCodeExecutionToolResultBlock": + replacement: "Generated.BetaRequestBashCodeExecutionToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestBashCodeExecutionToolResultError": + replacement: "Generated.BetaRequestBashCodeExecutionToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCharLocationCitation": + replacement: "Generated.BetaRequestCharLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCitationsConfig": + replacement: "Generated.BetaRequestCitationsConfig" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCodeExecutionOutputBlock": + replacement: "Generated.BetaRequestCodeExecutionOutputBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCodeExecutionResultBlock": + replacement: "Generated.BetaRequestCodeExecutionResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCodeExecutionToolResultBlock": + replacement: "Generated.BetaRequestCodeExecutionToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCodeExecutionToolResultError": + replacement: "Generated.BetaRequestCodeExecutionToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestContainerUploadBlock": + replacement: "Generated.BetaRequestContainerUploadBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestContentBlockLocationCitation": + replacement: "Generated.BetaRequestContentBlockLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestCounts": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestDocumentBlock": + replacement: "Generated.BetaRequestDocumentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestImageBlock": + replacement: "Generated.BetaRequestImageBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestMCPServerToolConfiguration": + replacement: "Generated.BetaRequestMCPServerToolConfiguration" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestMCPServerURLDefinition": + replacement: "Generated.BetaRequestMCPServerURLDefinition" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestMCPToolResultBlock": + replacement: "Generated.BetaRequestMCPToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestMCPToolUseBlock": + replacement: "Generated.BetaRequestMCPToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestPageLocationCitation": + replacement: "Generated.BetaRequestPageLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestRedactedThinkingBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestSearchResultBlock": + replacement: "Generated.BetaRequestSearchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestSearchResultLocationCitation": + replacement: "Generated.BetaRequestSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestServerToolUseBlock": + replacement: "Generated.BetaRequestServerToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestServerToolUseBlockName": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestTextBlock": + replacement: "Generated.BetaRequestTextBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestTextEditorCodeExecutionCreateResultBlock": + replacement: "Generated.BetaRequestTextEditorCodeExecutionCreateResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestTextEditorCodeExecutionStrReplaceResultBlock": + replacement: "Generated.BetaRequestTextEditorCodeExecutionStrReplaceResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestTextEditorCodeExecutionToolResultBlock": + replacement: "Generated.BetaRequestTextEditorCodeExecutionToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestTextEditorCodeExecutionToolResultError": + replacement: "Generated.BetaRequestTextEditorCodeExecutionToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestTextEditorCodeExecutionViewResultBlock": + replacement: "Generated.BetaRequestTextEditorCodeExecutionViewResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestTextEditorCodeExecutionViewResultBlockFileType": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestThinkingBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestToolResultBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestToolUseBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaRequestWebFetchResultBlock": + replacement: "Generated.BetaRequestWebFetchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestWebFetchToolResultBlock": + replacement: "Generated.BetaRequestWebFetchToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestWebFetchToolResultError": + replacement: "Generated.BetaRequestWebFetchToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestWebSearchResultBlock": + replacement: "Generated.BetaRequestWebSearchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestWebSearchResultLocationCitation": + replacement: "Generated.BetaRequestWebSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestWebSearchToolResultBlock": + replacement: "Generated.BetaRequestWebSearchToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaRequestWebSearchToolResultError": + replacement: "Generated.BetaRequestWebSearchToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseBashCodeExecutionOutputBlock": + replacement: "Generated.BetaResponseBashCodeExecutionOutputBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseBashCodeExecutionResultBlock": + replacement: "Generated.BetaResponseBashCodeExecutionResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseBashCodeExecutionToolResultBlock": + replacement: "Generated.BetaResponseBashCodeExecutionToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseBashCodeExecutionToolResultError": + replacement: "Generated.BetaResponseBashCodeExecutionToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseCharLocationCitation": + replacement: "Generated.BetaResponseCharLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseCitationsConfig": + replacement: "Generated.BetaResponseCitationsConfig" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseClearToolUses20250919Edit": + replacement: "Generated.BetaResponseClearToolUses20250919Edit" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseCodeExecutionOutputBlock": + replacement: "Generated.BetaResponseCodeExecutionOutputBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseCodeExecutionResultBlock": + replacement: "Generated.BetaResponseCodeExecutionResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseCodeExecutionToolResultBlock": + replacement: "Generated.BetaResponseCodeExecutionToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseCodeExecutionToolResultError": + replacement: "Generated.BetaResponseCodeExecutionToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseContainerUploadBlock": + replacement: "Generated.BetaResponseContainerUploadBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseContentBlockLocationCitation": + replacement: "Generated.BetaResponseContentBlockLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseContextManagement": + replacement: "Generated.BetaResponseContextManagement" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseDocumentBlock": + replacement: "Generated.BetaResponseDocumentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseMCPToolResultBlock": + replacement: "Generated.BetaResponseMCPToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseMCPToolUseBlock": + replacement: "Generated.BetaResponseMCPToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponsePageLocationCitation": + replacement: "Generated.BetaResponsePageLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseRedactedThinkingBlock": + replacement: "Generated.BetaResponseRedactedThinkingBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseSearchResultLocationCitation": + replacement: "Generated.BetaResponseSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseServerToolUseBlock": + replacement: "Generated.BetaResponseServerToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseServerToolUseBlockName": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaResponseTextBlock": + replacement: "Generated.BetaResponseTextBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseTextEditorCodeExecutionCreateResultBlock": + replacement: "Generated.BetaResponseTextEditorCodeExecutionCreateResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseTextEditorCodeExecutionStrReplaceResultBlock": + replacement: "Generated.BetaResponseTextEditorCodeExecutionStrReplaceResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseTextEditorCodeExecutionToolResultBlock": + replacement: "Generated.BetaResponseTextEditorCodeExecutionToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseTextEditorCodeExecutionToolResultError": + replacement: "Generated.BetaResponseTextEditorCodeExecutionToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseTextEditorCodeExecutionViewResultBlock": + replacement: "Generated.BetaResponseTextEditorCodeExecutionViewResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseTextEditorCodeExecutionViewResultBlockFileType": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaResponseThinkingBlock": + replacement: "Generated.BetaResponseThinkingBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseToolUseBlock": + replacement: "Generated.BetaResponseToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebFetchResultBlock": + replacement: "Generated.BetaResponseWebFetchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebFetchToolResultBlock": + replacement: "Generated.BetaResponseWebFetchToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebFetchToolResultError": + replacement: "Generated.BetaResponseWebFetchToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebSearchResultBlock": + replacement: "Generated.BetaResponseWebSearchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebSearchResultLocationCitation": + replacement: "Generated.BetaResponseWebSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebSearchToolResultBlock": + replacement: "Generated.BetaResponseWebSearchToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaResponseWebSearchToolResultError": + replacement: "Generated.BetaResponseWebSearchToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaServerToolUsage": + replacement: "Generated.BetaServerToolUsage" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaSkill": + replacement: "Generated.BetaSkill" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaSkillParams": + replacement: "Generated.BetaSkillParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaSkillParamsType": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaSkillType": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaSkillVersion": + replacement: "Generated.BetaSkillVersion" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaStopReason": + replacement: "Generated.BetaStopReason" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaTextEditor20241022": + replacement: "Generated.BetaTextEditor_20241022" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaTextEditor20250124": + replacement: "Generated.BetaTextEditor_20250124" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaTextEditor20250429": + replacement: "Generated.BetaTextEditor_20250429" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaTextEditor20250728": + replacement: "Generated.BetaTextEditor_20250728" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaTextEditorCodeExecutionToolResultErrorCode": + replacement: "Generated.BetaTextEditorCodeExecutionToolResultErrorCode" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaThinkingConfigDisabled": + replacement: "Generated.BetaThinkingConfigDisabled" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaThinkingConfigEnabled": + replacement: "Generated.BetaThinkingConfigEnabled" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaThinkingConfigParam": + replacement: "Generated.BetaThinkingConfigParam" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaTool": + replacement: "Generated.BetaTool" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolChoice": + replacement: "Generated.BetaToolChoice" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolChoiceAny": + replacement: "Generated.BetaToolChoiceAny" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolChoiceAuto": + replacement: "Generated.BetaToolChoiceAuto" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolChoiceNone": + replacement: "Generated.BetaToolChoiceNone" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolChoiceTool": + replacement: "Generated.BetaToolChoiceTool" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolUsesKeep": + replacement: "Generated.BetaToolUsesKeep" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaToolUsesTrigger": + replacement: "Generated.BetaToolUsesTrigger" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaUploadFileV1FilesPostParams": + replacement: "Generated.BetaUploadFileV1FilesPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaUploadFileV1FilesPostRequest": + replacement: "Generated.BetaUploadFileV1FilesPostRequestFormData" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaURLImageSource": + replacement: "Generated.BetaURLImageSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaURLPDFSource": + replacement: "Generated.BetaURLPDFSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaUsage": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaUsageServiceTierEnum": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#BetaUserLocation": + replacement: "Generated.BetaUserLocation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaWebFetchTool20250910": + replacement: "Generated.BetaWebFetchTool_20250910" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaWebFetchToolResultErrorCode": + replacement: "Generated.BetaWebFetchToolResultErrorCode" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaWebSearchTool20250305": + replacement: "Generated.BetaWebSearchTool_20250305" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BetaWebSearchToolResultErrorCode": + replacement: "Generated.BetaWebSearchToolResultErrorCode" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BillingError": + replacement: "Generated.BillingError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BodyCreateSkillV1SkillsPost": + replacement: "Generated.Body_create_skill_v1_skills_post" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#BodyCreateSkillVersionV1SkillsSkillIdVersionsPost": + replacement: "Generated.Body_create_skill_version_v1_skills__skill_id__versions_post" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CacheControlEphemeral": + replacement: "Generated.CacheControlEphemeral" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CacheControlEphemeralTtl": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#CacheCreation": + replacement: "Generated.CacheCreation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Client": + replacement: "Generated.AnthropicClient" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ClientError": + replacement: "Generated.AnthropicClientError" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CompletePostParams": + replacement: "Generated.CompletePostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CompletionRequest": + replacement: "Generated.CompletionRequest" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CompletionResponse": + replacement: "Generated.CompletionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ContentBlock": + replacement: "Generated.ContentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ContentBlockSource": + replacement: "Generated.ContentBlockSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CountMessageTokensParams": + replacement: "Generated.CountMessageTokensParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CountMessageTokensResponse": + replacement: "Generated.CountMessageTokensResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CreateMessageBatchParams": + replacement: "Generated.CreateMessageBatchParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CreateMessageParams": + replacement: "Generated.CreateMessageParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CreateMessageParamsServiceTier": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#CreateSkillResponse": + replacement: "Generated.CreateSkillResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CreateSkillV1SkillsPostParams": + replacement: "Generated.CreateSkillV1SkillsPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CreateSkillVersionResponse": + replacement: "Generated.CreateSkillVersionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#CreateSkillVersionV1SkillsSkillIdVersionsPostParams": + replacement: "Generated.CreateSkillVersionV1SkillsSkillIdVersionsPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DeleteFileV1FilesFileIdDeleteParams": + replacement: "Generated.DeleteFileV1FilesFileIdDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DeleteMessageBatchResponse": + replacement: "Generated.DeleteMessageBatchResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DeleteSkillResponse": + replacement: "Generated.DeleteSkillResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DeleteSkillV1SkillsSkillIdDeleteParams": + replacement: "Generated.DeleteSkillV1SkillsSkillIdDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DeleteSkillVersionResponse": + replacement: "Generated.DeleteSkillVersionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams": + replacement: "Generated.DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#DownloadFileV1FilesFileIdContentGetParams": + replacement: "Generated.DownloadFileV1FilesFileIdContentGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ErrorResponse": + replacement: "Generated.ErrorResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#FileDeleteResponse": + replacement: "Generated.FileDeleteResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#FileListResponse": + replacement: "Generated.FileListResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#FileMetadataSchema": + replacement: "Generated.FileMetadataSchema" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#GatewayTimeoutError": + replacement: "Generated.GatewayTimeoutError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#GetFileMetadataV1FilesFileIdGetParams": + replacement: "Generated.GetFileMetadataV1FilesFileIdGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#GetSkillResponse": + replacement: "Generated.GetSkillResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#GetSkillV1SkillsSkillIdGetParams": + replacement: "Generated.GetSkillV1SkillsSkillIdGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#GetSkillVersionResponse": + replacement: "Generated.GetSkillVersionResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams": + replacement: "Generated.GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#InputContentBlock": + replacement: "Generated.InputContentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#InputMessage": + replacement: "Generated.InputMessage" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#InputMessageRole": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#InputSchema": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#InvalidRequestError": + replacement: "Generated.InvalidRequestError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListFilesV1FilesGetParams": + replacement: "Generated.ListFilesV1FilesGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListResponseMessageBatch": + replacement: "Generated.ListResponse_MessageBatch_" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListResponseModelInfo": + replacement: "Generated.ListResponse_ModelInfo_" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListSkillsResponse": + replacement: "Generated.ListSkillsResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListSkillsV1SkillsGetParams": + replacement: "Generated.ListSkillsV1SkillsGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListSkillVersionsResponse": + replacement: "Generated.ListSkillVersionsResponse" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ListSkillVersionsV1SkillsSkillIdVersionsGetParams": + replacement: "Generated.ListSkillVersionsV1SkillsSkillIdVersionsGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#make": + replacement: "Generated.make" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Message": + replacement: "Generated.Message" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatch": + replacement: "Generated.MessageBatch" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchesCancelParams": + replacement: "Generated.MessageBatchesCancelParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchesDeleteParams": + replacement: "Generated.MessageBatchesDeleteParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchesListParams": + replacement: "Generated.MessageBatchesListParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchesPostParams": + replacement: "Generated.MessageBatchesPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchesResultsParams": + replacement: "Generated.MessageBatchesResultsParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchesRetrieveParams": + replacement: "Generated.MessageBatchesRetrieveParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchIndividualRequestParams": + replacement: "Generated.MessageBatchIndividualRequestParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessageBatchProcessingStatus": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#MessagesCountTokensPostParams": + replacement: "Generated.MessagesCountTokensPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#MessagesPostParams": + replacement: "Generated.MessagesPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Metadata": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#Model": + replacement: "Generated.Model" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ModelInfo": + replacement: "Generated.ModelInfo" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ModelsGetParams": + replacement: "Generated.ModelsGetParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ModelsListParams": + replacement: "Generated.ModelsListParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#NotFoundError": + replacement: "Generated.NotFoundError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#OverloadedError": + replacement: "Generated.OverloadedError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#PermissionError": + replacement: "Generated.PermissionError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#PlainTextSource": + replacement: "Generated.PlainTextSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RateLimitError": + replacement: "Generated.RateLimitError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestCharLocationCitation": + replacement: "Generated.RequestCharLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestCitationsConfig": + replacement: "Generated.RequestCitationsConfig" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestContentBlockLocationCitation": + replacement: "Generated.RequestContentBlockLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestCounts": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#RequestDocumentBlock": + replacement: "Generated.RequestDocumentBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestImageBlock": + replacement: "Generated.RequestImageBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestPageLocationCitation": + replacement: "Generated.RequestPageLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestRedactedThinkingBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#RequestSearchResultBlock": + replacement: "Generated.RequestSearchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestSearchResultLocationCitation": + replacement: "Generated.RequestSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestServerToolUseBlock": + replacement: "Generated.RequestServerToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestTextBlock": + replacement: "Generated.RequestTextBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestThinkingBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#RequestToolResultBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#RequestToolUseBlock": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#RequestWebSearchResultBlock": + replacement: "Generated.RequestWebSearchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestWebSearchResultLocationCitation": + replacement: "Generated.RequestWebSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestWebSearchToolResultBlock": + replacement: "Generated.RequestWebSearchToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#RequestWebSearchToolResultError": + replacement: "Generated.RequestWebSearchToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseCharLocationCitation": + replacement: "Generated.ResponseCharLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseContentBlockLocationCitation": + replacement: "Generated.ResponseContentBlockLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponsePageLocationCitation": + replacement: "Generated.ResponsePageLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseRedactedThinkingBlock": + replacement: "Generated.ResponseRedactedThinkingBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseSearchResultLocationCitation": + replacement: "Generated.ResponseSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseServerToolUseBlock": + replacement: "Generated.ResponseServerToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseTextBlock": + replacement: "Generated.ResponseTextBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseThinkingBlock": + replacement: "Generated.ResponseThinkingBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseToolUseBlock": + replacement: "Generated.ResponseToolUseBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseWebSearchResultBlock": + replacement: "Generated.ResponseWebSearchResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseWebSearchResultLocationCitation": + replacement: "Generated.ResponseWebSearchResultLocationCitation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseWebSearchToolResultBlock": + replacement: "Generated.ResponseWebSearchToolResultBlock" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ResponseWebSearchToolResultError": + replacement: "Generated.ResponseWebSearchToolResultError" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ServerToolUsage": + replacement: "Generated.ServerToolUsage" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Skill": + replacement: "Generated.Skill" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#SkillVersion": + replacement: "Generated.SkillVersion" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#StopReason": + replacement: "Generated.StopReason" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#TextEditor20250124": + replacement: "Generated.TextEditor_20250124" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#TextEditor20250429": + replacement: "Generated.TextEditor_20250429" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#TextEditor20250728": + replacement: "Generated.TextEditor_20250728" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ThinkingConfigDisabled": + replacement: "Generated.ThinkingConfigDisabled" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ThinkingConfigEnabled": + replacement: "Generated.ThinkingConfigEnabled" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ThinkingConfigParam": + replacement: "Generated.ThinkingConfigParam" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#Tool": + replacement: "Generated.Tool" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ToolChoice": + replacement: "Generated.ToolChoice" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ToolChoiceAny": + replacement: "Generated.ToolChoiceAny" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ToolChoiceAuto": + replacement: "Generated.ToolChoiceAuto" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ToolChoiceNone": + replacement: "Generated.ToolChoiceNone" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#ToolChoiceTool": + replacement: "Generated.ToolChoiceTool" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#UploadFileV1FilesPostParams": + replacement: "Generated.UploadFileV1FilesPostParams" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#UploadFileV1FilesPostRequest": + replacement: "Generated.UploadFileV1FilesPostRequestFormData" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#URLImageSource": + replacement: "Generated.URLImageSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#URLPDFSource": + replacement: "Generated.URLPDFSource" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#UsageServiceTierEnum": + replacement: "none" + note: "Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-anthropic/Generated#UserLocation": + replacement: "Generated.UserLocation" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#WebSearchTool20250305": + replacement: "Generated.WebSearchTool_20250305" + note: "Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-anthropic/Generated#WebSearchToolResultErrorCode": + replacement: "Generated.WebSearchToolResultErrorCode" + note: "Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed." diff --git a/migration/annotations/effect__ai-anthropic__index.yaml b/migration/annotations/effect__ai-anthropic__index.yaml new file mode 100644 index 000000000..f8fa2bd85 --- /dev/null +++ b/migration/annotations/effect__ai-anthropic__index.yaml @@ -0,0 +1,3 @@ +"@effect/ai-anthropic/index": + replacement: "@effect/ai-anthropic" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-anthropic package root or import specific modules directly." diff --git a/migration/annotations/effect__ai-google.yaml b/migration/annotations/effect__ai-google.yaml new file mode 100644 index 000000000..9de246d44 --- /dev/null +++ b/migration/annotations/effect__ai-google.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google": + replacement: "none" + note: "The @effect/ai-google provider package was removed from v4 with no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly." diff --git a/migration/annotations/effect__ai-google__Generated.yaml b/migration/annotations/effect__ai-google__Generated.yaml new file mode 100644 index 000000000..0b6ed2397 --- /dev/null +++ b/migration/annotations/effect__ai-google__Generated.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google/Generated": + replacement: none + note: The @effect/ai-google package was removed from v4, so this generated Google API schema has no Effect v4 replacement. Use Google's current SDK/API types directly or route supported Gemini models through another v4 provider integration. diff --git a/migration/annotations/effect__ai-google__GoogleClient.yaml b/migration/annotations/effect__ai-google__GoogleClient.yaml new file mode 100644 index 000000000..f344b4f94 --- /dev/null +++ b/migration/annotations/effect__ai-google__GoogleClient.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google/GoogleClient": + replacement: none + note: The @effect/ai-google provider package was removed from v4 and has no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. diff --git a/migration/annotations/effect__ai-google__GoogleConfig.yaml b/migration/annotations/effect__ai-google__GoogleConfig.yaml new file mode 100644 index 000000000..175622d5e --- /dev/null +++ b/migration/annotations/effect__ai-google__GoogleConfig.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google/GoogleConfig": + replacement: none + note: The @effect/ai-google provider package was removed from v4 and has no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. diff --git a/migration/annotations/effect__ai-google__GoogleLanguageModel.yaml b/migration/annotations/effect__ai-google__GoogleLanguageModel.yaml new file mode 100644 index 000000000..0cda6708c --- /dev/null +++ b/migration/annotations/effect__ai-google__GoogleLanguageModel.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google/GoogleLanguageModel": + replacement: none + note: The @effect/ai-google language-model integration was removed from v4. Use a supported v4 provider integration for Gemini models or implement LanguageModel.LanguageModel against Google's current SDK. diff --git a/migration/annotations/effect__ai-google__GoogleTool.yaml b/migration/annotations/effect__ai-google__GoogleTool.yaml new file mode 100644 index 000000000..cd91c242c --- /dev/null +++ b/migration/annotations/effect__ai-google__GoogleTool.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google/GoogleTool": + replacement: none + note: The @effect/ai-google package and its provider-defined tools were removed from v4. Model this capability in the provider integration you adopt, or define an application Tool when the replacement provider supports it. diff --git a/migration/annotations/effect__ai-google__index.yaml b/migration/annotations/effect__ai-google__index.yaml new file mode 100644 index 000000000..a5cfecfc0 --- /dev/null +++ b/migration/annotations/effect__ai-google__index.yaml @@ -0,0 +1,3 @@ +"@effect/ai-google/index": + replacement: "none" + note: "The @effect/ai-google provider package was removed from v4 with no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly." diff --git a/migration/annotations/effect__ai-openai__Generated.yaml b/migration/annotations/effect__ai-openai__Generated.yaml new file mode 100644 index 000000000..5f96a15c4 --- /dev/null +++ b/migration/annotations/effect__ai-openai__Generated.yaml @@ -0,0 +1,3714 @@ +"@effect/ai-openai/Generated#ActiveStatus": + replacement: "Generated.ActiveStatus" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ActiveStatusType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AddUploadPartRequest": + replacement: "Generated.AddUploadPartRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AdminApiKey": + replacement: "Generated.AdminApiKey" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AdminApiKeysCreateRequest": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AdminApiKeysDelete200": + replacement: "Generated.AdminApiKeysDelete200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AdminApiKeysListParams": + replacement: "Generated.AdminApiKeysListParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AdminApiKeysListParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Annotation": + replacement: "Generated.Annotation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApiKeyList": + replacement: "Generated.ApiKeyList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchCallOutputStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchCallOutputStatusParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchCallStatusParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchCreateFileOperation": + replacement: "Generated.ApplyPatchCreateFileOperation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchCreateFileOperationParam": + replacement: "Generated.ApplyPatchCreateFileOperationParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchCreateFileOperationParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchCreateFileOperationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchDeleteFileOperation": + replacement: "Generated.ApplyPatchDeleteFileOperation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchDeleteFileOperationParam": + replacement: "Generated.ApplyPatchDeleteFileOperationParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchDeleteFileOperationParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchDeleteFileOperationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchOperationParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolCall": + replacement: "Generated.ApplyPatchToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchToolCallItemParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolCallItemParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolCallOutput": + replacement: "Generated.ApplyPatchToolCallOutput" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchToolCallOutputItemParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolCallOutputItemParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolCallOutputType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchToolParam": + replacement: "Generated.ApplyPatchToolParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchToolParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchUpdateFileOperation": + replacement: "Generated.ApplyPatchUpdateFileOperation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchUpdateFileOperationParam": + replacement: "Generated.ApplyPatchUpdateFileOperationParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ApplyPatchUpdateFileOperationParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApplyPatchUpdateFileOperationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApproximateLocation": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ApproximateLocationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssignedRoleDetails": + replacement: "Generated.AssignedRoleDetails" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantMessageItem": + replacement: "Generated.AssistantMessageItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantMessageItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantMessageItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantObject": + replacement: "Generated.AssistantObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantsApiResponseFormatOption": + replacement: "Generated.AssistantsApiResponseFormatOption" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantsApiResponseFormatOptionEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantsNamedToolChoice": + replacement: "Generated.AssistantsNamedToolChoice" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantsNamedToolChoiceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantSupportedModels": + replacement: "Generated.AssistantSupportedModels" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantTool": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantToolsCode": + replacement: "Generated.AssistantToolsCode" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantToolsCodeType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantToolsFileSearch": + replacement: "Generated.AssistantToolsFileSearch" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantToolsFileSearchType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantToolsFileSearchTypeOnly": + replacement: "Generated.AssistantToolsFileSearchTypeOnly" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantToolsFileSearchTypeOnlyType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AssistantToolsFunction": + replacement: "Generated.AssistantToolsFunction" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AssistantToolsFunctionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Attachment": + replacement: "Generated.Attachment" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AttachmentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AudioResponseFormat": + replacement: "Generated.AudioResponseFormat" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AudioTranscription": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AudioTranscriptionModel": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AuditLog": + replacement: "Generated.AuditLog" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AuditLogActor": + replacement: "Generated.AuditLogActor" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AuditLogActorApiKey": + replacement: "Generated.AuditLogActorApiKey" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AuditLogActorApiKeyType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AuditLogActorServiceAccount": + replacement: "Generated.AuditLogActorServiceAccount" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AuditLogActorSession": + replacement: "Generated.AuditLogActorSession" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AuditLogActorType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AuditLogActorUser": + replacement: "Generated.AuditLogActorUser" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AuditLogEventType": + replacement: "Generated.AuditLogEventType" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#AutoChunkingStrategyRequestParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AutoChunkingStrategyRequestParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#AutomaticThreadTitlingParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Batch": + replacement: "Generated.Batch" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#BatchError": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#BatchFileExpirationAfter": + replacement: "Generated.BatchFileExpirationAfter" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#BatchFileExpirationAfterAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#BatchObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#BatchRequestCounts": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#BatchStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Certificate": + replacement: "Generated.Certificate" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CertificateObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionAllowedTools": + replacement: "Generated.ChatCompletionAllowedTools" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionAllowedToolsChoice": + replacement: "Generated.ChatCompletionAllowedToolsChoice" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionAllowedToolsChoiceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionAllowedToolsMode": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionDeleted": + replacement: "Generated.ChatCompletionDeleted" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionDeletedObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionFunctionCallOption": + replacement: "Generated.ChatCompletionFunctionCallOption" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionFunctions": + replacement: "Generated.ChatCompletionFunctions" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionList": + replacement: "Generated.ChatCompletionList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionListObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionMessageCustomToolCall": + replacement: "Generated.ChatCompletionMessageCustomToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionMessageCustomToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionMessageList": + replacement: "Generated.ChatCompletionMessageList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionMessageListObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionMessageToolCall": + replacement: "Generated.ChatCompletionMessageToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionMessageToolCalls": + replacement: "Generated.ChatCompletionMessageToolCalls" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionMessageToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionNamedToolChoice": + replacement: "Generated.ChatCompletionNamedToolChoice" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionNamedToolChoiceCustom": + replacement: "Generated.ChatCompletionNamedToolChoiceCustom" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionNamedToolChoiceCustomType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionNamedToolChoiceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestAssistantMessage": + replacement: "Generated.ChatCompletionRequestAssistantMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestAssistantMessageContentPart": + replacement: "Generated.ChatCompletionRequestAssistantMessageContentPart" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestAssistantMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestDeveloperMessage": + replacement: "Generated.ChatCompletionRequestDeveloperMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestDeveloperMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestFunctionMessage": + replacement: "Generated.ChatCompletionRequestFunctionMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestFunctionMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessage": + replacement: "Generated.ChatCompletionRequestMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartAudio": + replacement: "Generated.ChatCompletionRequestMessageContentPartAudio" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartAudioInputAudioFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartAudioType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartFile": + replacement: "Generated.ChatCompletionRequestMessageContentPartFile" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartFileType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartImage": + replacement: "Generated.ChatCompletionRequestMessageContentPartImage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartImageImageUrlDetail": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartImageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartRefusal": + replacement: "Generated.ChatCompletionRequestMessageContentPartRefusal" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartRefusalType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartText": + replacement: "Generated.ChatCompletionRequestMessageContentPartText" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestMessageContentPartTextType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestSystemMessage": + replacement: "Generated.ChatCompletionRequestSystemMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestSystemMessageContentPart": + replacement: "Generated.ChatCompletionRequestSystemMessageContentPart" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestSystemMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestToolMessage": + replacement: "Generated.ChatCompletionRequestToolMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestToolMessageContentPart": + replacement: "Generated.ChatCompletionRequestToolMessageContentPart" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestToolMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionRequestUserMessage": + replacement: "Generated.ChatCompletionRequestUserMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestUserMessageContentPart": + replacement: "Generated.ChatCompletionRequestUserMessageContentPart" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionRequestUserMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionResponseMessage": + replacement: "Generated.ChatCompletionResponseMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionResponseMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionStreamOptions": + replacement: "Generated.ChatCompletionStreamOptions" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionTokenLogprob": + replacement: "Generated.ChatCompletionTokenLogprob" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionTool": + replacement: "Generated.ChatCompletionTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionToolChoiceOption": + replacement: "Generated.ChatCompletionToolChoiceOption" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatCompletionToolChoiceOptionEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatCompletionToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatkitConfigurationParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatkitWorkflow": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatkitWorkflowTracing": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatModel": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionAutomaticThreadTitling": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionChatkitConfiguration": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionFileUpload": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionHistory": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionRateLimits": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionResource": + replacement: "Generated.ChatSessionResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChatSessionResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChatSessionStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ChunkingStrategyRequestParam": + replacement: "Generated.ChunkingStrategyRequestParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ChunkingStrategyResponse": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClickButtonType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClickParam": + replacement: "Generated.ClickParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ClickParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Client": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClientError": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClientToolCallItem": + replacement: "Generated.ClientToolCallItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ClientToolCallItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClientToolCallItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClientToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ClosedStatus": + replacement: "Generated.ClosedStatus" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ClosedStatusType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterContainerAuto": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterContainerAutoType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterOutputImage": + replacement: "Generated.CodeInterpreterOutputImage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CodeInterpreterOutputImageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterOutputLogs": + replacement: "Generated.CodeInterpreterOutputLogs" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CodeInterpreterOutputLogsType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterTool": + replacement: "Generated.CodeInterpreterTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CodeInterpreterToolCall": + replacement: "Generated.CodeInterpreterToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CodeInterpreterToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CodeInterpreterToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComparisonFilter": + replacement: "Generated.ComparisonFilter" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComparisonFilterType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComparisonFilterValueItems": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CompleteUploadRequest": + replacement: "Generated.CompleteUploadRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CompletionUsage": + replacement: "Generated.CompletionUsage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CompoundFilter": + replacement: "Generated.CompoundFilter" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CompoundFilterType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerAction": + replacement: "Generated.ComputerAction" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerCallOutputItemParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerCallOutputItemParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerCallSafetyCheckParam": + replacement: "Generated.ComputerCallSafetyCheckParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerEnvironment": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerScreenshotContent": + replacement: "Generated.ComputerScreenshotContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerScreenshotContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerScreenshotImage": + replacement: "Generated.ComputerScreenshotImage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerScreenshotImageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerToolCall": + replacement: "Generated.ComputerToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerToolCallOutputResource": + replacement: "Generated.ComputerToolCallOutputResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerToolCallOutputResourceStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerToolCallOutputResourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ComputerUsePreviewTool": + replacement: "Generated.ComputerUsePreviewTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ComputerUsePreviewToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ContainerFileCitationBody": + replacement: "Generated.ContainerFileCitationBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ContainerFileCitationBodyType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ContainerFileListResource": + replacement: "Generated.ContainerFileListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ContainerFileResource": + replacement: "Generated.ContainerFileResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ContainerListResource": + replacement: "Generated.ContainerListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ContainerMemoryLimit": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ContainerResource": + replacement: "Generated.ContainerResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ContainerResourceExpiresAfterAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Conversation2": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ConversationItem": + replacement: "Generated.ConversationItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ConversationItemList": + replacement: "Generated.ConversationItemList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ConversationParam": + replacement: "Generated.ConversationParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ConversationParam2": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ConversationResource": + replacement: "Generated.ConversationResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ConversationResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CostsResult": + replacement: "Generated.CostsResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CostsResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateAssistantRequest": + replacement: "Generated.CreateAssistantRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateBatchRequest": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateBatchRequestCompletionWindow": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateBatchRequestEndpoint": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateChatCompletionRequest": + replacement: "Generated.CreateChatCompletionRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateChatCompletionRequestAudioFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateChatCompletionRequestFunctionCallEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateChatCompletionRequestPromptCacheRetentionEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateChatCompletionRequestWebSearchOptionsUserLocationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateChatCompletionResponse": + replacement: "Generated.CreateChatCompletionResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateChatCompletionResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateChatSessionBody": + replacement: "Generated.CreateChatSessionBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateCompletionRequest": + replacement: "Generated.CreateCompletionRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateCompletionRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateCompletionResponse": + replacement: "Generated.CreateCompletionResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateCompletionResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateContainerBody": + replacement: "Generated.CreateContainerBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateContainerBodyExpiresAfterAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateContainerFileBody": + replacement: "Generated.CreateContainerFileBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateConversationBody": + replacement: "Generated.CreateConversationBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateConversationItemsParams": + replacement: "Generated.CreateConversationItemsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateConversationItemsRequest": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEmbeddingRequest": + replacement: "Generated.CreateEmbeddingRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateEmbeddingRequestEncodingFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEmbeddingRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEmbeddingResponse": + replacement: "Generated.CreateEmbeddingResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateEmbeddingResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalCompletionsRunDataSource": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalCompletionsRunDataSourceInputMessagesEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalCompletionsRunDataSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalCustomDataSourceConfig": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalCustomDataSourceConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalItem": + replacement: "Generated.CreateEvalItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateEvalJsonlRunDataSource": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalJsonlRunDataSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalLabelModelGrader": + replacement: "Generated.CreateEvalLabelModelGrader" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateEvalLabelModelGraderType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalLogsDataSourceConfig": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalLogsDataSourceConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalRequest": + replacement: "Generated.CreateEvalRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateEvalResponsesRunDataSource": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalResponsesRunDataSourceInputMessagesEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalResponsesRunDataSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalRunRequest": + replacement: "Generated.CreateEvalRunRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateEvalStoredCompletionsDataSourceConfig": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateEvalStoredCompletionsDataSourceConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateFileRequest": + replacement: "Generated.CreateFileRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateFineTuningCheckpointPermissionRequest": + replacement: "Generated.CreateFineTuningCheckpointPermissionRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateFineTuningJobRequest": + replacement: "Generated.CreateFineTuningJobRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateFineTuningJobRequestHyperparametersBatchSizeEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateFineTuningJobRequestHyperparametersLearningRateMultiplierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateFineTuningJobRequestHyperparametersNEpochsEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateFineTuningJobRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateGroupBody": + replacement: "Generated.CreateGroupBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateGroupUserBody": + replacement: "Generated.CreateGroupUserBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateImageEditRequest": + replacement: "Generated.CreateImageEditRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateImageEditRequestBackground": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageEditRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageEditRequestOutputFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageEditRequestQuality": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageEditRequestResponseFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageEditRequestSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequest": + replacement: "Generated.CreateImageRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateImageRequestBackground": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestModeration": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestOutputFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestQuality": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestResponseFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageRequestStyle": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageVariationRequest": + replacement: "Generated.CreateImageVariationRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateImageVariationRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageVariationRequestResponseFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateImageVariationRequestSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateMessageRequest": + replacement: "Generated.CreateMessageRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateMessageRequestRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateModerationRequest": + replacement: "Generated.CreateModerationRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateModerationRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateModerationResponse": + replacement: "Generated.CreateModerationResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateResponse": + replacement: "Generated.CreateResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateResponsePromptCacheRetentionEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateResponseTruncationEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateRunParams": + replacement: "Generated.CreateRunParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateRunRequest": + replacement: "Generated.CreateRunRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateRunRequestToolChoice": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateRunRequestToolChoiceEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateRunRequestTruncationStrategy": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateRunRequestTruncationStrategyEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateSpeechRequest": + replacement: "Generated.CreateSpeechRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateSpeechRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateSpeechRequestResponseFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateSpeechRequestStreamFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateThreadAndRunRequest": + replacement: "Generated.CreateThreadAndRunRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateThreadAndRunRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateThreadAndRunRequestToolChoice": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateThreadAndRunRequestToolChoiceEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateThreadAndRunRequestTruncationStrategy": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateThreadAndRunRequestTruncationStrategyEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateThreadRequest": + replacement: "Generated.CreateThreadRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranscription200": + replacement: "Generated.CreateTranscription200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranscriptionRequest": + replacement: "Generated.CreateTranscriptionRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranscriptionRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateTranscriptionResponseDiarizedJson": + replacement: "Generated.CreateTranscriptionResponseDiarizedJson" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranscriptionResponseDiarizedJsonTask": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateTranscriptionResponseJson": + replacement: "Generated.CreateTranscriptionResponseJson" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranscriptionResponseVerboseJson": + replacement: "Generated.CreateTranscriptionResponseVerboseJson" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranslation200": + replacement: "Generated.CreateTranslation200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranslationRequest": + replacement: "Generated.CreateTranslationRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranslationRequestModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateTranslationRequestResponseFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateTranslationResponseJson": + replacement: "Generated.CreateTranslationResponseJson" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateTranslationResponseVerboseJson": + replacement: "Generated.CreateTranslationResponseVerboseJson" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateUploadRequest": + replacement: "Generated.CreateUploadRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateUploadRequestPurpose": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateVectorStoreFileBatchRequest": + replacement: "Generated.CreateVectorStoreFileBatchRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateVectorStoreFileRequest": + replacement: "Generated.CreateVectorStoreFileRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateVectorStoreRequest": + replacement: "Generated.CreateVectorStoreRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CreateVideoBody": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CreateVideoRemixBody": + replacement: "Generated.CreateVideoRemixBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomGrammarFormatParam": + replacement: "Generated.CustomGrammarFormatParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomGrammarFormatParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomTextFormatParam": + replacement: "Generated.CustomTextFormatParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomTextFormatParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomToolCall": + replacement: "Generated.CustomToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomToolCallOutput": + replacement: "Generated.CustomToolCallOutput" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomToolCallOutputType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomToolChatCompletions": + replacement: "Generated.CustomToolChatCompletions" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomToolChatCompletionsCustomFormatEnumGrammarSyntax": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomToolChatCompletionsCustomFormatEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomToolChatCompletionsType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#CustomToolParam": + replacement: "Generated.CustomToolParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#CustomToolParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteAssistantResponse": + replacement: "Generated.DeleteAssistantResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteAssistantResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteCertificateResponse": + replacement: "Generated.DeleteCertificateResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeletedConversationResource": + replacement: "Generated.DeletedConversationResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeletedConversationResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeletedRoleAssignmentResource": + replacement: "Generated.DeletedRoleAssignmentResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeletedThreadResource": + replacement: "Generated.DeletedThreadResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeletedThreadResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeletedVideoResource": + replacement: "Generated.DeletedVideoResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeletedVideoResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteEval200": + replacement: "Generated.DeleteEval200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteEvalRun200": + replacement: "Generated.DeleteEvalRun200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteFileResponse": + replacement: "Generated.DeleteFileResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteFileResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteFineTuningCheckpointPermissionResponse": + replacement: "Generated.DeleteFineTuningCheckpointPermissionResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteFineTuningCheckpointPermissionResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteMessageResponse": + replacement: "Generated.DeleteMessageResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteMessageResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteModelResponse": + replacement: "Generated.DeleteModelResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteThreadResponse": + replacement: "Generated.DeleteThreadResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteThreadResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteVectorStoreFileResponse": + replacement: "Generated.DeleteVectorStoreFileResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteVectorStoreFileResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DeleteVectorStoreResponse": + replacement: "Generated.DeleteVectorStoreResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DeleteVectorStoreResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DetailEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DoubleClickAction": + replacement: "Generated.DoubleClickAction" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#DoubleClickActionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DownloadFile200": + replacement: "Generated.DownloadFile200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#Drag": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DragPoint": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#DragType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EasyInputMessage": + replacement: "Generated.EasyInputMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EasyInputMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EasyInputMessageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Embedding": + replacement: "Generated.Embedding" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EmbeddingObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Error": + replacement: "Generated.Error" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#Error2": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ErrorResponse": + replacement: "Generated.ErrorResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#Eval": + replacement: "Generated.Eval" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalApiError": + replacement: "Generated.EvalApiError" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalCustomDataSourceConfig": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalCustomDataSourceConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderLabelModel": + replacement: "Generated.EvalGraderLabelModel" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalGraderLabelModelType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderPython": + replacement: "Generated.EvalGraderPython" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalGraderPythonType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderScoreModel": + replacement: "Generated.EvalGraderScoreModel" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalGraderScoreModelType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderStringCheck": + replacement: "Generated.EvalGraderStringCheck" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalGraderStringCheckOperation": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderStringCheckType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderTextSimilarity": + replacement: "Generated.EvalGraderTextSimilarity" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalGraderTextSimilarityEvaluationMetric": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalGraderTextSimilarityType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalItem": + replacement: "Generated.EvalItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalItemContentEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalItemRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalJsonlFileContentSource": + replacement: "Generated.EvalJsonlFileContentSource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalJsonlFileContentSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalJsonlFileIdSource": + replacement: "Generated.EvalJsonlFileIdSource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalJsonlFileIdSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalList": + replacement: "Generated.EvalList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalListObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalLogsDataSourceConfig": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalLogsDataSourceConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalResponsesSource": + replacement: "Generated.EvalResponsesSource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalResponsesSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalRun": + replacement: "Generated.EvalRun" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalRunList": + replacement: "Generated.EvalRunList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalRunListObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalRunObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalRunOutputItem": + replacement: "Generated.EvalRunOutputItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalRunOutputItemList": + replacement: "Generated.EvalRunOutputItemList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalRunOutputItemListObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalRunOutputItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalRunOutputItemResult": + replacement: "Generated.EvalRunOutputItemResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalStoredCompletionsDataSourceConfig": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalStoredCompletionsDataSourceConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#EvalStoredCompletionsSource": + replacement: "Generated.EvalStoredCompletionsSource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#EvalStoredCompletionsSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ExpiresAfterParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ExpiresAfterParamAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileAnnotation": + replacement: "Generated.FileAnnotation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileAnnotationSource": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileAnnotationSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileAnnotationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileCitationBody": + replacement: "Generated.FileCitationBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileCitationBodyType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileExpirationAfter": + replacement: "Generated.FileExpirationAfter" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileExpirationAfterAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FilePath": + replacement: "Generated.FilePath" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FilePathType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FilePurpose": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileSearchRanker": + replacement: "Generated.FileSearchRanker" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileSearchRankingOptions": + replacement: "Generated.FileSearchRankingOptions" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileSearchTool": + replacement: "Generated.FileSearchTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileSearchToolCall": + replacement: "Generated.FileSearchToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FileSearchToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileSearchToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileSearchToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FileUploadParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Filters": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneDPOHyperparameters": + replacement: "Generated.FineTuneDPOHyperparameters" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuneDPOHyperparametersBatchSizeEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneDPOHyperparametersBetaEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneDPOHyperparametersLearningRateMultiplierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneDPOHyperparametersNEpochsEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneDPOMethod": + replacement: "Generated.FineTuneDPOMethod" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuneMethod": + replacement: "Generated.FineTuneMethod" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuneMethodType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparameters": + replacement: "Generated.FineTuneReinforcementHyperparameters" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersBatchSizeEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersComputeMultiplierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersEvalIntervalEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersEvalSamplesEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersLearningRateMultiplierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersNEpochsEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementHyperparametersReasoningEffort": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneReinforcementMethod": + replacement: "Generated.FineTuneReinforcementMethod" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuneSupervisedHyperparameters": + replacement: "Generated.FineTuneSupervisedHyperparameters" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuneSupervisedHyperparametersBatchSizeEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneSupervisedHyperparametersLearningRateMultiplierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneSupervisedHyperparametersNEpochsEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuneSupervisedMethod": + replacement: "Generated.FineTuneSupervisedMethod" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuningCheckpointPermission": + replacement: "Generated.FineTuningCheckpointPermission" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuningCheckpointPermissionObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningIntegration": + replacement: "Generated.FineTuningIntegration" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuningIntegrationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJob": + replacement: "Generated.FineTuningJob" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuningJobCheckpoint": + replacement: "Generated.FineTuningJobCheckpoint" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuningJobCheckpointObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobEvent": + replacement: "Generated.FineTuningJobEvent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FineTuningJobEventLevel": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobEventObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobEventType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobHyperparametersBatchSizeEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobHyperparametersLearningRateMultiplierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobHyperparametersNEpochsEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FineTuningJobStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionAndCustomToolCallOutput": + replacement: "Generated.FunctionAndCustomToolCallOutput" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionCallItemStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionCallOutputItemParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionCallOutputItemParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionObject": + replacement: "Generated.FunctionObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionParameters": + replacement: "Generated.FunctionParameters" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellAction": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellActionParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCall": + replacement: "Generated.FunctionShellCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallItemParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallItemParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallItemStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutput": + replacement: "Generated.FunctionShellCallOutput" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputContent": + replacement: "Generated.FunctionShellCallOutputContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputContentParam": + replacement: "Generated.FunctionShellCallOutputContentParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputExitOutcome": + replacement: "Generated.FunctionShellCallOutputExitOutcome" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputExitOutcomeParam": + replacement: "Generated.FunctionShellCallOutputExitOutcomeParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputExitOutcomeParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputExitOutcomeType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputItemParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputItemParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputOutcomeParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputTimeoutOutcome": + replacement: "Generated.FunctionShellCallOutputTimeoutOutcome" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputTimeoutOutcomeParam": + replacement: "Generated.FunctionShellCallOutputTimeoutOutcomeParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellCallOutputTimeoutOutcomeParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputTimeoutOutcomeType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallOutputType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionShellToolParam": + replacement: "Generated.FunctionShellToolParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionShellToolParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionTool": + replacement: "Generated.FunctionTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionToolCall": + replacement: "Generated.FunctionToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionToolCallOutputResource": + replacement: "Generated.FunctionToolCallOutputResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionToolCallOutputResourceStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionToolCallOutputResourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionToolCallResource": + replacement: "Generated.FunctionToolCallResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#FunctionToolCallResourceStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionToolCallResourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#FunctionToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GetCertificateParams": + replacement: "Generated.GetCertificateParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GetChatCompletionMessagesParams": + replacement: "Generated.GetChatCompletionMessagesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GetChatCompletionMessagesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GetConversationItemParams": + replacement: "Generated.GetConversationItemParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GetEvalRunOutputItemsParams": + replacement: "Generated.GetEvalRunOutputItemsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GetEvalRunOutputItemsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GetEvalRunOutputItemsParamsStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GetEvalRunsParams": + replacement: "Generated.GetEvalRunsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GetEvalRunsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GetEvalRunsParamsStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GetResponseParams": + replacement: "Generated.GetResponseParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GetRunStepParams": + replacement: "Generated.GetRunStepParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GraderLabelModel": + replacement: "Generated.GraderLabelModel" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GraderLabelModelType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderMulti": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderMultiType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderPython": + replacement: "Generated.GraderPython" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GraderPythonType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderScoreModel": + replacement: "Generated.GraderScoreModel" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GraderScoreModelType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderStringCheck": + replacement: "Generated.GraderStringCheck" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GraderStringCheckOperation": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderStringCheckType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderTextSimilarity": + replacement: "Generated.GraderTextSimilarity" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GraderTextSimilarityEvaluationMetric": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GraderTextSimilarityType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GrammarSyntax1": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Group": + replacement: "Generated.Group" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupDeletedResource": + replacement: "Generated.GroupDeletedResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupDeletedResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GroupListResource": + replacement: "Generated.GroupListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupListResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GroupObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GroupResourceWithSuccess": + replacement: "Generated.GroupResourceWithSuccess" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupResponse": + replacement: "Generated.GroupResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupRoleAssignment": + replacement: "Generated.GroupRoleAssignment" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupRoleAssignmentObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GroupUserAssignment": + replacement: "Generated.GroupUserAssignment" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupUserAssignmentObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#GroupUserDeletedResource": + replacement: "Generated.GroupUserDeletedResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#GroupUserDeletedResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#HistoryParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#HybridSearchOptions": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Image": + replacement: "Generated.Image" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ImageDetail": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenInputUsageDetails": + replacement: "Generated.ImageGenInputUsageDetails" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ImageGenTool": + replacement: "Generated.ImageGenTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ImageGenToolBackground": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolCall": + replacement: "Generated.ImageGenToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ImageGenToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolModel": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolModeration": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolOutputFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolQuality": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImageGenUsage": + replacement: "Generated.ImageGenUsage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ImagesResponse": + replacement: "Generated.ImagesResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ImagesResponseBackground": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImagesResponseOutputFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImagesResponseQuality": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ImagesResponseSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#IncludeEnum": + replacement: "Generated.IncludeEnum" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InferenceOptions": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputAudio": + replacement: "Generated.InputAudio" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputAudioInputAudioFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputAudioType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputContent": + replacement: "Generated.InputContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputFidelity": + replacement: "Generated.InputFidelity" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputFileContent": + replacement: "Generated.InputFileContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputFileContentParam": + replacement: "Generated.InputFileContentParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputFileContentParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputFileContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputImageContent": + replacement: "Generated.InputImageContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputImageContentParamAutoParam": + replacement: "Generated.InputImageContentParamAutoParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputImageContentParamAutoParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputImageContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputItem": + replacement: "Generated.InputItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputMessage": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputMessageContentList": + replacement: "Generated.InputMessageContentList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputMessageResource": + replacement: "Generated.InputMessageResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputMessageResourceRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputMessageResourceStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputMessageResourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputMessageStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputMessageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputParam": + replacement: "Generated.InputParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputTextContent": + replacement: "Generated.InputTextContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputTextContentParam": + replacement: "Generated.InputTextContentParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InputTextContentParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InputTextContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Invite": + replacement: "Generated.Invite" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InviteDeleteResponse": + replacement: "Generated.InviteDeleteResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InviteDeleteResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InviteListResponse": + replacement: "Generated.InviteListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InviteListResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InviteObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InviteProjectGroupBody": + replacement: "Generated.InviteProjectGroupBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InviteRequest": + replacement: "Generated.InviteRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#InviteRequestRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InviteRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#InviteStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Item": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ItemReferenceParam": + replacement: "Generated.ItemReferenceParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ItemReferenceParamTypeEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ItemResource": + replacement: "Generated.ItemResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#KeyPressAction": + replacement: "Generated.KeyPressAction" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#KeyPressActionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListAssistantsParams": + replacement: "Generated.ListAssistantsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListAssistantsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListAssistantsResponse": + replacement: "Generated.ListAssistantsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListAuditLogsParams": + replacement: "Generated.ListAuditLogsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListAuditLogsResponse": + replacement: "Generated.ListAuditLogsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListAuditLogsResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListBatchesParams": + replacement: "Generated.ListBatchesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListBatchesResponse": + replacement: "Generated.ListBatchesResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListBatchesResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListCertificatesResponse": + replacement: "Generated.ListCertificatesResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListCertificatesResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListChatCompletionsParams": + replacement: "Generated.ListChatCompletionsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListChatCompletionsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListContainerFilesParams": + replacement: "Generated.ListContainerFilesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListContainerFilesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListContainersParams": + replacement: "Generated.ListContainersParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListContainersParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListConversationItemsParams": + replacement: "Generated.ListConversationItemsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListConversationItemsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListEvalsParams": + replacement: "Generated.ListEvalsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListEvalsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListEvalsParamsOrderBy": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFilesInVectorStoreBatchParams": + replacement: "Generated.ListFilesInVectorStoreBatchParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFilesInVectorStoreBatchParamsFilter": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFilesInVectorStoreBatchParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFilesParams": + replacement: "Generated.ListFilesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFilesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFilesResponse": + replacement: "Generated.ListFilesResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningCheckpointPermissionResponse": + replacement: "Generated.ListFineTuningCheckpointPermissionResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningCheckpointPermissionResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFineTuningCheckpointPermissionsParams": + replacement: "Generated.ListFineTuningCheckpointPermissionsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningCheckpointPermissionsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFineTuningEventsParams": + replacement: "Generated.ListFineTuningEventsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningJobCheckpointsParams": + replacement: "Generated.ListFineTuningJobCheckpointsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningJobCheckpointsResponse": + replacement: "Generated.ListFineTuningJobCheckpointsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningJobCheckpointsResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListFineTuningJobEventsResponse": + replacement: "Generated.ListFineTuningJobEventsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListFineTuningJobEventsResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListGroupRoleAssignmentsParams": + replacement: "Generated.ListGroupRoleAssignmentsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListGroupRoleAssignmentsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListGroupsParams": + replacement: "Generated.ListGroupsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListGroupsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListGroupUsersParams": + replacement: "Generated.ListGroupUsersParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListGroupUsersParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListInputItemsParams": + replacement: "Generated.ListInputItemsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListInputItemsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListInvitesParams": + replacement: "Generated.ListInvitesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListMessagesParams": + replacement: "Generated.ListMessagesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListMessagesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListMessagesResponse": + replacement: "Generated.ListMessagesResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListModelsResponse": + replacement: "Generated.ListModelsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListModelsResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListOrganizationCertificatesParams": + replacement: "Generated.ListOrganizationCertificatesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListOrganizationCertificatesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListPaginatedFineTuningJobsParams": + replacement: "Generated.ListPaginatedFineTuningJobsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListPaginatedFineTuningJobsResponse": + replacement: "Generated.ListPaginatedFineTuningJobsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListPaginatedFineTuningJobsResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListProjectApiKeysParams": + replacement: "Generated.ListProjectApiKeysParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectCertificatesParams": + replacement: "Generated.ListProjectCertificatesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectCertificatesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListProjectGroupRoleAssignmentsParams": + replacement: "Generated.ListProjectGroupRoleAssignmentsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectGroupRoleAssignmentsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListProjectGroupsParams": + replacement: "Generated.ListProjectGroupsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectGroupsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListProjectRateLimitsParams": + replacement: "Generated.ListProjectRateLimitsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectRolesParams": + replacement: "Generated.ListProjectRolesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectRolesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListProjectServiceAccountsParams": + replacement: "Generated.ListProjectServiceAccountsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectsParams": + replacement: "Generated.ListProjectsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectUserRoleAssignmentsParams": + replacement: "Generated.ListProjectUserRoleAssignmentsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListProjectUserRoleAssignmentsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListProjectUsersParams": + replacement: "Generated.ListProjectUsersParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListRolesParams": + replacement: "Generated.ListRolesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListRolesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListRunsParams": + replacement: "Generated.ListRunsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListRunsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListRunsResponse": + replacement: "Generated.ListRunsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListRunStepsParams": + replacement: "Generated.ListRunStepsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListRunStepsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListRunStepsResponse": + replacement: "Generated.ListRunStepsResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListThreadItemsMethodParams": + replacement: "Generated.ListThreadItemsMethodParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListThreadsMethodParams": + replacement: "Generated.ListThreadsMethodParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListUserRoleAssignmentsParams": + replacement: "Generated.ListUserRoleAssignmentsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListUserRoleAssignmentsParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListUsersParams": + replacement: "Generated.ListUsersParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListVectorStoreFilesParams": + replacement: "Generated.ListVectorStoreFilesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListVectorStoreFilesParamsFilter": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListVectorStoreFilesParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListVectorStoreFilesResponse": + replacement: "Generated.ListVectorStoreFilesResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListVectorStoresParams": + replacement: "Generated.ListVectorStoresParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListVectorStoresParamsOrder": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ListVectorStoresResponse": + replacement: "Generated.ListVectorStoresResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ListVideosParams": + replacement: "Generated.ListVideosParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#LocalShellCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LocalShellExecAction": + replacement: "Generated.LocalShellExecAction" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#LocalShellExecActionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LocalShellToolCall": + replacement: "Generated.LocalShellToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#LocalShellToolCallOutput": + replacement: "Generated.LocalShellToolCallOutput" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#LocalShellToolCallOutputStatusEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LocalShellToolCallOutputType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LocalShellToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LocalShellToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LocalShellToolParam": + replacement: "Generated.LocalShellToolParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#LocalShellToolParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LockedStatus": + replacement: "Generated.LockedStatus" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#LockedStatusType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#LogProb": + replacement: "Generated.LogProb" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#make": + replacement: "Generated.make" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPApprovalRequest": + replacement: "Generated.MCPApprovalRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPApprovalRequestType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPApprovalResponse": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPApprovalResponseResource": + replacement: "Generated.MCPApprovalResponseResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPApprovalResponseResourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPApprovalResponseType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPListTools": + replacement: "Generated.MCPListTools" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPListToolsTool": + replacement: "Generated.MCPListToolsTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPListToolsType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPTool": + replacement: "Generated.MCPTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPToolCall": + replacement: "Generated.MCPToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPToolConnectorId": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPToolFilter": + replacement: "Generated.MCPToolFilter" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MCPToolRequireApprovalEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MCPToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Message": + replacement: "Generated.Message" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContent": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentImageFileObject": + replacement: "Generated.MessageContentImageFileObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContentImageFileObjectImageFileDetail": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentImageFileObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentImageUrlObject": + replacement: "Generated.MessageContentImageUrlObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContentImageUrlObjectImageUrlDetail": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentImageUrlObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentRefusalObject": + replacement: "Generated.MessageContentRefusalObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContentRefusalObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentTextAnnotationsFileCitationObject": + replacement: "Generated.MessageContentTextAnnotationsFileCitationObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContentTextAnnotationsFileCitationObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentTextAnnotationsFilePathObject": + replacement: "Generated.MessageContentTextAnnotationsFilePathObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContentTextAnnotationsFilePathObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageContentTextObject": + replacement: "Generated.MessageContentTextObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageContentTextObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageObject": + replacement: "Generated.MessageObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageObjectIncompleteDetailsEnumReason": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageObjectRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageObjectStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageRequestContentTextObject": + replacement: "Generated.MessageRequestContentTextObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#MessageRequestContentTextObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MessageStatus": + replacement: "OpenAiSchema.MessageStatus" + note: "Use the focused v4 OpenAiSchema definition; the old generated export was removed when the OpenAI specification client was regenerated." +"@effect/ai-openai/Generated#MessageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Metadata": + replacement: "Generated.Metadata" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#Model": + replacement: "Generated.Model" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModelIdsResponses": + replacement: "Generated.ModelIdsResponses" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModelIdsResponsesEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ModelIdsShared": + replacement: "Generated.ModelIdsShared" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModelObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ModerationImageURLInput": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ModerationImageURLInputType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ModerationTextInput": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ModerationTextInputType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ModifyAssistantRequest": + replacement: "Generated.ModifyAssistantRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModifyCertificateRequest": + replacement: "Generated.ModifyCertificateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModifyMessageRequest": + replacement: "Generated.ModifyMessageRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModifyRunRequest": + replacement: "Generated.ModifyRunRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ModifyThreadRequest": + replacement: "Generated.ModifyThreadRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#Move": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#MoveType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#NoiseReductionType": + replacement: "Generated.NoiseReductionType" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OpenAIFile": + replacement: "Generated.OpenAIFile" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OpenAIFileObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OpenAIFilePurpose": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OpenAIFileStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OrderEnum": + replacement: "Generated.OrderEnum" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OtherChunkingStrategyResponseParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OtherChunkingStrategyResponseParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OutputItem": + replacement: "Generated.OutputItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OutputMessage": + replacement: "Generated.OutputMessage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OutputMessageContent": + replacement: "Generated.OutputMessageContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OutputMessageRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OutputMessageStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OutputMessageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#OutputTextContent": + replacement: "Generated.OutputTextContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#OutputTextContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ParallelToolCalls": + replacement: "Generated.ParallelToolCalls" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PartialImages": + replacement: "Generated.PartialImages" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PredictionContent": + replacement: "Generated.PredictionContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PredictionContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Project": + replacement: "Generated.Project" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectApiKey": + replacement: "Generated.ProjectApiKey" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectApiKeyDeleteResponse": + replacement: "Generated.ProjectApiKeyDeleteResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectApiKeyDeleteResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectApiKeyListResponse": + replacement: "Generated.ProjectApiKeyListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectApiKeyListResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectApiKeyObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectApiKeyOwnerType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectCreateRequest": + replacement: "Generated.ProjectCreateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectCreateRequestGeography": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectGroup": + replacement: "Generated.ProjectGroup" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectGroupDeletedResource": + replacement: "Generated.ProjectGroupDeletedResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectGroupDeletedResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectGroupListResource": + replacement: "Generated.ProjectGroupListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectGroupListResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectGroupObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectListResponse": + replacement: "Generated.ProjectListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectListResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectRateLimit": + replacement: "Generated.ProjectRateLimit" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectRateLimitListResponse": + replacement: "Generated.ProjectRateLimitListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectRateLimitListResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectRateLimitObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectRateLimitUpdateRequest": + replacement: "Generated.ProjectRateLimitUpdateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccount": + replacement: "Generated.ProjectServiceAccount" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccountApiKey": + replacement: "Generated.ProjectServiceAccountApiKey" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccountApiKeyObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectServiceAccountCreateRequest": + replacement: "Generated.ProjectServiceAccountCreateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccountCreateResponse": + replacement: "Generated.ProjectServiceAccountCreateResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccountCreateResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectServiceAccountCreateResponseRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectServiceAccountDeleteResponse": + replacement: "Generated.ProjectServiceAccountDeleteResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccountDeleteResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectServiceAccountListResponse": + replacement: "Generated.ProjectServiceAccountListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectServiceAccountListResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectServiceAccountObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectServiceAccountRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectUpdateRequest": + replacement: "Generated.ProjectUpdateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectUser": + replacement: "Generated.ProjectUser" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectUserCreateRequest": + replacement: "Generated.ProjectUserCreateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectUserCreateRequestRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectUserDeleteResponse": + replacement: "Generated.ProjectUserDeleteResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectUserDeleteResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectUserListResponse": + replacement: "Generated.ProjectUserListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectUserObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectUserRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ProjectUserUpdateRequest": + replacement: "Generated.ProjectUserUpdateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ProjectUserUpdateRequestRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Prompt": + replacement: "Generated.Prompt" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PublicAssignOrganizationGroupRoleBody": + replacement: "Generated.PublicAssignOrganizationGroupRoleBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PublicCreateOrganizationRoleBody": + replacement: "Generated.PublicCreateOrganizationRoleBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PublicRoleListResource": + replacement: "Generated.PublicRoleListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#PublicRoleListResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#PublicUpdateOrganizationRoleBody": + replacement: "Generated.PublicUpdateOrganizationRoleBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RankerVersionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RankingOptions": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RateLimitsParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeAudioFormats": + replacement: "Generated.RealtimeAudioFormats" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeAudioFormatsEnumRate": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeAudioFormatsEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeCallCreateRequest": + replacement: "Generated.RealtimeCallCreateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeCallReferRequest": + replacement: "Generated.RealtimeCallReferRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeCallRejectRequest": + replacement: "Generated.RealtimeCallRejectRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeCreateClientSecretRequest": + replacement: "Generated.RealtimeCreateClientSecretRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeCreateClientSecretRequestExpiresAfterAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeCreateClientSecretResponse": + replacement: "Generated.RealtimeCreateClientSecretResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeFunctionTool": + replacement: "Generated.RealtimeFunctionTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeFunctionToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequest": + replacement: "Generated.RealtimeSessionCreateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestGA": + replacement: "Generated.RealtimeSessionCreateRequestGA" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestGAMaxOutputTokensEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestGAModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestGATracingEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestGAType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestMaxResponseOutputTokensEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateRequestTracingEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponse": + replacement: "Generated.RealtimeSessionCreateResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseGA": + replacement: "Generated.RealtimeSessionCreateResponseGA" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseGAMaxOutputTokensEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseGAModelEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseGATracingEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseGAType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseMaxOutputTokensEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeSessionCreateResponseTracingEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateRequest": + replacement: "Generated.RealtimeTranscriptionSessionCreateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateRequestGA": + replacement: "Generated.RealtimeTranscriptionSessionCreateRequestGA" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateRequestGAType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateRequestInputAudioFormat": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateRequestTurnDetectionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateResponse": + replacement: "Generated.RealtimeTranscriptionSessionCreateResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateResponseGA": + replacement: "Generated.RealtimeTranscriptionSessionCreateResponseGA" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeTranscriptionSessionCreateResponseGAType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTruncation": + replacement: "Generated.RealtimeTruncation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeTruncationEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTruncationEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RealtimeTurnDetection": + replacement: "Generated.RealtimeTurnDetection" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RealtimeTurnDetectionEnumEagerness": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Reasoning": + replacement: "Generated.Reasoning" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ReasoningEffort": + replacement: "Generated.ReasoningEffort" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ReasoningEffortEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ReasoningGenerateSummaryEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ReasoningItem": + replacement: "Generated.ReasoningItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ReasoningItemStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ReasoningItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ReasoningSummaryEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ReasoningTextContent": + replacement: "Generated.ReasoningTextContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ReasoningTextContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RefusalContent": + replacement: "Generated.RefusalContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RefusalContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Response": + replacement: "Generated.Response" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseError": + replacement: "Generated.ResponseError" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseErrorCode": + replacement: "Generated.ResponseErrorCode" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseFormatJsonObject": + replacement: "Generated.ResponseFormatJsonObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseFormatJsonObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseFormatJsonSchema": + replacement: "Generated.ResponseFormatJsonSchema" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseFormatJsonSchemaSchema": + replacement: "Generated.ResponseFormatJsonSchemaSchema" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseFormatJsonSchemaType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseFormatText": + replacement: "Generated.ResponseFormatText" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseFormatTextType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseIncompleteDetailsEnumReason": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseItemList": + replacement: "Generated.ResponseItemList" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseModalities": + replacement: "Generated.ResponseModalities" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseOutputText": + replacement: "Generated.ResponseOutputText" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseOutputTextType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponsePromptCacheRetentionEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponsePromptVariables": + replacement: "Generated.ResponsePromptVariables" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseStreamOptions": + replacement: "Generated.ResponseStreamOptions" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseTextParam": + replacement: "Generated.ResponseTextParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ResponseTruncationEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ResponseUsage": + replacement: "Generated.ResponseUsage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RetrieveVideoContent200": + replacement: "Generated.RetrieveVideoContent200" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RetrieveVideoContentParams": + replacement: "Generated.RetrieveVideoContentParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#Role": + replacement: "Generated.Role" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RoleDeletedResource": + replacement: "Generated.RoleDeletedResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RoleDeletedResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RoleListResource": + replacement: "Generated.RoleListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RoleListResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RoleObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunCompletionUsage": + replacement: "Generated.RunCompletionUsage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunGraderRequest": + replacement: "Generated.RunGraderRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunGraderResponse": + replacement: "Generated.RunGraderResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunObject": + replacement: "Generated.RunObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunObjectIncompleteDetailsReason": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectLastErrorCode": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectRequiredActionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectToolChoice": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectToolChoiceEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectTruncationStrategy": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunObjectTruncationStrategyEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepCompletionUsage": + replacement: "Generated.RunStepCompletionUsage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepDetailsMessageCreationObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsMessageCreationObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCall": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsCodeObject": + replacement: "Generated.RunStepDetailsToolCallsCodeObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsCodeObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsCodeOutputImageObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsCodeOutputImageObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsCodeOutputLogsObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsCodeOutputLogsObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsFileSearchObject": + replacement: "Generated.RunStepDetailsToolCallsFileSearchObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsFileSearchObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsFileSearchRankingOptionsObject": + replacement: "Generated.RunStepDetailsToolCallsFileSearchRankingOptionsObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsFileSearchResultObject": + replacement: "Generated.RunStepDetailsToolCallsFileSearchResultObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsFunctionObject": + replacement: "Generated.RunStepDetailsToolCallsFunctionObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsFunctionObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepDetailsToolCallsObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepObject": + replacement: "Generated.RunStepObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunStepObjectLastErrorEnumCode": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepObjectStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunStepObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#RunToolCallObject": + replacement: "Generated.RunToolCallObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#RunToolCallObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Screenshot": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ScreenshotType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Scroll": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ScrollType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#SearchContextSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ServiceTier": + replacement: "Generated.ServiceTier" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ServiceTierEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#SpecificApplyPatchParam": + replacement: "Generated.SpecificApplyPatchParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#SpecificApplyPatchParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#SpecificFunctionShellParam": + replacement: "Generated.SpecificFunctionShellParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#SpecificFunctionShellParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#StaticChunkingStrategy": + replacement: "Generated.StaticChunkingStrategy" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#StaticChunkingStrategyRequestParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#StaticChunkingStrategyRequestParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#StaticChunkingStrategyResponseParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#StaticChunkingStrategyResponseParamType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#StopConfiguration": + replacement: "Generated.StopConfiguration" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#SubmitToolOutputsRunRequest": + replacement: "Generated.SubmitToolOutputsRunRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#SummaryTextContent": + replacement: "Generated.SummaryTextContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#SummaryTextContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#SummaryType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TaskGroupItem": + replacement: "Generated.TaskGroupItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TaskGroupItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TaskGroupItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TaskGroupTask": + replacement: "Generated.TaskGroupTask" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TaskItem": + replacement: "Generated.TaskItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TaskItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TaskItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TaskType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TextAnnotation": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TextContent": + replacement: "Generated.TextContent" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TextContentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TextResponseFormatConfiguration": + replacement: "Generated.TextResponseFormatConfiguration" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TextResponseFormatJsonSchema": + replacement: "Generated.TextResponseFormatJsonSchema" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TextResponseFormatJsonSchemaType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ThreadItem": + replacement: "Generated.ThreadItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ThreadItemListResource": + replacement: "Generated.ThreadItemListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ThreadListResource": + replacement: "Generated.ThreadListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ThreadObject": + replacement: "Generated.ThreadObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ThreadObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ThreadResource": + replacement: "Generated.ThreadResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ThreadResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToggleCertificatesRequest": + replacement: "Generated.ToggleCertificatesRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TokenCountsBody": + replacement: "Generated.TokenCountsBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TokenCountsResource": + replacement: "Generated.TokenCountsResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TokenCountsResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Tool": + replacement: "Generated.Tool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoice": + replacement: "OpenAiSchema.ToolChoice" + note: "Use the focused v4 OpenAiSchema definition; the old generated export was removed when the OpenAI specification client was regenerated." +"@effect/ai-openai/Generated#ToolChoiceAllowed": + replacement: "Generated.ToolChoiceAllowed" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceAllowedMode": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToolChoiceAllowedType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToolChoiceCustom": + replacement: "Generated.ToolChoiceCustom" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceCustomType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToolChoiceFunction": + replacement: "Generated.ToolChoiceFunction" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceFunctionType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToolChoiceMCP": + replacement: "Generated.ToolChoiceMCP" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceMCPType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToolChoiceOptions": + replacement: "Generated.ToolChoiceOptions" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceParam": + replacement: "Generated.ToolChoiceParam" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceTypes": + replacement: "Generated.ToolChoiceTypes" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ToolChoiceTypesType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ToolsArray": + replacement: "Generated.ToolsArray" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TopLogProb": + replacement: "Generated.TopLogProb" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptionChunkingStrategy": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TranscriptionChunkingStrategyEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TranscriptionDiarizedSegment": + replacement: "Generated.TranscriptionDiarizedSegment" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptionDiarizedSegmentType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TranscriptionInclude": + replacement: "Generated.TranscriptionInclude" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptionSegment": + replacement: "Generated.TranscriptionSegment" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptionWord": + replacement: "Generated.TranscriptionWord" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptTextUsageDuration": + replacement: "Generated.TranscriptTextUsageDuration" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptTextUsageDurationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TranscriptTextUsageTokens": + replacement: "Generated.TranscriptTextUsageTokens" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#TranscriptTextUsageTokensType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TruncationEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Type": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#TypeType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UpdateChatCompletionRequest": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UpdateConversationBody": + replacement: "Generated.UpdateConversationBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UpdateEvalRequest": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UpdateGroupBody": + replacement: "Generated.UpdateGroupBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UpdateVectorStoreFileAttributesRequest": + replacement: "Generated.UpdateVectorStoreFileAttributesRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UpdateVectorStoreRequest": + replacement: "Generated.UpdateVectorStoreRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UpdateVectorStoreRequestExpiresAfter": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UpdateVectorStoreRequestExpiresAfterEnumAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Upload": + replacement: "Generated.Upload" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UploadCertificateRequest": + replacement: "Generated.UploadCertificateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UploadFile": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UploadFileEnumObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UploadFileEnumPurpose": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UploadFileEnumStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UploadObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UploadPart": + replacement: "Generated.UploadPart" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UploadPartObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UploadStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UrlAnnotation": + replacement: "Generated.UrlAnnotation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UrlAnnotationSource": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UrlAnnotationSourceType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UrlAnnotationType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UrlCitationBody": + replacement: "Generated.UrlCitationBody" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UrlCitationBodyType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageAudioSpeechesParams": + replacement: "Generated.UsageAudioSpeechesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageAudioSpeechesParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageAudioSpeechesResult": + replacement: "Generated.UsageAudioSpeechesResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageAudioSpeechesResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageAudioTranscriptionsParams": + replacement: "Generated.UsageAudioTranscriptionsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageAudioTranscriptionsParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageAudioTranscriptionsResult": + replacement: "Generated.UsageAudioTranscriptionsResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageAudioTranscriptionsResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageCodeInterpreterSessionsParams": + replacement: "Generated.UsageCodeInterpreterSessionsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageCodeInterpreterSessionsParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageCodeInterpreterSessionsResult": + replacement: "Generated.UsageCodeInterpreterSessionsResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageCodeInterpreterSessionsResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageCompletionsParams": + replacement: "Generated.UsageCompletionsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageCompletionsParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageCompletionsResult": + replacement: "Generated.UsageCompletionsResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageCompletionsResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageCostsParams": + replacement: "Generated.UsageCostsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageCostsParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageEmbeddingsParams": + replacement: "Generated.UsageEmbeddingsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageEmbeddingsParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageEmbeddingsResult": + replacement: "Generated.UsageEmbeddingsResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageEmbeddingsResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageImagesParams": + replacement: "Generated.UsageImagesParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageImagesParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageImagesResult": + replacement: "Generated.UsageImagesResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageImagesResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageModerationsParams": + replacement: "Generated.UsageModerationsParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageModerationsParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageModerationsResult": + replacement: "Generated.UsageModerationsResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageModerationsResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageResponse": + replacement: "Generated.UsageResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageTimeBucket": + replacement: "Generated.UsageTimeBucket" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageTimeBucketObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageVectorStoresParams": + replacement: "Generated.UsageVectorStoresParams" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageVectorStoresParamsBucketWidth": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UsageVectorStoresResult": + replacement: "Generated.UsageVectorStoresResult" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UsageVectorStoresResultObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#User": + replacement: "Generated.User" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserDeleteResponse": + replacement: "Generated.UserDeleteResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserDeleteResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserListResource": + replacement: "Generated.UserListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserListResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserListResponse": + replacement: "Generated.UserListResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserListResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserMessageInputText": + replacement: "Generated.UserMessageInputText" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserMessageInputTextType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserMessageItem": + replacement: "Generated.UserMessageItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserMessageItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserMessageItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserMessageQuotedText": + replacement: "Generated.UserMessageQuotedText" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserMessageQuotedTextType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserRoleAssignment": + replacement: "Generated.UserRoleAssignment" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserRoleAssignmentObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#UserRoleUpdateRequest": + replacement: "Generated.UserRoleUpdateRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#UserRoleUpdateRequestRole": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VadConfig": + replacement: "Generated.VadConfig" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VadConfigType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#ValidateGraderRequest": + replacement: "Generated.ValidateGraderRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#ValidateGraderResponse": + replacement: "Generated.ValidateGraderResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreExpirationAfter": + replacement: "Generated.VectorStoreExpirationAfter" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreExpirationAfterAnchor": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreFileAttributes": + replacement: "Generated.VectorStoreFileAttributes" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreFileBatchObject": + replacement: "Generated.VectorStoreFileBatchObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreFileBatchObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreFileBatchObjectStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreFileContentResponse": + replacement: "Generated.VectorStoreFileContentResponse" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreFileContentResponseObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreFileObject": + replacement: "Generated.VectorStoreFileObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreFileObjectLastErrorEnumCode": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreFileObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreFileObjectStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreObject": + replacement: "Generated.VectorStoreObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreObjectObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreObjectStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreSearchRequest": + replacement: "Generated.VectorStoreSearchRequest" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreSearchRequestRankingOptionsRanker": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreSearchResultContentObject": + replacement: "Generated.VectorStoreSearchResultContentObject" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreSearchResultContentObjectType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VectorStoreSearchResultItem": + replacement: "Generated.VectorStoreSearchResultItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreSearchResultsPage": + replacement: "Generated.VectorStoreSearchResultsPage" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VectorStoreSearchResultsPageObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Verbosity": + replacement: "Generated.Verbosity" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VerbosityEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VideoContentVariant": + replacement: "Generated.VideoContentVariant" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VideoListResource": + replacement: "Generated.VideoListResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VideoModel": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VideoResource": + replacement: "Generated.VideoResource" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VideoResourceObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VideoSeconds": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VideoSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VideoStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#VoiceIdsShared": + replacement: "Generated.VoiceIdsShared" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#VoiceIdsSharedEnum": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#Wait": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WaitType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchActionFind": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchActionFindType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchActionOpenPage": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchActionOpenPageType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchActionSearch": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchActionSearchType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchApproximateLocation": + replacement: "Generated.WebSearchApproximateLocation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WebSearchApproximateLocationEnumType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchContextSize": + replacement: "Generated.WebSearchContextSize" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WebSearchLocation": + replacement: "Generated.WebSearchLocation" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WebSearchPreviewTool": + replacement: "Generated.WebSearchPreviewTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WebSearchPreviewToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchTool": + replacement: "Generated.WebSearchTool" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WebSearchToolCall": + replacement: "Generated.WebSearchToolCall" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WebSearchToolCallStatus": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchToolCallType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchToolSearchContextSize": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WebSearchToolType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WidgetMessageItem": + replacement: "Generated.WidgetMessageItem" + note: "Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openai/Generated#WidgetMessageItemObject": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WidgetMessageItemType": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WorkflowParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openai/Generated#WorkflowTracingParam": + replacement: "none" + note: "Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper." diff --git a/migration/annotations/effect__ai-openai__OpenAiClient.yaml b/migration/annotations/effect__ai-openai__OpenAiClient.yaml new file mode 100644 index 000000000..2395a07f4 --- /dev/null +++ b/migration/annotations/effect__ai-openai__OpenAiClient.yaml @@ -0,0 +1,162 @@ +"@effect/ai-openai/OpenAiClient#LogProbs": + replacement: "Generated.LogProb" + note: "The client-local log-probability schema moved to the regenerated v4 OpenAI schema surface and changed shape." +"@effect/ai-openai/OpenAiClient#ResponseCodeInterpreterCallCodeDeltaEvent": + replacement: "Generated.ResponseCodeInterpreterCallCodeDeltaEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCodeInterpreterCallCodeDoneEvent": + replacement: "Generated.ResponseCodeInterpreterCallCodeDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCodeInterpreterCallCompletedEvent": + replacement: "Generated.ResponseCodeInterpreterCallCompletedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCodeInterpreterCallInProgressEvent": + replacement: "Generated.ResponseCodeInterpreterCallInProgressEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCodeInterpreterCallInterpretingEvent": + replacement: "Generated.ResponseCodeInterpreterCallInterpretingEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCompletedEvent": + replacement: "Generated.ResponseCompletedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseContentPartAddedEvent": + replacement: "Generated.ResponseContentPartAddedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseContentPartDoneEvent": + replacement: "Generated.ResponseContentPartDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCreatedEvent": + replacement: "Generated.ResponseCreatedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCustomToolCallInputDeltaEvent": + replacement: "Generated.ResponseCustomToolCallInputDeltaEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseCustomToolCallInputDoneEvent": + replacement: "Generated.ResponseCustomToolCallInputDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseErrorEvent": + replacement: "Generated.ResponseErrorEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseFailedEvent": + replacement: "Generated.ResponseFailedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseFileSearchCallCompletedEvent": + replacement: "Generated.ResponseFileSearchCallCompletedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseFileSearchCallInProgressEvent": + replacement: "Generated.ResponseFileSearchCallInProgressEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseFileSearchCallSearchingEvent": + replacement: "Generated.ResponseFileSearchCallSearchingEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseFunctionCallArgumentsDeltaEvent": + replacement: "Generated.ResponseFunctionCallArgumentsDeltaEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseFunctionCallArgumentsDoneEvent": + replacement: "Generated.ResponseFunctionCallArgumentsDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseImageGenerationCallCompletedEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseImageGenerationCallGeneratingEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseImageGenerationCallInProgressEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseImageGenerationCallPartialImageEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseIncompleteEvent": + replacement: "Generated.ResponseIncompleteEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseInProgressEvent": + replacement: "Generated.ResponseInProgressEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseMcpCallArgumentsDeltaEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpCallArgumentsDoneEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpCallCompletedEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpCallFailedEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpCallInProgressEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpListToolsCompletedEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpListToolsFailedEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseMcpListToolsInProgressEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseOutputItemAddedEvent": + replacement: "Generated.ResponseOutputItemAddedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseOutputItemDoneEvent": + replacement: "Generated.ResponseOutputItemDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseOutputTextAnnotationAddedEvent": + replacement: "Generated.ResponseOutputTextAnnotationAddedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseOutputTextDeltaEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseOutputTextDoneEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator." +"@effect/ai-openai/OpenAiClient#ResponseQueuedEvent": + replacement: "Generated.ResponseQueuedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseReasoningSummaryPartAddedEvent": + replacement: "Generated.ResponseReasoningSummaryPartAddedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseReasoningSummaryPartDoneEvent": + replacement: "Generated.ResponseReasoningSummaryPartDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseReasoningSummaryTextDeltaEvent": + replacement: "Generated.ResponseReasoningSummaryTextDeltaEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseReasoningSummaryTextDoneEvent": + replacement: "Generated.ResponseReasoningSummaryTextDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseReasoningTextDeltaEvent": + replacement: "Generated.ResponseReasoningTextDeltaEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseReasoningTextDoneEvent": + replacement: "Generated.ResponseReasoningTextDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseRefusalDeltaEvent": + replacement: "Generated.ResponseRefusalDeltaEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseRefusalDoneEvent": + replacement: "Generated.ResponseRefusalDoneEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseStreamEvent": + replacement: "OpenAiClient.ResponseStreamEvent" + note: "Still exported in v4; adapt to the rewritten Responses API client and its revised schema and error types." +"@effect/ai-openai/OpenAiClient#ResponseWebSearchCallCompletedEvent": + replacement: "Generated.ResponseWebSearchCallCompletedEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseWebSearchCallInProgressEvent": + replacement: "Generated.ResponseWebSearchCallInProgressEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#ResponseWebSearchCallSearchingEvent": + replacement: "Generated.ResponseWebSearchCallSearchingEvent" + note: "The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape." +"@effect/ai-openai/OpenAiClient#Service": + replacement: "OpenAiClient.Service" + note: "Still exported in v4; adapt to the rewritten Responses API client and its revised schema and error types." +"@effect/ai-openai/OpenAiClient#StreamCompletionRequest": + replacement: "OpenAiSchema.CreateResponse.Encoded" + note: "The chat-completions request alias was removed; the v4 client uses the Responses API, with streaming inferred by OpenAiClient.createResponseStream." +"@effect/ai-openai/OpenAiClient#SummaryPart": + replacement: "OpenAiSchema.SummaryTextContent" + note: "The client-local reasoning summary schema moved to the focused v4 OpenAiSchema module." diff --git a/migration/annotations/effect__ai-openai__OpenAiConfig.yaml b/migration/annotations/effect__ai-openai__OpenAiConfig.yaml new file mode 100644 index 000000000..b9a58fd0e --- /dev/null +++ b/migration/annotations/effect__ai-openai__OpenAiConfig.yaml @@ -0,0 +1,6 @@ +"@effect/ai-openai/OpenAiConfig#OpenAiConfig": + replacement: "OpenAiConfig.OpenAiConfig" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiConfig#OpenAiConfig.Service": + replacement: "OpenAiConfig.OpenAiConfig.Service" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." diff --git a/migration/annotations/effect__ai-openai__OpenAiEmbeddingModel.yaml b/migration/annotations/effect__ai-openai__OpenAiEmbeddingModel.yaml new file mode 100644 index 000000000..7152df239 --- /dev/null +++ b/migration/annotations/effect__ai-openai__OpenAiEmbeddingModel.yaml @@ -0,0 +1,24 @@ +"@effect/ai-openai/OpenAiEmbeddingModel#Config": + replacement: "OpenAiEmbeddingModel.Config" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiEmbeddingModel#Config.Batched": + replacement: "OpenAiEmbeddingModel.Config.Service" + note: "Batch-mode configuration was removed; use the unified embedding config and constructor." +"@effect/ai-openai/OpenAiEmbeddingModel#Config.DataLoader": + replacement: "OpenAiEmbeddingModel.Config.Service" + note: "Data-loader configuration was removed; use the unified embedding config and constructor." +"@effect/ai-openai/OpenAiEmbeddingModel#Config.Service": + replacement: "OpenAiEmbeddingModel.Config.Service" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiEmbeddingModel#layerBatched": + replacement: "OpenAiEmbeddingModel.layer" + note: "The batched and data-loader layers were replaced by one embedding layer; pass the model and request config explicitly." +"@effect/ai-openai/OpenAiEmbeddingModel#layerDataLoader": + replacement: "OpenAiEmbeddingModel.layer" + note: "The batched and data-loader layers were replaced by one embedding layer; pass the model and request config explicitly." +"@effect/ai-openai/OpenAiEmbeddingModel#makeDataLoader": + replacement: "OpenAiEmbeddingModel.make" + note: "The dedicated data-loader constructor was removed; use the unified v4 embedding service constructor." +"@effect/ai-openai/OpenAiEmbeddingModel#Model": + replacement: "OpenAiEmbeddingModel.Model" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." diff --git a/migration/annotations/effect__ai-openai__OpenAiLanguageModel.yaml b/migration/annotations/effect__ai-openai__OpenAiLanguageModel.yaml new file mode 100644 index 000000000..21af9e0d6 --- /dev/null +++ b/migration/annotations/effect__ai-openai__OpenAiLanguageModel.yaml @@ -0,0 +1,21 @@ +"@effect/ai-openai/OpenAiLanguageModel#Config": + replacement: "OpenAiLanguageModel.Config" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiLanguageModel#Config.Service": + replacement: "OpenAiLanguageModel.Config.Service" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiLanguageModel#layerWithTokenizer": + replacement: "OpenAiLanguageModel.layer" + note: "The tokenizer-combining layer was removed; provide the language model and any Tokenizer service separately." +"@effect/ai-openai/OpenAiLanguageModel#Model": + replacement: "OpenAiLanguageModel.Model" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiLanguageModel#modelWithTokenizer": + replacement: "OpenAiLanguageModel.model" + note: "The tokenizer-combining model was removed; use the v4 model descriptor and provide any Tokenizer service separately." +"@effect/ai-openai/OpenAiLanguageModel#ProviderMetadata": + replacement: "Prompt.ProviderOptions / Response.ProviderMetadata" + note: "The OpenAI metadata service wrapper was removed; v4 declares OpenAI-specific fields directly on Prompt and Response provider metadata." +"@effect/ai-openai/OpenAiLanguageModel#ProviderMetadata.Service": + replacement: "Prompt.ProviderOptions / Response.ProviderMetadata" + note: "The OpenAI metadata service wrapper was removed; v4 declares OpenAI-specific fields directly on Prompt and Response provider metadata." diff --git a/migration/annotations/effect__ai-openai__OpenAiTelemetry.yaml b/migration/annotations/effect__ai-openai__OpenAiTelemetry.yaml new file mode 100644 index 000000000..52fb66673 --- /dev/null +++ b/migration/annotations/effect__ai-openai__OpenAiTelemetry.yaml @@ -0,0 +1,9 @@ +"@effect/ai-openai/OpenAiTelemetry#addGenAIAnnotations": + replacement: "OpenAiTelemetry.addGenAIAnnotations" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiTelemetry#AllAttributes": + replacement: "OpenAiTelemetry.AllAttributes" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." +"@effect/ai-openai/OpenAiTelemetry#OpenAiTelemetryAttributeOptions": + replacement: "OpenAiTelemetry.OpenAiTelemetryAttributeOptions" + note: "Still exported in v4; update imports and adapt to the revised v4 service and schema types." diff --git a/migration/annotations/effect__ai-openai__OpenAiTokenizer.yaml b/migration/annotations/effect__ai-openai__OpenAiTokenizer.yaml new file mode 100644 index 000000000..9c459de0c --- /dev/null +++ b/migration/annotations/effect__ai-openai__OpenAiTokenizer.yaml @@ -0,0 +1,6 @@ +"@effect/ai-openai/OpenAiTokenizer#layer": + replacement: "Tokenizer.make" + note: "The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using gpt-tokenizer if equivalent OpenAI counting is required." +"@effect/ai-openai/OpenAiTokenizer#make": + replacement: "Tokenizer.make" + note: "The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using gpt-tokenizer if equivalent OpenAI counting is required." diff --git a/migration/annotations/effect__ai-openai__index.yaml b/migration/annotations/effect__ai-openai__index.yaml new file mode 100644 index 000000000..5f0446a2b --- /dev/null +++ b/migration/annotations/effect__ai-openai__index.yaml @@ -0,0 +1,3 @@ +"@effect/ai-openai/index": + replacement: "@effect/ai-openai" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-openai package root or import specific modules directly." diff --git a/migration/annotations/effect__ai-openrouter__Generated.yaml b/migration/annotations/effect__ai-openrouter__Generated.yaml new file mode 100644 index 000000000..611408a13 --- /dev/null +++ b/migration/annotations/effect__ai-openrouter__Generated.yaml @@ -0,0 +1,1053 @@ +"@effect/ai-openrouter/Generated#ActivityItem": + replacement: "Generated.ActivityItem" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#AnnotationDetail": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesRequestProviderSort": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesRequestRoute": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesRequestServiceTier": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesRequestThinkingEnumType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesRequestToolChoiceEnumType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesResponse": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesResponseRole": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesResponseStopReason": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesResponseType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AnthropicMessagesResponseUsageServiceTier": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#AssistantMessage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#BadGatewayResponse": + replacement: "Generated.BadGatewayResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BadGatewayResponseErrorData": + replacement: "Generated.BadGatewayResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BadRequestResponse": + replacement: "Generated.BadRequestResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BadRequestResponseErrorData": + replacement: "Generated.BadRequestResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BigNumberUnion": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#BulkAssignKeysToGuardrail200": + replacement: "Generated.BulkAssignKeysToGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BulkAssignKeysToGuardrailRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#BulkAssignMembersToGuardrail200": + replacement: "Generated.BulkAssignMembersToGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BulkAssignMembersToGuardrailRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#BulkUnassignKeysFromGuardrail200": + replacement: "Generated.BulkUnassignKeysFromGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BulkUnassignKeysFromGuardrailRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#BulkUnassignMembersFromGuardrail200": + replacement: "Generated.BulkUnassignMembersFromGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#BulkUnassignMembersFromGuardrailRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CacheControlEphemeral": + replacement: "Generated.ChatContentCacheControl" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatCompletionFinishReason": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatError": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatGenerationParams": + replacement: "Generated.ChatRequest" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatGenerationParamsProviderEnumDataCollectionEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatGenerationParamsReasoningEffortEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatGenerationParamsRouteEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatGenerationTokenUsage": + replacement: "Generated.ChatUsage" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageContentItem": + replacement: "Generated.ChatContentItems" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageContentItemAudio": + replacement: "Generated.ChatContentAudio" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageContentItemCacheControl": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatMessageContentItemCacheControlTtl": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatMessageContentItemImage": + replacement: "Generated.ChatContentImage" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageContentItemImageImageUrlDetail": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatMessageContentItemText": + replacement: "Generated.ChatContentText" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageContentItemVideo": + replacement: "Generated.ChatContentVideo" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageTokenLogprob": + replacement: "Generated.ChatTokenLogprob" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageTokenLogprobs": + replacement: "Generated.ChatTokenLogprobs" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatMessageToolCall": + replacement: "Generated.ChatToolCall" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatResponse": + replacement: "Generated.ChatResult" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ChatResponseChoice": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ChatStreamOptions": + replacement: "Generated.ChatStreamOptions" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#Client": + replacement: "Generated.OpenRouterClient" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ClientError": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionChoice": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionCreateParams": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionFinishReason": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionFinishReasonEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionLogprobs": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionResponse": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CompletionUsage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateAuthKeysCode200": + replacement: "Generated.CreateAuthKeysCode200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateAuthKeysCodeRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateAuthKeysCodeRequestCodeChallengeMethod": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateChargeRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateChargeRequestChainId": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateCoinbaseCharge200": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateEmbeddings200": + replacement: "Generated.CreateEmbeddings200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateEmbeddings200Object": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateEmbeddingsRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateEmbeddingsRequestEncodingFormat": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateGuardrail201": + replacement: "Generated.CreateGuardrail201" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateGuardrail201DataResetInterval": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateGuardrailRequest": + replacement: "Generated.CreateGuardrailRequest" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateGuardrailRequestResetInterval": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateKeys201": + replacement: "Generated.CreateKeys201" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateKeysRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateKeysRequestLimitReset": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages400": + replacement: "Generated.CreateMessages400" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages400Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages401": + replacement: "Generated.CreateMessages401" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages401Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages403": + replacement: "Generated.CreateMessages403" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages403Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages404": + replacement: "Generated.CreateMessages404" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages404Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages429": + replacement: "Generated.CreateMessages429" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages429Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages500": + replacement: "Generated.CreateMessages500" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages500Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages503": + replacement: "Generated.CreateMessages503" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages503Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#CreateMessages529": + replacement: "Generated.CreateMessages529" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#CreateMessages529Type": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#DataCollection": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#DefaultParameters": + replacement: "Generated.DefaultParameters" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#DeleteGuardrail200": + replacement: "Generated.DeleteGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#DeleteKeys200": + replacement: "Generated.DeleteKeys200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#EdgeNetworkTimeoutResponse": + replacement: "Generated.EdgeNetworkTimeoutResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#EdgeNetworkTimeoutResponseErrorData": + replacement: "Generated.EdgeNetworkTimeoutResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#EndpointStatus": + replacement: "Generated.EndpointStatus" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ExchangeAuthCodeForAPIKey200": + replacement: "Generated.ExchangeAuthCodeForAPIKey200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ExchangeAuthCodeForAPIKeyRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ExchangeAuthCodeForAPIKeyRequestCodeChallengeMethod": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#FileAnnotationDetail": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#FileCitation": + replacement: "Generated.FileCitation" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#FileCitationType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#FilePath": + replacement: "Generated.FilePath" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#FilePathType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ForbiddenResponse": + replacement: "Generated.ForbiddenResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ForbiddenResponseErrorData": + replacement: "Generated.ForbiddenResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetCredits200": + replacement: "Generated.GetCredits200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetCurrentKey200": + replacement: "Generated.GetCurrentKey200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetGeneration200": + replacement: "Generated.GetGeneration200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetGeneration200DataApiType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#GetGenerationParams": + replacement: "Generated.GetGenerationParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetGuardrail200": + replacement: "Generated.GetGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetGuardrail200DataResetInterval": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#GetKey200": + replacement: "Generated.GetKey200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetModelsParams": + replacement: "Generated.GetModelsParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetModelsParamsCategory": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#GetUserActivity200": + replacement: "Generated.GetUserActivity200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#GetUserActivityParams": + replacement: "Generated.GetUserActivityParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ImageGenerationStatus": + replacement: "Generated.ImageGenerationStatus" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#InputModality": + replacement: "Generated.InputModality" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#InternalServerResponse": + replacement: "Generated.InternalServerResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#InternalServerResponseErrorData": + replacement: "Generated.InternalServerResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#JSONSchemaConfig": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#List200": + replacement: "Generated.List200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListEndpoints200": + replacement: "Generated.ListEndpoints200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListEndpointsResponse": + replacement: "Generated.ListEndpointsResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListEndpointsResponseArchitecture": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ListEndpointsResponseArchitectureEnumInstructType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ListEndpointsZdr200": + replacement: "Generated.ListEndpointsZdr200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListGuardrailKeyAssignments200": + replacement: "Generated.ListGuardrailKeyAssignments200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListGuardrailKeyAssignmentsParams": + replacement: "Generated.ListGuardrailKeyAssignmentsParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListGuardrailMemberAssignments200": + replacement: "Generated.ListGuardrailMemberAssignments200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListGuardrailMemberAssignmentsParams": + replacement: "Generated.ListGuardrailMemberAssignmentsParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListGuardrails200": + replacement: "Generated.ListGuardrails200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListGuardrailsParams": + replacement: "Generated.ListGuardrailsParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListKeyAssignments200": + replacement: "Generated.ListKeyAssignments200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListKeyAssignmentsParams": + replacement: "Generated.ListKeyAssignmentsParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListMemberAssignments200": + replacement: "Generated.ListMemberAssignments200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListMemberAssignmentsParams": + replacement: "Generated.ListMemberAssignmentsParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListParams": + replacement: "Generated.ListParams" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ListProviders200": + replacement: "Generated.ListProviders200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#make": + replacement: "Generated.make" + note: "Still exported in v4, but the regenerated OpenRouter client has different operations and request/response schemas; update call sites to the current generated service." +"@effect/ai-openrouter/Generated#Message": + replacement: "Generated.ChatMessages" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#Model": + replacement: "Generated.Model" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ModelArchitecture": + replacement: "Generated.ModelArchitecture" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ModelArchitectureInstructType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ModelGroup": + replacement: "Generated.ModelGroup" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ModelName": + replacement: "Generated.ModelName" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ModelsCountResponse": + replacement: "Generated.ModelsCountResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ModelsListResponse": + replacement: "Generated.ModelsListResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ModelsListResponseData": + replacement: "Generated.ModelsListResponseData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#NamedToolChoice": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#NotFoundResponse": + replacement: "Generated.NotFoundResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#NotFoundResponseErrorData": + replacement: "Generated.NotFoundResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenAIResponsesAnnotation": + replacement: "Generated.OpenAIResponsesAnnotation" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenAIResponsesIncludable": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesIncompleteDetails": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesIncompleteDetailsReason": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesInput": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesPrompt": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesReasoningConfig": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesReasoningEffort": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesRefusalContent": + replacement: "Generated.OpenAIResponsesRefusalContent" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenAIResponsesRefusalContentType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesResponseStatus": + replacement: "Generated.OpenAIResponsesResponseStatus" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenAIResponsesServiceTier": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesToolChoice": + replacement: "Generated.OpenAIResponsesToolChoice" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenAIResponsesToolChoiceEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesToolChoiceEnumType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesToolChoiceEnumTypeEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenAIResponsesTruncation": + replacement: "Generated.OpenAIResponsesTruncation" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenAIResponsesUsage": + replacement: "Generated.OpenAIResponsesUsage" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OpenResponsesEasyInputMessage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesEasyInputMessageRoleEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesEasyInputMessageType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesFunctionCallOutput": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesFunctionCallOutputType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesFunctionToolCall": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesFunctionToolCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesInput": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesInputMessageItem": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesInputMessageItemRoleEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesInputMessageItemType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesNonStreamingResponse": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesNonStreamingResponseObject": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesReasoning": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesReasoningConfig": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesReasoningFormat": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesReasoningStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesReasoningType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesRequestMetadata": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesRequestRoute": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesRequestServiceTier": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesRequestTruncation": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesRequestTruncationEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesResponseText": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesResponseTextVerbosity": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearch20250826Tool": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearch20250826ToolType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearchPreview20250311Tool": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearchPreview20250311ToolType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearchPreviewTool": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearchPreviewToolType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearchTool": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenResponsesWebSearchToolType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenRouterAnthropicMessageParam": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OpenRouterAnthropicMessageParamRole": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemFileSearchCall": + replacement: "Generated.OutputItemFileSearchCall" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OutputItemFileSearchCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemFunctionCall": + replacement: "Generated.OutputItemFunctionCall" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OutputItemFunctionCallStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemFunctionCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemImageGenerationCall": + replacement: "Generated.OutputItemImageGenerationCall" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OutputItemImageGenerationCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemReasoning": + replacement: "Generated.OutputItemReasoning" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OutputItemReasoningStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemReasoningType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputItemWebSearchCall": + replacement: "Generated.OutputItemWebSearchCall" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OutputItemWebSearchCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputMessage": + replacement: "Generated.OutputMessage" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#OutputMessageRole": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputMessageStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputMessageType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#OutputModality": + replacement: "Generated.OutputModality" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#Parameter": + replacement: "Generated.Parameter" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PayloadTooLargeResponse": + replacement: "Generated.PayloadTooLargeResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PayloadTooLargeResponseErrorData": + replacement: "Generated.PayloadTooLargeResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PaymentRequiredResponse": + replacement: "Generated.PaymentRequiredResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PaymentRequiredResponseErrorData": + replacement: "Generated.PaymentRequiredResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PDFParserEngine": + replacement: "Generated.PDFParserEngine" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PDFParserOptions": + replacement: "Generated.PDFParserOptions" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PercentileLatencyCutoffs": + replacement: "Generated.PercentileLatencyCutoffs" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PercentileStats": + replacement: "Generated.PercentileStats" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PercentileThroughputCutoffs": + replacement: "Generated.PercentileThroughputCutoffs" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PerRequestLimits": + replacement: "Generated.PerRequestLimits" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PreferredMaxLatency": + replacement: "Generated.PreferredMaxLatency" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PreferredMinThroughput": + replacement: "Generated.PreferredMinThroughput" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderName": + replacement: "Generated.ProviderName" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderOverloadedResponse": + replacement: "Generated.ProviderOverloadedResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderOverloadedResponseErrorData": + replacement: "Generated.ProviderOverloadedResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderPreferences": + replacement: "Generated.ProviderPreferences" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderPreferencesSort": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ProviderSort": + replacement: "Generated.ProviderSort" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderSortConfig": + replacement: "Generated.ProviderSortConfig" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ProviderSortConfigPartitionEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ProviderSortUnion": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#PublicEndpoint": + replacement: "Generated.PublicEndpoint" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#PublicEndpointQuantization": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#PublicEndpointQuantizationEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#PublicEndpointThroughputLast30M": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#PublicPricing": + replacement: "Generated.PublicPricing" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#Quantization": + replacement: "Generated.Quantization" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningDetail": + replacement: "Generated.ReasoningDetailUnion" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ReasoningDetailEncrypted": + replacement: "Generated.ReasoningDetailEncrypted" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningDetailSummary": + replacement: "Generated.ReasoningDetailSummary" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningDetailText": + replacement: "Generated.ReasoningDetailText" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningSummaryText": + replacement: "Generated.ReasoningSummaryText" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningSummaryTextType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ReasoningSummaryVerbosity": + replacement: "Generated.ReasoningSummaryVerbosity" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningTextContent": + replacement: "Generated.ReasoningTextContent" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ReasoningTextContentType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#RequestTimeoutResponse": + replacement: "Generated.RequestTimeoutResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#RequestTimeoutResponseErrorData": + replacement: "Generated.RequestTimeoutResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ResponseFormatJSONSchema": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseFormatTextConfig": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseFormatTextGrammar": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputAudio": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputAudioInputAudioFormat": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputAudioType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputFile": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputFileType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputImage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputImageDetail": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputImageType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputText": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputTextType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputVideo": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseInputVideoType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseOutputText": + replacement: "Generated.ResponseOutputText" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ResponseOutputTextType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesErrorField": + replacement: "Generated.ResponsesErrorField" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ResponsesErrorFieldCode": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesFormatJSONObject": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesFormatJSONObjectType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesFormatText": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesFormatTextJSONSchemaConfig": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesFormatTextJSONSchemaConfigType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesFormatTextType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesImageGenerationCall": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesImageGenerationCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemFileSearchCall": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemFileSearchCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemFunctionCall": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemFunctionCallStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemFunctionCallType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemReasoning": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemReasoningFormat": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemReasoningStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputItemReasoningType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputMessage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputMessageRole": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputMessageStatusEnum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputMessageType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesOutputModality": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesSearchContextSize": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesWebSearchCallOutput": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesWebSearchCallOutputType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesWebSearchUserLocation": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponsesWebSearchUserLocationType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseTextConfig": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ResponseTextConfigVerbosity": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema0": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema1": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema2": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema3": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema4": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema4Enum": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema5": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#Schema6": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ServiceUnavailableResponse": + replacement: "Generated.ServiceUnavailableResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ServiceUnavailableResponseErrorData": + replacement: "Generated.ServiceUnavailableResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#SystemMessage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#ToolCallStatus": + replacement: "Generated.ToolCallStatus" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#ToolChoiceOption": + replacement: "Generated.ChatToolChoice" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ToolDefinitionJson": + replacement: "Generated.ChatFunctionTool" + note: "Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape." +"@effect/ai-openrouter/Generated#ToolResponseMessage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#TooManyRequestsResponse": + replacement: "Generated.TooManyRequestsResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#TooManyRequestsResponseErrorData": + replacement: "Generated.TooManyRequestsResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#TopProviderInfo": + replacement: "Generated.TopProviderInfo" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UnauthorizedResponse": + replacement: "Generated.UnauthorizedResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UnauthorizedResponseErrorData": + replacement: "Generated.UnauthorizedResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UnprocessableEntityResponse": + replacement: "Generated.UnprocessableEntityResponse" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UnprocessableEntityResponseErrorData": + replacement: "Generated.UnprocessableEntityResponseErrorData" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UpdateGuardrail200": + replacement: "Generated.UpdateGuardrail200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UpdateGuardrail200DataResetInterval": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#UpdateGuardrailRequest": + replacement: "Generated.UpdateGuardrailRequest" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UpdateGuardrailRequestResetInterval": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#UpdateKeys200": + replacement: "Generated.UpdateKeys200" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#UpdateKeysRequest": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#UpdateKeysRequestLimitReset": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#URLCitation": + replacement: "Generated.URLCitation" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#URLCitationAnnotationDetail": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#URLCitationType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#UserMessage": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#WebSearchEngine": + replacement: "Generated.WebSearchEngine" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." +"@effect/ai-openrouter/Generated#WebSearchPreviewToolUserLocation": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#WebSearchPreviewToolUserLocationType": + replacement: "none" + note: "Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper." +"@effect/ai-openrouter/Generated#WebSearchStatus": + replacement: "Generated.WebSearchStatus" + note: "Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed." diff --git a/migration/annotations/effect__ai-openrouter__OpenRouterClient.yaml b/migration/annotations/effect__ai-openrouter__OpenRouterClient.yaml new file mode 100644 index 000000000..81023018b --- /dev/null +++ b/migration/annotations/effect__ai-openrouter__OpenRouterClient.yaml @@ -0,0 +1,15 @@ +"@effect/ai-openrouter/OpenRouterClient#ChatStreamingChoice": + replacement: "Generated.ChatStreamChoice" + note: "The client-local streaming choice schema moved into the regenerated OpenRouter schema surface and changed shape." +"@effect/ai-openrouter/OpenRouterClient#ChatStreamingMessageChunk": + replacement: "Generated.ChatStreamDelta" + note: "The client-local streaming message delta moved into the regenerated OpenRouter schema surface and changed shape." +"@effect/ai-openrouter/OpenRouterClient#ChatStreamingMessageToolCall": + replacement: "Generated.ChatStreamToolCall" + note: "The client-local streaming tool-call delta moved into the regenerated OpenRouter schema surface and changed shape." +"@effect/ai-openrouter/OpenRouterClient#ChatStreamingResponseChunk": + replacement: "OpenRouterClient.ChatStreamingResponseChunkData" + note: "The standalone streaming chunk schema was replaced by the decoded data type from Generated.ChatStreamingResponse." +"@effect/ai-openrouter/OpenRouterClient#Service": + replacement: "OpenRouterClient.Service" + note: "Still exported in v4; adapt to the regenerated client, revised request and response schemas, and the new streaming result tuple." diff --git a/migration/annotations/effect__ai-openrouter__OpenRouterConfig.yaml b/migration/annotations/effect__ai-openrouter__OpenRouterConfig.yaml new file mode 100644 index 000000000..5ec7a2e8a --- /dev/null +++ b/migration/annotations/effect__ai-openrouter__OpenRouterConfig.yaml @@ -0,0 +1,6 @@ +"@effect/ai-openrouter/OpenRouterConfig#OpenRouterConfig": + replacement: "OpenRouterConfig.OpenRouterConfig" + note: "Still exported in v4; update imports to the v4 package and use the revised Context.Service-based configuration service." +"@effect/ai-openrouter/OpenRouterConfig#OpenRouterConfig.Service": + replacement: "OpenRouterConfig.OpenRouterConfig.Service" + note: "Still exported in v4; update imports to the v4 package and use the revised Context.Service-based configuration service." diff --git a/migration/annotations/effect__ai-openrouter__OpenRouterLanguageModel.yaml b/migration/annotations/effect__ai-openrouter__OpenRouterLanguageModel.yaml new file mode 100644 index 000000000..18fdc4ef1 --- /dev/null +++ b/migration/annotations/effect__ai-openrouter__OpenRouterLanguageModel.yaml @@ -0,0 +1,9 @@ +"@effect/ai-openrouter/OpenRouterLanguageModel#Config": + replacement: "OpenRouterLanguageModel.Config" + note: "Still exported in v4; update imports and adapt to the regenerated chat request schema and revised Context.Service configuration." +"@effect/ai-openrouter/OpenRouterLanguageModel#Config.Service": + replacement: "OpenRouterLanguageModel.Config.Service" + note: "Still exported in v4; update imports and adapt to the regenerated chat request schema and revised Context.Service configuration." +"@effect/ai-openrouter/OpenRouterLanguageModel#OpenRouterReasoningInfo": + replacement: "OpenRouterLanguageModel.ReasoningDetails" + note: "The bespoke reasoning-info union was replaced by the provider's raw reasoning-details array, preserved through Prompt options and Response metadata." diff --git a/migration/annotations/effect__ai-openrouter__index.yaml b/migration/annotations/effect__ai-openrouter__index.yaml new file mode 100644 index 000000000..49daa878e --- /dev/null +++ b/migration/annotations/effect__ai-openrouter__index.yaml @@ -0,0 +1,3 @@ +"@effect/ai-openrouter/index": + replacement: "@effect/ai-openrouter" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-openrouter package root or import specific modules directly." diff --git a/migration/annotations/effect__ai.yaml b/migration/annotations/effect__ai.yaml new file mode 100644 index 000000000..2a4a65299 --- /dev/null +++ b/migration/annotations/effect__ai.yaml @@ -0,0 +1,3 @@ +"@effect/ai": + replacement: "effect/unstable/ai" + note: "The @effect/ai package was merged into the effect package; import the effect/unstable/ai barrel or import specific modules directly (e.g. effect/unstable/ai/)." diff --git a/migration/annotations/effect__ai__AiError.yaml b/migration/annotations/effect__ai__AiError.yaml new file mode 100644 index 000000000..34d041d50 --- /dev/null +++ b/migration/annotations/effect__ai__AiError.yaml @@ -0,0 +1,24 @@ +"@effect/ai/AiError#AiError": + replacement: "AiError.AiError" + note: "Moved to effect/unstable/ai/AiError and redesigned from a union of separately tagged errors into one AiError wrapper with a semantic reason. Construct it with AiError.make({ module, method, reason }) and match error.reason rather than the old top-level tags." +"@effect/ai/AiError#HttpRequestError": + replacement: "AiError.make + AiError.NetworkError" + note: "Replace the old top-level request error with an AiError whose reason is NetworkError. NetworkError.fromRequestError converts a v4 HttpClientError.RequestError." +"@effect/ai/AiError#HttpResponseError": + replacement: "AiError.make + AiError.reasonFromHttpStatus / AiError.InvalidOutputError" + note: "There is no single v4 response-error class. Wrap a semantic reason with AiError.make: use reasonFromHttpStatus for status failures and InvalidOutputError for decode or empty-body failures." +"@effect/ai/AiError#MalformedInput": + replacement: "AiError.make + AiError.InvalidUserInputError" + note: "Replace the old top-level input error with an AiError whose reason is InvalidUserInputError. Use InvalidRequestError when the provider request parameters themselves are malformed." +"@effect/ai/AiError#MalformedOutput": + replacement: "AiError.make + AiError.InvalidOutputError" + note: "Replace the old top-level output error with an AiError whose reason is InvalidOutputError. The old fromParseError helper becomes InvalidOutputError.fromSchemaError." +"@effect/ai/AiError#TypeId": + replacement: "AiError.isAiError" + note: "The AiError brand is private in v4. Use isAiError for runtime narrowing, or isAiErrorReason for a reason, instead of inspecting or constructing the type id." +"@effect/ai/AiError#UnknownError": + replacement: "AiError.make + AiError.UnknownError" + note: "UnknownError is now a semantic reason rather than a top-level error. Put module and method on AiError.make and inspect reason._tag when handling the outer AiError." +"@effect/ai/AiError#HttpRequestDetails": + replacement: "AiError.HttpRequestDetails" + note: "Retained in effect/unstable/ai/AiError and also re-exported as Response.HttpRequestDetails. Hash is an optional string instead of Option, headers may contain Redacted strings, and method includes TRACE." diff --git a/migration/annotations/effect__ai__EmbeddingModel.yaml b/migration/annotations/effect__ai__EmbeddingModel.yaml new file mode 100644 index 000000000..a4c065c0d --- /dev/null +++ b/migration/annotations/effect__ai__EmbeddingModel.yaml @@ -0,0 +1,3 @@ +"@effect/ai/EmbeddingModel#makeDataLoader": + replacement: "EmbeddingModel.make + RequestResolver.setDelay + RequestResolver.batchN" + note: "The dedicated data-loader constructor was removed. EmbeddingModel.make batches concurrent embed requests through its resolver; compose the exposed resolver with setDelay and optional batchN for the old window and maximum-batch behavior." diff --git a/migration/annotations/effect__ai__IdGenerator.yaml b/migration/annotations/effect__ai__IdGenerator.yaml new file mode 100644 index 000000000..8079a2375 --- /dev/null +++ b/migration/annotations/effect__ai__IdGenerator.yaml @@ -0,0 +1,3 @@ +"@effect/ai/IdGenerator#make": + replacement: "IdGenerator.make" + note: "Moved to effect/unstable/ai/IdGenerator with the same configurable alphabet, prefix, separator, and size behavior. Invalid configuration now fails with Cause.IllegalArgumentError." diff --git a/migration/annotations/effect__ai__LanguageModel.yaml b/migration/annotations/effect__ai__LanguageModel.yaml new file mode 100644 index 000000000..7436f1bf5 --- /dev/null +++ b/migration/annotations/effect__ai__LanguageModel.yaml @@ -0,0 +1,6 @@ +"@effect/ai/LanguageModel#ConstructorParams": + replacement: "none" + note: "V4 inlines this provider-adapter shape in LanguageModel.make. Pass generateText and streamText directly to make, with optional codecTransformer, instead of naming a constructor-parameter type." +"@effect/ai/LanguageModel#ExtractContext": + replacement: "LanguageModel.ExtractServices" + note: "Renamed in effect/unstable/ai/LanguageModel. ExtractServices infers toolkit handler, result-decoding, and effectful-toolkit service requirements." diff --git a/migration/annotations/effect__ai__McpSchema.yaml b/migration/annotations/effect__ai__McpSchema.yaml new file mode 100644 index 000000000..2c7a565d1 --- /dev/null +++ b/migration/annotations/effect__ai__McpSchema.yaml @@ -0,0 +1,27 @@ +"@effect/ai/McpSchema#ContentBlock": + replacement: "McpSchema.ContentBlock" + note: "Moved to effect/unstable/ai/McpSchema. It remains the MCP content-block union, but v4 exports it as a const schema rather than a Schema.Union subclass. Binary image, audio, and blob data still use Uint8Array values with base64 wire encoding." +"@effect/ai/McpSchema#ElicitResult": + replacement: "McpSchema.ElicitResult" + note: "Moved to effect/unstable/ai/McpSchema. It remains discriminated by action, but accepted content is now optional and, when present, is a record of strings, finite numbers, booleans, or string arrays; declined and canceled responses still omit content." +"@effect/ai/McpSchema#FailureEncoded": + replacement: "McpSchema.FailureEncoded" + note: "Moved to effect/unstable/ai/McpSchema and still derives an encoded JSON-RPC failure union from an RpcGroup." +"@effect/ai/McpSchema#FromClientEncoded": + replacement: "McpSchema.FromClientEncoded" + note: "Moved to effect/unstable/ai/McpSchema and remains the union of client requests and client notifications." +"@effect/ai/McpSchema#FromServerEncoded": + replacement: "McpSchema.FromServerEncoded" + note: "Moved to effect/unstable/ai/McpSchema and remains the union of server results and server notifications." +"@effect/ai/McpSchema#McpError": + replacement: "McpSchema.McpError" + note: "Moved, but changed from a constructable base class to a union schema of standard tagged protocol errors plus McpErrorBase. Use McpErrorBase to construct a generic MCP error." +"@effect/ai/McpSchema#param": + replacement: "McpSchema.param" + note: "Moved to effect/unstable/ai/McpSchema. V4 wraps the schema and exposes Param.name and Param.schema instead of attaching a public symbol annotation." +"@effect/ai/McpSchema#ParamAnnotation": + replacement: "McpSchema.isParam / Param.name" + note: "The public symbol annotation was removed. Detect parameter wrappers with McpSchema.isParam and read the narrowed Param.name instead of inspecting AST annotations." +"@effect/ai/McpSchema#SuccessEncoded": + replacement: "McpSchema.SuccessEncoded" + note: "Moved to effect/unstable/ai/McpSchema and still derives an encoded JSON-RPC success union from an RpcGroup." diff --git a/migration/annotations/effect__ai__McpServer.yaml b/migration/annotations/effect__ai__McpServer.yaml new file mode 100644 index 000000000..7ded9767a --- /dev/null +++ b/migration/annotations/effect__ai__McpServer.yaml @@ -0,0 +1,15 @@ +"@effect/ai/McpServer#layer": + replacement: "McpServer.layer" + note: "Moved to effect/unstable/ai/McpServer. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025_06_18], imported with McpProtocol from effect/unstable/ai; it still runs over a caller-provided RpcServer.Protocol." +"@effect/ai/McpServer#layerHttp": + replacement: "McpServer.layerHttp" + note: "Moved to effect/unstable/ai/McpServer and the unified HttpRouter. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025_06_18], imported with McpProtocol from effect/unstable/ai." +"@effect/ai/McpServer#layerHttpRouter": + replacement: "McpServer.layerHttp" + note: "Renamed and consolidated. V4 layerHttp registers the Streamable HTTP endpoint in the unified HttpRouter; pass a non-empty protocols array of adapters, such as [McpProtocol.v2025_06_18], imported with McpProtocol from effect/unstable/ai." +"@effect/ai/McpServer#layerStdio": + replacement: "McpServer.layerStdio" + note: "Moved to effect/unstable/ai/McpServer. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025_06_18], imported with McpProtocol from effect/unstable/ai." +"@effect/ai/McpServer#run": + replacement: "McpServer.run" + note: "Moved to effect/unstable/ai/McpServer. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025_06_18], imported with McpProtocol from effect/unstable/ai; it remains the Effect-level runner over RpcServer.Protocol." diff --git a/migration/annotations/effect__ai__Model.yaml b/migration/annotations/effect__ai__Model.yaml new file mode 100644 index 000000000..a09b05774 --- /dev/null +++ b/migration/annotations/effect__ai__Model.yaml @@ -0,0 +1,3 @@ +"@effect/ai/Model#TypeId": + replacement: "none" + note: "The Model brand still exists internally, but its TypeId is not exported and v4 has no public isModel guard. Use Model values created by Model.make rather than inspecting or constructing the brand." diff --git a/migration/annotations/effect__ai__Prompt.yaml b/migration/annotations/effect__ai__Prompt.yaml new file mode 100644 index 000000000..d08583bc6 --- /dev/null +++ b/migration/annotations/effect__ai__Prompt.yaml @@ -0,0 +1,69 @@ +"@effect/ai/Prompt#FilePart": + replacement: "Prompt.FilePart" + note: "Moved to effect/unstable/ai/Prompt with the same file-part model and schema; update the module import." +"@effect/ai/Prompt#FilePartEncoded": + replacement: "Prompt.FilePartEncoded" + note: "Moved to effect/unstable/ai/Prompt with the same encoded file-part shape; update the module import." +"@effect/ai/Prompt#FromJson": + replacement: "Schema.fromJsonString(Prompt.Prompt)" + note: "The module-specific JSON schema was removed. Compose the general v4 JSON-string codec with the public Prompt codec." +"@effect/ai/Prompt#isPart": + replacement: "Prompt.isPart" + note: "Moved to effect/unstable/ai/Prompt and remains the public runtime guard for prompt parts." +"@effect/ai/Prompt#isPrompt": + replacement: "Prompt.isPrompt" + note: "Moved to effect/unstable/ai/Prompt and remains the public runtime guard for Prompt values." +"@effect/ai/Prompt#makePart": + replacement: "Prompt.makePart" + note: "Moved to effect/unstable/ai/Prompt. The generic constructor also supports the new tool-approval request and response part variants." +"@effect/ai/Prompt#merge": + replacement: "Prompt.concat" + note: "Renamed in v4. concat preserves the old dual API and concatenates the messages from a Prompt with additional raw input." +"@effect/ai/Prompt#MessageContentFromString": + replacement: "Prompt.ContentFromString" + note: "Renamed in effect/unstable/ai/Prompt. It still decodes a string to a non-empty array containing one TextPart and encodes the first part's text." +"@effect/ai/Prompt#MessageTypeId": + replacement: "Prompt.isMessage" + note: "The message type id is private in v4. Use the public isMessage guard for runtime refinement instead of importing or inspecting the marker." +"@effect/ai/Prompt#Part": + replacement: "Prompt.Part" + note: "Moved to effect/unstable/ai/Prompt. The union now also includes tool-approval request and response parts." +"@effect/ai/Prompt#PartEncoded": + replacement: "Prompt.PartEncoded" + note: "Moved to effect/unstable/ai/Prompt. The encoded union now also includes tool-approval request and response parts." +"@effect/ai/Prompt#PartTypeId": + replacement: "Prompt.isPart" + note: "The part type id is private in v4. Use the public isPart guard for runtime refinement instead of importing or inspecting the marker." +"@effect/ai/Prompt#PromptFromSelf": + replacement: "Prompt.Prompt" + note: "The standalone declared from-self schema was removed. Use the public Prompt codec for prompt validation and encoding, or Prompt.isPrompt when only runtime refinement is needed." +"@effect/ai/Prompt#ReasoningPart": + replacement: "Prompt.ReasoningPart" + note: "Moved to effect/unstable/ai/Prompt with the same reasoning-part model and schema; update the module import." +"@effect/ai/Prompt#ReasoningPartEncoded": + replacement: "Prompt.ReasoningPartEncoded" + note: "Moved to effect/unstable/ai/Prompt with the same encoded reasoning payload; update the module import." +"@effect/ai/Prompt#TextPart": + replacement: "Prompt.TextPart" + note: "Moved to effect/unstable/ai/Prompt with the same text-part model and schema; update the module import." +"@effect/ai/Prompt#TextPartEncoded": + replacement: "Prompt.TextPartEncoded" + note: "Moved to effect/unstable/ai/Prompt with the same encoded text payload; update the module import." +"@effect/ai/Prompt#toolCallPart": + replacement: "Prompt.toolCallPart" + note: "Moved to effect/unstable/ai/Prompt and remains the typed convenience constructor over makePart(\"tool-call\", params)." +"@effect/ai/Prompt#ToolCallPart": + replacement: "Prompt.ToolCallPart" + note: "Moved to effect/unstable/ai/Prompt with the same tool-call model and schema; update the module import." +"@effect/ai/Prompt#ToolCallPartEncoded": + replacement: "Prompt.ToolCallPartEncoded" + note: "Moved to effect/unstable/ai/Prompt with the same encoded tool-call shape; update the module import." +"@effect/ai/Prompt#toolResultPart": + replacement: "Prompt.toolResultPart" + note: "Moved to effect/unstable/ai/Prompt. V4 removes providerExecuted from prompt tool-result parts; provider-executed response results are handled when converting Response parts." +"@effect/ai/Prompt#ToolResultPartEncoded": + replacement: "Prompt.ToolResultPartEncoded" + note: "Moved to effect/unstable/ai/Prompt, but providerExecuted was removed from the encoded prompt tool-result shape." +"@effect/ai/Prompt#TypeId": + replacement: "Prompt.isPrompt" + note: "The Prompt type id is private in v4 and its internal literal changed. Use the public isPrompt guard instead of importing or inspecting the marker." diff --git a/migration/annotations/effect__ai__Response.yaml b/migration/annotations/effect__ai__Response.yaml new file mode 100644 index 000000000..886befa16 --- /dev/null +++ b/migration/annotations/effect__ai__Response.yaml @@ -0,0 +1,72 @@ +"@effect/ai/Response#documentSourcePart": + replacement: "Response.makePart(\"source\", { ...params, sourceType: \"document\" })" + note: "The lowercase convenience constructor was removed. The DocumentSourcePart model remains, and the generic constructor now requires the document source discriminator." +"@effect/ai/Response#errorPart": + replacement: "Response.makePart(\"error\", params)" + note: "The lowercase convenience constructor was removed; construct the retained error part through Response.makePart." +"@effect/ai/Response#FilePartEncoded": + replacement: "Response.FilePartEncoded" + note: "Moved to effect/unstable/ai/Response; file data remains a base64 string in the encoded representation." +"@effect/ai/Response#finishPart": + replacement: "Response.makePart(\"finish\", params)" + note: "The lowercase convenience constructor was removed. V4 Usage has nested inputTokens and outputTokens objects, and FinishPart adds optional HTTP response details." +"@effect/ai/Response#Part": + replacement: "Response.Part" + note: "Moved to effect/unstable/ai/Response. The non-streaming union now also includes ToolApprovalRequestPart." +"@effect/ai/Response#PartTypeId": + replacement: "Response.isPart" + note: "The public PartTypeId was removed and the marker is internal in v4. Use Response.isPart for runtime refinement." +"@effect/ai/Response#reasoningDeltaPart": + replacement: "Response.makePart(\"reasoning-delta\", params)" + note: "The lowercase convenience constructor was removed; construct the retained ReasoningDeltaPart through Response.makePart." +"@effect/ai/Response#reasoningEndPart": + replacement: "Response.makePart(\"reasoning-end\", params)" + note: "The lowercase convenience constructor was removed; construct the retained ReasoningEndPart through Response.makePart." +"@effect/ai/Response#ReasoningPartEncoded": + replacement: "Response.ReasoningPartEncoded" + note: "Moved to effect/unstable/ai/Response; the encoded reasoning payload remains text: string." +"@effect/ai/Response#reasoningStartPart": + replacement: "Response.makePart(\"reasoning-start\", params)" + note: "The lowercase convenience constructor was removed; construct the retained ReasoningStartPart through Response.makePart." +"@effect/ai/Response#responseMetadataPart": + replacement: "Response.makePart(\"response-metadata\", params)" + note: "The lowercase convenience constructor was removed. V4 id, modelId, and timestamp are optional raw values rather than Option values, and optional HTTP request details were added." +"@effect/ai/Response#textDeltaPart": + replacement: "Response.makePart(\"text-delta\", params)" + note: "The lowercase convenience constructor was removed; construct the retained TextDeltaPart through Response.makePart." +"@effect/ai/Response#textEndPart": + replacement: "Response.makePart(\"text-end\", params)" + note: "The lowercase convenience constructor was removed; construct the retained TextEndPart through Response.makePart." +"@effect/ai/Response#TextPartEncoded": + replacement: "Response.TextPartEncoded" + note: "Moved to effect/unstable/ai/Response; the encoded text payload remains text: string." +"@effect/ai/Response#textStartPart": + replacement: "Response.makePart(\"text-start\", params)" + note: "The lowercase convenience constructor was removed; construct the retained TextStartPart through Response.makePart." +"@effect/ai/Response#toolCallPart": + replacement: "Response.toolCallPart" + note: "Moved to effect/unstable/ai/Response. The constructor remains, but providerName was removed from tool-call parts." +"@effect/ai/Response#ToolCallPartEncoded": + replacement: "Response.ToolCallPartEncoded" + note: "Moved to effect/unstable/ai/Response; providerName was removed while providerExecuted remains optional when encoded." +"@effect/ai/Response#toolParamsDeltaPart": + replacement: "Response.makePart(\"tool-params-delta\", params)" + note: "The lowercase convenience constructor was removed; construct the retained ToolParamsDeltaPart through Response.makePart." +"@effect/ai/Response#toolParamsEndPart": + replacement: "Response.makePart(\"tool-params-end\", params)" + note: "The lowercase convenience constructor was removed; construct the retained ToolParamsEndPart through Response.makePart." +"@effect/ai/Response#toolParamsStartPart": + replacement: "Response.makePart(\"tool-params-start\", params)" + note: "The lowercase convenience constructor was removed; providerName was also removed from ToolParamsStartPart in v4." +"@effect/ai/Response#toolResultPart": + replacement: "Response.toolResultPart" + note: "Moved to effect/unstable/ai/Response; providerName was removed and decoded tool results now require preliminary, normally false." +"@effect/ai/Response#ToolResultPartEncoded": + replacement: "Response.ToolResultPartEncoded" + note: "Moved to effect/unstable/ai/Response; providerName was removed and optional preliminary was added to the encoded shape." +"@effect/ai/Response#urlSourcePart": + replacement: "Response.makePart(\"source\", { ...params, sourceType: \"url\" })" + note: "The lowercase convenience constructor was removed. The UrlSourcePart model remains, and the generic constructor now requires the URL source discriminator." +"@effect/ai/Response#ToolResultPart": + replacement: "Response.ToolResultPart" + note: "The schema selects success or failure using isFailure rather than trying both result schemas. It returns Schema.Codec; supply decoding services when decoding and encoding services when encoding." diff --git a/migration/annotations/effect__ai__Tool.yaml b/migration/annotations/effect__ai__Tool.yaml new file mode 100644 index 000000000..e0fefb044 --- /dev/null +++ b/migration/annotations/effect__ai__Tool.yaml @@ -0,0 +1,63 @@ +"@effect/ai/Tool#AnyParametersSchema": + replacement: "Schema.Constraint" + note: "The AI-specific alias was removed. V4 Tool parameter schemas use the general Schema.Constraint type and are no longer restricted to the old struct-or-EmptyParams union." +"@effect/ai/Tool#AnyTaggedRequestSchema": + replacement: "none" + note: "The TaggedRequest-specific Tool adapter contract was removed. Model the operation directly with Tool.make and ordinary v4 Schema.Constraint values." +"@effect/ai/Tool#Destructive": + replacement: "Tool.Destructive" + note: "Moved to effect/unstable/ai/Tool. It is now a Context.Reference value rather than a Reference subclass; its default remains true." +"@effect/ai/Tool#Failure": + replacement: "Tool.Failure" + note: "Moved to effect/unstable/ai/Tool and remains the utility type that extracts a tool's decoded failure type." +"@effect/ai/Tool#fromTaggedRequest": + replacement: "Tool.make" + note: "The adapter was removed. Rebuild the tool explicitly with Tool.make(name, { parameters, success, failure }); Toolkit.make no longer converts schema values automatically." +"@effect/ai/Tool#FromTaggedRequest": + replacement: "Tool.Tool" + note: "The dedicated derived alias was removed. Construct with Tool.make and let Tool.Tool infer the name, parameter, success, and failure schemas." +"@effect/ai/Tool#getDescriptionFromSchemaAst": + replacement: "SchemaAST.resolveDescription" + note: "Moved out of Tool to the general v4 AST annotation resolver. For a Tool value, prefer Tool.getDescription." +"@effect/ai/Tool#getJsonSchemaFromSchemaAst": + replacement: "Tool.getJsonSchemaFromSchema" + note: "Renamed to accept a Schema.Constraint instead of a raw AST and now emits the v4 JSON Schema model. Wrap a raw AST with Schema.make first." +"@effect/ai/Tool#Idempotent": + replacement: "Tool.Idempotent" + note: "Moved to effect/unstable/ai/Tool. It is now a Context.Reference value rather than a Reference subclass; its default remains false." +"@effect/ai/Tool#OpenWorld": + replacement: "Tool.OpenWorld" + note: "Moved to effect/unstable/ai/Tool. It is now a Context.Reference value rather than a Reference subclass; its default remains true." +"@effect/ai/Tool#ProviderDefinedTypeId": + replacement: "Tool.ProviderDefinedTypeId" + note: "Moved to effect/unstable/ai/Tool and remains public. Its literal changed, so use the export rather than retaining the old hard-coded string." +"@effect/ai/Tool#Readonly": + replacement: "Tool.Readonly" + note: "Moved to effect/unstable/ai/Tool. It is now a Context.Reference value rather than a Reference subclass; its default remains false." +"@effect/ai/Tool#Requirements": + replacement: "Tool.HandlerServices" + note: "Renamed and refined. HandlerServices combines parameter-decoding, result-encoding, and request-level dependencies required by a tool handler." +"@effect/ai/Tool#Success": + replacement: "Tool.Success" + note: "Moved to effect/unstable/ai/Tool and remains the utility type that extracts a tool's decoded success type." +"@effect/ai/Tool#Title": + replacement: "Tool.Title" + note: "Moved to effect/unstable/ai/Tool. It is now a Context.Service annotation key; continue attaching the string title with tool.annotate(Tool.Title, value)." +"@effect/ai/Tool#Tool.ProviderDefinedProto": + replacement: "Tool.ProviderDefined" + note: "This implementation-brand interface is no longer public. Use Tool.ProviderDefined for the model type and Tool.isProviderDefined for runtime narrowing." +"@effect/ai/Tool#Tool.Variance": + replacement: "Tool.Tool / Tool.Any" + note: "This implementation variance interface is no longer public; its requirement marker is inline in Tool.Tool. Constrain generic code with Tool.Tool or Tool.Any." +"@effect/ai/Tool#Tool.VarianceStruct": + replacement: "Tool.Tool / Tool.Any" + note: "This implementation variance payload is no longer public; the requirements marker is inline in Tool.Tool and should not be named independently." +"@effect/ai/Tool#TypeId": + replacement: "Tool.TypeId" + note: "Moved to effect/unstable/ai/Tool and remains public. Its literal changed, so use the export rather than retaining the old hard-coded string." +"@effect/ai/Tool#Result": + replacement: "Tool.Result" + note: "The result includes success, declared failure, and execution-denied or execution-interrupted values in both failure modes. Return mode also includes AiError; handle these variants when narrowing results." +"@effect/ai/Tool#ResultEncoded": + replacement: "Tool.ResultEncoded" + note: "The encoded result includes execution-denied and execution-interrupted variants in both failure modes, plus encoded AiError in return mode." diff --git a/migration/annotations/effect__ai__Toolkit.yaml b/migration/annotations/effect__ai__Toolkit.yaml new file mode 100644 index 000000000..7a07a0369 --- /dev/null +++ b/migration/annotations/effect__ai__Toolkit.yaml @@ -0,0 +1,9 @@ +"@effect/ai/Toolkit#HandlersFrom": + replacement: "Toolkit.HandlersFrom" + note: "Moved to effect/unstable/ai/Toolkit. V4 handlers receive a HandlerContext argument and may fail with the declared failure, AiError, or AiErrorReason while requiring Tool.HandlerServices." +"@effect/ai/Toolkit#make": + replacement: "Toolkit.make" + note: "Moved to effect/unstable/ai/Toolkit. It now accepts Tool.Any values only and no longer converts TaggedRequest schemas; create each tool explicitly with Tool.make first." +"@effect/ai/Toolkit#TypeId": + replacement: "Toolkit.Toolkit / Toolkit.Any" + note: "The toolkit nominal id is private in v4. Use Toolkit.Toolkit or Toolkit.Any for typing instead of importing or inspecting the marker." diff --git a/migration/annotations/effect__ai__index.yaml b/migration/annotations/effect__ai__index.yaml new file mode 100644 index 000000000..c8787f31a --- /dev/null +++ b/migration/annotations/effect__ai__index.yaml @@ -0,0 +1,3 @@ +"@effect/ai/index": + replacement: "effect/unstable/ai" + note: "The package barrel was removed; import the same namespaces from the effect/unstable/ai barrel or import specific modules directly." diff --git a/migration/annotations/effect__cli.yaml b/migration/annotations/effect__cli.yaml new file mode 100644 index 000000000..a52d95b5a --- /dev/null +++ b/migration/annotations/effect__cli.yaml @@ -0,0 +1,3 @@ +"@effect/cli": + replacement: "effect/unstable/cli" + note: "The @effect/cli package was merged into the effect package; import the effect/unstable/cli barrel or import specific modules directly (e.g. effect/unstable/cli/)." diff --git a/migration/annotations/effect__cli__Args.yaml b/migration/annotations/effect__cli__Args.yaml new file mode 100644 index 000000000..c07e2b7fe --- /dev/null +++ b/migration/annotations/effect__cli__Args.yaml @@ -0,0 +1,126 @@ +"@effect/cli/Args#all": + replacement: "Command.make(name, config)" + note: "Collect arguments in the config record passed to Command.make; there is no standalone Argument.all." +"@effect/cli/Args#All.ArgsAny": + replacement: "Param.AnyArgument" + note: "Use the shared any-positional-parameter type." +"@effect/cli/Args#All.Return": + replacement: "Command.Command.Config.Infer" + note: "Infer the output of a command config record; standalone argument collections were removed." +"@effect/cli/Args#Args": + replacement: "Argument.Argument" + note: "Args was renamed to Argument in effect/unstable/cli." +"@effect/cli/Args#Args.BaseArgsConfig": + replacement: "name: string" + note: "Argument constructors now take the name as a required first parameter." +"@effect/cli/Args#Args.FormatArgsConfig": + replacement: "Primitive.FileParseOptions" + note: "Pass the name separately and use the format option with Argument.FileParse or Argument.FileSchema." +"@effect/cli/Args#Args.PathArgsConfig": + replacement: "Argument.Path(name, { pathType, mustExist })" + note: "Path options are inline; map exists=yes to mustExist=true and either to omission. exists=no has no exact replacement." +"@effect/cli/Args#Args.Variance": + replacement: "Argument.Argument" + note: "The separate variance artifact was removed; Argument inherits the shared Param variance." +"@effect/cli/Args#ArgsTypeId": + replacement: "Param.isParam(value) && value.kind === Param.argumentKind" + note: "The public Args type id was removed; use the Param guard and argument kind discriminator." +"@effect/cli/Args#atLeast": + replacement: "Argument.atLeast" + note: "Use the moved combinator; v4 returns ReadonlyArray and does not encode non-emptiness in the type." +"@effect/cli/Args#atMost": + replacement: "Argument.atMost" + note: "Use the moved combinator." +"@effect/cli/Args#between": + replacement: "Argument.between" + note: "Use the moved combinator; v4 validates bounds when constructing the parameter." +"@effect/cli/Args#boolean": + replacement: "Flag.Boolean / Argument.ChoiceWithValue" + note: "Positional booleans were removed as ambiguous; prefer a boolean flag or explicit true/false positional choices." +"@effect/cli/Args#choice": + replacement: "Argument.Literals" + note: "Use the renamed constructor and pass the argument name explicitly." +"@effect/cli/Args#date": + replacement: "Argument.Date" + note: "Use the renamed constructor and pass the argument name explicitly." +"@effect/cli/Args#directory": + replacement: "Argument.Directory" + note: "Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement." +"@effect/cli/Args#file": + replacement: "Argument.File" + note: "Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement." +"@effect/cli/Args#fileContent": + replacement: "Argument.File + Argument.mapEffect" + note: "Parse a path and read it with FileSystem.readFile; no binary-content argument constructor remains." +"@effect/cli/Args#fileParse": + replacement: "Argument.FileParse" + note: "Pass the old format as an options field; v4 returns parsed content rather than a path/content tuple." +"@effect/cli/Args#fileSchema": + replacement: "Argument.FileSchema" + note: "Pass the old format as an options field and use a v4 Schema constraint decoder." +"@effect/cli/Args#fileText": + replacement: "Argument.FileText" + note: "Use the renamed constructor; it returns content only." +"@effect/cli/Args#float": + replacement: "Argument.Finite" + note: "Use the renamed constructor; it rejects non-finite numbers." +"@effect/cli/Args#getHelp": + replacement: "none" + note: "Per-argument help introspection was removed; Command generates help internally." +"@effect/cli/Args#getIdentifier": + replacement: "none" + note: "Public argument identifier introspection was removed." +"@effect/cli/Args#getMaxSize": + replacement: "none" + note: "Public arity introspection was removed; command parsing enforces variadic bounds internally." +"@effect/cli/Args#getMinSize": + replacement: "none" + note: "Public arity introspection was removed; command parsing enforces variadic bounds internally." +"@effect/cli/Args#getUsage": + replacement: "none" + note: "The public Usage tree was removed; Command generates a usage string internally." +"@effect/cli/Args#integer": + replacement: "Argument.Int" + note: "Use the renamed constructor and pass the argument name explicitly." +"@effect/cli/Args#isArgs": + replacement: "Param.isParam(value) && value.kind === Param.argumentKind" + note: "Arguments now use the shared Param representation and an explicit kind discriminator." +"@effect/cli/Args#map": + replacement: "Argument.map" + note: "Use the moved combinator." +"@effect/cli/Args#none": + replacement: "omit the config entry" + note: "V4 Argument.Never is an always-failing sentinel, not v3's empty successful argument set." +"@effect/cli/Args#optional": + replacement: "Argument.optional" + note: "Use the moved combinator; it still returns Option." +"@effect/cli/Args#path": + replacement: "Argument.Path" + note: "Path options are inline; map exists=yes to mustExist=true and either to omission. exists=no has no exact replacement." +"@effect/cli/Args#redacted": + replacement: "Argument.Redacted" + note: "Use the renamed constructor and pass the argument name explicitly." +"@effect/cli/Args#repeated": + replacement: "Argument.variadic" + note: "Renamed to variadic; pass optional min and max bounds." +"@effect/cli/Args#secret": + replacement: "Argument.Redacted" + note: "Use Redacted-backed positional input." +"@effect/cli/Args#text": + replacement: "Argument.String" + note: "Renamed to String; pass the argument name explicitly." +"@effect/cli/Args#validate": + replacement: "argument.parse({ flags: {}, arguments: args })" + note: "Parsing is now a Param method and returns leftover tokens with the value; errors are CliError." +"@effect/cli/Args#withDefault": + replacement: "Argument.withDefault" + note: "Use the moved combinator; v4 also accepts an Effect fallback." +"@effect/cli/Args#withDescription": + replacement: "Argument.withDescription" + note: "Use the moved combinator." +"@effect/cli/Args#withFallbackConfig": + replacement: "Argument.withFallbackConfig" + note: "Use the moved combinator; invalid configuration becomes CliError.InvalidValue." +"@effect/cli/Args#withSchema": + replacement: "Argument.withSchema" + note: "Use the moved combinator with a v4 Schema constraint decoder." diff --git a/migration/annotations/effect__cli__AutoCorrect.yaml b/migration/annotations/effect__cli__AutoCorrect.yaml new file mode 100644 index 000000000..7bdf4dd13 --- /dev/null +++ b/migration/annotations/effect__cli__AutoCorrect.yaml @@ -0,0 +1,3 @@ +"@effect/cli/AutoCorrect": + replacement: none + note: V4 suggestion distance is internal and fixed; the public configurable distance helper was removed. diff --git a/migration/annotations/effect__cli__BuiltInOptions.yaml b/migration/annotations/effect__cli__BuiltInOptions.yaml new file mode 100644 index 000000000..66c0860dd --- /dev/null +++ b/migration/annotations/effect__cli__BuiltInOptions.yaml @@ -0,0 +1,48 @@ +"@effect/cli/BuiltInOptions#builtInOptions": + replacement: "GlobalFlag.BuiltIns" + note: "Built-ins are global flag definitions consumed automatically by Command.run and Command.runWith." +"@effect/cli/BuiltInOptions#BuiltInOptions": + replacement: "GlobalFlag.BuiltIn" + note: "The parsed directive union became a union of global Action and Setting definitions." +"@effect/cli/BuiltInOptions#BuiltInOptions.ShellType": + replacement: "Completions.Shell" + note: "The shell union moved to Completions." +"@effect/cli/BuiltInOptions#isShowCompletions": + replacement: "none" + note: "Parsed ShowCompletions directives were removed; the runner processes GlobalFlag.Completions directly." +"@effect/cli/BuiltInOptions#isShowHelp": + replacement: "none" + note: "Parsed ShowHelp directives were removed; the runner processes GlobalFlag.Help directly." +"@effect/cli/BuiltInOptions#isShowVersion": + replacement: "none" + note: "Parsed ShowVersion directives were removed; the runner processes GlobalFlag.Version directly." +"@effect/cli/BuiltInOptions#isShowWizard": + replacement: "none" + note: "Parsed ShowWizard directives were removed; the runner processes GlobalFlag.Wizard directly." +"@effect/cli/BuiltInOptions#SetLogLevel": + replacement: "GlobalFlag.LogLevel" + note: "Log level is now a global Setting whose parsed value is provided through context." +"@effect/cli/BuiltInOptions#showCompletions": + replacement: "GlobalFlag.Completions" + note: "Use the built-in completion action; the shell is parsed from --completions." +"@effect/cli/BuiltInOptions#ShowCompletions": + replacement: "GlobalFlag.Completions" + note: "The directive payload was replaced by a global completion action definition." +"@effect/cli/BuiltInOptions#showHelp": + replacement: "GlobalFlag.Help" + note: "Use the built-in help action; usage and help are derived from the active Command." +"@effect/cli/BuiltInOptions#ShowHelp": + replacement: "GlobalFlag.Help" + note: "The directive payload was replaced by a global help action definition." +"@effect/cli/BuiltInOptions#showVersion": + replacement: "GlobalFlag.Version" + note: "Use the built-in version action; supply the version to Command.run or Command.runWith." +"@effect/cli/BuiltInOptions#ShowVersion": + replacement: "GlobalFlag.Version" + note: "The directive value was replaced by a global version action definition." +"@effect/cli/BuiltInOptions#showWizard": + replacement: "GlobalFlag.Wizard" + note: "Use the built-in wizard action; runner context supplies the active Command." +"@effect/cli/BuiltInOptions#ShowWizard": + replacement: "GlobalFlag.Wizard" + note: "The directive payload was replaced by a global wizard action definition." diff --git a/migration/annotations/effect__cli__CliApp.yaml b/migration/annotations/effect__cli__CliApp.yaml new file mode 100644 index 000000000..50985d4b8 --- /dev/null +++ b/migration/annotations/effect__cli__CliApp.yaml @@ -0,0 +1,12 @@ +"@effect/cli/CliApp#CliApp": + replacement: "Command.Command" + note: "The separate application wrapper was folded into the runnable v4 Command tree." +"@effect/cli/CliApp#CliApp.ConstructorArgs": + replacement: "none" + note: "Build the command with Command.make and withDescription, then pass version to Command.run; the old app constructor shape was removed." +"@effect/cli/CliApp#make": + replacement: "Command.make" + note: "Build the executable Command directly; there is no separate CliApp wrapper." +"@effect/cli/CliApp#run": + replacement: "Command.run" + note: "The CliApp wrapper was removed. Attach the execute function with Command.withHandler, then run the Command with its version; v4 reads arguments through the CLI environment instead of accepting args and execute at this call." diff --git a/migration/annotations/effect__cli__CliConfig.yaml b/migration/annotations/effect__cli__CliConfig.yaml new file mode 100644 index 000000000..ad5e73f6d --- /dev/null +++ b/migration/annotations/effect__cli__CliConfig.yaml @@ -0,0 +1,18 @@ +"@effect/cli/CliConfig#CliConfig": + replacement: "CliConfig.CliConfig.Service" + note: "The service was redesigned to configure built-in global flags; old parser and help switches were removed." +"@effect/cli/CliConfig#defaultConfig": + replacement: "CliConfig.defaults" + note: "Renamed to defaults with the redesigned service shape." +"@effect/cli/CliConfig#defaultLayer": + replacement: "CliConfig.layer" + note: "Call CliConfig.layer() to provide the defaults." +"@effect/cli/CliConfig#make": + replacement: "CliConfig.make" + note: "The constructor remains but accepts the redesigned service options." +"@effect/cli/CliConfig#normalizeCase": + replacement: "none" + note: "Case normalization is no longer configurable through CliConfig." +"@effect/cli/CliConfig#layer": + replacement: "CliConfig.layer" + note: "The layer constructor remains, but its options configure the redesigned CliConfig.Service for built-in global flags." diff --git a/migration/annotations/effect__cli__Command.yaml b/migration/annotations/effect__cli__Command.yaml new file mode 100644 index 000000000..295f9e1ac --- /dev/null +++ b/migration/annotations/effect__cli__Command.yaml @@ -0,0 +1,60 @@ +"@effect/cli/Command#Command.Context": + replacement: "Command.CommandContext" + note: "Renamed to CommandContext." +"@effect/cli/Command#Command.ParseConfig": + replacement: "Command.Command.Config.Infer" + note: "Use the v4 command-config inference helper." +"@effect/cli/Command#Command.ParseConfigValue": + replacement: "Command.Command.Config.InferValue" + note: "Use the v4 command-config value inference helper." +"@effect/cli/Command#Command.ParsedConfig": + replacement: "none" + note: "The parsed config representation is internal in v4." +"@effect/cli/Command#Command.ParsedConfigNode": + replacement: "none" + note: "The parsed config node representation is internal in v4." +"@effect/cli/Command#Command.ParsedConfigTree": + replacement: "none" + note: "The parsed config tree representation is internal in v4." +"@effect/cli/Command#Command.Transform": + replacement: "none" + note: "The handler transformation type and machinery are internal in v4." +"@effect/cli/Command#fromDescriptor": + replacement: "Command.make" + note: "The descriptor layer was folded into Command; define config and handler directly on Command.make." +"@effect/cli/Command#getBashCompletions": + replacement: "Completions.generate" + note: "Generation now returns one script string; normally use GlobalFlag.Completions through the runner." +"@effect/cli/Command#getFishCompletions": + replacement: "Completions.generate" + note: "Generation now returns one script string; normally use GlobalFlag.Completions through the runner." +"@effect/cli/Command#getHelp": + replacement: "none" + note: "Help generation for a command path is internal; use GlobalFlag.Help through Command.run or runWith." +"@effect/cli/Command#getNames": + replacement: "Command.Command.name / Command.Command.alias" + note: "Read the public name and optional alias fields; no HashSet accessor remains." +"@effect/cli/Command#getSubcommands": + replacement: "Command.Command.subcommands" + note: "Read the public grouped subcommands field; its shape is no longer a name map." +"@effect/cli/Command#getUsage": + replacement: "none" + note: "Usage is generated internally as part of structured HelpDoc." +"@effect/cli/Command#getZshCompletions": + replacement: "Completions.generate" + note: "Generation now returns one script string; normally use GlobalFlag.Completions through the runner." +"@effect/cli/Command#make": + replacement: "Command.make" + note: "Use the redesigned constructor with one nested config object of Argument and Flag values." +"@effect/cli/Command#run": + replacement: "Command.runWith" + note: "Use runWith for the v3-style function that accepts an argv array; use run to read arguments from Stdio." +"@effect/cli/Command#transformHandler": + replacement: "none" + note: "Transform in the handler or use the specific provide combinators; the generic handler transform is internal." +"@effect/cli/Command#TypeId": + replacement: "Command.isCommand" + note: "The type id is internal in v4; use the public runtime guard." +"@effect/cli/Command#withDescription": + replacement: "Command.withDescription" + note: "Use the retained combinator; v4 descriptions are strings rather than the old HelpDoc ADT." diff --git a/migration/annotations/effect__cli__CommandDescriptor.yaml b/migration/annotations/effect__cli__CommandDescriptor.yaml new file mode 100644 index 000000000..6875422f4 --- /dev/null +++ b/migration/annotations/effect__cli__CommandDescriptor.yaml @@ -0,0 +1,57 @@ +"@effect/cli/CommandDescriptor#Command.ComputeParsedType": + replacement: "Types.Simplify" + note: "Use the general simplification utility, or Command.Command.Config.Infer for command config." +"@effect/cli/CommandDescriptor#Command.GetParsedType": + replacement: "none" + note: "No public parsed-input extractor remains; v4 handlers receive inferred config directly." +"@effect/cli/CommandDescriptor#Command.ParsedStandardCommand": + replacement: "none" + note: "The name/options/args parsed wrapper was removed; handlers receive inferred config directly." +"@effect/cli/CommandDescriptor#Command.ParsedUserInputCommand": + replacement: "none" + note: "The descriptor-level prompt command was removed; use Prompt APIs or Command.wizard." +"@effect/cli/CommandDescriptor#Command.Subcommands": + replacement: "none" + note: "Compose independently handled commands with Command.withSubcommands instead of parsing a tuple union." +"@effect/cli/CommandDescriptor#Command.Variance": + replacement: "Command.Command.Variance" + note: "The command variance helper remains conceptually, now tracking input, error, and requirements." +"@effect/cli/CommandDescriptor#getBashCompletions": + replacement: "Completions.generate" + note: "Completion generation moved to one shell-parameterized function; command conversion is internal." +"@effect/cli/CommandDescriptor#getFishCompletions": + replacement: "Completions.generate" + note: "Completion generation moved to one shell-parameterized function; command conversion is internal." +"@effect/cli/CommandDescriptor#getHelp": + replacement: "none" + note: "Help generation is internal to the Command runner." +"@effect/cli/CommandDescriptor#getNames": + replacement: "Command.Command.name / Command.Command.alias" + note: "Read the public fields; no HashSet accessor remains." +"@effect/cli/CommandDescriptor#getSubcommands": + replacement: "Command.Command.subcommands" + note: "Read the public grouped subcommands field." +"@effect/cli/CommandDescriptor#getUsage": + replacement: "none" + note: "Usage generation is internal to Command help generation." +"@effect/cli/CommandDescriptor#getZshCompletions": + replacement: "Completions.generate" + note: "Completion generation moved to one shell-parameterized function; command conversion is internal." +"@effect/cli/CommandDescriptor#make": + replacement: "Command.make" + note: "The descriptor and executable command layers were merged into one constructor." +"@effect/cli/CommandDescriptor#map": + replacement: "none" + note: "Map individual Argument or Flag values, or transform inside the command handler." +"@effect/cli/CommandDescriptor#mapEffect": + replacement: "none" + note: "Use parameter mapEffect where the transformation belongs to an input, or perform the Effect in the handler." +"@effect/cli/CommandDescriptor#parse": + replacement: "Command.runWith" + note: "Parsing was folded into execution and no intermediate CommandDirective is returned." +"@effect/cli/CommandDescriptor#TypeId": + replacement: "Command.isCommand" + note: "The type id is internal in v4; use the public runtime guard." +"@effect/cli/CommandDescriptor#withDescription": + replacement: "Command.withDescription" + note: "Use the retained behavior; v4 descriptions are strings." diff --git a/migration/annotations/effect__cli__CommandDirective.yaml b/migration/annotations/effect__cli__CommandDirective.yaml new file mode 100644 index 000000000..8dc872ab7 --- /dev/null +++ b/migration/annotations/effect__cli__CommandDirective.yaml @@ -0,0 +1,21 @@ +"@effect/cli/CommandDirective#builtIn": + replacement: "GlobalFlag.Action" + note: "Define a custom action flag; v4 runners no longer return built-in directives." +"@effect/cli/CommandDirective#BuiltIn": + replacement: "GlobalFlag.Action" + note: "Use the global action definition type; it is processed directly by the runner." +"@effect/cli/CommandDirective#CommandDirective": + replacement: "none" + note: "The intermediate parse-result model was removed; the runner invokes the selected handler directly." +"@effect/cli/CommandDirective#isBuiltIn": + replacement: "none" + note: "Intermediate built-in directives were removed." +"@effect/cli/CommandDirective#map": + replacement: "none" + note: "Map parameters or transform in the handler; there is no intermediate directive to map." +"@effect/cli/CommandDirective#userDefined": + replacement: "none" + note: "Parsed input is delivered directly to the selected command handler." +"@effect/cli/CommandDirective#UserDefined": + replacement: "none" + note: "The user-defined intermediate directive was removed." diff --git a/migration/annotations/effect__cli__ConfigFile.yaml b/migration/annotations/effect__cli__ConfigFile.yaml new file mode 100644 index 000000000..707bf62b8 --- /dev/null +++ b/migration/annotations/effect__cli__ConfigFile.yaml @@ -0,0 +1,12 @@ +"@effect/cli/ConfigFile#ConfigErrorTypeId": + replacement: "none" + note: "ConfigProvider.SourceError has no public type-id export." +"@effect/cli/ConfigFile#ConfigFileError": + replacement: "ConfigProvider.SourceError" + note: "Use the general source error when implementing a custom file-backed provider." +"@effect/cli/ConfigFile#layer": + replacement: "ConfigProvider.layerAdd(customProviderEffect)" + note: "Build the provider explicitly and add it as fallback to preserve the v3 composition order." +"@effect/cli/ConfigFile#makeProvider": + replacement: "none" + note: "V4 has no API that discovers, parses, and composes config files; use FileSystem, a format parser, and ConfigProvider.fromUnknown explicitly." diff --git a/migration/annotations/effect__cli__HelpDoc.yaml b/migration/annotations/effect__cli__HelpDoc.yaml new file mode 100644 index 000000000..950aca332 --- /dev/null +++ b/migration/annotations/effect__cli__HelpDoc.yaml @@ -0,0 +1,78 @@ +"@effect/cli/HelpDoc#blocks": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#descriptionList": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#DescriptionList": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#empty": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#Empty": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#enumeration": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#Enumeration": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#getSpan": + replacement: "none" + note: "The Span ADT and document-to-span conversion were removed; v4 help fields are strings." +"@effect/cli/HelpDoc#h1": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#h2": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#h3": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#Header": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#HelpDoc": + replacement: "HelpDoc.HelpDoc" + note: "The name remains, but v4 is a structured command-help record rather than a tagged document AST." +"@effect/cli/HelpDoc#isDescriptionList": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#isEnumeration": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#isHeader": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#isParagraph": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#isSequence": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#mapDescriptionList": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#orElse": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#p": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#Paragraph": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#Sequence": + replacement: "none" + note: "The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput." +"@effect/cli/HelpDoc#toAnsiDoc": + replacement: "none" + note: "CliOutput owns rendering and exposes formatted text rather than an AnsiDoc." +"@effect/cli/HelpDoc#toAnsiText": + replacement: "CliOutput.defaultFormatter().formatHelpDoc" + note: "Format the structured help record; inside Effect code prefer the CliOutput.Formatter service." +"@effect/cli/HelpDoc#isEmpty": + replacement: "none" + note: "The Empty variant was removed when HelpDoc became a structured record; inspect the relevant flags, args, subcommands, and examples arrays when an application-specific emptiness test is needed." diff --git a/migration/annotations/effect__cli__HelpDoc__Span.yaml b/migration/annotations/effect__cli__HelpDoc__Span.yaml new file mode 100644 index 000000000..3ee5b3125 --- /dev/null +++ b/migration/annotations/effect__cli__HelpDoc__Span.yaml @@ -0,0 +1,3 @@ +"@effect/cli/HelpDoc/Span": + replacement: none + note: The Span ADT was removed; v4 help fields are strings and terminal styling is owned by CliOutput. diff --git a/migration/annotations/effect__cli__Options.yaml b/migration/annotations/effect__cli__Options.yaml new file mode 100644 index 000000000..966f792e3 --- /dev/null +++ b/migration/annotations/effect__cli__Options.yaml @@ -0,0 +1,150 @@ +"@effect/cli/Options#all": + replacement: "Command.make(name, config)" + note: "Collect flags in the config record passed to Command.make; there is no standalone Flag.all." +"@effect/cli/Options#All.OptionsAny": + replacement: "Param.AnyFlag" + note: "Use the shared any-flag-parameter type." +"@effect/cli/Options#All.Return": + replacement: "Command.Command.Config.Infer" + note: "Infer the output of a command config record; standalone flag collections were removed." +"@effect/cli/Options#atLeast": + replacement: "Flag.atLeast" + note: "Use the moved combinator; v4 returns ReadonlyArray rather than NonEmptyArray." +"@effect/cli/Options#atMost": + replacement: "Flag.atMost" + note: "Use the moved combinator." +"@effect/cli/Options#between": + replacement: "Flag.between" + note: "Use the moved combinator; v4 validates bounds when constructing the parameter." +"@effect/cli/Options#boolean": + replacement: "Flag.Boolean + Flag.withDefault" + note: "Use Flag.Boolean(name).pipe(Flag.withDefault(false)) to preserve v3's omitted-flag default; bare Flag.Boolean is now required. --no-name is automatic and aliases are added with Flag.withAlias." +"@effect/cli/Options#choice": + replacement: "Flag.Literals" + note: "Use the moved constructor." +"@effect/cli/Options#choiceWithValue": + replacement: "Flag.ChoiceWithValue" + note: "Use the moved constructor." +"@effect/cli/Options#date": + replacement: "Flag.Date" + note: "Use the moved constructor." +"@effect/cli/Options#directory": + replacement: "Flag.Directory" + note: "Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement." +"@effect/cli/Options#file": + replacement: "Flag.File" + note: "Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement." +"@effect/cli/Options#fileContent": + replacement: "Flag.File + Flag.mapEffect" + note: "Parse a path and read it with FileSystem.readFile; no binary-content flag constructor remains." +"@effect/cli/Options#fileParse": + replacement: "Flag.FileParse" + note: "Pass the old format as an options field; v4 returns parsed content rather than a path/content tuple." +"@effect/cli/Options#fileSchema": + replacement: "Flag.FileSchema" + note: "Pass the old format as an options field and use a v4 Schema constraint decoder." +"@effect/cli/Options#fileText": + replacement: "Flag.File + Flag.mapEffect" + note: "Flag.FileText returns content only; read after Flag.File when the path/content tuple must be preserved." +"@effect/cli/Options#filterMap": + replacement: "Flag.filterMap" + note: "Use the moved combinator and replace the fixed message with an onNone function." +"@effect/cli/Options#float": + replacement: "Flag.Finite" + note: "Use the moved constructor." +"@effect/cli/Options#getHelp": + replacement: "none" + note: "Per-flag help introspection was removed; Command generates help internally." +"@effect/cli/Options#getIdentifier": + replacement: "none" + note: "Public flag identifier introspection was removed." +"@effect/cli/Options#getUsage": + replacement: "none" + note: "The public Usage tree was removed; Command generates a usage string internally." +"@effect/cli/Options#integer": + replacement: "Flag.Int" + note: "Use the moved constructor." +"@effect/cli/Options#isBool": + replacement: "none" + note: "No public flag-shape predicate remains; boolean-shape inspection is internal." +"@effect/cli/Options#isOptions": + replacement: "Param.isParam(value) && value.kind === Param.flagKind" + note: "Flags now use the shared Param representation and an explicit kind discriminator." +"@effect/cli/Options#keyValueMap": + replacement: "Flag.KeyValuePair" + note: "Renamed and now returns Record rather than HashMap." +"@effect/cli/Options#map": + replacement: "Flag.map" + note: "Use the moved combinator." +"@effect/cli/Options#mapEffect": + replacement: "Flag.mapEffect" + note: "Use the moved combinator; mapping failures are CliError." +"@effect/cli/Options#mapTryCatch": + replacement: "Flag.mapTryCatch" + note: "Use the moved combinator; onError now returns a string rather than HelpDoc." +"@effect/cli/Options#none": + replacement: "omit the config entry" + note: "V4 Flag.Never is an always-failing sentinel, not v3's empty successful option set." +"@effect/cli/Options#optional": + replacement: "Flag.optional" + note: "Use the moved combinator; it still returns Option." +"@effect/cli/Options#Options": + replacement: "Flag.Flag" + note: "Options was renamed to Flag in effect/unstable/cli." +"@effect/cli/Options#Options.BooleanOptionsConfig": + replacement: "Flag.Boolean + Flag.withAlias + Flag.map" + note: "The config object was removed; aliases and value inversion are combinators, while custom negation names need application logic." +"@effect/cli/Options#Options.PathOptionsConfig": + replacement: "{ readonly mustExist?: boolean }" + note: "Path options are inline; true replaces exists=yes and omission replaces either. exists=no has no exact replacement." +"@effect/cli/Options#Options.Variance": + replacement: "Flag.Flag" + note: "The separate variance artifact was removed; Flag inherits the shared Param variance." +"@effect/cli/Options#OptionsTypeId": + replacement: "Param.isParam(value) && value.kind === Param.flagKind" + note: "The public Options type id was removed; use the Param guard and flag kind discriminator." +"@effect/cli/Options#orElse": + replacement: "Flag.orElse(() => fallback)" + note: "The fallback is now lazy; add explicit exclusivity validation if both flags must be rejected." +"@effect/cli/Options#orElseEither": + replacement: "Flag.orElseResult(() => fallback)" + note: "Either became Result and the fallback is lazy; v4 no longer rejects both flags being present." +"@effect/cli/Options#parse": + replacement: "flag.parse({ flags, arguments: [] })" + note: "Parsing is now a Param method over a Record and returns leftover arguments with the value; errors are CliError." +"@effect/cli/Options#processCommandLine": + replacement: "Command.runWith" + note: "Raw argv processing is now whole-command execution; no public standalone flag tokenizer remains." +"@effect/cli/Options#redacted": + replacement: "Flag.Redacted" + note: "Use the moved constructor." +"@effect/cli/Options#repeated": + replacement: "Flag.variadic" + note: "Renamed to variadic; pass optional min and max bounds." +"@effect/cli/Options#secret": + replacement: "Flag.Redacted" + note: "The deprecated Secret constructor was removed; use Redacted-backed input." +"@effect/cli/Options#text": + replacement: "Flag.String" + note: "Renamed from text to String." +"@effect/cli/Options#withAlias": + replacement: "Flag.withAlias" + note: "Use the moved combinator." +"@effect/cli/Options#withDefault": + replacement: "Flag.withDefault" + note: "Use the moved combinator; v4 also accepts an Effect fallback." +"@effect/cli/Options#withDescription": + replacement: "Flag.withDescription" + note: "Use the moved combinator." +"@effect/cli/Options#withFallbackConfig": + replacement: "Flag.withFallbackConfig" + note: "Use the moved combinator; invalid configuration becomes CliError.InvalidValue." +"@effect/cli/Options#withFallbackPrompt": + replacement: "Flag.withFallbackPrompt" + note: "Use the moved combinator; v4 can construct the Prompt lazily in Effect." +"@effect/cli/Options#withPseudoName": + replacement: "Flag.withMetavar" + note: "Renamed to withMetavar." +"@effect/cli/Options#withSchema": + replacement: "Flag.withSchema" + note: "Use the moved combinator with a v4 Schema constraint decoder." diff --git a/migration/annotations/effect__cli__Primitive.yaml b/migration/annotations/effect__cli__Primitive.yaml new file mode 100644 index 000000000..ab6eec519 --- /dev/null +++ b/migration/annotations/effect__cli__Primitive.yaml @@ -0,0 +1,42 @@ +"@effect/cli/Primitive#boolean": + replacement: "Primitive.Boolean" + note: "Boolean is now a singleton value; defaults belong on Flag.Boolean or withDefault." +"@effect/cli/Primitive#choice": + replacement: "Primitive.Choice" + note: "Use the moved constructor." +"@effect/cli/Primitive#date": + replacement: "Primitive.Date" + note: "Date is now a singleton Primitive value." +"@effect/cli/Primitive#float": + replacement: "Primitive.Finite" + note: "Finite is now a singleton Primitive value and rejects non-finite numbers." +"@effect/cli/Primitive#getChoices": + replacement: "none" + note: "Choice introspection is internal in v4; retain alternatives in application code when needed." +"@effect/cli/Primitive#getHelp": + replacement: "none" + note: "Primitive-level help generation was removed from the public API." +"@effect/cli/Primitive#integer": + replacement: "Primitive.Int" + note: "Int is now a singleton Primitive value." +"@effect/cli/Primitive#isBool": + replacement: "none" + note: "The boolean Primitive predicate is internal in v4." +"@effect/cli/Primitive#Primitive.PathExists": + replacement: "mustExist?: boolean" + note: "Use true for yes and omit for either; no cannot be represented exactly because false permits existing paths." +"@effect/cli/Primitive#Primitive.ValueType": + replacement: "P extends Primitive.Primitive ? A : never" + note: "The named helper was removed; infer the value with a local conditional type." +"@effect/cli/Primitive#Primitive.Variance": + replacement: "Primitive.Primitive.Variance" + note: "The variance interface remains, but its brand key is internal; prefer Primitive in user APIs." +"@effect/cli/Primitive#PrimitiveTypeId": + replacement: "none" + note: "The public Primitive type-id symbol was removed." +"@effect/cli/Primitive#text": + replacement: "Primitive.String" + note: "Renamed from text to String." +"@effect/cli/Primitive#validate": + replacement: "primitive.parse(value)" + note: "Parsing is now the Primitive.parse method over a string; defaults and case normalization moved out of this layer." diff --git a/migration/annotations/effect__cli__Prompt.yaml b/migration/annotations/effect__cli__Prompt.yaml new file mode 100644 index 000000000..04071fb7b --- /dev/null +++ b/migration/annotations/effect__cli__Prompt.yaml @@ -0,0 +1,69 @@ +"@effect/cli/Prompt#All.PromptAny": + replacement: "Prompt.Any" + note: "The any-prompt alias moved out of the All namespace." +"@effect/cli/Prompt#All.Return": + replacement: "Prompt.All.Return" + note: "The collection result helper remains under Prompt.All." +"@effect/cli/Prompt#confirm": + replacement: "Prompt.Confirm" + note: "Use the renamed constructor." +"@effect/cli/Prompt#custom": + replacement: "Prompt.Custom" + note: "Use the renamed constructor; both overloads are preserved." +"@effect/cli/Prompt#date": + replacement: "Prompt.Date" + note: "Use the moved constructor." +"@effect/cli/Prompt#file": + replacement: "Prompt.File" + note: "Use the moved constructor; v4 also supports a default selected path." +"@effect/cli/Prompt#flatMap": + replacement: "Prompt.flatMap" + note: "Use the moved combinator." +"@effect/cli/Prompt#float": + replacement: "Prompt.Number" + note: "Use the moved constructor; v4 also supports a default value." +"@effect/cli/Prompt#hidden": + replacement: "Prompt.Hidden" + note: "Use the renamed constructor." +"@effect/cli/Prompt#integer": + replacement: "Prompt.Int" + note: "Use the moved constructor; v4 also supports a default value." +"@effect/cli/Prompt#list": + replacement: "Prompt.List" + note: "Use the renamed constructor." +"@effect/cli/Prompt#map": + replacement: "Prompt.map" + note: "Use the moved combinator." +"@effect/cli/Prompt#multiSelect": + replacement: "Prompt.MultiSelect" + note: "Use the renamed constructor." +"@effect/cli/Prompt#password": + replacement: "Prompt.Password" + note: "Use the renamed constructor." +"@effect/cli/Prompt#Prompt": + replacement: "Prompt.Prompt" + note: "The model moved to effect/unstable/cli; quitting now fails with Terminal.QuitError." +"@effect/cli/Prompt#Prompt.Variance": + replacement: "Prompt.Prompt" + note: "The named variance artifact was removed; use Prompt." +"@effect/cli/Prompt#Prompt.VarianceStruct": + replacement: "Prompt.Prompt" + note: "The named variance structure was removed; use Prompt." +"@effect/cli/Prompt#PromptTypeId": + replacement: "Prompt.isPrompt" + note: "The public type-id symbol was removed; use the runtime guard." +"@effect/cli/Prompt#select": + replacement: "Prompt.Select" + note: "Use the renamed constructor." +"@effect/cli/Prompt#text": + replacement: "Prompt.String" + note: "Use the moved constructor." +"@effect/cli/Prompt#toggle": + replacement: "Prompt.Toggle" + note: "Use the renamed constructor." +"@effect/cli/Prompt#Prompt.IntegerOptions": + replacement: "Prompt.IntOptions" + note: "Use the renamed public options type for Prompt.Int; option fields are preserved." +"@effect/cli/Prompt#Prompt.FloatOptions": + replacement: "Prompt.NumberOptions" + note: "Use the renamed public options type for Prompt.Number; it still extends the integer options type, now IntOptions." diff --git a/migration/annotations/effect__cli__Usage.yaml b/migration/annotations/effect__cli__Usage.yaml new file mode 100644 index 000000000..aed15625d --- /dev/null +++ b/migration/annotations/effect__cli__Usage.yaml @@ -0,0 +1,3 @@ +"@effect/cli/Usage": + replacement: none + note: The Usage ADT was removed; Command builds a plain HelpDoc.usage string internally. diff --git a/migration/annotations/effect__cli__ValidationError.yaml b/migration/annotations/effect__cli__ValidationError.yaml new file mode 100644 index 000000000..5c9b66be6 --- /dev/null +++ b/migration/annotations/effect__cli__ValidationError.yaml @@ -0,0 +1,111 @@ +"@effect/cli/ValidationError#commandMismatch": + replacement: "none" + note: "V4 runners receive arguments after the root name; unknown child commands use CliError.UnknownSubcommand." +"@effect/cli/ValidationError#CommandMismatch": + replacement: "none" + note: "The root-command mismatch error was removed." +"@effect/cli/ValidationError#correctedFlag": + replacement: "new CliError.UnrecognizedOption({ option, command, suggestions })" + note: "Suggestions are carried by UnrecognizedOption; there is no separate corrected-flag case." +"@effect/cli/ValidationError#CorrectedFlag": + replacement: "CliError.UnrecognizedOption" + note: "Use the unrecognized-option class and its suggestions field." +"@effect/cli/ValidationError#helpRequested": + replacement: "new CliError.ShowHelp({ commandPath, errors: [] })" + note: "Help requests now carry a command path and optional underlying errors." +"@effect/cli/ValidationError#HelpRequested": + replacement: "CliError.ShowHelp" + note: "Renamed and redesigned as ShowHelp." +"@effect/cli/ValidationError#invalidArgument": + replacement: "new CliError.InvalidValue({ option, value, expected, kind: \"argument\" })" + note: "Use InvalidValue for undecodable arguments and UnexpectedArgument for leftover operands." +"@effect/cli/ValidationError#InvalidArgument": + replacement: "CliError.InvalidValue | CliError.UnexpectedArgument" + note: "Argument decoding and leftover operands are separate v4 errors." +"@effect/cli/ValidationError#invalidValue": + replacement: "new CliError.InvalidValue({ option, value, expected, kind })" + note: "Replace the HelpDoc payload with structured option, value, expected, and kind fields." +"@effect/cli/ValidationError#InvalidValue": + replacement: "CliError.InvalidValue" + note: "Use the schema-backed v4 error class." +"@effect/cli/ValidationError#isCommandMismatch": + replacement: "none" + note: "The root-command mismatch error was removed." +"@effect/cli/ValidationError#isCorrectedFlag": + replacement: "error._tag === \"UnrecognizedOption\" && error.suggestions.length > 0" + note: "Check the v4 tag and suggestions array." +"@effect/cli/ValidationError#isHelpRequested": + replacement: "error._tag === \"ShowHelp\"" + note: "Narrow the CliError union by its tag." +"@effect/cli/ValidationError#isInvalidArgument": + replacement: "(error._tag === \"InvalidValue\" && error.kind === \"argument\") || error._tag === \"UnexpectedArgument\"" + note: "Check both v4 argument error forms." +"@effect/cli/ValidationError#isInvalidValue": + replacement: "error._tag === \"InvalidValue\"" + note: "Narrow the CliError union by its tag." +"@effect/cli/ValidationError#isMissingFlag": + replacement: "error._tag === \"MissingOption\"" + note: "MissingFlag was renamed to MissingOption." +"@effect/cli/ValidationError#isMissingSubcommand": + replacement: "none" + note: "Missing subcommands now cause ShowHelp rather than a dedicated error." +"@effect/cli/ValidationError#isMissingValue": + replacement: "error._tag === \"InvalidValue\" && error.value === \"\"" + note: "Missing values are represented as InvalidValue with an empty value." +"@effect/cli/ValidationError#isMultipleValuesDetected": + replacement: "none" + note: "Count violations are summarized as InvalidValue without a stable subtype." +"@effect/cli/ValidationError#isNoBuiltInMatch": + replacement: "none" + note: "Built-ins are GlobalFlag definitions and the intermediate failure was removed." +"@effect/cli/ValidationError#isUnclusteredFlag": + replacement: "none" + note: "Cluster expansion is internal and has no public intermediate error." +"@effect/cli/ValidationError#isValidationError": + replacement: "CliError.isCliError" + note: "Use the renamed union guard." +"@effect/cli/ValidationError#keyValuesDetected": + replacement: "new CliError.InvalidValue({ option, value, expected, kind: \"flag\" })" + note: "Represent count violations with structured InvalidValue fields." +"@effect/cli/ValidationError#missingFlag": + replacement: "new CliError.MissingOption({ option })" + note: "MissingFlag was renamed to MissingOption." +"@effect/cli/ValidationError#MissingFlag": + replacement: "CliError.MissingOption" + note: "Renamed to MissingOption." +"@effect/cli/ValidationError#missingSubcommand": + replacement: "none" + note: "A parent without a selected subcommand now shows help rather than emitting a dedicated error." +"@effect/cli/ValidationError#MissingSubcommand": + replacement: "none" + note: "The dedicated missing-subcommand error was removed." +"@effect/cli/ValidationError#missingValue": + replacement: "new CliError.InvalidValue({ option, value: \"\", expected, kind })" + note: "Missing values are represented as InvalidValue with an empty value." +"@effect/cli/ValidationError#MissingValue": + replacement: "CliError.InvalidValue" + note: "The dedicated tag was folded into InvalidValue." +"@effect/cli/ValidationError#MultipleValuesDetected": + replacement: "CliError.InvalidValue" + note: "Count violations are summarized as InvalidValue without preserving the old values array." +"@effect/cli/ValidationError#noBuiltInMatch": + replacement: "none" + note: "Built-ins are GlobalFlag definitions and the intermediate failure was removed." +"@effect/cli/ValidationError#NoBuiltInMatch": + replacement: "none" + note: "The intermediate built-in matching error was removed." +"@effect/cli/ValidationError#unclusteredFlag": + replacement: "none" + note: "Flag cluster expansion is internal in v4." +"@effect/cli/ValidationError#UnclusteredFlag": + replacement: "none" + note: "The public cluster error was removed." +"@effect/cli/ValidationError#ValidationError": + replacement: "CliError.CliError" + note: "The validation union was redesigned and renamed to CliError." +"@effect/cli/ValidationError#ValidationError.Proto": + replacement: "none" + note: "V4 errors are schema-backed classes and expose no shared public prototype type." +"@effect/cli/ValidationError#ValidationErrorTypeId": + replacement: "none" + note: "The CliError type id is private; use CliError.isCliError." diff --git a/migration/annotations/effect__cli__index.yaml b/migration/annotations/effect__cli__index.yaml new file mode 100644 index 000000000..8a416c619 --- /dev/null +++ b/migration/annotations/effect__cli__index.yaml @@ -0,0 +1,3 @@ +"@effect/cli/index": + replacement: "effect/unstable/cli" + note: "The package barrel was removed; import the same namespaces from the effect/unstable/cli barrel or import specific modules directly." diff --git a/migration/annotations/effect__cluster.yaml b/migration/annotations/effect__cluster.yaml new file mode 100644 index 000000000..ecbb12117 --- /dev/null +++ b/migration/annotations/effect__cluster.yaml @@ -0,0 +1,3 @@ +"@effect/cluster": + replacement: "effect/unstable/cluster" + note: "The @effect/cluster package was merged into the effect package; import the effect/unstable/cluster barrel or import specific modules directly (e.g. effect/unstable/cluster/)." diff --git a/migration/annotations/effect__cluster__ClusterCron.yaml b/migration/annotations/effect__cluster__ClusterCron.yaml new file mode 100644 index 000000000..e32af55c5 --- /dev/null +++ b/migration/annotations/effect__cluster__ClusterCron.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/ClusterCron#make": + replacement: "effect/unstable/cluster/ClusterCron#make" + note: "Moved into core Effect. The constructor remains; Duration.DurationInput is now Duration.Input." diff --git a/migration/annotations/effect__cluster__ClusterError.yaml b/migration/annotations/effect__cluster__ClusterError.yaml new file mode 100644 index 000000000..9dd6c41b1 --- /dev/null +++ b/migration/annotations/effect__cluster__ClusterError.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/ClusterError#TypeId": + replacement: "none" + note: "The shared marker is private in v4. Use the exported tagged error classes, their _tag fields, or class-specific is guards." diff --git a/migration/annotations/effect__cluster__ClusterSchema.yaml b/migration/annotations/effect__cluster__ClusterSchema.yaml new file mode 100644 index 000000000..dc3c5c316 --- /dev/null +++ b/migration/annotations/effect__cluster__ClusterSchema.yaml @@ -0,0 +1,12 @@ +"@effect/cluster/ClusterSchema#ClientTracingEnabled": + replacement: "effect/unstable/cluster/ClusterSchema#ClientTracingEnabled" + note: "Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value." +"@effect/cluster/ClusterSchema#Persisted": + replacement: "effect/unstable/cluster/ClusterSchema#Persisted" + note: "Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same false default." +"@effect/cluster/ClusterSchema#ShardGroup": + replacement: "effect/unstable/cluster/ClusterSchema#ShardGroup" + note: "Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value." +"@effect/cluster/ClusterSchema#Uninterruptible": + replacement: "effect/unstable/cluster/ClusterSchema#Uninterruptible" + note: "Now a Context.Reference value. Replace its static methods with ClusterSchema.isUninterruptibleForServer and isUninterruptibleForClient." diff --git a/migration/annotations/effect__cluster__ClusterWorkflowEngine.yaml b/migration/annotations/effect__cluster__ClusterWorkflowEngine.yaml new file mode 100644 index 000000000..d7bf79fc1 --- /dev/null +++ b/migration/annotations/effect__cluster__ClusterWorkflowEngine.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/ClusterWorkflowEngine#layer": + replacement: "effect/unstable/cluster/ClusterWorkflowEngine#layer" + note: "Moved into core Effect with the same cluster-backed WorkflowEngine layer composition." +"@effect/cluster/ClusterWorkflowEngine#make": + replacement: "effect/unstable/cluster/ClusterWorkflowEngine#make" + note: "Moved into core Effect; the constructor still uses Sharding and MessageStorage." diff --git a/migration/annotations/effect__cluster__DeliverAt.yaml b/migration/annotations/effect__cluster__DeliverAt.yaml new file mode 100644 index 000000000..bba0f7b70 --- /dev/null +++ b/migration/annotations/effect__cluster__DeliverAt.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/DeliverAt#symbol": + replacement: "effect/unstable/cluster/DeliverAt#symbol" + note: "Moved into core Effect; the protocol key is now the string literal ~effect/cluster/DeliverAt rather than a global symbol." diff --git a/migration/annotations/effect__cluster__Entity.yaml b/migration/annotations/effect__cluster__Entity.yaml new file mode 100644 index 000000000..661a7e0f4 --- /dev/null +++ b/migration/annotations/effect__cluster__Entity.yaml @@ -0,0 +1,15 @@ +"@effect/cluster/Entity#Any": + replacement: "effect/unstable/cluster/Entity#Any" + note: "Moved into core Effect with the same erased entity type." +"@effect/cluster/Entity#makeTestClient": + replacement: "effect/unstable/cluster/Entity#makeTestClient" + note: "Moved into core Effect; adapt its inputs and requirements to the v4 RPC, Layer, Scope, and Context APIs." +"@effect/cluster/Entity#TypeId": + replacement: "none" + note: "The entity marker is private in v4. Use Entity.isEntity for runtime refinement." +"@effect/cluster/Entity#HandlersFrom": + replacement: "effect/unstable/cluster/Entity#HandlersFrom" + note: "Moved into core Effect; handler results now use Rpc.WrapperOr, which accepts either the raw RPC result or its wrapper." +"@effect/cluster/Entity#Replier.Success": + replacement: "effect/unstable/cluster/Entity#Replier.Success" + note: "Moved into core Effect; streaming replies may use Queue.Dequeue with Cause.Done instead of the removed Mailbox type." diff --git a/migration/annotations/effect__cluster__EntityAddress.yaml b/migration/annotations/effect__cluster__EntityAddress.yaml new file mode 100644 index 000000000..0bc1367ae --- /dev/null +++ b/migration/annotations/effect__cluster__EntityAddress.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/EntityAddress#EntityAddressFromSelf": + replacement: "effect/unstable/cluster/EntityAddress#EntityAddress" + note: "The separate self schema was removed; the v4 Schema.Class is itself the EntityAddress schema." +"@effect/cluster/EntityAddress#make": + replacement: "effect/unstable/cluster/EntityAddress#make" + note: "Moved into core Effect with the same options-object constructor." +"@effect/cluster/EntityAddress#TypeId": + replacement: "none" + note: "The marker is private in v4. Use the exported EntityAddress class and schema." diff --git a/migration/annotations/effect__cluster__EntityId.yaml b/migration/annotations/effect__cluster__EntityId.yaml new file mode 100644 index 000000000..dbc9ad7b0 --- /dev/null +++ b/migration/annotations/effect__cluster__EntityId.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/EntityId#make": + replacement: "effect/unstable/cluster/EntityId#make" + note: "Moved into core Effect; the branding helper remains and performs no validation or normalization." diff --git a/migration/annotations/effect__cluster__EntityProxy.yaml b/migration/annotations/effect__cluster__EntityProxy.yaml new file mode 100644 index 000000000..f6e0df345 --- /dev/null +++ b/migration/annotations/effect__cluster__EntityProxy.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/EntityProxy#ConvertHttpApi": + replacement: "effect/unstable/cluster/EntityProxy#ConvertHttpApi" + note: "Moved into core Effect and updated to the v4 HttpApiEndpoint and Schema types." +"@effect/cluster/EntityProxy#ConvertRpcs": + replacement: "effect/unstable/cluster/EntityProxy#ConvertRpcs" + note: "Moved into core Effect and updated to the v4 Rpc and Schema type parameters." diff --git a/migration/annotations/effect__cluster__EntityProxyServer.yaml b/migration/annotations/effect__cluster__EntityProxyServer.yaml new file mode 100644 index 000000000..57ef342ba --- /dev/null +++ b/migration/annotations/effect__cluster__EntityProxyServer.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/EntityProxyServer#layerHttpApi": + replacement: "effect/unstable/cluster/EntityProxyServer#layerHttpApi" + note: "Moved into core Effect. Use v4 HttpApi identifiers and provide Sharding plus both Rpc.ServicesServer and Rpc.ServicesClient codec requirements." +"@effect/cluster/EntityProxyServer#layerRpcHandlers": + replacement: "effect/unstable/cluster/EntityProxyServer#layerRpcHandlers" + note: "Moved into core Effect; replace Rpc.Context with both Rpc.ServicesServer and Rpc.ServicesClient codec requirements, alongside Sharding." +"@effect/cluster/EntityProxyServer#RpcHandlers": + replacement: "effect/unstable/cluster/EntityProxyServer#RpcHandlers" + note: "Moved into core Effect and updated for the additional v4 Rpc requirements type parameter." diff --git a/migration/annotations/effect__cluster__EntityResource.yaml b/migration/annotations/effect__cluster__EntityResource.yaml new file mode 100644 index 000000000..19472a5e2 --- /dev/null +++ b/migration/annotations/effect__cluster__EntityResource.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/EntityResource#make": + replacement: "effect/unstable/cluster/EntityResource#make" + note: "Moved into core Effect. Acquisition is lazy by default in v4; set acquireEagerly: true to preserve v3 behavior." +"@effect/cluster/EntityResource#TypeId": + replacement: "effect/unstable/cluster/EntityResource#TypeId" + note: "Moved into core Effect; its literal changed to ~effect/cluster/EntityResource." diff --git a/migration/annotations/effect__cluster__Envelope.yaml b/migration/annotations/effect__cluster__Envelope.yaml new file mode 100644 index 000000000..2d749b6f7 --- /dev/null +++ b/migration/annotations/effect__cluster__Envelope.yaml @@ -0,0 +1,45 @@ +"@effect/cluster/Envelope#Envelope.Any": + replacement: "effect/unstable/cluster/Envelope#Envelope.Any" + note: "Moved into core Effect with the same erased envelope type." +"@effect/cluster/Envelope#Envelope.Encoded": + replacement: "effect/unstable/cluster/Envelope#Encoded" + note: "The encoded envelope union moved to the module-level Encoded type." +"@effect/cluster/Envelope#Envelope.PartialEncoded": + replacement: "effect/unstable/cluster/Envelope#Partial" + note: "The partially decoded runtime union was renamed to Partial; use PartialJson for its JSON codec." +"@effect/cluster/Envelope#EnvelopeFromSelf": + replacement: "effect/unstable/cluster/Envelope#Envelope" + note: "The self schema was renamed to Envelope and declaration-merges with the envelope type and namespace." +"@effect/cluster/Envelope#PartialEncoded": + replacement: "effect/unstable/cluster/Envelope#PartialJson" + note: "The partially decoded envelope JSON codec was renamed to PartialJson." +"@effect/cluster/Envelope#PartialEncodedArray": + replacement: "effect/unstable/cluster/Envelope#PartialArray" + note: "The mutable array codec was renamed to PartialArray." +"@effect/cluster/Envelope#PartialEncodedFromSelf": + replacement: "effect/unstable/cluster/Envelope#Partial" + note: "The separate self schema was folded into Partial; derive JSON encoding with PartialJson." +"@effect/cluster/Envelope#PartialEncodedRequest": + replacement: "Schema.toCodecJson(Envelope.PartialRequest)" + note: "V4 exports the self schema as PartialRequest and derives its JSON codec with Schema.toCodecJson." +"@effect/cluster/Envelope#PartialEncodedRequestFromSelf": + replacement: "effect/unstable/cluster/Envelope#PartialRequest" + note: "The partially decoded request self schema was renamed to PartialRequest." +"@effect/cluster/Envelope#Request": + replacement: "effect/unstable/cluster/Envelope#Request" + note: "The request interface remains and declaration-merges with the exported Request schema." +"@effect/cluster/Envelope#Request.Any": + replacement: "effect/unstable/cluster/Envelope#Request.Any" + note: "Moved into core Effect with the same erased request type." +"@effect/cluster/Envelope#Request.Encoded": + replacement: "effect/unstable/cluster/Envelope#PartialRequestEncoded" + note: "The JSON request shape moved to the module-level PartialRequestEncoded interface." +"@effect/cluster/Envelope#Request.PartialEncoded": + replacement: "effect/unstable/cluster/Envelope#PartialRequest" + note: "The partially decoded request shape moved to the module-level PartialRequest class and type." +"@effect/cluster/Envelope#RequestFromSelf": + replacement: "effect/unstable/cluster/Envelope#Request" + note: "The request self schema was renamed to Request and declaration-merges with the runtime interface." +"@effect/cluster/Envelope#TypeId": + replacement: "typeof Envelope.TypeId" + note: "The marker value remains, but the type alias was removed and the value is now a string literal; use typeof in type position." diff --git a/migration/annotations/effect__cluster__HttpRunner.yaml b/migration/annotations/effect__cluster__HttpRunner.yaml new file mode 100644 index 000000000..77a734675 --- /dev/null +++ b/migration/annotations/effect__cluster__HttpRunner.yaml @@ -0,0 +1,12 @@ +"@effect/cluster/HttpRunner#layerClient": + replacement: "effect/unstable/cluster/HttpRunner#layerClient" + note: "Moved into core Effect with the same client-side Sharding and Runners layer composition." +"@effect/cluster/HttpRunner#layerHttp": + replacement: "effect/unstable/cluster/HttpRunner#layerHttp" + note: "Moved into core Effect with the same HTTP runner composition." +"@effect/cluster/HttpRunner#toHttpEffect": + replacement: "effect/unstable/cluster/HttpRunner#toHttpEffect" + note: "Moved into core Effect with the same nested HTTP server effect and service requirements." +"@effect/cluster/HttpRunner#toHttpEffectWebsocket": + replacement: "effect/unstable/cluster/HttpRunner#toHttpEffectWebsocket" + note: "Moved into core Effect with the same WebSocket HTTP effect shape and requirements." diff --git a/migration/annotations/effect__cluster__K8sHttpClient.yaml b/migration/annotations/effect__cluster__K8sHttpClient.yaml new file mode 100644 index 000000000..ea31aef07 --- /dev/null +++ b/migration/annotations/effect__cluster__K8sHttpClient.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/K8sHttpClient#layer": + replacement: "effect/unstable/cluster/K8sHttpClient#layer" + note: "Moved into core Effect with the same in-cluster Kubernetes client behavior." diff --git a/migration/annotations/effect__cluster__MachineId.yaml b/migration/annotations/effect__cluster__MachineId.yaml new file mode 100644 index 000000000..95933caa4 --- /dev/null +++ b/migration/annotations/effect__cluster__MachineId.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/MachineId#make": + replacement: "effect/unstable/cluster/MachineId#make" + note: "Moved into core Effect. The v4 helper is an unchecked cast; validate external input with the MachineId schema when needed." diff --git a/migration/annotations/effect__cluster__Message.yaml b/migration/annotations/effect__cluster__Message.yaml new file mode 100644 index 000000000..e41b871a4 --- /dev/null +++ b/migration/annotations/effect__cluster__Message.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/Message#serialize": + replacement: "effect/unstable/cluster/Message#serialize" + note: "Moved into core Effect. Pass the transport's codecFor as the second argument; use serializeEnvelope for the JSON Envelope.Encoded form." diff --git a/migration/annotations/effect__cluster__MessageStorage.yaml b/migration/annotations/effect__cluster__MessageStorage.yaml new file mode 100644 index 000000000..11f3b99c3 --- /dev/null +++ b/migration/annotations/effect__cluster__MessageStorage.yaml @@ -0,0 +1,15 @@ +"@effect/cluster/MessageStorage#layerMemory": + replacement: "effect/unstable/cluster/MessageStorage#layerMemory" + note: "Moved into core Effect; it still provides MessageStorage and MemoryDriver and requires ShardingConfig." +"@effect/cluster/MessageStorage#layerNoop": + replacement: "effect/unstable/cluster/MessageStorage#layerNoop" + note: "Moved into core Effect with the same dependency-free no-op implementation." +"@effect/cluster/MessageStorage#make": + replacement: "effect/unstable/cluster/MessageStorage#make" + note: "Moved into core Effect. Context service projections now use the Service property instead of Type. Custom service implementations must also provide resetAddresses for batched mailbox resets." +"@effect/cluster/MessageStorage#makeEncoded": + replacement: "effect/unstable/cluster/MessageStorage#makeEncoded" + note: "Moved into core Effect. Custom encoded drivers must replace resetAddress with resetAddresses and may use the new limit and addresses options passed to unprocessedMessages." +"@effect/cluster/MessageStorage#Encoded": + replacement: "effect/unstable/cluster/MessageStorage#Encoded" + note: "Moved into core Effect; use the v4 Envelope.Encoded and Reply.Encoded aliases. Custom drivers now implement batched resetAddresses, and unprocessedMessages receives optional limit and address filters." diff --git a/migration/annotations/effect__cluster__Reply.yaml b/migration/annotations/effect__cluster__Reply.yaml new file mode 100644 index 000000000..842dd729a --- /dev/null +++ b/migration/annotations/effect__cluster__Reply.yaml @@ -0,0 +1,12 @@ +"@effect/cluster/Reply#ReplyEncoded": + replacement: "effect/unstable/cluster/Reply#Encoded" + note: "Renamed to Encoded and no longer parameterized by an Rpc; payload fields are unknown and validated by Reply.Reply(rpc, codecFor) with the transport's codec." +"@effect/cluster/Reply#serialize": + replacement: "effect/unstable/cluster/Reply#serialize" + note: "Moved into core Effect and now returns the non-generic Reply.Encoded wire union. Pass the transport's codecFor as the second argument." +"@effect/cluster/Reply#TypeId": + replacement: "none" + note: "The reply marker is private in v4. Use Reply.isReply for runtime refinement." +"@effect/cluster/Reply#Reply": + replacement: "Reply.Reply(rpc, codecFor)" + note: "Pass the transport codec. Decoding replies requires Rpc.ServicesClient; encoding replies requires Rpc.ServicesServer." diff --git a/migration/annotations/effect__cluster__Runner.yaml b/migration/annotations/effect__cluster__Runner.yaml new file mode 100644 index 000000000..ebc99a826 --- /dev/null +++ b/migration/annotations/effect__cluster__Runner.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/Runner#TypeId": + replacement: "none" + note: "The runner marker is private in v4. Use the exported Runner class and schema." diff --git a/migration/annotations/effect__cluster__RunnerAddress.yaml b/migration/annotations/effect__cluster__RunnerAddress.yaml new file mode 100644 index 000000000..33246c9a4 --- /dev/null +++ b/migration/annotations/effect__cluster__RunnerAddress.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/RunnerAddress#make": + replacement: "effect/unstable/cluster/RunnerAddress#make" + note: "Moved into core Effect with the same host and port constructor; the host schema is now Schema.String." +"@effect/cluster/RunnerAddress#TypeId": + replacement: "none" + note: "The runner-address marker is private in v4. Use the exported RunnerAddress class and schema." diff --git a/migration/annotations/effect__cluster__RunnerHealth.yaml b/migration/annotations/effect__cluster__RunnerHealth.yaml new file mode 100644 index 000000000..ed573b9b0 --- /dev/null +++ b/migration/annotations/effect__cluster__RunnerHealth.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/RunnerHealth#layerNoop": + replacement: "effect/unstable/cluster/RunnerHealth#layerNoop" + note: "Moved into core Effect with the same dependency-free health implementation." diff --git a/migration/annotations/effect__cluster__RunnerServer.yaml b/migration/annotations/effect__cluster__RunnerServer.yaml new file mode 100644 index 000000000..b74c5f1cc --- /dev/null +++ b/migration/annotations/effect__cluster__RunnerServer.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/RunnerServer#layer": + replacement: "effect/unstable/cluster/RunnerServer#layer" + note: "Moved into core Effect; it still serves runner RPCs over a separately provided RpcServer.Protocol." +"@effect/cluster/RunnerServer#layerClientOnly": + replacement: "effect/unstable/cluster/RunnerServer#layerClientOnly" + note: "Moved into core Effect with the same client-only Sharding and Runners composition." diff --git a/migration/annotations/effect__cluster__RunnerStorage.yaml b/migration/annotations/effect__cluster__RunnerStorage.yaml new file mode 100644 index 000000000..59560ad17 --- /dev/null +++ b/migration/annotations/effect__cluster__RunnerStorage.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/RunnerStorage#layerMemory": + replacement: "effect/unstable/cluster/RunnerStorage#layerMemory" + note: "Moved into core Effect with the same in-memory registration and shard-lock implementation for tests and local use." +"@effect/cluster/RunnerStorage#makeMemory": + replacement: "effect/unstable/cluster/RunnerStorage#makeMemory" + note: "Moved into core Effect; it still constructs the in-memory RunnerStorage service implementation." diff --git a/migration/annotations/effect__cluster__Runners.yaml b/migration/annotations/effect__cluster__Runners.yaml new file mode 100644 index 000000000..e380f964d --- /dev/null +++ b/migration/annotations/effect__cluster__Runners.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/Runners#layerNoop": + replacement: "effect/unstable/cluster/Runners#layerNoop" + note: "Moved into core Effect with the same no-op runner communication layer." +"@effect/cluster/Runners#make": + replacement: "effect/unstable/cluster/Runners#make" + note: "Moved into core Effect. Its options now require codecFor; pass the codec used by the remote runner transport, such as RpcSerialization.json.codecFor for JSON. Context service projections now use Service instead of Type." +"@effect/cluster/Runners#makeNoop": + replacement: "effect/unstable/cluster/Runners#makeNoop" + note: "Moved into core Effect; it returns the Context.Service implementation through the Service projection instead of Type." diff --git a/migration/annotations/effect__cluster__ShardId.yaml b/migration/annotations/effect__cluster__ShardId.yaml new file mode 100644 index 000000000..3f416f62a --- /dev/null +++ b/migration/annotations/effect__cluster__ShardId.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/ShardId#make": + replacement: "effect/unstable/cluster/ShardId#make" + note: "Moved into core Effect with the same cached group and id constructor." +"@effect/cluster/ShardId#ShardId": + replacement: "effect/unstable/cluster/ShardId#ShardId" + note: "The class became a merged interface and schema value. Use ShardId.make; former static parsers and printers are module functions." +"@effect/cluster/ShardId#TypeId": + replacement: "none" + note: "The shard marker is private in v4. Use ShardId.isShardId for runtime refinement." diff --git a/migration/annotations/effect__cluster__Sharding.yaml b/migration/annotations/effect__cluster__Sharding.yaml new file mode 100644 index 000000000..774b80e24 --- /dev/null +++ b/migration/annotations/effect__cluster__Sharding.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/Sharding#layer": + replacement: "effect/unstable/cluster/Sharding#layer" + note: "Moved into core Effect with the same main sharding runtime composition and public service requirements." diff --git a/migration/annotations/effect__cluster__ShardingConfig.yaml b/migration/annotations/effect__cluster__ShardingConfig.yaml new file mode 100644 index 000000000..f429966f0 --- /dev/null +++ b/migration/annotations/effect__cluster__ShardingConfig.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/ShardingConfig#config": + replacement: "effect/unstable/cluster/ShardingConfig#config" + note: "Moved into core Effect; its Context service value type now uses the Service property instead of Type." +"@effect/cluster/ShardingConfig#defaults": + replacement: "effect/unstable/cluster/ShardingConfig#defaults" + note: "Moved into core Effect; service type projections now use Service instead of Type. V4 also defaults maxResidentEntities to 10,000 and unprocessedMessageBatchSize to 1,024." +"@effect/cluster/ShardingConfig#layer": + replacement: "effect/unstable/cluster/ShardingConfig#layer" + note: "Moved into core Effect with the same shallow default merge; service type projections now use Service instead of Type." diff --git a/migration/annotations/effect__cluster__ShardingRegistrationEvent.yaml b/migration/annotations/effect__cluster__ShardingRegistrationEvent.yaml new file mode 100644 index 000000000..8aeb99b27 --- /dev/null +++ b/migration/annotations/effect__cluster__ShardingRegistrationEvent.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/ShardingRegistrationEvent#match": + replacement: "effect/unstable/cluster/ShardingRegistrationEvent#match" + note: "Moved into core Effect with the same tagged-enum matcher." diff --git a/migration/annotations/effect__cluster__SingleRunner.yaml b/migration/annotations/effect__cluster__SingleRunner.yaml new file mode 100644 index 000000000..d8dd53d8d --- /dev/null +++ b/migration/annotations/effect__cluster__SingleRunner.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/SingleRunner#layer": + replacement: "effect/unstable/cluster/SingleRunner#layer" + note: "Moved into core Effect. V4 additionally requires Crypto.Crypto because SQL message storage hashes long deduplication keys." diff --git a/migration/annotations/effect__cluster__Singleton.yaml b/migration/annotations/effect__cluster__Singleton.yaml new file mode 100644 index 000000000..dd9745911 --- /dev/null +++ b/migration/annotations/effect__cluster__Singleton.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/Singleton#make": + replacement: "effect/unstable/cluster/Singleton#make" + note: "Moved into core Effect with the same singleton Layer constructor." diff --git a/migration/annotations/effect__cluster__SingletonAddress.yaml b/migration/annotations/effect__cluster__SingletonAddress.yaml new file mode 100644 index 000000000..6f81f8f00 --- /dev/null +++ b/migration/annotations/effect__cluster__SingletonAddress.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/SingletonAddress#TypeId": + replacement: "none" + note: "The singleton-address marker is private in v4. Use the exported SingletonAddress class and schema." diff --git a/migration/annotations/effect__cluster__Snowflake.yaml b/migration/annotations/effect__cluster__Snowflake.yaml new file mode 100644 index 000000000..67e87e5d4 --- /dev/null +++ b/migration/annotations/effect__cluster__Snowflake.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/Snowflake#Generator": + replacement: "effect/unstable/cluster/Snowflake#Generator" + note: "Moved into core Effect and changed to Context.Service; its unsafeNext method was renamed to nextUnsafe." +"@effect/cluster/Snowflake#make": + replacement: "effect/unstable/cluster/Snowflake#make" + note: "Moved into core Effect with the same timestamp, machine-id, and sequence packing constructor." +"@effect/cluster/Snowflake#TypeId": + replacement: "effect/unstable/cluster/Snowflake#TypeId" + note: "Moved into core Effect; the public marker is now the string literal ~effect/cluster/Snowflake." diff --git a/migration/annotations/effect__cluster__SocketRunner.yaml b/migration/annotations/effect__cluster__SocketRunner.yaml new file mode 100644 index 000000000..76e9b2481 --- /dev/null +++ b/migration/annotations/effect__cluster__SocketRunner.yaml @@ -0,0 +1,6 @@ +"@effect/cluster/SocketRunner#layer": + replacement: "effect/unstable/cluster/SocketRunner#layer" + note: "Moved into core Effect with the same full socket runner composition." +"@effect/cluster/SocketRunner#layerClientOnly": + replacement: "effect/unstable/cluster/SocketRunner#layerClientOnly" + note: "Moved into core Effect; it remains the client-only runner layer and does not start a socket server." diff --git a/migration/annotations/effect__cluster__SqlMessageStorage.yaml b/migration/annotations/effect__cluster__SqlMessageStorage.yaml new file mode 100644 index 000000000..833229bae --- /dev/null +++ b/migration/annotations/effect__cluster__SqlMessageStorage.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/SqlMessageStorage#layer": + replacement: "effect/unstable/cluster/SqlMessageStorage#layer" + note: "Moved into core Effect. V4 adds a Crypto.Crypto requirement for hashing long deduplication keys." +"@effect/cluster/SqlMessageStorage#layerWith": + replacement: "effect/unstable/cluster/SqlMessageStorage#layerWith" + note: "Moved into core Effect with the same optional table prefix; v4 additionally requires Crypto.Crypto." +"@effect/cluster/SqlMessageStorage#make": + replacement: "effect/unstable/cluster/SqlMessageStorage#make" + note: "Moved into core Effect with the same prefix option; v4 additionally requires Crypto.Crypto." diff --git a/migration/annotations/effect__cluster__SqlRunnerStorage.yaml b/migration/annotations/effect__cluster__SqlRunnerStorage.yaml new file mode 100644 index 000000000..39c6b0c37 --- /dev/null +++ b/migration/annotations/effect__cluster__SqlRunnerStorage.yaml @@ -0,0 +1,9 @@ +"@effect/cluster/SqlRunnerStorage#layer": + replacement: "effect/unstable/cluster/SqlRunnerStorage#layer" + note: "Moved into core Effect with the same default-prefix SQL runner storage layer." +"@effect/cluster/SqlRunnerStorage#layerWith": + replacement: "effect/unstable/cluster/SqlRunnerStorage#layerWith" + note: "Moved into core Effect with the same optional table prefix." +"@effect/cluster/SqlRunnerStorage#make": + replacement: "effect/unstable/cluster/SqlRunnerStorage#make" + note: "Moved into core Effect with the same prefix option and service requirements." diff --git a/migration/annotations/effect__cluster__TestRunner.yaml b/migration/annotations/effect__cluster__TestRunner.yaml new file mode 100644 index 000000000..521f937d8 --- /dev/null +++ b/migration/annotations/effect__cluster__TestRunner.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/TestRunner#layer": + replacement: "effect/unstable/cluster/TestRunner#layer" + note: "Moved into core Effect with the same dependency-free in-memory test cluster composition." diff --git a/migration/annotations/effect__cluster__index.yaml b/migration/annotations/effect__cluster__index.yaml new file mode 100644 index 000000000..00d9e4089 --- /dev/null +++ b/migration/annotations/effect__cluster__index.yaml @@ -0,0 +1,3 @@ +"@effect/cluster/index": + replacement: "effect/unstable/cluster" + note: "The package barrel was removed; import the same namespaces from the effect/unstable/cluster barrel or import specific modules directly." diff --git a/migration/annotations/effect__experimental.yaml b/migration/annotations/effect__experimental.yaml new file mode 100644 index 000000000..8f4c3ffa2 --- /dev/null +++ b/migration/annotations/effect__experimental.yaml @@ -0,0 +1,3 @@ +"@effect/experimental": + replacement: "none" + note: "The @effect/experimental package was folded into the effect package, split across effect/unstable/* (devtools, eventlog, persistence, reactivity, ...); follow the Import Map for each module." diff --git a/migration/annotations/effect__experimental__DevTools.yaml b/migration/annotations/effect__experimental__DevTools.yaml new file mode 100644 index 000000000..44ce415b7 --- /dev/null +++ b/migration/annotations/effect__experimental__DevTools.yaml @@ -0,0 +1,6 @@ +"@effect/experimental/DevTools#layer": + replacement: effect/unstable/devtools/DevTools#layer + note: Import layer from the v4 unstable DevTools module. +"@effect/experimental/DevTools#layerWebSocket": + replacement: effect/unstable/devtools/DevTools#layerWebSocket + note: Import layerWebSocket from the v4 unstable DevTools module. diff --git a/migration/annotations/effect__experimental__DevTools__Client.yaml b/migration/annotations/effect__experimental__DevTools__Client.yaml new file mode 100644 index 000000000..6009364c9 --- /dev/null +++ b/migration/annotations/effect__experimental__DevTools__Client.yaml @@ -0,0 +1,15 @@ +"@effect/experimental/DevTools/Client#Client": + replacement: effect/unstable/devtools/DevToolsClient#DevToolsClient + note: Client was renamed to the DevToolsClient Context.Service class. +"@effect/experimental/DevTools/Client#ClientImpl": + replacement: effect/unstable/devtools/DevToolsClient#DevToolsClient["Service"] + note: Use the service shape from DevToolsClient; unsafeAddSpan was replaced by sendUnsafe. +"@effect/experimental/DevTools/Client#layer": + replacement: effect/unstable/devtools/DevToolsClient#layer + note: Import layer from the v4 unstable DevToolsClient module. +"@effect/experimental/DevTools/Client#layerTracer": + replacement: effect/unstable/devtools/DevToolsClient#layerTracer + note: Import layerTracer from the v4 unstable DevToolsClient module. +"@effect/experimental/DevTools/Client#make": + replacement: effect/unstable/devtools/DevToolsClient#make + note: Import make from the v4 unstable DevToolsClient module. diff --git a/migration/annotations/effect__experimental__DevTools__Domain.yaml b/migration/annotations/effect__experimental__DevTools__Domain.yaml new file mode 100644 index 000000000..71f36dcb2 --- /dev/null +++ b/migration/annotations/effect__experimental__DevTools__Domain.yaml @@ -0,0 +1,18 @@ +"@effect/experimental/DevTools/Domain#ExternalSpanFrom": + replacement: effect/Schema#Codec.Encoded + note: The named encoded alias was removed; derive it with Schema.Codec.Encoded from ExternalSpan. +"@effect/experimental/DevTools/Domain#metric": + replacement: none + note: The metric schema helper is private in v4; use the exported Counter, Frequency, Gauge, Histogram, Summary, or Metric schemas, or build a Schema.Struct. +"@effect/experimental/DevTools/Domain#MetricFrom": + replacement: effect/Schema#Codec.Encoded + note: The named encoded alias was removed; derive it with Schema.Codec.Encoded from Metric. +"@effect/experimental/DevTools/Domain#MetricsSnapshotFrom": + replacement: effect/Schema#Codec.Encoded + note: The named encoded alias was removed; derive it with Schema.Codec.Encoded from MetricsSnapshot. +"@effect/experimental/DevTools/Domain#ParentSpanFrom": + replacement: effect/Schema#Codec.Encoded + note: The named encoded alias was removed; derive it with Schema.Codec.Encoded from ParentSpan. +"@effect/experimental/DevTools/Domain#SpanFrom": + replacement: effect/Schema#Codec.Encoded + note: The named encoded alias was removed; derive it with Schema.Codec.Encoded from Span. diff --git a/migration/annotations/effect__experimental__DevTools__Server.yaml b/migration/annotations/effect__experimental__DevTools__Server.yaml new file mode 100644 index 000000000..15396b28a --- /dev/null +++ b/migration/annotations/effect__experimental__DevTools__Server.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/DevTools/Server#run": + replacement: effect/unstable/devtools/DevToolsServer#run + note: Import run from the v4 unstable DevToolsServer module. diff --git a/migration/annotations/effect__experimental__Event.yaml b/migration/annotations/effect__experimental__Event.yaml new file mode 100644 index 000000000..89e43d33b --- /dev/null +++ b/migration/annotations/effect__experimental__Event.yaml @@ -0,0 +1,42 @@ +"@effect/experimental/Event#Event.AddError": + replacement: effect/unstable/eventlog/Event#AddError + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.Any": + replacement: effect/unstable/eventlog/Event#Any + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.Context": + replacement: effect/unstable/eventlog/Event#Services + note: Event schema context is now represented by decoding and encoding Services. +"@effect/experimental/Event#Event.ContextWithTag": + replacement: effect/unstable/eventlog/Event#Services> + note: Filter with WithTag and derive its decoding and encoding Services. +"@effect/experimental/Event#Event.Error": + replacement: effect/unstable/eventlog/Event#Error + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.ErrorSchema": + replacement: effect/unstable/eventlog/Event#ErrorSchema + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.Payload": + replacement: effect/unstable/eventlog/Event#Payload + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.PayloadSchema": + replacement: effect/unstable/eventlog/Event#PayloadSchema + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.Success": + replacement: effect/unstable/eventlog/Event#Success + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.SuccessSchema": + replacement: effect/unstable/eventlog/Event#SuccessSchema + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.Tag": + replacement: effect/unstable/eventlog/Event#Tag + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#Event.ToService": + replacement: effect/unstable/eventlog/Event#ToService + note: This type moved from the Event namespace to a top-level export. +"@effect/experimental/Event#make": + replacement: effect/unstable/eventlog/Event#make + note: Import make from the v4 unstable Event module. +"@effect/experimental/Event#TypeId": + replacement: effect/unstable/eventlog/Event#TypeId + note: Import TypeId from the v4 unstable Event module; its runtime representation is now a string brand. diff --git a/migration/annotations/effect__experimental__EventGroup.yaml b/migration/annotations/effect__experimental__EventGroup.yaml new file mode 100644 index 000000000..75f54efe3 --- /dev/null +++ b/migration/annotations/effect__experimental__EventGroup.yaml @@ -0,0 +1,15 @@ +"@effect/experimental/EventGroup#EventGroup.Any": + replacement: effect/unstable/eventlog/EventGroup#Any + note: This type moved from the EventGroup namespace to a top-level export. +"@effect/experimental/EventGroup#EventGroup.AnyWithProps": + replacement: effect/unstable/eventlog/EventGroup#AnyWithProps + note: This type moved from the EventGroup namespace to a top-level export. +"@effect/experimental/EventGroup#EventGroup.Context": + replacement: effect/unstable/eventlog/EventGroup#ServicesClient | effect/unstable/eventlog/EventGroup#ServicesServer + note: Choose the client or server schema services for the required direction. +"@effect/experimental/EventGroup#EventGroup.ToService": + replacement: effect/unstable/eventlog/EventGroup#ToService + note: This type moved from the EventGroup namespace to a top-level export. +"@effect/experimental/EventGroup#TypeId": + replacement: effect/unstable/eventlog/EventGroup#TypeId + note: Import TypeId from the v4 unstable EventGroup module; its runtime representation is now a string brand. diff --git a/migration/annotations/effect__experimental__EventJournal.yaml b/migration/annotations/effect__experimental__EventJournal.yaml new file mode 100644 index 000000000..d7ad705bc --- /dev/null +++ b/migration/annotations/effect__experimental__EventJournal.yaml @@ -0,0 +1,18 @@ +"@effect/experimental/EventJournal#EntryIdTypeId": + replacement: effect/unstable/eventlog/EventJournal#EntryIdTypeId + note: Import EntryIdTypeId from the v4 EventJournal module; it is now a string brand. +"@effect/experimental/EventJournal#ErrorTypeId": + replacement: none + note: The v4 error marker is private; narrow with EventJournalError instead. +"@effect/experimental/EventJournal#makeEntryId": + replacement: effect/unstable/eventlog/EventJournal#makeEntryIdUnsafe + note: The unchecked EntryId constructor was renamed to makeEntryIdUnsafe. +"@effect/experimental/EventJournal#makeRemoteId": + replacement: effect/unstable/eventlog/EventJournal#makeRemoteIdUnsafe + note: The unchecked RemoteId constructor was renamed to makeRemoteIdUnsafe. +"@effect/experimental/EventJournal#RemoteIdTypeId": + replacement: effect/unstable/eventlog/EventJournal#RemoteIdTypeId + note: Import RemoteIdTypeId from the v4 EventJournal module; it is now a string brand. +"@effect/experimental/EventJournal#makeMemory": + replacement: effect/unstable/eventlog/EventJournal#makeMemory + note: The in-memory constructor moved into core Effect and now returns the Context.Service implementation through its Service projection. diff --git a/migration/annotations/effect__experimental__EventLog.yaml b/migration/annotations/effect__experimental__EventLog.yaml new file mode 100644 index 000000000..869753522 --- /dev/null +++ b/migration/annotations/effect__experimental__EventLog.yaml @@ -0,0 +1,27 @@ +"@effect/experimental/EventLog#group": + replacement: effect/unstable/eventlog/EventLog#group + note: Import group from the v4 EventLog module; it now requires the shared Registry service. +"@effect/experimental/EventLog#Handlers": + replacement: effect/unstable/eventlog/EventLog#Handlers + note: Import Handlers from the v4 EventLog module; handlers now also receive storeId. +"@effect/experimental/EventLog#Handlers.Error": + replacement: effect/unstable/eventlog/EventLog#Handlers.Error + note: Import the retained Handlers.Error type from the v4 EventLog module. +"@effect/experimental/EventLog#Handlers.ValidateReturn": + replacement: effect/unstable/eventlog/EventLog#Handlers.ValidateReturn + note: Import the retained Handlers.ValidateReturn type from the v4 EventLog module. +"@effect/experimental/EventLog#HandlersTypeId": + replacement: effect/unstable/eventlog/EventLog#HandlersTypeId + note: Import HandlersTypeId from the v4 EventLog module. +"@effect/experimental/EventLog#layer": + replacement: effect/unstable/eventlog/EventLog#layer + note: The v4 layer takes both the schema and handler layer; use layerEventLog for runtime only. +"@effect/experimental/EventLog#layerIdentityKvs": + replacement: none + note: Compose KeyValueStore.toSchemaStore, EventLog.IdentitySchema, EventLog.makeIdentity, and Layer.effect manually. +"@effect/experimental/EventLog#schema": + replacement: effect/unstable/eventlog/EventLog#schema + note: Import schema from the v4 EventLog module. +"@effect/experimental/EventLog#SchemaTypeId": + replacement: effect/unstable/eventlog/EventLog#SchemaTypeId + note: Import SchemaTypeId from the v4 EventLog module. diff --git a/migration/annotations/effect__experimental__EventLogRemote.yaml b/migration/annotations/effect__experimental__EventLogRemote.yaml new file mode 100644 index 000000000..c0016a6dc --- /dev/null +++ b/migration/annotations/effect__experimental__EventLogRemote.yaml @@ -0,0 +1,57 @@ +"@effect/experimental/EventLogRemote#Ack": + replacement: none + note: A write acknowledgement is now the void success of EventLogMessage.WriteSingleRpc or WriteChunkedRpc. +"@effect/experimental/EventLogRemote#Changes": + replacement: effect/unstable/eventlog/EventLogMessage#ChangesRpc + note: ChangesRpc replaces the separate request and response models with one streaming RPC. +"@effect/experimental/EventLogRemote#decodeRequest": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific request decoder. +"@effect/experimental/EventLogRemote#decodeResponse": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific response decoder. +"@effect/experimental/EventLogRemote#encodeRequest": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific request encoder. +"@effect/experimental/EventLogRemote#encodeResponse": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific response encoder. +"@effect/experimental/EventLogRemote#EventLogRemote": + replacement: effect/unstable/eventlog/EventLogRemote#EventLogRemote + note: Use the v4 Context.Service; methods now take storeId-aware options. +"@effect/experimental/EventLogRemote#fromSocket": + replacement: effect/unstable/eventlog/EventLogRemote#makeEncrypted + effect/unstable/rpc/RpcClient#makeProtocolSocket + note: Construct the encrypted remote separately from its generic RPC socket protocol. +"@effect/experimental/EventLogRemote#Hello": + replacement: effect/unstable/eventlog/EventLogMessage#HelloResponse + note: HelloResponse replaces Hello and includes the v4 authentication challenge; HelloRpc defines the endpoint. +"@effect/experimental/EventLogRemote#layerWebSocket": + replacement: effect/unstable/eventlog/EventLogRemote#layerEncrypted + effect/unstable/rpc/RpcClient#layerProtocolSocket + note: Compose the encrypted remote with the generic socket protocol, SchemaBinary serialization, and a Socket provider. +"@effect/experimental/EventLogRemote#layerWebSocketBrowser": + replacement: effect/unstable/eventlog/EventLogRemote#layerEncrypted + effect/unstable/rpc/RpcClient#layerProtocolSocket + @effect/platform-browser/BrowserSocket#layerWebSocket + note: Compose the encrypted remote and generic RPC socket protocol with the browser WebSocket layer. +"@effect/experimental/EventLogRemote#Pong": + replacement: none + note: The event-log Pong model was removed; heartbeats belong to the generic RPC socket protocol. +"@effect/experimental/EventLogRemote#ProtocolRequest": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: EventLogRemoteRpcs and generic RPC serialization replace the old protocol request union. +"@effect/experimental/EventLogRemote#ProtocolRequestMsgPack": + replacement: effect/unstable/rpc/RpcSerialization#layerSchemaBinary + note: Use the generic SchemaBinary RPC serialization layer instead of a request-specific schema. +"@effect/experimental/EventLogRemote#ProtocolResponse": + replacement: effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs + note: EventLogRemoteRpcs and generic RPC serialization replace the old protocol response union. +"@effect/experimental/EventLogRemote#ProtocolResponseMsgPack": + replacement: effect/unstable/rpc/RpcSerialization#layerSchemaBinary + note: Use the generic SchemaBinary RPC serialization layer instead of a response-specific schema. +"@effect/experimental/EventLogRemote#RemoteAdditions": + replacement: none + note: This unused protocol model has no v4 counterpart. +"@effect/experimental/EventLogRemote#RequestChanges": + replacement: effect/unstable/eventlog/EventLogMessage#ChangesRpc + note: ChangesRpc replaces the separate request model with one streaming RPC. +"@effect/experimental/EventLogRemote#StopChanges": + replacement: none + note: Interrupt the ChangesRpc stream instead of sending a StopChanges message. diff --git a/migration/annotations/effect__experimental__EventLogServer.yaml b/migration/annotations/effect__experimental__EventLogServer.yaml new file mode 100644 index 000000000..8a044978f --- /dev/null +++ b/migration/annotations/effect__experimental__EventLogServer.yaml @@ -0,0 +1,15 @@ +"@effect/experimental/EventLogServer#layerStorageMemory": + replacement: effect/unstable/eventlog/EventLogServerEncrypted#layerStorageMemory + note: Use the encrypted server memory storage layer; storage is now storeId- and session-aware. +"@effect/experimental/EventLogServer#makeHandler": + replacement: effect/unstable/eventlog/EventLogServerEncrypted#layer + effect/unstable/rpc/RpcServer#layerProtocolSocketServer + note: Compose the encrypted server layer with the generic RPC socket server; there is no per-socket handler factory. +"@effect/experimental/EventLogServer#makeHandlerHttp": + replacement: effect/unstable/eventlog/EventLogServerEncrypted#layer + effect/unstable/rpc/RpcServer#makeProtocolWithHttpEffectWebsocket + note: Use the returned httpEffect for upgrades and provide its protocol to the encrypted server layer. +"@effect/experimental/EventLogServer#makeStorageMemory": + replacement: effect/unstable/eventlog/EventLogServerEncrypted#makeStorageMemory + note: Use the encrypted server memory storage constructor. +"@effect/experimental/EventLogServer#Storage": + replacement: effect/unstable/eventlog/EventLogServerEncrypted#Storage + note: Use the encrypted server Storage service, which is storeId- and session-aware. diff --git a/migration/annotations/effect__experimental__EventLogServer__Cloudflare.yaml b/migration/annotations/effect__experimental__EventLogServer__Cloudflare.yaml new file mode 100644 index 000000000..bec876f63 --- /dev/null +++ b/migration/annotations/effect__experimental__EventLogServer__Cloudflare.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/EventLogServer/Cloudflare": + replacement: none + note: The Cloudflare adapter was not ported; combine EventLogServerEncrypted.layer with a custom Durable Object RpcServer.Protocol adapter. diff --git a/migration/annotations/effect__experimental__Machine.yaml b/migration/annotations/effect__experimental__Machine.yaml new file mode 100644 index 000000000..d4d737f3d --- /dev/null +++ b/migration/annotations/effect__experimental__Machine.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/Machine": + replacement: none + note: The experimental local Machine actor runtime, model, boot process, constructors, brands, and serializable variants were not ported to v4. Redesign request contracts with Rpc/RpcGroup and choose Cluster Entity, Workflow, or a local actor built from Queue, Ref, PubSub, and scoped fibers according to the required semantics; ClusterWorkflowEngine is a different durable Workflow abstraction. For serializable actors, define schemas with Rpc/RpcGroup and choose Cluster Entity or Workflow explicitly. Context and initialization helpers (including the serializable initialization contract), input/private/public/state extractors, and the Machine-specific handler context were also removed, so request handling and state management must be explicit. Use ordinary Effect tracing controls and Effect.retry instead of the removed Machine-specific wrappers; its defect wrapper was also removed. Snapshot restoration was not ported, so implement persistence explicitly for the replacement architecture. diff --git a/migration/annotations/effect__experimental__Machine__Procedure.yaml b/migration/annotations/effect__experimental__Machine__Procedure.yaml new file mode 100644 index 000000000..2a62aeb1e --- /dev/null +++ b/migration/annotations/effect__experimental__Machine__Procedure.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/Machine/Procedure": + replacement: none + note: The stateful Machine Procedure model, its serializable variant and guard, and both Procedure brands were not ported to v4. Define request contracts with Rpc (using schemas for serializable procedures) and implement state handling in an explicit actor architecture, because Rpc provides only the request contract. The handler context, context and request extractors, and no-reply sentinel were removed; use the corresponding Rpc request types after redesigning the contract. Replace the removed tagged-request base and helpers with schema-backed Rpc requests and Rpc helper types where appropriate. diff --git a/migration/annotations/effect__experimental__Machine__ProcedureList.yaml b/migration/annotations/effect__experimental__Machine__ProcedureList.yaml new file mode 100644 index 000000000..6dd50fbfb --- /dev/null +++ b/migration/annotations/effect__experimental__Machine__ProcedureList.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/Machine/ProcedureList": + replacement: none + note: The stateful Machine ProcedureList abstraction and brand were not ported to v4. RpcGroup is the closest protocol collection for its schema-backed operations, but it has no initial state or public/private visibility split. Implement state handling and initialization in the replacement actor or workflow, and enforce visibility in that architecture. diff --git a/migration/annotations/effect__experimental__Machine__SerializableProcedureList.yaml b/migration/annotations/effect__experimental__Machine__SerializableProcedureList.yaml new file mode 100644 index 000000000..3dcce020f --- /dev/null +++ b/migration/annotations/effect__experimental__Machine__SerializableProcedureList.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/Machine/SerializableProcedureList": + replacement: none + note: The serializable stateful ProcedureList abstraction was not ported to v4. RpcGroup is the closest protocol collection for its schema-backed operations, but it has no initial state or public/private visibility split. Implement state handling and initialization in the replacement actor or workflow, and enforce visibility in that architecture. diff --git a/migration/annotations/effect__experimental__PersistedCache.yaml b/migration/annotations/effect__experimental__PersistedCache.yaml new file mode 100644 index 000000000..d766b9c6b --- /dev/null +++ b/migration/annotations/effect__experimental__PersistedCache.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/PersistedCache#make": + replacement: effect/unstable/persistence/PersistedCache#make + note: Pass lookup as the first argument and options second; timeToLive now receives exit before request and the service is Persistence.Persistence. diff --git a/migration/annotations/effect__experimental__PersistedQueue.yaml b/migration/annotations/effect__experimental__PersistedQueue.yaml new file mode 100644 index 000000000..68aada232 --- /dev/null +++ b/migration/annotations/effect__experimental__PersistedQueue.yaml @@ -0,0 +1,15 @@ +"@effect/experimental/PersistedQueue#layer": + replacement: effect/unstable/persistence/PersistedQueue#layer + note: Import layer from the v4 unstable PersistedQueue module. +"@effect/experimental/PersistedQueue#layerStoreMemory": + replacement: effect/unstable/persistence/PersistedQueue#layerStoreMemory + note: Import layerStoreMemory from the v4 unstable PersistedQueue module. +"@effect/experimental/PersistedQueue#make": + replacement: effect/unstable/persistence/PersistedQueue#make + note: Import make from the v4 unstable PersistedQueue module. +"@effect/experimental/PersistedQueue#ErrorTypeId": + replacement: effect/unstable/persistence/PersistedQueue#ErrorTypeId + note: Retained as a string brand; the runtime marker now uses the persistence module path. +"@effect/experimental/PersistedQueue#TypeId": + replacement: effect/unstable/persistence/PersistedQueue#TypeId + note: Import TypeId from the v4 unstable PersistedQueue module; it is now a string brand. diff --git a/migration/annotations/effect__experimental__PersistedQueue__Redis.yaml b/migration/annotations/effect__experimental__PersistedQueue__Redis.yaml new file mode 100644 index 000000000..cc9dd54be --- /dev/null +++ b/migration/annotations/effect__experimental__PersistedQueue__Redis.yaml @@ -0,0 +1,9 @@ +"@effect/experimental/PersistedQueue/Redis#layerStore": + replacement: effect/unstable/persistence/PersistedQueue#layerStoreRedis + note: The Redis adapter was merged into PersistedQueue and now requires the generic Redis.Redis service. +"@effect/experimental/PersistedQueue/Redis#layerStoreConfig": + replacement: none + note: Configure a Redis provider such as NodeRedis.layerConfig separately, then compose it with PersistedQueue.layerStoreRedis. +"@effect/experimental/PersistedQueue/Redis#make": + replacement: effect/unstable/persistence/PersistedQueue#makeStoreRedis + note: The Redis adapter was merged into PersistedQueue and now requires the generic Redis.Redis service. diff --git a/migration/annotations/effect__experimental__Persistence.yaml b/migration/annotations/effect__experimental__Persistence.yaml new file mode 100644 index 000000000..44ee17247 --- /dev/null +++ b/migration/annotations/effect__experimental__Persistence.yaml @@ -0,0 +1,51 @@ +"@effect/experimental/Persistence#BackingPersistence": + replacement: effect/unstable/persistence/Persistence#BackingPersistence + note: Use the v4 BackingPersistence Context.Service class. +"@effect/experimental/Persistence#BackingPersistenceTypeId": + replacement: none + note: The BackingPersistence brand is no longer publicly exported in v4. +"@effect/experimental/Persistence#ErrorTypeId": + replacement: none + note: The v4 persistence error identifier is private; narrow with the exported error classes. +"@effect/experimental/Persistence#layerKeyValueStore": + replacement: effect/unstable/persistence/Persistence#layerBackingKvs + note: The KeyValueStore backing layer was renamed to layerBackingKvs. +"@effect/experimental/Persistence#layerMemory": + replacement: effect/unstable/persistence/Persistence#layerBackingMemory + note: Use layerBackingMemory for the old backing service; v4 layerMemory creates the higher-level Persistence service. +"@effect/experimental/Persistence#layerResult": + replacement: effect/unstable/persistence/Persistence#layer + note: The ResultPersistence service layer was renamed to layer. +"@effect/experimental/Persistence#layerResultKeyValueStore": + replacement: effect/unstable/persistence/Persistence#layerKvs + note: The combined KeyValueStore-backed result layer was renamed to layerKvs. +"@effect/experimental/Persistence#layerResultMemory": + replacement: effect/unstable/persistence/Persistence#layerMemory + note: The combined memory-backed result layer was renamed to layerMemory. +"@effect/experimental/Persistence#PersistenceBackingError": + replacement: effect/unstable/persistence/Persistence#PersistenceError + note: PersistenceError now represents failures from the backing persistence implementation. +"@effect/experimental/Persistence#PersistenceError": + replacement: effect/unstable/persistence/Persistence#PersistenceError | effect/Schema#SchemaError + note: The old combined alias was split into backing PersistenceError and schema SchemaError. +"@effect/experimental/Persistence#PersistenceParseError": + replacement: effect/Schema#SchemaError + note: Persistence parsing failures now use the core SchemaError type. +"@effect/experimental/Persistence#ResultPersistence": + replacement: effect/unstable/persistence/Persistence#Persistence + note: ResultPersistence was renamed to Persistence and is now a Context.Service class. +"@effect/experimental/Persistence#ResultPersistence.Key": + replacement: effect/unstable/persistence/Persistable#Persistable + note: Persistable is the v4 schema-backed persistence key contract. +"@effect/experimental/Persistence#ResultPersistence.KeyAny": + replacement: effect/unstable/persistence/Persistable#Any + note: Use Persistable.Any for an arbitrary v4 persistence key contract. +"@effect/experimental/Persistence#ResultPersistence.TimeToLiveArgs": + replacement: Parameters> + note: Derive the tuple from TimeToLiveFn; its order is now exit then request. +"@effect/experimental/Persistence#ResultPersistenceStore": + replacement: effect/unstable/persistence/Persistence#PersistenceStore + note: ResultPersistenceStore was renamed to PersistenceStore. +"@effect/experimental/Persistence#ResultPersistenceTypeId": + replacement: none + note: The ResultPersistence brand is no longer publicly exported in v4. diff --git a/migration/annotations/effect__experimental__Persistence__Lmdb.yaml b/migration/annotations/effect__experimental__Persistence__Lmdb.yaml new file mode 100644 index 000000000..16ac3dfd3 --- /dev/null +++ b/migration/annotations/effect__experimental__Persistence__Lmdb.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/Persistence/Lmdb": + replacement: none + note: The LMDB backend was not ported; implement a custom BackingPersistence layer or use a supported Kvs, Redis, or SQL backend. diff --git a/migration/annotations/effect__experimental__Persistence__Redis.yaml b/migration/annotations/effect__experimental__Persistence__Redis.yaml new file mode 100644 index 000000000..dfc6b6e41 --- /dev/null +++ b/migration/annotations/effect__experimental__Persistence__Redis.yaml @@ -0,0 +1,15 @@ +"@effect/experimental/Persistence/Redis#layer": + replacement: effect/unstable/persistence/Persistence#layerBackingRedis + note: The Redis backing adapter was merged into Persistence and now requires the generic Redis.Redis service. +"@effect/experimental/Persistence/Redis#layerConfig": + replacement: none + note: Compose Persistence.layerBackingRedis with a config-driven provider such as NodeRedis.layerConfig. +"@effect/experimental/Persistence/Redis#layerResult": + replacement: effect/unstable/persistence/Persistence#layerRedis + note: The combined Redis persistence layer was merged into Persistence and now requires Redis.Redis. +"@effect/experimental/Persistence/Redis#layerResultConfig": + replacement: none + note: Compose Persistence.layerRedis with a config-driven provider such as NodeRedis.layerConfig. +"@effect/experimental/Persistence/Redis#make": + replacement: none + note: V4 exposes Redis-backed layers over the Redis.Redis service, not a constructor that creates an ioredis client directly. diff --git a/migration/annotations/effect__experimental__RateLimiter.yaml b/migration/annotations/effect__experimental__RateLimiter.yaml new file mode 100644 index 000000000..32537e758 --- /dev/null +++ b/migration/annotations/effect__experimental__RateLimiter.yaml @@ -0,0 +1,21 @@ +"@effect/experimental/RateLimiter#layer": + replacement: effect/unstable/persistence/RateLimiter#layer + note: Import layer from the v4 unstable RateLimiter module. +"@effect/experimental/RateLimiter#make": + replacement: effect/unstable/persistence/RateLimiter#make + note: Import make from the v4 unstable RateLimiter module. +"@effect/experimental/RateLimiter#makeSleep": + replacement: effect/unstable/persistence/RateLimiter#sleep + note: The accessor Effect was replaced by sleep; obtain the RateLimiter service and pass it to sleep directly or with its curried overload. +"@effect/experimental/RateLimiter#RateLimiterError": + replacement: effect/unstable/persistence/RateLimiter#RateLimiterError + note: The retained name is now a wrapper error class whose reason is RateLimitExceeded or RateLimitStoreError. +"@effect/experimental/RateLimiter#ErrorTypeId": + replacement: effect/unstable/persistence/RateLimiter#ErrorTypeId + note: Retained as a string brand; the runtime marker now uses the persistence module path. +"@effect/experimental/RateLimiter#TypeId": + replacement: effect/unstable/persistence/RateLimiter#TypeId + note: Import TypeId from the v4 unstable RateLimiter module; it is now a string brand. +"@effect/experimental/RateLimiter#RateLimiterStore": + replacement: RateLimiter.RateLimiterStore + note: "Use the Context.Service class from effect/unstable/persistence/RateLimiter. Custom tokenBucket implementations must return [remaining, elapsedMillis] instead of a number, preserving fractional counts and elapsed refill time." diff --git a/migration/annotations/effect__experimental__RateLimiter__Redis.yaml b/migration/annotations/effect__experimental__RateLimiter__Redis.yaml new file mode 100644 index 000000000..27592db8a --- /dev/null +++ b/migration/annotations/effect__experimental__RateLimiter__Redis.yaml @@ -0,0 +1,9 @@ +"@effect/experimental/RateLimiter/Redis#layerStore": + replacement: effect/unstable/persistence/RateLimiter#layerStoreRedis + note: The Redis adapter was merged into RateLimiter and now requires the generic Redis.Redis service. +"@effect/experimental/RateLimiter/Redis#layerStoreConfig": + replacement: effect/unstable/persistence/RateLimiter#layerStoreRedisConfig + note: Use the merged Redis store config layer; connection configuration belongs to a separate Redis provider. +"@effect/experimental/RateLimiter/Redis#make": + replacement: effect/unstable/persistence/RateLimiter#makeStoreRedis + note: The Redis adapter was merged into RateLimiter and now requires the generic Redis.Redis service. diff --git a/migration/annotations/effect__experimental__Reactivity.yaml b/migration/annotations/effect__experimental__Reactivity.yaml new file mode 100644 index 000000000..621a7f78d --- /dev/null +++ b/migration/annotations/effect__experimental__Reactivity.yaml @@ -0,0 +1,15 @@ +"@effect/experimental/Reactivity#layer": + replacement: effect/unstable/reactivity/Reactivity#layer + note: Import layer from the v4 unstable Reactivity module. +"@effect/experimental/Reactivity#make": + replacement: effect/unstable/reactivity/Reactivity#make + note: Import make from the v4 unstable Reactivity module. +"@effect/experimental/Reactivity#Reactivity": + replacement: effect/unstable/reactivity/Reactivity#Reactivity + note: Use the v4 Reactivity Context.Service; unsafe methods were renamed with an Unsafe suffix. +"@effect/experimental/Reactivity#Reactivity.Service": + replacement: effect/unstable/reactivity/Reactivity#Reactivity["Service"] + note: The named namespace member was removed; derive the service shape from the Context.Service class. +"@effect/experimental/Reactivity#stream": + replacement: effect/unstable/reactivity/Reactivity#stream + note: Import stream from the v4 unstable Reactivity module. diff --git a/migration/annotations/effect__experimental__RequestResolver.yaml b/migration/annotations/effect__experimental__RequestResolver.yaml new file mode 100644 index 000000000..69fe62a7b --- /dev/null +++ b/migration/annotations/effect__experimental__RequestResolver.yaml @@ -0,0 +1,12 @@ +"@effect/experimental/RequestResolver#dataLoader": + replacement: effect/RequestResolver#setDelay + effect/RequestResolver#batchN + note: Pipe the resolver through setDelay(options.window) and batchN(options.maxBatchSize ?? Infinity); the transformation is now pure. +"@effect/experimental/RequestResolver#PersistedRequest": + replacement: effect/Request#Request & effect/unstable/persistence/Persistable#Persistable + note: Intersect a Request with Persistable or define it with Persistable.Class; there is no combined named export. +"@effect/experimental/RequestResolver#PersistedRequest.Any": + replacement: effect/Request#Any & effect/unstable/persistence/Persistable#Any + note: Intersect the Request and Persistable helper types for an arbitrary persisted request. +"@effect/experimental/RequestResolver#persisted": + replacement: effect/RequestResolver#persisted + note: Retained after moving to core RequestResolver; requests now implement Persistable and use Persistence.Persistence, timeToLive is optional, and staleWhileRevalidate is supported. diff --git a/migration/annotations/effect__experimental__Sse.yaml b/migration/annotations/effect__experimental__Sse.yaml new file mode 100644 index 000000000..5f233fe6c --- /dev/null +++ b/migration/annotations/effect__experimental__Sse.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/Sse#RetryTypeId": + replacement: none + note: The Retry identifier is private in v4; use effect/unstable/encoding/Sse#Retry and Retry.is instead of inspecting the brand. diff --git a/migration/annotations/effect__experimental__VariantSchema.yaml b/migration/annotations/effect__experimental__VariantSchema.yaml new file mode 100644 index 000000000..865c3f4d0 --- /dev/null +++ b/migration/annotations/effect__experimental__VariantSchema.yaml @@ -0,0 +1,39 @@ +"@effect/experimental/VariantSchema#Field.Any": + replacement: effect/unstable/schema/VariantSchema#Field.Any + note: Import the retained Field.Any helper type from the v4 unstable VariantSchema module. +"@effect/experimental/VariantSchema#Field.Config": + replacement: effect/unstable/schema/VariantSchema#Field.Config + note: Import the retained Field.Config helper type from the v4 unstable VariantSchema module. +"@effect/experimental/VariantSchema#Field.Fields": + replacement: effect/unstable/schema/VariantSchema#Field.Fields + note: Import the retained Field.Fields helper type from the v4 unstable VariantSchema module. +"@effect/experimental/VariantSchema#Field.ValueAny": + replacement: effect/Schema#Top + note: Use the core Schema.Top constraint for an arbitrary field value schema. +"@effect/experimental/VariantSchema#FieldTypeId": + replacement: none + note: The Field brand is private in v4; use VariantSchema.isField for narrowing. +"@effect/experimental/VariantSchema#fromKey": + replacement: none + note: Field-level fromKey was not ported; for whole-struct encoded-key renaming consider Schema.encodeKeys. +"@effect/experimental/VariantSchema#fromKey.Rename": + replacement: none + note: The fromKey rename helper was not ported; for whole-struct encoded-key renaming consider Schema.encodeKeys. +"@effect/experimental/VariantSchema#isField": + replacement: effect/unstable/schema/VariantSchema#isField + note: Import isField from the v4 unstable VariantSchema module. +"@effect/experimental/VariantSchema#make": + replacement: effect/unstable/schema/VariantSchema#make + note: Import make from the v4 module; FieldOnly and FieldExcept take one key array and Union takes one member array. +"@effect/experimental/VariantSchema#Override": + replacement: effect/unstable/schema/VariantSchema#Override + note: Import Override from the v4 unstable VariantSchema module. +"@effect/experimental/VariantSchema#Struct.Fields": + replacement: effect/unstable/schema/VariantSchema#Struct.Fields + note: Import the retained Struct.Fields helper type from the v4 unstable VariantSchema module. +"@effect/experimental/VariantSchema#TypeId": + replacement: effect/unstable/schema/VariantSchema#TypeId + note: Use the retained runtime value; in type position use typeof VariantSchema.TypeId. +"@effect/experimental/VariantSchema#Extract": + replacement: effect/unstable/schema/VariantSchema#Extract + note: Import the retained helper from the v4 module; its erased schema constraint is Schema.Top. diff --git a/migration/annotations/effect__experimental__index.yaml b/migration/annotations/effect__experimental__index.yaml new file mode 100644 index 000000000..ca42fdd69 --- /dev/null +++ b/migration/annotations/effect__experimental__index.yaml @@ -0,0 +1,3 @@ +"@effect/experimental/index": + replacement: "none" + note: "The package barrel was removed along with the package; import each module from its new effect/unstable/* location per the Import Map." diff --git a/migration/annotations/effect__index.yaml b/migration/annotations/effect__index.yaml new file mode 100644 index 000000000..1f52497db --- /dev/null +++ b/migration/annotations/effect__index.yaml @@ -0,0 +1,12 @@ +"effect/index#Context": + replacement: "Context" + note: "Keep importing Context from effect; v4 removes declaration merges that made tags and references STM subtypes." +"effect/index#Effect": + replacement: "Effect" + note: "Keep importing Effect from effect; v4 removes declaration merges that made Effects structural Sink, Stream, and Channel subtypes." +"effect/index#Either": + replacement: "Result" + note: "Either was renamed to Result; Right and Left became Success and Failure, with Result.succeed and Result.fail constructors." +"effect/index#Option": + replacement: "Option" + note: "Keep importing Option from effect; Option is no longer an Effect or STM subtype, so use Effect.fromOption when needed." diff --git a/migration/annotations/effect__opentelemetry__Logger.yaml b/migration/annotations/effect__opentelemetry__Logger.yaml new file mode 100644 index 000000000..ea049f648 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__Logger.yaml @@ -0,0 +1,9 @@ +"@effect/opentelemetry/Logger#layerLoggerAdd": + replacement: "OtelLogger.layer({ mergeWithExisting: true })" + note: "The Logger module was renamed to OtelLogger; logger installation is now one configurable layer, with true preserving the v3 additive behavior." +"@effect/opentelemetry/Logger#layerLoggerReplace": + replacement: "OtelLogger.layer({ mergeWithExisting: false })" + note: "The Logger module was renamed to OtelLogger; logger installation is now one configurable layer, with false replacing existing loggers." +"@effect/opentelemetry/Logger#make": + replacement: "OtelLogger.make" + note: "The constructor remains in the renamed OtelLogger module." diff --git a/migration/annotations/effect__opentelemetry__Metrics.yaml b/migration/annotations/effect__opentelemetry__Metrics.yaml new file mode 100644 index 000000000..ad26cfa0d --- /dev/null +++ b/migration/annotations/effect__opentelemetry__Metrics.yaml @@ -0,0 +1,3 @@ +"@effect/opentelemetry/Metrics#layer": + replacement: "OtelMetrics.layer" + note: "The Metrics module was renamed to OtelMetrics; the layer remains and now also accepts an optional temporality setting." diff --git a/migration/annotations/effect__opentelemetry__NodeSdk.yaml b/migration/annotations/effect__opentelemetry__NodeSdk.yaml new file mode 100644 index 000000000..4c9ab8a47 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__NodeSdk.yaml @@ -0,0 +1,3 @@ +"@effect/opentelemetry/NodeSdk#Configuration": + replacement: "NodeSdk.Configuration" + note: "The configuration interface remains; v4 adds metricTemporality and loggerMergeWithExisting options." diff --git a/migration/annotations/effect__opentelemetry__Otlp.yaml b/migration/annotations/effect__opentelemetry__Otlp.yaml new file mode 100644 index 000000000..6737fd2d4 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__Otlp.yaml @@ -0,0 +1,3 @@ +"@effect/opentelemetry/Otlp#layer": + replacement: "Otlp.layer" + note: "Moved to effect/unstable/observability/Otlp; replaceLogger was replaced by loggerMergeWithExisting, and metricsTemporality is now configurable." diff --git a/migration/annotations/effect__opentelemetry__OtlpLogger.yaml b/migration/annotations/effect__opentelemetry__OtlpLogger.yaml new file mode 100644 index 000000000..abe3f8563 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__OtlpLogger.yaml @@ -0,0 +1,6 @@ +"@effect/opentelemetry/OtlpLogger#layer": + replacement: "OtlpLogger.layer" + note: "Moved to effect/unstable/observability/OtlpLogger; use mergeWithExisting instead of passing replaceLogger." +"@effect/opentelemetry/OtlpLogger#make": + replacement: "OtlpLogger.make" + note: "The constructor remains in the module moved to effect/unstable/observability/OtlpLogger." diff --git a/migration/annotations/effect__opentelemetry__OtlpMetrics.yaml b/migration/annotations/effect__opentelemetry__OtlpMetrics.yaml new file mode 100644 index 000000000..c6b7b28b8 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__OtlpMetrics.yaml @@ -0,0 +1,6 @@ +"@effect/opentelemetry/OtlpMetrics#layer": + replacement: "OtlpMetrics.layer" + note: "Moved to effect/unstable/observability/OtlpMetrics; the layer now also accepts optional cumulative or delta temporality." +"@effect/opentelemetry/OtlpMetrics#make": + replacement: "OtlpMetrics.make" + note: "Moved to effect/unstable/observability/OtlpMetrics; the constructor now also accepts optional cumulative or delta temporality." diff --git a/migration/annotations/effect__opentelemetry__OtlpResource.yaml b/migration/annotations/effect__opentelemetry__OtlpResource.yaml new file mode 100644 index 000000000..a649a5fb4 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__OtlpResource.yaml @@ -0,0 +1,6 @@ +"@effect/opentelemetry/OtlpResource#make": + replacement: "OtlpResource.make" + note: "The constructor remains in the module moved to effect/unstable/observability/OtlpResource." +"@effect/opentelemetry/OtlpResource#unsafeServiceName": + replacement: "OtlpResource.serviceNameUnsafe" + note: "Moved to effect/unstable/observability/OtlpResource and renamed to follow the v4 unsafe-suffix convention." diff --git a/migration/annotations/effect__opentelemetry__OtlpTracer.yaml b/migration/annotations/effect__opentelemetry__OtlpTracer.yaml new file mode 100644 index 000000000..2487741a9 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__OtlpTracer.yaml @@ -0,0 +1,6 @@ +"@effect/opentelemetry/OtlpTracer#layer": + replacement: "OtlpTracer.layer" + note: "The layer remains in the module moved to effect/unstable/observability/OtlpTracer." +"@effect/opentelemetry/OtlpTracer#make": + replacement: "OtlpTracer.make" + note: "The constructor remains in the module moved to effect/unstable/observability/OtlpTracer." diff --git a/migration/annotations/effect__opentelemetry__Resource.yaml b/migration/annotations/effect__opentelemetry__Resource.yaml new file mode 100644 index 000000000..efe4a6026 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__Resource.yaml @@ -0,0 +1,3 @@ +"@effect/opentelemetry/Resource#Resource": + replacement: "Resource.Resource" + note: "The service remains in @effect/opentelemetry/Resource but is now a Context.Service class rather than a separate Tag interface and value." diff --git a/migration/annotations/effect__opentelemetry__Tracer.yaml b/migration/annotations/effect__opentelemetry__Tracer.yaml new file mode 100644 index 000000000..c6448494f --- /dev/null +++ b/migration/annotations/effect__opentelemetry__Tracer.yaml @@ -0,0 +1,21 @@ +"@effect/opentelemetry/Tracer#layer": + replacement: "OtelTracer.layer" + note: "The Tracer module was renamed to OtelTracer; this still creates an OpenTelemetry tracer and installs it as Effect's tracer." +"@effect/opentelemetry/Tracer#layerTracer": + replacement: "OtelTracer.layerTracer" + note: "The Tracer module was renamed to OtelTracer; this layer still creates only the OpenTelemetry tracer service." +"@effect/opentelemetry/Tracer#make": + replacement: "OtelTracer.make" + note: "The constructor remains in the renamed OtelTracer module." +"@effect/opentelemetry/Tracer#OtelTraceFlags": + replacement: "OtelTracer.OtelTraceFlags" + note: "The service moved with the module and is now declared as a Context.Service class." +"@effect/opentelemetry/Tracer#OtelTracer": + replacement: "OtelTracer.OtelTracer" + note: "The service moved with the renamed module and is now declared as a Context.Service class." +"@effect/opentelemetry/Tracer#OtelTracerProvider": + replacement: "OtelTracer.OtelTracerProvider" + note: "The service moved with the renamed module and is now declared as a Context.Service class." +"@effect/opentelemetry/Tracer#OtelTraceState": + replacement: "OtelTracer.OtelTraceState" + note: "The service moved with the module and is now declared as a Context.Service class." diff --git a/migration/annotations/effect__opentelemetry__WebSdk.yaml b/migration/annotations/effect__opentelemetry__WebSdk.yaml new file mode 100644 index 000000000..5b6fe7aa7 --- /dev/null +++ b/migration/annotations/effect__opentelemetry__WebSdk.yaml @@ -0,0 +1,3 @@ +"@effect/opentelemetry/WebSdk#Configuration": + replacement: "WebSdk.Configuration" + note: "The configuration interface remains; v4 adds metricTemporality and loggerMergeWithExisting options." diff --git a/migration/annotations/effect__opentelemetry__index.yaml b/migration/annotations/effect__opentelemetry__index.yaml new file mode 100644 index 000000000..7ae5d345b --- /dev/null +++ b/migration/annotations/effect__opentelemetry__index.yaml @@ -0,0 +1,3 @@ +"@effect/opentelemetry/index": + replacement: "@effect/opentelemetry" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/opentelemetry package root or import specific modules directly." diff --git a/migration/annotations/effect__platform-browser__BrowserHttpClient.yaml b/migration/annotations/effect__platform-browser__BrowserHttpClient.yaml new file mode 100644 index 000000000..f27bb5e68 --- /dev/null +++ b/migration/annotations/effect__platform-browser__BrowserHttpClient.yaml @@ -0,0 +1,3 @@ +"@effect/platform-browser/BrowserHttpClient#currentXHRResponseType": + replacement: "BrowserHttpClient.CurrentXHRResponseType" + note: "The FiberRef became a defaulted Context.Reference; use withXHRArrayBuffer or provide the reference as a service." diff --git a/migration/annotations/effect__platform-browser__BrowserWorker.yaml b/migration/annotations/effect__platform-browser__BrowserWorker.yaml new file mode 100644 index 000000000..1d91ac92e --- /dev/null +++ b/migration/annotations/effect__platform-browser__BrowserWorker.yaml @@ -0,0 +1,6 @@ +"@effect/platform-browser/BrowserWorker#layerManager": + replacement: "BrowserWorker.layerPlatform" + note: "WorkerManager was removed. Provide WorkerPlatform directly, or use BrowserWorker.layer(spawn) when a Worker.Spawner is also required." +"@effect/platform-browser/BrowserWorker#layerWorker": + replacement: "BrowserWorker.layerPlatform" + note: "PlatformWorker became Worker.WorkerPlatform. The platform-only layer no longer takes a spawn callback; BrowserWorker.layer(spawn) combines platform and spawner layers." diff --git a/migration/annotations/effect__platform-browser__BrowserWorkerRunner.yaml b/migration/annotations/effect__platform-browser__BrowserWorkerRunner.yaml new file mode 100644 index 000000000..c0b9d7bdc --- /dev/null +++ b/migration/annotations/effect__platform-browser__BrowserWorkerRunner.yaml @@ -0,0 +1,3 @@ +"@effect/platform-browser/BrowserWorkerRunner#launch": + replacement: "Layer.launch + RpcServer.layerProtocolWorkerRunner" + note: "The close-latch launcher was removed. Compose BrowserWorkerRunner.layer with the worker RPC server protocol layer and launch the resulting handler layer." diff --git a/migration/annotations/effect__platform-browser__Clipboard.yaml b/migration/annotations/effect__platform-browser__Clipboard.yaml new file mode 100644 index 000000000..f9e8846c0 --- /dev/null +++ b/migration/annotations/effect__platform-browser__Clipboard.yaml @@ -0,0 +1,9 @@ +"@effect/platform-browser/Clipboard#Clipboard": + replacement: "Clipboard.Clipboard" + note: "The service remains, now as a Context.Service with a private brand; normal access and provision are unchanged." +"@effect/platform-browser/Clipboard#ErrorTypeId": + replacement: "none" + note: "The error marker is private in v4; discriminate ClipboardError by its _tag instead." +"@effect/platform-browser/Clipboard#TypeId": + replacement: "none" + note: "The service brand is private in v4; use the Clipboard Context.Service value." diff --git a/migration/annotations/effect__platform-browser__Geolocation.yaml b/migration/annotations/effect__platform-browser__Geolocation.yaml new file mode 100644 index 000000000..f7e226d97 --- /dev/null +++ b/migration/annotations/effect__platform-browser__Geolocation.yaml @@ -0,0 +1,12 @@ +"@effect/platform-browser/Geolocation#ErrorTypeId": + replacement: "none" + note: "The error marker is private in v4; discriminate GeolocationError and its tagged reason." +"@effect/platform-browser/Geolocation#Geolocation": + replacement: "Geolocation.Geolocation" + note: "The service remains, now as a Context.Service with a private brand." +"@effect/platform-browser/Geolocation#GeolocationError": + replacement: "Geolocation.GeolocationError" + note: "The class remains, but reason is now PositionUnavailable, PermissionDenied, or Timeout, with the cause stored on that tagged reason." +"@effect/platform-browser/Geolocation#TypeId": + replacement: "none" + note: "The service marker is private in v4; use the Geolocation Context.Service value." diff --git a/migration/annotations/effect__platform-browser__Permissions.yaml b/migration/annotations/effect__platform-browser__Permissions.yaml new file mode 100644 index 000000000..9d87f2777 --- /dev/null +++ b/migration/annotations/effect__platform-browser__Permissions.yaml @@ -0,0 +1,12 @@ +"@effect/platform-browser/Permissions#ErrorTypeId": + replacement: "none" + note: "The error marker is private in v4; discriminate PermissionsError and its tagged reason." +"@effect/platform-browser/Permissions#Permissions": + replacement: "Permissions.Permissions" + note: "The query service remains, now as a Context.Service with a private brand." +"@effect/platform-browser/Permissions#PermissionsError": + replacement: "Permissions.PermissionsError" + note: "The class remains, but reason is now PermissionsInvalidStateError or PermissionsTypeError, with the cause stored on that tagged reason." +"@effect/platform-browser/Permissions#TypeId": + replacement: "none" + note: "The service marker is private in v4; use the Permissions Context.Service value." diff --git a/migration/annotations/effect__platform-browser__index.yaml b/migration/annotations/effect__platform-browser__index.yaml new file mode 100644 index 000000000..0def0b900 --- /dev/null +++ b/migration/annotations/effect__platform-browser__index.yaml @@ -0,0 +1,3 @@ +"@effect/platform-browser/index": + replacement: "@effect/platform-browser" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/platform-browser package root or import specific modules directly." diff --git a/migration/annotations/effect__platform-bun__BunCommandExecutor.yaml b/migration/annotations/effect__platform-bun__BunCommandExecutor.yaml new file mode 100644 index 000000000..ac93b76bd --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunCommandExecutor.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunCommandExecutor#layer": + replacement: "BunChildProcessSpawner.layer" + note: "CommandExecutor became effect/unstable/process/ChildProcessSpawner; the Bun adapter was renamed and still requires FileSystem and Path." diff --git a/migration/annotations/effect__platform-bun__BunContext.yaml b/migration/annotations/effect__platform-bun__BunContext.yaml new file mode 100644 index 000000000..c4cc6a98e --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunContext.yaml @@ -0,0 +1,6 @@ +"@effect/platform-bun/BunContext#BunContext": + replacement: "BunServices.BunServices" + note: "The aggregate was renamed and now provides ChildProcessSpawner, Crypto, FileSystem, Path, Stdio, and Terminal; add BunWorker separately when needed." +"@effect/platform-bun/BunContext#layer": + replacement: "BunServices.layer" + note: "Use the renamed aggregate layer; worker services are no longer included." diff --git a/migration/annotations/effect__platform-bun__BunFileSystem__ParcelWatcher.yaml b/migration/annotations/effect__platform-bun__BunFileSystem__ParcelWatcher.yaml new file mode 100644 index 000000000..a90b9e375 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunFileSystem__ParcelWatcher.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunFileSystem/ParcelWatcher#layer": + replacement: "BunFileSystem.layer" + note: "The Parcel watcher adapter was removed. BunFileSystem.layer uses the built-in node:fs-compatible watcher; provide a custom FileSystem.WatchBackend for specialized behavior." diff --git a/migration/annotations/effect__platform-bun__BunHttpPlatform.yaml b/migration/annotations/effect__platform-bun__BunHttpPlatform.yaml new file mode 100644 index 000000000..2fdea4a15 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunHttpPlatform.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunHttpPlatform#make": + replacement: "BunHttpPlatform.layer" + note: "The Bun-specific constructor is private; provide the public layer and consume HttpPlatform.HttpPlatform." diff --git a/migration/annotations/effect__platform-bun__BunHttpServer.yaml b/migration/annotations/effect__platform-bun__BunHttpServer.yaml new file mode 100644 index 000000000..95b7882e1 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunHttpServer.yaml @@ -0,0 +1,6 @@ +"@effect/platform-bun/BunHttpServer#layerContext": + replacement: "BunHttpServer.layerHttpServices" + note: "Direct rename; it provides HttpPlatform, Etag.Generator, and BunServices." +"@effect/platform-bun/BunHttpServer#ServeOptions": + replacement: "BunHttpServer.ServeOptions" + note: "The alias remains, but R is now a route-key string union and routes uses Bun.Serve.Routes; update old route-map generic arguments or infer R from routes." diff --git a/migration/annotations/effect__platform-bun__BunHttpServerRequest.yaml b/migration/annotations/effect__platform-bun__BunHttpServerRequest.yaml new file mode 100644 index 000000000..9f720a621 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunHttpServerRequest.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunHttpServerRequest#toRequest": + replacement: "BunHttpServerRequest.toBunServerRequest" + note: "Direct rename with the more precise Bun.BunRequest result type." diff --git a/migration/annotations/effect__platform-bun__BunKeyValueStore.yaml b/migration/annotations/effect__platform-bun__BunKeyValueStore.yaml new file mode 100644 index 000000000..000285be3 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunKeyValueStore.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunKeyValueStore": + replacement: "effect/unstable/persistence/KeyValueStore" + note: "layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via BunServices.layer or BunFileSystem.layer with BunPath.layer." diff --git a/migration/annotations/effect__platform-bun__BunSink.yaml b/migration/annotations/effect__platform-bun__BunSink.yaml new file mode 100644 index 000000000..3d2ed68a6 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunSink.yaml @@ -0,0 +1,9 @@ +"@effect/platform-bun/BunSink#stderr": + replacement: "stdio.stderr() from Stdio.Stdio" + note: "Process stdio moved behind effect/Stdio; provide BunStdio.layer. stderr remains a Sink and can be configured with endOnDone." +"@effect/platform-bun/BunSink#stdin": + replacement: "stdio.stdin from Stdio.Stdio" + note: "Standard input is correctly modeled as a Stream in v4, not a writable Sink; manually adapt process.stdin only if writing to it was intentional." +"@effect/platform-bun/BunSink#stdout": + replacement: "stdio.stdout() from Stdio.Stdio" + note: "Process stdio moved behind effect/Stdio; provide BunStdio.layer. stdout remains a Sink." diff --git a/migration/annotations/effect__platform-bun__BunSocket.yaml b/migration/annotations/effect__platform-bun__BunSocket.yaml new file mode 100644 index 000000000..49ab6a192 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunSocket.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunSocket#NetSocket": + replacement: "BunSocket.NetSocket" + note: "The identifier remains, but the old interface/tag pair is now one Context.Service for node:net.Socket." diff --git a/migration/annotations/effect__platform-bun__BunSocketServer.yaml b/migration/annotations/effect__platform-bun__BunSocketServer.yaml new file mode 100644 index 000000000..8d2679ede --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunSocketServer.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/BunSocketServer#IncomingMessage": + replacement: "BunSocketServer.IncomingMessage" + note: "The identifier remains and is now a Context.Service for node:http.IncomingMessage." diff --git a/migration/annotations/effect__platform-bun__BunStream.yaml b/migration/annotations/effect__platform-bun__BunStream.yaml new file mode 100644 index 000000000..dee586b78 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunStream.yaml @@ -0,0 +1,15 @@ +"@effect/platform-bun/BunStream#FromReadableOptions": + replacement: "Pick[0], \"chunkSize\" | \"closeOnDone\">" + note: "The named interface was inlined into the constructor options; chunkSize is now a number and the full options also contain evaluate and onError. The ignored bufferSize option was removed." +"@effect/platform-bun/BunStream#FromWritableOptions": + replacement: "Pick[0], \"endOnDone\" | \"encoding\">" + note: "The named interface was inlined into BunSink.fromWritable and duplex constructor options." +"@effect/platform-bun/BunStream#stderr": + replacement: "stdio.stderr() from Stdio.Stdio" + note: "Standard error is correctly modeled as a Sink in v4. Explicitly adapt process.stderr with BunStream.fromReadable only to preserve the old unusual read behavior." +"@effect/platform-bun/BunStream#stdin": + replacement: "stdio.stdin from Stdio.Stdio" + note: "Standard input moved to the Stdio service; provide BunStdio.layer. Its stream exposes PlatformError instead of dying." +"@effect/platform-bun/BunStream#stdout": + replacement: "stdio.stdout() from Stdio.Stdio" + note: "Standard output is correctly modeled as a Sink in v4. Explicitly adapt process.stdout with BunStream.fromReadable only to preserve the old unusual read behavior." diff --git a/migration/annotations/effect__platform-bun__BunWorker.yaml b/migration/annotations/effect__platform-bun__BunWorker.yaml new file mode 100644 index 000000000..9757ec982 --- /dev/null +++ b/migration/annotations/effect__platform-bun__BunWorker.yaml @@ -0,0 +1,6 @@ +"@effect/platform-bun/BunWorker#layerManager": + replacement: "BunWorker.layerPlatform" + note: "WorkerManager was removed. Provide WorkerPlatform directly, or use BunWorker.layer(spawn) when a Worker.Spawner is also required." +"@effect/platform-bun/BunWorker#layerWorker": + replacement: "BunWorker.layerPlatform" + note: "PlatformWorker became Worker.WorkerPlatform; BunWorker.layer(spawn) combines the platform and spawner layers." diff --git a/migration/annotations/effect__platform-bun__index.yaml b/migration/annotations/effect__platform-bun__index.yaml new file mode 100644 index 000000000..ad9ef5d50 --- /dev/null +++ b/migration/annotations/effect__platform-bun__index.yaml @@ -0,0 +1,3 @@ +"@effect/platform-bun/index": + replacement: "@effect/platform-bun" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/platform-bun package root or import specific modules directly." diff --git a/migration/annotations/effect__platform-node-shared__NodeCommandExecutor.yaml b/migration/annotations/effect__platform-node-shared__NodeCommandExecutor.yaml new file mode 100644 index 000000000..87da42189 --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeCommandExecutor.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node-shared/NodeCommandExecutor#layer": + replacement: "NodeChildProcessSpawner.layer" + note: "CommandExecutor became effect/unstable/process/ChildProcessSpawner; the Node adapter was renamed and still requires FileSystem and Path." diff --git a/migration/annotations/effect__platform-node-shared__NodeFileSystem__ParcelWatcher.yaml b/migration/annotations/effect__platform-node-shared__NodeFileSystem__ParcelWatcher.yaml new file mode 100644 index 000000000..cf2eb77b4 --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeFileSystem__ParcelWatcher.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node-shared/NodeFileSystem/ParcelWatcher#layer": + replacement: "NodeFileSystem.layer" + note: "The Parcel watcher adapter was removed. NodeFileSystem.layer uses node:fs.watch; provide a custom FileSystem.WatchBackend for specialized behavior." diff --git a/migration/annotations/effect__platform-node-shared__NodeKeyValueStore.yaml b/migration/annotations/effect__platform-node-shared__NodeKeyValueStore.yaml new file mode 100644 index 000000000..8215e91d1 --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeKeyValueStore.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node-shared/NodeKeyValueStore": + replacement: "effect/unstable/persistence/KeyValueStore" + note: "layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via NodeServices.layer or NodeFileSystem.layer with NodePath.layer." diff --git a/migration/annotations/effect__platform-node-shared__NodeMultipart.yaml b/migration/annotations/effect__platform-node-shared__NodeMultipart.yaml new file mode 100644 index 000000000..d130beed3 --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeMultipart.yaml @@ -0,0 +1,6 @@ +"@effect/platform-node-shared/NodeMultipart#fileToReadable": + replacement: "@effect/platform-node/NodeMultipart#fileToReadable" + note: "The Node multipart implementation moved from @effect/platform-node-shared to @effect/platform-node; its behavior remains." +"@effect/platform-node-shared/NodeMultipart#stream": + replacement: "@effect/platform-node/NodeMultipart#stream" + note: "The Node multipart implementation moved from @effect/platform-node-shared to @effect/platform-node; the source and headers call shape remains." diff --git a/migration/annotations/effect__platform-node-shared__NodeSink.yaml b/migration/annotations/effect__platform-node-shared__NodeSink.yaml new file mode 100644 index 000000000..2f59e6685 --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeSink.yaml @@ -0,0 +1,9 @@ +"@effect/platform-node-shared/NodeSink#stderr": + replacement: "stdio.stderr() from Stdio.Stdio" + note: "Standard error moved to effect/Stdio; provide NodeStdio.layer or NodeServices.layer." +"@effect/platform-node-shared/NodeSink#stdin": + replacement: "NodeSink.fromWritable({ evaluate: () => process.stdin, onError: ... })" + note: "There is no Stdio sink because stdin is a readable stream in v4; use a manual adapter only if writing to process.stdin was intentional." +"@effect/platform-node-shared/NodeSink#stdout": + replacement: "stdio.stdout() from Stdio.Stdio" + note: "Standard output moved to effect/Stdio; provide NodeStdio.layer or NodeServices.layer." diff --git a/migration/annotations/effect__platform-node-shared__NodeSocket.yaml b/migration/annotations/effect__platform-node-shared__NodeSocket.yaml new file mode 100644 index 000000000..9994d65cd --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeSocket.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node-shared/NodeSocket#NetSocket": + replacement: "NodeSocket.NetSocket" + note: "The identifier remains, but the old interface/tag pair is now one Context.Service; use NodeSocket.NetSocket[\"Service\"] for the node:net.Socket value type." diff --git a/migration/annotations/effect__platform-node-shared__NodeStream.yaml b/migration/annotations/effect__platform-node-shared__NodeStream.yaml new file mode 100644 index 000000000..8ee6af441 --- /dev/null +++ b/migration/annotations/effect__platform-node-shared__NodeStream.yaml @@ -0,0 +1,15 @@ +"@effect/platform-node-shared/NodeStream#FromReadableOptions": + replacement: "{ readonly chunkSize?: number; readonly closeOnDone?: boolean }" + note: "The named interface was removed and its fields were inlined into readable constructor options; chunkSize narrowed from SizeInput to number." +"@effect/platform-node-shared/NodeStream#FromWritableOptions": + replacement: "{ readonly endOnDone?: boolean; readonly encoding?: BufferEncoding }" + note: "The named interface was removed and its fields were inlined into NodeSink and duplex constructor options." +"@effect/platform-node-shared/NodeStream#stderr": + replacement: "NodeStream.fromReadable({ evaluate: () => process.stderr, closeOnDone: false }).pipe(Stream.orDie)" + note: "This preserves the unusual v3 read behavior; for normal error output use the stdio.stderr() Sink from effect/Stdio." +"@effect/platform-node-shared/NodeStream#stdin": + replacement: "stdio.stdin from Stdio.Stdio" + note: "Standard input moved to effect/Stdio; provide NodeStdio.layer or NodeServices.layer. The service stream exposes PlatformError instead of dying." +"@effect/platform-node-shared/NodeStream#stdout": + replacement: "NodeStream.fromReadable({ evaluate: () => process.stdout, closeOnDone: false }).pipe(Stream.orDie)" + note: "This preserves the unusual v3 read behavior; for normal output use the stdio.stdout() Sink from effect/Stdio." diff --git a/migration/annotations/effect__platform-node__NodeCommandExecutor.yaml b/migration/annotations/effect__platform-node__NodeCommandExecutor.yaml new file mode 100644 index 000000000..20a52d962 --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeCommandExecutor.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node/NodeCommandExecutor#layer": + replacement: "NodeChildProcessSpawner.layer" + note: "CommandExecutor became ChildProcessSpawner; use the @effect/platform-node/NodeChildProcessSpawner re-export." diff --git a/migration/annotations/effect__platform-node__NodeContext.yaml b/migration/annotations/effect__platform-node__NodeContext.yaml new file mode 100644 index 000000000..670e8ac4c --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeContext.yaml @@ -0,0 +1,6 @@ +"@effect/platform-node/NodeContext#layer": + replacement: "NodeServices.layer" + note: "The aggregate was renamed and now provides ChildProcessSpawner, Crypto, FileSystem, Path, Stdio, and Terminal; add NodeWorker separately when needed." +"@effect/platform-node/NodeContext#NodeContext": + replacement: "NodeServices.NodeServices" + note: "Use the renamed service union; it replaces CommandExecutor with ChildProcessSpawner, adds Crypto and Stdio, and omits WorkerManager." diff --git a/migration/annotations/effect__platform-node__NodeFileSystem__ParcelWatcher.yaml b/migration/annotations/effect__platform-node__NodeFileSystem__ParcelWatcher.yaml new file mode 100644 index 000000000..55d29969b --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeFileSystem__ParcelWatcher.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node/NodeFileSystem/ParcelWatcher#layer": + replacement: "NodeFileSystem.layer" + note: "The Parcel watcher adapter was removed. Native node:fs.watch support is built in; FileSystem.WatchBackend is the extension point." diff --git a/migration/annotations/effect__platform-node__NodeHttpClient.yaml b/migration/annotations/effect__platform-node__NodeHttpClient.yaml new file mode 100644 index 000000000..a706f503b --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeHttpClient.yaml @@ -0,0 +1,33 @@ +"@effect/platform-node/NodeHttpClient#agentLayer": + replacement: "NodeHttpClient.layerAgent" + note: "Direct rename; it provides the default scoped Node HTTP and HTTPS agents." +"@effect/platform-node/NodeHttpClient#Dispatcher": + replacement: "NodeHttpClient.Dispatcher" + note: "The identifier remains but is now a Context.Service class; use Dispatcher[\"Service\"] for the concrete Undici dispatcher type." +"@effect/platform-node/NodeHttpClient#dispatcherLayer": + replacement: "NodeHttpClient.layerDispatcher" + note: "Direct rename; the layer owns and finalizes a scoped Undici Agent." +"@effect/platform-node/NodeHttpClient#HttpAgent": + replacement: "NodeHttpClient.HttpAgent" + note: "The identifier remains but is now a Context.Service class; use HttpAgent[\"Service\"] for the concrete http/https agent pair." +"@effect/platform-node/NodeHttpClient#HttpAgentTypeId": + replacement: "none" + note: "The public marker was removed; the HttpAgent Context.Service class supplies service identity." +"@effect/platform-node/NodeHttpClient#layer": + replacement: "NodeHttpClient.layerNodeHttp" + note: "Use the renamed node:http/node:https backend layer; choose layerUndici only when intentionally changing backends." +"@effect/platform-node/NodeHttpClient#layerUndiciWithoutDispatcher": + replacement: "NodeHttpClient.layerUndiciNoDispatcher" + note: "Direct rename; the layer still requires NodeHttpClient.Dispatcher." +"@effect/platform-node/NodeHttpClient#layerWithoutAgent": + replacement: "NodeHttpClient.layerNodeHttpNoAgent" + note: "Direct rename; the node:http client layer still requires NodeHttpClient.HttpAgent." +"@effect/platform-node/NodeHttpClient#make": + replacement: "NodeHttpClient.makeNodeHttp" + note: "Direct rename of the node:http/node:https client constructor." +"@effect/platform-node/NodeHttpClient#makeAgentLayer": + replacement: "NodeHttpClient.layerAgentOptions" + note: "Direct rename; it accepts Https.AgentOptions and scopes both agents." +"@effect/platform-node/NodeHttpClient#UndiciRequestOptions": + replacement: "NodeHttpClient.UndiciOptions" + note: "The required Context.Tag became a defaulted Context.Reference>; override it with Effect.provideService." diff --git a/migration/annotations/effect__platform-node__NodeHttpServer.yaml b/migration/annotations/effect__platform-node__NodeHttpServer.yaml new file mode 100644 index 000000000..7de86ada6 --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeHttpServer.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node/NodeHttpServer#layerContext": + replacement: "NodeHttpServer.layerHttpServices" + note: "Direct rename; it provides NodeServices, HttpPlatform, and Etag.Generator, without the removed WorkerManager." diff --git a/migration/annotations/effect__platform-node__NodeKeyValueStore.yaml b/migration/annotations/effect__platform-node__NodeKeyValueStore.yaml new file mode 100644 index 000000000..64cc6a8b5 --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeKeyValueStore.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node/NodeKeyValueStore": + replacement: "effect/unstable/persistence/KeyValueStore" + note: "layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via NodeServices.layer or NodeFileSystem.layer with NodePath.layer." diff --git a/migration/annotations/effect__platform-node__NodeWorker.yaml b/migration/annotations/effect__platform-node__NodeWorker.yaml new file mode 100644 index 000000000..95a7a7fe3 --- /dev/null +++ b/migration/annotations/effect__platform-node__NodeWorker.yaml @@ -0,0 +1,6 @@ +"@effect/platform-node/NodeWorker#layerManager": + replacement: "NodeWorker.layerPlatform" + note: "WorkerManager was removed. Provide WorkerPlatform directly, or use NodeWorker.layer(spawn) when a Worker.Spawner is also required." +"@effect/platform-node/NodeWorker#layerWorker": + replacement: "NodeWorker.layerPlatform" + note: "PlatformWorker became Worker.WorkerPlatform; NodeWorker.layer(spawn) combines the platform and spawner layers." diff --git a/migration/annotations/effect__platform-node__Undici.yaml b/migration/annotations/effect__platform-node__Undici.yaml new file mode 100644 index 000000000..04d45d0fc --- /dev/null +++ b/migration/annotations/effect__platform-node__Undici.yaml @@ -0,0 +1,117 @@ +"@effect/platform-node/Undici#Agent": + replacement: "undici.Agent" + note: "Import the upstream Agent directly. Undici 8 removes maxRedirections and option-level interceptors, adds maxOrigins, and enables HTTP/2 negotiation unless allowH2 is false." +"@effect/platform-node/Undici#Agent.DispatchOptions": + replacement: "undici.Agent.DispatchOptions" + note: "Import the same Agent namespace type from undici; Undici 8 removes maxRedirections." +"@effect/platform-node/Undici#Agent.Options": + replacement: "undici.Agent.Options" + note: "Import the same Agent namespace type; Undici 8 removes maxRedirections and option-level interceptors, adds maxOrigins, and uses dispatcher.compose for interceptors." +"@effect/platform-node/Undici#buildConnector": + replacement: "undici.buildConnector" + note: "Import the upstream function directly; Undici 8 expands connector options with preferH2, typeOfService, and socketPath handling." +"@effect/platform-node/Undici#buildConnector.BuildOptions": + replacement: "undici.buildConnector.BuildOptions" + note: "Import the same buildConnector namespace type; Undici 8 adds preferH2 and typeOfService." +"@effect/platform-node/Undici#Client": + replacement: "undici.Client" + note: "Import the upstream Client directly; custom handlers must use Undici 8's controller-based v2 handler API." +"@effect/platform-node/Undici#Client.Options": + replacement: "undici.Client.Options" + note: "Import the same Client namespace type; Undici 8 removes maxRedirections and option-level interceptors and adds WebSocket and HTTP/2 options." +"@effect/platform-node/Undici#Client.OptionsInterceptors": + replacement: "undici.Dispatcher.DispatcherComposeInterceptor + dispatcher.compose" + note: "Undici 8 removed option-level interceptor tuples; keep DispatcherComposeInterceptor functions and apply them after construction with dispatcher.compose(...)." +"@effect/platform-node/Undici#default.cacheStores": + replacement: "undici.cacheStores" + note: "Use Undici 8's named cacheStores export instead of reaching through the default aggregate." +"@effect/platform-node/Undici#deleteCookie": + replacement: "undici.deleteCookie" + note: "Import the upstream function directly; its optional attributes use path and domain and no longer include name." +"@effect/platform-node/Undici#DiagnosticsChannel": + replacement: "undici.DiagnosticsChannel" + note: "Import this type-only namespace from undici; subscribe at runtime through node:diagnostics_channel using Undici's channel names." +"@effect/platform-node/Undici#DiagnosticsChannel.ClientConnectErrorMessage": + replacement: "undici.DiagnosticsChannel.ClientConnectErrorMessage" + note: "Import the same type-only namespace member from undici; runtime delivery uses node:diagnostics_channel." +"@effect/platform-node/Undici#DiagnosticsChannel.Error": + replacement: "Error" + note: "Undici 8 removed this unknown alias; diagnostic error fields now use the built-in Error type." +"@effect/platform-node/Undici#DiagnosticsChannel.RequestErrorMessage": + replacement: "undici.DiagnosticsChannel.RequestErrorMessage" + note: "Import the same type-only namespace member; its error field is the built-in Error type in Undici 8." +"@effect/platform-node/Undici#Dispatcher": + replacement: "undici.Dispatcher" + note: "Import the upstream Dispatcher directly; custom dispatchers must adopt Undici 8's controller-based v2 handler API." +"@effect/platform-node/Undici#Dispatcher.ConnectOptions": + replacement: "undici.Dispatcher.ConnectOptions" + note: "Import the same Dispatcher namespace type; Undici 8 removes maxRedirections and redirectionLimitReached." +"@effect/platform-node/Undici#Dispatcher.DispatchHandler": + replacement: "undici.Dispatcher.DispatchHandler" + note: "Use Undici 8's onRequestStart/onResponseStart/onResponseData/onResponseEnd/onResponseError callbacks and controller pause/resume/abort methods." +"@effect/platform-node/Undici#Dispatcher.DispatchOptions": + replacement: "undici.Dispatcher.DispatchOptions" + note: "Import the same namespace type; Undici 8 removes throwOnError, adds typeOfService, and handles redirects through composed interceptors." +"@effect/platform-node/Undici#Dispatcher.RequestOptions": + replacement: "undici.Dispatcher.RequestOptions" + note: "Import the same namespace type; Undici 8 removes maxRedirections and redirectionLimitReached, so compose a redirect interceptor when needed." +"@effect/platform-node/Undici#Dispatcher.UpgradeOptions": + replacement: "undici.Dispatcher.UpgradeOptions" + note: "Import the same namespace type; Undici 8 removes maxRedirections and redirectionLimitReached." +"@effect/platform-node/Undici#errors": + replacement: "undici.errors" + note: "Import the upstream errors object directly; individual classes follow the Undici 8 API." +"@effect/platform-node/Undici#errors.ResponseStatusCodeError": + replacement: "undici.errors.ResponseError" + note: "Undici 8 replaced ResponseStatusCodeError with ResponseError; construct it with message, statusCode, and the headers/body object." +"@effect/platform-node/Undici#H2CClient": + replacement: "undici.H2CClient" + note: "Import the upstream cleartext HTTP/2 client directly; callbacks follow Undici 8's handler API." +"@effect/platform-node/Undici#H2CClient.Options": + replacement: "undici.H2CClient.Options" + note: "Import the same H2CClient namespace type; Undici 8 removes maxRedirections." +"@effect/platform-node/Undici#interceptors": + replacement: "undici.interceptors" + note: "Import the upstream interceptors object and apply returned interceptors with dispatcher.compose(...)." +"@effect/platform-node/Undici#interceptors.DNSInterceptorOpts": + replacement: "undici.interceptors.DNSInterceptorOpts" + note: "Import the same namespace type; Undici 8 lookup receives an origin URL and supports optional DNS storage." +"@effect/platform-node/Undici#interceptors.DNSInterceptorOriginRecords": + replacement: "undici.interceptors.DNSInterceptorOriginRecords" + note: "Import the same namespace type, but adopt Undici 8's shape with IPv4 and IPv6 entries nested under records." +"@effect/platform-node/Undici#interceptors.RedirectInterceptorOpts": + replacement: "undici.interceptors.RedirectInterceptorOpts" + note: "Import the same namespace type; Undici 8 adds throwOnMaxRedirect and redirect header-stripping options." +"@effect/platform-node/Undici#MessageEvent": + replacement: "undici.MessageEvent" + note: "Import Undici's named constructor/type directly to preserve the installed package identity." +"@effect/platform-node/Undici#MessageEventInit": + replacement: "undici.MessageEventInit" + note: "Import the upstream type directly; message ports and source use MessagePort instances in Undici 8." +"@effect/platform-node/Undici#Pool": + replacement: "undici.Pool" + note: "Import the upstream Pool directly and apply interceptors after construction with pool.compose(...)." +"@effect/platform-node/Undici#Pool.Options": + replacement: "undici.Pool.Options" + note: "Import the same Pool namespace type; Undici 8 removes the interceptors option in favor of pool.compose(...)." +"@effect/platform-node/Undici#ProxyAgent": + replacement: "undici.ProxyAgent" + note: "Import the upstream ProxyAgent directly; inherited Agent options and handlers follow Undici 8." +"@effect/platform-node/Undici#ProxyAgent.Options": + replacement: "undici.ProxyAgent.Options" + note: "Import the same ProxyAgent namespace type; Undici 8 types proxy headers as OutgoingHttpHeaders." +"@effect/platform-node/Undici#RedirectHandler": + replacement: "undici.RedirectHandler" + note: "Import the upstream class; Undici 8 removes redirectionLimitReached from the constructor and adds static buildDispatch." +"@effect/platform-node/Undici#Request": + replacement: "undici.Request" + note: "Import Undici's named Request directly; clone is a method in Undici 8." +"@effect/platform-node/Undici#Response": + replacement: "undici.Response" + note: "Import Undici's named Response directly; clone is a method and Response.redirect status is optional in Undici 8." +"@effect/platform-node/Undici#SpecIterable": + replacement: "undici.SpecIterable" + note: "Import the upstream type directly; its iterator returns SpecIterableIterator in Undici 8." +"@effect/platform-node/Undici#SpecIterableIterator": + replacement: "undici.SpecIterableIterator" + note: "Import the upstream type directly; it extends SpecIteratorObject and includes iterator-helper methods in Undici 8." diff --git a/migration/annotations/effect__platform-node__index.yaml b/migration/annotations/effect__platform-node__index.yaml new file mode 100644 index 000000000..3e30ad165 --- /dev/null +++ b/migration/annotations/effect__platform-node__index.yaml @@ -0,0 +1,3 @@ +"@effect/platform-node/index": + replacement: "@effect/platform-node" + note: "The explicit /index entrypoint was removed; import Node-prefixed namespaces from the @effect/platform-node package root or import specific modules directly. Undici is no longer in the root barrel; import it from @effect/platform-node/Undici or directly from undici." diff --git a/migration/annotations/effect__platform.yaml b/migration/annotations/effect__platform.yaml new file mode 100644 index 000000000..4653d0b89 --- /dev/null +++ b/migration/annotations/effect__platform.yaml @@ -0,0 +1,3 @@ +"@effect/platform": + replacement: "none" + note: "The @effect/platform package was folded into the effect package: core services live in effect root modules (e.g. effect/FileSystem, effect/Path) and HTTP in effect/unstable/http; follow the Import Map for each module." diff --git a/migration/annotations/effect__platform__ChannelSchema.yaml b/migration/annotations/effect__platform__ChannelSchema.yaml new file mode 100644 index 000000000..7a48fbcb2 --- /dev/null +++ b/migration/annotations/effect__platform__ChannelSchema.yaml @@ -0,0 +1,9 @@ +"@effect/platform/ChannelSchema#decode": + replacement: "ChannelSchema.decode" + note: "The API moved to effect/ChannelSchema; update the import and adapt the schema to the v4 Schema.Constraint model." +"@effect/platform/ChannelSchema#duplex": + replacement: "ChannelSchema.duplex" + note: "The API moved to effect/ChannelSchema; update the import and adapt the schema to the v4 Schema.Constraint model." +"@effect/platform/ChannelSchema#encode": + replacement: "ChannelSchema.encode" + note: "The API moved to effect/ChannelSchema; update the import and adapt the schema to the v4 Schema.Constraint model." diff --git a/migration/annotations/effect__platform__Command.yaml b/migration/annotations/effect__platform__Command.yaml new file mode 100644 index 000000000..af9a59b08 --- /dev/null +++ b/migration/annotations/effect__platform__Command.yaml @@ -0,0 +1,54 @@ +"@effect/platform/Command#Command": + replacement: "ChildProcess.Command" + note: "Commands moved to effect/unstable/process/ChildProcess and are now directly Effectable to spawn a ChildProcessHandle." +"@effect/platform/Command#Command.Input": + replacement: "ChildProcess.CommandInput" + note: "The standard-input configuration type was flattened out of the Command namespace." +"@effect/platform/Command#Command.Proto": + replacement: "ChildProcess.StandardCommand | ChildProcess.PipedCommand" + note: "The public command prototype was removed; narrow the Command union to its StandardCommand or PipedCommand interfaces." +"@effect/platform/Command#CommandTypeId": + replacement: "none" + note: "The command type-id alias is internal in v4; use ChildProcess.Command or ChildProcess.isCommand instead." +"@effect/platform/Command#env": + replacement: "ChildProcess.setEnv" + note: "Use the renamed command combinator." +"@effect/platform/Command#exitCode": + replacement: "ChildProcessSpawner.ChildProcessSpawner.exitCode" + note: "Obtain the ChildProcessSpawner service and call exitCode, or spawn the Effectable command and read the handle exitCode." +"@effect/platform/Command#feed": + replacement: "ChildProcess.CommandOptions[\"stdin\"]" + note: "The feed combinator was removed; pass a Stream as stdin when constructing the command." +"@effect/platform/Command#flatten": + replacement: "none" + note: "No flatten helper remains; inspect StandardCommand and PipedCommand recursively when command structure is required." +"@effect/platform/Command#lines": + replacement: "ChildProcessSpawner.ChildProcessSpawner.lines" + note: "Output collection moved onto the ChildProcessSpawner service." +"@effect/platform/Command#runInShell": + replacement: "ChildProcess.CommandOptions[\"shell\"]" + note: "Set shell when calling ChildProcess.make; there is no post-construction shell combinator." +"@effect/platform/Command#start": + replacement: "ChildProcessSpawner.ChildProcessSpawner.spawn" + note: "Use the spawner service, or yield the Effectable ChildProcess.Command directly, to obtain a ChildProcessHandle." +"@effect/platform/Command#stderr": + replacement: "ChildProcess.CommandOptions[\"stderr\"]" + note: "Configure stderr in ChildProcess.make options; the standalone combinator was removed." +"@effect/platform/Command#stdin": + replacement: "ChildProcess.CommandOptions[\"stdin\"]" + note: "Configure stdin in ChildProcess.make options; the standalone combinator was removed." +"@effect/platform/Command#stdout": + replacement: "ChildProcess.CommandOptions[\"stdout\"]" + note: "Configure stdout in ChildProcess.make options; the standalone combinator was removed." +"@effect/platform/Command#stream": + replacement: "ChildProcessSpawner.ChildProcessSpawner.spawn + ChildProcessHandle.stdout" + note: "Spawn within a scope and consume the returned handle's stdout stream." +"@effect/platform/Command#streamLines": + replacement: "ChildProcessSpawner.ChildProcessSpawner.streamLines" + note: "Text-line streaming moved onto the ChildProcessSpawner service." +"@effect/platform/Command#string": + replacement: "ChildProcessSpawner.ChildProcessSpawner.string" + note: "Output collection moved onto the ChildProcessSpawner service." +"@effect/platform/Command#workingDirectory": + replacement: "ChildProcess.setCwd" + note: "Use the renamed command combinator." diff --git a/migration/annotations/effect__platform__CommandExecutor.yaml b/migration/annotations/effect__platform__CommandExecutor.yaml new file mode 100644 index 000000000..976d72eb2 --- /dev/null +++ b/migration/annotations/effect__platform__CommandExecutor.yaml @@ -0,0 +1,18 @@ +"@effect/platform/CommandExecutor#CommandExecutor": + replacement: "ChildProcessSpawner.ChildProcessSpawner" + note: "The executor service moved to effect/unstable/process/ChildProcessSpawner and was renamed." +"@effect/platform/CommandExecutor#makeExecutor": + replacement: "ChildProcessSpawner.make" + note: "Use the renamed constructor; it derives output helpers from a spawn implementation." +"@effect/platform/CommandExecutor#Process": + replacement: "ChildProcessSpawner.ChildProcessHandle" + note: "Running-process handles were renamed and moved to ChildProcessSpawner." +"@effect/platform/CommandExecutor#Process.Id": + replacement: "ChildProcessSpawner.ProcessId" + note: "The process-id brand is now exported directly." +"@effect/platform/CommandExecutor#ProcessTypeId": + replacement: "none" + note: "The ChildProcessHandle marker is internal in v4; use the ChildProcessHandle interface." +"@effect/platform/CommandExecutor#TypeId": + replacement: "none" + note: "The Context.Service class replaces the public executor type-id alias." diff --git a/migration/annotations/effect__platform__Cookies.yaml b/migration/annotations/effect__platform__Cookies.yaml new file mode 100644 index 000000000..ef4edc1e9 --- /dev/null +++ b/migration/annotations/effect__platform__Cookies.yaml @@ -0,0 +1,24 @@ +"@effect/platform/Cookies#CookieTypeId": + replacement: "Cookies.isCookie" + note: "The cookie brand is private in v4; use the public refinement instead of reading the type-id symbol." +"@effect/platform/Cookies#ErrorTypeId": + replacement: "Cookies.CookiesError" + note: "The error brand is private in v4; identify the exported error class instead." +"@effect/platform/Cookies#remove": + replacement: "Cookies.remove" + note: "Retained with the same dual name-based removal signature." +"@effect/platform/Cookies#TypeId": + replacement: "Cookies.isCookies" + note: "The collection brand is private in v4; use the public refinement instead." +"@effect/platform/Cookies#unsafeMakeCookie": + replacement: "Cookies.makeCookieUnsafe" + note: "Renamed to put Unsafe last; it still throws on invalid cookie data." +"@effect/platform/Cookies#unsafeSet": + replacement: "Cookies.setUnsafe" + note: "Renamed to put Unsafe last; the dual throwing behavior is retained." +"@effect/platform/Cookies#unsafeSetAll": + replacement: "Cookies.setAllUnsafe" + note: "Renamed to put Unsafe last; the dual all-or-throw behavior is retained." +"@effect/platform/Cookies#CookiesError": + replacement: "Cookies.CookiesError" + note: "The error tag is CookiesError rather than CookieError. Update catchTag calls and _tag comparisons; validation details are in the reason field." diff --git a/migration/annotations/effect__platform__Effectify.yaml b/migration/annotations/effect__platform__Effectify.yaml new file mode 100644 index 000000000..ca0d6fe34 --- /dev/null +++ b/migration/annotations/effect__platform__Effectify.yaml @@ -0,0 +1,3 @@ +"@effect/platform/Effectify": + replacement: "effect/Effect" + note: "effectify moved into the Effect module as Effect.effectify; the Effectify and EffectifyError type helpers live in the Effect namespace as well." diff --git a/migration/annotations/effect__platform__Error.yaml b/migration/annotations/effect__platform__Error.yaml new file mode 100644 index 000000000..61763d683 --- /dev/null +++ b/migration/annotations/effect__platform__Error.yaml @@ -0,0 +1,18 @@ +"@effect/platform/Error#isPlatformError": + replacement: "value instanceof PlatformError.PlatformError" + note: "PlatformError is a class in v4; use an instanceof check or match its PlatformError tag." +"@effect/platform/Error#Module": + replacement: "string" + note: "The closed module-name Schema was removed; PlatformError reason records accept any module string." +"@effect/platform/Error#PlatformError": + replacement: "PlatformError.PlatformError" + note: "The module moved to effect/PlatformError and PlatformError became a wrapper class around BadArgument or SystemError." +"@effect/platform/Error#SystemErrorReason": + replacement: "PlatformError.SystemErrorTag" + note: "The normalized system-error reason union was renamed." +"@effect/platform/Error#TypeId": + replacement: "none" + note: "The PlatformError runtime marker is internal in v4; use the PlatformError class/tag." +"@effect/platform/Error#TypeIdError": + replacement: "Data.TaggedError or Schema.Error" + note: "The platform-specific error-class factory was removed; define tagged data errors or schema-backed error classes directly." diff --git a/migration/annotations/effect__platform__Etag.yaml b/migration/annotations/effect__platform__Etag.yaml new file mode 100644 index 000000000..4c3f0b6ce --- /dev/null +++ b/migration/annotations/effect__platform__Etag.yaml @@ -0,0 +1,9 @@ +"@effect/platform/Etag#GeneratorTypeId": + replacement: "Etag.Generator" + note: "The standalone generator brand was removed; Generator is now a Context.Service class." +"@effect/platform/Etag#layer": + replacement: "Etag.layer" + note: "Retained; it still provides the strong metadata-based ETag Generator service." +"@effect/platform/Etag#toString": + replacement: "Etag.toString" + note: "Retained with the same Etag-to-header-string behavior and signature." diff --git a/migration/annotations/effect__platform__FetchHttpClient.yaml b/migration/annotations/effect__platform__FetchHttpClient.yaml new file mode 100644 index 000000000..1186c7d05 --- /dev/null +++ b/migration/annotations/effect__platform__FetchHttpClient.yaml @@ -0,0 +1,6 @@ +"@effect/platform/FetchHttpClient#Fetch": + replacement: "FetchHttpClient.Fetch" + note: "Retained as a Context.Reference that defaults to globalThis.fetch." +"@effect/platform/FetchHttpClient#layer": + replacement: "FetchHttpClient.layer" + note: "Retained as the HttpClient layer backed by the configured Fetch reference." diff --git a/migration/annotations/effect__platform__FileSystem.yaml b/migration/annotations/effect__platform__FileSystem.yaml new file mode 100644 index 000000000..7dafaa372 --- /dev/null +++ b/migration/annotations/effect__platform__FileSystem.yaml @@ -0,0 +1,90 @@ +"@effect/platform/FileSystem#AccessFileOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#CopyOptions": + replacement: "NonNullable[2]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#File.Descriptor": + replacement: "none" + note: "Native file descriptors are no longer part of the portable File interface; use the File methods and keep any platform handle private in custom implementations." +"@effect/platform/FileSystem#FileDescriptor": + replacement: "none" + note: "The descriptor branding constructor was removed with the public fd field; use File operations instead of exposing a native descriptor." +"@effect/platform/FileSystem#FileTypeId": + replacement: "typeof FileSystem.FileTypeId" + note: "The runtime marker remains exported, but the separate type alias was removed." +"@effect/platform/FileSystem#GiB": + replacement: "ByteSize.gibibytes" + note: "Use the ByteSize binary unit constructor." +"@effect/platform/FileSystem#isFile": + replacement: "FileSystem.isFile" + note: "The guard remains after moving the module to effect/FileSystem." +"@effect/platform/FileSystem#KiB": + replacement: "ByteSize.kibibytes" + note: "Use the ByteSize binary unit constructor." +"@effect/platform/FileSystem#layerNoop": + replacement: "FileSystem.layerNoop" + note: "The helper remains after moving the module to effect/FileSystem." +"@effect/platform/FileSystem#make": + replacement: "FileSystem.make" + note: "The constructor remains after moving the module to effect/FileSystem; adapt the implementation to the v4 service shape." +"@effect/platform/FileSystem#MakeDirectoryOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#makeNoop": + replacement: "FileSystem.makeNoop" + note: "The helper remains after moving the module to effect/FileSystem." +"@effect/platform/FileSystem#MakeTempDirectoryOptions": + replacement: "NonNullable[0]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#MakeTempFileOptions": + replacement: "NonNullable[0]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#MiB": + replacement: "ByteSize.mebibytes" + note: "Use the ByteSize binary unit constructor." +"@effect/platform/FileSystem#OpenFileOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#PiB": + replacement: "ByteSize.pebibytes" + note: "Use the ByteSize binary unit constructor." +"@effect/platform/FileSystem#ReadDirectoryOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#RemoveOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#SinkOptions": + replacement: "NonNullable[1]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#Size": + replacement: "ByteSize.ByteSize" + note: "Use ByteSize.bytes or unit constructors for file sizes. Truncation lengths, buffer sizes, and read/write counts use number. File.seek takes and returns signed bigint positions; it can fail with PlatformError, including BadArgument when seeking before the start." +"@effect/platform/FileSystem#SizeInput": + replacement: "ByteSize.Input" + note: "File-size and path-backed range inputs use ByteSize.Input. Truncation lengths, Web File ranges, and buffer sizes use number." +"@effect/platform/FileSystem#StreamOptions": + replacement: "NonNullable[1]>" + note: "Stream options are inline; bufferSize was removed, bytesToRead and offset accept ByteSize inputs, and chunkSize uses number." +"@effect/platform/FileSystem#TiB": + replacement: "ByteSize.tebibytes" + note: "Use the ByteSize binary unit constructor." +"@effect/platform/FileSystem#WatchEventCreate": + replacement: "FileSystem.WatchEvent.Create" + note: "The constructor was removed; construct a tagged object with _tag: \"Create\" and path." +"@effect/platform/FileSystem#WatchEventRemove": + replacement: "FileSystem.WatchEvent.Remove" + note: "The constructor was removed; construct a tagged object with _tag: \"Remove\" and path." +"@effect/platform/FileSystem#WatchEventUpdate": + replacement: "FileSystem.WatchEvent.Update" + note: "The constructor was removed; construct a tagged object with _tag: \"Update\" and path." +"@effect/platform/FileSystem#WatchOptions": + replacement: "FileSystem.WatchOptions" + note: "Retained after moving the module to effect/FileSystem; pass `{ recursive: true }` as the optional second argument to FileSystem.watch." +"@effect/platform/FileSystem#WriteFileOptions": + replacement: "NonNullable[2]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." +"@effect/platform/FileSystem#WriteFileStringOptions": + replacement: "NonNullable[2]>" + note: "Operation option interfaces are inline in the v4 FileSystem service." diff --git a/migration/annotations/effect__platform__Headers.yaml b/migration/annotations/effect__platform__Headers.yaml new file mode 100644 index 000000000..2cd2ffa57 --- /dev/null +++ b/migration/annotations/effect__platform__Headers.yaml @@ -0,0 +1,45 @@ +"@effect/platform/Headers#currentRedactedNames": + replacement: "Headers.CurrentRedactedNames" + note: "Renamed and changed from FiberRef to Context.Reference; override it with service provisioning." +"@effect/platform/Headers#empty": + replacement: "Headers.empty" + note: "Retained as the empty immutable Headers value." +"@effect/platform/Headers#fromInput": + replacement: "Headers.fromInput" + note: "Retained with the same optional Input and lowercase normalization behavior." +"@effect/platform/Headers#get": + replacement: "Headers.get" + note: "Retained with the same dual, case-insensitive Option-returning signature." +"@effect/platform/Headers#has": + replacement: "Headers.has" + note: "Retained with the same dual, case-insensitive signature." +"@effect/platform/Headers#Headers": + replacement: "Headers.Headers" + note: "Import Headers from effect/unstable/http; the immutable string-record interface is retained with its v4 TypeId brand." +"@effect/platform/Headers#HeadersTypeId": + replacement: "Headers.TypeId" + note: "The public Headers type-id symbol was renamed from HeadersTypeId to TypeId." +"@effect/platform/Headers#Input": + replacement: "Headers.Input" + note: "Retained with the same record-or-entry-iterable input shape." +"@effect/platform/Headers#merge": + replacement: "Headers.merge" + note: "Retained with the same dual signature; values from the second collection win." +"@effect/platform/Headers#remove": + replacement: "Headers.remove / Headers.removeMany" + note: "Use remove for one name or removeMany for an iterable; RegExp removal requires enumerating matching names." +"@effect/platform/Headers#schema": + replacement: "Schema.Headers" + note: "The encoded-record and self schemas were consolidated and moved to effect/Schema as Schema.Headers." +"@effect/platform/Headers#schemaFromSelf": + replacement: "Schema.Headers" + note: "The encoded-record and self schemas were consolidated and moved to effect/Schema as Schema.Headers." +"@effect/platform/Headers#set": + replacement: "Headers.set" + note: "Retained with the same dual signature and lowercase key normalization." +"@effect/platform/Headers#setAll": + replacement: "Headers.setAll" + note: "Retained with the same dual Input signature; supplied values override existing names." +"@effect/platform/Headers#unsafeFromRecord": + replacement: "Headers.fromRecordUnsafe" + note: "Renamed to put Unsafe last; it still skips name normalization." diff --git a/migration/annotations/effect__platform__HttpApi.yaml b/migration/annotations/effect__platform__HttpApi.yaml new file mode 100644 index 000000000..d761c45e2 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApi.yaml @@ -0,0 +1,15 @@ +"@effect/platform/HttpApi#Api": + replacement: "none" + note: "The Context tag carrying the API was removed. Pass the HttpApi value explicitly to builders and clients." +"@effect/platform/HttpApi#HttpApi.Any": + replacement: "effect/unstable/httpapi/HttpApi#Constraint" + note: "Use the erased marker constraint when only HttpApi identity is needed." +"@effect/platform/HttpApi#HttpApi.AnyWithProps": + replacement: "effect/unstable/httpapi/HttpApi#Top" + note: "Use the widened HttpApi type that retains runtime properties." +"@effect/platform/HttpApi#make": + replacement: "effect/unstable/httpapi/HttpApi#make" + note: "The constructor remains, but API-wide error and service parameters were removed; declare errors on endpoints and attach middleware." +"@effect/platform/HttpApi#TypeId": + replacement: "none" + note: "The marker is private in v4; use HttpApi.isHttpApi for runtime narrowing and Constraint or Top for types." diff --git a/migration/annotations/effect__platform__HttpApiBuilder.yaml b/migration/annotations/effect__platform__HttpApiBuilder.yaml new file mode 100644 index 000000000..35231d587 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiBuilder.yaml @@ -0,0 +1,51 @@ +"@effect/platform/HttpApiBuilder#api": + replacement: "effect/unstable/httpapi/HttpApiBuilder#layer" + note: "Use layer(api) and provide the group layers; it registers the completed API with HttpRouter." +"@effect/platform/HttpApiBuilder#buildMiddleware": + replacement: "none" + note: "API-wide middleware assembly was removed. Declared HttpApiMiddleware services are applied while routes are built; use HttpRouter.middleware for additional middleware." +"@effect/platform/HttpApiBuilder#group": + replacement: "effect/unstable/httpapi/HttpApiBuilder#group" + note: "The group layer remains; names are now identifiers and API/group global error channels are gone." +"@effect/platform/HttpApiBuilder#handler": + replacement: "effect/unstable/httpapi/HttpApiBuilder#handler" + note: "The typed callback helper remains; names are now identifiers and API/group global error channels are gone. Pass the returned callback to handlers.handle." +"@effect/platform/HttpApiBuilder#Handlers": + replacement: "effect/unstable/httpapi/HttpApiBuilder#Handlers" + note: "Handlers now tracks an endpoint map and handled identifiers. Prefer Handlers.FromGroup." +"@effect/platform/HttpApiBuilder#Handlers.Error": + replacement: "effect/unstable/httpapi/HttpApiBuilder#Handlers.Error" + note: "The helper remains and extracts the error channel of an effectful group-builder return." +"@effect/platform/HttpApiBuilder#Handlers.Middleware": + replacement: "none" + note: "The handler-internal HttpApp middleware alias was removed. Use HttpRouter.middleware inference or HttpRouter.middleware.Fn." +"@effect/platform/HttpApiBuilder#Handlers.ValidateReturn": + replacement: "effect/unstable/httpapi/HttpApiBuilder#Handlers.ValidateReturn" + note: "The validator remains and now checks the endpoint map against handled identifiers." +"@effect/platform/HttpApiBuilder#HandlersTypeId": + replacement: "none" + note: "The exported symbol was removed; do not inspect or construct the private Handlers marker." +"@effect/platform/HttpApiBuilder#Middleware": + replacement: "none" + note: "The API-specific middleware service tag was removed. Declared HttpApiMiddleware services are applied while routes are built; use HttpRouter.middleware for additional global middleware." +"@effect/platform/HttpApiBuilder#httpApp": + replacement: "effect/unstable/http/HttpRouter#toHttpEffect" + note: "Build the application from the assembled API route layer; HTTP apps are Effects in v4." +"@effect/platform/HttpApiBuilder#middleware": + replacement: "effect/unstable/http/HttpRouter#middleware" + note: "Use router effect middleware and provide its layer to the API route layer; global middleware can target all router routes." +"@effect/platform/HttpApiBuilder#middlewareCors": + replacement: "effect/unstable/http/HttpRouter#cors" + note: "Use the router CORS layer, or provide route-scoped HttpMiddleware.cors through HttpRouter.middleware." +"@effect/platform/HttpApiBuilder#MiddlewareFn": + replacement: "effect/unstable/http/HttpRouter#middleware.Fn" + note: "HTTP apps are Effects in v4; use the router middleware function type or infer it through HttpRouter.middleware." +"@effect/platform/HttpApiBuilder#middlewareOpenApi": + replacement: "effect/unstable/httpapi/HttpApiBuilder#layer" + note: "Set openapiPath in layer(api, options). The additionalPropertiesStrategy option was removed." +"@effect/platform/HttpApiBuilder#Router": + replacement: "effect/unstable/http/HttpRouter#HttpRouter" + note: "The API-specific router tag was removed; API and group layers register with the shared HttpRouter service." +"@effect/platform/HttpApiBuilder#toWebHandler": + replacement: "effect/unstable/http/HttpRouter#toWebHandler" + note: "Pass the assembled API route layer to HttpRouter.toWebHandler; the handler and dispose lifecycle is retained." diff --git a/migration/annotations/effect__platform__HttpApiClient.yaml b/migration/annotations/effect__platform__HttpApiClient.yaml new file mode 100644 index 000000000..92cf124c3 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiClient.yaml @@ -0,0 +1,12 @@ +"@effect/platform/HttpApiClient#Client.Method": + replacement: "effect/unstable/httpapi/HttpApiClient#Client.Method" + note: "The type remains without GroupError. Requests use params/query and responseMode instead of path/urlParams and withResponse." +"@effect/platform/HttpApiClient#endpoint": + replacement: "effect/unstable/httpapi/HttpApiClient#endpoint" + note: "The endpoint client remains, selected by group and endpoint identifiers and using v4 request and responseMode fields." +"@effect/platform/HttpApiClient#make": + replacement: "effect/unstable/httpapi/HttpApiClient#make" + note: "The generated client remains; errors and services are now derived per endpoint and middleware." +"@effect/platform/HttpApiClient#makeWith": + replacement: "effect/unstable/httpapi/HttpApiClient#makeWith" + note: "The supplied-HttpClient constructor remains and now requires endpoint client-middleware services." diff --git a/migration/annotations/effect__platform__HttpApiEndpoint.yaml b/migration/annotations/effect__platform__HttpApiEndpoint.yaml new file mode 100644 index 000000000..5c8f3b0be --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiEndpoint.yaml @@ -0,0 +1,105 @@ +"@effect/platform/HttpApiEndpoint#get": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#get" + note: "Use get(identifier, path, options?); tagged templates and fluent schema setters were removed." +"@effect/platform/HttpApiEndpoint#head": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#head" + note: "Use head(identifier, path, options?); tagged templates and fluent schema setters were removed." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#HttpApiEndpoint" + note: "The model remains, but its generics now carry path literals, schemas, middleware, and middleware services." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.AddContext": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#AddMiddleware" + note: "Use AddMiddleware to add a middleware identifier and compute its service transformation." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.AddError": + replacement: "none" + note: "Declare error schemas in the endpoint constructor options; the type helper and fluent addError method were removed." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.Constructor": + replacement: "none" + note: "The tagged-template constructor type was removed; use HttpApiEndpoint.make(method)(identifier, path, options?)." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.Context": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ServerServices" + note: "Use ServerServices for handler requirements; middleware IDs and extra requirements have separate extractors." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ContextWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ServerServicesWithIdentifier" + note: "Name became Identifier; combine with middleware extractors when the complete handler requirement union is needed." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.Error": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#Errors" + note: "Use Errors for the decoded endpoint and middleware error union; v4 Error extracts the schema." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ErrorContext": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ErrorServicesEncode / ErrorServicesDecode" + note: "The single schema context split into server encoding and client decoding services." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ErrorContextWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ErrorServicesEncode / ErrorServicesDecode" + note: "Select the endpoint with WithIdentifier, then apply the encode or decode service extractor." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ErrorWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ErrorsWithIdentifier" + note: "Renamed for identifier and returns the decoded endpoint plus middleware error union." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ExcludeName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ExcludeIdentifier" + note: "Direct rename from name to identifier." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ExtractPath": + replacement: "none" + note: "Tagged-template path extraction was removed. Put a params schema or field record in constructor option params." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.HandlerRawWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#HandlerRawWithIdentifier" + note: "Direct rename; raw request fields are now params and query." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.HandlerWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#HandlerWithIdentifier" + note: "Direct rename from name to identifier." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.OptionalTypePropertySignature": + replacement: "none" + note: "Removed with the tagged-template path implementation." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.PathEntries": + replacement: "none" + note: "Removed with tagged-template path extraction; declare endpoint params explicitly." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.PathParsed": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#Params" + note: "Path data became params; Params extracts the schema, so use Params[\"Type\"] for decoded data." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.Payload": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#Payload" + note: "The name remains but now extracts the schema; use Payload[\"Type\"] for buffered decoded data." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.Success": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#SuccessWithIdentifier" + note: "Use SuccessWithIdentifier for the decoded, stream-aware result; v4 Success extracts the schema." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.SuccessWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#SuccessWithIdentifier" + note: "Direct rename from name to identifier; the result remains decoded and stream-aware." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.UrlParams": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#Query" + note: "urlParams became query; Query extracts the schema, so use Query[\"Type\"] for decoded data." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ValidateHeaders": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#HeadersConstraint" + note: "Validation moved from an intersection helper to a constructor generic constraint." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ValidateParams": + replacement: "none" + note: "Tagged-template interpolation validation was removed; params are declared explicitly in options.params." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ValidatePath": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#ParamsConstraint" + note: "path became params and validation is now a constructor constraint." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ValidatePayload": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#PayloadConstraint" + note: "Payload validation is now a method-sensitive constructor constraint." +"@effect/platform/HttpApiEndpoint#HttpApiEndpoint.ValidateUrlParams": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#QueryConstraint" + note: "urlParams became query and validation is now a constructor constraint." +"@effect/platform/HttpApiEndpoint#make": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#make" + note: "The factory remains but now requires identifier, path, and options and applies codecs unless disabled." +"@effect/platform/HttpApiEndpoint#options": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#options" + note: "Same HTTP method constructor with the new identifier, path, and options signature." +"@effect/platform/HttpApiEndpoint#patch": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#patch" + note: "Same HTTP method constructor with the new identifier, path, and options signature." +"@effect/platform/HttpApiEndpoint#PathSegment": + replacement: "effect/unstable/http/HttpRouter#PathInput" + note: "Path input moved to the shared router and is generalized to slash-prefixed paths or wildcard." +"@effect/platform/HttpApiEndpoint#post": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#post" + note: "Same HTTP method constructor with the new identifier, path, and options signature." +"@effect/platform/HttpApiEndpoint#put": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#put" + note: "Same HTTP method constructor with the new identifier, path, and options signature." +"@effect/platform/HttpApiEndpoint#TypeId": + replacement: "none" + note: "The endpoint type ID is private; use HttpApiEndpoint.isHttpApiEndpoint for runtime narrowing." diff --git a/migration/annotations/effect__platform__HttpApiError.yaml b/migration/annotations/effect__platform__HttpApiError.yaml new file mode 100644 index 000000000..2d3532777 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiError.yaml @@ -0,0 +1,12 @@ +"@effect/platform/HttpApiError#Forbidden": + replacement: "effect/unstable/httpapi/HttpApiError#ForbiddenNoContent" + note: "Use ForbiddenNoContent to preserve the empty 403 wire schema; Forbidden now has a JSON-tagged body." +"@effect/platform/HttpApiError#HttpApiDecodeError": + replacement: "effect/unstable/httpapi/HttpApiError#HttpApiSchemaError" + note: "Validation now stores kind and a SchemaError cause and is a defect unless transformed by schema-error middleware." +"@effect/platform/HttpApiError#Issue": + replacement: "effect/SchemaIssue#Issue" + note: "Structured failures now live at HttpApiSchemaError.cause.issue; format them explicitly when a flat external list is needed." +"@effect/platform/HttpApiError#TypeId": + replacement: "effect/unstable/httpapi/HttpApiError#HttpApiSchemaErrorTypeId" + note: "The old module symbol is gone; prefer HttpApiSchemaError.is for runtime narrowing." diff --git a/migration/annotations/effect__platform__HttpApiGroup.yaml b/migration/annotations/effect__platform__HttpApiGroup.yaml new file mode 100644 index 000000000..5297e522d --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiGroup.yaml @@ -0,0 +1,54 @@ +"@effect/platform/HttpApiGroup#ApiGroup": + replacement: "effect/unstable/httpapi/HttpApiGroup#Service" + note: "Renamed; the service field and type parameter are now identifier rather than name." +"@effect/platform/HttpApiGroup#HttpApiGroup.AddContext": + replacement: "none" + note: "Groups no longer carry arbitrary context. Use AddMiddleware for middleware service transformations." +"@effect/platform/HttpApiGroup#HttpApiGroup.Any": + replacement: "effect/unstable/httpapi/HttpApiGroup#Constraint" + note: "Renamed widened structural constraint." +"@effect/platform/HttpApiGroup#HttpApiGroup.AnyWithProps": + replacement: "effect/unstable/httpapi/HttpApiGroup#Top" + note: "Renamed widened runtime-property type." +"@effect/platform/HttpApiGroup#HttpApiGroup.ClientContext": + replacement: "effect/unstable/httpapi/HttpApiGroup#ClientServices / ErrorServicesDecode / MiddlewareClient" + note: "Client schema services and required client middleware are separate extractors in v4." +"@effect/platform/HttpApiGroup#HttpApiGroup.Context": + replacement: "none" + note: "Group error and context generics were removed; derive server requirements from the group's endpoints." +"@effect/platform/HttpApiGroup#HttpApiGroup.ContextWithName": + replacement: "none" + note: "Select with WithIdentifier and derive endpoint server requirements; groups no longer have a context generic." +"@effect/platform/HttpApiGroup#HttpApiGroup.EndpointsWithName": + replacement: "effect/unstable/httpapi/HttpApiGroup#EndpointsWithIdentifier" + note: "Direct rename from name to identifier." +"@effect/platform/HttpApiGroup#HttpApiGroup.Error": + replacement: "none" + note: "Group-level errors were removed. Declare shared errors on each endpoint or through middleware." +"@effect/platform/HttpApiGroup#HttpApiGroup.ErrorContext": + replacement: "effect/unstable/httpapi/HttpApiGroup#ErrorServicesEncode / ErrorServicesDecode" + note: "The closest endpoint-error aggregate splits server encoding from client decoding services." +"@effect/platform/HttpApiGroup#HttpApiGroup.ErrorWithName": + replacement: "none" + note: "Group-level errors were removed; select with WithIdentifier and inspect Errors over the selected endpoints." +"@effect/platform/HttpApiGroup#HttpApiGroup.Middleware": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#Middleware" + note: "Middleware is attached to the endpoints present when group.middleware is called; extract it from group endpoints." +"@effect/platform/HttpApiGroup#HttpApiGroup.MiddlewareWithName": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#Middleware" + note: "Select the group with WithIdentifier, get its endpoints, then apply the endpoint Middleware extractor." +"@effect/platform/HttpApiGroup#HttpApiGroup.Provides": + replacement: "effect/unstable/httpapi/HttpApiGroup#MiddlewareProvides" + note: "Renamed; derives provided services from endpoint middleware." +"@effect/platform/HttpApiGroup#HttpApiGroup.ToService": + replacement: "effect/unstable/httpapi/HttpApiGroup#ToService" + note: "Same role and now produces Service." +"@effect/platform/HttpApiGroup#HttpApiGroup.WithName": + replacement: "effect/unstable/httpapi/HttpApiGroup#WithIdentifier" + note: "Direct rename from name to identifier." +"@effect/platform/HttpApiGroup#make": + replacement: "effect/unstable/httpapi/HttpApiGroup#make" + note: "The constructor remains; group error and context generics are gone and add is variadic." +"@effect/platform/HttpApiGroup#TypeId": + replacement: "none" + note: "The group type ID is private; use HttpApiGroup.isHttpApiGroup for runtime narrowing." diff --git a/migration/annotations/effect__platform__HttpApiMiddleware.yaml b/migration/annotations/effect__platform__HttpApiMiddleware.yaml new file mode 100644 index 000000000..d0389a554 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiMiddleware.yaml @@ -0,0 +1,60 @@ +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#HttpApiMiddleware" + note: "The model remains but now wraps the response effect and carries provided services, an error schema, and required services." +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware.Any": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#AnyService" + note: "Renamed widened middleware service-key shape." +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware.AnyId": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#AnyId" + note: "Same name; metadata now includes provided and required services, error schema, client error, and client requirement." +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware.Error": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#Error" + note: "Same name and now derives the decoded type from the configured error schema." +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware.ErrorContext": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#ErrorServicesEncode / ErrorServicesDecode" + note: "The single schema context split into server encoding and client decoding services." +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware.Only": + replacement: "Extract" + note: "The helper was removed because middleware IDs are explicit; use Extract when the direct filter is still needed." +"@effect/platform/HttpApiMiddleware#HttpApiMiddleware.Provides": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#Provides" + note: "Same name and reads the expanded v4 middleware ID metadata." +"@effect/platform/HttpApiMiddleware#SecurityTypeId": + replacement: "none" + note: "The marker is private; use HttpApiMiddleware.isSecurity." +"@effect/platform/HttpApiMiddleware#Tag": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#Service" + note: "Renamed and redesigned; use error, requires, provides, clientError, and requiredForClient configuration." +"@effect/platform/HttpApiMiddleware#TagClass": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#ServiceClass" + note: "Renamed class type with the new two-stage type configuration and wrapping service shape." +"@effect/platform/HttpApiMiddleware#TagClass.BaseSecurity": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#ServiceClass" + note: "Security is conditional metadata on ServiceClass; there is no separate public base interface." +"@effect/platform/HttpApiMiddleware#TagClass.Failure": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#Error" + note: "failure terminology became error; apply the extractor to the middleware ID." +"@effect/platform/HttpApiMiddleware#TagClass.FailureContext": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#ErrorServicesEncode / ErrorServicesDecode" + note: "Failure schema services split by server encoding and client decoding direction." +"@effect/platform/HttpApiMiddleware#TagClass.FailureSchema": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#ErrorSchema" + note: "Renamed and applied to the middleware ID rather than constructor options." +"@effect/platform/HttpApiMiddleware#TagClass.FailureService": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#Error" + note: "Use the decoded error extractor; optional middleware fallback was removed." +"@effect/platform/HttpApiMiddleware#TagClass.Optional": + replacement: "none" + note: "Optional declaration and fallback-on-failure behavior were removed; model fallback in the wrapping middleware." +"@effect/platform/HttpApiMiddleware#TagClass.Provides": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#Provides" + note: "Moved to the module level and applied to the middleware ID." +"@effect/platform/HttpApiMiddleware#TagClassAny": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#AnyService" + note: "Renamed widened service-key type." +"@effect/platform/HttpApiMiddleware#TagClassSecurityAny": + replacement: "effect/unstable/httpapi/HttpApiMiddleware#AnyServiceSecurity" + note: "Renamed widened security service-key type." +"@effect/platform/HttpApiMiddleware#TypeId": + replacement: "none" + note: "The marker is private; use public guards and type extractors." diff --git a/migration/annotations/effect__platform__HttpApiScalar.yaml b/migration/annotations/effect__platform__HttpApiScalar.yaml new file mode 100644 index 000000000..a6853fcaf --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiScalar.yaml @@ -0,0 +1,9 @@ +"@effect/platform/HttpApiScalar#layer": + replacement: "effect/unstable/httpapi/HttpApiScalar#layer" + note: "Pass the HttpApi as the first argument; the layer now contributes directly to HttpRouter." +"@effect/platform/HttpApiScalar#layerHttpLayerRouter": + replacement: "effect/unstable/httpapi/HttpApiScalar#layer" + note: "The duplicate was removed. Pass options.api as the first layer argument and the remaining Scalar options second." +"@effect/platform/HttpApiScalar#layerHttpLayerRouterCdn": + replacement: "effect/unstable/httpapi/HttpApiScalar#layerCdn" + note: "Use the explicit-api CDN layer with path, version, and Scalar options." diff --git a/migration/annotations/effect__platform__HttpApiSchema.yaml b/migration/annotations/effect__platform__HttpApiSchema.yaml new file mode 100644 index 000000000..3f0cc5009 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiSchema.yaml @@ -0,0 +1,99 @@ +"@effect/platform/HttpApiSchema#AnnotationEmptyDecodeable": + replacement: "effect/unstable/httpapi/HttpApiSchema#asNoContent" + note: "The public marker was removed; represent no-content decoding structurally with asNoContent({ decode })." +"@effect/platform/HttpApiSchema#AnnotationEncoding": + replacement: "effect/unstable/httpapi/HttpApiSchema#asJson / asFormUrlEncoded / asText / asUint8Array" + note: "The key is internal; select encoding with a public combinator." +"@effect/platform/HttpApiSchema#AnnotationMultipart": + replacement: "effect/unstable/httpapi/HttpApiSchema#asMultipart" + note: "The symbol annotation became a brand plus internal encoding metadata; apply the schema combinator." +"@effect/platform/HttpApiSchema#AnnotationMultipartStream": + replacement: "effect/unstable/httpapi/HttpApiSchema#asMultipartStream" + note: "The symbol annotation became a brand plus internal encoding metadata; apply the schema combinator." +"@effect/platform/HttpApiSchema#AnnotationParam": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#params" + note: "Path names now live in the router path and schemas in endpoint option params, not schema annotations." +"@effect/platform/HttpApiSchema#annotations": + replacement: "effect/Schema#annotate" + note: "Schema annotations became annotate; set httpApiStatus directly or prefer HttpApiSchema.status for status only." +"@effect/platform/HttpApiSchema#AnnotationStatus": + replacement: "effect/unstable/httpapi/HttpApiSchema#status" + note: "The public symbol was removed; apply status(code), which uses the httpApiStatus schema annotation." +"@effect/platform/HttpApiSchema#asEmpty": + replacement: "effect/unstable/httpapi/HttpApiSchema#asNoContent" + note: "Use schema.pipe(asNoContent({ decode }), status(code)); status is now a separate combinator." +"@effect/platform/HttpApiSchema#deunionize": + replacement: "none" + note: "Pass schema arrays to endpoint success, error, and body alternatives so each member retains status and content type." +"@effect/platform/HttpApiSchema#Empty": + replacement: "effect/unstable/httpapi/HttpApiSchema#Empty" + note: "The API remains and returns Schema.Void annotated with the supplied status." +"@effect/platform/HttpApiSchema#EmptyError": + replacement: "effect/Schema#Error" + note: "Define a normal schema error with httpApiStatus, then derive its no-content wire schema with asNoContent." +"@effect/platform/HttpApiSchema#EmptyErrorClass": + replacement: "effect/Schema#Error" + note: "The class and no-content codec are separate in v4; combine Schema.Error with HttpApiSchema.asNoContent." +"@effect/platform/HttpApiSchema#EmptyErrorUnify": + replacement: "none" + note: "Removed with EmptyError; Schema.Error instances already support yieldable-error behavior." +"@effect/platform/HttpApiSchema#EmptyErrorUnifyIgnore": + replacement: "none" + note: "Removed with EmptyError; do not recreate the old Unify marker." +"@effect/platform/HttpApiSchema#Encoding": + replacement: "effect/unstable/httpapi/HttpApiSchema#Encoding" + note: "The name remains but is now a discriminated PayloadEncoding or ResponseEncoding union; prefer public as* combinators." +"@effect/platform/HttpApiSchema#extractAnnotations": + replacement: "none" + note: "The internal symbol-copy helper was removed; HTTP metadata is schema-native and resolved through AST traversal." +"@effect/platform/HttpApiSchema#getEmptyDecodeable": + replacement: "effect/unstable/httpapi/HttpApiSchema#isNoContent" + note: "Use isNoContent only to test bodylessness; decodeability is structural and has no exact query replacement." +"@effect/platform/HttpApiSchema#getEncoding": + replacement: "effect/unstable/httpapi/HttpApiSchema#getPayloadEncoding / getResponseEncoding" + note: "Encoding lookup split by direction; application code should normally use public as* combinators." +"@effect/platform/HttpApiSchema#getMultipart": + replacement: "effect/unstable/httpapi/HttpApiSchema#getPayloadEncoding" + note: "Narrow the payload encoding to Multipart with buffered mode; multipart limits are on the encoding value." +"@effect/platform/HttpApiSchema#getMultipartStream": + replacement: "effect/unstable/httpapi/HttpApiSchema#getPayloadEncoding" + note: "Narrow the payload encoding to Multipart with stream mode; multipart limits are on the encoding value." +"@effect/platform/HttpApiSchema#getParam": + replacement: "none" + note: "Param identity moved out of schema metadata; read endpoint.path and endpoint.params." +"@effect/platform/HttpApiSchema#getStatus": + replacement: "effect/SchemaAST#resolveAt" + note: "Resolve the httpApiStatus annotation directly, or prefer getStatusSuccess and getStatusError for response logic." +"@effect/platform/HttpApiSchema#getStatusError": + replacement: "effect/unstable/httpapi/HttpApiSchema#getStatusError" + note: "The helper remains but accepts an AST and defaults to 500." +"@effect/platform/HttpApiSchema#getStatusErrorAST": + replacement: "effect/unstable/httpapi/HttpApiSchema#getStatusError" + note: "The AST suffix collapsed into the sole helper, which defaults to 500." +"@effect/platform/HttpApiSchema#getStatusSuccess": + replacement: "effect/unstable/httpapi/HttpApiSchema#getStatusSuccess" + note: "The helper remains but accepts an AST; bare Schema.Void now defaults to 200, so use Empty(204) for 204." +"@effect/platform/HttpApiSchema#getStatusSuccessAST": + replacement: "effect/unstable/httpapi/HttpApiSchema#getStatusSuccess" + note: "The AST suffix collapsed into the sole helper; bare Schema.Void no longer implies 204." +"@effect/platform/HttpApiSchema#Multipart": + replacement: "effect/unstable/httpapi/HttpApiSchema#asMultipart" + note: "The type and constructor became a curried schema combinator: schema.pipe(asMultipart(options))." +"@effect/platform/HttpApiSchema#MultipartStream": + replacement: "effect/unstable/httpapi/HttpApiSchema#asMultipartStream" + note: "The type and constructor became a curried schema combinator." +"@effect/platform/HttpApiSchema#param": + replacement: "effect/unstable/httpapi/HttpApiEndpoint#params" + note: "Use a literal /:name path and the matching field in endpoint constructor option params." +"@effect/platform/HttpApiSchema#Text": + replacement: "effect/unstable/httpapi/HttpApiSchema#asText" + note: "Apply the encoding combinator to Schema.String instead of using a dedicated constructor." +"@effect/platform/HttpApiSchema#Uint8Array": + replacement: "effect/unstable/httpapi/HttpApiSchema#asUint8Array" + note: "Apply the encoding combinator to Schema.Uint8Array instead of using a dedicated constructor." +"@effect/platform/HttpApiSchema#UnionUnify": + replacement: "effect/Schema#Union" + note: "Use Schema.Union([self, that]); for endpoint alternatives, pass the schema array directly to preserve metadata." +"@effect/platform/HttpApiSchema#withEncoding": + replacement: "effect/unstable/httpapi/HttpApiSchema#asJson / asFormUrlEncoded / asUint8Array / asText" + note: "Replace the generic kind with the matching public curried encoding combinator." diff --git a/migration/annotations/effect__platform__HttpApiSecurity.yaml b/migration/annotations/effect__platform__HttpApiSecurity.yaml new file mode 100644 index 000000000..f29b17c5a --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiSecurity.yaml @@ -0,0 +1,15 @@ +"@effect/platform/HttpApiSecurity#annotate": + replacement: "effect/unstable/httpapi/HttpApiSecurity#annotate" + note: "The combinator remains; its key is now the v4 Context.Key abstraction." +"@effect/platform/HttpApiSecurity#annotateContext": + replacement: "effect/unstable/httpapi/HttpApiSecurity#annotateMerge" + note: "Renamed; it still merges a Context into existing OpenAPI annotations." +"@effect/platform/HttpApiSecurity#Bearer": + replacement: "effect/unstable/httpapi/HttpApiSecurity#Http" + note: "Bearer was generalized to Http with scheme Bearer; the value-level bearer singleton remains." +"@effect/platform/HttpApiSecurity#HttpApiSecurity.Type": + replacement: "effect/unstable/httpapi/HttpApiSecurity#HttpApiSecurity.Type" + note: "Unchanged after the module move; still extracts the credential type." +"@effect/platform/HttpApiSecurity#TypeId": + replacement: "none" + note: "The marker is private; use the public union or specific Http, ApiKey, and Basic types." diff --git a/migration/annotations/effect__platform__HttpApiSwagger.yaml b/migration/annotations/effect__platform__HttpApiSwagger.yaml new file mode 100644 index 000000000..170c2c414 --- /dev/null +++ b/migration/annotations/effect__platform__HttpApiSwagger.yaml @@ -0,0 +1,6 @@ +"@effect/platform/HttpApiSwagger#layer": + replacement: "effect/unstable/httpapi/HttpApiSwagger#layer" + note: "Pass the HttpApi as the first argument; the layer now contributes directly to HttpRouter." +"@effect/platform/HttpApiSwagger#layerHttpLayerRouter": + replacement: "effect/unstable/httpapi/HttpApiSwagger#layer" + note: "The duplicate was removed. Pass options.api first and the path option second." diff --git a/migration/annotations/effect__platform__HttpApp.yaml b/migration/annotations/effect__platform__HttpApp.yaml new file mode 100644 index 000000000..774f5823c --- /dev/null +++ b/migration/annotations/effect__platform__HttpApp.yaml @@ -0,0 +1,21 @@ +"@effect/platform/HttpApp#currentPreResponseHandlers": + replacement: "HttpEffect.appendPreResponseHandler / HttpEffect.withPreResponseHandler" + note: "The FiberRef was removed; register request-local handlers through HttpEffect." +"@effect/platform/HttpApp#Default": + replacement: "Effect.Effect" + note: "The alias was removed; v4 HTTP applications are ordinary response-producing Effects." +"@effect/platform/HttpApp#ejectDefaultScopeClose": + replacement: "HttpEffect.scopeDisableClose" + note: "Renamed; it disables automatic request-scope closure, leaving closure to the caller." +"@effect/platform/HttpApp#HttpApp": + replacement: "Effect.Effect" + note: "The alias was removed; use the underlying Effect type and HttpEffect boundary combinators." +"@effect/platform/HttpApp#toWebHandler": + replacement: "HttpEffect.toWebHandler" + note: "Moved to HttpEffect for converting an HTTP effect to a Web handler." +"@effect/platform/HttpApp#toWebHandlerRuntime": + replacement: "HttpEffect.toWebHandlerWith(context)" + note: "Runtime was removed in v4; supply a Context with toWebHandlerWith instead." +"@effect/platform/HttpApp#unsafeEjectStreamScope": + replacement: "HttpEffect.scopeTransferToStream" + note: "Renamed; it transfers request-scope closure to a streaming response." diff --git a/migration/annotations/effect__platform__HttpBody.yaml b/migration/annotations/effect__platform__HttpBody.yaml new file mode 100644 index 000000000..8258bcc0c --- /dev/null +++ b/migration/annotations/effect__platform__HttpBody.yaml @@ -0,0 +1,57 @@ +"@effect/platform/HttpBody#empty": + replacement: "HttpBody.empty" + note: "Retained as the singleton Empty body." +"@effect/platform/HttpBody#Empty": + replacement: "HttpBody.Empty" + note: "Retained with the same tag, but v4 exports a class rather than an interface." +"@effect/platform/HttpBody#ErrorReason": + replacement: "HttpBody.ErrorReason" + note: "Retained but reshaped; original causes now live on HttpBodyError.cause." +"@effect/platform/HttpBody#ErrorTypeId": + replacement: "HttpBody.HttpBodyError" + note: "The error type-id is private in v4; identify the exported error class instead." +"@effect/platform/HttpBody#file": + replacement: "HttpBody.file" + note: "Retained; bufferSize was replaced by numeric chunkSize. Offset and bytesToRead accept ByteSize.Input. Invalid ranges and a final EOF-clamped content length above Number.MAX_SAFE_INTEGER fail with PlatformError / BadArgument." +"@effect/platform/HttpBody#fileInfo": + replacement: "HttpBody.fileFromInfo" + note: "Renamed; it uses supplied File.Info with ByteSize size metadata and requires FileSystem. Offset and bytesToRead accept ByteSize.Input, while chunkSize is numeric. Invalid ranges and a final EOF-clamped content length above Number.MAX_SAFE_INTEGER fail with PlatformError / BadArgument." +"@effect/platform/HttpBody#formData": + replacement: "HttpBody.formData" + note: "Retained with the same Web FormData input." +"@effect/platform/HttpBody#HttpBodyError": + replacement: "HttpBody.HttpBodyError" + note: "Changed from a factory/interface to a class constructed with reason and optional cause." +"@effect/platform/HttpBody#json": + replacement: "HttpBody.json" + note: "Retained as the safe Effect-returning JSON serializer." +"@effect/platform/HttpBody#raw": + replacement: "HttpBody.raw" + note: "Retained with optional contentType and contentLength metadata." +"@effect/platform/HttpBody#Raw": + replacement: "HttpBody.Raw" + note: "Retained with the same tag and payload, but v4 exports a class." +"@effect/platform/HttpBody#stream": + replacement: "HttpBody.stream" + note: "Retained with the same byte stream and optional content metadata." +"@effect/platform/HttpBody#Stream": + replacement: "HttpBody.Stream" + note: "Retained with the same tag and byte stream, but v4 exports a class." +"@effect/platform/HttpBody#text": + replacement: "HttpBody.text" + note: "Retained; it UTF-8 encodes and defaults to text/plain." +"@effect/platform/HttpBody#TypeId": + replacement: "HttpBody.isHttpBody" + note: "The body brand is private in v4; use the public refinement instead." +"@effect/platform/HttpBody#uint8Array": + replacement: "HttpBody.uint8Array" + note: "Retained with the same bytes and optional content type." +"@effect/platform/HttpBody#Uint8Array": + replacement: "HttpBody.Uint8Array" + note: "Retained with the same fields and tag, but v4 exports a class." +"@effect/platform/HttpBody#unsafeJson": + replacement: "HttpBody.jsonUnsafe" + note: "Renamed to put Unsafe last; serialization failures still throw." +"@effect/platform/HttpBody#urlParams": + replacement: "HttpBody.urlParams" + note: "Retained and widened to accept UrlParams.Input." diff --git a/migration/annotations/effect__platform__HttpClient.yaml b/migration/annotations/effect__platform__HttpClient.yaml new file mode 100644 index 000000000..6b7033dbb --- /dev/null +++ b/migration/annotations/effect__platform__HttpClient.yaml @@ -0,0 +1,51 @@ +"@effect/platform/HttpClient#catchAll": + replacement: "HttpClient.catch" + note: "Renamed to catch; the recovery callback still returns a response effect." +"@effect/platform/HttpClient#catchTag": + replacement: "HttpClient.catchTag" + note: "Retained and widened to accept one or more error tags." +"@effect/platform/HttpClient#currentTracerDisabledWhen": + replacement: "HttpClient.TracerDisabledWhen" + note: "Renamed and changed from FiberRef to Context.Reference." +"@effect/platform/HttpClient#currentTracerPropagation": + replacement: "HttpClient.TracerPropagationEnabled" + note: "Renamed and changed from FiberRef to Context.Reference." +"@effect/platform/HttpClient#filterOrFail": + replacement: "HttpClient.filterOrFail" + note: "Retained; v4 also provides refinement overloads." +"@effect/platform/HttpClient#filterStatus": + replacement: "HttpClient.filterStatus" + note: "Retained; rejection now fails with the HttpClientError wrapper." +"@effect/platform/HttpClient#filterStatusOk": + replacement: "HttpClient.filterStatusOk" + note: "Retained; non-2xx responses now fail with the HttpClientError wrapper." +"@effect/platform/HttpClient#make": + replacement: "HttpClient.make" + note: "Retained; the runner receives Fiber.Fiber and failures use the v4 error wrapper." +"@effect/platform/HttpClient#makeWith": + replacement: "HttpClient.makeWith" + note: "Retained with the preprocess and postprocess constructor pattern." +"@effect/platform/HttpClient#retry": + replacement: "HttpClient.retry" + note: "Retained; the Schedule error channel is included in the resulting client error type." +"@effect/platform/HttpClient#SpanNameGenerator": + replacement: "HttpClient.SpanNameGenerator" + note: "The interface became a Context.Reference containing the generator function." +"@effect/platform/HttpClient#TypeId": + replacement: "HttpClient.isHttpClient" + note: "The brand key is private in v4; use the public runtime refinement." +"@effect/platform/HttpClient#withSpanNameGenerator": + replacement: "HttpClient.transformResponse(Effect.provideService(HttpClient.SpanNameGenerator, f))" + note: "The convenience combinator was removed; provide the reference around response effects." +"@effect/platform/HttpClient#withTracerDisabledWhen": + replacement: "HttpClient.transformResponse(Effect.provideService(HttpClient.TracerDisabledWhen, predicate))" + note: "The convenience combinator was removed; provide the reference around response effects." +"@effect/platform/HttpClient#withTracerPropagation": + replacement: "HttpClient.transformResponse(Effect.provideService(HttpClient.TracerPropagationEnabled, enabled))" + note: "Provide the renamed propagation reference around response effects." +"@effect/platform/HttpClient#tap": + replacement: "effect/unstable/http/HttpClient#tap" + note: "Moved to the v4 HTTP module with the same response-effect callback and client error/service widening." +"@effect/platform/HttpClient#transform": + replacement: "effect/unstable/http/HttpClient#transform" + note: "Moved to the v4 HTTP module with the same request-aware transformation shape." diff --git a/migration/annotations/effect__platform__HttpClientError.yaml b/migration/annotations/effect__platform__HttpClientError.yaml new file mode 100644 index 000000000..37baa2d15 --- /dev/null +++ b/migration/annotations/effect__platform__HttpClientError.yaml @@ -0,0 +1,9 @@ +"@effect/platform/HttpClientError#HttpClientError": + replacement: "HttpClientError.HttpClientError" + note: "Changed from a union to a tagged wrapper class containing a concrete failure in reason." +"@effect/platform/HttpClientError#RequestError": + replacement: "HttpClientError.RequestError" + note: "Now a type-only reason union; construct a concrete reason and wrap it in HttpClientError." +"@effect/platform/HttpClientError#TypeId": + replacement: "HttpClientError.isHttpClientError" + note: "The brand key is private in v4; use the public runtime refinement." diff --git a/migration/annotations/effect__platform__HttpClientRequest.yaml b/migration/annotations/effect__platform__HttpClientRequest.yaml new file mode 100644 index 000000000..e6eba3580 --- /dev/null +++ b/migration/annotations/effect__platform__HttpClientRequest.yaml @@ -0,0 +1,48 @@ +"@effect/platform/HttpClientRequest#bodyFileWeb": + replacement: "HttpClientRequest.setBody + HttpBody.stream + Stream.fromReadableStream" + note: "No one-call replacement remains; stream file.stream() and pass file.type and file.size to HttpBody.stream." +"@effect/platform/HttpClientRequest#bodyUnsafeJson": + replacement: "HttpClientRequest.bodyJsonUnsafe" + note: "Renamed to put Unsafe last; serialization remains synchronous and throwing." +"@effect/platform/HttpClientRequest#get": + replacement: "HttpClientRequest.get" + note: "Retained; options now use Options.NoUrl and no longer exclude body." +"@effect/platform/HttpClientRequest#head": + replacement: "HttpClientRequest.head" + note: "Retained; options now use Options.NoUrl and no longer exclude body." +"@effect/platform/HttpClientRequest#make": + replacement: "HttpClientRequest.make" + note: "Retained; all methods now accept Options.NoUrl without the GET/HEAD body restriction." +"@effect/platform/HttpClientRequest#modify": + replacement: "HttpClientRequest.modify" + note: "Retained with data-first and data-last overloads." +"@effect/platform/HttpClientRequest#options": + replacement: "HttpClientRequest.options" + note: "Retained with Options.NoUrl." +"@effect/platform/HttpClientRequest#Options.NoBody": + replacement: "HttpClientRequest.Options.NoUrl" + note: "NoBody was removed; v4 method helpers uniformly omit only url." +"@effect/platform/HttpClientRequest#patch": + replacement: "HttpClientRequest.patch" + note: "Retained with Options.NoUrl." +"@effect/platform/HttpClientRequest#post": + replacement: "HttpClientRequest.post" + note: "Retained with Options.NoUrl." +"@effect/platform/HttpClientRequest#put": + replacement: "HttpClientRequest.put" + note: "Retained with Options.NoUrl." +"@effect/platform/HttpClientRequest#setBody": + replacement: "HttpClientRequest.setBody" + note: "Retained and still synchronizes body content metadata into headers." +"@effect/platform/HttpClientRequest#setHeader": + replacement: "HttpClientRequest.setHeader" + note: "Retained with data-first and data-last overloads." +"@effect/platform/HttpClientRequest#setHeaders": + replacement: "HttpClientRequest.setHeaders" + note: "Retained with data-first and data-last overloads." +"@effect/platform/HttpClientRequest#toUrl": + replacement: "HttpClientRequest.toUrl" + note: "Retained and still returns Option." +"@effect/platform/HttpClientRequest#TypeId": + replacement: "HttpClientRequest.isHttpClientRequest" + note: "The request brand is private in v4; use the public runtime refinement." diff --git a/migration/annotations/effect__platform__HttpClientResponse.yaml b/migration/annotations/effect__platform__HttpClientResponse.yaml new file mode 100644 index 000000000..49154affc --- /dev/null +++ b/migration/annotations/effect__platform__HttpClientResponse.yaml @@ -0,0 +1,30 @@ +"@effect/platform/HttpClientResponse#filterStatus": + replacement: "HttpClientResponse.filterStatus" + note: "Retained; rejected status now fails with an HttpClientError wrapper." +"@effect/platform/HttpClientResponse#filterStatusOk": + replacement: "HttpClientResponse.filterStatusOk" + note: "Retained; non-2xx status now fails with an HttpClientError wrapper." +"@effect/platform/HttpClientResponse#schemaBodyJson": + replacement: "HttpClientResponse.schemaBodyJson" + note: "Retained with v4 Schema constraints and SchemaError failures." +"@effect/platform/HttpClientResponse#schemaBodyUrlParams": + replacement: "HttpClientResponse.schemaBodyUrlParams" + note: "Retained with ConstraintCodec input and SchemaError failures." +"@effect/platform/HttpClientResponse#schemaHeaders": + replacement: "HttpClientResponse.schemaHeaders" + note: "Retained with ConstraintCodec input and SchemaError failures." +"@effect/platform/HttpClientResponse#schemaJson": + replacement: "HttpClientResponse.schemaJson" + note: "Retained with ConstraintCodec input and v4 error types." +"@effect/platform/HttpClientResponse#schemaNoBody": + replacement: "HttpClientResponse.schemaNoBody" + note: "Retained with Schema.Codec input and SchemaError failures." +"@effect/platform/HttpClientResponse#stream": + replacement: "HttpClientResponse.stream" + note: "Retained; body failures now use the broader HttpClientError wrapper." +"@effect/platform/HttpClientResponse#TypeId": + replacement: "typeof HttpClientResponse.TypeId" + note: "TypeId remains public but is now a string constant; use typeof in type position." +"@effect/platform/HttpClientResponse#HttpClientResponse": + replacement: "HttpClientResponse.HttpClientResponse" + note: "Custom implementations must supply the required url string. It represents the resolved URL including query parameters and excluding the hash, uses the final URL after redirects, and is empty when unknown." diff --git a/migration/annotations/effect__platform__HttpIncomingMessage.yaml b/migration/annotations/effect__platform__HttpIncomingMessage.yaml new file mode 100644 index 000000000..055745ce5 --- /dev/null +++ b/migration/annotations/effect__platform__HttpIncomingMessage.yaml @@ -0,0 +1,9 @@ +"@effect/platform/HttpIncomingMessage#MaxBodySize": + replacement: "HttpIncomingMessage.MaxBodySize" + note: "Changed from a Reference subclass holding Option to Context.Reference." +"@effect/platform/HttpIncomingMessage#TypeId": + replacement: "typeof HttpIncomingMessage.TypeId" + note: "TypeId remains public but is now a string constant; use typeof in type position." +"@effect/platform/HttpIncomingMessage#withMaxBodySize": + replacement: "Effect.provideService(HttpIncomingMessage.MaxBodySize, size)" + note: "The helper was removed; provide a ByteSize value or undefined directly." diff --git a/migration/annotations/effect__platform__HttpLayerRouter.yaml b/migration/annotations/effect__platform__HttpLayerRouter.yaml new file mode 100644 index 000000000..ef12c88d0 --- /dev/null +++ b/migration/annotations/effect__platform__HttpLayerRouter.yaml @@ -0,0 +1,69 @@ +"@effect/platform/HttpLayerRouter#addHttpApi": + replacement: "HttpApiBuilder.layer" + note: "HTTP API registration moved to effect/unstable/httpapi." +"@effect/platform/HttpLayerRouter#cors": + replacement: "HttpRouter.cors" + note: "HttpLayerRouter was consolidated into v4 HttpRouter." +"@effect/platform/HttpLayerRouter#FindMyWay.make": + replacement: "FindMyWay.make" + note: "Import FindMyWay from effect/unstable/http." +"@effect/platform/HttpLayerRouter#FindMyWay.PathInput": + replacement: "FindMyWay.PathInput" + note: "Import FindMyWay from effect/unstable/http." +"@effect/platform/HttpLayerRouter#make": + replacement: "HttpRouter.make" + note: "The layer-oriented router became the sole v4 HttpRouter implementation." +"@effect/platform/HttpLayerRouter#MiddlewareTypeId": + replacement: "none" + note: "The middleware type id is internal in v4; use HttpRouter.Middleware." +"@effect/platform/HttpLayerRouter#PathInput": + replacement: "HttpRouter.PathInput" + note: "Moved to the consolidated v4 router." +"@effect/platform/HttpLayerRouter#RouteContext": + replacement: "HttpRouter.RouteContext" + note: "Moved to the consolidated v4 router." +"@effect/platform/HttpLayerRouter#RouterConfig": + replacement: "HttpRouter.RouterConfig" + note: "Now a Context.Reference containing Partial." +"@effect/platform/HttpLayerRouter#RouteTypeId": + replacement: "none" + note: "Route nominal ids are internal in v4; construct routes with HttpRouter.route." +"@effect/platform/HttpLayerRouter#schemaJson": + replacement: "HttpRouter.schemaJson" + note: "Moved to the consolidated router with v4 Schema and error types." +"@effect/platform/HttpLayerRouter#schemaNoBody": + replacement: "HttpRouter.schemaNoBody" + note: "Moved to the consolidated router with v4 Schema types." +"@effect/platform/HttpLayerRouter#serve": + replacement: "HttpRouter.serve" + note: "Moved to the consolidated router; pass the route-registration layer." +"@effect/platform/HttpLayerRouter#toWebHandler": + replacement: "HttpRouter.toWebHandler" + note: "Moved to the consolidated router for building a Fetch handler and disposer." +"@effect/platform/HttpLayerRouter#TypeId": + replacement: "none" + note: "The router nominal service id is internal in v4; use HttpRouter.HttpRouter." +"@effect/platform/HttpLayerRouter#Request.From": + replacement: "HttpRouter.Request.From" + note: "Moved with the layer-oriented router into the consolidated HttpRouter module." +"@effect/platform/HttpLayerRouter#Request.Only": + replacement: "HttpRouter.Request.Only" + note: "Moved with the layer-oriented router into the consolidated HttpRouter module." +"@effect/platform/HttpLayerRouter#Route.Context": + replacement: "HttpRouter.Route.Context" + note: "Moved with the Route helper types into the consolidated HttpRouter module." +"@effect/platform/HttpLayerRouter#Route.Error": + replacement: "HttpRouter.Route.Error" + note: "Moved with the Route helper types into the consolidated HttpRouter module." +"@effect/platform/HttpLayerRouter#add": + replacement: "HttpRouter.add" + note: "Moved to the consolidated HttpRouter; it still returns a route-registration Layer." +"@effect/platform/HttpLayerRouter#addAll": + replacement: "HttpRouter.addAll" + note: "Moved to the consolidated HttpRouter; it still registers route values through a Layer and supports a prefix option." +"@effect/platform/HttpLayerRouter#layer": + replacement: "HttpRouter.layer" + note: "Use the layer for the consolidated HttpRouter service." +"@effect/platform/HttpLayerRouter#toHttpEffect": + replacement: "HttpRouter.toHttpEffect" + note: "Moved to the consolidated HttpRouter; route-not-found failures now use HttpServerError.HttpServerError." diff --git a/migration/annotations/effect__platform__HttpMethod.yaml b/migration/annotations/effect__platform__HttpMethod.yaml new file mode 100644 index 000000000..73edb9d95 --- /dev/null +++ b/migration/annotations/effect__platform__HttpMethod.yaml @@ -0,0 +1,3 @@ +"@effect/platform/HttpMethod#all": + replacement: "HttpMethod.all" + note: "Retained as the readonly set of all supported methods." diff --git a/migration/annotations/effect__platform__HttpMiddleware.yaml b/migration/annotations/effect__platform__HttpMiddleware.yaml new file mode 100644 index 000000000..dd8f50df1 --- /dev/null +++ b/migration/annotations/effect__platform__HttpMiddleware.yaml @@ -0,0 +1,27 @@ +"@effect/platform/HttpMiddleware#cors": + replacement: "HttpMiddleware.cors" + note: "Retained with the same CORS options and behavior." +"@effect/platform/HttpMiddleware#currentTracerDisabledWhen": + replacement: "HttpMiddleware.TracerDisabledWhen" + note: "The FiberRef became a Context.Reference containing the request predicate." +"@effect/platform/HttpMiddleware#loggerDisabled": + replacement: "HttpMiddleware.withLoggerDisabled" + note: "The FiberRef was removed; locally wrap an effect or use HttpRouter.disableLogger." +"@effect/platform/HttpMiddleware#make": + replacement: "HttpMiddleware.make" + note: "Retained as the precise middleware constructor." +"@effect/platform/HttpMiddleware#SpanNameGenerator": + replacement: "HttpMiddleware.SpanNameGenerator" + note: "The branded interface became a Context.Reference containing the generator." +"@effect/platform/HttpMiddleware#withSpanNameGenerator": + replacement: "Layer.provide(layer, Layer.succeed(HttpMiddleware.SpanNameGenerator)(f))" + note: "Provide the SpanNameGenerator reference to the target layer." +"@effect/platform/HttpMiddleware#withTracerDisabledForUrls": + replacement: "Layer.provide(layer, HttpMiddleware.layerTracerDisabledForUrls(urls))" + note: "Provide the new URL-predicate layer to the target layer." +"@effect/platform/HttpMiddleware#withTracerDisabledWhen": + replacement: "Layer.provide(layer, Layer.succeed(HttpMiddleware.TracerDisabledWhen)(predicate))" + note: "Provide the TracerDisabledWhen reference to the target layer." +"@effect/platform/HttpMiddleware#withTracerDisabledWhenEffect": + replacement: "Effect.provideService(effect, HttpMiddleware.TracerDisabledWhen, predicate)" + note: "Provide the TracerDisabledWhen reference locally to the effect." diff --git a/migration/annotations/effect__platform__HttpMultiplex.yaml b/migration/annotations/effect__platform__HttpMultiplex.yaml new file mode 100644 index 000000000..d11372f2a --- /dev/null +++ b/migration/annotations/effect__platform__HttpMultiplex.yaml @@ -0,0 +1,3 @@ +"@effect/platform/HttpMultiplex": + replacement: none + note: The HttpMultiplex module, value, constructor, and nominal type id were removed with no v4 counterpart. Replace them with a custom first-match Effect dispatcher requiring HttpServerRequest; initialize an empty dispatcher, then add or fold predicate/app pairs into it. Recreate header helpers with predicates over lower-cased request header values using exact equality, String.startsWith, String.endsWith, or RegExp.test; recreate host helpers with the same comparisons over request.headers.host. diff --git a/migration/annotations/effect__platform__HttpPlatform.yaml b/migration/annotations/effect__platform__HttpPlatform.yaml new file mode 100644 index 000000000..523d462f0 --- /dev/null +++ b/migration/annotations/effect__platform__HttpPlatform.yaml @@ -0,0 +1,12 @@ +"@effect/platform/HttpPlatform#HttpPlatform": + replacement: "HttpPlatform.HttpPlatform" + note: "The service is now a Context.Service class; use its Service member for the implementation type. Path-backed offset and bytesToRead accept ByteSize.Input, while chunkSize and all Web File range options use number." +"@effect/platform/HttpPlatform#layer": + replacement: "HttpPlatform.layer" + note: "Retained as the default file-response layer." +"@effect/platform/HttpPlatform#make": + replacement: "HttpPlatform.make" + note: "Retained; v4 returns the service implementation. The fileResponse callback receives contentLength as bigint, while start and end remain numbers. Web File range options use number." +"@effect/platform/HttpPlatform#TypeId": + replacement: "none" + note: "The public type id was removed; use the HttpPlatform Context.Service class." diff --git a/migration/annotations/effect__platform__HttpRouter.yaml b/migration/annotations/effect__platform__HttpRouter.yaml new file mode 100644 index 000000000..7fdd24d99 --- /dev/null +++ b/migration/annotations/effect__platform__HttpRouter.yaml @@ -0,0 +1,102 @@ +"@effect/platform/HttpRouter#all": + replacement: "HttpRouter.add(\"*\", path, handler, options)" + note: "v4 registers a route layer instead of returning an immutable router." +"@effect/platform/HttpRouter#append": + replacement: "HttpRouter.addAll([route])" + note: "Register the route and merge its layer with other route layers." +"@effect/platform/HttpRouter#catchAll": + replacement: "HttpRouter.middleware + Effect.catch" + note: "Apply typed-error recovery in route middleware provided to route layers." +"@effect/platform/HttpRouter#catchAllCause": + replacement: "HttpRouter.middleware + Effect.catchCause" + note: "Apply cause recovery in route middleware provided to route layers." +"@effect/platform/HttpRouter#catchTag": + replacement: "HttpRouter.middleware + Effect.catchTag" + note: "Apply tagged-error recovery in route middleware provided to route layers." +"@effect/platform/HttpRouter#concat": + replacement: "Layer.merge" + note: "Routers are now route-registration layers; merge the two layers." +"@effect/platform/HttpRouter#concatAll": + replacement: "Layer.mergeAll" + note: "Routers are now route-registration layers; merge all layers." +"@effect/platform/HttpRouter#currentRouterConfig": + replacement: "HttpRouter.RouterConfig" + note: "The FiberRef became a Context.Reference containing Partial." +"@effect/platform/HttpRouter#Default": + replacement: "HttpRouter.HttpRouter + HttpRouter.layer" + note: "Custom/default router tags were removed; v4 provides one router service." +"@effect/platform/HttpRouter#empty": + replacement: "Layer.empty" + note: "There is no immutable empty router; use an empty registration layer." +"@effect/platform/HttpRouter#fromIterable": + replacement: "HttpRouter.addAll(Array.from(routes))" + note: "Materialize and register the route descriptors as a layer." +"@effect/platform/HttpRouter#get": + replacement: "HttpRouter.add(\"GET\", path, handler, options)" + note: "Register a route layer; handlers must produce HttpServerResponse." +"@effect/platform/HttpRouter#head": + replacement: "HttpRouter.addAll([HttpRouter.route(\"HEAD\", path, handler, options)])" + note: "Use route plus addAll because add does not expose HEAD." +"@effect/platform/HttpRouter#HttpRouter": + replacement: "HttpRouter.HttpRouter" + note: "The name remains, but now denotes the mutable layer-oriented registration service." +"@effect/platform/HttpRouter#HttpRouter.DefaultServices": + replacement: "none" + note: "The custom tagged-router default-service bundle was removed." +"@effect/platform/HttpRouter#HttpRouter.Service": + replacement: "HttpRouter.HttpRouter" + note: "Use the consolidated router service interface." +"@effect/platform/HttpRouter#makeRoute": + replacement: "HttpRouter.route" + note: "Renamed to route; v4 route options no longer expose the old prefix field." +"@effect/platform/HttpRouter#mount": + replacement: "HttpRouter.addAll(routes, { prefix: path })" + note: "Register child routes with a prefix, or use router.prefixed(path)." +"@effect/platform/HttpRouter#mountApp": + replacement: "HttpRouter.use((router) => router.prefixed(path).add(\"*\", \"/*\", app))" + note: "Register the app on the prefixed router service; no direct mount API remains." +"@effect/platform/HttpRouter#options": + replacement: "HttpRouter.add(\"OPTIONS\", path, handler, options)" + note: "Register a route layer; handlers must produce HttpServerResponse." +"@effect/platform/HttpRouter#patch": + replacement: "HttpRouter.add(\"PATCH\", path, handler, options)" + note: "Register a route layer; handlers must produce HttpServerResponse." +"@effect/platform/HttpRouter#PathInput": + replacement: "HttpRouter.PathInput" + note: "Retained as an absolute slash path or wildcard." +"@effect/platform/HttpRouter#post": + replacement: "HttpRouter.add(\"POST\", path, handler, options)" + note: "Register a route layer; handlers must produce HttpServerResponse." +"@effect/platform/HttpRouter#prefixAll": + replacement: "HttpRouter.addAll(routes, { prefix })" + note: "Apply the prefix while registering route descriptors." +"@effect/platform/HttpRouter#put": + replacement: "HttpRouter.add(\"PUT\", path, handler, options)" + note: "Register a route layer; handlers must produce HttpServerResponse." +"@effect/platform/HttpRouter#Route.Middleware": + replacement: "Effect.Effect" + note: "Spell the route response Effect directly, or use HttpRouter.middleware for transforms." +"@effect/platform/HttpRouter#RouteContextTypeId": + replacement: "none" + note: "The nominal id is internal in v4; access HttpRouter.RouteContext as a service." +"@effect/platform/HttpRouter#RouteTypeId": + replacement: "none" + note: "The nominal id is internal in v4; construct routes with HttpRouter.route." +"@effect/platform/HttpRouter#setRouterConfig": + replacement: "Layer.succeed(HttpRouter.RouterConfig)(config)" + note: "Provide the RouterConfig Context.Reference as a layer." +"@effect/platform/HttpRouter#Tag": + replacement: "none" + note: "Custom router tags were removed; use the singleton router service and registration layers." +"@effect/platform/HttpRouter#toHttpApp": + replacement: "HttpRouter.toHttpEffect" + note: "Pass the route-registration layer to build the server handler effect." +"@effect/platform/HttpRouter#transform": + replacement: "HttpRouter.middleware" + note: "Express the route-wide response Effect transform as router middleware." +"@effect/platform/HttpRouter#TypeId": + replacement: "none" + note: "The router nominal service id is internal in v4; use HttpRouter.HttpRouter." +"@effect/platform/HttpRouter#withRouterConfig": + replacement: "Effect.provideService(effect, HttpRouter.RouterConfig, config)" + note: "Provide the RouterConfig Context.Reference locally instead of setting a FiberRef." diff --git a/migration/annotations/effect__platform__HttpServer.yaml b/migration/annotations/effect__platform__HttpServer.yaml new file mode 100644 index 000000000..c49498a3f --- /dev/null +++ b/migration/annotations/effect__platform__HttpServer.yaml @@ -0,0 +1,30 @@ +"@effect/platform/HttpServer#Address": + replacement: "effect/unstable/net/NetAddress#SocketAddress" + note: "Replaced by the shared concrete internet-or-Unix socket address union." +"@effect/platform/HttpServer#addressWith": + replacement: "HttpServer.HttpServer.use(({ address }) => effect(address))" + note: "The accessor was removed; read the service and pass its Address to the callback." +"@effect/platform/HttpServer#HttpServer": + replacement: "HttpServer.HttpServer" + note: "The interface and tag became one Context.Service class; use its Service member for implementations." +"@effect/platform/HttpServer#layerContext": + replacement: "HttpServer.layerServices" + note: "Renamed; it provides the standard HTTP platform services." +"@effect/platform/HttpServer#make": + replacement: "HttpServer.make" + note: "Retained; it returns the Context.Service implementation." +"@effect/platform/HttpServer#ServeOptions": + replacement: "none" + note: "The unused respond option model was removed with no shared v4 counterpart." +"@effect/platform/HttpServer#TcpAddress": + replacement: "effect/unstable/net/NetAddress#InetAddress" + note: "Replaced by the shared resolved internet-address model; use address and port instead of hostname and port." +"@effect/platform/HttpServer#TypeId": + replacement: "none" + note: "The public TypeId was removed; HttpServer is now a Context.Service class." +"@effect/platform/HttpServer#UnixAddress": + replacement: "effect/unstable/net/NetAddress#UnixPathAddress" + note: "Replaced by the shared Unix filesystem-path address model." +"@effect/platform/HttpServer#serve": + replacement: "effect/unstable/http/HttpServer#serve" + note: "Moved to the v4 HTTP module; the application is now an Effect producing HttpServerResponse rather than the separate HttpApp model." diff --git a/migration/annotations/effect__platform__HttpServerError.yaml b/migration/annotations/effect__platform__HttpServerError.yaml new file mode 100644 index 000000000..60470daf8 --- /dev/null +++ b/migration/annotations/effect__platform__HttpServerError.yaml @@ -0,0 +1,15 @@ +"@effect/platform/HttpServerError#clientAbortFiberId": + replacement: "HttpServerError.ClientAbort.annotation" + note: "Client aborts now use a Cause context annotation rather than a sentinel FiberId." +"@effect/platform/HttpServerError#HttpServerError": + replacement: "HttpServerError.HttpServerError | HttpServerError.ServeError" + note: "Handler failures became a tagged wrapper, while ServeError remains separate." +"@effect/platform/HttpServerError#isServerError": + replacement: "HttpServerError.isHttpServerError" + note: "Renamed and narrowed to wrapped handler errors; test ServeError separately if needed." +"@effect/platform/HttpServerError#RequestError": + replacement: "HttpServerError.RequestParseError (constructor) / HttpServerError.RequestError (type)" + note: "The constructible class became RequestParseError; RequestError is now a broader type union." +"@effect/platform/HttpServerError#TypeId": + replacement: "HttpServerError.isHttpServerError" + note: "The brand is private in v4; use the public runtime refinement." diff --git a/migration/annotations/effect__platform__HttpServerRequest.yaml b/migration/annotations/effect__platform__HttpServerRequest.yaml new file mode 100644 index 000000000..82ea35001 --- /dev/null +++ b/migration/annotations/effect__platform__HttpServerRequest.yaml @@ -0,0 +1,27 @@ +"@effect/platform/HttpServerRequest#fromWeb": + replacement: "HttpServerRequest.fromWeb" + note: "Retained for wrapping a Web Request." +"@effect/platform/HttpServerRequest#ParsedSearchParams": + replacement: "HttpServerRequest.ParsedSearchParams" + note: "The marker and tag became one Context.Service class." +"@effect/platform/HttpServerRequest#persistedMultipart": + replacement: "HttpServerRequest.HttpServerRequest.use((request) => request.multipart)" + note: "Use the request service's `.use` helper to return its cached multipart effect." +"@effect/platform/HttpServerRequest#schemaBodyJson": + replacement: "HttpServerRequest.schemaBodyJson" + note: "Retained with v4 Schema constraints and error types." +"@effect/platform/HttpServerRequest#schemaBodyUrlParams": + replacement: "HttpServerRequest.schemaBodyUrlParams" + note: "Retained with ConstraintCodec input and v4 error types." +"@effect/platform/HttpServerRequest#schemaHeaders": + replacement: "HttpServerRequest.schemaHeaders" + note: "Retained with ConstraintCodec input and SchemaError failures." +"@effect/platform/HttpServerRequest#toWeb": + replacement: "HttpServerRequest.toWeb" + note: "Retained and captures the current Context for streamed bodies." +"@effect/platform/HttpServerRequest#toWebEither": + replacement: "HttpServerRequest.toWebResult" + note: "Either became Result, and the optional Runtime became an optional Context." +"@effect/platform/HttpServerRequest#TypeId": + replacement: "typeof HttpServerRequest.TypeId" + note: "TypeId remains public but is now a string constant; use typeof in type position." diff --git a/migration/annotations/effect__platform__HttpServerRespondable.yaml b/migration/annotations/effect__platform__HttpServerRespondable.yaml new file mode 100644 index 000000000..306afb813 --- /dev/null +++ b/migration/annotations/effect__platform__HttpServerRespondable.yaml @@ -0,0 +1,3 @@ +"@effect/platform/HttpServerRespondable#symbol": + replacement: "HttpServerRespondable.symbol" + note: "Retained as a string protocol key rather than a unique symbol." diff --git a/migration/annotations/effect__platform__HttpServerResponse.yaml b/migration/annotations/effect__platform__HttpServerResponse.yaml new file mode 100644 index 000000000..e91e0818b --- /dev/null +++ b/migration/annotations/effect__platform__HttpServerResponse.yaml @@ -0,0 +1,66 @@ +"@effect/platform/HttpServerResponse#empty": + replacement: "HttpServerResponse.empty" + note: "Retained with default status 204; responses are no longer Effects or Respondables." +"@effect/platform/HttpServerResponse#expireCookie": + replacement: "HttpServerResponse.expireCookie" + note: "Now effectful and safe; use expireCookieUnsafe for synchronous throwing behavior." +"@effect/platform/HttpServerResponse#file": + replacement: "HttpServerResponse.file" + note: "Retained; offset and bytesToRead accept ByteSize.Input, while chunkSize uses number. Path-backed responses validate ranges and clamp content length to the available bytes." +"@effect/platform/HttpServerResponse#formData": + replacement: "HttpServerResponse.formData" + note: "Moved unchanged." +"@effect/platform/HttpServerResponse#fromWeb": + replacement: "HttpServerResponse.fromWeb" + note: "Retained; Set-Cookie headers become Cookies and Web bodies become stream bodies." +"@effect/platform/HttpServerResponse#isServerResponse": + replacement: "HttpServerResponse.isHttpServerResponse" + note: "Renamed." +"@effect/platform/HttpServerResponse#json": + replacement: "HttpServerResponse.json" + note: "Retained as the safe effectful JSON constructor." +"@effect/platform/HttpServerResponse#raw": + replacement: "HttpServerResponse.raw" + note: "Moved unchanged." +"@effect/platform/HttpServerResponse#setBody": + replacement: "HttpServerResponse.setBody" + note: "Retained and reflects body content metadata in response headers." +"@effect/platform/HttpServerResponse#setCookie": + replacement: "HttpServerResponse.setCookie" + note: "Retained as the safe effectful cookie setter." +"@effect/platform/HttpServerResponse#setHeader": + replacement: "HttpServerResponse.setHeader" + note: "Retained with data-first and data-last overloads." +"@effect/platform/HttpServerResponse#setHeaders": + replacement: "HttpServerResponse.setHeaders" + note: "Retained with data-first and data-last overloads." +"@effect/platform/HttpServerResponse#stream": + replacement: "HttpServerResponse.stream" + note: "Retained; v4 Stream no longer has a service type parameter." +"@effect/platform/HttpServerResponse#text": + replacement: "HttpServerResponse.text" + note: "Moved unchanged." +"@effect/platform/HttpServerResponse#toWeb": + replacement: "HttpServerResponse.toWeb" + note: "Retained, but the optional Runtime became an optional Context for stream execution." +"@effect/platform/HttpServerResponse#TypeId": + replacement: "HttpServerResponse.isHttpServerResponse" + note: "The response brand is private in v4; use the public runtime refinement." +"@effect/platform/HttpServerResponse#uint8Array": + replacement: "HttpServerResponse.uint8Array" + note: "Moved unchanged." +"@effect/platform/HttpServerResponse#unsafeJson": + replacement: "HttpServerResponse.jsonUnsafe" + note: "Renamed to put Unsafe last; serialization failures still throw." +"@effect/platform/HttpServerResponse#unsafeSetCookie": + replacement: "HttpServerResponse.setCookieUnsafe" + note: "Renamed to put Unsafe last; invalid cookies still throw." +"@effect/platform/HttpServerResponse#unsafeSetCookies": + replacement: "HttpServerResponse.setCookiesUnsafe" + note: "Renamed to put Unsafe last; invalid cookies still throw." +"@effect/platform/HttpServerResponse#urlParams": + replacement: "HttpServerResponse.urlParams" + note: "Retained and widened to accept UrlParams.Input." +"@effect/platform/HttpServerResponse#fileWeb": + replacement: "HttpServerResponse.fileWeb" + note: "Web File offset, bytesToRead, and chunkSize options use number, unlike path-backed ByteSize.Input ranges." diff --git a/migration/annotations/effect__platform__KeyValueStore.yaml b/migration/annotations/effect__platform__KeyValueStore.yaml new file mode 100644 index 000000000..ba36c699f --- /dev/null +++ b/migration/annotations/effect__platform__KeyValueStore.yaml @@ -0,0 +1,27 @@ +"@effect/platform/KeyValueStore#KeyValueStore": + replacement: "KeyValueStore.KeyValueStore" + note: "The service moved to effect/unstable/persistence/KeyValueStore; missing values now use undefined and operations fail with KeyValueStoreError." +"@effect/platform/KeyValueStore#KeyValueStore.AnyStore": + replacement: "KeyValueStore.KeyValueStore | KeyValueStore.SchemaStore" + note: "The convenience namespace alias was removed; write the store union explicitly when needed." +"@effect/platform/KeyValueStore#layerMemory": + replacement: "KeyValueStore.layerMemory" + note: "The in-memory layer remains in the moved module." +"@effect/platform/KeyValueStore#layerSchema": + replacement: "KeyValueStore.toSchemaStore" + note: "Schema stores are now derived with toSchemaStore; define the desired Context.Service and layer explicitly." +"@effect/platform/KeyValueStore#layerStorage": + replacement: "KeyValueStore.layerStorage" + note: "The Web Storage layer remains in the moved module." +"@effect/platform/KeyValueStore#make": + replacement: "KeyValueStore.make" + note: "The constructor remains in the moved module with v4 MakeOptions." +"@effect/platform/KeyValueStore#prefix": + replacement: "KeyValueStore.prefix" + note: "The prefixed-store combinator remains in the moved module." +"@effect/platform/KeyValueStore#SchemaStoreTypeId": + replacement: "none" + note: "The v4 SchemaStore has no public type-id alias; use the SchemaStore interface." +"@effect/platform/KeyValueStore#TypeId": + replacement: "none" + note: "The KeyValueStore runtime marker is internal in v4; use the service and interface." diff --git a/migration/annotations/effect__platform__MsgPack.yaml b/migration/annotations/effect__platform__MsgPack.yaml new file mode 100644 index 000000000..e6ff8e67c --- /dev/null +++ b/migration/annotations/effect__platform__MsgPack.yaml @@ -0,0 +1,30 @@ +"@effect/platform/MsgPack": + replacement: "effect/unstable/encoding/SchemaBinary" + note: "MessagePack support was removed. Schema-aware encode, decode, and duplex now live on SchemaBinary. Untyped MessagePack of unknown values has no replacement." +"@effect/platform/MsgPack#duplex": + replacement: "SchemaBinary.duplex" + note: "MessagePack was removed. Wrap a byte channel with SchemaBinary.duplex and explicit input and output schemas." +"@effect/platform/MsgPack#duplexSchema": + replacement: "SchemaBinary.duplex" + note: "MessagePack was removed. Use SchemaBinary.duplex with the v4 Schema model." +"@effect/platform/MsgPack#ErrorTypeId": + replacement: "Schema.SchemaError" + note: "MsgPackError was removed with MessagePack. SchemaBinary channels fail with Schema.SchemaError." +"@effect/platform/MsgPack#MsgPackError": + replacement: "Schema.SchemaError" + note: "MsgPackError was removed with MessagePack. SchemaBinary channels fail with Schema.SchemaError." +"@effect/platform/MsgPack#pack": + replacement: "SchemaBinary.encode" + note: "MessagePack was removed. Encode schema values to binary frames with SchemaBinary.encode." +"@effect/platform/MsgPack#packSchema": + replacement: "SchemaBinary.encode" + note: "MessagePack was removed. Use SchemaBinary.encode with the v4 Schema model." +"@effect/platform/MsgPack#schema": + replacement: "SchemaBinary.toCodec" + note: "MessagePack was removed. Derive a binary codec from a Schema with SchemaBinary.toCodec." +"@effect/platform/MsgPack#unpack": + replacement: "SchemaBinary.decode" + note: "MessagePack was removed. Decode binary frames with SchemaBinary.decode." +"@effect/platform/MsgPack#unpackSchema": + replacement: "SchemaBinary.decode" + note: "MessagePack was removed. Use SchemaBinary.decode with the v4 Schema model." diff --git a/migration/annotations/effect__platform__Multipart.yaml b/migration/annotations/effect__platform__Multipart.yaml new file mode 100644 index 000000000..3e1a86345 --- /dev/null +++ b/migration/annotations/effect__platform__Multipart.yaml @@ -0,0 +1,57 @@ +"@effect/platform/Multipart#ErrorTypeId": + replacement: "Multipart.MultipartError" + note: "The public error type-id alias was removed; use the MultipartError class." +"@effect/platform/Multipart#FieldMimeTypes": + replacement: "Multipart.FieldMimeTypes" + note: "The setting remains but is now a Context.Reference rather than a service class." +"@effect/platform/Multipart#FileSchema": + replacement: "Multipart.PersistedFileSchema" + note: "The schema for persisted multipart files was renamed." +"@effect/platform/Multipart#isField": + replacement: "Multipart.isField" + note: "The guard remains in effect/unstable/http/Multipart." +"@effect/platform/Multipart#isFile": + replacement: "Multipart.isFile" + note: "The guard remains in effect/unstable/http/Multipart." +"@effect/platform/Multipart#isPart": + replacement: "Multipart.isPart" + note: "The guard remains in effect/unstable/http/Multipart." +"@effect/platform/Multipart#MaxFieldSize": + replacement: "Multipart.MaxFieldSize" + note: "Now a Context.Reference; provide a value such as ByteSize.bytes(100)." +"@effect/platform/Multipart#MaxFileSize": + replacement: "Multipart.MaxFileSize" + note: "Now a Context.Reference; provide ByteSize.bytes(100), for example, or undefined for no limit." +"@effect/platform/Multipart#MaxParts": + replacement: "Multipart.MaxParts" + note: "The setting remains as a Context.Reference; use undefined rather than Option.none for no limit." +"@effect/platform/Multipart#schemaJson": + replacement: "Multipart.schemaJson" + note: "The JSON-field decoder remains in effect/unstable/http/Multipart and uses v4 Schema constraints." +"@effect/platform/Multipart#TypeId": + replacement: "typeof Multipart.TypeId" + note: "The runtime marker remains exported, but the separate type alias was removed." +"@effect/platform/Multipart#withFieldMimeTypes": + replacement: "Effect.provideService(Multipart.FieldMimeTypes, mimeTypes)" + note: "Provide the v4 Context.Reference around the effect." +"@effect/platform/Multipart#withLimits": + replacement: "Effect.provideContext(effect, Multipart.limitsServices(options))" + note: "Build the multipart limit context and provide it to the effect; Option-valued limits became optional plain values." +"@effect/platform/Multipart#withLimitsStream": + replacement: "Stream.provideContext(stream, Multipart.limitsServices(options))" + note: "Build the multipart limit context and provide it to the stream; Option-valued limits became optional plain values." +"@effect/platform/Multipart#withMaxFieldSize": + replacement: "Effect.provideService(Multipart.MaxFieldSize, size)" + note: "Provide a ByteSize value, such as ByteSize.bytes(100). To normalize ByteSize.Input options, use Multipart.limitsServices." +"@effect/platform/Multipart#withMaxFileSize": + replacement: "Effect.provideService(Multipart.MaxFileSize, size)" + note: "Replace Option.none with undefined and Option.some(value) with a normalized ByteSize value. To normalize ByteSize.Input options, use Multipart.limitsServices." +"@effect/platform/Multipart#withMaxParts": + replacement: "Effect.provideService(Multipart.MaxParts, count)" + note: "Provide the v4 Context.Reference around the effect, converting Option.none to undefined." +"@effect/platform/Multipart#makeChannel": + replacement: "effect/unstable/http/Multipart#makeChannel" + note: "The channel constructor moved and no longer accepts bufferSize; input and output chunks use non-empty readonly arrays." +"@effect/platform/Multipart#withLimits.Options": + replacement: "Multipart.withLimits.Options" + note: "Limit fields now use optional plain numbers or ByteSize inputs; convert Option.none to undefined and Option.some(value) to value." diff --git a/migration/annotations/effect__platform__Ndjson.yaml b/migration/annotations/effect__platform__Ndjson.yaml new file mode 100644 index 000000000..9786ff676 --- /dev/null +++ b/migration/annotations/effect__platform__Ndjson.yaml @@ -0,0 +1,39 @@ +"@effect/platform/Ndjson#duplex": + replacement: "Ndjson.duplex" + note: "The API moved to effect/unstable/encoding/Ndjson." +"@effect/platform/Ndjson#duplexSchema": + replacement: "Ndjson.duplexSchema" + note: "The API moved to effect/unstable/encoding/Ndjson and uses v4 Schema constraints." +"@effect/platform/Ndjson#ErrorTypeId": + replacement: "Ndjson.NdjsonError" + note: "The public error marker was removed; use the NdjsonError class." +"@effect/platform/Ndjson#NdjsonErrorTypeId": + replacement: "Ndjson.NdjsonError" + note: "The public error type-id alias was removed; use the NdjsonError class." +"@effect/platform/Ndjson#NdjsonOptions": + replacement: "{ readonly ignoreEmptyLines?: boolean }" + note: "The standalone options interface was removed; decoding and duplex APIs accept this inline shape." +"@effect/platform/Ndjson#pack": + replacement: "Ndjson.encode" + note: "The NDJSON channel constructor was renamed from pack to encode." +"@effect/platform/Ndjson#packSchema": + replacement: "Ndjson.encodeSchema" + note: "The schema-aware pack channel was renamed to encodeSchema." +"@effect/platform/Ndjson#packSchemaString": + replacement: "Ndjson.encodeSchemaString" + note: "The string schema pack channel was renamed to encodeSchemaString." +"@effect/platform/Ndjson#packString": + replacement: "Ndjson.encodeString" + note: "The string pack channel was renamed to encodeString." +"@effect/platform/Ndjson#unpack": + replacement: "Ndjson.decode" + note: "The NDJSON channel constructor was renamed from unpack to decode." +"@effect/platform/Ndjson#unpackSchema": + replacement: "Ndjson.decodeSchema" + note: "The schema-aware unpack channel was renamed to decodeSchema." +"@effect/platform/Ndjson#unpackSchemaString": + replacement: "Ndjson.decodeSchemaString" + note: "The string schema unpack channel was renamed to decodeSchemaString." +"@effect/platform/Ndjson#unpackString": + replacement: "Ndjson.decodeString" + note: "The string unpack channel was renamed to decodeString." diff --git a/migration/annotations/effect__platform__OpenApi.yaml b/migration/annotations/effect__platform__OpenApi.yaml new file mode 100644 index 000000000..fe9cb44cf --- /dev/null +++ b/migration/annotations/effect__platform__OpenApi.yaml @@ -0,0 +1,18 @@ +"@effect/platform/OpenApi#AdditionalPropertiesStrategy": + replacement: "none" + note: "OpenApi.fromApi no longer accepts generation options; standalone JSON Schema generation has a separate additionalProperties option." +"@effect/platform/OpenApi#annotations": + replacement: "effect/unstable/httpapi/OpenApi#annotations" + note: "Same annotation-context helper after the module move." +"@effect/platform/OpenApi#Exclude": + replacement: "effect/unstable/httpapi/OpenApi#Exclude" + note: "Same annotation key and default; it is now a Context.Reference value." +"@effect/platform/OpenApi#fromApi": + replacement: "effect/unstable/httpapi/OpenApi#fromApi" + note: "The operation remains and returns OpenAPI 3.1, but the signature is now only fromApi(api)." +"@effect/platform/OpenApi#OpenApiSpecContentType": + replacement: "string" + note: "The closed media-type union was removed so custom and streaming media types are supported." +"@effect/platform/OpenApi#Title": + replacement: "effect/unstable/httpapi/OpenApi#Title" + note: "Same annotation role, now implemented as a v4 Context.Service." diff --git a/migration/annotations/effect__platform__OpenApiJsonSchema.yaml b/migration/annotations/effect__platform__OpenApiJsonSchema.yaml new file mode 100644 index 000000000..0bb6527bc --- /dev/null +++ b/migration/annotations/effect__platform__OpenApiJsonSchema.yaml @@ -0,0 +1,60 @@ +"@effect/platform/OpenApiJsonSchema#Any": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces were consolidated into the open, dialect-neutral JSON Schema object model." +"@effect/platform/OpenApiJsonSchema#AnyObject": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces were consolidated; construct the required object directly." +"@effect/platform/OpenApiJsonSchema#AnyOf": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces were consolidated into the general object model." +"@effect/platform/OpenApiJsonSchema#Array": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces were consolidated into the general object model." +"@effect/platform/OpenApiJsonSchema#Boolean": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow boolean interface was consolidated into the open, dialect-neutral JSON Schema object model." +"@effect/platform/OpenApiJsonSchema#Empty": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces and special id shapes were removed." +"@effect/platform/OpenApiJsonSchema#Enum": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces were consolidated into the general object model." +"@effect/platform/OpenApiJsonSchema#Enums": + replacement: "effect/JsonSchema#JsonSchema" + note: "The Effect-specific comment enum shape has no named v4 interface; use the general object model." +"@effect/platform/OpenApiJsonSchema#Integer": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow numeric interfaces were consolidated into the general object model." +"@effect/platform/OpenApiJsonSchema#JsonSchema": + replacement: "effect/JsonSchema#JsonSchema" + note: "Use the dialect-neutral open JSON Schema object model." +"@effect/platform/OpenApiJsonSchema#make": + replacement: "effect/Schema#toJsonSchemaDocument + effect/JsonSchema#toMultiDocumentOpenApi3_1" + note: "Generate Draft 2020-12, wrap the root in a multi-document, then convert references and definitions to OpenAPI 3.1." +"@effect/platform/OpenApiJsonSchema#makeWithDefs": + replacement: "effect/SchemaRepresentation#toJsonSchemaMultiDocument + effect/JsonSchema#toMultiDocumentOpenApi3_1" + note: "Definitions are returned separately; build a multi-document representation and convert it to OpenAPI 3.1." +"@effect/platform/OpenApiJsonSchema#Never": + replacement: "effect/JsonSchema#JsonSchema" + note: "The special never-schema interface was consolidated into the open JSON Schema object model; represent it with a not constraint." +"@effect/platform/OpenApiJsonSchema#Number": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow number interface was consolidated into the open, dialect-neutral JSON Schema object model." +"@effect/platform/OpenApiJsonSchema#Numeric": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow numeric interfaces were consolidated into the general object model." +"@effect/platform/OpenApiJsonSchema#Object": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow node interfaces were consolidated into the general object model." +"@effect/platform/OpenApiJsonSchema#Ref": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow ref interface was consolidated; OpenAPI conversion rewrites definition references." +"@effect/platform/OpenApiJsonSchema#Root": + replacement: "effect/JsonSchema#MultiDocument" + note: "OpenAPI generation keeps roots in schemas and shared components in definitions; the inline-definitions root model is gone." +"@effect/platform/OpenApiJsonSchema#String": + replacement: "effect/JsonSchema#JsonSchema" + note: "The narrow string interface was consolidated into the open, dialect-neutral JSON Schema object model." +"@effect/platform/OpenApiJsonSchema#Void": + replacement: "effect/JsonSchema#JsonSchema" + note: "The special void-schema interface was consolidated into the open JSON Schema object model." diff --git a/migration/annotations/effect__platform__Path.yaml b/migration/annotations/effect__platform__Path.yaml new file mode 100644 index 000000000..c80325ae5 --- /dev/null +++ b/migration/annotations/effect__platform__Path.yaml @@ -0,0 +1,3 @@ +"@effect/platform/Path#TypeId": + replacement: "typeof Path.TypeId" + note: "The module moved to effect/Path; the runtime marker remains exported but the separate type alias was removed." diff --git a/migration/annotations/effect__platform__PlatformConfigProvider.yaml b/migration/annotations/effect__platform__PlatformConfigProvider.yaml new file mode 100644 index 000000000..82b29e988 --- /dev/null +++ b/migration/annotations/effect__platform__PlatformConfigProvider.yaml @@ -0,0 +1,15 @@ +"@effect/platform/PlatformConfigProvider#fromFileTree": + replacement: "ConfigProvider.fromDir" + note: "The provider moved into effect/ConfigProvider and was renamed; rootDirectory is now rootPath." +"@effect/platform/PlatformConfigProvider#layerDotEnv": + replacement: "ConfigProvider.layer(ConfigProvider.fromDotEnv({ path }))" + note: "Use the v4 dotenv provider effect and install it with ConfigProvider.layer." +"@effect/platform/PlatformConfigProvider#layerDotEnvAdd": + replacement: "ConfigProvider.layerAdd(ConfigProvider.fromDotEnv({ path }))" + note: "Use the v4 dotenv provider effect and compose it with ConfigProvider.layerAdd." +"@effect/platform/PlatformConfigProvider#layerFileTree": + replacement: "ConfigProvider.layer(ConfigProvider.fromDir({ rootPath }))" + note: "Use the renamed directory-tree provider and install it with ConfigProvider.layer." +"@effect/platform/PlatformConfigProvider#layerFileTreeAdd": + replacement: "ConfigProvider.layerAdd(ConfigProvider.fromDir({ rootPath }))" + note: "Use the renamed directory-tree provider and compose it with ConfigProvider.layerAdd." diff --git a/migration/annotations/effect__platform__PlatformLogger.yaml b/migration/annotations/effect__platform__PlatformLogger.yaml new file mode 100644 index 000000000..6e418cd39 --- /dev/null +++ b/migration/annotations/effect__platform__PlatformLogger.yaml @@ -0,0 +1,3 @@ +"@effect/platform/PlatformLogger": + replacement: "effect/Logger" + note: "toFile moved to Logger.toFile; it still requires a FileSystem service (e.g. NodeFileSystem.layer) and Scope." diff --git a/migration/annotations/effect__platform__Runtime.yaml b/migration/annotations/effect__platform__Runtime.yaml new file mode 100644 index 000000000..750590e00 --- /dev/null +++ b/migration/annotations/effect__platform__Runtime.yaml @@ -0,0 +1,3 @@ +"@effect/platform/Runtime#RunMain": + replacement: "ReturnType" + note: "The standalone interface was removed; derive the runner type from effect/Runtime.makeRunMain. disablePrettyLogger is no longer an option." diff --git a/migration/annotations/effect__platform__Socket.yaml b/migration/annotations/effect__platform__Socket.yaml new file mode 100644 index 000000000..ecfcfec2d --- /dev/null +++ b/migration/annotations/effect__platform__Socket.yaml @@ -0,0 +1,36 @@ +"@effect/platform/Socket#CloseEventTypeId": + replacement: "Socket.CloseEvent" + note: "The close-event marker is internal in v4; use the CloseEvent class or Socket.isCloseEvent." +"@effect/platform/Socket#currentSendQueueCapacity": + replacement: none + note: "The send queue was removed. The v4 Socket is pull-based: acquire socket.reader in a scope and pull frame batches; writes apply the transport's native backpressure." +"@effect/platform/Socket#layerWebSocket": + replacement: "Socket.layerWebSocket" + note: "The constructor remains in effect/unstable/socket/Socket; its URL may now also be an Effect." +"@effect/platform/Socket#SocketError": + replacement: "Socket.SocketError" + note: "The old union became a tagged wrapper around SocketReadError, SocketWriteError, SocketOpenError, or SocketCloseError." +"@effect/platform/Socket#SocketErrorTypeId": + replacement: "Socket.SocketErrorTypeId" + note: "The error marker remains in effect/unstable/socket/Socket." +"@effect/platform/Socket#SocketGenericError": + replacement: "Socket.SocketReadError | Socket.SocketWriteError | Socket.SocketOpenError" + note: "The generic reason discriminator was replaced by dedicated read, write, and open error classes." +"@effect/platform/Socket#TypeId": + replacement: "typeof Socket.TypeId" + note: "The socket marker remains exported, but the separate type alias was removed." +"@effect/platform/Socket#WebSocket": + replacement: "Socket.WebSocket" + note: "The opaque service moved to effect/unstable/socket/Socket and is now a Context.Service class for globalThis.WebSocket." +"@effect/platform/Socket#WebSocketConstructor": + replacement: "Socket.WebSocketConstructor" + note: "The service moved to effect/unstable/socket/Socket and is now a Context.Service class." +"@effect/platform/Socket#defaultCloseCodeIsError": + replacement: none + note: "Sockets no longer classify close codes; every close fails the reader's pull with a SocketError wrapping SocketCloseError. Consumers that treat a close as normal catch the error." +"@effect/platform/Socket#fromTransformStream": + replacement: "Socket.fromTransformStream" + note: "The constructor remains in effect/unstable/socket/Socket but drops closeCodeIsError; every close fails the reader's pull with a SocketError wrapping SocketCloseError." +"@effect/platform/Socket#toChannelMap": + replacement: none + note: "The v4 Socket read side is an Effect that never completes via Cause.Done; map frames by acquiring Socket.readerBytes or Socket.readerString, or Effect.map the reader from socket.reader, and use Socket.toChannel or Socket.toChannelString for duplex channels." diff --git a/migration/annotations/effect__platform__SocketServer.yaml b/migration/annotations/effect__platform__SocketServer.yaml new file mode 100644 index 000000000..6367ff8e1 --- /dev/null +++ b/migration/annotations/effect__platform__SocketServer.yaml @@ -0,0 +1,12 @@ +"@effect/platform/SocketServer#Address": + replacement: "effect/unstable/net/NetAddress#SocketAddress" + note: "Replaced by the shared concrete internet-or-Unix socket address union." +"@effect/platform/SocketServer#ErrorTypeId": + replacement: "SocketServer.ErrorTypeId" + note: "The API moved to effect/unstable/socket/SocketServer and retains this name." +"@effect/platform/SocketServer#TcpAddress": + replacement: "effect/unstable/net/NetAddress#InetAddress" + note: "Replaced by the shared resolved internet-address model; use address and port instead of hostname and port." +"@effect/platform/SocketServer#UnixAddress": + replacement: "effect/unstable/net/NetAddress#UnixPathAddress" + note: "Replaced by the shared Unix filesystem-path address model." diff --git a/migration/annotations/effect__platform__Template.yaml b/migration/annotations/effect__platform__Template.yaml new file mode 100644 index 000000000..92bdf007f --- /dev/null +++ b/migration/annotations/effect__platform__Template.yaml @@ -0,0 +1,9 @@ +"@effect/platform/Template#Interpolated.Context": + replacement: "Template.Interpolated.Context" + note: "The API moved to effect/unstable/http/Template; v4 interpolation types also account for Effect values." +"@effect/platform/Template#Interpolated.Error": + replacement: "Template.Interpolated.Error" + note: "The API moved to effect/unstable/http/Template; v4 interpolation types also account for Effect values." +"@effect/platform/Template#make": + replacement: "Template.make" + note: "The API moved to effect/unstable/http/Template; v4 interpolation types also account for Effect values." diff --git a/migration/annotations/effect__platform__Terminal.yaml b/migration/annotations/effect__platform__Terminal.yaml new file mode 100644 index 000000000..9750e3f2a --- /dev/null +++ b/migration/annotations/effect__platform__Terminal.yaml @@ -0,0 +1,6 @@ +"@effect/platform/Terminal#isQuitException": + replacement: "Terminal.isQuitError" + note: "The quit sentinel was renamed from QuitException to QuitError." +"@effect/platform/Terminal#QuitException": + replacement: "Terminal.QuitError" + note: "The quit sentinel was renamed and moved to effect/Terminal." diff --git a/migration/annotations/effect__platform__Transferable.yaml b/migration/annotations/effect__platform__Transferable.yaml new file mode 100644 index 000000000..02a5415ee --- /dev/null +++ b/migration/annotations/effect__platform__Transferable.yaml @@ -0,0 +1,12 @@ +"@effect/platform/Transferable#CollectorService": + replacement: "Transferable.Collector[\"Service\"]" + note: "The collector interface is now the service type of the Transferable.Collector Context.Service class." +"@effect/platform/Transferable#schema": + replacement: "Transferable.schema" + note: "The schema wrapper moved to effect/unstable/workers/Transferable and uses the v4 Schema model." +"@effect/platform/Transferable#Uint8Array": + replacement: "Transferable.Uint8Array" + note: "The transferable Uint8Array schema remains in the moved module." +"@effect/platform/Transferable#unsafeMakeCollector": + replacement: "Transferable.makeCollectorUnsafe" + note: "The unsafe collector constructor was renamed." diff --git a/migration/annotations/effect__platform__Url.yaml b/migration/annotations/effect__platform__Url.yaml new file mode 100644 index 000000000..de6c44677 --- /dev/null +++ b/migration/annotations/effect__platform__Url.yaml @@ -0,0 +1,9 @@ +"@effect/platform/Url#fromString": + replacement: "Url.fromString" + note: "Retained; returns Result with IllegalArgumentError instead of Either with IllegalArgumentException." +"@effect/platform/Url#setUrlParams": + replacement: "Url.setUrlParams" + note: "Retained and widened to accept UrlParams.Input." +"@effect/platform/Url#urlParams": + replacement: "Url.urlParams" + note: "Retained and returns the v4 UrlParams wrapper." diff --git a/migration/annotations/effect__platform__UrlParams.yaml b/migration/annotations/effect__platform__UrlParams.yaml new file mode 100644 index 000000000..2e4a856df --- /dev/null +++ b/migration/annotations/effect__platform__UrlParams.yaml @@ -0,0 +1,54 @@ +"@effect/platform/UrlParams#append": + replacement: "UrlParams.append" + note: "Retained and returns the immutable UrlParams wrapper." +"@effect/platform/UrlParams#appendAll": + replacement: "UrlParams.appendAll" + note: "Retained and preserves existing parameters." +"@effect/platform/UrlParams#CoercibleRecord": + replacement: "UrlParams.CoercibleRecord" + note: "The recursive interface became a generic mapped type preserving the input shape." +"@effect/platform/UrlParams#empty": + replacement: "UrlParams.empty" + note: "Now a branded iterable object with params rather than a ReadonlyArray." +"@effect/platform/UrlParams#fromInput": + replacement: "UrlParams.fromInput" + note: "Retained and now also accepts an existing UrlParams." +"@effect/platform/UrlParams#Input": + replacement: "UrlParams.Input" + note: "Retained and broadened to include UrlParams itself." +"@effect/platform/UrlParams#makeUrl": + replacement: "Url.make" + note: "Moved to Url, returns Result, and takes string | undefined for the hash." +"@effect/platform/UrlParams#remove": + replacement: "UrlParams.remove" + note: "Retained and removes every value for the key." +"@effect/platform/UrlParams#schemaFromSelf": + replacement: "Schema.UrlParams" + note: "The declaration schema for the v4 wrapper moved to effect/Schema." +"@effect/platform/UrlParams#schemaFromString": + replacement: "Schema.String.pipe(Schema.decodeTo(Schema.UrlParams, { decode: SchemaGetter.transform((s) => UrlParams.fromInput(new URLSearchParams(s))), encode: SchemaGetter.transform(UrlParams.toString) }))" + note: "No prebuilt string codec remains; recreate it by transforming between a query string and UrlParams." +"@effect/platform/UrlParams#schemaJson": + replacement: "Schema.JsonFromUrlParamsField(field).pipe(Schema.decodeTo(schema), Schema.decodeEffect)" + note: "The field codec moved to effect/Schema. Compose it with the target schema, then decode it." +"@effect/platform/UrlParams#schemaParse": + replacement: "UrlParamsFromString.pipe(Schema.decodeTo(Schema.RecordFromUrlParams.pipe(Schema.decodeTo(schema))))" + note: "Recreate the removed helper by composing the string, record, and target codecs." +"@effect/platform/UrlParams#schemaRecord": + replacement: "Schema.RecordFromUrlParams.pipe(Schema.decodeTo(schema))" + note: "RecordFromUrlParams is a base codec in effect/Schema; compose it with the target schema." +"@effect/platform/UrlParams#schemaStruct": + replacement: "Schema.RecordFromUrlParams.pipe(Schema.decodeTo(schema), Schema.decodeEffect)" + note: "Compose the record codec from effect/Schema with the target schema and decode it." +"@effect/platform/UrlParams#set": + replacement: "UrlParams.set" + note: "Retained and replaces all existing values for the key." +"@effect/platform/UrlParams#setAll": + replacement: "UrlParams.setAll" + note: "Retained; supplied keys replace existing values and other keys remain." +"@effect/platform/UrlParams#toString": + replacement: "UrlParams.toString" + note: "Retained and broadened to accept any UrlParams.Input." +"@effect/platform/UrlParams#UrlParams": + replacement: "UrlParams.UrlParams" + note: "Import UrlParams from effect/unstable/http. It is now a branded iterable object with a params field rather than a ReadonlyArray; construct it with UrlParams.make or UrlParams.fromInput." diff --git a/migration/annotations/effect__platform__Worker.yaml b/migration/annotations/effect__platform__Worker.yaml new file mode 100644 index 000000000..21e8d6ae2 --- /dev/null +++ b/migration/annotations/effect__platform__Worker.yaml @@ -0,0 +1,66 @@ +"@effect/platform/Worker#BackingWorker": + replacement: "Worker.Worker" + note: "The low-level backing worker became the primary Worker interface with send and run operations." +"@effect/platform/Worker#layerManager": + replacement: "Worker.WorkerPlatform" + note: "WorkerManager was removed; provide the adapter's WorkerPlatform layer directly." +"@effect/platform/Worker#makeManager": + replacement: "Worker.WorkerPlatform" + note: "WorkerManager was removed; obtain WorkerPlatform and call its spawn method." +"@effect/platform/Worker#makePool": + replacement: "Pool + Worker.WorkerPlatform.spawn" + note: "Generic worker pools are no longer built by this module; build a Pool around WorkerPlatform.spawn, or use RpcClient.makeProtocolWorker for RPC workers." +"@effect/platform/Worker#makePoolLayer": + replacement: "RpcClient.layerProtocolWorker" + note: "The standard v4 worker-pool layer is the worker-backed RPC client protocol; compose it with the RPC client layer." +"@effect/platform/Worker#makePoolSerialized": + replacement: "RpcClient.makeProtocolWorker" + note: "Serialized tagged-request workers were replaced by the worker-backed RPC protocol." +"@effect/platform/Worker#makePoolSerializedLayer": + replacement: "RpcClient.layerProtocolWorker" + note: "Serialized tagged-request worker pools were replaced by the worker-backed RPC protocol layer." +"@effect/platform/Worker#makeSerialized": + replacement: "RpcClient with RpcClient.layerProtocolWorker" + note: "Serialized tagged-request execution moved to the v4 RPC model; define an RpcGroup and use the worker protocol." +"@effect/platform/Worker#PlatformWorker": + replacement: "Worker.WorkerPlatform" + note: "The platform service was renamed and is now a Context.Service class." +"@effect/platform/Worker#PlatformWorkerTypeId": + replacement: "none" + note: "The Context.Service class replaces the public platform-worker type-id alias." +"@effect/platform/Worker#SerializedWorker": + replacement: "RpcClient with RpcClient.layerProtocolWorker" + note: "The serialized worker facade was removed; v4 routes schema-defined RPCs through the worker protocol." +"@effect/platform/Worker#SerializedWorker.Options": + replacement: "RpcWorker.layerInitialMessage" + note: "Use RpcWorker.layerInitialMessage when a worker RPC protocol needs schema-encoded initialization." +"@effect/platform/Worker#SerializedWorkerPool": + replacement: "RpcClient.makeProtocolWorker" + note: "The worker-backed RPC protocol owns its worker pool in v4." +"@effect/platform/Worker#SerializedWorkerPool.Options": + replacement: "Parameters[0]" + note: "Pool sizing options moved to the worker RPC protocol; initial messages are provided separately with RpcWorker.layerInitialMessage." +"@effect/platform/Worker#Worker": + replacement: "Worker.Worker" + note: "The name remains in effect/unstable/workers/Worker, but it is now the low-level send/run abstraction rather than execute/executeEffect." +"@effect/platform/Worker#Worker.Options": + replacement: "Worker.Worker[\"run\"] options" + note: "Encoding moved to RPC schemas; the low-level run operation only accepts an optional onSpawn effect." +"@effect/platform/Worker#Worker.Response": + replacement: "none" + note: "The old tagged-request wire response is gone; worker RPC wire messages are internal to RpcClient and RpcServer." +"@effect/platform/Worker#Worker.Span": + replacement: "none" + note: "The explicit span tuple was removed; the RPC worker protocol handles span propagation internally." +"@effect/platform/Worker#WorkerManager": + replacement: "Worker.WorkerPlatform" + note: "WorkerPlatform now spawns low-level Worker values directly, replacing WorkerManager." +"@effect/platform/Worker#WorkerManagerTypeId": + replacement: "none" + note: "The removed WorkerManager has no v4 type-id; WorkerPlatform is a Context.Service class." +"@effect/platform/Worker#WorkerPool": + replacement: "RpcClient.Protocol" + note: "For serialized request/response workloads use the worker-backed RPC Protocol; for raw messages build a Pool around WorkerPlatform.spawn." +"@effect/platform/Worker#WorkerPool.Options": + replacement: "Parameters[0]" + note: "Worker RPC pool sizing is configured on makeProtocolWorker or layerProtocolWorker." diff --git a/migration/annotations/effect__platform__WorkerError.yaml b/migration/annotations/effect__platform__WorkerError.yaml new file mode 100644 index 000000000..52956590e --- /dev/null +++ b/migration/annotations/effect__platform__WorkerError.yaml @@ -0,0 +1,6 @@ +"@effect/platform/WorkerError#WorkerErrorFrom": + replacement: "WorkerError.WorkerError" + note: "The old serializable reason object was replaced by WorkerError wrapping dedicated spawn, send, receive, or unknown reason classes." +"@effect/platform/WorkerError#WorkerErrorTypeId": + replacement: "WorkerError.TypeId" + note: "The type-level worker error marker was shortened to TypeId in the moved module." diff --git a/migration/annotations/effect__platform__WorkerRunner.yaml b/migration/annotations/effect__platform__WorkerRunner.yaml new file mode 100644 index 000000000..8991b6766 --- /dev/null +++ b/migration/annotations/effect__platform__WorkerRunner.yaml @@ -0,0 +1,54 @@ +"@effect/platform/WorkerRunner#BackingRunner": + replacement: "WorkerRunner.WorkerRunner" + note: "The low-level backing runner became the primary WorkerRunner interface." +"@effect/platform/WorkerRunner#BackingRunner.Message": + replacement: "WorkerRunner.PlatformMessage" + note: "The request/close wire tuple moved to the top-level PlatformMessage type." +"@effect/platform/WorkerRunner#CloseLatch": + replacement: "none" + note: "The public close-latch service was removed; WorkerRunner implementations manage lifetime through their run effect and adapter scope." +"@effect/platform/WorkerRunner#launch": + replacement: "RpcServer.layerProtocolWorkerRunner" + note: "For schema-defined workers, provide the worker-runner RPC protocol and launch the normal RpcServer layer." +"@effect/platform/WorkerRunner#layer": + replacement: "WorkerRunner.WorkerRunnerPlatform.start + WorkerRunner.WorkerRunner.run" + note: "The generic processing layer was removed; use the low-level runner directly or the RpcServer worker protocol." +"@effect/platform/WorkerRunner#layerCloseLatch": + replacement: "none" + note: "The public close-latch layer was removed; adapter runner lifetime is managed internally." +"@effect/platform/WorkerRunner#layerSerialized": + replacement: "RpcServer.layerProtocolWorkerRunner" + note: "Serialized tagged-request handlers moved to RpcGroup handlers served through the worker-runner RPC protocol." +"@effect/platform/WorkerRunner#make": + replacement: "WorkerRunner.WorkerRunnerPlatform.start + WorkerRunner.WorkerRunner.run" + note: "Start the platform runner and register the low-level message handler directly." +"@effect/platform/WorkerRunner#makeSerialized": + replacement: "RpcServer.makeProtocolWorkerRunner" + note: "Serialized tagged-request execution moved to RpcServer with an RpcGroup handler layer." +"@effect/platform/WorkerRunner#PlatformRunner": + replacement: "WorkerRunner.WorkerRunnerPlatform" + note: "The platform service was renamed and is now a Context.Service class." +"@effect/platform/WorkerRunner#PlatformRunnerTypeId": + replacement: "none" + note: "The Context.Service class replaces the public platform-runner type-id alias." +"@effect/platform/WorkerRunner#Runner": + replacement: "WorkerRunner.WorkerRunner" + note: "The namespace-only runner API was replaced by the low-level WorkerRunner interface." +"@effect/platform/WorkerRunner#Runner.Options": + replacement: "none" + note: "The custom decode/encode callbacks were removed; use raw low-level messages or define schemas in the v4 RPC model." +"@effect/platform/WorkerRunner#SerializedRunner": + replacement: "RpcServer with RpcGroup handlers" + note: "The serialized runner namespace was removed in favor of typed Rpc definitions and RpcServer." +"@effect/platform/WorkerRunner#SerializedRunner.Handlers": + replacement: "RpcGroup.HandlersFrom" + note: "Define an RpcGroup and derive its server handler object type with HandlersFrom." +"@effect/platform/WorkerRunner#SerializedRunner.HandlersContext": + replacement: "RpcGroup.HandlersServices" + note: "Derive services required by an RpcGroup handler object with HandlersServices." +"@effect/platform/WorkerRunner#SerializedRunner.InitialContext": + replacement: "none" + note: "Initial-message layer outputs are no longer inferred by this helper; model initialization as normal RpcGroup handler layers and services." +"@effect/platform/WorkerRunner#SerializedRunner.InitialEnv": + replacement: "none" + note: "Initial-message layer inputs are no longer inferred by this helper; model initialization as normal RpcGroup handler layers and services." diff --git a/migration/annotations/effect__platform__index.yaml b/migration/annotations/effect__platform__index.yaml new file mode 100644 index 000000000..ae474455a --- /dev/null +++ b/migration/annotations/effect__platform__index.yaml @@ -0,0 +1,3 @@ +"@effect/platform/index": + replacement: "none" + note: "The package barrel was removed along with the package; import each module from its new effect location (e.g. effect/FileSystem, effect/unstable/http/HttpClient) per the Import Map." diff --git a/migration/annotations/effect__printer-ansi.yaml b/migration/annotations/effect__printer-ansi.yaml new file mode 100644 index 000000000..26e03671d --- /dev/null +++ b/migration/annotations/effect__printer-ansi.yaml @@ -0,0 +1,3 @@ +"@effect/printer-ansi": + replacement: "none" + note: "The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required." diff --git a/migration/annotations/effect__printer-ansi__Ansi.yaml b/migration/annotations/effect__printer-ansi__Ansi.yaml new file mode 100644 index 000000000..08a09ef88 --- /dev/null +++ b/migration/annotations/effect__printer-ansi__Ansi.yaml @@ -0,0 +1,3 @@ +"@effect/printer-ansi/Ansi": + replacement: none + note: The @effect/printer-ansi package was removed in v4 with no public replacement. Use a maintained ANSI library or local escape-string helpers; the v4 CLI ANSI helpers are internal and cannot be imported. diff --git a/migration/annotations/effect__printer-ansi__AnsiDoc.yaml b/migration/annotations/effect__printer-ansi__AnsiDoc.yaml new file mode 100644 index 000000000..6b8aa9c5c --- /dev/null +++ b/migration/annotations/effect__printer-ansi__AnsiDoc.yaml @@ -0,0 +1,3 @@ +"@effect/printer-ansi/AnsiDoc": + replacement: none + note: The @effect/printer-ansi package and its annotated document algebra were removed in v4. Use strings or another pretty-printing library; for Effect CLI help only, use HelpDoc with CliOutput from effect/unstable/cli. diff --git a/migration/annotations/effect__printer-ansi__Color.yaml b/migration/annotations/effect__printer-ansi__Color.yaml new file mode 100644 index 000000000..aab454ff4 --- /dev/null +++ b/migration/annotations/effect__printer-ansi__Color.yaml @@ -0,0 +1,3 @@ +"@effect/printer-ansi/Color": + replacement: none + note: The @effect/printer-ansi package was removed in v4, and Effect no longer provides a public ANSI color ADT. Use a maintained ANSI library or local escape-string helpers. diff --git a/migration/annotations/effect__printer-ansi__index.yaml b/migration/annotations/effect__printer-ansi__index.yaml new file mode 100644 index 000000000..a29ae873a --- /dev/null +++ b/migration/annotations/effect__printer-ansi__index.yaml @@ -0,0 +1,3 @@ +"@effect/printer-ansi/index": + replacement: "none" + note: "The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required." diff --git a/migration/annotations/effect__printer.yaml b/migration/annotations/effect__printer.yaml new file mode 100644 index 000000000..2f14c60c4 --- /dev/null +++ b/migration/annotations/effect__printer.yaml @@ -0,0 +1,3 @@ +"@effect/printer": + replacement: "none" + note: "The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required." diff --git a/migration/annotations/effect__printer__Doc.yaml b/migration/annotations/effect__printer__Doc.yaml new file mode 100644 index 000000000..7d407cdc9 --- /dev/null +++ b/migration/annotations/effect__printer__Doc.yaml @@ -0,0 +1,3 @@ +"@effect/printer/Doc": + replacement: none + note: The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required. diff --git a/migration/annotations/effect__printer__DocStream.yaml b/migration/annotations/effect__printer__DocStream.yaml new file mode 100644 index 000000000..af84c3873 --- /dev/null +++ b/migration/annotations/effect__printer__DocStream.yaml @@ -0,0 +1,3 @@ +"@effect/printer/DocStream": + replacement: none + note: The @effect/printer package and its laid-out DocStream intermediate representation were removed in v4. Use a target-specific renderer or another pretty-printing library. diff --git a/migration/annotations/effect__printer__DocTree.yaml b/migration/annotations/effect__printer__DocTree.yaml new file mode 100644 index 000000000..64e960263 --- /dev/null +++ b/migration/annotations/effect__printer__DocTree.yaml @@ -0,0 +1,3 @@ +"@effect/printer/DocTree": + replacement: none + note: The @effect/printer package and its structured DocTree rendering representation were removed in v4. Use a target-specific tree and renderer or another pretty-printing library. diff --git a/migration/annotations/effect__printer__Flatten.yaml b/migration/annotations/effect__printer__Flatten.yaml new file mode 100644 index 000000000..4e63575e8 --- /dev/null +++ b/migration/annotations/effect__printer__Flatten.yaml @@ -0,0 +1,3 @@ +"@effect/printer/Flatten": + replacement: none + note: This printer-specific flattening result was removed with the @effect/printer document algebra in v4 and has no direct replacement. diff --git a/migration/annotations/effect__printer__Layout.yaml b/migration/annotations/effect__printer__Layout.yaml new file mode 100644 index 000000000..a393f185b --- /dev/null +++ b/migration/annotations/effect__printer__Layout.yaml @@ -0,0 +1,3 @@ +"@effect/printer/Layout": + replacement: none + note: The @effect/printer layout pipeline was removed in v4 with no general replacement. Use another pretty-printing library; for Effect CLI output only, use CliOutput from effect/unstable/cli. diff --git a/migration/annotations/effect__printer__Optimize.yaml b/migration/annotations/effect__printer__Optimize.yaml new file mode 100644 index 000000000..74cb297f3 --- /dev/null +++ b/migration/annotations/effect__printer__Optimize.yaml @@ -0,0 +1,3 @@ +"@effect/printer/Optimize": + replacement: none + note: The @effect/printer document optimizer was removed with the document algebra in v4. String-based output needs no equivalent optimization stage. diff --git a/migration/annotations/effect__printer__PageWidth.yaml b/migration/annotations/effect__printer__PageWidth.yaml new file mode 100644 index 000000000..d549449d4 --- /dev/null +++ b/migration/annotations/effect__printer__PageWidth.yaml @@ -0,0 +1,3 @@ +"@effect/printer/PageWidth": + replacement: none + note: The @effect/printer page-width layout model was removed in v4 with no direct replacement. Use Terminal.columns for terminal dimensions, or another pretty-printing library for page-width-aware layout. diff --git a/migration/annotations/effect__printer__index.yaml b/migration/annotations/effect__printer__index.yaml new file mode 100644 index 000000000..aaa28f6ad --- /dev/null +++ b/migration/annotations/effect__printer__index.yaml @@ -0,0 +1,3 @@ +"@effect/printer/index": + replacement: "none" + note: "The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required." diff --git a/migration/annotations/effect__rpc.yaml b/migration/annotations/effect__rpc.yaml new file mode 100644 index 000000000..c20087b59 --- /dev/null +++ b/migration/annotations/effect__rpc.yaml @@ -0,0 +1,3 @@ +"@effect/rpc": + replacement: "effect/unstable/rpc" + note: "The @effect/rpc package was merged into the effect package; import the effect/unstable/rpc barrel or import specific modules directly (e.g. effect/unstable/rpc/)." diff --git a/migration/annotations/effect__rpc__Rpc.yaml b/migration/annotations/effect__rpc__Rpc.yaml new file mode 100644 index 000000000..afbdd05e9 --- /dev/null +++ b/migration/annotations/effect__rpc__Rpc.yaml @@ -0,0 +1,75 @@ +"@effect/rpc/Rpc#AddError": + replacement: "effect/unstable/rpc/Rpc#AddError" + note: "Retained; the added error must now be a Schema.Top and the resulting RPC also preserves its explicit service requirements." +"@effect/rpc/Rpc#AddMiddleware": + replacement: "effect/unstable/rpc/Rpc#AddMiddleware" + note: "Retained; middleware is now an RpcMiddleware.AnyService and its provides/requires metadata updates the RPC service requirements." +"@effect/rpc/Rpc#Any": + replacement: "effect/unstable/rpc/Rpc#Any" + note: "Retained as the erased RPC shape; use AnyWithProps when schema and middleware fields are required." +"@effect/rpc/Rpc#AnySchema": + replacement: "Schema.Top" + note: "The RPC-specific erased schema alias was removed; use the v4 top schema constraint." +"@effect/rpc/Rpc#AnyTaggedRequestSchema": + replacement: "none" + note: "RpcGroup no longer converts Schema.TaggedRequest classes into RPCs; declare the contract explicitly with Rpc.make." +"@effect/rpc/Rpc#Context": + replacement: "effect/unstable/rpc/Rpc#Services" + note: "Schema Context became decoding and encoding services; use Services, or ServicesClient / ServicesServer at the corresponding boundary." +"@effect/rpc/Rpc#Error": + replacement: "effect/unstable/rpc/Rpc#Error" + note: "Retained; it includes decoded errors contributed by attached middleware." +"@effect/rpc/Rpc#ErrorEncoded": + replacement: "Rpc.ErrorSchema[\"Encoded\"]" + note: "The alias was removed; index the v4 error schema's Encoded member directly." +"@effect/rpc/Rpc#ErrorExitEncoded": + replacement: "Rpc.ErrorExitSchema[\"Encoded\"]" + note: "Use the new exit error schema, which includes stream and middleware errors, then select its Encoded member." +"@effect/rpc/Rpc#ErrorSchema": + replacement: "effect/unstable/rpc/Rpc#ErrorSchema" + note: "Retained; middleware errors now come from each service's error metadata." +"@effect/rpc/Rpc#fromTaggedRequest": + replacement: "Rpc.make" + note: "Automatic TaggedRequest conversion was removed; pass the tag, payload, success, and error schemas explicitly to Rpc.make." +"@effect/rpc/Rpc#Handler": + replacement: "effect/unstable/rpc/Rpc#Handler" + note: "Retained; handler metadata now supplies ServerClient, RequestId, headers, and the concrete RPC." +"@effect/rpc/Rpc#make": + replacement: "effect/unstable/rpc/Rpc#make" + note: "Retained; schemas use v4 Schema.Top constraints and the defect option accepts Rpc.DefectSchema." +"@effect/rpc/Rpc#Middleware": + replacement: "effect/unstable/rpc/Rpc#Middleware" + note: "Retained and extracts Context.Service identifiers from the attached middleware services." +"@effect/rpc/Rpc#MiddlewareClient": + replacement: "effect/unstable/rpc/Rpc#MiddlewareClient" + note: "Retained; required client middleware is derived from services configured with requiredForClient." +"@effect/rpc/Rpc#Payload": + replacement: "effect/unstable/rpc/Rpc#Payload" + note: "Retained as the decoded payload type; use PayloadConstructor for the input accepted by generated clients." +"@effect/rpc/Rpc#Success": + replacement: "effect/unstable/rpc/Rpc#Success" + note: "Retained as the decoded success type." +"@effect/rpc/Rpc#SuccessChunkEncoded": + replacement: "Rpc.SuccessExitSchema[\"Encoded\"]" + note: "The alias was removed; for a streaming RPC the exit success schema is the stream element schema." +"@effect/rpc/Rpc#SuccessExitEncoded": + replacement: "Rpc.SuccessExitSchema[\"Encoded\"]" + note: "Use the new exit success schema and select its Encoded member; streaming RPC exits use the element schema separately from the terminal void exit." +"@effect/rpc/Rpc#SuccessSchema": + replacement: "effect/unstable/rpc/Rpc#SuccessSchema" + note: "Retained and uses the v4 Schema.Top constraint." +"@effect/rpc/Rpc#Tag": + replacement: "effect/unstable/rpc/Rpc#Tag" + note: "Retained and also accounts for the v4 RPC service-requirement parameter." +"@effect/rpc/Rpc#TypeId": + replacement: "none" + note: "The RPC marker is private in v4; use Rpc.isRpc for runtime checks and Rpc.Any for type constraints." +"@effect/rpc/Rpc#WrapperTypeId": + replacement: "none" + note: "The wrapper marker is private in v4; use Rpc.isWrapper and the public Wrapper type." +"@effect/rpc/Rpc#wrap": + replacement: "effect/unstable/rpc/Rpc#wrap" + note: "Retained after the module move; it still applies fork and uninterruptible handler options, while the return type is now uniformly Rpc.Wrapper." +"@effect/rpc/Rpc#SuccessEncoded": + replacement: "effect/unstable/rpc/Rpc#SuccessEncoded" + note: "Retained after the module move and now accounts for the RPC's explicit service-requirement parameter." diff --git a/migration/annotations/effect__rpc__RpcClient.yaml b/migration/annotations/effect__rpc__RpcClient.yaml new file mode 100644 index 000000000..99a872332 --- /dev/null +++ b/migration/annotations/effect__rpc__RpcClient.yaml @@ -0,0 +1,27 @@ +"@effect/rpc/RpcClient#currentHeaders": + replacement: "effect/unstable/rpc/RpcClient#CurrentHeaders" + note: "Renamed and changed from FiberRef to Context.Reference; prefer RpcClient.withHeaders for scoped overrides." +"@effect/rpc/RpcClient#layerProtocolHttp": + replacement: "effect/unstable/rpc/RpcClient#layerProtocolHttp" + note: "Retained; it provides the v4 Protocol from HttpClient and RpcSerialization." +"@effect/rpc/RpcClient#make": + replacement: "effect/unstable/rpc/RpcClient#make" + note: "Retained; generated calls can now take per-request headers and Context, and include RpcClientError in their error channel." +"@effect/rpc/RpcClient#makeProtocolHttp": + replacement: "effect/unstable/rpc/RpcClient#makeProtocolHttp" + note: "Retained; it creates the Protocol service implementation from an HttpClient." +"@effect/rpc/RpcClient#Protocol": + replacement: "effect/unstable/rpc/RpcClient#Protocol" + note: "Retained as a Context.Service; custom transports now route multiple client ids through run and send." +"@effect/rpc/RpcClient#RpcClient.NonPrefixed": + replacement: "none" + note: "The prefix-partition helper was removed; v4 clients map every RPC tag directly to an object property." +"@effect/rpc/RpcClient#RpcClient.Prefixes": + replacement: "none" + note: "Nested prefix client objects were removed; v4 preserves the full RPC tag as the generated client property." +"@effect/rpc/RpcClient#withHeadersEffect": + replacement: "Effect.flatMap(headers, (value) => RpcClient.withHeaders(effect, value))" + note: "withHeaders now accepts Headers.Input synchronously; evaluate effectful headers first and then scope the client effect." +"@effect/rpc/RpcClient#RpcClient.From": + replacement: "effect/unstable/rpc/RpcClient#RpcClient.From" + note: "Generated clients now preserve full RPC tags as property names, remove the Prefix type parameter, and expose streaming results through the asQueue option instead of asMailbox." diff --git a/migration/annotations/effect__rpc__RpcClientError.yaml b/migration/annotations/effect__rpc__RpcClientError.yaml new file mode 100644 index 000000000..db32a74ef --- /dev/null +++ b/migration/annotations/effect__rpc__RpcClientError.yaml @@ -0,0 +1,3 @@ +"@effect/rpc/RpcClientError#TypeId": + replacement: "none" + note: "The marker is private in v4; narrow with instanceof RpcClientError or inspect the public _tag." diff --git a/migration/annotations/effect__rpc__RpcGroup.yaml b/migration/annotations/effect__rpc__RpcGroup.yaml new file mode 100644 index 000000000..b26b2af22 --- /dev/null +++ b/migration/annotations/effect__rpc__RpcGroup.yaml @@ -0,0 +1,15 @@ +"@effect/rpc/RpcGroup#HandlerContext": + replacement: "effect/unstable/rpc/RpcGroup#HandlerServices" + note: "Renamed for v4 service terminology and now includes explicit RPC requirements after removing middleware-provided services." +"@effect/rpc/RpcGroup#HandlersContext": + replacement: "effect/unstable/rpc/RpcGroup#HandlersServices" + note: "Renamed; it unions HandlerServices across the handler object." +"@effect/rpc/RpcGroup#make": + replacement: "effect/unstable/rpc/RpcGroup#make" + note: "Retained for explicit Rpc definitions; passing TaggedRequest schema classes for implicit conversion is no longer supported." +"@effect/rpc/RpcGroup#TypeId": + replacement: "none" + note: "The group marker is private in v4; use RpcGroup.Any for an erased group constraint." +"@effect/rpc/RpcGroup#Any": + replacement: "effect/unstable/rpc/RpcGroup#Any" + note: "Moved unchanged as the erased RpcGroup constraint." diff --git a/migration/annotations/effect__rpc__RpcMessage.yaml b/migration/annotations/effect__rpc__RpcMessage.yaml new file mode 100644 index 000000000..6694182b5 --- /dev/null +++ b/migration/annotations/effect__rpc__RpcMessage.yaml @@ -0,0 +1,9 @@ +"@effect/rpc/RpcMessage#FromServerEncoded": + replacement: "effect/unstable/rpc/RpcMessage#FromServerEncoded" + note: "The union is retained and now also includes RequestEncoded for server-originated requests and notifications. Handle _tag: \"Request\" when matching exhaustively; isNotification identifies notifications." +"@effect/rpc/RpcMessage#RequestIdTypeId": + replacement: "effect/unstable/rpc/RpcMessage#RequestId" + note: "The public symbol marker was removed; use the branded RequestId type and RequestId constructor rather than inspecting its brand." +"@effect/rpc/RpcMessage#RequestId": + replacement: "effect/unstable/rpc/RpcMessage#RequestId" + note: "Request ids are now branded string or number values; convert bigint ids before calling the retained RequestId constructor." diff --git a/migration/annotations/effect__rpc__RpcMiddleware.yaml b/migration/annotations/effect__rpc__RpcMiddleware.yaml new file mode 100644 index 000000000..3fad622b6 --- /dev/null +++ b/migration/annotations/effect__rpc__RpcMiddleware.yaml @@ -0,0 +1,45 @@ +"@effect/rpc/RpcMiddleware#ForClient": + replacement: "effect/unstable/rpc/RpcMiddleware#ForClient" + note: "Retained as the marker requirement provided by a client middleware layer." +"@effect/rpc/RpcMiddleware#layerClient": + replacement: "effect/unstable/rpc/RpcMiddleware#layerClient" + note: "Retained; the client function can now modify the typed Request passed to next and carry a client-only error type." +"@effect/rpc/RpcMiddleware#RpcMiddlewareWrap": + replacement: "effect/unstable/rpc/RpcMiddleware#RpcMiddleware" + note: "The wrap and non-wrap shapes were unified; implement a function receiving the handler effect and request options." +"@effect/rpc/RpcMiddleware#Tag": + replacement: "effect/unstable/rpc/RpcMiddleware#Service" + note: "Renamed and redesigned with explicit requires, provides, clientError, error, and requiredForClient configuration." +"@effect/rpc/RpcMiddleware#TagClass": + replacement: "effect/unstable/rpc/RpcMiddleware#ServiceClass" + note: "Renamed class type for the v4 Context.Service-based middleware declaration." +"@effect/rpc/RpcMiddleware#TagClass.Failure": + replacement: "effect/unstable/rpc/RpcMiddleware#Error" + note: "Failure terminology became error; apply the extractor to the middleware ID." +"@effect/rpc/RpcMiddleware#TagClass.FailureContext": + replacement: "effect/unstable/rpc/RpcMiddleware#ErrorServicesEncode / ErrorServicesDecode" + note: "The single schema context split into server encoding and client decoding services." +"@effect/rpc/RpcMiddleware#TagClass.FailureSchema": + replacement: "effect/unstable/rpc/RpcMiddleware#ErrorSchema" + note: "Renamed and applied to the middleware ID rather than constructor options." +"@effect/rpc/RpcMiddleware#TagClass.FailureService": + replacement: "effect/unstable/rpc/RpcMiddleware#Error" + note: "Use the decoded error extractor; optional middleware fallback was removed." +"@effect/rpc/RpcMiddleware#TagClass.Optional": + replacement: "none" + note: "Optional declaration and fallback-on-failure behavior were removed; model fallback inside the middleware effect." +"@effect/rpc/RpcMiddleware#TagClass.Provides": + replacement: "effect/unstable/rpc/RpcMiddleware#Provides" + note: "Moved to the module level and applied to the middleware ID metadata." +"@effect/rpc/RpcMiddleware#TagClass.RequiredForClient": + replacement: "RpcMiddleware.ServiceClass[\"requiredForClient\"]" + note: "The standalone options extractor was removed; the boolean is exposed directly by the resulting service class." +"@effect/rpc/RpcMiddleware#TagClassAny": + replacement: "effect/unstable/rpc/RpcMiddleware#AnyService" + note: "Renamed widened middleware service-key shape." +"@effect/rpc/RpcMiddleware#TagClassAnyWithProps": + replacement: "effect/unstable/rpc/RpcMiddleware#AnyServiceWithProps" + note: "Renamed erased service key whose value has the unified server middleware function shape." +"@effect/rpc/RpcMiddleware#TypeId": + replacement: "effect/unstable/rpc/RpcMiddleware#TypeId" + note: "Retained as the public middleware metadata marker and now has a string-literal type." diff --git a/migration/annotations/effect__rpc__RpcSchema.yaml b/migration/annotations/effect__rpc__RpcSchema.yaml new file mode 100644 index 000000000..6164045df --- /dev/null +++ b/migration/annotations/effect__rpc__RpcSchema.yaml @@ -0,0 +1,15 @@ +"@effect/rpc/RpcSchema#getStreamSchemas": + replacement: "effect/unstable/rpc/RpcSchema#getStreamSchemas" + note: "Retained for internal-style schema inspection; pass the schema itself rather than its AST." +"@effect/rpc/RpcSchema#isStreamSchema": + replacement: "effect/unstable/rpc/RpcSchema#isStreamSchema" + note: "Retained; it accepts a v4 Schema.Constraint." +"@effect/rpc/RpcSchema#isStreamSerializable": + replacement: "RpcSchema.isStreamSchema(schema)" + note: "The separate WithResult serializability predicate was removed; v4 RPC streaming is identified by its explicit Stream schema." +"@effect/rpc/RpcSchema#Stream": + replacement: "effect/unstable/rpc/RpcSchema#Stream" + note: "Retained as both the stream schema interface and constructor; error is the second argument and schema services are split by direction." +"@effect/rpc/RpcSchema#StreamSchemaId": + replacement: "none" + note: "The stream marker is private in v4; use RpcSchema.isStreamSchema and getStreamSchemas." diff --git a/migration/annotations/effect__rpc__RpcSerialization.yaml b/migration/annotations/effect__rpc__RpcSerialization.yaml new file mode 100644 index 000000000..fae2c1f87 --- /dev/null +++ b/migration/annotations/effect__rpc__RpcSerialization.yaml @@ -0,0 +1,15 @@ +"@effect/rpc/RpcSerialization#RpcSerializationError": + replacement: "effect/unstable/rpc/RpcSerialization#MaxBufferSizeExceeded" + note: "Buffer-limit failures now use MaxBufferSizeExceeded. MessagePack-specific decode errors have no counterpart." +"@effect/rpc/RpcSerialization#layerMsgPack": + replacement: "effect/unstable/rpc/RpcSerialization#layerSchemaBinary" + note: "MessagePack RPC serialization was removed. Use SchemaBinary, or layerNdjson when you need newline-delimited JSON framing." +"@effect/rpc/RpcSerialization#layerMsgPackWith": + replacement: "effect/unstable/rpc/RpcSerialization#layerSchemaBinary" + note: "MessagePack RPC serialization was removed. Pass maxFrameSize to layerSchemaBinary; NDJSON buffer limits remain on layerNdjsonWith." +"@effect/rpc/RpcSerialization#makeMsgPack": + replacement: "effect/unstable/rpc/RpcSerialization#layerSchemaBinary" + note: "MessagePack RPC serialization was removed. Construct SchemaBinary serialization with layerSchemaBinary." +"@effect/rpc/RpcSerialization#msgPack": + replacement: "effect/unstable/rpc/RpcSerialization#layerSchemaBinary" + note: "The MessagePack RpcSerialization service value was removed. Provide layerSchemaBinary instead." diff --git a/migration/annotations/effect__rpc__RpcServer.yaml b/migration/annotations/effect__rpc__RpcServer.yaml new file mode 100644 index 000000000..fdba78685 --- /dev/null +++ b/migration/annotations/effect__rpc__RpcServer.yaml @@ -0,0 +1,51 @@ +"@effect/rpc/RpcServer#fiberIdClientInterrupt": + replacement: "effect/unstable/rpc/RpcSchema#ClientAbort" + note: "The sentinel FiberId was replaced by a Cause annotation; inspect ClientAbort in the interruption cause when client cancellation must be distinguished." +"@effect/rpc/RpcServer#fiberIdTransientInterrupt": + replacement: "none" + note: "The internal transient sentinel was removed; protocol shutdown and disconnect now interrupt with the active parent fiber identity." +"@effect/rpc/RpcServer#layerHttpRouter": + replacement: "effect/unstable/rpc/RpcServer#layerHttp" + note: "Renamed; it installs an HTTP or WebSocket RPC route into the v4 HttpRouter service." +"@effect/rpc/RpcServer#layerProtocolHttp": + replacement: "effect/unstable/rpc/RpcServer#layerProtocolHttp" + note: "Retained; v4 has one HttpRouter service and no router tag option." +"@effect/rpc/RpcServer#layerProtocolHttpRouter": + replacement: "effect/unstable/rpc/RpcServer#layerProtocolHttp" + note: "The separate layer-router variant was unified with layerProtocolHttp." +"@effect/rpc/RpcServer#layerProtocolWebsocketRouter": + replacement: "effect/unstable/rpc/RpcServer#layerProtocolWebsocket" + note: "Renamed after the HTTP router services were unified." +"@effect/rpc/RpcServer#make": + replacement: "effect/unstable/rpc/RpcServer#make" + note: "Retained; schema encoding services are now explicit server requirements." +"@effect/rpc/RpcServer#makeProtocolHttp": + replacement: "effect/unstable/rpc/RpcServer#makeProtocolHttp" + note: "Retained; it registers a POST route in the current v4 HttpRouter." +"@effect/rpc/RpcServer#makeProtocolHttpRouter": + replacement: "effect/unstable/rpc/RpcServer#makeProtocolHttp" + note: "The separate router constructor was unified with makeProtocolHttp." +"@effect/rpc/RpcServer#makeProtocolWebsocketRouter": + replacement: "effect/unstable/rpc/RpcServer#makeProtocolWebsocket" + note: "Renamed after the HTTP router services were unified." +"@effect/rpc/RpcServer#makeProtocolWithHttpApp": + replacement: "effect/unstable/rpc/RpcServer#makeProtocolWithHttpEffect" + note: "HttpApp became HttpEffect; the result contains protocol and httpEffect." +"@effect/rpc/RpcServer#makeProtocolWithHttpAppWebsocket": + replacement: "effect/unstable/rpc/RpcServer#makeProtocolWithHttpEffectWebsocket" + note: "HttpApp became HttpEffect; the result contains the WebSocket protocol and upgrade effect." +"@effect/rpc/RpcServer#Protocol": + replacement: "effect/unstable/rpc/RpcServer#Protocol" + note: "Retained as a Context.Service; custom transports now expose a disconnect queue, explicit capability flags, and codecFor for schema-aware payload and exit encoding." +"@effect/rpc/RpcServer#toHttpApp": + replacement: "effect/unstable/rpc/RpcServer#toHttpEffect" + note: "Renamed for the v4 HTTP effect model; it starts the RPC server and returns the request effect." +"@effect/rpc/RpcServer#toHttpAppWebsocket": + replacement: "effect/unstable/rpc/RpcServer#toHttpEffectWebsocket" + note: "Renamed for the v4 HTTP effect model; it returns the WebSocket upgrade effect." +"@effect/rpc/RpcServer#toWebHandler": + replacement: "HttpRouter.toWebHandler(RpcServer.layerHttp(options).pipe(Layer.provide(options.layer)))" + note: "The RPC convenience wrapper was removed; build the RPC route layer and convert it with the generic v4 HttpRouter web-handler adapter." +"@effect/rpc/RpcServer#layer": + replacement: "effect/unstable/rpc/RpcServer#layer" + note: "Moved to core Effect; server requirements are now derived with Rpc.ServicesServer rather than the former combined Rpc.Context alias." diff --git a/migration/annotations/effect__rpc__RpcTest.yaml b/migration/annotations/effect__rpc__RpcTest.yaml new file mode 100644 index 000000000..caa66472b --- /dev/null +++ b/migration/annotations/effect__rpc__RpcTest.yaml @@ -0,0 +1,3 @@ +"@effect/rpc/RpcTest#makeClient": + replacement: "effect/unstable/rpc/RpcTest#makeClient" + note: "Retained; it uses the v4 no-serialization client/server path and requires handlers plus any server and client middleware services." diff --git a/migration/annotations/effect__rpc__index.yaml b/migration/annotations/effect__rpc__index.yaml new file mode 100644 index 000000000..3387ce048 --- /dev/null +++ b/migration/annotations/effect__rpc__index.yaml @@ -0,0 +1,3 @@ +"@effect/rpc/index": + replacement: "effect/unstable/rpc" + note: "The package barrel was removed; import the same namespaces from the effect/unstable/rpc barrel or import specific modules directly." diff --git a/migration/annotations/effect__sql-clickhouse__ClickhouseClient.yaml b/migration/annotations/effect__sql-clickhouse__ClickhouseClient.yaml new file mode 100644 index 000000000..2453212dd --- /dev/null +++ b/migration/annotations/effect__sql-clickhouse__ClickhouseClient.yaml @@ -0,0 +1,12 @@ +"@effect/sql-clickhouse/ClickhouseClient#ClickhouseClient": + replacement: "@effect/sql-clickhouse/ClickhouseClient#ClickhouseClient" + note: "Retained; the service value is now a Context.Service rather than a GenericTag." +"@effect/sql-clickhouse/ClickhouseClient#currentClickhouseSettings": + replacement: "@effect/sql-clickhouse/ClickhouseClient#ClickhouseSettings" + note: "Renamed and changed from FiberRef to Context.Reference; prefer client.withClickhouseSettings or provide the reference as a service." +"@effect/sql-clickhouse/ClickhouseClient#currentClientMethod": + replacement: "@effect/sql-clickhouse/ClickhouseClient#ClientMethod" + note: "Renamed and changed from FiberRef to Context.Reference; prefer client.asCommand or provide the reference as a service." +"@effect/sql-clickhouse/ClickhouseClient#currentQueryId": + replacement: "@effect/sql-clickhouse/ClickhouseClient#QueryId" + note: "Renamed and changed from FiberRef to Context.Reference; prefer client.withQueryId or provide the reference as a service." diff --git a/migration/annotations/effect__sql-clickhouse__ClickhouseMigrator.yaml b/migration/annotations/effect__sql-clickhouse__ClickhouseMigrator.yaml new file mode 100644 index 000000000..c773d443f --- /dev/null +++ b/migration/annotations/effect__sql-clickhouse__ClickhouseMigrator.yaml @@ -0,0 +1,3 @@ +"@effect/sql-clickhouse/ClickhouseMigrator#MigrationError": + replacement: "@effect/sql-clickhouse/ClickhouseMigrator#MigrationError" + note: "Retained via effect/unstable/sql/Migrator; migrate reason and its lowercase values to kind with PascalCase values." diff --git a/migration/annotations/effect__sql-clickhouse__index.yaml b/migration/annotations/effect__sql-clickhouse__index.yaml new file mode 100644 index 000000000..61fd8dd3f --- /dev/null +++ b/migration/annotations/effect__sql-clickhouse__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-clickhouse/index": + replacement: "@effect/sql-clickhouse" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-clickhouse package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-d1__D1Client.yaml b/migration/annotations/effect__sql-d1__D1Client.yaml new file mode 100644 index 000000000..a73f16358 --- /dev/null +++ b/migration/annotations/effect__sql-d1__D1Client.yaml @@ -0,0 +1,3 @@ +"@effect/sql-d1/D1Client#D1ClientConfig": + replacement: "@effect/sql-d1/D1Client#D1ClientConfig" + note: "Retained; prepareCacheTTL now uses Duration.Input." diff --git a/migration/annotations/effect__sql-d1__index.yaml b/migration/annotations/effect__sql-d1__index.yaml new file mode 100644 index 000000000..a0299ad51 --- /dev/null +++ b/migration/annotations/effect__sql-d1__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-d1/index": + replacement: "@effect/sql-d1" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-d1 package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-drizzle__Mysql.yaml b/migration/annotations/effect__sql-drizzle__Mysql.yaml new file mode 100644 index 000000000..963da1e61 --- /dev/null +++ b/migration/annotations/effect__sql-drizzle__Mysql.yaml @@ -0,0 +1,15 @@ +"@effect/sql-drizzle/Mysql#layer": + replacement: "Layer.effect(AppDb, MysqlDrizzle.makeWithDefaults())" + note: "The package was removed; import MysqlDrizzle from drizzle-orm/effect-mysql2, define an application service tag, and compose with MysqlClient.layer." +"@effect/sql-drizzle/Mysql#layerWithConfig": + replacement: "Layer.effect(AppDb, MysqlDrizzle.makeWithDefaults(config))" + note: "The package was removed; use drizzle-orm/effect-mysql2, port config to EffectDrizzleMySqlConfig, and define an application service tag." +"@effect/sql-drizzle/Mysql#make": + replacement: "drizzle-orm/effect-mysql2#makeWithDefaults" + note: "Use Drizzle's Effect 4 integration; it returns EffectMysql2Database and requires MysqlClient." +"@effect/sql-drizzle/Mysql#makeWithConfig": + replacement: "drizzle-orm/effect-mysql2#makeWithDefaults" + note: "The constructor split was removed; use makeWithDefaults(config), or make(config) when explicitly providing logger and cache services." +"@effect/sql-drizzle/Mysql#MysqlDrizzle": + replacement: "drizzle-orm/effect-mysql2#EffectMysql2Database" + note: "The service tag was removed; use the database type and define an application Context.Tag if service access is required." diff --git a/migration/annotations/effect__sql-drizzle__Pg.yaml b/migration/annotations/effect__sql-drizzle__Pg.yaml new file mode 100644 index 000000000..a435b5544 --- /dev/null +++ b/migration/annotations/effect__sql-drizzle__Pg.yaml @@ -0,0 +1,15 @@ +"@effect/sql-drizzle/Pg#layer": + replacement: "Layer.effect(AppDb, PgDrizzle.makeWithDefaults())" + note: "The package was removed; import PgDrizzle from drizzle-orm/effect-postgres, define an application service tag, and compose with PgClient.layer." +"@effect/sql-drizzle/Pg#layerWithConfig": + replacement: "Layer.effect(AppDb, PgDrizzle.makeWithDefaults(config))" + note: "The package was removed; use drizzle-orm/effect-postgres, port config to EffectDrizzlePgConfig, and define an application service tag." +"@effect/sql-drizzle/Pg#make": + replacement: "drizzle-orm/effect-postgres#makeWithDefaults" + note: "Use Drizzle's Effect 4 integration; it returns EffectPgDatabase and requires PgClient." +"@effect/sql-drizzle/Pg#makeWithConfig": + replacement: "drizzle-orm/effect-postgres#makeWithDefaults" + note: "The constructor split was removed; use makeWithDefaults(config), or make(config) when explicitly providing logger and cache services." +"@effect/sql-drizzle/Pg#PgDrizzle": + replacement: "drizzle-orm/effect-postgres#EffectPgDatabase" + note: "The service tag was removed; use the database type and define an application Context.Tag if service access is required." diff --git a/migration/annotations/effect__sql-drizzle__Sqlite.yaml b/migration/annotations/effect__sql-drizzle__Sqlite.yaml new file mode 100644 index 000000000..7a1f5fc35 --- /dev/null +++ b/migration/annotations/effect__sql-drizzle__Sqlite.yaml @@ -0,0 +1,15 @@ +"@effect/sql-drizzle/Sqlite#layer": + replacement: "Layer.effect(AppDb, SqliteDrizzle.makeWithDefaults())" + note: "The package was removed; select the matching drizzle-orm Effect backend module, define an application service tag, and compose with its SQL client layer." +"@effect/sql-drizzle/Sqlite#layerWithConfig": + replacement: "Layer.effect(AppDb, SqliteDrizzle.makeWithDefaults(config))" + note: "Select the matching drizzle-orm Effect backend, port config to EffectDrizzleSQLiteConfig, and define an application service tag." +"@effect/sql-drizzle/Sqlite#make": + replacement: "drizzle-orm/effect-sqlite-node#makeWithDefaults" + note: "SQLite integration is backend-specific; use the module matching sql-sqlite-node, -bun, -do, -wasm, libsql, or d1." +"@effect/sql-drizzle/Sqlite#makeWithConfig": + replacement: "matching drizzle-orm Effect SQLite module#makeWithDefaults" + note: "The generic constructor was removed; select the concrete backend and use makeWithDefaults(config), or make(config) with explicit services." +"@effect/sql-drizzle/Sqlite#SqliteDrizzle": + replacement: "matching drizzle-orm Effect SQLite database type" + note: "The generic service tag was removed; use the backend-specific database type and define an application Context.Tag if needed." diff --git a/migration/annotations/effect__sql-kysely__Kysely.yaml b/migration/annotations/effect__sql-kysely__Kysely.yaml new file mode 100644 index 000000000..6846ace99 --- /dev/null +++ b/migration/annotations/effect__sql-kysely__Kysely.yaml @@ -0,0 +1,3 @@ +"@effect/sql-kysely/Kysely": + replacement: none + note: The Effect integration was removed. Use native kysely#Kysely, explicitly wrap promise execution with Effect.tryPromise, and define an application service if needed. No Effect-native equivalent remains; construct native new Kysely(config) and explicitly wrap builder execution and errors with Effect.tryPromise. diff --git a/migration/annotations/effect__sql-kysely__Mssql.yaml b/migration/annotations/effect__sql-kysely__Mssql.yaml new file mode 100644 index 000000000..1c8b0f2e4 --- /dev/null +++ b/migration/annotations/effect__sql-kysely__Mssql.yaml @@ -0,0 +1,3 @@ +"@effect/sql-kysely/Mssql": + replacement: none + note: The integration was removed; use native Kysely with MssqlDialect and wrap promises, or rewrite against @effect/sql-mssql for Effect-native queries. diff --git a/migration/annotations/effect__sql-kysely__Mysql.yaml b/migration/annotations/effect__sql-kysely__Mysql.yaml new file mode 100644 index 000000000..40b3de2d2 --- /dev/null +++ b/migration/annotations/effect__sql-kysely__Mysql.yaml @@ -0,0 +1,3 @@ +"@effect/sql-kysely/Mysql": + replacement: none + note: The integration was removed; use native Kysely with MysqlDialect and wrap promises, or rewrite against @effect/sql-mysql2 for Effect-native queries. diff --git a/migration/annotations/effect__sql-kysely__Pg.yaml b/migration/annotations/effect__sql-kysely__Pg.yaml new file mode 100644 index 000000000..b0caeee56 --- /dev/null +++ b/migration/annotations/effect__sql-kysely__Pg.yaml @@ -0,0 +1,3 @@ +"@effect/sql-kysely/Pg": + replacement: none + note: The integration was removed; use native Kysely with PostgresDialect and wrap promises, or rewrite against @effect/sql-pg for Effect-native queries. diff --git a/migration/annotations/effect__sql-kysely__Sqlite.yaml b/migration/annotations/effect__sql-kysely__Sqlite.yaml new file mode 100644 index 000000000..868b4e6d7 --- /dev/null +++ b/migration/annotations/effect__sql-kysely__Sqlite.yaml @@ -0,0 +1,3 @@ +"@effect/sql-kysely/Sqlite": + replacement: none + note: The integration was removed; use native Kysely with SqliteDialect and wrap promises, or rewrite against a matching @effect/sql-sqlite-* client. diff --git a/migration/annotations/effect__sql-kysely__patch.types.yaml b/migration/annotations/effect__sql-kysely__patch.types.yaml new file mode 100644 index 000000000..7d18812ba --- /dev/null +++ b/migration/annotations/effect__sql-kysely__patch.types.yaml @@ -0,0 +1,3 @@ +"@effect/sql-kysely/patch.types": + replacement: "none" + note: "The @effect/sql-kysely package was removed in v4 along with its kysely type patches; depend on native kysely types directly and wrap query execution with Effect.tryPromise." diff --git a/migration/annotations/effect__sql-libsql__index.yaml b/migration/annotations/effect__sql-libsql__index.yaml new file mode 100644 index 000000000..e2efef269 --- /dev/null +++ b/migration/annotations/effect__sql-libsql__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-libsql/index": + replacement: "@effect/sql-libsql" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-libsql package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-mssql__MssqlClient.yaml b/migration/annotations/effect__sql-mssql__MssqlClient.yaml new file mode 100644 index 000000000..31b4626f0 --- /dev/null +++ b/migration/annotations/effect__sql-mssql__MssqlClient.yaml @@ -0,0 +1,6 @@ +"@effect/sql-mssql/MssqlClient#MssqlClient": + replacement: "@effect/sql-mssql/MssqlClient#MssqlClient" + note: "Retained; the service value is now a Context.Service rather than a GenericTag." +"@effect/sql-mssql/MssqlClient#MssqlClientConfig": + replacement: "@effect/sql-mssql/MssqlClient#MssqlClientConfig" + note: "Retained; durations use Duration.Input, parameterTypes is keyed by Statement.PrimitiveKind, and v4 adds retry and timeout options." diff --git a/migration/annotations/effect__sql-mssql__Parameter.yaml b/migration/annotations/effect__sql-mssql__Parameter.yaml new file mode 100644 index 000000000..12e910afc --- /dev/null +++ b/migration/annotations/effect__sql-mssql__Parameter.yaml @@ -0,0 +1,6 @@ +"@effect/sql-mssql/Parameter#Parameter": + replacement: "@effect/sql-mssql/Parameter#Parameter" + note: "Retained; the phantom brand key was renamed from ParameterId to TypeId." +"@effect/sql-mssql/Parameter#ParameterId": + replacement: "@effect/sql-mssql/Parameter#TypeId" + note: "Renamed; use TypeId for direct brand-key and type references." diff --git a/migration/annotations/effect__sql-mssql__Procedure.yaml b/migration/annotations/effect__sql-mssql__Procedure.yaml new file mode 100644 index 000000000..611b275ee --- /dev/null +++ b/migration/annotations/effect__sql-mssql__Procedure.yaml @@ -0,0 +1,6 @@ +"@effect/sql-mssql/Procedure#Procedure": + replacement: "@effect/sql-mssql/Procedure#Procedure" + note: "Retained with the same generics and fields." +"@effect/sql-mssql/Procedure#Procedure.ParametersRecord": + replacement: "@effect/sql-mssql/Procedure#Procedure.ParametersRecord" + note: "Retained unchanged; from the deep module it is also available as Procedure.ParametersRecord." diff --git a/migration/annotations/effect__sql-mssql__index.yaml b/migration/annotations/effect__sql-mssql__index.yaml new file mode 100644 index 000000000..56292676b --- /dev/null +++ b/migration/annotations/effect__sql-mssql__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-mssql/index": + replacement: "@effect/sql-mssql" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-mssql package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-mysql2__MysqlClient.yaml b/migration/annotations/effect__sql-mysql2__MysqlClient.yaml new file mode 100644 index 000000000..817cc7ed0 --- /dev/null +++ b/migration/annotations/effect__sql-mysql2__MysqlClient.yaml @@ -0,0 +1,3 @@ +"@effect/sql-mysql2/MysqlClient#MysqlClientConfig": + replacement: "@effect/sql-mysql2/MysqlClient#MysqlClientConfig" + note: "Retained; connectionTTL uses Duration.Input and v4 adds disablePreparedStatements." diff --git a/migration/annotations/effect__sql-mysql2__index.yaml b/migration/annotations/effect__sql-mysql2__index.yaml new file mode 100644 index 000000000..81c416a1d --- /dev/null +++ b/migration/annotations/effect__sql-mysql2__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-mysql2/index": + replacement: "@effect/sql-mysql2" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-mysql2 package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-pg__PgClient.yaml b/migration/annotations/effect__sql-pg__PgClient.yaml new file mode 100644 index 000000000..d9ae6977b --- /dev/null +++ b/migration/annotations/effect__sql-pg__PgClient.yaml @@ -0,0 +1,15 @@ +"@effect/sql-pg/PgClient#fromPool": + replacement: none + note: "Wrapping an existing node-pg Pool was removed with the native protocol client. Use PgClient.make or PgClient.layer with connection settings." +"@effect/sql-pg/PgClient#layerFromPool": + replacement: "PgClient.layer" + note: "Wrapping an existing node-pg Pool was removed. Provide connection settings to PgClient.layer instead." +"@effect/sql-pg/PgClient#PgClient": + replacement: "@effect/sql-pg/PgClient#PgClient" + note: "Retained; the service value is now a Context.Service." +"@effect/sql-pg/PgClient#PgClientConfig": + replacement: "@effect/sql-pg/PgClient#PgClientConfig / PgPoolConfig" + note: "Use PgClientConfig for base settings and PgPoolConfig for make/layer; pool sizing, idle timeout, and connection TTL moved to PgPoolConfig." +"@effect/sql-pg/PgClient#PgClientFromPoolOptions": + replacement: none + note: "The node-pg Pool wrapper options were removed with fromPool. Use PgClient.PgPoolConfig with PgClient.make or PgClient.layer." diff --git a/migration/annotations/effect__sql-pg__index.yaml b/migration/annotations/effect__sql-pg__index.yaml new file mode 100644 index 000000000..ad7fcc5a6 --- /dev/null +++ b/migration/annotations/effect__sql-pg__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-pg/index": + replacement: "@effect/sql-pg" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-pg package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-sqlite-bun__SqliteClient.yaml b/migration/annotations/effect__sql-sqlite-bun__SqliteClient.yaml new file mode 100644 index 000000000..d936bcb8c --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-bun__SqliteClient.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-bun/SqliteClient#SqliteClient": + replacement: "@effect/sql-sqlite-bun/SqliteClient#SqliteClient" + note: "Retained; the service value is now a Context.Service." diff --git a/migration/annotations/effect__sql-sqlite-bun__index.yaml b/migration/annotations/effect__sql-sqlite-bun__index.yaml new file mode 100644 index 000000000..2104c4ec6 --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-bun__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-bun/index": + replacement: "@effect/sql-sqlite-bun" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-bun package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-sqlite-do__SqliteClient.yaml b/migration/annotations/effect__sql-sqlite-do__SqliteClient.yaml new file mode 100644 index 000000000..aabf0cd66 --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-do__SqliteClient.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-do/SqliteClient#SqliteClientConfig": + replacement: "@effect/sql-sqlite-do/SqliteClient#SqliteClientConfig" + note: "Retained; db is optional and storage may be supplied, but one of db or storage is required at runtime." diff --git a/migration/annotations/effect__sql-sqlite-do__index.yaml b/migration/annotations/effect__sql-sqlite-do__index.yaml new file mode 100644 index 000000000..1ce99cfef --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-do__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-do/index": + replacement: "@effect/sql-sqlite-do" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-do package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-sqlite-node__SqliteClient.yaml b/migration/annotations/effect__sql-sqlite-node__SqliteClient.yaml new file mode 100644 index 000000000..5bbf9cfbe --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-node__SqliteClient.yaml @@ -0,0 +1,6 @@ +"@effect/sql-sqlite-node/SqliteClient#SqliteClient": + replacement: "@effect/sql-sqlite-node/SqliteClient#SqliteClient" + note: "Retained on node:sqlite, but the byte-export member was removed; use backup(destination) for file backup." +"@effect/sql-sqlite-node/SqliteClient#SqliteClientConfig": + replacement: "@effect/sql-sqlite-node/SqliteClient#SqliteClientConfig" + note: "Retained; prepareCacheTTL now uses Duration.Input." diff --git a/migration/annotations/effect__sql-sqlite-node__index.yaml b/migration/annotations/effect__sql-sqlite-node__index.yaml new file mode 100644 index 000000000..415dbb0d0 --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-node__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-node/index": + replacement: "@effect/sql-sqlite-node" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-node package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-sqlite-react-native__SqliteClient.yaml b/migration/annotations/effect__sql-sqlite-react-native__SqliteClient.yaml new file mode 100644 index 000000000..d46613df6 --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-react-native__SqliteClient.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-react-native/SqliteClient#asyncQuery": + replacement: "@effect/sql-sqlite-react-native/SqliteClient#AsyncQuery" + note: "Renamed and changed from FiberRef to Context.Reference; prefer withAsyncQuery or provide AsyncQuery as a service." diff --git a/migration/annotations/effect__sql-sqlite-react-native__index.yaml b/migration/annotations/effect__sql-sqlite-react-native__index.yaml new file mode 100644 index 000000000..45a5fa68c --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-react-native__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-react-native/index": + replacement: "@effect/sql-sqlite-react-native" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-react-native package root or import specific modules directly." diff --git a/migration/annotations/effect__sql-sqlite-wasm__SqliteClient.yaml b/migration/annotations/effect__sql-sqlite-wasm__SqliteClient.yaml new file mode 100644 index 000000000..64cf9a2ff --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-wasm__SqliteClient.yaml @@ -0,0 +1,6 @@ +"@effect/sql-sqlite-wasm/SqliteClient#currentTransferables": + replacement: "@effect/sql-sqlite-wasm/SqliteClient#Transferables" + note: "Renamed and changed from FiberRef to Context.Reference; prefer withTransferables or provide Transferables as a service." +"@effect/sql-sqlite-wasm/SqliteClient#SqliteClient": + replacement: "@effect/sql-sqlite-wasm/SqliteClient#SqliteClient" + note: "Retained with the same export/import surface; the service value is now a Context.Service." diff --git a/migration/annotations/effect__sql-sqlite-wasm__index.yaml b/migration/annotations/effect__sql-sqlite-wasm__index.yaml new file mode 100644 index 000000000..a649b2af9 --- /dev/null +++ b/migration/annotations/effect__sql-sqlite-wasm__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql-sqlite-wasm/index": + replacement: "@effect/sql-sqlite-wasm" + note: "The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-wasm package root or import specific modules directly." diff --git a/migration/annotations/effect__sql.yaml b/migration/annotations/effect__sql.yaml new file mode 100644 index 000000000..613890f1d --- /dev/null +++ b/migration/annotations/effect__sql.yaml @@ -0,0 +1,3 @@ +"@effect/sql": + replacement: "effect/unstable/sql" + note: "The @effect/sql package was merged into the effect package; import the effect/unstable/sql barrel or import specific modules directly (e.g. effect/unstable/sql/)." diff --git a/migration/annotations/effect__sql__Migrator.yaml b/migration/annotations/effect__sql__Migrator.yaml new file mode 100644 index 000000000..3ca711ee6 --- /dev/null +++ b/migration/annotations/effect__sql__Migrator.yaml @@ -0,0 +1,3 @@ +"@effect/sql/Migrator#make": + replacement: "effect/unstable/sql/Migrator#make" + note: "Moved with the same curried make({ dumpSchema })(options) pattern." diff --git a/migration/annotations/effect__sql__Migrator__FileSystem.yaml b/migration/annotations/effect__sql__Migrator__FileSystem.yaml new file mode 100644 index 000000000..10a624a97 --- /dev/null +++ b/migration/annotations/effect__sql__Migrator__FileSystem.yaml @@ -0,0 +1,3 @@ +"@effect/sql/Migrator/FileSystem": + replacement: "effect/unstable/sql/Migrator" + note: "fromFileSystem was merged into the main Migrator module with the same (directory) signature; use Migrator.fromFileSystem as the loader." diff --git a/migration/annotations/effect__sql__Model.yaml b/migration/annotations/effect__sql__Model.yaml new file mode 100644 index 000000000..6c82eda85 --- /dev/null +++ b/migration/annotations/effect__sql__Model.yaml @@ -0,0 +1,36 @@ +"@effect/sql/Model#Any": + replacement: "effect/unstable/schema/Model#Any" + note: "Moved; v4 schemas track DecodingServices and EncodingServices separately instead of one Context type." +"@effect/sql/Model#AnyNoContext": + replacement: "effect/unstable/schema/Model#Any" + note: "The distinct no-context alias was removed; Model.Any propagates decoding and encoding services. Constrain both service types to never when required." +"@effect/sql/Model#BooleanFromNumber": + replacement: "effect/Schema#BooleanFromBit" + note: "Use the core 0 | 1 to boolean schema; Model.BooleanSqlite is the ready-made model field." +"@effect/sql/Model#Class": + replacement: "effect/unstable/schema/Model#Class" + note: "Moved; model variants remain select, insert, update, json, jsonCreate, and jsonUpdate." +"@effect/sql/Model#Date": + replacement: "effect/unstable/schema/Model#Date" + note: "Moved; still serializes DateTime.Utc as a YYYY-MM-DD string." +"@effect/sql/Model#DateTimeFromDate": + replacement: "effect/Schema#DateTimeUtcFromDate" + note: "Moved to core Schema and retains Date to DateTime.Utc conversion." +"@effect/sql/Model#extract": + replacement: "effect/unstable/schema/Model#extract" + note: "Retained after moving the model variant helpers into core Effect's unstable schema package." +"@effect/sql/Model#fieldFromKey": + replacement: "effect/Schema#encodeKeys" + note: "The field helper was removed; apply encodeKeys to each concrete struct or model-variant schema that crosses the naming boundary." +"@effect/sql/Model#Generated": + replacement: "effect/unstable/schema/Model#GeneratedByDb" + note: "Renamed and now read-only, with select and json variants only. Use Model.Field with select, update, and json to preserve writable v3 behavior." +"@effect/sql/Model#makeDataLoaders": + replacement: "effect/unstable/sql/SqlModel#makeResolvers" + note: "Returns RequestResolvers instead of callable loaders; execute with SqlResolver.request and use RequestResolver delay/batch combinators for batching controls. The insert resolver requires model decoding services as well as insert-schema encoding services." +"@effect/sql/Model#Override": + replacement: "effect/unstable/schema/Model#Override" + note: "Moved with the same explicit-default override purpose." +"@effect/sql/Model#fields": + replacement: "effect/unstable/schema/Model#fields" + note: "Moved with the variant-model helpers into core Effect's unstable schema package." diff --git a/migration/annotations/effect__sql__SqlClient.yaml b/migration/annotations/effect__sql__SqlClient.yaml new file mode 100644 index 000000000..e97901a03 --- /dev/null +++ b/migration/annotations/effect__sql__SqlClient.yaml @@ -0,0 +1,12 @@ +"@effect/sql/SqlClient#make": + replacement: "effect/unstable/sql/SqlClient#make" + note: "Moved; custom clients rename MakeOptions.reactiveMailbox to reactiveQueue and may supply transactionService." +"@effect/sql/SqlClient#SafeIntegers": + replacement: "effect/unstable/sql/SqlClient#SafeIntegers" + note: "Moved and changed from a Reference subclass to a Context.Reference value; provide it as a service." +"@effect/sql/SqlClient#TransactionConnection": + replacement: "effect/unstable/sql/SqlClient#TransactionConnection" + note: "Now a factory keyed by client id, not a singleton tag. Prefer the client's transactionService; the payload type is TransactionConnection.Service." +"@effect/sql/SqlClient#TypeId": + replacement: "none" + note: "The brand is private in v4; do not inspect or attach it, and obtain clients through SqlClient or SqlClient.make." diff --git a/migration/annotations/effect__sql__SqlConnection.yaml b/migration/annotations/effect__sql__SqlConnection.yaml new file mode 100644 index 000000000..56a7889ac --- /dev/null +++ b/migration/annotations/effect__sql__SqlConnection.yaml @@ -0,0 +1,3 @@ +"@effect/sql/SqlConnection#Connection": + replacement: "effect/unstable/sql/SqlConnection#Connection" + note: "Moved; Connection.Acquirer is now top-level SqlConnection.Acquirer, and custom connections must implement executeValuesUnprepared." diff --git a/migration/annotations/effect__sql__SqlError.yaml b/migration/annotations/effect__sql__SqlError.yaml new file mode 100644 index 000000000..50b119fac --- /dev/null +++ b/migration/annotations/effect__sql__SqlError.yaml @@ -0,0 +1,3 @@ +"@effect/sql/SqlError#SqlErrorTypeId": + replacement: "effect/unstable/sql/SqlError#isSqlError" + note: "The type id is private; use isSqlError for runtime narrowing or isSqlErrorReason for structured reason values." diff --git a/migration/annotations/effect__sql__SqlEventJournal.yaml b/migration/annotations/effect__sql__SqlEventJournal.yaml new file mode 100644 index 000000000..60b3b2d96 --- /dev/null +++ b/migration/annotations/effect__sql__SqlEventJournal.yaml @@ -0,0 +1,6 @@ +"@effect/sql/SqlEventJournal#layer": + replacement: "effect/unstable/eventlog/SqlEventJournal#layer" + note: "Moved; rename the eventLogTable layer option to entryTable." +"@effect/sql/SqlEventJournal#make": + replacement: "effect/unstable/eventlog/SqlEventJournal#make" + note: "Moved with the same entryTable and remotesTable options." diff --git a/migration/annotations/effect__sql__SqlEventLogServer.yaml b/migration/annotations/effect__sql__SqlEventLogServer.yaml new file mode 100644 index 000000000..53e298176 --- /dev/null +++ b/migration/annotations/effect__sql__SqlEventLogServer.yaml @@ -0,0 +1,6 @@ +"@effect/sql/SqlEventLogServer#layerStorage": + replacement: "effect/unstable/eventlog/SqlEventLogServerEncrypted#layerStorage" + note: "Moved to the encrypted server module with the same options and EventLogEncryption requirement." +"@effect/sql/SqlEventLogServer#makeStorage": + replacement: "effect/unstable/eventlog/SqlEventLogServerEncrypted#makeStorage" + note: "Moved to the encrypted server module with the same SQL, encryption, and scope requirements." diff --git a/migration/annotations/effect__sql__SqlPersistedQueue.yaml b/migration/annotations/effect__sql__SqlPersistedQueue.yaml new file mode 100644 index 000000000..fadb08336 --- /dev/null +++ b/migration/annotations/effect__sql__SqlPersistedQueue.yaml @@ -0,0 +1,6 @@ +"@effect/sql/SqlPersistedQueue#layerStore": + replacement: "effect/unstable/persistence/PersistedQueue#layerStoreSql" + note: "Moved into PersistedQueue and renamed with the Sql suffix; options are unchanged." +"@effect/sql/SqlPersistedQueue#make": + replacement: "effect/unstable/persistence/PersistedQueue#makeStoreSql" + note: "Use the SQL store constructor; PersistedQueue.make creates a typed queue from a store factory and is not equivalent." diff --git a/migration/annotations/effect__sql__SqlResolver.yaml b/migration/annotations/effect__sql__SqlResolver.yaml new file mode 100644 index 000000000..a87b23afd --- /dev/null +++ b/migration/annotations/effect__sql__SqlResolver.yaml @@ -0,0 +1,6 @@ +"@effect/sql/SqlResolver#SqlResolver": + replacement: "RequestResolver.RequestResolver>" + note: "The wrapper interface was removed; constructors return RequestResolvers. Execute them with effect/unstable/sql/SqlResolver#request." +"@effect/sql/SqlResolver#void": + replacement: "effect/unstable/sql/SqlResolver#void" + note: "Moved, but remove the leading tag and withContext arguments; it now returns a RequestResolver synchronously and runs through SqlResolver.request." diff --git a/migration/annotations/effect__sql__SqlSchema.yaml b/migration/annotations/effect__sql__SqlSchema.yaml new file mode 100644 index 000000000..4159f622b --- /dev/null +++ b/migration/annotations/effect__sql__SqlSchema.yaml @@ -0,0 +1,6 @@ +"@effect/sql/SqlSchema#single": + replacement: "effect/unstable/sql/SqlSchema#findOne" + note: "Renamed with the same first-row-or-fail behavior; empty results use Cause.NoSuchElementError and schema failures use Schema.SchemaError." +"@effect/sql/SqlSchema#void": + replacement: "effect/unstable/sql/SqlSchema#void" + note: "Moved with the same encode, execute, and discard-result pattern; schema failures now use Schema.SchemaError." diff --git a/migration/annotations/effect__sql__Statement.yaml b/migration/annotations/effect__sql__Statement.yaml new file mode 100644 index 000000000..353d499c6 --- /dev/null +++ b/migration/annotations/effect__sql__Statement.yaml @@ -0,0 +1,39 @@ +"@effect/sql/Statement#currentTransformer": + replacement: "effect/unstable/sql/Statement#CurrentTransformer" + note: "Capitalized and changed from FiberRef> to Context.Reference." +"@effect/sql/Statement#custom": + replacement: "effect/unstable/sql/Statement#custom" + note: "Retained, but returns a Custom segment and uses paramA/paramB/paramC; wrap it with Statement.fragment when a Fragment is required." +"@effect/sql/Statement#defaultEscape": + replacement: "effect/unstable/sql/Statement#defaultEscape" + note: "Moved with the same signature." +"@effect/sql/Statement#FragmentId": + replacement: "none" + note: "The v4 fragment brand is private; use Fragment, fragment, and isFragment instead of direct type-id access." +"@effect/sql/Statement#join": + replacement: "effect/unstable/sql/Statement#join" + note: "Moved with the same empty, single, and multiple-clause behavior." +"@effect/sql/Statement#make": + replacement: "effect/unstable/sql/Statement#make" + note: "Moved with the same constructor inputs." +"@effect/sql/Statement#or": + replacement: "effect/unstable/sql/Statement#or" + note: "Moved unchanged." +"@effect/sql/Statement#setTransformer": + replacement: "Layer.succeed(Statement.CurrentTransformer, transformer)" + note: "The helper was removed; provide the CurrentTransformer reference as a layer." +"@effect/sql/Statement#Statement": + replacement: "effect/unstable/sql/Statement#Statement" + note: "Moved; the nested Transformer type is now top-level and its callback receives Fiber.Fiber rather than FiberRefs.FiberRefs." +"@effect/sql/Statement#unsafeFragment": + replacement: "Statement.fragment([Statement.literal(sql, params)])" + note: "The helper was removed; construct the low-level fragment explicitly, or use the active constructor's sql.unsafe for an executable statement." +"@effect/sql/Statement#withTransformer": + replacement: "Effect.provideService(Statement.CurrentTransformer, transformer)" + note: "The helper was removed; locally provide the transformer reference around the effect." +"@effect/sql/Statement#withTransformerDisabled": + replacement: "Effect.provideService(Statement.CurrentTransformer, undefined)" + note: "The helper was removed; locally provide undefined for the transformer reference." +"@effect/sql/Statement#makeCompiler": + replacement: "effect/unstable/sql/Statement#makeCompiler" + note: "Moved to core Effect; the constructor options are exposed as Statement.CompilerOptions and retain the dialect-specific callbacks." diff --git a/migration/annotations/effect__sql__index.yaml b/migration/annotations/effect__sql__index.yaml new file mode 100644 index 000000000..045e8637e --- /dev/null +++ b/migration/annotations/effect__sql__index.yaml @@ -0,0 +1,3 @@ +"@effect/sql/index": + replacement: "effect/unstable/sql" + note: "The package barrel was removed; import the same namespaces from the effect/unstable/sql barrel or import specific modules directly." diff --git a/migration/annotations/effect__typeclass.yaml b/migration/annotations/effect__typeclass.yaml new file mode 100644 index 000000000..9ebe60ebe --- /dev/null +++ b/migration/annotations/effect__typeclass.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass": + replacement: "none" + note: "The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite abstractions against the concrete v4 data type and its module functions." diff --git a/migration/annotations/effect__typeclass__Alternative.yaml b/migration/annotations/effect__typeclass__Alternative.yaml new file mode 100644 index 000000000..b50c13482 --- /dev/null +++ b/migration/annotations/effect__typeclass__Alternative.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Alternative": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Applicative.yaml b/migration/annotations/effect__typeclass__Applicative.yaml new file mode 100644 index 000000000..a7a8ebd98 --- /dev/null +++ b/migration/annotations/effect__typeclass__Applicative.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Applicative": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Bicovariant.yaml b/migration/annotations/effect__typeclass__Bicovariant.yaml new file mode 100644 index 000000000..1fabb3fd7 --- /dev/null +++ b/migration/annotations/effect__typeclass__Bicovariant.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Bicovariant": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Bounded.yaml b/migration/annotations/effect__typeclass__Bounded.yaml new file mode 100644 index 000000000..04a30c268 --- /dev/null +++ b/migration/annotations/effect__typeclass__Bounded.yaml @@ -0,0 +1,21 @@ +"@effect/typeclass/Bounded#between": + replacement: "Order.isBetween(B.compare)" + note: "Use the v4 Order predicate with { minimum: B.minBound, maximum: B.maxBound }; the Bounded dictionary itself was removed." +"@effect/typeclass/Bounded#Bounded": + replacement: "none" + note: "V4 removed Bounded dictionaries. Keep the Order and minimum/maximum bounds as separate application values." +"@effect/typeclass/Bounded#BoundedTypeLambda": + replacement: "none" + note: "V4 removed the @effect/typeclass higher-kinded Bounded instance machinery." +"@effect/typeclass/Bounded#clamp": + replacement: "Order.clamp(B.compare)" + note: "Use the v4 Order combinator with { minimum: B.minBound, maximum: B.maxBound }; the Bounded dictionary itself was removed." +"@effect/typeclass/Bounded#max": + replacement: "Reducer.make(Combiner.max(B.compare).combine, B.minBound)" + note: "V4 removed Bounded dictionaries. Build the maximum Reducer from the separately retained Order and minimum bound." +"@effect/typeclass/Bounded#min": + replacement: "Reducer.make(Combiner.min(B.compare).combine, B.maxBound)" + note: "V4 removed Bounded dictionaries. Build the minimum Reducer from the separately retained Order and maximum bound." +"@effect/typeclass/Bounded#reverse": + replacement: "Order.flip(B.compare)" + note: "Flip the Order and swap the separately stored minimum and maximum bounds; v4 has no bundled Bounded dictionary." diff --git a/migration/annotations/effect__typeclass__Chainable.yaml b/migration/annotations/effect__typeclass__Chainable.yaml new file mode 100644 index 000000000..3f6c02a19 --- /dev/null +++ b/migration/annotations/effect__typeclass__Chainable.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Chainable": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Contravariant.yaml b/migration/annotations/effect__typeclass__Contravariant.yaml new file mode 100644 index 000000000..62039ee4e --- /dev/null +++ b/migration/annotations/effect__typeclass__Contravariant.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Contravariant": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Coproduct.yaml b/migration/annotations/effect__typeclass__Coproduct.yaml new file mode 100644 index 000000000..86f2652a1 --- /dev/null +++ b/migration/annotations/effect__typeclass__Coproduct.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Coproduct": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Covariant.yaml b/migration/annotations/effect__typeclass__Covariant.yaml new file mode 100644 index 000000000..9d8f80180 --- /dev/null +++ b/migration/annotations/effect__typeclass__Covariant.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Covariant": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Filterable.yaml b/migration/annotations/effect__typeclass__Filterable.yaml new file mode 100644 index 000000000..b64d96c4f --- /dev/null +++ b/migration/annotations/effect__typeclass__Filterable.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Filterable": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__FlatMap.yaml b/migration/annotations/effect__typeclass__FlatMap.yaml new file mode 100644 index 000000000..e5cfb09a6 --- /dev/null +++ b/migration/annotations/effect__typeclass__FlatMap.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/FlatMap": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Foldable.yaml b/migration/annotations/effect__typeclass__Foldable.yaml new file mode 100644 index 000000000..3c3d180b2 --- /dev/null +++ b/migration/annotations/effect__typeclass__Foldable.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Foldable": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Invariant.yaml b/migration/annotations/effect__typeclass__Invariant.yaml new file mode 100644 index 000000000..25129fe1f --- /dev/null +++ b/migration/annotations/effect__typeclass__Invariant.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Invariant": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Monad.yaml b/migration/annotations/effect__typeclass__Monad.yaml new file mode 100644 index 000000000..0ce29d2cc --- /dev/null +++ b/migration/annotations/effect__typeclass__Monad.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Monad": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Monoid.yaml b/migration/annotations/effect__typeclass__Monoid.yaml new file mode 100644 index 000000000..d40e6f502 --- /dev/null +++ b/migration/annotations/effect__typeclass__Monoid.yaml @@ -0,0 +1,24 @@ +"@effect/typeclass/Monoid#array": + replacement: "Array.makeReducerConcat" + note: "Use the v4 array concatenation Reducer; Reducer replaces Monoid and names the identity initialValue." +"@effect/typeclass/Monoid#fromSemigroup": + replacement: "Reducer.make(S.combine, empty)" + note: "Construct a v4 Reducer from the replacement Combiner operation and identity value." +"@effect/typeclass/Monoid#max": + replacement: "Reducer.make(Combiner.max(B.compare).combine, B.minBound)" + note: "Reducer replaces Monoid. Build it from the v4 maximum Combiner and the bounded order's minimum value." +"@effect/typeclass/Monoid#min": + replacement: "Reducer.make(Combiner.min(B.compare).combine, B.maxBound)" + note: "Reducer replaces Monoid. Build it from the v4 minimum Combiner and the bounded order's maximum value." +"@effect/typeclass/Monoid#Monoid": + replacement: "Reducer.Reducer" + note: "Reducer replaces Monoid in v4; empty is renamed initialValue and combineAll remains available." +"@effect/typeclass/Monoid#reverse": + replacement: "Reducer.flip" + note: "Use the v4 Reducer combinator; it preserves initialValue and reverses combine argument order." +"@effect/typeclass/Monoid#struct": + replacement: "Struct.makeReducer" + note: "Pass a record of v4 Reducers to derive a field-wise Reducer." +"@effect/typeclass/Monoid#tuple": + replacement: "Tuple.makeReducer" + note: "Pass one array of v4 Reducers instead of rest Monoid arguments." diff --git a/migration/annotations/effect__typeclass__Of.yaml b/migration/annotations/effect__typeclass__Of.yaml new file mode 100644 index 000000000..3afd034cc --- /dev/null +++ b/migration/annotations/effect__typeclass__Of.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Of": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Pointed.yaml b/migration/annotations/effect__typeclass__Pointed.yaml new file mode 100644 index 000000000..d03ceb668 --- /dev/null +++ b/migration/annotations/effect__typeclass__Pointed.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Pointed": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Product.yaml b/migration/annotations/effect__typeclass__Product.yaml new file mode 100644 index 000000000..6c3fd5e16 --- /dev/null +++ b/migration/annotations/effect__typeclass__Product.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Product": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__SemiAlternative.yaml b/migration/annotations/effect__typeclass__SemiAlternative.yaml new file mode 100644 index 000000000..16d84e90f --- /dev/null +++ b/migration/annotations/effect__typeclass__SemiAlternative.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/SemiAlternative": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__SemiApplicative.yaml b/migration/annotations/effect__typeclass__SemiApplicative.yaml new file mode 100644 index 000000000..a5f0a5917 --- /dev/null +++ b/migration/annotations/effect__typeclass__SemiApplicative.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/SemiApplicative": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__SemiCoproduct.yaml b/migration/annotations/effect__typeclass__SemiCoproduct.yaml new file mode 100644 index 000000000..8ac86cb92 --- /dev/null +++ b/migration/annotations/effect__typeclass__SemiCoproduct.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/SemiCoproduct": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__SemiProduct.yaml b/migration/annotations/effect__typeclass__SemiProduct.yaml new file mode 100644 index 000000000..fd13e924d --- /dev/null +++ b/migration/annotations/effect__typeclass__SemiProduct.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/SemiProduct": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__Semigroup.yaml b/migration/annotations/effect__typeclass__Semigroup.yaml new file mode 100644 index 000000000..e10dd882e --- /dev/null +++ b/migration/annotations/effect__typeclass__Semigroup.yaml @@ -0,0 +1,51 @@ +"@effect/typeclass/Semigroup#array": + replacement: "Array.makeReducerConcat" + note: "The v4 concatenation Reducer is also a Combiner and replaces the array Semigroup." +"@effect/typeclass/Semigroup#constant": + replacement: "Combiner.constant" + note: "Combiner replaces Semigroup in v4." +"@effect/typeclass/Semigroup#first": + replacement: "Combiner.first" + note: "Combiner replaces Semigroup in v4." +"@effect/typeclass/Semigroup#imap": + replacement: "Combiner.make" + note: "V4 has no generic invariant instance; build a Combiner that maps both inputs with from, combines them, then maps the result with to." +"@effect/typeclass/Semigroup#intercalate": + replacement: "Combiner.intercalate" + note: "Combiner replaces Semigroup; v4 takes the separator first and then the Combiner." +"@effect/typeclass/Semigroup#Invariant": + replacement: "none" + note: "The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions." +"@effect/typeclass/Semigroup#last": + replacement: "Combiner.last" + note: "Combiner replaces Semigroup in v4." +"@effect/typeclass/Semigroup#make": + replacement: "Combiner.make" + note: "Combiner replaces Semigroup. V4 accepts only the binary combine function and has no combineMany override." +"@effect/typeclass/Semigroup#max": + replacement: "Combiner.max" + note: "Combiner replaces Semigroup; pass the same Order to retain last-maximum tie behavior." +"@effect/typeclass/Semigroup#min": + replacement: "Combiner.min" + note: "Combiner replaces Semigroup; pass the same Order to retain last-minimum tie behavior." +"@effect/typeclass/Semigroup#Product": + replacement: "none" + note: "The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions." +"@effect/typeclass/Semigroup#reverse": + replacement: "Combiner.flip" + note: "Use the v4 Combiner combinator to reverse combine argument order." +"@effect/typeclass/Semigroup#Semigroup": + replacement: "Combiner.Combiner" + note: "Combiner replaces Semigroup in v4 and retains the binary combine method; combineMany was removed." +"@effect/typeclass/Semigroup#SemigroupTypeLambda": + replacement: "none" + note: "The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions." +"@effect/typeclass/Semigroup#SemiProduct": + replacement: "none" + note: "The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions." +"@effect/typeclass/Semigroup#struct": + replacement: "Struct.makeCombiner" + note: "Pass a record of v4 Combiners to derive a field-wise Combiner." +"@effect/typeclass/Semigroup#tuple": + replacement: "Tuple.makeCombiner" + note: "Pass one array of v4 Combiners instead of rest Semigroup arguments." diff --git a/migration/annotations/effect__typeclass__Traversable.yaml b/migration/annotations/effect__typeclass__Traversable.yaml new file mode 100644 index 000000000..a939a1622 --- /dev/null +++ b/migration/annotations/effect__typeclass__Traversable.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/Traversable": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__TraversableFilterable.yaml b/migration/annotations/effect__typeclass__TraversableFilterable.yaml new file mode 100644 index 000000000..add881f13 --- /dev/null +++ b/migration/annotations/effect__typeclass__TraversableFilterable.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/TraversableFilterable": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__data__Array.yaml b/migration/annotations/effect__typeclass__data__Array.yaml new file mode 100644 index 000000000..e20b9559e --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Array.yaml @@ -0,0 +1,51 @@ +"@effect/typeclass/data/Array#Applicative": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Chainable": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Covariant": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Filterable": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#FlatMap": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Foldable": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#getMonoid": + replacement: "Array.makeReducerConcat" + note: "Use the v4 concatenation Reducer; Reducer replaces Monoid." +"@effect/typeclass/data/Array#getSemigroup": + replacement: "Array.makeReducerConcat" + note: "The v4 concatenation Reducer is also a Combiner and replaces this Semigroup." +"@effect/typeclass/data/Array#Invariant": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Monad": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Of": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Pointed": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Product": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#SemiApplicative": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#SemiProduct": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#Traversable": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." +"@effect/typeclass/data/Array#TraversableFilterable": + replacement: "effect/Array" + note: "The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly." diff --git a/migration/annotations/effect__typeclass__data__BigInt.yaml b/migration/annotations/effect__typeclass__data__BigInt.yaml new file mode 100644 index 000000000..e94aed7f5 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__BigInt.yaml @@ -0,0 +1,18 @@ +"@effect/typeclass/data/BigInt#MonoidMultiply": + replacement: "BigInt.ReducerMultiply" + note: "Renamed and moved to the concrete v4 BigInt module." +"@effect/typeclass/data/BigInt#MonoidSum": + replacement: "BigInt.ReducerSum" + note: "Renamed and moved to the concrete v4 BigInt module." +"@effect/typeclass/data/BigInt#SemigroupMax": + replacement: "BigInt.CombinerMax" + note: "Renamed and moved to the concrete v4 BigInt module." +"@effect/typeclass/data/BigInt#SemigroupMin": + replacement: "BigInt.CombinerMin" + note: "Renamed and moved to the concrete v4 BigInt module." +"@effect/typeclass/data/BigInt#SemigroupMultiply": + replacement: "BigInt.ReducerMultiply" + note: "The v4 Reducer is also a Combiner and preserves multiplication combine semantics." +"@effect/typeclass/data/BigInt#SemigroupSum": + replacement: "BigInt.ReducerSum" + note: "The v4 Reducer is also a Combiner and preserves addition combine semantics." diff --git a/migration/annotations/effect__typeclass__data__Boolean.yaml b/migration/annotations/effect__typeclass__data__Boolean.yaml new file mode 100644 index 000000000..560fdb587 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Boolean.yaml @@ -0,0 +1,24 @@ +"@effect/typeclass/data/Boolean#MonoidEqv": + replacement: "Reducer.make(Boolean.eqv, true)" + note: "Rebuild the removed instance with the v4 boolean operation and its identity." +"@effect/typeclass/data/Boolean#MonoidEvery": + replacement: "Boolean.ReducerAnd" + note: "Renamed and moved to the concrete v4 Boolean module." +"@effect/typeclass/data/Boolean#MonoidSome": + replacement: "Boolean.ReducerOr" + note: "Renamed and moved to the concrete v4 Boolean module." +"@effect/typeclass/data/Boolean#MonoidXor": + replacement: "Reducer.make(Boolean.xor, false)" + note: "Rebuild the removed instance with the v4 boolean operation and its identity." +"@effect/typeclass/data/Boolean#SemigroupEqv": + replacement: "Combiner.make(Boolean.eqv)" + note: "Rebuild the removed instance as a v4 Combiner." +"@effect/typeclass/data/Boolean#SemigroupEvery": + replacement: "Boolean.ReducerAnd" + note: "The v4 Reducer is also a Combiner and preserves logical-AND combine semantics." +"@effect/typeclass/data/Boolean#SemigroupSome": + replacement: "Boolean.ReducerOr" + note: "The v4 Reducer is also a Combiner and preserves logical-OR combine semantics." +"@effect/typeclass/data/Boolean#SemigroupXor": + replacement: "Combiner.make(Boolean.xor)" + note: "Rebuild the removed instance as a v4 Combiner." diff --git a/migration/annotations/effect__typeclass__data__Duration.yaml b/migration/annotations/effect__typeclass__data__Duration.yaml new file mode 100644 index 000000000..0a6b7dbc0 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Duration.yaml @@ -0,0 +1,21 @@ +"@effect/typeclass/data/Duration#Bounded": + replacement: "none" + note: "V4 has no Bounded dictionary; use Duration.Order with Duration.zero and Duration.infinity as separate bounds." +"@effect/typeclass/data/Duration#MonoidMax": + replacement: "Reducer.make(Duration.max, Duration.zero)" + note: "Rebuild the removed maximum Monoid as a v4 Reducer with the same identity." +"@effect/typeclass/data/Duration#MonoidMin": + replacement: "Reducer.make(Duration.min, Duration.infinity)" + note: "Rebuild the removed minimum Monoid as a v4 Reducer with the same identity." +"@effect/typeclass/data/Duration#MonoidSum": + replacement: "Duration.ReducerSum" + note: "Renamed and moved to the concrete v4 Duration module." +"@effect/typeclass/data/Duration#SemigroupMax": + replacement: "Duration.CombinerMax" + note: "Renamed and moved to the concrete v4 Duration module." +"@effect/typeclass/data/Duration#SemigroupMin": + replacement: "Duration.CombinerMin" + note: "Renamed and moved to the concrete v4 Duration module." +"@effect/typeclass/data/Duration#SemigroupSum": + replacement: "Duration.ReducerSum" + note: "The v4 Reducer is also a Combiner and preserves Duration.sum combine semantics." diff --git a/migration/annotations/effect__typeclass__data__Effect.yaml b/migration/annotations/effect__typeclass__data__Effect.yaml new file mode 100644 index 000000000..29c7fe373 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Effect.yaml @@ -0,0 +1,36 @@ +"@effect/typeclass/data/Effect#Chainable": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#ConcurrencyOptions": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#Covariant": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#FlatMap": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#getApplicative": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#getProduct": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#getSemiApplicative": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#getSemiProduct": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#Invariant": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#Monad": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#Of": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." +"@effect/typeclass/data/Effect#Pointed": + replacement: "effect/Effect" + note: "The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly." diff --git a/migration/annotations/effect__typeclass__data__Either.yaml b/migration/annotations/effect__typeclass__data__Either.yaml new file mode 100644 index 000000000..ad27bb0d7 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Either.yaml @@ -0,0 +1,48 @@ +"@effect/typeclass/data/Either#Applicative": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Bicovariant": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Chainable": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Covariant": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#FlatMap": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Foldable": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Invariant": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Monad": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Of": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Pointed": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Product": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#SemiAlternative": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#SemiApplicative": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#SemiCoproduct": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#SemiProduct": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." +"@effect/typeclass/data/Either#Traversable": + replacement: "effect/Either" + note: "The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly." diff --git a/migration/annotations/effect__typeclass__data__Identity.yaml b/migration/annotations/effect__typeclass__data__Identity.yaml new file mode 100644 index 000000000..6f3f4349e --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Identity.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/data/Identity": + replacement: none + note: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. diff --git a/migration/annotations/effect__typeclass__data__Micro.yaml b/migration/annotations/effect__typeclass__data__Micro.yaml new file mode 100644 index 000000000..03a02542a --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Micro.yaml @@ -0,0 +1,36 @@ +"@effect/typeclass/data/Micro#Chainable": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#ConcurrencyOptions": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#Covariant": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#FlatMap": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#getApplicative": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#getProduct": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#getSemiApplicative": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#getSemiProduct": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#Invariant": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#Monad": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#Of": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." +"@effect/typeclass/data/Micro#Pointed": + replacement: "effect/Micro" + note: "The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly." diff --git a/migration/annotations/effect__typeclass__data__Number.yaml b/migration/annotations/effect__typeclass__data__Number.yaml new file mode 100644 index 000000000..14d0a9a9e --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Number.yaml @@ -0,0 +1,27 @@ +"@effect/typeclass/data/Number#Bounded": + replacement: "none" + note: "V4 has no Bounded dictionary; use Number.Order with -Infinity and Infinity as separate bounds." +"@effect/typeclass/data/Number#MonoidMax": + replacement: "Number.ReducerMax" + note: "Renamed and moved to the concrete v4 Number module." +"@effect/typeclass/data/Number#MonoidMin": + replacement: "Number.ReducerMin" + note: "Renamed and moved to the concrete v4 Number module." +"@effect/typeclass/data/Number#MonoidMultiply": + replacement: "Number.ReducerMultiply" + note: "Renamed and moved to the concrete v4 Number module." +"@effect/typeclass/data/Number#MonoidSum": + replacement: "Number.ReducerSum" + note: "Renamed and moved to the concrete v4 Number module." +"@effect/typeclass/data/Number#SemigroupMax": + replacement: "Number.ReducerMax" + note: "The v4 Reducer is also a Combiner and preserves maximum combine semantics." +"@effect/typeclass/data/Number#SemigroupMin": + replacement: "Number.ReducerMin" + note: "The v4 Reducer is also a Combiner and preserves minimum combine semantics." +"@effect/typeclass/data/Number#SemigroupMultiply": + replacement: "Number.ReducerMultiply" + note: "The v4 Reducer is also a Combiner and preserves multiplication combine semantics." +"@effect/typeclass/data/Number#SemigroupSum": + replacement: "Number.ReducerSum" + note: "The v4 Reducer is also a Combiner and preserves addition combine semantics." diff --git a/migration/annotations/effect__typeclass__data__Option.yaml b/migration/annotations/effect__typeclass__data__Option.yaml new file mode 100644 index 000000000..bc1a02fd4 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Option.yaml @@ -0,0 +1,57 @@ +"@effect/typeclass/data/Option#Alternative": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Applicative": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Chainable": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Coproduct": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Covariant": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Filterable": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#FlatMap": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Foldable": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#getOptionalMonoid": + replacement: "Option.makeReducer" + note: "Pass the replacement Combiner; the v4 Reducer uses None as initialValue and combines two Some values." +"@effect/typeclass/data/Option#Invariant": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Monad": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Of": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Pointed": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Product": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#SemiAlternative": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#SemiApplicative": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#SemiCoproduct": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#SemiProduct": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." +"@effect/typeclass/data/Option#Traversable": + replacement: "effect/Option" + note: "The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly." diff --git a/migration/annotations/effect__typeclass__data__Ordering.yaml b/migration/annotations/effect__typeclass__data__Ordering.yaml new file mode 100644 index 000000000..0410c651e --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Ordering.yaml @@ -0,0 +1,6 @@ +"@effect/typeclass/data/Ordering#Monoid": + replacement: "Ordering.Reducer" + note: "Renamed and moved to the concrete v4 Ordering module." +"@effect/typeclass/data/Ordering#Semigroup": + replacement: "Ordering.Reducer" + note: "The v4 Reducer is also a Combiner and preserves Ordering combination semantics." diff --git a/migration/annotations/effect__typeclass__data__Predicate.yaml b/migration/annotations/effect__typeclass__data__Predicate.yaml new file mode 100644 index 000000000..551003298 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Predicate.yaml @@ -0,0 +1,39 @@ +"@effect/typeclass/data/Predicate#Contravariant": + replacement: "effect/Predicate" + note: "The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly." +"@effect/typeclass/data/Predicate#getMonoidEqv": + replacement: "Reducer.make(Predicate.eqv, Predicate.isUnknown)" + note: "Rebuild the removed predicate instance as a v4 Reducer with the always-true predicate as initialValue." +"@effect/typeclass/data/Predicate#getMonoidEvery": + replacement: "Reducer.make(Predicate.and, Predicate.isUnknown)" + note: "Rebuild the removed predicate instance as a v4 Reducer with the always-true predicate as initialValue." +"@effect/typeclass/data/Predicate#getMonoidSome": + replacement: "Reducer.make(Predicate.or, Predicate.isNever)" + note: "Rebuild the removed predicate instance as a v4 Reducer with the always-false predicate as initialValue." +"@effect/typeclass/data/Predicate#getMonoidXor": + replacement: "Reducer.make(Predicate.xor, Predicate.isNever)" + note: "Rebuild the removed predicate instance as a v4 Reducer with the always-false predicate as initialValue." +"@effect/typeclass/data/Predicate#getSemigroupEqv": + replacement: "Combiner.make(Predicate.eqv)" + note: "Rebuild the removed predicate instance as a v4 Combiner." +"@effect/typeclass/data/Predicate#getSemigroupEvery": + replacement: "Combiner.make(Predicate.and)" + note: "Rebuild the removed predicate instance as a v4 Combiner." +"@effect/typeclass/data/Predicate#getSemigroupSome": + replacement: "Combiner.make(Predicate.or)" + note: "Rebuild the removed predicate instance as a v4 Combiner." +"@effect/typeclass/data/Predicate#getSemigroupXor": + replacement: "Combiner.make(Predicate.xor)" + note: "Rebuild the removed predicate instance as a v4 Combiner." +"@effect/typeclass/data/Predicate#Invariant": + replacement: "effect/Predicate" + note: "The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly." +"@effect/typeclass/data/Predicate#Of": + replacement: "effect/Predicate" + note: "The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly." +"@effect/typeclass/data/Predicate#Product": + replacement: "effect/Predicate" + note: "The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly." +"@effect/typeclass/data/Predicate#SemiProduct": + replacement: "effect/Predicate" + note: "The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly." diff --git a/migration/annotations/effect__typeclass__data__Record.yaml b/migration/annotations/effect__typeclass__data__Record.yaml new file mode 100644 index 000000000..b10996128 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Record.yaml @@ -0,0 +1,39 @@ +"@effect/typeclass/data/Record#Covariant": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#Filterable": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#getCovariant": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#getFilterable": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#getInvariant": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#getMonoidUnion": + replacement: "Record.makeReducerUnion" + note: "Pass the replacement value Combiner; the v4 Reducer uses an empty record as initialValue." +"@effect/typeclass/data/Record#getSemigroupIntersection": + replacement: "Record.makeReducerIntersection" + note: "Pass the replacement value Combiner and use the returned Reducer's combine operation for pairwise intersection." +"@effect/typeclass/data/Record#getSemigroupUnion": + replacement: "Record.makeReducerUnion" + note: "The v4 Reducer is also a Combiner and preserves pairwise union semantics." +"@effect/typeclass/data/Record#getTraversable": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#getTraversableFilterable": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#Invariant": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#Traversable": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." +"@effect/typeclass/data/Record#TraversableFilterable": + replacement: "effect/Record" + note: "The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly." diff --git a/migration/annotations/effect__typeclass__data__String.yaml b/migration/annotations/effect__typeclass__data__String.yaml new file mode 100644 index 000000000..c79396fdf --- /dev/null +++ b/migration/annotations/effect__typeclass__data__String.yaml @@ -0,0 +1,6 @@ +"@effect/typeclass/data/String#Monoid": + replacement: "String.ReducerConcat" + note: "Renamed and moved to the concrete v4 String module." +"@effect/typeclass/data/String#Semigroup": + replacement: "String.ReducerConcat" + note: "The v4 Reducer is also a Combiner and preserves string concatenation." diff --git a/migration/annotations/effect__typeclass__data__Tuple.yaml b/migration/annotations/effect__typeclass__data__Tuple.yaml new file mode 100644 index 000000000..8f1e2c205 --- /dev/null +++ b/migration/annotations/effect__typeclass__data__Tuple.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/data/Tuple#Bicovariant": + replacement: "effect/Tuple" + note: "The @effect/typeclass package and its Tuple instance dictionaries were removed in v4. Use the concrete effect/Tuple operations directly." diff --git a/migration/annotations/effect__typeclass__index.yaml b/migration/annotations/effect__typeclass__index.yaml new file mode 100644 index 000000000..ff85bec6c --- /dev/null +++ b/migration/annotations/effect__typeclass__index.yaml @@ -0,0 +1,3 @@ +"@effect/typeclass/index": + replacement: "none" + note: "The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite abstractions against the concrete v4 data type and its module functions." diff --git a/migration/annotations/effect__vitest__index.yaml b/migration/annotations/effect__vitest__index.yaml new file mode 100644 index 000000000..515c9201a --- /dev/null +++ b/migration/annotations/effect__vitest__index.yaml @@ -0,0 +1,279 @@ +"@effect/vitest/index#scoped": + replacement: "@effect/vitest#effect" + note: "V4 effect tests are scoped and provide the test environment. Replace scoped(...) with effect(...), and it.scoped(...) with it.effect(...)." +"@effect/vitest/index#scopedLive": + replacement: "@effect/vitest#live" + note: "V4 live tests are scoped automatically. Replace scopedLive(...) with live(...), and it.scopedLive(...) with it.live(...)." +"@effect/vitest/index#chai.Should": + replacement: "vitest#chai.Should" + note: "This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route." +"@effect/vitest/index#expect": + replacement: "vitest#expect" + note: "This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route." +"@effect/vitest/index#Mock": + replacement: "vitest#Mock" + note: "This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route." +"@effect/vitest/index#should": + replacement: "vitest#should" + note: "This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route." +"@effect/vitest/index#ApiConfig": + replacement: "vitest/node#ApiConfig" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#BaseCoverageOptions": + replacement: "vitest/node#BaseCoverageOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#BenchmarkUserOptions": + replacement: "vitest/node#BenchmarkUserOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#BrowserConfigOptions": + replacement: "vitest/node#BrowserConfigOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#BrowserScript": + replacement: "vitest/node#BrowserScript" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#BuiltinEnvironment": + replacement: "vitest/node#BuiltinEnvironment" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CoverageIstanbulOptions": + replacement: "vitest/node#CoverageIstanbulOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CoverageOptions": + replacement: "vitest/node#CoverageOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CoverageProvider": + replacement: "vitest/node#CoverageProvider" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CoverageProviderModule": + replacement: "vitest/node#CoverageProviderModule" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CoverageReporter": + replacement: "vitest/node#CoverageReporter" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CoverageV8Options": + replacement: "vitest/node#CoverageV8Options" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CSSModuleScopeStrategy": + replacement: "vitest/node#CSSModuleScopeStrategy" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CustomProviderOptions": + replacement: "vitest/node#CustomProviderOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#DepsOptimizationOptions": + replacement: "vitest/node#DepsOptimizationOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#EnvironmentOptions": + replacement: "vitest/node#EnvironmentOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#InlineConfig": + replacement: "vitest/node#InlineConfig" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#Pool": + replacement: "vitest/node#Pool" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#ProjectConfig": + replacement: "vitest/node#ProjectConfig" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#ReportContext": + replacement: "vitest/node#ReportContext" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#ResolvedConfig": + replacement: "vitest/node#ResolvedConfig" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#ResolvedCoverageOptions": + replacement: "vitest/node#ResolvedCoverageOptions" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#SequenceHooks": + replacement: "vitest/node#SequenceHooks" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#SequenceSetupFiles": + replacement: "vitest/node#SequenceSetupFiles" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#TypecheckConfig": + replacement: "vitest/node#TypecheckConfig" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#VitestEnvironment": + replacement: "vitest/node#VitestEnvironment" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#VitestRunMode": + replacement: "vitest/node#VitestRunMode" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#WorkerContext": + replacement: "vitest/node#WorkerContext" + note: "This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape." +"@effect/vitest/index#CollectLineNumbers": + replacement: "vitest/node#TypeCheckCollectLineNumbers" + note: "Vitest 3 deprecated the root alias in favor of this renamed vitest/node type." +"@effect/vitest/index#CollectLines": + replacement: "vitest/node#TypeCheckCollectLines" + note: "Vitest 3 deprecated the root alias in favor of this renamed vitest/node type." +"@effect/vitest/index#Context": + replacement: "vitest/node#TypeCheckContext" + note: "Vitest 3 deprecated the root alias in favor of this renamed vitest/node type." +"@effect/vitest/index#RawErrsMap": + replacement: "vitest/node#TypeCheckRawErrorsMap" + note: "Vitest 3 deprecated the root alias in favor of this renamed vitest/node type." +"@effect/vitest/index#RootAndTarget": + replacement: "vitest/node#TypeCheckRootAndTarget" + note: "Vitest 3 deprecated the root alias in favor of this renamed vitest/node type." +"@effect/vitest/index#TscErrorInfo": + replacement: "vitest/node#TypeCheckErrorInfo" + note: "Vitest 3 deprecated the root alias in favor of this renamed vitest/node type." +"@effect/vitest/index#Custom": + replacement: "vitest#RunnerTestCase" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#RunnerCustomCase": + replacement: "vitest#RunnerTestCase" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#File": + replacement: "vitest#RunnerTestFile" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#Suite": + replacement: "vitest#RunnerTestSuite" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#Task": + replacement: "vitest#RunnerTask" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#TaskBase": + replacement: "vitest#RunnerTaskBase" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#TaskResult": + replacement: "vitest#RunnerTaskResult" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#TaskResultPack": + replacement: "vitest#RunnerTaskResultPack" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#Test": + replacement: "vitest#RunnerTestCase" + note: "Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner* type from vitest." +"@effect/vitest/index#ExtendedContext": + replacement: "vitest#TestContext" + note: "The separate context alias was removed. Vitest 5 uses TestContext, which includes the current task and lifecycle methods." +"@effect/vitest/index#TaskContext": + replacement: "vitest#TestContext" + note: "The separate context alias was removed. Vitest 5 uses TestContext, which includes the current task and lifecycle methods." +"@effect/vitest/index#Environment": + replacement: "vitest/runtime#Environment" + note: "This was a deprecated root re-export. Import it from vitest/runtime; Vitest 5 exposes custom environments through vitest/runtime." +"@effect/vitest/index#EnvironmentReturn": + replacement: "vitest/runtime#EnvironmentReturn" + note: "This was a deprecated root re-export. Import it from vitest/runtime; Vitest 5 exposes custom environments through vitest/runtime." +"@effect/vitest/index#VmEnvironmentReturn": + replacement: "vitest/runtime#VmEnvironmentReturn" + note: "This was a deprecated root re-export. Import it from vitest/runtime; Vitest 5 exposes custom environments through vitest/runtime." +"@effect/vitest/index#HappyDOMOptions": + replacement: "NonNullable" + note: "Vitest 5 keeps this shape only as a property of EnvironmentOptions; derive it from the public vitest/node type." +"@effect/vitest/index#JSDOMOptions": + replacement: "NonNullable" + note: "Vitest 5 keeps this shape only as a property of EnvironmentOptions; derive it from the public vitest/node type." +"@effect/vitest/index#ArgumentsType": + replacement: "T extends (...args: infer A) => any ? A : never" + note: "Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals." +"@effect/vitest/index#Arrayable": + replacement: "T | Array" + note: "Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals." +"@effect/vitest/index#Awaitable": + replacement: "T | PromiseLike" + note: "Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals." +"@effect/vitest/index#Constructable": + replacement: "new (...args: any[]) => any" + note: "Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals." +"@effect/vitest/index#MutableArray": + replacement: "{ -readonly [K in keyof T]: T[K] }" + note: "Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals." +"@effect/vitest/index#Nullable": + replacement: "T | null | undefined" + note: "Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals." +"@effect/vitest/index#ErrorWithDiff": + replacement: "vitest#TestError" + note: "Vitest 3 deprecated ErrorWithDiff in favor of TestError; review the tightened actual, expected, and cause fields." +"@effect/vitest/index#SerializableSpec": + replacement: "vitest#SerializedTestSpecification" + note: "Use the non-deprecated Vitest name; SerializableSpec was only an alias." +"@effect/vitest/index#Reporter": + replacement: "vitest/node#Reporter" + note: "Import Reporter from vitest/node; the deprecated vitest/reporters entrypoint was removed in Vitest 5." +"@effect/vitest/index#UserConfig": + replacement: "vitest/config#TestUserConfig" + note: "Vitest 5 exposes its config as TestUserConfig; ViteUserConfig is the separate Vite configuration type." +"@effect/vitest/index#UserWorkspaceConfig": + replacement: "vitest/config#UserWorkspaceConfig" + note: "Import the type from vitest/config and migrate Vitest workspace configuration to projects." +"@effect/vitest/index#PoolOptions": + replacement: "vitest/config#TestUserConfig" + note: "The v3 built-in poolOptions object was removed. Move its fields to Vitest 5 top-level config such as maxWorkers and vmMemoryLimit; vitest/node PoolOptions is a different custom-pool API." +"@effect/vitest/index#RuntimeContext": + replacement: "none" + note: "Vitest 5 deprecates @vitest/runner and does not expose this internal state type. Extend TestRunner from vitest and use its public methods instead." +"@effect/vitest/index#SuiteHooks": + replacement: "ReturnType" + note: "Derive the hook collection from Vitest 5's public TestRunner API; ordinary tests should use public hook functions." +"@effect/vitest/index#DoneCallback": + replacement: "none" + note: "Vitest does not support callback-style tests. Return a Promise or, in @effect/vitest tests, return an Effect." +"@effect/vitest/index#HookCleanupCallback": + replacement: "none" + note: "No named Vitest 5 export replaces this alias. Let the hook return type infer, or type the cleanup function locally." +"@effect/vitest/index#HookListener": + replacement: "none" + note: "Infer the callback from the public hook function, or derive it with Parameters[0] and the corresponding hook name." +"@effect/vitest/index#ModuleCache": + replacement: "none" + note: "Vitest 3 marked this unused internal cache shape deprecated; Vitest 5 has no public replacement." +"@effect/vitest/index#ResolvedTestEnvironment": + replacement: "none" + note: "Vitest 3 marked this type unsupported. Use Environment from vitest/runtime for custom environments." +"@effect/vitest/index#ResolveIdFunction": + replacement: "none" + note: "This deprecated vite-node callback was removed. Use Vite environment or module-runner APIs." +"@effect/vitest/index#TransformModePatterns": + replacement: "none" + note: "This was removed with vite-node transform modes. Configure the Vite environment and its dependency optimizer instead." +"@effect/vitest/index#WorkerRPC": + replacement: "none" + note: "The concrete worker RPC composition is internal. Use public Vitest RuntimeRPC, RunnerRPC, ContextRPC, or WorkerRequest types only when their narrower contract fits." +"@effect/vitest/index#bench": + replacement: "vitest#test" + note: "Vitest 5 removes the top-level bench export. Destructure bench from a regular test's context and await bench(name, fn).run(); use skip, only, or todo on the enclosing test." +"@effect/vitest/index#BenchFactory": + replacement: "vitest#Bench" + note: "Use the Vitest 5 test-context bench fixture type. It is no longer the tinybench factory constructor." +"@effect/vitest/index#BenchFunction": + replacement: "vitest#BenchFn" + note: "Use BenchFn for the callback passed to the Vitest 5 test-context bench fixture." +"@effect/vitest/index#BenchTask": + replacement: "vitest#BenchRegistration" + note: "Migrate to a fixture registration and await its run() method; review its fields instead of treating it as a tinybench task." +"@effect/vitest/index#BenchTaskResult": + replacement: "vitest#BenchResult" + note: "Use the result returned by awaiting the Vitest 5 fixture registration's run() method." +"@effect/vitest/index#Benchmark": + replacement: "vitest#TestBenchmark" + note: "Use TestBenchmark for recorded benchmark data on a test; benchmarks are no longer standalone test tasks." +"@effect/vitest/index#BenchmarkAPI": + replacement: "vitest#Bench" + note: "Use the test-context bench fixture. Move skip, only, and todo to the enclosing test." +"@effect/vitest/index#BenchmarkResult": + replacement: "vitest#BenchResult" + note: "Use the result returned by awaiting the Vitest 5 fixture registration's run() method; review its changed fields." +"@effect/vitest/index#BenchmarkRunner": + replacement: "vitest#BenchmarkProvider" + note: "Vitest 5 runs benchmarks through providers. Export a BenchmarkProvider with an asynchronous run(group) method for a custom engine." +"@effect/vitest/index#ExpectPollOptions": + replacement: "NonNullable[1]>" + note: "Vitest 5 removes the named options export; derive the options from the public expect.poll function." +"@effect/vitest/index#Assertion": + replacement: "vitest#Assertion" + note: "Vitest 5 takes the matcher return type first. Replace Assertion with Assertion or Assertion, T> for asynchronous assertions." +"@effect/vitest/index#Matchers": + replacement: "vitest#Matchers" + note: "Augment vitest.Matchers for custom matchers. R is the matcher return type and T is the received value; @vitest/expect no longer shares Vitest's assertion state." +"@effect/vitest/index#it": + replacement: "@effect/vitest#it" + note: "Effect helpers retain their calling convention. Vitest 5 removes it.sequential; pass { concurrent: false } to the native test or as the Effect helper's third argument." +"@effect/vitest/index#test": + replacement: "vitest#test" + note: "Vitest 5 removes test.sequential and sequential options. Pass { concurrent: false } to opt out of inherited concurrency." +"@effect/vitest/index#describe": + replacement: "vitest#describe" + note: "Vitest 5 removes describe.sequential and sequential options. Use describe(name, { concurrent: false }, body) for suites that depend on ordering." diff --git a/migration/annotations/effect__vitest__utils.yaml b/migration/annotations/effect__vitest__utils.yaml new file mode 100644 index 000000000..240ffe3da --- /dev/null +++ b/migration/annotations/effect__vitest__utils.yaml @@ -0,0 +1,15 @@ +"@effect/vitest/utils#assertFailure": + replacement: "assertExitFailure" + note: "For v3 Exit values, rename to assertExitFailure. In v4, assertFailure instead asserts Result.Failure." +"@effect/vitest/utils#assertLeft": + replacement: "assertFailure" + note: "Either became Result in v4: migrate Left to Result.Failure, then use assertFailure; narrowed payload access changes from .left to .failure." +"@effect/vitest/utils#assertMatch": + replacement: "assertMatch" + note: "Unchanged positional helper and behavior; only the parameter spelling changed, so call sites need no change." +"@effect/vitest/utils#assertRight": + replacement: "assertSuccess" + note: "Either became Result in v4: migrate Right to Result.Success, then use assertSuccess; narrowed payload access changes from .right to .success." +"@effect/vitest/utils#assertSuccess": + replacement: "assertExitSuccess" + note: "For v3 Exit values, rename to assertExitSuccess. In v4, assertSuccess instead asserts Result.Success." diff --git a/migration/annotations/effect__workflow.yaml b/migration/annotations/effect__workflow.yaml new file mode 100644 index 000000000..0995bdb5e --- /dev/null +++ b/migration/annotations/effect__workflow.yaml @@ -0,0 +1,3 @@ +"@effect/workflow": + replacement: "effect/unstable/workflow" + note: "The @effect/workflow package was merged into the effect package; import the effect/unstable/workflow barrel or import specific modules directly (e.g. effect/unstable/workflow/)." diff --git a/migration/annotations/effect__workflow__Activity.yaml b/migration/annotations/effect__workflow__Activity.yaml new file mode 100644 index 000000000..cc4b67596 --- /dev/null +++ b/migration/annotations/effect__workflow__Activity.yaml @@ -0,0 +1,18 @@ +"@effect/workflow/Activity#Any": + replacement: "effect/unstable/workflow/Activity#Any" + note: "Moved into core Effect. V4 Any is minimal; use AnyWithProps when schemas or execution properties are required." +"@effect/workflow/Activity#CurrentAttempt": + replacement: "effect/unstable/workflow/Activity#CurrentAttempt" + note: "Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same default of 1." +"@effect/workflow/Activity#make": + replacement: "effect/unstable/workflow/Activity#make" + note: "Moved into core Effect with the same constructor shape, v4 Schema.Constraint service directions, and optional annotations." +"@effect/workflow/Activity#raceAll": + replacement: "effect/unstable/workflow/Activity#raceAll" + note: "Moved into core Effect with the same named durable race behavior." +"@effect/workflow/Activity#retry": + replacement: "effect/unstable/workflow/Activity#retry" + note: "Moved into core Effect and updated to v4 Effect.retry option types." +"@effect/workflow/Activity#TypeId": + replacement: "none" + note: "The activity marker is private in v4. Use Activity, Activity.Any, or Activity.AnyWithProps constraints." diff --git a/migration/annotations/effect__workflow__DurableClock.yaml b/migration/annotations/effect__workflow__DurableClock.yaml new file mode 100644 index 000000000..adb1bf416 --- /dev/null +++ b/migration/annotations/effect__workflow__DurableClock.yaml @@ -0,0 +1,6 @@ +"@effect/workflow/DurableClock#make": + replacement: "effect/unstable/workflow/DurableClock#make" + note: "Moved into core Effect; Duration.DurationInput is now Duration.Input." +"@effect/workflow/DurableClock#TypeId": + replacement: "none" + note: "The durable-clock marker is private in v4. Use DurableClock values structurally." diff --git a/migration/annotations/effect__workflow__DurableDeferred.yaml b/migration/annotations/effect__workflow__DurableDeferred.yaml new file mode 100644 index 000000000..6e01c63c9 --- /dev/null +++ b/migration/annotations/effect__workflow__DurableDeferred.yaml @@ -0,0 +1,30 @@ +"@effect/workflow/DurableDeferred#Any": + replacement: "effect/unstable/workflow/DurableDeferred#Any" + note: "Moved into core Effect. V4 Any is minimal; use AnyWithProps when success, error, or exit schemas are required." +"@effect/workflow/DurableDeferred#await": + replacement: "effect/unstable/workflow/DurableDeferred#await" + note: "Moved into core Effect with the same persisted-result and workflow-suspension behavior." +"@effect/workflow/DurableDeferred#done": + replacement: "effect/unstable/workflow/DurableDeferred#done" + note: "Moved into core Effect; schema requirements now use explicit directional encoding services." +"@effect/workflow/DurableDeferred#fail": + replacement: "effect/unstable/workflow/DurableDeferred#fail" + note: "Moved into core Effect and now requires the error schema encoding services." +"@effect/workflow/DurableDeferred#failCause": + replacement: "effect/unstable/workflow/DurableDeferred#failCause" + note: "Moved into core Effect and now requires the error schema encoding services." +"@effect/workflow/DurableDeferred#into": + replacement: "effect/unstable/workflow/DurableDeferred#into" + note: "Moved into core Effect with the same exit recording and suspension propagation behavior. Provide both decoding and encoding services for the success and error schemas; recording the exit requires encoding services." +"@effect/workflow/DurableDeferred#make": + replacement: "effect/unstable/workflow/DurableDeferred#make" + note: "Moved into core Effect with the same name and optional schemas, expressed through v4 Schema.Constraint." +"@effect/workflow/DurableDeferred#raceAll": + replacement: "effect/unstable/workflow/DurableDeferred#raceAll" + note: "Moved into core Effect with the same persisted-winner behavior." +"@effect/workflow/DurableDeferred#succeed": + replacement: "effect/unstable/workflow/DurableDeferred#succeed" + note: "Moved into core Effect and now requires the success schema encoding services." +"@effect/workflow/DurableDeferred#TypeId": + replacement: "none" + note: "The durable-deferred marker is private in v4. Use DurableDeferred, Any, or AnyWithProps constraints." diff --git a/migration/annotations/effect__workflow__DurableQueue.yaml b/migration/annotations/effect__workflow__DurableQueue.yaml new file mode 100644 index 000000000..5c2dbb0f0 --- /dev/null +++ b/migration/annotations/effect__workflow__DurableQueue.yaml @@ -0,0 +1,6 @@ +"@effect/workflow/DurableQueue#make": + replacement: "effect/unstable/workflow/DurableQueue#make" + note: "Moved into core Effect; queue persistence now comes from effect/unstable/persistence." +"@effect/workflow/DurableQueue#TypeId": + replacement: "effect/unstable/workflow/DurableQueue#TypeId" + note: "Moved into core Effect; the marker literal changed to ~effect/workflow/DurableQueue. Use typeof DurableQueue.TypeId in type position." diff --git a/migration/annotations/effect__workflow__DurableRateLimiter.yaml b/migration/annotations/effect__workflow__DurableRateLimiter.yaml new file mode 100644 index 000000000..a0261367e --- /dev/null +++ b/migration/annotations/effect__workflow__DurableRateLimiter.yaml @@ -0,0 +1,3 @@ +"@effect/workflow/DurableRateLimiter": + replacement: none + note: "Not ported. Build an Activity whose execute uses persistence RateLimiter.consume with onExceeded: delay, then sleeps for the returned delay with DurableClock." diff --git a/migration/annotations/effect__workflow__Workflow.yaml b/migration/annotations/effect__workflow__Workflow.yaml new file mode 100644 index 000000000..941cb9f25 --- /dev/null +++ b/migration/annotations/effect__workflow__Workflow.yaml @@ -0,0 +1,51 @@ +"@effect/workflow/Workflow#Any": + replacement: "effect/unstable/workflow/Workflow#Any" + note: "Moved into core Effect. Workflow identity changed from name to _tag and definitions are now class-compatible constructors." +"@effect/workflow/Workflow#AnyTaggedRequestSchema": + replacement: "none" + note: "The TaggedRequest adapter constraint was removed. Define the workflow explicitly with Workflow.make and the request payload, success, error, and PrimaryKey schemas." +"@effect/workflow/Workflow#CaptureDefects": + replacement: "effect/unstable/workflow/Workflow#CaptureDefects" + note: "Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same true default." +"@effect/workflow/Workflow#Execution": + replacement: "effect/unstable/workflow/Workflow#Execution" + note: "Moved into core Effect; its workflow discriminator changed from name to _tag." +"@effect/workflow/Workflow#fromTaggedRequest": + replacement: "none" + note: "Removed. Expand to Workflow.make(schema._tag, { payload: schema, success: schema.success, error: schema.failure, idempotencyKey: PrimaryKey.value })." +"@effect/workflow/Workflow#isResult": + replacement: "effect/unstable/workflow/Workflow#isResult" + note: "Moved into core Effect with the same result refinement behavior." +"@effect/workflow/Workflow#make": + replacement: "effect/unstable/workflow/Workflow#make" + note: "The signature changed from make({ name, ... }) to make(tag, { ... }); definitions expose _tag and are class-compatible constructors." +"@effect/workflow/Workflow#Requirements": + replacement: "Workflow.RequirementsClient / Workflow.RequirementsHandler" + note: "The schema Context union split by direction: client payload encoding and result decoding versus handler payload decoding and result encoding." +"@effect/workflow/Workflow#Result": + replacement: "effect/unstable/workflow/Workflow#Result" + note: "Moved into core Effect and remains the Complete or Suspended result type and schema constructor." +"@effect/workflow/Workflow#ResultEncoded": + replacement: "effect/unstable/workflow/Workflow#ResultEncoded" + note: "Moved into core Effect and remains both the encoded result type and generic encoded-result codec." +"@effect/workflow/Workflow#ResultTypeId": + replacement: "none" + note: "The result marker is private in v4. Use Workflow.isResult for narrowing." +"@effect/workflow/Workflow#SuspendOnFailure": + replacement: "effect/unstable/workflow/Workflow#SuspendOnFailure" + note: "Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same false default." +"@effect/workflow/Workflow#TypeId": + replacement: "none" + note: "The workflow marker is private in v4. Use Workflow.Any or Workflow.Workflow constraints." +"@effect/workflow/Workflow#Workflow": + replacement: "effect/unstable/workflow/Workflow#Workflow" + note: "Name and name became Tag and _tag, schemas use directional services, definitions are constructable, and poll returns Option." +"@effect/workflow/Workflow#Workflow.Error": + replacement: "W[\"errorSchema\"][\"Type\"]" + note: "The namespace alias was removed. Extract the decoded error type from the public errorSchema property." +"@effect/workflow/Workflow#Workflow.Payload": + replacement: "Schema.Schema.Type>" + note: "The namespace alias was removed. Extract the decoded payload from the exported PayloadSchema helper." +"@effect/workflow/Workflow#Workflow.Success": + replacement: "W[\"successSchema\"][\"Type\"]" + note: "The namespace alias was removed. Extract the decoded success type from the public successSchema property." diff --git a/migration/annotations/effect__workflow__WorkflowEngine.yaml b/migration/annotations/effect__workflow__WorkflowEngine.yaml new file mode 100644 index 000000000..543c7002c --- /dev/null +++ b/migration/annotations/effect__workflow__WorkflowEngine.yaml @@ -0,0 +1,6 @@ +"@effect/workflow/WorkflowEngine#layerMemory": + replacement: "effect/unstable/workflow/WorkflowEngine#layerMemory" + note: "Moved into core Effect and remains the non-durable engine for tests and local development." +"@effect/workflow/WorkflowEngine#makeUnsafe": + replacement: "effect/unstable/workflow/WorkflowEngine#makeUnsafe" + note: "Moved into core Effect. Context service projections now use Service instead of Type, and absent encoded results use Option." diff --git a/migration/annotations/effect__workflow__WorkflowProxy.yaml b/migration/annotations/effect__workflow__WorkflowProxy.yaml new file mode 100644 index 000000000..bc1d27b4a --- /dev/null +++ b/migration/annotations/effect__workflow__WorkflowProxy.yaml @@ -0,0 +1,6 @@ +"@effect/workflow/WorkflowProxy#ConvertHttpApi": + replacement: "effect/unstable/workflow/WorkflowProxy#ConvertHttpApi" + note: "Moved into core Effect and updated to v4 HttpApiEndpoint types and the consolidated HttpApi architecture." +"@effect/workflow/WorkflowProxy#ConvertRpcs": + replacement: "effect/unstable/workflow/WorkflowProxy#ConvertRpcs" + note: "Moved into core Effect; generated execute, discard, and resume RPCs are now keyed from workflow _tag." diff --git a/migration/annotations/effect__workflow__WorkflowProxyServer.yaml b/migration/annotations/effect__workflow__WorkflowProxyServer.yaml new file mode 100644 index 000000000..b604847ef --- /dev/null +++ b/migration/annotations/effect__workflow__WorkflowProxyServer.yaml @@ -0,0 +1,9 @@ +"@effect/workflow/WorkflowProxyServer#layerHttpApi": + replacement: "effect/unstable/workflow/WorkflowProxyServer#layerHttpApi" + note: "Moved into core Effect. Use v4 HttpApi group identifiers and Workflow.RequirementsHandler schema services." +"@effect/workflow/WorkflowProxyServer#layerRpcHandlers": + replacement: "effect/unstable/workflow/WorkflowProxyServer#layerRpcHandlers" + note: "Moved into core Effect; generated handlers require Workflow.RequirementsHandler rather than the undirected Requirements union." +"@effect/workflow/WorkflowProxyServer#RpcHandlers": + replacement: "effect/unstable/workflow/WorkflowProxyServer#RpcHandlers" + note: "Moved into core Effect; handler names derive from workflow _tag and the optional prefix." diff --git a/migration/fiberref.md b/migration/fiberref.md index 51a088622..abfc17fa6 100644 --- a/migration/fiberref.md +++ b/migration/fiberref.md @@ -11,7 +11,6 @@ from `References` and related modules. | v3 FiberRef | v4 Reference | | ----------------------------------- | ---------------------------------- | -| `FiberRef.currentConcurrency` | `References.CurrentConcurrency` | | `FiberRef.currentLogLevel` | `References.CurrentLogLevel` | | `FiberRef.currentMinimumLogLevel` | `References.MinimumLogLevel` | | `FiberRef.currentLogAnnotations` | `References.CurrentLogAnnotations` | @@ -88,8 +87,8 @@ set via `Effect.provideService`, which scopes the value to the provided effect. import { Effect, FiberRef } from "effect" const program = Effect.gen(function*() { - yield* FiberRef.set(FiberRef.currentConcurrency, 10) - // subsequent code sees concurrency = 10 + yield* FiberRef.set(FiberRef.currentMaxOpsBeforeYield, 500) + // subsequent code sees maxOpsBeforeYield = 500 }) ``` @@ -100,10 +99,10 @@ import { Effect, References } from "effect" const program = Effect.provideService( Effect.gen(function*() { - const concurrency = yield* References.CurrentConcurrency - console.log(concurrency) // 10 + const maxOps = yield* References.MaxOpsBeforeYield + console.log(maxOps) // 500 }), - References.CurrentConcurrency, - 10 + References.MaxOpsBeforeYield, + 500 ) ``` diff --git a/migration/schema.md b/migration/schema.md index 98c2f046b..bc5ead367 100644 --- a/migration/schema.md +++ b/migration/schema.md @@ -29,7 +29,8 @@ This document maps v3 Schema APIs to their v4 equivalents. Simple renames and ar | `RedactedFromSelf` | `Redacted` | rename | | `Redacted` | `RedactedFromValue` | rename | | `EitherFromSelf` | `Result` | rename | -| `TaggedError` | `TaggedErrorClass` | rename | +| `DateFromNumber` | `DateFromMillis` | rename | +| `Date` | `DateFromString` | restructure | | `decodeUnknown` | `decodeUnknownEffect` | rename | | `decode` | `decodeEffect` | rename | | `decodeUnknownEither` | `decodeUnknownExit` | rename | @@ -86,6 +87,30 @@ The following `*FromSelf` schemas have been renamed to drop the suffix: `DateFromSelf` → `Date`, `DurationFromSelf` → `Duration`, `ChunkFromSelf` → `Chunk`, `ReadonlyMapFromSelf` → `ReadonlyMap`, `ReadonlySetFromSelf` → `ReadonlySet`, `HashMapFromSelf` → `HashMap`, `HashSetFromSelf` → `HashSet`, `BigDecimalFromSelf` → `BigDecimal`, `CauseFromSelf` → `Cause`, `ExitFromSelf` → `Exit`, `OptionFromSelf` → `Option`, `RegExpFromSelf` → `RegExp` +### `Date` encoded contract + +**Migration: restructure** + +In v3, `Schema.Date` decoded an ISO date string to a `Date` and rejected invalid dates. In v4, `Schema.Date` is the renamed `Schema.DateFromSelf`, so it expects a valid `Date` as its encoded value. Existing code can still type-check after upgrading while no longer accepting the same input. + +v3 + +```ts +import { Schema } from "effect" + +const DateFromIsoString = Schema.Date +``` + +v4 + +```ts +import { Schema } from "effect" + +const DateFromIsoString = Schema.DateFromString +``` + +`Schema.DateFromString` preserves the string-to-`Date` transformation and rejects strings that produce invalid dates. + ### Filter renames All filters have been renamed with an `is` prefix and now use `check(...)` or `pipe(Schema.check(...))`: @@ -242,7 +267,7 @@ v4 ```ts import { Schema, SchemaRepresentation } from "effect" -const doc = SchemaRepresentation.fromAST(Schema.String.ast) +const doc = SchemaRepresentation.toRepresentation(Schema.String.ast) const multi = SchemaRepresentation.toMultiDocument(doc) const codeDoc = SchemaRepresentation.toCodeDocument(multi) console.log(codeDoc.codes[0].Type) @@ -872,14 +897,14 @@ const NumberFromString = Schema.transformOrFail(Schema.String, Schema.Number, { v4 ```ts -import { Effect, Number, Option, Schema, SchemaGetter, SchemaIssue } from "effect" +import { Effect, Number, Schema, SchemaGetter, SchemaIssue } from "effect" const NumberFromString = Schema.String.pipe( Schema.decodeTo(Schema.Number, { decode: SchemaGetter.transformOrFail((s) => { const n = Number.parse(s) if (n === undefined) { - return Effect.fail(new SchemaIssue.InvalidValue(Option.some(s))) + return Effect.fail(new SchemaIssue.InvalidValue()) } return Effect.succeed(n) }), diff --git a/migration/v3-to-v4.md b/migration/v3-to-v4.md index 5e3f3c771..767e4acbb 100644 --- a/migration/v3-to-v4.md +++ b/migration/v3-to-v4.md @@ -1,14 +1,12 @@ -# v3 to v4 Import and API Rename Maps + -Mapped modules: 290 -No counterpart: 43 -API renames: 53 +# v3 to v4 Migration Reference -This file is intended for migration agents. It contains user-facing import -specifier mappings and API rename mappings. +Base: `origin/v3` (`2e471d9cec31889cd6548aa5423b64c2b85238be`) -Use the import map when rewriting import declarations. Use the API renames when -rewriting renamed symbols. +Head: `agent/bob/738d126b22f9` (`b4773df70eb78ac7c222b236ff5a0605e6632ac6`) + +This file is generated from the API diff and `migration/annotations/*.yaml`. ## Import Map @@ -33,7 +31,7 @@ effect/TReentrantLock -> effect/TxReentrantLock (barrel: effect) effect/TRef -> effect/TxRef (barrel: effect) effect/TSemaphore -> effect/TxSemaphore (barrel: effect) effect/TSubscriptionRef -> effect/TxSubscriptionRef (barrel: effect) -effect/FastCheck -> effect/testing/FastCheck (barrel: effect/testing) +effect/FastCheck -> fast-check effect/TestClock -> effect/testing/TestClock (barrel: effect/testing) @effect/cli/Args -> effect/unstable/cli/Argument (barrel: effect/unstable/cli) @effect/cli/ValidationError -> effect/unstable/cli/CliError (barrel: effect/unstable/cli) @@ -86,7 +84,7 @@ effect/TestClock -> effect/testing/TestClock (barrel: effect/testing) @effect/experimental/DevTools/Client -> effect/unstable/devtools/DevToolsClient (barrel: effect/unstable/devtools) @effect/experimental/DevTools/Domain -> effect/unstable/devtools/DevToolsSchema (barrel: effect/unstable/devtools) @effect/experimental/DevTools/Server -> effect/unstable/devtools/DevToolsServer (barrel: effect/unstable/devtools) -@effect/platform/MsgPack -> effect/unstable/encoding/Msgpack (barrel: effect/unstable/encoding) +@effect/platform/MsgPack -> effect/unstable/encoding/SchemaBinary (barrel: effect/unstable/encoding) @effect/platform/Ndjson -> effect/unstable/encoding/Ndjson (barrel: effect/unstable/encoding) @effect/experimental/Sse -> effect/unstable/encoding/Sse (barrel: effect/unstable/encoding) @effect/ai/AiError -> effect/unstable/ai/AiError (barrel: effect/unstable/ai) @@ -314,6 +312,7 @@ These v4 modules did not have a mapped v3 module. Treat them as v4-only unless a more specific migration guide says otherwise. ```text +@effect/platform-node/NodeMultipartParser (barrel: @effect/platform-node) effect/ErrorReporter (barrel: effect) effect/Filter (barrel: effect) effect/JsonPatch (barrel: effect) @@ -324,7 +323,6 @@ effect/Optic (barrel: effect) effect/Pull (barrel: effect) effect/SchemaGetter (barrel: effect) effect/SchemaRepresentation (barrel: effect) -effect/SchemaUtils (barrel: effect) effect/Semaphore (barrel: effect) effect/Stdio (barrel: effect) effect/TxChunk (barrel: effect) @@ -341,11 +339,9 @@ effect/unstable/eventlog/EventLogSessionAuth (barrel: effect/unstable/eventlog) effect/unstable/eventlog/SqlEventLogServerUnencrypted (barrel: effect/unstable/eventlog) effect/unstable/http/FindMyWay (barrel: effect/unstable/http) effect/unstable/http/HttpStaticServer (barrel: effect/unstable/http) -effect/unstable/http/Multipasta (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/HeadersParser (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/Node (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/Search (barrel: effect/unstable/http) -effect/unstable/http/Multipasta/Web (barrel: effect/unstable/http) +effect/unstable/http/MultipartParser (barrel: effect/unstable/http) +effect/unstable/http/MultipartParser/HeadersParser (barrel: effect/unstable/http) +effect/unstable/http/MultipartParser/Search (barrel: effect/unstable/http) effect/unstable/httpapi/HttpApiTest (barrel: effect/unstable/httpapi) effect/unstable/observability/PrometheusMetrics (barrel: effect/unstable/observability) effect/unstable/persistence/Redis (barrel: effect/unstable/persistence) @@ -359,63 +355,16389 @@ effect/unstable/reactivity/Hydration (barrel: effect/unstable/reactivity) effect/unstable/rpc/Utils (barrel: effect/unstable/rpc) ``` -## API Renames +## Removed Modules -Each line is `v3 API -> v4 API`. Use these mappings when rewriting renamed -symbols from v3 source code to v4. +- `@effect/ai` -> `effect/unstable/ai`: The @effect/ai package was merged into the effect package; import the effect/unstable/ai barrel or import specific modules directly (e.g. effect/unstable/ai/\). +- `@effect/ai-amazon-bedrock` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4 with no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockClient` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4, so AmazonBedrockClient, layer, layerConfig, make, and Service have no direct replacements. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockConfig` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4, so AmazonBedrockConfig has no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockLanguageModel` -> `none`: The @effect/ai-amazon-bedrock language-model integration was removed from v4. Use another supported v4 provider or implement LanguageModel.LanguageModel with @aws-sdk/client-bedrock-runtime. +- `@effect/ai-amazon-bedrock/AmazonBedrockSchema` -> `none`: The @effect/ai-amazon-bedrock package was removed from v4, including its hand-written Bedrock schemas. Use @aws-sdk/client-bedrock-runtime request and response types, or schemas supplied by a custom v4 provider integration. +- `@effect/ai-amazon-bedrock/AmazonBedrockTool` -> `none`: The @effect/ai-amazon-bedrock package and its Anthropic-on-Bedrock provider tools were removed from v4. Recreate the capability in a custom provider integration if the Bedrock model still requires it. +- `@effect/ai-amazon-bedrock/EventStreamEncoding` -> `none`: The @effect/ai-amazon-bedrock package and its AWS event-stream decoder were removed from v4. Use the AWS SDK's Bedrock Runtime streaming support or implement decoding in a custom provider client. +- `@effect/ai-amazon-bedrock/index` -> `none`: The @effect/ai-amazon-bedrock provider package was removed from v4 with no direct replacement. Use @aws-sdk/client-bedrock-runtime directly or build a custom v4 provider integration. +- `@effect/ai-anthropic/AnthropicTokenizer`: No single module replacement; follow the curated per-API guidance below. +- `@effect/ai-anthropic/index` -> `@effect/ai-anthropic`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-anthropic package root or import specific modules directly. +- `@effect/ai-google` -> `none`: The @effect/ai-google provider package was removed from v4 with no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-google/Generated` -> `none`: The @effect/ai-google package was removed from v4, so this generated Google API schema has no Effect v4 replacement. Use Google's current SDK/API types directly or route supported Gemini models through another v4 provider integration. +- `@effect/ai-google/GoogleClient` -> `none`: The @effect/ai-google provider package was removed from v4 and has no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-google/GoogleConfig` -> `none`: The @effect/ai-google provider package was removed from v4 and has no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-google/GoogleLanguageModel` -> `none`: The @effect/ai-google language-model integration was removed from v4. Use a supported v4 provider integration for Gemini models or implement LanguageModel.LanguageModel against Google's current SDK. +- `@effect/ai-google/GoogleTool` -> `none`: The @effect/ai-google package and its provider-defined tools were removed from v4. Model this capability in the provider integration you adopt, or define an application Tool when the replacement provider supports it. +- `@effect/ai-google/index` -> `none`: The @effect/ai-google provider package was removed from v4 with no direct replacement. Use a supported v4 provider integration for Gemini models or integrate Google's current SDK directly. +- `@effect/ai-openai/OpenAiTokenizer`: No single module replacement; follow the curated per-API guidance below. +- `@effect/ai-openai/index` -> `@effect/ai-openai`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-openai package root or import specific modules directly. +- `@effect/ai-openrouter/index` -> `@effect/ai-openrouter`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/ai-openrouter package root or import specific modules directly. +- `@effect/ai/AiError` -> `effect/unstable/ai/AiError` +- `@effect/ai/Chat` -> `effect/unstable/ai/Chat` +- `@effect/ai/EmbeddingModel` -> `effect/unstable/ai/EmbeddingModel` +- `@effect/ai/IdGenerator` -> `effect/unstable/ai/IdGenerator` +- `@effect/ai/LanguageModel` -> `effect/unstable/ai/LanguageModel` +- `@effect/ai/McpSchema` -> `effect/unstable/ai/McpSchema` +- `@effect/ai/McpServer` -> `effect/unstable/ai/McpServer` +- `@effect/ai/Model` -> `effect/unstable/ai/Model` +- `@effect/ai/Prompt` -> `effect/unstable/ai/Prompt` +- `@effect/ai/Response` -> `effect/unstable/ai/Response` +- `@effect/ai/Telemetry` -> `effect/unstable/ai/Telemetry` +- `@effect/ai/Tokenizer` -> `effect/unstable/ai/Tokenizer` +- `@effect/ai/Tool` -> `effect/unstable/ai/Tool` +- `@effect/ai/Toolkit` -> `effect/unstable/ai/Toolkit` +- `@effect/ai/index` -> `effect/unstable/ai`: The package barrel was removed; import the same namespaces from the effect/unstable/ai barrel or import specific modules directly. +- `@effect/cli` -> `effect/unstable/cli`: The @effect/cli package was merged into the effect package; import the effect/unstable/cli barrel or import specific modules directly (e.g. effect/unstable/cli/\). +- `@effect/cli/Args` -> `effect/unstable/cli/Argument` +- `@effect/cli/AutoCorrect` -> `none`: V4 suggestion distance is internal and fixed; the public configurable distance helper was removed. +- `@effect/cli/BuiltInOptions` -> `effect/unstable/cli/GlobalFlag` +- `@effect/cli/CliApp`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/CliConfig`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/Command` -> `effect/unstable/cli/Command` +- `@effect/cli/CommandDescriptor` -> `effect/unstable/cli/Completions` +- `@effect/cli/CommandDirective`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/ConfigFile`: No single module replacement; follow the curated per-API guidance below. +- `@effect/cli/HelpDoc` -> `effect/unstable/cli/HelpDoc` +- `@effect/cli/HelpDoc/Span` -> `none`: The Span ADT was removed; v4 help fields are strings and terminal styling is owned by CliOutput. +- `@effect/cli/Options` -> `effect/unstable/cli/Flag` +- `@effect/cli/Primitive` -> `effect/unstable/cli/Primitive` +- `@effect/cli/Prompt` -> `effect/unstable/cli/Prompt` +- `@effect/cli/Usage` -> `none`: The Usage ADT was removed; Command builds a plain HelpDoc.usage string internally. +- `@effect/cli/ValidationError` -> `effect/unstable/cli/CliError` +- `@effect/cli/index` -> `effect/unstable/cli`: The package barrel was removed; import the same namespaces from the effect/unstable/cli barrel or import specific modules directly. +- `@effect/cluster` -> `effect/unstable/cluster`: The @effect/cluster package was merged into the effect package; import the effect/unstable/cluster barrel or import specific modules directly (e.g. effect/unstable/cluster/\). +- `@effect/cluster/ClusterCron` -> `effect/unstable/cluster/ClusterCron` +- `@effect/cluster/ClusterError` -> `effect/unstable/cluster/ClusterError` +- `@effect/cluster/ClusterMetrics` -> `effect/unstable/cluster/ClusterMetrics` +- `@effect/cluster/ClusterSchema` -> `effect/unstable/cluster/ClusterSchema` +- `@effect/cluster/ClusterWorkflowEngine` -> `effect/unstable/cluster/ClusterWorkflowEngine` +- `@effect/cluster/DeliverAt` -> `effect/unstable/cluster/DeliverAt` +- `@effect/cluster/Entity` -> `effect/unstable/cluster/Entity` +- `@effect/cluster/EntityAddress` -> `effect/unstable/cluster/EntityAddress` +- `@effect/cluster/EntityId` -> `effect/unstable/cluster/EntityId` +- `@effect/cluster/EntityProxy` -> `effect/unstable/cluster/EntityProxy` +- `@effect/cluster/EntityProxyServer` -> `effect/unstable/cluster/EntityProxyServer` +- `@effect/cluster/EntityResource` -> `effect/unstable/cluster/EntityResource` +- `@effect/cluster/EntityType` -> `effect/unstable/cluster/EntityType` +- `@effect/cluster/Envelope` -> `effect/unstable/cluster/Envelope` +- `@effect/cluster/HttpRunner` -> `effect/unstable/cluster/HttpRunner` +- `@effect/cluster/K8sHttpClient` -> `effect/unstable/cluster/K8sHttpClient` +- `@effect/cluster/MachineId` -> `effect/unstable/cluster/MachineId` +- `@effect/cluster/Message` -> `effect/unstable/cluster/Message` +- `@effect/cluster/MessageStorage` -> `effect/unstable/cluster/MessageStorage` +- `@effect/cluster/Reply` -> `effect/unstable/cluster/Reply` +- `@effect/cluster/Runner` -> `effect/unstable/cluster/Runner` +- `@effect/cluster/RunnerAddress` -> `effect/unstable/cluster/RunnerAddress` +- `@effect/cluster/RunnerHealth` -> `effect/unstable/cluster/RunnerHealth` +- `@effect/cluster/RunnerServer` -> `effect/unstable/cluster/RunnerServer` +- `@effect/cluster/RunnerStorage` -> `effect/unstable/cluster/RunnerStorage` +- `@effect/cluster/Runners` -> `effect/unstable/cluster/Runners` +- `@effect/cluster/ShardId` -> `effect/unstable/cluster/ShardId` +- `@effect/cluster/Sharding` -> `effect/unstable/cluster/Sharding` +- `@effect/cluster/ShardingConfig` -> `effect/unstable/cluster/ShardingConfig` +- `@effect/cluster/ShardingRegistrationEvent` -> `effect/unstable/cluster/ShardingRegistrationEvent` +- `@effect/cluster/SingleRunner` -> `effect/unstable/cluster/SingleRunner` +- `@effect/cluster/Singleton` -> `effect/unstable/cluster/Singleton` +- `@effect/cluster/SingletonAddress` -> `effect/unstable/cluster/SingletonAddress` +- `@effect/cluster/Snowflake` -> `effect/unstable/cluster/Snowflake` +- `@effect/cluster/SocketRunner` -> `effect/unstable/cluster/SocketRunner` +- `@effect/cluster/SqlMessageStorage` -> `effect/unstable/cluster/SqlMessageStorage` +- `@effect/cluster/SqlRunnerStorage` -> `effect/unstable/cluster/SqlRunnerStorage` +- `@effect/cluster/TestRunner` -> `effect/unstable/cluster/TestRunner` +- `@effect/cluster/index` -> `effect/unstable/cluster`: The package barrel was removed; import the same namespaces from the effect/unstable/cluster barrel or import specific modules directly. +- `@effect/experimental` -> `none`: The @effect/experimental package was folded into the effect package, split across effect/unstable/\* (devtools, eventlog, persistence, reactivity, ...); follow the Import Map for each module. +- `@effect/experimental/DevTools` -> `effect/unstable/devtools/DevTools` +- `@effect/experimental/DevTools/Client` -> `effect/unstable/devtools/DevToolsClient` +- `@effect/experimental/DevTools/Domain` -> `effect/unstable/devtools/DevToolsSchema` +- `@effect/experimental/DevTools/Server` -> `effect/unstable/devtools/DevToolsServer` +- `@effect/experimental/Event` -> `effect/unstable/eventlog/Event` +- `@effect/experimental/EventGroup` -> `effect/unstable/eventlog/EventGroup` +- `@effect/experimental/EventJournal` -> `effect/unstable/eventlog/EventJournal` +- `@effect/experimental/EventLog` -> `effect/unstable/eventlog/EventLog` +- `@effect/experimental/EventLogEncryption` -> `effect/unstable/eventlog/EventLogEncryption` +- `@effect/experimental/EventLogRemote` -> `effect/unstable/eventlog/EventLogMessage`, `effect/unstable/eventlog/EventLogRemote` +- `@effect/experimental/EventLogServer` -> `effect/unstable/eventlog/EventLogServer`, `effect/unstable/eventlog/EventLogServerEncrypted` +- `@effect/experimental/EventLogServer/Cloudflare` -> `none`: The Cloudflare adapter was not ported; combine EventLogServerEncrypted.layer with a custom Durable Object RpcServer.Protocol adapter. +- `@effect/experimental/Machine` -> `none`: The experimental local Machine actor runtime, model, boot process, constructors, brands, and serializable variants were not ported to v4. Redesign request contracts with Rpc/RpcGroup and choose Cluster Entity, Workflow, or a local actor built from Queue, Ref, PubSub, and scoped fibers according to the required semantics; ClusterWorkflowEngine is a different durable Workflow abstraction. For serializable actors, define schemas with Rpc/RpcGroup and choose Cluster Entity or Workflow explicitly. Context and initialization helpers (including the serializable initialization contract), input/private/public/state extractors, and the Machine-specific handler context were also removed, so request handling and state management must be explicit. Use ordinary Effect tracing controls and Effect.retry instead of the removed Machine-specific wrappers; its defect wrapper was also removed. Snapshot restoration was not ported, so implement persistence explicitly for the replacement architecture. +- `@effect/experimental/Machine/Procedure` -> `none`: The stateful Machine Procedure model, its serializable variant and guard, and both Procedure brands were not ported to v4. Define request contracts with Rpc (using schemas for serializable procedures) and implement state handling in an explicit actor architecture, because Rpc provides only the request contract. The handler context, context and request extractors, and no-reply sentinel were removed; use the corresponding Rpc request types after redesigning the contract. Replace the removed tagged-request base and helpers with schema-backed Rpc requests and Rpc helper types where appropriate. +- `@effect/experimental/Machine/ProcedureList` -> `none`: The stateful Machine ProcedureList abstraction and brand were not ported to v4. RpcGroup is the closest protocol collection for its schema-backed operations, but it has no initial state or public/private visibility split. Implement state handling and initialization in the replacement actor or workflow, and enforce visibility in that architecture. +- `@effect/experimental/Machine/SerializableProcedureList` -> `none`: The serializable stateful ProcedureList abstraction was not ported to v4. RpcGroup is the closest protocol collection for its schema-backed operations, but it has no initial state or public/private visibility split. Implement state handling and initialization in the replacement actor or workflow, and enforce visibility in that architecture. +- `@effect/experimental/PersistedCache` -> `effect/unstable/persistence/PersistedCache` +- `@effect/experimental/PersistedQueue` -> `effect/unstable/persistence/PersistedQueue` +- `@effect/experimental/PersistedQueue/Redis`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/Persistence` -> `effect/unstable/persistence/Persistable`, `effect/unstable/persistence/Persistence` +- `@effect/experimental/Persistence/Lmdb` -> `none`: The LMDB backend was not ported; implement a custom BackingPersistence layer or use a supported Kvs, Redis, or SQL backend. +- `@effect/experimental/Persistence/Redis`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/RateLimiter` -> `effect/unstable/persistence/RateLimiter` +- `@effect/experimental/RateLimiter/Redis`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/Reactivity` -> `effect/unstable/reactivity/Reactivity` +- `@effect/experimental/RequestResolver`: No single module replacement; follow the curated per-API guidance below. +- `@effect/experimental/Sse` -> `effect/unstable/encoding/Sse` +- `@effect/experimental/VariantSchema` -> `effect/unstable/schema/VariantSchema` +- `@effect/experimental/index` -> `none`: The package barrel was removed along with the package; import each module from its new effect/unstable/\* location per the Import Map. +- `@effect/opentelemetry/Logger`: No single module replacement; follow the curated per-API guidance below. +- `@effect/opentelemetry/Metrics`: No single module replacement; follow the curated per-API guidance below. +- `@effect/opentelemetry/Otlp` -> `effect/unstable/observability/Otlp` +- `@effect/opentelemetry/OtlpLogger` -> `effect/unstable/observability/OtlpLogger` +- `@effect/opentelemetry/OtlpMetrics` -> `effect/unstable/observability/OtlpMetrics` +- `@effect/opentelemetry/OtlpResource` -> `effect/unstable/observability/OtlpResource` +- `@effect/opentelemetry/OtlpSerialization` -> `effect/unstable/observability/OtlpSerialization` +- `@effect/opentelemetry/OtlpTracer` -> `effect/unstable/observability/OtlpTracer` +- `@effect/opentelemetry/Tracer`: No single module replacement; follow the curated per-API guidance below. +- `@effect/opentelemetry/index` -> `@effect/opentelemetry`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/opentelemetry package root or import specific modules directly. +- `@effect/platform` -> `none`: The @effect/platform package was folded into the effect package: core services live in effect root modules (e.g. effect/FileSystem, effect/Path) and HTTP in effect/unstable/http; follow the Import Map for each module. +- `@effect/platform-browser/index` -> `@effect/platform-browser`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/platform-browser package root or import specific modules directly. +- `@effect/platform-bun/BunCommandExecutor`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-bun/BunContext`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-bun/BunFileSystem/ParcelWatcher`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-bun/BunKeyValueStore` -> `effect/unstable/persistence/KeyValueStore`: layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via BunServices.layer or BunFileSystem.layer with BunPath.layer. +- `@effect/platform-bun/index` -> `@effect/platform-bun`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/platform-bun package root or import specific modules directly. +- `@effect/platform-node-shared/NodeCommandExecutor`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node-shared/NodeFileSystem/ParcelWatcher`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node-shared/NodeKeyValueStore` -> `effect/unstable/persistence/KeyValueStore`: layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via NodeServices.layer or NodeFileSystem.layer with NodePath.layer. +- `@effect/platform-node-shared/NodeMultipart`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeCommandExecutor`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeContext`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeFileSystem/ParcelWatcher`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform-node/NodeKeyValueStore` -> `effect/unstable/persistence/KeyValueStore`: layerFileSystem is now platform-neutral as KeyValueStore.layerFileSystem(directory); provide FileSystem and Path via NodeServices.layer or NodeFileSystem.layer with NodePath.layer. +- `@effect/platform-node/index` -> `@effect/platform-node`: The explicit /index entrypoint was removed; import Node-prefixed namespaces from the @effect/platform-node package root or import specific modules directly. Undici is no longer in the root barrel; import it from @effect/platform-node/Undici or directly from undici. +- `@effect/platform/ChannelSchema` -> `effect/ChannelSchema` +- `@effect/platform/Command` -> `effect/unstable/process/ChildProcess` +- `@effect/platform/CommandExecutor` -> `effect/unstable/process/ChildProcessSpawner` +- `@effect/platform/Cookies` -> `effect/unstable/http/Cookies` +- `@effect/platform/Effectify` -> `effect/Effect`: effectify moved into the Effect module as Effect.effectify; the Effectify and EffectifyError type helpers live in the Effect namespace as well. +- `@effect/platform/Error` -> `effect/PlatformError` +- `@effect/platform/Etag` -> `effect/unstable/http/Etag` +- `@effect/platform/FetchHttpClient` -> `effect/unstable/http/FetchHttpClient` +- `@effect/platform/FileSystem` -> `effect/FileSystem` +- `@effect/platform/Headers` -> `effect/unstable/http/Headers` +- `@effect/platform/HttpApi` -> `effect/unstable/httpapi/HttpApi` +- `@effect/platform/HttpApiBuilder` -> `effect/unstable/httpapi/HttpApiBuilder` +- `@effect/platform/HttpApiClient` -> `effect/unstable/httpapi/HttpApiClient` +- `@effect/platform/HttpApiEndpoint` -> `effect/unstable/httpapi/HttpApiEndpoint` +- `@effect/platform/HttpApiError` -> `effect/unstable/httpapi/HttpApiError` +- `@effect/platform/HttpApiGroup` -> `effect/unstable/httpapi/HttpApiGroup` +- `@effect/platform/HttpApiMiddleware` -> `effect/unstable/httpapi/HttpApiMiddleware` +- `@effect/platform/HttpApiScalar` -> `effect/unstable/httpapi/HttpApiScalar` +- `@effect/platform/HttpApiSchema` -> `effect/unstable/httpapi/HttpApiSchema` +- `@effect/platform/HttpApiSecurity` -> `effect/unstable/httpapi/HttpApiSecurity` +- `@effect/platform/HttpApiSwagger` -> `effect/unstable/httpapi/HttpApiSwagger` +- `@effect/platform/HttpApp` -> `effect/unstable/http/HttpEffect` +- `@effect/platform/HttpBody` -> `effect/unstable/http/HttpBody` +- `@effect/platform/HttpClient` -> `effect/unstable/http/HttpClient` +- `@effect/platform/HttpClientError` -> `effect/unstable/http/HttpClientError` +- `@effect/platform/HttpClientRequest` -> `effect/unstable/http/HttpClientRequest` +- `@effect/platform/HttpClientResponse` -> `effect/unstable/http/HttpClientResponse` +- `@effect/platform/HttpIncomingMessage` -> `effect/unstable/http/HttpIncomingMessage` +- `@effect/platform/HttpLayerRouter`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform/HttpMethod` -> `effect/unstable/http/HttpMethod` +- `@effect/platform/HttpMiddleware` -> `effect/unstable/http/HttpMiddleware` +- `@effect/platform/HttpMultiplex` -> `none`: The HttpMultiplex module, value, constructor, and nominal type id were removed with no v4 counterpart. Replace them with a custom first-match Effect dispatcher requiring HttpServerRequest; initialize an empty dispatcher, then add or fold predicate/app pairs into it. Recreate header helpers with predicates over lower-cased request header values using exact equality, String.startsWith, String.endsWith, or RegExp.test; recreate host helpers with the same comparisons over request.headers.host. +- `@effect/platform/HttpPlatform` -> `effect/unstable/http/HttpPlatform` +- `@effect/platform/HttpRouter` -> `effect/unstable/http/HttpRouter` +- `@effect/platform/HttpServer` -> `effect/unstable/http/HttpServer` +- `@effect/platform/HttpServerError` -> `effect/unstable/http/HttpServerError` +- `@effect/platform/HttpServerRequest` -> `effect/unstable/http/HttpServerRequest` +- `@effect/platform/HttpServerRespondable` -> `effect/unstable/http/HttpServerRespondable` +- `@effect/platform/HttpServerResponse` -> `effect/unstable/http/HttpServerResponse` +- `@effect/platform/HttpTraceContext` -> `effect/unstable/http/HttpTraceContext` +- `@effect/platform/KeyValueStore` -> `effect/unstable/persistence/KeyValueStore` +- `@effect/platform/MsgPack` -> `effect/unstable/encoding/SchemaBinary`: MessagePack support was removed. Schema-aware encode, decode, and duplex now live on SchemaBinary. Untyped MessagePack of unknown values has no replacement. +- `@effect/platform/Multipart` -> `effect/unstable/http/Multipart` +- `@effect/platform/Ndjson` -> `effect/unstable/encoding/Ndjson` +- `@effect/platform/OpenApi` -> `effect/unstable/httpapi/OpenApi` +- `@effect/platform/OpenApiJsonSchema`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform/Path` -> `effect/Path` +- `@effect/platform/PlatformConfigProvider`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform/PlatformLogger` -> `effect/Logger`: toFile moved to Logger.toFile; it still requires a FileSystem service (e.g. NodeFileSystem.layer) and Scope. +- `@effect/platform/Runtime`: No single module replacement; follow the curated per-API guidance below. +- `@effect/platform/Socket` -> `effect/unstable/socket/Socket` +- `@effect/platform/SocketServer` -> `effect/unstable/socket/SocketServer` +- `@effect/platform/Template` -> `effect/unstable/http/Template` +- `@effect/platform/Terminal` -> `effect/Terminal` +- `@effect/platform/Transferable` -> `effect/unstable/workers/Transferable` +- `@effect/platform/Url` -> `effect/unstable/http/Url` +- `@effect/platform/UrlParams` -> `effect/unstable/http/UrlParams` +- `@effect/platform/Worker` -> `effect/unstable/workers/Worker` +- `@effect/platform/WorkerError` -> `effect/unstable/workers/WorkerError` +- `@effect/platform/WorkerRunner` -> `effect/unstable/workers/WorkerRunner` +- `@effect/platform/index` -> `none`: The package barrel was removed along with the package; import each module from its new effect location (e.g. effect/FileSystem, effect/unstable/http/HttpClient) per the Import Map. +- `@effect/printer` -> `none`: The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required. +- `@effect/printer-ansi` -> `none`: The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required. +- `@effect/printer-ansi/Ansi` -> `none`: The @effect/printer-ansi package was removed in v4 with no public replacement. Use a maintained ANSI library or local escape-string helpers; the v4 CLI ANSI helpers are internal and cannot be imported. +- `@effect/printer-ansi/AnsiDoc` -> `none`: The @effect/printer-ansi package and its annotated document algebra were removed in v4. Use strings or another pretty-printing library; for Effect CLI help only, use HelpDoc with CliOutput from effect/unstable/cli. +- `@effect/printer-ansi/Color` -> `none`: The @effect/printer-ansi package was removed in v4, and Effect no longer provides a public ANSI color ADT. Use a maintained ANSI library or local escape-string helpers. +- `@effect/printer-ansi/index` -> `none`: The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required. +- `@effect/printer/Doc` -> `none`: The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required. +- `@effect/printer/DocStream` -> `none`: The @effect/printer package and its laid-out DocStream intermediate representation were removed in v4. Use a target-specific renderer or another pretty-printing library. +- `@effect/printer/DocTree` -> `none`: The @effect/printer package and its structured DocTree rendering representation were removed in v4. Use a target-specific tree and renderer or another pretty-printing library. +- `@effect/printer/Flatten` -> `none`: This printer-specific flattening result was removed with the @effect/printer document algebra in v4 and has no direct replacement. +- `@effect/printer/Layout` -> `none`: The @effect/printer layout pipeline was removed in v4 with no general replacement. Use another pretty-printing library; for Effect CLI output only, use CliOutput from effect/unstable/cli. +- `@effect/printer/Optimize` -> `none`: The @effect/printer document optimizer was removed with the document algebra in v4. String-based output needs no equivalent optimization stage. +- `@effect/printer/PageWidth` -> `none`: The @effect/printer page-width layout model was removed in v4 with no direct replacement. Use Terminal.columns for terminal dimensions, or another pretty-printing library for page-width-aware layout. +- `@effect/printer/index` -> `none`: The @effect/printer document algebra was removed in v4 with no direct replacement. Use strings and joins for simple output, or adopt another pretty-printing library when adaptive layout is required. +- `@effect/rpc` -> `effect/unstable/rpc`: The @effect/rpc package was merged into the effect package; import the effect/unstable/rpc barrel or import specific modules directly (e.g. effect/unstable/rpc/\). +- `@effect/rpc/Rpc` -> `effect/unstable/rpc/Rpc` +- `@effect/rpc/RpcClient` -> `effect/unstable/rpc/RpcClient` +- `@effect/rpc/RpcClientError` -> `effect/unstable/rpc/RpcClientError` +- `@effect/rpc/RpcGroup` -> `effect/unstable/rpc/RpcGroup` +- `@effect/rpc/RpcMessage` -> `effect/unstable/rpc/RpcMessage` +- `@effect/rpc/RpcMiddleware` -> `effect/unstable/rpc/RpcMiddleware` +- `@effect/rpc/RpcSchema` -> `effect/unstable/rpc/RpcSchema` +- `@effect/rpc/RpcSerialization` -> `effect/unstable/rpc/RpcSerialization` +- `@effect/rpc/RpcServer` -> `effect/unstable/rpc/RpcServer` +- `@effect/rpc/RpcTest` -> `effect/unstable/rpc/RpcTest` +- `@effect/rpc/RpcWorker` -> `effect/unstable/rpc/RpcWorker` +- `@effect/rpc/index` -> `effect/unstable/rpc`: The package barrel was removed; import the same namespaces from the effect/unstable/rpc barrel or import specific modules directly. +- `@effect/sql` -> `effect/unstable/sql`: The @effect/sql package was merged into the effect package; import the effect/unstable/sql barrel or import specific modules directly (e.g. effect/unstable/sql/\). +- `@effect/sql-clickhouse/index` -> `@effect/sql-clickhouse`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-clickhouse package root or import specific modules directly. +- `@effect/sql-d1/index` -> `@effect/sql-d1`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-d1 package root or import specific modules directly. +- `@effect/sql-drizzle/Mysql`: No single module replacement; follow the curated per-API guidance below. +- `@effect/sql-drizzle/Pg`: No single module replacement; follow the curated per-API guidance below. +- `@effect/sql-drizzle/Sqlite`: No single module replacement; follow the curated per-API guidance below. +- `@effect/sql-kysely/Kysely` -> `none`: The Effect integration was removed. Use native kysely#Kysely, explicitly wrap promise execution with Effect.tryPromise, and define an application service if needed. No Effect-native equivalent remains; construct native new Kysely(config) and explicitly wrap builder execution and errors with Effect.tryPromise. +- `@effect/sql-kysely/Mssql` -> `none`: The integration was removed; use native Kysely with MssqlDialect and wrap promises, or rewrite against @effect/sql-mssql for Effect-native queries. +- `@effect/sql-kysely/Mysql` -> `none`: The integration was removed; use native Kysely with MysqlDialect and wrap promises, or rewrite against @effect/sql-mysql2 for Effect-native queries. +- `@effect/sql-kysely/Pg` -> `none`: The integration was removed; use native Kysely with PostgresDialect and wrap promises, or rewrite against @effect/sql-pg for Effect-native queries. +- `@effect/sql-kysely/Sqlite` -> `none`: The integration was removed; use native Kysely with SqliteDialect and wrap promises, or rewrite against a matching @effect/sql-sqlite-\* client. +- `@effect/sql-kysely/patch.types` -> `none`: The @effect/sql-kysely package was removed in v4 along with its kysely type patches; depend on native kysely types directly and wrap query execution with Effect.tryPromise. +- `@effect/sql-libsql/index` -> `@effect/sql-libsql`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-libsql package root or import specific modules directly. +- `@effect/sql-mssql/index` -> `@effect/sql-mssql`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-mssql package root or import specific modules directly. +- `@effect/sql-mysql2/index` -> `@effect/sql-mysql2`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-mysql2 package root or import specific modules directly. +- `@effect/sql-pg/index` -> `@effect/sql-pg`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-pg package root or import specific modules directly. +- `@effect/sql-sqlite-bun/index` -> `@effect/sql-sqlite-bun`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-bun package root or import specific modules directly. +- `@effect/sql-sqlite-do/index` -> `@effect/sql-sqlite-do`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-do package root or import specific modules directly. +- `@effect/sql-sqlite-node/index` -> `@effect/sql-sqlite-node`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-node package root or import specific modules directly. +- `@effect/sql-sqlite-react-native/index` -> `@effect/sql-sqlite-react-native`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-react-native package root or import specific modules directly. +- `@effect/sql-sqlite-wasm/index` -> `@effect/sql-sqlite-wasm`: The explicit /index entrypoint was removed; import the same namespaces from the @effect/sql-sqlite-wasm package root or import specific modules directly. +- `@effect/sql/Migrator` -> `effect/unstable/sql/Migrator` +- `@effect/sql/Migrator/FileSystem` -> `effect/unstable/sql/Migrator`: fromFileSystem was merged into the main Migrator module with the same (directory) signature; use Migrator.fromFileSystem as the loader. +- `@effect/sql/Model` -> `effect/unstable/schema/Model`, `effect/unstable/sql/SqlModel` +- `@effect/sql/SqlClient` -> `effect/unstable/sql/SqlClient` +- `@effect/sql/SqlConnection` -> `effect/unstable/sql/SqlConnection` +- `@effect/sql/SqlError` -> `effect/unstable/sql/SqlError` +- `@effect/sql/SqlEventJournal` -> `effect/unstable/eventlog/SqlEventJournal` +- `@effect/sql/SqlEventLogServer` -> `effect/unstable/eventlog/SqlEventLogServerEncrypted` +- `@effect/sql/SqlPersistedQueue`: No single module replacement; follow the curated per-API guidance below. +- `@effect/sql/SqlResolver` -> `effect/unstable/sql/SqlResolver` +- `@effect/sql/SqlSchema` -> `effect/unstable/sql/SqlSchema` +- `@effect/sql/SqlStream` -> `effect/unstable/sql/SqlStream` +- `@effect/sql/Statement` -> `effect/unstable/sql/Statement` +- `@effect/sql/index` -> `effect/unstable/sql`: The package barrel was removed; import the same namespaces from the effect/unstable/sql barrel or import specific modules directly. +- `@effect/typeclass` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite abstractions against the concrete v4 data type and its module functions. +- `@effect/typeclass/Alternative` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Applicative` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Bicovariant` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Bounded`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/Chainable` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Contravariant` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Coproduct` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Covariant` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Filterable` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/FlatMap` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Foldable` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Invariant` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Monad` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Monoid` -> `effect/Reducer` +- `@effect/typeclass/Of` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Pointed` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Product` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/SemiAlternative` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/SemiApplicative` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/SemiCoproduct` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/SemiProduct` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/Semigroup` -> `effect/Combiner` +- `@effect/typeclass/Traversable` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/TraversableFilterable` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/data/Array`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/BigInt`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Boolean`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Duration`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Effect`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Either`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Identity` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. +- `@effect/typeclass/data/Micro`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Number`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Option`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Ordering`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Predicate`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Record`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/String`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/data/Tuple`: No single module replacement; follow the curated per-API guidance below. +- `@effect/typeclass/index` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite abstractions against the concrete v4 data type and its module functions. +- `@effect/vitest/index`: No single module replacement; follow the curated per-API guidance below. +- `@effect/workflow` -> `effect/unstable/workflow`: The @effect/workflow package was merged into the effect package; import the effect/unstable/workflow barrel or import specific modules directly (e.g. effect/unstable/workflow/\). +- `@effect/workflow/Activity` -> `effect/unstable/workflow/Activity` +- `@effect/workflow/DurableClock` -> `effect/unstable/workflow/DurableClock` +- `@effect/workflow/DurableDeferred` -> `effect/unstable/workflow/DurableDeferred` +- `@effect/workflow/DurableQueue` -> `effect/unstable/workflow/DurableQueue` +- `@effect/workflow/DurableRateLimiter` -> `none`: Not ported. Build an Activity whose execute uses persistence RateLimiter.consume with onExceeded: delay, then sleeps for the returned delay with DurableClock. +- `@effect/workflow/Workflow` -> `effect/unstable/workflow/Workflow` +- `@effect/workflow/WorkflowEngine` -> `effect/unstable/workflow/WorkflowEngine` +- `@effect/workflow/WorkflowProxy` -> `effect/unstable/workflow/WorkflowProxy` +- `@effect/workflow/WorkflowProxyServer` -> `effect/unstable/workflow/WorkflowProxyServer` +- `effect/Arbitrary` -> `effect/unstable/arbitrary/Arbitrary`: Schema-derived generation moved to the native Arbitrary module. Effect no longer bridges to fast-check. +- `effect/ChildExecutorDecision` -> `none`: Removed with the v3 channel executor and Channel.concatMapWithCustom. Choose Channel.flatMap, Channel.switchMap, or Channel.mergeAll instead; v4 exposes no child-executor decision ADT. +- `effect/ConfigError`: No single module replacement; follow the curated per-API guidance below. +- `effect/ConfigProviderPathPatch`: No single module replacement; follow the curated per-API guidance below. +- `effect/DefaultServices`: No single module replacement; follow the curated per-API guidance below. +- `effect/Either` -> `effect/Result` +- `effect/ExecutionStrategy`: No single module replacement; follow the curated per-API guidance below. +- `effect/FastCheck` -> `fast-check`: Effect no longer re-exports fast-check. Depend on the fast-check package and import it directly. For Schema-derived generation, use Arbitrary.schema from effect/unstable/arbitrary. +- `effect/FiberId`: No single module replacement; follow the curated per-API guidance below. +- `effect/FiberRef` -> `effect/References` +- `effect/FiberRefs`: No single module replacement; follow the curated per-API guidance below. +- `effect/FiberRefsPatch`: No single module replacement; follow the curated per-API guidance below. +- `effect/FiberStatus`: No single module replacement; follow the curated per-API guidance below. +- `effect/GlobalValue`: No single module replacement; follow the curated per-API guidance below. +- `effect/GroupBy`: No single module replacement; follow the curated per-API guidance below. +- `effect/JSONSchema` -> `effect/JsonSchema` +- `effect/KeyedPool`: No single module replacement; follow the curated per-API guidance below. +- `effect/List`: No single module replacement; follow the curated per-API guidance below. +- `effect/LogSpan`: No single module replacement; follow the curated per-API guidance below. +- `effect/Mailbox`: No single module replacement; follow the curated per-API guidance below. +- `effect/MergeDecision`: No single module replacement; follow the curated per-API guidance below. +- `effect/MergeState` -> `none`: Internal execution state of the removed Channel.mergeWith implementation. V4 Channel.merge manages its fibers and queues internally and exposes only a haltStrategy option. +- `effect/MergeStrategy`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricBoundaries`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricHook`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricKey`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricKeyType`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricLabel`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricPair`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricPolling`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricRegistry`: No single module replacement; follow the curated per-API guidance below. +- `effect/MetricState`: No single module replacement; follow the curated per-API guidance below. +- `effect/Micro`: No single module replacement; follow the curated per-API guidance below. +- `effect/ModuleVersion` -> `none`: The mutable module-version facility was removed; the v4 build version is private and effect/package.json is metadata, not an equivalent runtime API. No mutable version setter remains; the v3 runtime-isolation mechanism has no public v4 equivalent. +- `effect/MutableQueue`: No single module replacement; follow the curated per-API guidance below. +- `effect/ParseResult` -> `effect/SchemaIssue`, `effect/SchemaParser` +- `effect/Pretty`: No single module replacement; follow the curated per-API guidance below. +- `effect/RateLimiter` -> `none`: The old limit, interval, and algorithm options belonged to the removed in-process limiter; choose and configure an application limiter explicitly. The scoped in-process callable limiter was not ported to v4; effect/unstable/persistence/RateLimiter is a keyed persistence service with different semantics, not a drop-in replacement. The FiberRef-based per-effect cost annotation was removed with the core RateLimiter; pass token cost explicitly to the replacement limiter. +- `effect/Readable`: No single module replacement; follow the curated per-API guidance below. +- `effect/RedBlackTree`: No single module replacement; follow the curated per-API guidance below. +- `effect/Reloadable`: No single module replacement; follow the curated per-API guidance below. +- `effect/RequestBlock`: No single module replacement; follow the curated per-API guidance below. +- `effect/RuntimeFlags`: No single module replacement; follow the curated per-API guidance below. +- `effect/RuntimeFlagsPatch` -> `none`: The aggregate RuntimeFlagsPatch abstraction, its enabled/disabled bit sets, set operations, queries, and renderer were removed; no aggregate patch value remains to construct, combine, inspect, or render. Enable, disable, or invert the corresponding semantic behavior directly, combining semantic configurations where needed. Configure scheduler yielding, interruptibility, or metrics directly, and inspect the corresponding semantic facility when needed. +- `effect/STM`: No single module replacement; follow the curated per-API guidance below. +- `effect/ScheduleDecision`: No single module replacement; follow the curated per-API guidance below. +- `effect/ScheduleInterval` -> `none`: The public ScheduleInterval module was removed in v4. Schedule steps now express only a relative Duration; combine policies with Schedule.max or Schedule.min, or implement custom timing with Schedule.fromStep. +- `effect/ScheduleIntervals` -> `none`: The public ScheduleIntervals module was removed in v4 along with absolute interval-set decisions. Use relative Duration values in Schedule.fromStep and Schedule.max or Schedule.min for standard policy composition. +- `effect/Secret`: No single module replacement; follow the curated per-API guidance below. +- `effect/SingleProducerAsyncInput`: No single module replacement; follow the curated per-API guidance below. +- `effect/SortedMap`: No single module replacement; follow the curated per-API guidance below. +- `effect/SortedSet`: No single module replacement; follow the curated per-API guidance below. +- `effect/StreamEmit`: No single module replacement; follow the curated per-API guidance below. +- `effect/StreamHaltStrategy`: No single module replacement; follow the curated per-API guidance below. +- `effect/Streamable` -> `none`: Removed in v4 with no direct replacement. Instead of extending Streamable.Class, expose the underlying stream as a value (e.g. a property or method built with Stream.suspend). +- `effect/Subscribable`: No single module replacement; follow the curated per-API guidance below. +- `effect/Supervisor`: No single module replacement; follow the curated per-API guidance below. +- `effect/TArray`: No single module replacement; follow the curated per-API guidance below. +- `effect/TDeferred` -> `effect/TxDeferred` +- `effect/TMap` -> `effect/TxHashMap` +- `effect/TPriorityQueue` -> `effect/TxPriorityQueue` +- `effect/TPubSub` -> `effect/TxPubSub` +- `effect/TQueue` -> `effect/TxQueue` +- `effect/TRandom`: No single module replacement; follow the curated per-API guidance below. +- `effect/TReentrantLock` -> `effect/TxReentrantLock` +- `effect/TRef` -> `effect/TxRef` +- `effect/TSemaphore` -> `effect/TxSemaphore` +- `effect/TSet` -> `effect/TxHashSet` +- `effect/TSubscriptionRef` -> `effect/TxSubscriptionRef` +- `effect/TestAnnotation` -> `none`: The legacy test-runner annotation key and built-in counters were removed. Use Vitest skip/repeat/retry options for runner concerns and FiberSet for explicit fiber tracking; there is no annotation-key equivalent. +- `effect/TestAnnotationMap` -> `none`: TestAnnotationMap was removed with TestAnnotation. Use an application-owned HashMap or Ref only when arbitrary typed annotations are still required; it is not part of the v4 test runner. +- `effect/TestAnnotations` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. +- `effect/TestClock` -> `effect/testing/TestClock` +- `effect/TestConfig`: No single module replacement; follow the curated per-API guidance below. +- `effect/TestContext`: No single module replacement; follow the curated per-API guidance below. +- `effect/TestLive`: No single module replacement; follow the curated per-API guidance below. +- `effect/TestServices`: No single module replacement; follow the curated per-API guidance below. +- `effect/TestSized`: No single module replacement; follow the curated per-API guidance below. +- `effect/UpstreamPullRequest` -> `none`: Removed with Channel.concatMapWithCustom; v4 does not expose channel-executor pull-request events. Use supported flattening operators or implement exceptional behavior with Channel.fromTransform and Pull. +- `effect/UpstreamPullStrategy` -> `none`: Removed with Channel.concatMapWithCustom. Select flattening and scheduling through Channel.flatMap, Channel.switchMap, or Channel.mergeAll; v4 has no upstream-pull strategy ADT. +- `effect/index`: No single module replacement; follow the curated per-API guidance below. -```text -Effect.async -> Effect.callback -Effect.zipRight -> Effect.andThen -Effect.zipLeft -> Effect.tap -Effect.either -> Effect.result -Effect.catchAll -> Effect.catch -Effect.catchAllCause -> Effect.catchCause -Effect.catchAllDefect -> Effect.catchDefect -Effect.catchSome -> Effect.catchIf -Effect.catchIf -> Effect.catchIf -Effect.optionFromOptional -> Effect.catchNoSuchElement -Effect.catchSomeCause -> Effect.catchCauseIf -Effect.tapErrorCause -> Effect.tapCause -Effect.ignoreLogged -> Effect.ignore -Effect.makeLatchUnsafe -> Latch.makeUnsafe -Effect.makeLatch -> Latch.make -Layer.scoped -> Layer.effect -Layer.scopedDiscard -> Layer.effectDiscard -Layer.tapErrorCause -> Layer.tapCause -Mailbox -> Queue.Queue -Mailbox.make -> Queue.make -Either -> Result.Result -Either.right -> Result.succeed -Either.left -> Result.fail -Scope.extend -> Scope.provide -Effect.makeSemaphoreUnsafe -> Semaphore.makeUnsafe -Effect.makeSemaphore -> Semaphore.make -Stream.Context -> Stream.Services -StreamHaltStrategy.HaltStrategy -> Stream.HaltStrategy -Stream.repeatEffect -> Stream.fromEffectRepeat -Stream.repeatEffectWithSchedule -> Stream.fromEffectSchedule -Stream.async -> Stream.callback -Stream.asyncEffect -> Stream.callback -Stream.asyncPush -> Stream.callback -Stream.asyncScoped -> Stream.callback -Stream.repeatEffectChunk -> Stream.fromIterableEffectRepeat -Stream.fromChunk -> Stream.fromArray -Stream.fromChunks -> Stream.fromArrays -Stream.mapChunks -> Stream.mapArray -Stream.mapChunksEffect -> Stream.mapArrayEffect -Stream.either -> Stream.result -Stream.flattenChunks -> Stream.flattenArray -Stream.flattenIterables -> Stream.flattenIterable -Stream.mergeEither -> Stream.mergeResult -Stream.zipWithChunks -> Stream.zipWithArray -Stream.bufferChunks -> Stream.bufferArray -Stream.catchAllCause -> Stream.catchCause -Stream.tapErrorCause -> Stream.tapCause -Stream.catchAll -> Stream.catch -Stream.catchSome -> Stream.catchIf -Stream.catchSomeCause -> Stream.catchCauseIf -Stream.combineChunks -> Stream.combineArray -provideSomeLayer -> Stream.provide -provideSomeContext -> Stream.provide -``` +## API Reference + +### `@effect/ai-anthropic/AnthropicClient` + +- `AnthropicClient.CitationsDelta` -> `Generated.BetaCitationsDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ContentBlockDeltaEvent` -> `Generated.BetaContentBlockDeltaEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ContentBlockStartEvent` -> `Generated.BetaContentBlockStartEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ContentBlockStopEvent` -> `Generated.BetaContentBlockStopEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ErrorEvent` -> `Generated.BetaErrorResponse`: The client-local stream error schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.InputJsonContentBlockDelta` -> `Generated.BetaInputJsonContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageDelta` -> `Generated.BetaMessageDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageDeltaEvent` -> `Generated.BetaMessageDeltaEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageDeltaUsage` -> `typeof Generated.BetaMessageDeltaEvent.Type["usage"]`: The standalone usage schema was inlined into the regenerated v4 message-delta event. + +- `AnthropicClient.MessageStartEvent` -> `Generated.BetaMessageStartEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageStopEvent` -> `Generated.BetaMessageStopEvent`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.MessageStreamEvent` -> `AnthropicClient.MessageStreamEvent`: Still exported in v4 as a type union of generated beta stream events; adapt to the revised client stream contract. + +- `AnthropicClient.PingEvent` -> `none`: The v4 client consumes ping events internally and filters them from MessageStreamEvent, so no public ping schema is needed. + +- `AnthropicClient.ServerToolUsage` -> `Generated.BetaServerToolUsage`: The client-local usage schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.Service` -> `AnthropicClient.Service`: Still exported in v4; adapt to the revised generated client, streamRequest, and message response contracts. + +- `AnthropicClient.SignatureContentBlockDelta` -> `Generated.BetaSignatureContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.TextContentBlockDelta` -> `Generated.BetaTextContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +- `AnthropicClient.ThinkingContentBlockDelta` -> `Generated.BetaThinkingContentBlockDelta`: The client-local stream schema moved to the regenerated v4 Anthropic schema surface; re-check its Type/Encoded shape. + +### `@effect/ai-anthropic/AnthropicConfig` + +- `AnthropicConfig.AnthropicConfig` -> `AnthropicConfig.AnthropicConfig`: Still exported in v4; update imports and adapt to the revised v4 service and HTTP client types. + +- `AnthropicConfig.AnthropicConfig.Service` -> `AnthropicConfig.AnthropicConfig.Service`: Still exported in v4; update imports and adapt to the revised v4 service and HTTP client types. + +### `@effect/ai-anthropic/AnthropicLanguageModel` + +- `AnthropicLanguageModel.AnthropicReasoningInfo` -> `Prompt.ReasoningPartOptions / Response reasoning metadata`: The standalone reasoning-info union was removed; v4 declares Anthropic thinking and redacted-thinking data directly on Prompt and Response provider metadata. + +- `AnthropicLanguageModel.AnthropicTools` -> `AnthropicLanguageModel.AnthropicUserDefinedTool | AnthropicLanguageModel.AnthropicProviderDefinedTool`: The old combined tool union was split into explicit user-defined and provider-defined Anthropic request tool types. + +- `AnthropicLanguageModel.Config` -> `AnthropicLanguageModel.Config`: Still exported in v4; update imports and adapt to the revised Messages API request fields. + +- `AnthropicLanguageModel.Config.Service` -> `AnthropicLanguageModel.Config.Service`: Still exported in v4; update imports and adapt to the revised Messages API request fields. + +- `AnthropicLanguageModel.layerWithTokenizer` -> `AnthropicLanguageModel.layer`: The tokenizer-combining layer was removed; provide the language model and any Tokenizer service separately. + +- `AnthropicLanguageModel.modelWithTokenizer` -> `AnthropicLanguageModel.model`: The tokenizer-combining model was removed; use the v4 model descriptor and provide any Tokenizer service separately. + +- `AnthropicLanguageModel.prepareTools` -> `none`: Tool conversion became an internal part of the v4 Anthropic language model; use AnthropicTool constructors and pass tools through LanguageModel provider options instead. + +### `@effect/ai-anthropic/AnthropicTokenizer` + +- `AnthropicTokenizer.layer` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using @anthropic-ai/tokenizer if equivalent Anthropic counting is required. + +- `AnthropicTokenizer.make` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using @anthropic-ai/tokenizer if equivalent Anthropic counting is required. + +### `@effect/ai-anthropic/AnthropicTool` + +- `AnthropicTool.ProviderDefinedTools` -> `AnthropicTool.AnthropicTool`: The provider-defined schema union was replaced by the union of v4 Anthropic provider tool constructor return types. + +- `AnthropicTool.getProviderDefinedToolName` -> `Tool.NameMapper`: The Anthropic-specific name lookup was removed; v4 provider tools carry custom and provider names through the shared Tool.NameMapper. + +### `@effect/ai-anthropic/Generated` + +- `Generated.APIError` -> `Generated.APIError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuthenticationError` -> `Generated.AuthenticationError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Base64ImageSource` -> `Generated.Base64ImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Base64ImageSourceMediaType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Base64PDFSource` -> `Generated.Base64PDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BashTool20250124` -> `Generated.BashTool_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaAPIError` -> `Generated.BetaAPIError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaAuthenticationError` -> `Generated.BetaAuthenticationError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBase64ImageSource` -> `Generated.BetaBase64ImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBase64ImageSourceMediaType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaBase64PDFSource` -> `Generated.BetaBase64PDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBashCodeExecutionToolResultErrorCode` -> `Generated.BetaBashCodeExecutionToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBashTool20241022` -> `Generated.BetaBashTool_20241022`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBashTool20250124` -> `Generated.BetaBashTool_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBillingError` -> `Generated.BetaBillingError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBodyCreateSkillV1SkillsPost` -> `Generated.BetaBody_create_skill_v1_skills_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaBodyCreateSkillVersionV1SkillsSkillIdVersionsPost` -> `Generated.BetaBody_create_skill_version_v1_skills__skill_id__versions_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCacheControlEphemeral` -> `Generated.BetaCacheControlEphemeral`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCacheControlEphemeralTtl` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaCacheCreation` -> `Generated.BetaCacheCreation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaClearToolUses20250919` -> `Generated.BetaClearToolUses20250919`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCodeExecutionTool20250522` -> `Generated.BetaCodeExecutionTool_20250522`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCodeExecutionTool20250825` -> `Generated.BetaCodeExecutionTool_20250825`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCodeExecutionToolResultErrorCode` -> `Generated.BetaCodeExecutionToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaComputerUseTool20241022` -> `Generated.BetaComputerUseTool_20241022`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaComputerUseTool20250124` -> `Generated.BetaComputerUseTool_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContainer` -> `Generated.BetaContainer`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContainerParams` -> `Generated.BetaContainerParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContentBlock` -> `Generated.BetaContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContentBlockSource` -> `Generated.BetaContentBlockSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContextManagementConfig` -> `Generated.BetaContextManagementConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaContextManagementResponse` -> `Generated.BetaContextManagementResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCountMessageTokensParams` -> `Generated.BetaCountMessageTokensParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCountMessageTokensResponse` -> `Generated.BetaCountMessageTokensResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateMessageBatchParams` -> `Generated.BetaCreateMessageBatchParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateMessageParams` -> `Generated.BetaCreateMessageParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateMessageParamsServiceTier` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaCreateSkillResponse` -> `Generated.BetaCreateSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateSkillV1SkillsPostParams` -> `Generated.BetaCreateSkillV1SkillsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateSkillVersionResponse` -> `Generated.BetaCreateSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams` -> `Generated.BetaCreateSkillVersionV1SkillsSkillIdVersionsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteFileV1FilesFileIdDeleteParams` -> `Generated.BetaDeleteFileV1FilesFileIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteMessageBatchResponse` -> `Generated.BetaDeleteMessageBatchResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillResponse` -> `Generated.BetaDeleteSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillV1SkillsSkillIdDeleteParams` -> `Generated.BetaDeleteSkillV1SkillsSkillIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillVersionResponse` -> `Generated.BetaDeleteSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams` -> `Generated.BetaDeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaDownloadFileV1FilesFileIdContentGetParams` -> `Generated.BetaDownloadFileV1FilesFileIdContentGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaErrorResponse` -> `Generated.BetaErrorResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileDeleteResponse` -> `Generated.BetaFileDeleteResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileDocumentSource` -> `Generated.BetaFileDocumentSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileImageSource` -> `Generated.BetaFileImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileListResponse` -> `Generated.BetaFileListResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaFileMetadataSchema` -> `Generated.BetaFileMetadataSchema`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGatewayTimeoutError` -> `Generated.BetaGatewayTimeoutError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetFileMetadataV1FilesFileIdGetParams` -> `Generated.BetaGetFileMetadataV1FilesFileIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillResponse` -> `Generated.BetaGetSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillV1SkillsSkillIdGetParams` -> `Generated.BetaGetSkillV1SkillsSkillIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillVersionResponse` -> `Generated.BetaGetSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams` -> `Generated.BetaGetSkillVersionV1SkillsSkillIdVersionsVersionGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputContentBlock` -> `Generated.BetaInputContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputMessage` -> `Generated.BetaInputMessage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputMessageRole` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaInputSchema` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaInputTokensClearAtLeast` -> `Generated.BetaInputTokensClearAtLeast`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInputTokensTrigger` -> `Generated.BetaInputTokensTrigger`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaInvalidRequestError` -> `Generated.BetaInvalidRequestError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListFilesV1FilesGetParams` -> `Generated.BetaListFilesV1FilesGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListResponseMessageBatch` -> `Generated.BetaListResponse_MessageBatch_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListResponseModelInfo` -> `Generated.BetaListResponse_ModelInfo_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillVersionsResponse` -> `Generated.BetaListSkillVersionsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams` -> `Generated.BetaListSkillVersionsV1SkillsSkillIdVersionsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillsResponse` -> `Generated.BetaListSkillsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaListSkillsV1SkillsGetParams` -> `Generated.BetaListSkillsV1SkillsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMemoryTool20250818` -> `Generated.BetaMemoryTool_20250818`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessage` -> `Generated.BetaMessage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatch` -> `Generated.BetaMessageBatch`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchIndividualRequestParams` -> `Generated.BetaMessageBatchIndividualRequestParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchProcessingStatus` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaMessageBatchesCancelParams` -> `Generated.BetaMessageBatchesCancelParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesDeleteParams` -> `Generated.BetaMessageBatchesDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesListParams` -> `Generated.BetaMessageBatchesListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesPostParams` -> `Generated.BetaMessageBatchesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesResultsParams` -> `Generated.BetaMessageBatchesResultsParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessageBatchesRetrieveParams` -> `Generated.BetaMessageBatchesRetrieveParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessagesCountTokensPostParams` -> `Generated.BetaMessagesCountTokensPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMessagesPostParams` -> `Generated.BetaMessagesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaMetadata` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaModelInfo` -> `Generated.BetaModelInfo`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaModelsGetParams` -> `Generated.BetaModelsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaModelsListParams` -> `Generated.BetaModelsListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaNotFoundError` -> `Generated.BetaNotFoundError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaOverloadedError` -> `Generated.BetaOverloadedError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaPermissionError` -> `Generated.BetaPermissionError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaPlainTextSource` -> `Generated.BetaPlainTextSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRateLimitError` -> `Generated.BetaRateLimitError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionOutputBlock` -> `Generated.BetaRequestBashCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionResultBlock` -> `Generated.BetaRequestBashCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionToolResultBlock` -> `Generated.BetaRequestBashCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestBashCodeExecutionToolResultError` -> `Generated.BetaRequestBashCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCharLocationCitation` -> `Generated.BetaRequestCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCitationsConfig` -> `Generated.BetaRequestCitationsConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionOutputBlock` -> `Generated.BetaRequestCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionResultBlock` -> `Generated.BetaRequestCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionToolResultBlock` -> `Generated.BetaRequestCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCodeExecutionToolResultError` -> `Generated.BetaRequestCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestContainerUploadBlock` -> `Generated.BetaRequestContainerUploadBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestContentBlockLocationCitation` -> `Generated.BetaRequestContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestCounts` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestDocumentBlock` -> `Generated.BetaRequestDocumentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestImageBlock` -> `Generated.BetaRequestImageBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPServerToolConfiguration` -> `Generated.BetaRequestMCPServerToolConfiguration`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPServerURLDefinition` -> `Generated.BetaRequestMCPServerURLDefinition`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPToolResultBlock` -> `Generated.BetaRequestMCPToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestMCPToolUseBlock` -> `Generated.BetaRequestMCPToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestPageLocationCitation` -> `Generated.BetaRequestPageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestRedactedThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestSearchResultBlock` -> `Generated.BetaRequestSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestSearchResultLocationCitation` -> `Generated.BetaRequestSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestServerToolUseBlock` -> `Generated.BetaRequestServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestServerToolUseBlockName` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestTextBlock` -> `Generated.BetaRequestTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionCreateResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionCreateResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionStrReplaceResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionStrReplaceResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionToolResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionToolResultError` -> `Generated.BetaRequestTextEditorCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionViewResultBlock` -> `Generated.BetaRequestTextEditorCodeExecutionViewResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestTextEditorCodeExecutionViewResultBlockFileType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestToolResultBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestToolUseBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaRequestWebFetchResultBlock` -> `Generated.BetaRequestWebFetchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebFetchToolResultBlock` -> `Generated.BetaRequestWebFetchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebFetchToolResultError` -> `Generated.BetaRequestWebFetchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchResultBlock` -> `Generated.BetaRequestWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchResultLocationCitation` -> `Generated.BetaRequestWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchToolResultBlock` -> `Generated.BetaRequestWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaRequestWebSearchToolResultError` -> `Generated.BetaRequestWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionOutputBlock` -> `Generated.BetaResponseBashCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionResultBlock` -> `Generated.BetaResponseBashCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionToolResultBlock` -> `Generated.BetaResponseBashCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseBashCodeExecutionToolResultError` -> `Generated.BetaResponseBashCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCharLocationCitation` -> `Generated.BetaResponseCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCitationsConfig` -> `Generated.BetaResponseCitationsConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseClearToolUses20250919Edit` -> `Generated.BetaResponseClearToolUses20250919Edit`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionOutputBlock` -> `Generated.BetaResponseCodeExecutionOutputBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionResultBlock` -> `Generated.BetaResponseCodeExecutionResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionToolResultBlock` -> `Generated.BetaResponseCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseCodeExecutionToolResultError` -> `Generated.BetaResponseCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseContainerUploadBlock` -> `Generated.BetaResponseContainerUploadBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseContentBlockLocationCitation` -> `Generated.BetaResponseContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseContextManagement` -> `Generated.BetaResponseContextManagement`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseDocumentBlock` -> `Generated.BetaResponseDocumentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseMCPToolResultBlock` -> `Generated.BetaResponseMCPToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseMCPToolUseBlock` -> `Generated.BetaResponseMCPToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponsePageLocationCitation` -> `Generated.BetaResponsePageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseRedactedThinkingBlock` -> `Generated.BetaResponseRedactedThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseSearchResultLocationCitation` -> `Generated.BetaResponseSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseServerToolUseBlock` -> `Generated.BetaResponseServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseServerToolUseBlockName` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaResponseTextBlock` -> `Generated.BetaResponseTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionCreateResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionCreateResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionStrReplaceResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionStrReplaceResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionToolResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionToolResultError` -> `Generated.BetaResponseTextEditorCodeExecutionToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionViewResultBlock` -> `Generated.BetaResponseTextEditorCodeExecutionViewResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseTextEditorCodeExecutionViewResultBlockFileType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaResponseThinkingBlock` -> `Generated.BetaResponseThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseToolUseBlock` -> `Generated.BetaResponseToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebFetchResultBlock` -> `Generated.BetaResponseWebFetchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebFetchToolResultBlock` -> `Generated.BetaResponseWebFetchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebFetchToolResultError` -> `Generated.BetaResponseWebFetchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchResultBlock` -> `Generated.BetaResponseWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchResultLocationCitation` -> `Generated.BetaResponseWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchToolResultBlock` -> `Generated.BetaResponseWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaResponseWebSearchToolResultError` -> `Generated.BetaResponseWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaServerToolUsage` -> `Generated.BetaServerToolUsage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaSkill` -> `Generated.BetaSkill`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaSkillParams` -> `Generated.BetaSkillParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaSkillParamsType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaSkillType` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaSkillVersion` -> `Generated.BetaSkillVersion`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaStopReason` -> `Generated.BetaStopReason`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20241022` -> `Generated.BetaTextEditor_20241022`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20250124` -> `Generated.BetaTextEditor_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20250429` -> `Generated.BetaTextEditor_20250429`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditor20250728` -> `Generated.BetaTextEditor_20250728`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTextEditorCodeExecutionToolResultErrorCode` -> `Generated.BetaTextEditorCodeExecutionToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaThinkingConfigDisabled` -> `Generated.BetaThinkingConfigDisabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaThinkingConfigEnabled` -> `Generated.BetaThinkingConfigEnabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaThinkingConfigParam` -> `Generated.BetaThinkingConfigParam`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaTool` -> `Generated.BetaTool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoice` -> `Generated.BetaToolChoice`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceAny` -> `Generated.BetaToolChoiceAny`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceAuto` -> `Generated.BetaToolChoiceAuto`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceNone` -> `Generated.BetaToolChoiceNone`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolChoiceTool` -> `Generated.BetaToolChoiceTool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolUsesKeep` -> `Generated.BetaToolUsesKeep`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaToolUsesTrigger` -> `Generated.BetaToolUsesTrigger`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaURLImageSource` -> `Generated.BetaURLImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaURLPDFSource` -> `Generated.BetaURLPDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaUploadFileV1FilesPostParams` -> `Generated.BetaUploadFileV1FilesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaUploadFileV1FilesPostRequest` -> `Generated.BetaUploadFileV1FilesPostRequestFormData`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaUsage` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaUsageServiceTierEnum` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BetaUserLocation` -> `Generated.BetaUserLocation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebFetchTool20250910` -> `Generated.BetaWebFetchTool_20250910`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebFetchToolResultErrorCode` -> `Generated.BetaWebFetchToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebSearchTool20250305` -> `Generated.BetaWebSearchTool_20250305`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaWebSearchToolResultErrorCode` -> `Generated.BetaWebSearchToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BetaapiSchemasSkillsSkill` -> `Generated.Betaapi__schemas__skills__Skill`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BillingError` -> `Generated.BillingError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BodyCreateSkillV1SkillsPost` -> `Generated.Body_create_skill_v1_skills_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BodyCreateSkillVersionV1SkillsSkillIdVersionsPost` -> `Generated.Body_create_skill_version_v1_skills__skill_id__versions_post`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CacheControlEphemeral` -> `Generated.CacheControlEphemeral`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CacheControlEphemeralTtl` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CacheCreation` -> `Generated.CacheCreation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Client` -> `Generated.AnthropicClient`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClientError` -> `Generated.AnthropicClientError`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletePostParams` -> `Generated.CompletePostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletionRequest` -> `Generated.CompletionRequest`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletionResponse` -> `Generated.CompletionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContentBlock` -> `Generated.ContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContentBlockSource` -> `Generated.ContentBlockSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CountMessageTokensParams` -> `Generated.CountMessageTokensParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CountMessageTokensResponse` -> `Generated.CountMessageTokensResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageBatchParams` -> `Generated.CreateMessageBatchParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageParams` -> `Generated.CreateMessageParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageParamsServiceTier` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSkillResponse` -> `Generated.CreateSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSkillV1SkillsPostParams` -> `Generated.CreateSkillV1SkillsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSkillVersionResponse` -> `Generated.CreateSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSkillVersionV1SkillsSkillIdVersionsPostParams` -> `Generated.CreateSkillVersionV1SkillsSkillIdVersionsPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFileV1FilesFileIdDeleteParams` -> `Generated.DeleteFileV1FilesFileIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteMessageBatchResponse` -> `Generated.DeleteMessageBatchResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillResponse` -> `Generated.DeleteSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillV1SkillsSkillIdDeleteParams` -> `Generated.DeleteSkillV1SkillsSkillIdDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillVersionResponse` -> `Generated.DeleteSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams` -> `Generated.DeleteSkillVersionV1SkillsSkillIdVersionsVersionDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DownloadFileV1FilesFileIdContentGetParams` -> `Generated.DownloadFileV1FilesFileIdContentGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ErrorResponse` -> `Generated.ErrorResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileDeleteResponse` -> `Generated.FileDeleteResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileListResponse` -> `Generated.FileListResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileMetadataSchema` -> `Generated.FileMetadataSchema`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GatewayTimeoutError` -> `Generated.GatewayTimeoutError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetFileMetadataV1FilesFileIdGetParams` -> `Generated.GetFileMetadataV1FilesFileIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillResponse` -> `Generated.GetSkillResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillV1SkillsSkillIdGetParams` -> `Generated.GetSkillV1SkillsSkillIdGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillVersionResponse` -> `Generated.GetSkillVersionResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams` -> `Generated.GetSkillVersionV1SkillsSkillIdVersionsVersionGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputContentBlock` -> `Generated.InputContentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessage` -> `Generated.InputMessage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessageRole` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputSchema` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InvalidRequestError` -> `Generated.InvalidRequestError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFilesV1FilesGetParams` -> `Generated.ListFilesV1FilesGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListResponseMessageBatch` -> `Generated.ListResponse_MessageBatch_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListResponseModelInfo` -> `Generated.ListResponse_ModelInfo_`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillVersionsResponse` -> `Generated.ListSkillVersionsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillVersionsV1SkillsSkillIdVersionsGetParams` -> `Generated.ListSkillVersionsV1SkillsSkillIdVersionsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillsResponse` -> `Generated.ListSkillsResponse`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListSkillsV1SkillsGetParams` -> `Generated.ListSkillsV1SkillsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Message` -> `Generated.Message`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatch` -> `Generated.MessageBatch`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchIndividualRequestParams` -> `Generated.MessageBatchIndividualRequestParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchProcessingStatus` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageBatchesCancelParams` -> `Generated.MessageBatchesCancelParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesDeleteParams` -> `Generated.MessageBatchesDeleteParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesListParams` -> `Generated.MessageBatchesListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesPostParams` -> `Generated.MessageBatchesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesResultsParams` -> `Generated.MessageBatchesResultsParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageBatchesRetrieveParams` -> `Generated.MessageBatchesRetrieveParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessagesCountTokensPostParams` -> `Generated.MessagesCountTokensPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessagesPostParams` -> `Generated.MessagesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Metadata` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Model` -> `Generated.Model`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelInfo` -> `Generated.ModelInfo`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsGetParams` -> `Generated.ModelsGetParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsListParams` -> `Generated.ModelsListParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.NotFoundError` -> `Generated.NotFoundError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OverloadedError` -> `Generated.OverloadedError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PermissionError` -> `Generated.PermissionError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PlainTextSource` -> `Generated.PlainTextSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RateLimitError` -> `Generated.RateLimitError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestCharLocationCitation` -> `Generated.RequestCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestCitationsConfig` -> `Generated.RequestCitationsConfig`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestContentBlockLocationCitation` -> `Generated.RequestContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestCounts` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestDocumentBlock` -> `Generated.RequestDocumentBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestImageBlock` -> `Generated.RequestImageBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestPageLocationCitation` -> `Generated.RequestPageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestRedactedThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestSearchResultBlock` -> `Generated.RequestSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestSearchResultLocationCitation` -> `Generated.RequestSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestServerToolUseBlock` -> `Generated.RequestServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestTextBlock` -> `Generated.RequestTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestThinkingBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestToolResultBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestToolUseBlock` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestWebSearchResultBlock` -> `Generated.RequestWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestWebSearchResultLocationCitation` -> `Generated.RequestWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestWebSearchToolResultBlock` -> `Generated.RequestWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestWebSearchToolResultError` -> `Generated.RequestWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseCharLocationCitation` -> `Generated.ResponseCharLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseContentBlockLocationCitation` -> `Generated.ResponseContentBlockLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponsePageLocationCitation` -> `Generated.ResponsePageLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseRedactedThinkingBlock` -> `Generated.ResponseRedactedThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseSearchResultLocationCitation` -> `Generated.ResponseSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseServerToolUseBlock` -> `Generated.ResponseServerToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseTextBlock` -> `Generated.ResponseTextBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseThinkingBlock` -> `Generated.ResponseThinkingBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseToolUseBlock` -> `Generated.ResponseToolUseBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchResultBlock` -> `Generated.ResponseWebSearchResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchResultLocationCitation` -> `Generated.ResponseWebSearchResultLocationCitation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchToolResultBlock` -> `Generated.ResponseWebSearchToolResultBlock`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseWebSearchToolResultError` -> `Generated.ResponseWebSearchToolResultError`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ServerToolUsage` -> `Generated.ServerToolUsage`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Skill` -> `Generated.Skill`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SkillVersion` -> `Generated.SkillVersion`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.StopReason` -> `Generated.StopReason`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextEditor20250124` -> `Generated.TextEditor_20250124`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextEditor20250429` -> `Generated.TextEditor_20250429`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextEditor20250728` -> `Generated.TextEditor_20250728`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThinkingConfigDisabled` -> `Generated.ThinkingConfigDisabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThinkingConfigEnabled` -> `Generated.ThinkingConfigEnabled`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThinkingConfigParam` -> `Generated.ThinkingConfigParam`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Tool` -> `Generated.Tool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoice` -> `Generated.ToolChoice`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceAny` -> `Generated.ToolChoiceAny`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceAuto` -> `Generated.ToolChoiceAuto`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceNone` -> `Generated.ToolChoiceNone`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceTool` -> `Generated.ToolChoiceTool`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLImageSource` -> `Generated.URLImageSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLPDFSource` -> `Generated.URLPDFSource`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadFileV1FilesPostParams` -> `Generated.UploadFileV1FilesPostParams`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadFileV1FilesPostRequest` -> `Generated.UploadFileV1FilesPostRequestFormData`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageServiceTierEnum` -> `none`: Removed or inlined when the Anthropic specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserLocation` -> `Generated.UserLocation`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchTool20250305` -> `Generated.WebSearchTool_20250305`: Regenerated in v4 under this name; update the identifier and re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchToolResultErrorCode` -> `Generated.WebSearchToolResultErrorCode`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.make` -> `Generated.make`: Still generated in v4 from the current Anthropic specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +### `@effect/ai-openai/Generated` + +- `Generated.ActiveStatus` -> `Generated.ActiveStatus`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ActiveStatusType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AddUploadPartRequest` -> `Generated.AddUploadPartRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKey` -> `Generated.AdminApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKeysCreateRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AdminApiKeysDelete200` -> `Generated.AdminApiKeysDelete200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKeysListParams` -> `Generated.AdminApiKeysListParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AdminApiKeysListParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Annotation` -> `Generated.Annotation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApiKeyList` -> `Generated.ApiKeyList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchCallOutputStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCallOutputStatusParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCallStatusParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCreateFileOperation` -> `Generated.ApplyPatchCreateFileOperation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchCreateFileOperationParam` -> `Generated.ApplyPatchCreateFileOperationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchCreateFileOperationParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchCreateFileOperationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchDeleteFileOperation` -> `Generated.ApplyPatchDeleteFileOperation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchDeleteFileOperationParam` -> `Generated.ApplyPatchDeleteFileOperationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchDeleteFileOperationParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchDeleteFileOperationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchOperationParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCall` -> `Generated.ApplyPatchToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchToolCallItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallOutput` -> `Generated.ApplyPatchToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchToolCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchToolParam` -> `Generated.ApplyPatchToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchUpdateFileOperation` -> `Generated.ApplyPatchUpdateFileOperation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchUpdateFileOperationParam` -> `Generated.ApplyPatchUpdateFileOperationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ApplyPatchUpdateFileOperationParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApplyPatchUpdateFileOperationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApproximateLocation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ApproximateLocationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssignedRoleDetails` -> `Generated.AssignedRoleDetails`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantMessageItem` -> `Generated.AssistantMessageItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantMessageItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantMessageItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantObject` -> `Generated.AssistantObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantSupportedModels` -> `Generated.AssistantSupportedModels`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantTool` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsCode` -> `Generated.AssistantToolsCode`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsCodeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsFileSearch` -> `Generated.AssistantToolsFileSearch`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsFileSearchType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsFileSearchTypeOnly` -> `Generated.AssistantToolsFileSearchTypeOnly`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsFileSearchTypeOnlyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantToolsFunction` -> `Generated.AssistantToolsFunction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantToolsFunctionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantsApiResponseFormatOption` -> `Generated.AssistantsApiResponseFormatOption`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantsApiResponseFormatOptionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantsNamedToolChoice` -> `Generated.AssistantsNamedToolChoice`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AssistantsNamedToolChoiceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Attachment` -> `Generated.Attachment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AttachmentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AudioResponseFormat` -> `Generated.AudioResponseFormat`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AudioTranscription` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AudioTranscriptionModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AuditLog` -> `Generated.AuditLog`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActor` -> `Generated.AuditLogActor`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorApiKey` -> `Generated.AuditLogActorApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorApiKeyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AuditLogActorServiceAccount` -> `Generated.AuditLogActorServiceAccount`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorSession` -> `Generated.AuditLogActorSession`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogActorType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AuditLogActorUser` -> `Generated.AuditLogActorUser`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AuditLogEventType` -> `Generated.AuditLogEventType`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AutoChunkingStrategyRequestParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AutoChunkingStrategyRequestParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AutomaticThreadTitlingParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Batch` -> `Generated.Batch`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BatchError` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchFileExpirationAfter` -> `Generated.BatchFileExpirationAfter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BatchFileExpirationAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchRequestCounts` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BatchStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Certificate` -> `Generated.Certificate`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CertificateObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionAllowedTools` -> `Generated.ChatCompletionAllowedTools`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionAllowedToolsChoice` -> `Generated.ChatCompletionAllowedToolsChoice`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionAllowedToolsChoiceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionAllowedToolsMode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionDeleted` -> `Generated.ChatCompletionDeleted`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionDeletedObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionFunctionCallOption` -> `Generated.ChatCompletionFunctionCallOption`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionFunctions` -> `Generated.ChatCompletionFunctions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionList` -> `Generated.ChatCompletionList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageCustomToolCall` -> `Generated.ChatCompletionMessageCustomToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionMessageCustomToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageList` -> `Generated.ChatCompletionMessageList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionMessageListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageToolCall` -> `Generated.ChatCompletionMessageToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionMessageToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionMessageToolCalls` -> `Generated.ChatCompletionMessageToolCalls`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionNamedToolChoice` -> `Generated.ChatCompletionNamedToolChoice`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionNamedToolChoiceCustom` -> `Generated.ChatCompletionNamedToolChoiceCustom`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionNamedToolChoiceCustomType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionNamedToolChoiceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestAssistantMessage` -> `Generated.ChatCompletionRequestAssistantMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestAssistantMessageContentPart` -> `Generated.ChatCompletionRequestAssistantMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestAssistantMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestDeveloperMessage` -> `Generated.ChatCompletionRequestDeveloperMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestDeveloperMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestFunctionMessage` -> `Generated.ChatCompletionRequestFunctionMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestFunctionMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessage` -> `Generated.ChatCompletionRequestMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartAudio` -> `Generated.ChatCompletionRequestMessageContentPartAudio`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartAudioInputAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartAudioType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartFile` -> `Generated.ChatCompletionRequestMessageContentPartFile`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartFileType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartImage` -> `Generated.ChatCompletionRequestMessageContentPartImage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartImageImageUrlDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartImageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartRefusal` -> `Generated.ChatCompletionRequestMessageContentPartRefusal`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartRefusalType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestMessageContentPartText` -> `Generated.ChatCompletionRequestMessageContentPartText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestMessageContentPartTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestSystemMessage` -> `Generated.ChatCompletionRequestSystemMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestSystemMessageContentPart` -> `Generated.ChatCompletionRequestSystemMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestSystemMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestToolMessage` -> `Generated.ChatCompletionRequestToolMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestToolMessageContentPart` -> `Generated.ChatCompletionRequestToolMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestToolMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionRequestUserMessage` -> `Generated.ChatCompletionRequestUserMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestUserMessageContentPart` -> `Generated.ChatCompletionRequestUserMessageContentPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionRequestUserMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionResponseMessage` -> `Generated.ChatCompletionResponseMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionResponseMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionStreamOptions` -> `Generated.ChatCompletionStreamOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionTokenLogprob` -> `Generated.ChatCompletionTokenLogprob`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionTool` -> `Generated.ChatCompletionTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionToolChoiceOption` -> `Generated.ChatCompletionToolChoiceOption`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatCompletionToolChoiceOptionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatCompletionToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionAutomaticThreadTitling` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionChatkitConfiguration` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionFileUpload` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionHistory` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionRateLimits` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionResource` -> `Generated.ChatSessionResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChatSessionResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatSessionStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatkitConfigurationParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatkitWorkflow` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatkitWorkflowTracing` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChunkingStrategyRequestParam` -> `Generated.ChunkingStrategyRequestParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ChunkingStrategyResponse` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClickButtonType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClickParam` -> `Generated.ClickParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClickParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Client` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientError` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientToolCallItem` -> `Generated.ClientToolCallItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClientToolCallItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientToolCallItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClientToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ClosedStatus` -> `Generated.ClosedStatus`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ClosedStatusType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterContainerAuto` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterContainerAutoType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterOutputImage` -> `Generated.CodeInterpreterOutputImage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterOutputImageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterOutputLogs` -> `Generated.CodeInterpreterOutputLogs`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterOutputLogsType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterTool` -> `Generated.CodeInterpreterTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterToolCall` -> `Generated.CodeInterpreterToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CodeInterpreterToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CodeInterpreterToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComparisonFilter` -> `Generated.ComparisonFilter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComparisonFilterType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComparisonFilterValueItems` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompleteUploadRequest` -> `Generated.CompleteUploadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompletionUsage` -> `Generated.CompletionUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompoundFilter` -> `Generated.CompoundFilter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CompoundFilterType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerAction` -> `Generated.ComputerAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerCallSafetyCheckParam` -> `Generated.ComputerCallSafetyCheckParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerEnvironment` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerScreenshotContent` -> `Generated.ComputerScreenshotContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerScreenshotContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerScreenshotImage` -> `Generated.ComputerScreenshotImage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerScreenshotImageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCall` -> `Generated.ComputerToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerToolCallOutputResource` -> `Generated.ComputerToolCallOutputResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerToolCallOutputResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCallOutputResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ComputerUsePreviewTool` -> `Generated.ComputerUsePreviewTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ComputerUsePreviewToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ContainerFileCitationBody` -> `Generated.ContainerFileCitationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerFileCitationBodyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ContainerFileListResource` -> `Generated.ContainerFileListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerFileResource` -> `Generated.ContainerFileResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerListResource` -> `Generated.ContainerListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerMemoryLimit` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ContainerResource` -> `Generated.ContainerResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ContainerResourceExpiresAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Conversation2` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ConversationItem` -> `Generated.ConversationItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationItemList` -> `Generated.ConversationItemList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationParam` -> `Generated.ConversationParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationParam2` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ConversationResource` -> `Generated.ConversationResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ConversationResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CostsResult` -> `Generated.CostsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CostsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateAssistantRequest` -> `Generated.CreateAssistantRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateBatchRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateBatchRequestCompletionWindow` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateBatchRequestEndpoint` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequest` -> `Generated.CreateChatCompletionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateChatCompletionRequestAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequestFunctionCallEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequestPromptCacheRetentionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionRequestWebSearchOptionsUserLocationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatCompletionResponse` -> `Generated.CreateChatCompletionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateChatCompletionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChatSessionBody` -> `Generated.CreateChatSessionBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateCompletionRequest` -> `Generated.CreateCompletionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateCompletionRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateCompletionResponse` -> `Generated.CreateCompletionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateCompletionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateContainerBody` -> `Generated.CreateContainerBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateContainerBodyExpiresAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateContainerFileBody` -> `Generated.CreateContainerFileBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateConversationBody` -> `Generated.CreateConversationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateConversationItemsParams` -> `Generated.CreateConversationItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateConversationItemsRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingRequest` -> `Generated.CreateEmbeddingRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEmbeddingRequestEncodingFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingResponse` -> `Generated.CreateEmbeddingResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEmbeddingResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCompletionsRunDataSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCompletionsRunDataSourceInputMessagesEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCompletionsRunDataSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCustomDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalCustomDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalItem` -> `Generated.CreateEvalItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalJsonlRunDataSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalJsonlRunDataSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalLabelModelGrader` -> `Generated.CreateEvalLabelModelGrader`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalLabelModelGraderType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalLogsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalLogsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalRequest` -> `Generated.CreateEvalRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalResponsesRunDataSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalResponsesRunDataSourceInputMessagesEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalResponsesRunDataSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalRunRequest` -> `Generated.CreateEvalRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEvalStoredCompletionsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEvalStoredCompletionsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFileRequest` -> `Generated.CreateFileRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateFineTuningCheckpointPermissionRequest` -> `Generated.CreateFineTuningCheckpointPermissionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateFineTuningJobRequest` -> `Generated.CreateFineTuningJobRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateFineTuningJobRequestHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFineTuningJobRequestHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFineTuningJobRequestHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateFineTuningJobRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateGroupBody` -> `Generated.CreateGroupBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateGroupUserBody` -> `Generated.CreateGroupUserBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageEditRequest` -> `Generated.CreateImageEditRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageEditRequestBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageEditRequestSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequest` -> `Generated.CreateImageRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageRequestBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestModeration` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageRequestStyle` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageVariationRequest` -> `Generated.CreateImageVariationRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateImageVariationRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageVariationRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateImageVariationRequestSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessageRequest` -> `Generated.CreateMessageRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessageRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateModerationRequest` -> `Generated.CreateModerationRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateModerationRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateModerationResponse` -> `Generated.CreateModerationResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateResponse` -> `Generated.CreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateResponsePromptCacheRetentionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateResponseTruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunParams` -> `Generated.CreateRunParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateRunRequest` -> `Generated.CreateRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateRunRequestToolChoice` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunRequestToolChoiceEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunRequestTruncationStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateRunRequestTruncationStrategyEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSpeechRequest` -> `Generated.CreateSpeechRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateSpeechRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSpeechRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateSpeechRequestStreamFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequest` -> `Generated.CreateThreadAndRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateThreadAndRunRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestToolChoice` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestToolChoiceEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestTruncationStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadAndRunRequestTruncationStrategyEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateThreadRequest` -> `Generated.CreateThreadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscription200` -> `Generated.CreateTranscription200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionRequest` -> `Generated.CreateTranscriptionRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranscriptionResponseDiarizedJson` -> `Generated.CreateTranscriptionResponseDiarizedJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionResponseDiarizedJsonTask` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranscriptionResponseJson` -> `Generated.CreateTranscriptionResponseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranscriptionResponseVerboseJson` -> `Generated.CreateTranscriptionResponseVerboseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslation200` -> `Generated.CreateTranslation200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslationRequest` -> `Generated.CreateTranslationRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslationRequestModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranslationRequestResponseFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateTranslationResponseJson` -> `Generated.CreateTranslationResponseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateTranslationResponseVerboseJson` -> `Generated.CreateTranslationResponseVerboseJson`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateUploadRequest` -> `Generated.CreateUploadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateUploadRequestPurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateVectorStoreFileBatchRequest` -> `Generated.CreateVectorStoreFileBatchRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateVectorStoreFileRequest` -> `Generated.CreateVectorStoreFileRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateVectorStoreRequest` -> `Generated.CreateVectorStoreRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateVideoBody` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateVideoRemixBody` -> `Generated.CreateVideoRemixBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomGrammarFormatParam` -> `Generated.CustomGrammarFormatParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomGrammarFormatParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomTextFormatParam` -> `Generated.CustomTextFormatParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomTextFormatParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolCall` -> `Generated.CustomToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolCallOutput` -> `Generated.CustomToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolChatCompletions` -> `Generated.CustomToolChatCompletions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolChatCompletionsCustomFormatEnumGrammarSyntax` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolChatCompletionsCustomFormatEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolChatCompletionsType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CustomToolParam` -> `Generated.CustomToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CustomToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteAssistantResponse` -> `Generated.DeleteAssistantResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteAssistantResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteCertificateResponse` -> `Generated.DeleteCertificateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteEval200` -> `Generated.DeleteEval200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteEvalRun200` -> `Generated.DeleteEvalRun200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFileResponse` -> `Generated.DeleteFileResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFileResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteFineTuningCheckpointPermissionResponse` -> `Generated.DeleteFineTuningCheckpointPermissionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteFineTuningCheckpointPermissionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteMessageResponse` -> `Generated.DeleteMessageResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteMessageResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteModelResponse` -> `Generated.DeleteModelResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteThreadResponse` -> `Generated.DeleteThreadResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteThreadResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteVectorStoreFileResponse` -> `Generated.DeleteVectorStoreFileResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteVectorStoreFileResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeleteVectorStoreResponse` -> `Generated.DeleteVectorStoreResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteVectorStoreResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeletedConversationResource` -> `Generated.DeletedConversationResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedConversationResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeletedRoleAssignmentResource` -> `Generated.DeletedRoleAssignmentResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedThreadResource` -> `Generated.DeletedThreadResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedThreadResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DeletedVideoResource` -> `Generated.DeletedVideoResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeletedVideoResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DetailEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DoubleClickAction` -> `Generated.DoubleClickAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DoubleClickActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DownloadFile200` -> `Generated.DownloadFile200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Drag` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DragPoint` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DragType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EasyInputMessage` -> `Generated.EasyInputMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EasyInputMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EasyInputMessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Embedding` -> `Generated.Embedding`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EmbeddingObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Error` -> `Generated.Error`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Error2` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ErrorResponse` -> `Generated.ErrorResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Eval` -> `Generated.Eval`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalApiError` -> `Generated.EvalApiError`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalCustomDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalCustomDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderLabelModel` -> `Generated.EvalGraderLabelModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderLabelModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderPython` -> `Generated.EvalGraderPython`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderPythonType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderScoreModel` -> `Generated.EvalGraderScoreModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderScoreModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderStringCheck` -> `Generated.EvalGraderStringCheck`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderStringCheckOperation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderStringCheckType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderTextSimilarity` -> `Generated.EvalGraderTextSimilarity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalGraderTextSimilarityEvaluationMetric` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalGraderTextSimilarityType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalItem` -> `Generated.EvalItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalItemContentEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalItemRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalJsonlFileContentSource` -> `Generated.EvalJsonlFileContentSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalJsonlFileContentSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalJsonlFileIdSource` -> `Generated.EvalJsonlFileIdSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalJsonlFileIdSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalList` -> `Generated.EvalList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalLogsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalLogsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalResponsesSource` -> `Generated.EvalResponsesSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalResponsesSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRun` -> `Generated.EvalRun`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunList` -> `Generated.EvalRunList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunOutputItem` -> `Generated.EvalRunOutputItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunOutputItemList` -> `Generated.EvalRunOutputItemList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalRunOutputItemListObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunOutputItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalRunOutputItemResult` -> `Generated.EvalRunOutputItemResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalStoredCompletionsDataSourceConfig` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalStoredCompletionsDataSourceConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.EvalStoredCompletionsSource` -> `Generated.EvalStoredCompletionsSource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EvalStoredCompletionsSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ExpiresAfterParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ExpiresAfterParamAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotation` -> `Generated.FileAnnotation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileAnnotationSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotationSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileCitationBody` -> `Generated.FileCitationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileCitationBodyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileExpirationAfter` -> `Generated.FileExpirationAfter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileExpirationAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FilePath` -> `Generated.FilePath`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FilePathType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FilePurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileSearchRanker` -> `Generated.FileSearchRanker`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchRankingOptions` -> `Generated.FileSearchRankingOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchTool` -> `Generated.FileSearchTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchToolCall` -> `Generated.FileSearchToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileSearchToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileSearchToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileSearchToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileUploadParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Filters` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparameters` -> `Generated.FineTuneDPOHyperparameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneDPOHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparametersBetaEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneDPOMethod` -> `Generated.FineTuneDPOMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneMethod` -> `Generated.FineTuneMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneMethodType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparameters` -> `Generated.FineTuneReinforcementHyperparameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneReinforcementHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersComputeMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersEvalIntervalEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersEvalSamplesEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementHyperparametersReasoningEffort` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneReinforcementMethod` -> `Generated.FineTuneReinforcementMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneSupervisedHyperparameters` -> `Generated.FineTuneSupervisedHyperparameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuneSupervisedHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneSupervisedHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneSupervisedHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuneSupervisedMethod` -> `Generated.FineTuneSupervisedMethod`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningCheckpointPermission` -> `Generated.FineTuningCheckpointPermission`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningCheckpointPermissionObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningIntegration` -> `Generated.FineTuningIntegration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningIntegrationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJob` -> `Generated.FineTuningJob`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningJobCheckpoint` -> `Generated.FineTuningJobCheckpoint`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningJobCheckpointObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobEvent` -> `Generated.FineTuningJobEvent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FineTuningJobEventLevel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobEventObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobEventType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobHyperparametersBatchSizeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobHyperparametersLearningRateMultiplierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobHyperparametersNEpochsEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FineTuningJobStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionAndCustomToolCallOutput` -> `Generated.FunctionAndCustomToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionCallItemStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionObject` -> `Generated.FunctionObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionParameters` -> `Generated.FunctionParameters`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellAction` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellActionParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCall` -> `Generated.FunctionShellCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallItemStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutput` -> `Generated.FunctionShellCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputContent` -> `Generated.FunctionShellCallOutputContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputContentParam` -> `Generated.FunctionShellCallOutputContentParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputExitOutcome` -> `Generated.FunctionShellCallOutputExitOutcome`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputExitOutcomeParam` -> `Generated.FunctionShellCallOutputExitOutcomeParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputExitOutcomeParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputExitOutcomeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputItemParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputItemParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputOutcomeParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputTimeoutOutcome` -> `Generated.FunctionShellCallOutputTimeoutOutcome`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputTimeoutOutcomeParam` -> `Generated.FunctionShellCallOutputTimeoutOutcomeParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellCallOutputTimeoutOutcomeParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputTimeoutOutcomeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionShellToolParam` -> `Generated.FunctionShellToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionShellToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionTool` -> `Generated.FunctionTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCall` -> `Generated.FunctionToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCallOutputResource` -> `Generated.FunctionToolCallOutputResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCallOutputResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallOutputResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallResource` -> `Generated.FunctionToolCallResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FunctionToolCallResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FunctionToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetCertificateParams` -> `Generated.GetCertificateParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetChatCompletionMessagesParams` -> `Generated.GetChatCompletionMessagesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetChatCompletionMessagesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetConversationItemParams` -> `Generated.GetConversationItemParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetEvalRunOutputItemsParams` -> `Generated.GetEvalRunOutputItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetEvalRunOutputItemsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetEvalRunOutputItemsParamsStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetEvalRunsParams` -> `Generated.GetEvalRunsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetEvalRunsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetEvalRunsParamsStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetResponseParams` -> `Generated.GetResponseParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetRunStepParams` -> `Generated.GetRunStepParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderLabelModel` -> `Generated.GraderLabelModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderLabelModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderMulti` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderMultiType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderPython` -> `Generated.GraderPython`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderPythonType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderScoreModel` -> `Generated.GraderScoreModel`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderScoreModelType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderStringCheck` -> `Generated.GraderStringCheck`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderStringCheckOperation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderStringCheckType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderTextSimilarity` -> `Generated.GraderTextSimilarity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GraderTextSimilarityEvaluationMetric` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GraderTextSimilarityType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GrammarSyntax1` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Group` -> `Generated.Group`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupDeletedResource` -> `Generated.GroupDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupListResource` -> `Generated.GroupListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupResourceWithSuccess` -> `Generated.GroupResourceWithSuccess`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupResponse` -> `Generated.GroupResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupRoleAssignment` -> `Generated.GroupRoleAssignment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupRoleAssignmentObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupUserAssignment` -> `Generated.GroupUserAssignment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupUserAssignmentObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GroupUserDeletedResource` -> `Generated.GroupUserDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GroupUserDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.HistoryParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.HybridSearchOptions` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Image` -> `Generated.Image`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenInputUsageDetails` -> `Generated.ImageGenInputUsageDetails`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenTool` -> `Generated.ImageGenTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenToolBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolCall` -> `Generated.ImageGenToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolModeration` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImageGenUsage` -> `Generated.ImageGenUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImagesResponse` -> `Generated.ImagesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImagesResponseBackground` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImagesResponseOutputFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImagesResponseQuality` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ImagesResponseSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.IncludeEnum` -> `Generated.IncludeEnum`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InferenceOptions` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputAudio` -> `Generated.InputAudio`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputAudioInputAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputAudioType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputContent` -> `Generated.InputContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFidelity` -> `Generated.InputFidelity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFileContent` -> `Generated.InputFileContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFileContentParam` -> `Generated.InputFileContentParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputFileContentParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputFileContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputImageContent` -> `Generated.InputImageContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputImageContentParamAutoParam` -> `Generated.InputImageContentParamAutoParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputImageContentParamAutoParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputImageContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputItem` -> `Generated.InputItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessage` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageContentList` -> `Generated.InputMessageContentList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessageResource` -> `Generated.InputMessageResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputMessageResourceRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageResourceStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputMessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputParam` -> `Generated.InputParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputTextContent` -> `Generated.InputTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputTextContentParam` -> `Generated.InputTextContentParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputTextContentParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InputTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Invite` -> `Generated.Invite`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteDeleteResponse` -> `Generated.InviteDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteListResponse` -> `Generated.InviteListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteProjectGroupBody` -> `Generated.InviteProjectGroupBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteRequest` -> `Generated.InviteRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InviteRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.InviteStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Item` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ItemReferenceParam` -> `Generated.ItemReferenceParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ItemReferenceParamTypeEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ItemResource` -> `Generated.ItemResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.KeyPressAction` -> `Generated.KeyPressAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.KeyPressActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListAssistantsParams` -> `Generated.ListAssistantsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAssistantsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListAssistantsResponse` -> `Generated.ListAssistantsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAuditLogsParams` -> `Generated.ListAuditLogsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAuditLogsResponse` -> `Generated.ListAuditLogsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListAuditLogsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListBatchesParams` -> `Generated.ListBatchesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListBatchesResponse` -> `Generated.ListBatchesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListBatchesResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListCertificatesResponse` -> `Generated.ListCertificatesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListCertificatesResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListChatCompletionsParams` -> `Generated.ListChatCompletionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListChatCompletionsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListContainerFilesParams` -> `Generated.ListContainerFilesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListContainerFilesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListContainersParams` -> `Generated.ListContainersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListContainersParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListConversationItemsParams` -> `Generated.ListConversationItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListConversationItemsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEvalsParams` -> `Generated.ListEvalsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEvalsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEvalsParamsOrderBy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesInVectorStoreBatchParams` -> `Generated.ListFilesInVectorStoreBatchParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFilesInVectorStoreBatchParamsFilter` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesInVectorStoreBatchParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesParams` -> `Generated.ListFilesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFilesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFilesResponse` -> `Generated.ListFilesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningCheckpointPermissionResponse` -> `Generated.ListFineTuningCheckpointPermissionResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningCheckpointPermissionResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFineTuningCheckpointPermissionsParams` -> `Generated.ListFineTuningCheckpointPermissionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningCheckpointPermissionsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFineTuningEventsParams` -> `Generated.ListFineTuningEventsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobCheckpointsParams` -> `Generated.ListFineTuningJobCheckpointsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobCheckpointsResponse` -> `Generated.ListFineTuningJobCheckpointsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobCheckpointsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListFineTuningJobEventsResponse` -> `Generated.ListFineTuningJobEventsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListFineTuningJobEventsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListGroupRoleAssignmentsParams` -> `Generated.ListGroupRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGroupRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListGroupUsersParams` -> `Generated.ListGroupUsersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGroupUsersParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListGroupsParams` -> `Generated.ListGroupsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGroupsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListInputItemsParams` -> `Generated.ListInputItemsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListInputItemsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListInvitesParams` -> `Generated.ListInvitesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMessagesParams` -> `Generated.ListMessagesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMessagesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListMessagesResponse` -> `Generated.ListMessagesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListModelsResponse` -> `Generated.ListModelsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListModelsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListOrganizationCertificatesParams` -> `Generated.ListOrganizationCertificatesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListOrganizationCertificatesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListPaginatedFineTuningJobsParams` -> `Generated.ListPaginatedFineTuningJobsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListPaginatedFineTuningJobsResponse` -> `Generated.ListPaginatedFineTuningJobsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListPaginatedFineTuningJobsResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectApiKeysParams` -> `Generated.ListProjectApiKeysParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectCertificatesParams` -> `Generated.ListProjectCertificatesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectCertificatesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectGroupRoleAssignmentsParams` -> `Generated.ListProjectGroupRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectGroupRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectGroupsParams` -> `Generated.ListProjectGroupsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectGroupsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectRateLimitsParams` -> `Generated.ListProjectRateLimitsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectRolesParams` -> `Generated.ListProjectRolesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectRolesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectServiceAccountsParams` -> `Generated.ListProjectServiceAccountsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectUserRoleAssignmentsParams` -> `Generated.ListProjectUserRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectUserRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListProjectUsersParams` -> `Generated.ListProjectUsersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProjectsParams` -> `Generated.ListProjectsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRolesParams` -> `Generated.ListRolesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRolesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListRunStepsParams` -> `Generated.ListRunStepsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRunStepsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListRunStepsResponse` -> `Generated.ListRunStepsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRunsParams` -> `Generated.ListRunsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListRunsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListRunsResponse` -> `Generated.ListRunsResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListThreadItemsMethodParams` -> `Generated.ListThreadItemsMethodParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListThreadsMethodParams` -> `Generated.ListThreadsMethodParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListUserRoleAssignmentsParams` -> `Generated.ListUserRoleAssignmentsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListUserRoleAssignmentsParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListUsersParams` -> `Generated.ListUsersParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoreFilesParams` -> `Generated.ListVectorStoreFilesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoreFilesParamsFilter` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListVectorStoreFilesParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListVectorStoreFilesResponse` -> `Generated.ListVectorStoreFilesResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoresParams` -> `Generated.ListVectorStoresParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVectorStoresParamsOrder` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListVectorStoresResponse` -> `Generated.ListVectorStoresResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListVideosParams` -> `Generated.ListVideosParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellExecAction` -> `Generated.LocalShellExecAction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellExecActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCall` -> `Generated.LocalShellToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellToolCallOutput` -> `Generated.LocalShellToolCallOutput`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellToolCallOutputStatusEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCallOutputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LocalShellToolParam` -> `Generated.LocalShellToolParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LocalShellToolParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LockedStatus` -> `Generated.LockedStatus`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.LockedStatusType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.LogProb` -> `Generated.LogProb`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPApprovalRequest` -> `Generated.MCPApprovalRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPApprovalRequestType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPApprovalResponse` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPApprovalResponseResource` -> `Generated.MCPApprovalResponseResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPApprovalResponseResourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPApprovalResponseType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPListTools` -> `Generated.MCPListTools`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPListToolsTool` -> `Generated.MCPListToolsTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPListToolsType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPTool` -> `Generated.MCPTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPToolCall` -> `Generated.MCPToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolConnectorId` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolFilter` -> `Generated.MCPToolFilter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MCPToolRequireApprovalEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MCPToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Message` -> `Generated.Message`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContent` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageFileObject` -> `Generated.MessageContentImageFileObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentImageFileObjectImageFileDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageFileObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageUrlObject` -> `Generated.MessageContentImageUrlObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentImageUrlObjectImageUrlDetail` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentImageUrlObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentRefusalObject` -> `Generated.MessageContentRefusalObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentRefusalObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentTextAnnotationsFileCitationObject` -> `Generated.MessageContentTextAnnotationsFileCitationObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentTextAnnotationsFileCitationObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentTextAnnotationsFilePathObject` -> `Generated.MessageContentTextAnnotationsFilePathObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentTextAnnotationsFilePathObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageContentTextObject` -> `Generated.MessageContentTextObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageContentTextObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObject` -> `Generated.MessageObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageObjectIncompleteDetailsEnumReason` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObjectRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageRequestContentTextObject` -> `Generated.MessageRequestContentTextObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.MessageRequestContentTextObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MessageStatus` -> `OpenAiSchema.MessageStatus`: Use the focused v4 OpenAiSchema definition; the old generated export was removed when the OpenAI specification client was regenerated. + +- `Generated.MessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Metadata` -> `Generated.Metadata`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Model` -> `Generated.Model`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelIdsResponses` -> `Generated.ModelIdsResponses`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelIdsResponsesEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModelIdsShared` -> `Generated.ModelIdsShared`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationImageURLInput` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationImageURLInputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationTextInput` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModerationTextInputType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModifyAssistantRequest` -> `Generated.ModifyAssistantRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyCertificateRequest` -> `Generated.ModifyCertificateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyMessageRequest` -> `Generated.ModifyMessageRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyRunRequest` -> `Generated.ModifyRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModifyThreadRequest` -> `Generated.ModifyThreadRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Move` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.MoveType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.NoiseReductionType` -> `Generated.NoiseReductionType`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIFile` -> `Generated.OpenAIFile`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIFileObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIFilePurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIFileStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OrderEnum` -> `Generated.OrderEnum`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OtherChunkingStrategyResponseParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OtherChunkingStrategyResponseParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItem` -> `Generated.OutputItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessage` -> `Generated.OutputMessage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessageContent` -> `Generated.OutputMessageContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessageRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputTextContent` -> `Generated.OutputTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ParallelToolCalls` -> `Generated.ParallelToolCalls`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PartialImages` -> `Generated.PartialImages`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PredictionContent` -> `Generated.PredictionContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PredictionContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Project` -> `Generated.Project`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKey` -> `Generated.ProjectApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKeyDeleteResponse` -> `Generated.ProjectApiKeyDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKeyDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectApiKeyListResponse` -> `Generated.ProjectApiKeyListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectApiKeyListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectApiKeyObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectApiKeyOwnerType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectCreateRequest` -> `Generated.ProjectCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectCreateRequestGeography` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectGroup` -> `Generated.ProjectGroup`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectGroupDeletedResource` -> `Generated.ProjectGroupDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectGroupDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectGroupListResource` -> `Generated.ProjectGroupListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectGroupListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectGroupObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectListResponse` -> `Generated.ProjectListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectRateLimit` -> `Generated.ProjectRateLimit`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectRateLimitListResponse` -> `Generated.ProjectRateLimitListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectRateLimitListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectRateLimitObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectRateLimitUpdateRequest` -> `Generated.ProjectRateLimitUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccount` -> `Generated.ProjectServiceAccount`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountApiKey` -> `Generated.ProjectServiceAccountApiKey`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountApiKeyObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountCreateRequest` -> `Generated.ProjectServiceAccountCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountCreateResponse` -> `Generated.ProjectServiceAccountCreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountCreateResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountCreateResponseRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountDeleteResponse` -> `Generated.ProjectServiceAccountDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountListResponse` -> `Generated.ProjectServiceAccountListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectServiceAccountListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectServiceAccountRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUpdateRequest` -> `Generated.ProjectUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUser` -> `Generated.ProjectUser`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserCreateRequest` -> `Generated.ProjectUserCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserCreateRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserDeleteResponse` -> `Generated.ProjectUserDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserListResponse` -> `Generated.ProjectUserListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProjectUserUpdateRequest` -> `Generated.ProjectUserUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProjectUserUpdateRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Prompt` -> `Generated.Prompt`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicAssignOrganizationGroupRoleBody` -> `Generated.PublicAssignOrganizationGroupRoleBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicCreateOrganizationRoleBody` -> `Generated.PublicCreateOrganizationRoleBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicRoleListResource` -> `Generated.PublicRoleListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicRoleListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicUpdateOrganizationRoleBody` -> `Generated.PublicUpdateOrganizationRoleBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RankerVersionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RankingOptions` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RateLimitsParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeAudioFormats` -> `Generated.RealtimeAudioFormats`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeAudioFormatsEnumRate` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeAudioFormatsEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeCallCreateRequest` -> `Generated.RealtimeCallCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCallReferRequest` -> `Generated.RealtimeCallReferRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCallRejectRequest` -> `Generated.RealtimeCallRejectRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCreateClientSecretRequest` -> `Generated.RealtimeCreateClientSecretRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeCreateClientSecretRequestExpiresAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeCreateClientSecretResponse` -> `Generated.RealtimeCreateClientSecretResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeFunctionTool` -> `Generated.RealtimeFunctionTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeFunctionToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequest` -> `Generated.RealtimeSessionCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateRequestGA` -> `Generated.RealtimeSessionCreateRequestGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateRequestGAMaxOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestGAModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestGATracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestMaxResponseOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateRequestTracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponse` -> `Generated.RealtimeSessionCreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateResponseGA` -> `Generated.RealtimeSessionCreateResponseGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeSessionCreateResponseGAMaxOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseGAModelEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseGATracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseMaxOutputTokensEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeSessionCreateResponseTracingEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateRequest` -> `Generated.RealtimeTranscriptionSessionCreateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateRequestGA` -> `Generated.RealtimeTranscriptionSessionCreateRequestGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateRequestGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateRequestInputAudioFormat` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateRequestTurnDetectionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTranscriptionSessionCreateResponse` -> `Generated.RealtimeTranscriptionSessionCreateResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateResponseGA` -> `Generated.RealtimeTranscriptionSessionCreateResponseGA`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTranscriptionSessionCreateResponseGAType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTruncation` -> `Generated.RealtimeTruncation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTruncationEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RealtimeTurnDetection` -> `Generated.RealtimeTurnDetection`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RealtimeTurnDetectionEnumEagerness` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Reasoning` -> `Generated.Reasoning`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningEffort` -> `Generated.ReasoningEffort`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningEffortEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningGenerateSummaryEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningItem` -> `Generated.ReasoningItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningItemStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningSummaryEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningTextContent` -> `Generated.ReasoningTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RefusalContent` -> `Generated.RefusalContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RefusalContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Response` -> `Generated.Response`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseError` -> `Generated.ResponseError`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseErrorCode` -> `Generated.ResponseErrorCode`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonObject` -> `Generated.ResponseFormatJsonObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatJsonSchema` -> `Generated.ResponseFormatJsonSchema`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonSchemaSchema` -> `Generated.ResponseFormatJsonSchemaSchema`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJsonSchemaType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatText` -> `Generated.ResponseFormatText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseIncompleteDetailsEnumReason` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseItemList` -> `Generated.ResponseItemList`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseModalities` -> `Generated.ResponseModalities`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseOutputText` -> `Generated.ResponseOutputText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseOutputTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsePromptCacheRetentionEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsePromptVariables` -> `Generated.ResponsePromptVariables`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseStreamOptions` -> `Generated.ResponseStreamOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseTextParam` -> `Generated.ResponseTextParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseTruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseUsage` -> `Generated.ResponseUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RetrieveVideoContent200` -> `Generated.RetrieveVideoContent200`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RetrieveVideoContentParams` -> `Generated.RetrieveVideoContentParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Role` -> `Generated.Role`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RoleDeletedResource` -> `Generated.RoleDeletedResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RoleDeletedResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RoleListResource` -> `Generated.RoleListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RoleListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RoleObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunCompletionUsage` -> `Generated.RunCompletionUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunGraderRequest` -> `Generated.RunGraderRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunGraderResponse` -> `Generated.RunGraderResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunObject` -> `Generated.RunObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunObjectIncompleteDetailsReason` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectLastErrorCode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectRequiredActionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectToolChoice` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectToolChoiceEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectTruncationStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunObjectTruncationStrategyEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepCompletionUsage` -> `Generated.RunStepCompletionUsage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsMessageCreationObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsMessageCreationObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCall` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeObject` -> `Generated.RunStepDetailsToolCallsCodeObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsCodeObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputImageObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputImageObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputLogsObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsCodeOutputLogsObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsFileSearchObject` -> `Generated.RunStepDetailsToolCallsFileSearchObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFileSearchObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsFileSearchRankingOptionsObject` -> `Generated.RunStepDetailsToolCallsFileSearchRankingOptionsObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFileSearchResultObject` -> `Generated.RunStepDetailsToolCallsFileSearchResultObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFunctionObject` -> `Generated.RunStepDetailsToolCallsFunctionObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepDetailsToolCallsFunctionObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepDetailsToolCallsObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObject` -> `Generated.RunStepObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunStepObjectLastErrorEnumCode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunStepObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RunToolCallObject` -> `Generated.RunToolCallObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RunToolCallObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Screenshot` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ScreenshotType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Scroll` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ScrollType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SearchContextSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ServiceTier` -> `Generated.ServiceTier`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ServiceTierEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SpecificApplyPatchParam` -> `Generated.SpecificApplyPatchParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SpecificApplyPatchParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SpecificFunctionShellParam` -> `Generated.SpecificFunctionShellParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SpecificFunctionShellParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategy` -> `Generated.StaticChunkingStrategy`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.StaticChunkingStrategyRequestParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategyRequestParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategyResponseParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StaticChunkingStrategyResponseParamType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.StopConfiguration` -> `Generated.StopConfiguration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SubmitToolOutputsRunRequest` -> `Generated.SubmitToolOutputsRunRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SummaryTextContent` -> `Generated.SummaryTextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SummaryTextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.SummaryType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskGroupItem` -> `Generated.TaskGroupItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TaskGroupItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskGroupItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskGroupTask` -> `Generated.TaskGroupTask`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TaskItem` -> `Generated.TaskItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TaskItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TaskType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TextAnnotation` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TextContent` -> `Generated.TextContent`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextContentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TextResponseFormatConfiguration` -> `Generated.TextResponseFormatConfiguration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextResponseFormatJsonSchema` -> `Generated.TextResponseFormatJsonSchema`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TextResponseFormatJsonSchemaType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ThreadItem` -> `Generated.ThreadItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadItemListResource` -> `Generated.ThreadItemListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadListResource` -> `Generated.ThreadListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadObject` -> `Generated.ThreadObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ThreadResource` -> `Generated.ThreadResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ThreadResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToggleCertificatesRequest` -> `Generated.ToggleCertificatesRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TokenCountsBody` -> `Generated.TokenCountsBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TokenCountsResource` -> `Generated.TokenCountsResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TokenCountsResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Tool` -> `Generated.Tool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoice` -> `OpenAiSchema.ToolChoice`: Use the focused v4 OpenAiSchema definition; the old generated export was removed when the OpenAI specification client was regenerated. + +- `Generated.ToolChoiceAllowed` -> `Generated.ToolChoiceAllowed`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceAllowedMode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceAllowedType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceCustom` -> `Generated.ToolChoiceCustom`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceCustomType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceFunction` -> `Generated.ToolChoiceFunction`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceFunctionType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceMCP` -> `Generated.ToolChoiceMCP`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceMCPType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolChoiceOptions` -> `Generated.ToolChoiceOptions`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceParam` -> `Generated.ToolChoiceParam`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceTypes` -> `Generated.ToolChoiceTypes`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceTypesType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ToolsArray` -> `Generated.ToolsArray`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TopLogProb` -> `Generated.TopLogProb`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptTextUsageDuration` -> `Generated.TranscriptTextUsageDuration`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptTextUsageDurationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptTextUsageTokens` -> `Generated.TranscriptTextUsageTokens`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptTextUsageTokensType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionChunkingStrategy` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionChunkingStrategyEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionDiarizedSegment` -> `Generated.TranscriptionDiarizedSegment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptionDiarizedSegmentType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TranscriptionInclude` -> `Generated.TranscriptionInclude`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptionSegment` -> `Generated.TranscriptionSegment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TranscriptionWord` -> `Generated.TranscriptionWord`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TruncationEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Type` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TypeType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateChatCompletionRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateConversationBody` -> `Generated.UpdateConversationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateEvalRequest` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateGroupBody` -> `Generated.UpdateGroupBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateVectorStoreFileAttributesRequest` -> `Generated.UpdateVectorStoreFileAttributesRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateVectorStoreRequest` -> `Generated.UpdateVectorStoreRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateVectorStoreRequestExpiresAfter` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateVectorStoreRequestExpiresAfterEnumAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Upload` -> `Generated.Upload`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadCertificateRequest` -> `Generated.UploadCertificateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadFile` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadFileEnumObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadFileEnumPurpose` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadFileEnumStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadPart` -> `Generated.UploadPart`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UploadPartObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UploadStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlAnnotation` -> `Generated.UrlAnnotation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UrlAnnotationSource` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlAnnotationSourceType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlAnnotationType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UrlCitationBody` -> `Generated.UrlCitationBody`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UrlCitationBodyType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioSpeechesParams` -> `Generated.UsageAudioSpeechesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioSpeechesParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioSpeechesResult` -> `Generated.UsageAudioSpeechesResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioSpeechesResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioTranscriptionsParams` -> `Generated.UsageAudioTranscriptionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioTranscriptionsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageAudioTranscriptionsResult` -> `Generated.UsageAudioTranscriptionsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageAudioTranscriptionsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCodeInterpreterSessionsParams` -> `Generated.UsageCodeInterpreterSessionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCodeInterpreterSessionsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCodeInterpreterSessionsResult` -> `Generated.UsageCodeInterpreterSessionsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCodeInterpreterSessionsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCompletionsParams` -> `Generated.UsageCompletionsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCompletionsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCompletionsResult` -> `Generated.UsageCompletionsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCompletionsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageCostsParams` -> `Generated.UsageCostsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageCostsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageEmbeddingsParams` -> `Generated.UsageEmbeddingsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageEmbeddingsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageEmbeddingsResult` -> `Generated.UsageEmbeddingsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageEmbeddingsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageImagesParams` -> `Generated.UsageImagesParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageImagesParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageImagesResult` -> `Generated.UsageImagesResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageImagesResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageModerationsParams` -> `Generated.UsageModerationsParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageModerationsParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageModerationsResult` -> `Generated.UsageModerationsResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageModerationsResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageResponse` -> `Generated.UsageResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageTimeBucket` -> `Generated.UsageTimeBucket`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageTimeBucketObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageVectorStoresParams` -> `Generated.UsageVectorStoresParams`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageVectorStoresParamsBucketWidth` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UsageVectorStoresResult` -> `Generated.UsageVectorStoresResult`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UsageVectorStoresResultObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.User` -> `Generated.User`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserDeleteResponse` -> `Generated.UserDeleteResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserDeleteResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserListResource` -> `Generated.UserListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserListResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserListResponse` -> `Generated.UserListResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserListResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageInputText` -> `Generated.UserMessageInputText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserMessageInputTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageItem` -> `Generated.UserMessageItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserMessageItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessageQuotedText` -> `Generated.UserMessageQuotedText`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserMessageQuotedTextType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserRoleAssignment` -> `Generated.UserRoleAssignment`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserRoleAssignmentObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserRoleUpdateRequest` -> `Generated.UserRoleUpdateRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UserRoleUpdateRequestRole` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VadConfig` -> `Generated.VadConfig`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VadConfigType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ValidateGraderRequest` -> `Generated.ValidateGraderRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ValidateGraderResponse` -> `Generated.ValidateGraderResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreExpirationAfter` -> `Generated.VectorStoreExpirationAfter`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreExpirationAfterAnchor` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileAttributes` -> `Generated.VectorStoreFileAttributes`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileBatchObject` -> `Generated.VectorStoreFileBatchObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileBatchObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileBatchObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileContentResponse` -> `Generated.VectorStoreFileContentResponse`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileContentResponseObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileObject` -> `Generated.VectorStoreFileObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreFileObjectLastErrorEnumCode` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreFileObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreObject` -> `Generated.VectorStoreObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreObjectObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreObjectStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreSearchRequest` -> `Generated.VectorStoreSearchRequest`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchRequestRankingOptionsRanker` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreSearchResultContentObject` -> `Generated.VectorStoreSearchResultContentObject`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchResultContentObjectType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VectorStoreSearchResultItem` -> `Generated.VectorStoreSearchResultItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchResultsPage` -> `Generated.VectorStoreSearchResultsPage`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VectorStoreSearchResultsPageObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Verbosity` -> `Generated.Verbosity`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VerbosityEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoContentVariant` -> `Generated.VideoContentVariant`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VideoListResource` -> `Generated.VideoListResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VideoModel` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoResource` -> `Generated.VideoResource`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VideoResourceObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoSeconds` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VideoStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.VoiceIdsShared` -> `Generated.VoiceIdsShared`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.VoiceIdsSharedEnum` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Wait` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WaitType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionFind` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionFindType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionOpenPage` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionOpenPageType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionSearch` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchActionSearchType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchApproximateLocation` -> `Generated.WebSearchApproximateLocation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchApproximateLocationEnumType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchContextSize` -> `Generated.WebSearchContextSize`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchLocation` -> `Generated.WebSearchLocation`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchPreviewTool` -> `Generated.WebSearchPreviewTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchPreviewToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchTool` -> `Generated.WebSearchTool`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchToolCall` -> `Generated.WebSearchToolCall`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchToolCallStatus` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchToolCallType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchToolSearchContextSize` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchToolType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WidgetMessageItem` -> `Generated.WidgetMessageItem`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WidgetMessageItemObject` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WidgetMessageItemType` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WorkflowParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WorkflowTracingParam` -> `none`: Removed when the OpenAI specification client was regenerated; use the current Generated or OpenAiSchema request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.make` -> `Generated.make`: Still generated in v4 from the current OpenAI specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +### `@effect/ai-openai/OpenAiClient` + +- `OpenAiClient.LogProbs` -> `Generated.LogProb`: The client-local log-probability schema moved to the regenerated v4 OpenAI schema surface and changed shape. + +- `OpenAiClient.ResponseCodeInterpreterCallCodeDeltaEvent` -> `Generated.ResponseCodeInterpreterCallCodeDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallCodeDoneEvent` -> `Generated.ResponseCodeInterpreterCallCodeDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallCompletedEvent` -> `Generated.ResponseCodeInterpreterCallCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallInProgressEvent` -> `Generated.ResponseCodeInterpreterCallInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCodeInterpreterCallInterpretingEvent` -> `Generated.ResponseCodeInterpreterCallInterpretingEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCompletedEvent` -> `Generated.ResponseCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseContentPartAddedEvent` -> `Generated.ResponseContentPartAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseContentPartDoneEvent` -> `Generated.ResponseContentPartDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCreatedEvent` -> `Generated.ResponseCreatedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCustomToolCallInputDeltaEvent` -> `Generated.ResponseCustomToolCallInputDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseCustomToolCallInputDoneEvent` -> `Generated.ResponseCustomToolCallInputDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseErrorEvent` -> `Generated.ResponseErrorEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFailedEvent` -> `Generated.ResponseFailedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFileSearchCallCompletedEvent` -> `Generated.ResponseFileSearchCallCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFileSearchCallInProgressEvent` -> `Generated.ResponseFileSearchCallInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFileSearchCallSearchingEvent` -> `Generated.ResponseFileSearchCallSearchingEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFunctionCallArgumentsDeltaEvent` -> `Generated.ResponseFunctionCallArgumentsDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseFunctionCallArgumentsDoneEvent` -> `Generated.ResponseFunctionCallArgumentsDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseImageGenerationCallCompletedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseImageGenerationCallGeneratingEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseImageGenerationCallInProgressEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseImageGenerationCallPartialImageEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseInProgressEvent` -> `Generated.ResponseInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseIncompleteEvent` -> `Generated.ResponseIncompleteEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseMcpCallArgumentsDeltaEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallArgumentsDoneEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallCompletedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallFailedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpCallInProgressEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpListToolsCompletedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpListToolsFailedEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseMcpListToolsInProgressEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseOutputItemAddedEvent` -> `Generated.ResponseOutputItemAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseOutputItemDoneEvent` -> `Generated.ResponseOutputItemDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseOutputTextAnnotationAddedEvent` -> `Generated.ResponseOutputTextAnnotationAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseOutputTextDeltaEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseOutputTextDoneEvent` -> `OpenAiClient.ResponseStreamEvent`: The standalone event helper was removed from OpenAiClient; narrow the v4 ResponseStreamEvent union by its `type` discriminator. + +- `OpenAiClient.ResponseQueuedEvent` -> `Generated.ResponseQueuedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryPartAddedEvent` -> `Generated.ResponseReasoningSummaryPartAddedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryPartDoneEvent` -> `Generated.ResponseReasoningSummaryPartDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryTextDeltaEvent` -> `Generated.ResponseReasoningSummaryTextDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningSummaryTextDoneEvent` -> `Generated.ResponseReasoningSummaryTextDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningTextDeltaEvent` -> `Generated.ResponseReasoningTextDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseReasoningTextDoneEvent` -> `Generated.ResponseReasoningTextDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseRefusalDeltaEvent` -> `Generated.ResponseRefusalDeltaEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseRefusalDoneEvent` -> `Generated.ResponseRefusalDoneEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseStreamEvent` -> `OpenAiClient.ResponseStreamEvent`: Still exported in v4; adapt to the rewritten Responses API client and its revised schema and error types. + +- `OpenAiClient.ResponseWebSearchCallCompletedEvent` -> `Generated.ResponseWebSearchCallCompletedEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseWebSearchCallInProgressEvent` -> `Generated.ResponseWebSearchCallInProgressEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.ResponseWebSearchCallSearchingEvent` -> `Generated.ResponseWebSearchCallSearchingEvent`: The event schema moved out of OpenAiClient into the regenerated OpenAI schema surface; re-check its v4 Type/Encoded shape. + +- `OpenAiClient.Service` -> `OpenAiClient.Service`: Still exported in v4; adapt to the rewritten Responses API client and its revised schema and error types. + +- `OpenAiClient.StreamCompletionRequest` -> `OpenAiSchema.CreateResponse.Encoded`: The chat-completions request alias was removed; the v4 client uses the Responses API, with streaming inferred by OpenAiClient.createResponseStream. + +- `OpenAiClient.SummaryPart` -> `OpenAiSchema.SummaryTextContent`: The client-local reasoning summary schema moved to the focused v4 OpenAiSchema module. + +### `@effect/ai-openai/OpenAiConfig` + +- `OpenAiConfig.OpenAiConfig` -> `OpenAiConfig.OpenAiConfig`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiConfig.OpenAiConfig.Service` -> `OpenAiConfig.OpenAiConfig.Service`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +### `@effect/ai-openai/OpenAiEmbeddingModel` + +- `OpenAiEmbeddingModel.Config` -> `OpenAiEmbeddingModel.Config`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiEmbeddingModel.Config.Batched` -> `OpenAiEmbeddingModel.Config.Service`: Batch-mode configuration was removed; use the unified embedding config and constructor. + +- `OpenAiEmbeddingModel.Config.DataLoader` -> `OpenAiEmbeddingModel.Config.Service`: Data-loader configuration was removed; use the unified embedding config and constructor. + +- `OpenAiEmbeddingModel.Config.Service` -> `OpenAiEmbeddingModel.Config.Service`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiEmbeddingModel.Model` -> `OpenAiEmbeddingModel.Model`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiEmbeddingModel.layerBatched` -> `OpenAiEmbeddingModel.layer`: The batched and data-loader layers were replaced by one embedding layer; pass the model and request config explicitly. + +- `OpenAiEmbeddingModel.layerDataLoader` -> `OpenAiEmbeddingModel.layer`: The batched and data-loader layers were replaced by one embedding layer; pass the model and request config explicitly. + +- `OpenAiEmbeddingModel.makeDataLoader` -> `OpenAiEmbeddingModel.make`: The dedicated data-loader constructor was removed; use the unified v4 embedding service constructor. + +### `@effect/ai-openai/OpenAiLanguageModel` + +- `OpenAiLanguageModel.Config` -> `OpenAiLanguageModel.Config`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiLanguageModel.Config.Service` -> `OpenAiLanguageModel.Config.Service`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiLanguageModel.Model` -> `OpenAiLanguageModel.Model`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiLanguageModel.ProviderMetadata` -> `Prompt.ProviderOptions / Response.ProviderMetadata`: The OpenAI metadata service wrapper was removed; v4 declares OpenAI-specific fields directly on Prompt and Response provider metadata. + +- `OpenAiLanguageModel.ProviderMetadata.Service` -> `Prompt.ProviderOptions / Response.ProviderMetadata`: The OpenAI metadata service wrapper was removed; v4 declares OpenAI-specific fields directly on Prompt and Response provider metadata. + +- `OpenAiLanguageModel.layerWithTokenizer` -> `OpenAiLanguageModel.layer`: The tokenizer-combining layer was removed; provide the language model and any Tokenizer service separately. + +- `OpenAiLanguageModel.modelWithTokenizer` -> `OpenAiLanguageModel.model`: The tokenizer-combining model was removed; use the v4 model descriptor and provide any Tokenizer service separately. + +### `@effect/ai-openai/OpenAiTelemetry` + +- `OpenAiTelemetry.AllAttributes` -> `OpenAiTelemetry.AllAttributes`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiTelemetry.OpenAiTelemetryAttributeOptions` -> `OpenAiTelemetry.OpenAiTelemetryAttributeOptions`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +- `OpenAiTelemetry.addGenAIAnnotations` -> `OpenAiTelemetry.addGenAIAnnotations`: Still exported in v4; update imports and adapt to the revised v4 service and schema types. + +### `@effect/ai-openai/OpenAiTokenizer` + +- `OpenAiTokenizer.layer` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using gpt-tokenizer if equivalent OpenAI counting is required. + +- `OpenAiTokenizer.make` -> `Tokenizer.make`: The provider-specific tokenizer module was removed; build and provide an effect/unstable/ai/Tokenizer service explicitly, using gpt-tokenizer if equivalent OpenAI counting is required. + +### `@effect/ai-openrouter/Generated` + +- `Generated.ActivityItem` -> `Generated.ActivityItem`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.AnnotationDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestProviderSort` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestRoute` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestThinkingEnumType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesRequestToolChoiceEnumType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponse` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseStopReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AnthropicMessagesResponseUsageServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.AssistantMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BadGatewayResponse` -> `Generated.BadGatewayResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BadGatewayResponseErrorData` -> `Generated.BadGatewayResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BadRequestResponse` -> `Generated.BadRequestResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BadRequestResponseErrorData` -> `Generated.BadRequestResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BigNumberUnion` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkAssignKeysToGuardrail200` -> `Generated.BulkAssignKeysToGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkAssignKeysToGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkAssignMembersToGuardrail200` -> `Generated.BulkAssignMembersToGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkAssignMembersToGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkUnassignKeysFromGuardrail200` -> `Generated.BulkUnassignKeysFromGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkUnassignKeysFromGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.BulkUnassignMembersFromGuardrail200` -> `Generated.BulkUnassignMembersFromGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.BulkUnassignMembersFromGuardrailRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CacheControlEphemeral` -> `Generated.ChatContentCacheControl`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatCompletionFinishReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatError` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationParams` -> `Generated.ChatRequest`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatGenerationParamsProviderEnumDataCollectionEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationParamsReasoningEffortEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationParamsRouteEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatGenerationTokenUsage` -> `Generated.ChatUsage`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItem` -> `Generated.ChatContentItems`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemAudio` -> `Generated.ChatContentAudio`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemCacheControl` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatMessageContentItemCacheControlTtl` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatMessageContentItemImage` -> `Generated.ChatContentImage`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemImageImageUrlDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatMessageContentItemText` -> `Generated.ChatContentText`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageContentItemVideo` -> `Generated.ChatContentVideo`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageTokenLogprob` -> `Generated.ChatTokenLogprob`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageTokenLogprobs` -> `Generated.ChatTokenLogprobs`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatMessageToolCall` -> `Generated.ChatToolCall`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatResponse` -> `Generated.ChatResult`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ChatResponseChoice` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ChatStreamOptions` -> `Generated.ChatStreamOptions`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Client` -> `Generated.OpenRouterClient`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ClientError` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionChoice` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionCreateParams` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionFinishReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionFinishReasonEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionLogprobs` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionResponse` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CompletionUsage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateAuthKeysCode200` -> `Generated.CreateAuthKeysCode200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateAuthKeysCodeRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateAuthKeysCodeRequestCodeChallengeMethod` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChargeRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateChargeRequestChainId` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateCoinbaseCharge200` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddings200` -> `Generated.CreateEmbeddings200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateEmbeddings200Object` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingsRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateEmbeddingsRequestEncodingFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateGuardrail201` -> `Generated.CreateGuardrail201`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateGuardrail201DataResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateGuardrailRequest` -> `Generated.CreateGuardrailRequest`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateGuardrailRequestResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateKeys201` -> `Generated.CreateKeys201`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateKeysRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateKeysRequestLimitReset` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages400` -> `Generated.CreateMessages400`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages400Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages401` -> `Generated.CreateMessages401`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages401Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages403` -> `Generated.CreateMessages403`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages403Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages404` -> `Generated.CreateMessages404`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages404Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages429` -> `Generated.CreateMessages429`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages429Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages500` -> `Generated.CreateMessages500`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages500Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages503` -> `Generated.CreateMessages503`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages503Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.CreateMessages529` -> `Generated.CreateMessages529`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.CreateMessages529Type` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DataCollection` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.DefaultParameters` -> `Generated.DefaultParameters`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteGuardrail200` -> `Generated.DeleteGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.DeleteKeys200` -> `Generated.DeleteKeys200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EdgeNetworkTimeoutResponse` -> `Generated.EdgeNetworkTimeoutResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EdgeNetworkTimeoutResponseErrorData` -> `Generated.EdgeNetworkTimeoutResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.EndpointStatus` -> `Generated.EndpointStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ExchangeAuthCodeForAPIKey200` -> `Generated.ExchangeAuthCodeForAPIKey200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ExchangeAuthCodeForAPIKeyRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ExchangeAuthCodeForAPIKeyRequestCodeChallengeMethod` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileAnnotationDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FileCitation` -> `Generated.FileCitation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FileCitationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.FilePath` -> `Generated.FilePath`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.FilePathType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ForbiddenResponse` -> `Generated.ForbiddenResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ForbiddenResponseErrorData` -> `Generated.ForbiddenResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetCredits200` -> `Generated.GetCredits200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetCurrentKey200` -> `Generated.GetCurrentKey200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGeneration200` -> `Generated.GetGeneration200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGeneration200DataApiType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetGenerationParams` -> `Generated.GetGenerationParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGuardrail200` -> `Generated.GetGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetGuardrail200DataResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetKey200` -> `Generated.GetKey200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetModelsParams` -> `Generated.GetModelsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetModelsParamsCategory` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.GetUserActivity200` -> `Generated.GetUserActivity200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.GetUserActivityParams` -> `Generated.GetUserActivityParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ImageGenerationStatus` -> `Generated.ImageGenerationStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InputModality` -> `Generated.InputModality`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InternalServerResponse` -> `Generated.InternalServerResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.InternalServerResponseErrorData` -> `Generated.InternalServerResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.JSONSchemaConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.List200` -> `Generated.List200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEndpoints200` -> `Generated.ListEndpoints200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEndpointsResponse` -> `Generated.ListEndpointsResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListEndpointsResponseArchitecture` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEndpointsResponseArchitectureEnumInstructType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ListEndpointsZdr200` -> `Generated.ListEndpointsZdr200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailKeyAssignments200` -> `Generated.ListGuardrailKeyAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailKeyAssignmentsParams` -> `Generated.ListGuardrailKeyAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailMemberAssignments200` -> `Generated.ListGuardrailMemberAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailMemberAssignmentsParams` -> `Generated.ListGuardrailMemberAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrails200` -> `Generated.ListGuardrails200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListGuardrailsParams` -> `Generated.ListGuardrailsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListKeyAssignments200` -> `Generated.ListKeyAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListKeyAssignmentsParams` -> `Generated.ListKeyAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMemberAssignments200` -> `Generated.ListMemberAssignments200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListMemberAssignmentsParams` -> `Generated.ListMemberAssignmentsParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListParams` -> `Generated.ListParams`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ListProviders200` -> `Generated.ListProviders200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Message` -> `Generated.ChatMessages`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.Model` -> `Generated.Model`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelArchitecture` -> `Generated.ModelArchitecture`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelArchitectureInstructType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ModelGroup` -> `Generated.ModelGroup`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelName` -> `Generated.ModelName`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsCountResponse` -> `Generated.ModelsCountResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsListResponse` -> `Generated.ModelsListResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ModelsListResponseData` -> `Generated.ModelsListResponseData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.NamedToolChoice` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.NotFoundResponse` -> `Generated.NotFoundResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.NotFoundResponseErrorData` -> `Generated.NotFoundResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesAnnotation` -> `Generated.OpenAIResponsesAnnotation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesIncludable` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesIncompleteDetails` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesIncompleteDetailsReason` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesInput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesPrompt` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesReasoningConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesReasoningEffort` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesRefusalContent` -> `Generated.OpenAIResponsesRefusalContent`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesRefusalContentType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesResponseStatus` -> `Generated.OpenAIResponsesResponseStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesToolChoice` -> `Generated.OpenAIResponsesToolChoice`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesToolChoiceEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesToolChoiceEnumType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesToolChoiceEnumTypeEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenAIResponsesTruncation` -> `Generated.OpenAIResponsesTruncation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenAIResponsesUsage` -> `Generated.OpenAIResponsesUsage`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OpenResponsesEasyInputMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesEasyInputMessageRoleEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesEasyInputMessageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionCallOutput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionCallOutputType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionToolCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesFunctionToolCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInputMessageItem` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInputMessageItemRoleEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesInputMessageItemType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesNonStreamingResponse` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesNonStreamingResponseObject` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoning` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesReasoningType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestMetadata` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestRoute` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestServiceTier` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestTruncation` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesRequestTruncationEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesResponseText` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesResponseTextVerbosity` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearch20250826Tool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearch20250826ToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreview20250311Tool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreview20250311ToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreviewTool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchPreviewToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchTool` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenResponsesWebSearchToolType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenRouterAnthropicMessageParam` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OpenRouterAnthropicMessageParamRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemFileSearchCall` -> `Generated.OutputItemFileSearchCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemFileSearchCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemFunctionCall` -> `Generated.OutputItemFunctionCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemFunctionCallStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemFunctionCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemImageGenerationCall` -> `Generated.OutputItemImageGenerationCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemImageGenerationCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemReasoning` -> `Generated.OutputItemReasoning`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemReasoningStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemReasoningType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputItemWebSearchCall` -> `Generated.OutputItemWebSearchCall`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputItemWebSearchCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessage` -> `Generated.OutputMessage`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.OutputMessageRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputMessageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.OutputModality` -> `Generated.OutputModality`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PDFParserEngine` -> `Generated.PDFParserEngine`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PDFParserOptions` -> `Generated.PDFParserOptions`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Parameter` -> `Generated.Parameter`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PayloadTooLargeResponse` -> `Generated.PayloadTooLargeResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PayloadTooLargeResponseErrorData` -> `Generated.PayloadTooLargeResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PaymentRequiredResponse` -> `Generated.PaymentRequiredResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PaymentRequiredResponseErrorData` -> `Generated.PaymentRequiredResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PerRequestLimits` -> `Generated.PerRequestLimits`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PercentileLatencyCutoffs` -> `Generated.PercentileLatencyCutoffs`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PercentileStats` -> `Generated.PercentileStats`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PercentileThroughputCutoffs` -> `Generated.PercentileThroughputCutoffs`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PreferredMaxLatency` -> `Generated.PreferredMaxLatency`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PreferredMinThroughput` -> `Generated.PreferredMinThroughput`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderName` -> `Generated.ProviderName`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderOverloadedResponse` -> `Generated.ProviderOverloadedResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderOverloadedResponseErrorData` -> `Generated.ProviderOverloadedResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderPreferences` -> `Generated.ProviderPreferences`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderPreferencesSort` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProviderSort` -> `Generated.ProviderSort`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderSortConfig` -> `Generated.ProviderSortConfig`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ProviderSortConfigPartitionEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ProviderSortUnion` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicEndpoint` -> `Generated.PublicEndpoint`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.PublicEndpointQuantization` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicEndpointQuantizationEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicEndpointThroughputLast30M` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.PublicPricing` -> `Generated.PublicPricing`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.Quantization` -> `Generated.Quantization`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningDetail` -> `Generated.ReasoningDetailUnion`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ReasoningDetailEncrypted` -> `Generated.ReasoningDetailEncrypted`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningDetailSummary` -> `Generated.ReasoningDetailSummary`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningDetailText` -> `Generated.ReasoningDetailText`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningSummaryText` -> `Generated.ReasoningSummaryText`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningSummaryTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ReasoningSummaryVerbosity` -> `Generated.ReasoningSummaryVerbosity`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningTextContent` -> `Generated.ReasoningTextContent`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ReasoningTextContentType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.RequestTimeoutResponse` -> `Generated.RequestTimeoutResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.RequestTimeoutResponseErrorData` -> `Generated.RequestTimeoutResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseFormatJSONSchema` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatTextConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseFormatTextGrammar` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputAudio` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputAudioInputAudioFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputAudioType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputFile` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputFileType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputImage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputImageDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputImageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputText` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputVideo` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseInputVideoType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseOutputText` -> `Generated.ResponseOutputText`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponseOutputTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseTextConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponseTextConfigVerbosity` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesErrorField` -> `Generated.ResponsesErrorField`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ResponsesErrorFieldCode` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatJSONObject` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatJSONObjectType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatText` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatTextJSONSchemaConfig` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatTextJSONSchemaConfigType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesFormatTextType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesImageGenerationCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesImageGenerationCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFileSearchCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFileSearchCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFunctionCall` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFunctionCallStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemFunctionCallType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoning` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoningFormat` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoningStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputItemReasoningType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessageRole` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessageStatusEnum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputMessageType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesOutputModality` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesSearchContextSize` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchCallOutput` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchCallOutputType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchUserLocation` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ResponsesWebSearchUserLocationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema0` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema1` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema2` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema3` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema4` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema4Enum` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema5` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.Schema6` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.ServiceUnavailableResponse` -> `Generated.ServiceUnavailableResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ServiceUnavailableResponseErrorData` -> `Generated.ServiceUnavailableResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.SystemMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TooManyRequestsResponse` -> `Generated.TooManyRequestsResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.TooManyRequestsResponseErrorData` -> `Generated.TooManyRequestsResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolCallStatus` -> `Generated.ToolCallStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.ToolChoiceOption` -> `Generated.ChatToolChoice`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ToolDefinitionJson` -> `Generated.ChatFunctionTool`: Renamed when the OpenRouter client was regenerated from the current specification; re-check the replacement schema's Type/Encoded shape. + +- `Generated.ToolResponseMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.TopProviderInfo` -> `Generated.TopProviderInfo`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLCitation` -> `Generated.URLCitation`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.URLCitationAnnotationDetail` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.URLCitationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UnauthorizedResponse` -> `Generated.UnauthorizedResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UnauthorizedResponseErrorData` -> `Generated.UnauthorizedResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UnprocessableEntityResponse` -> `Generated.UnprocessableEntityResponse`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UnprocessableEntityResponseErrorData` -> `Generated.UnprocessableEntityResponseErrorData`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateGuardrail200` -> `Generated.UpdateGuardrail200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateGuardrail200DataResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateGuardrailRequest` -> `Generated.UpdateGuardrailRequest`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateGuardrailRequestResetInterval` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateKeys200` -> `Generated.UpdateKeys200`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.UpdateKeysRequest` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UpdateKeysRequestLimitReset` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.UserMessage` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchEngine` -> `Generated.WebSearchEngine`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.WebSearchPreviewToolUserLocation` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchPreviewToolUserLocationType` -> `none`: Removed when the OpenRouter specification client was regenerated; use the current Generated request/response schema for the relevant endpoint instead of this old snapshot helper. + +- `Generated.WebSearchStatus` -> `Generated.WebSearchStatus`: Still generated in v4 from the current OpenRouter specification; re-check the schema's Type/Encoded shape because the generated definition changed. + +- `Generated.make` -> `Generated.make`: Still exported in v4, but the regenerated OpenRouter client has different operations and request/response schemas; update call sites to the current generated service. + +### `@effect/ai-openrouter/OpenRouterClient` + +- `OpenRouterClient.ChatStreamingChoice` -> `Generated.ChatStreamChoice`: The client-local streaming choice schema moved into the regenerated OpenRouter schema surface and changed shape. + +- `OpenRouterClient.ChatStreamingMessageChunk` -> `Generated.ChatStreamDelta`: The client-local streaming message delta moved into the regenerated OpenRouter schema surface and changed shape. + +- `OpenRouterClient.ChatStreamingMessageToolCall` -> `Generated.ChatStreamToolCall`: The client-local streaming tool-call delta moved into the regenerated OpenRouter schema surface and changed shape. + +- `OpenRouterClient.ChatStreamingResponseChunk` -> `OpenRouterClient.ChatStreamingResponseChunkData`: The standalone streaming chunk schema was replaced by the decoded data type from Generated.ChatStreamingResponse. + +- `OpenRouterClient.Service` -> `OpenRouterClient.Service`: Still exported in v4; adapt to the regenerated client, revised request and response schemas, and the new streaming result tuple. + +### `@effect/ai-openrouter/OpenRouterConfig` + +- `OpenRouterConfig.OpenRouterConfig` -> `OpenRouterConfig.OpenRouterConfig`: Still exported in v4; update imports to the v4 package and use the revised Context.Service-based configuration service. + +- `OpenRouterConfig.OpenRouterConfig.Service` -> `OpenRouterConfig.OpenRouterConfig.Service`: Still exported in v4; update imports to the v4 package and use the revised Context.Service-based configuration service. + +### `@effect/ai-openrouter/OpenRouterLanguageModel` + +- `OpenRouterLanguageModel.Config` -> `OpenRouterLanguageModel.Config`: Still exported in v4; update imports and adapt to the regenerated chat request schema and revised Context.Service configuration. + +- `OpenRouterLanguageModel.Config.Service` -> `OpenRouterLanguageModel.Config.Service`: Still exported in v4; update imports and adapt to the regenerated chat request schema and revised Context.Service configuration. + +- `OpenRouterLanguageModel.OpenRouterReasoningInfo` -> `OpenRouterLanguageModel.ReasoningDetails`: The bespoke reasoning-info union was replaced by the provider's raw reasoning-details array, preserved through Prompt options and Response metadata. + +### `@effect/ai/AiError` + +- `AiError.AiError` -> `AiError.AiError`: Moved to effect/unstable/ai/AiError and redesigned from a union of separately tagged errors into one AiError wrapper with a semantic reason. Construct it with AiError.make({ module, method, reason }) and match error.reason rather than the old top-level tags. + +- `AiError.HttpRequestDetails` -> `AiError.HttpRequestDetails`: Retained in effect/unstable/ai/AiError and also re-exported as Response.HttpRequestDetails. Hash is an optional string instead of Option, headers may contain Redacted strings, and method includes TRACE. + +- `AiError.HttpRequestError` -> `AiError.make + AiError.NetworkError`: Replace the old top-level request error with an AiError whose reason is NetworkError. NetworkError.fromRequestError converts a v4 HttpClientError.RequestError. + +- `AiError.HttpResponseError` -> `AiError.make + AiError.reasonFromHttpStatus / AiError.InvalidOutputError`: There is no single v4 response-error class. Wrap a semantic reason with AiError.make: use reasonFromHttpStatus for status failures and InvalidOutputError for decode or empty-body failures. + +- `AiError.MalformedInput` -> `AiError.make + AiError.InvalidUserInputError`: Replace the old top-level input error with an AiError whose reason is InvalidUserInputError. Use InvalidRequestError when the provider request parameters themselves are malformed. + +- `AiError.MalformedOutput` -> `AiError.make + AiError.InvalidOutputError`: Replace the old top-level output error with an AiError whose reason is InvalidOutputError. The old fromParseError helper becomes InvalidOutputError.fromSchemaError. + +- `AiError.TypeId` -> `AiError.isAiError`: The AiError brand is private in v4. Use isAiError for runtime narrowing, or isAiErrorReason for a reason, instead of inspecting or constructing the type id. + +- `AiError.UnknownError` -> `AiError.make + AiError.UnknownError`: UnknownError is now a semantic reason rather than a top-level error. Put module and method on AiError.make and inspect reason.\_tag when handling the outer AiError. + +### `@effect/ai/EmbeddingModel` + +- `EmbeddingModel.Result`: TODO: needs guidance + +- `EmbeddingModel.makeDataLoader` -> `EmbeddingModel.make + RequestResolver.setDelay + RequestResolver.batchN`: The dedicated data-loader constructor was removed. EmbeddingModel.make batches concurrent embed requests through its resolver; compose the exposed resolver with setDelay and optional batchN for the old window and maximum-batch behavior. + +### `@effect/ai/IdGenerator` + +- `IdGenerator.make` -> `IdGenerator.make`: Moved to effect/unstable/ai/IdGenerator with the same configurable alphabet, prefix, separator, and size behavior. Invalid configuration now fails with Cause.IllegalArgumentError. + +### `@effect/ai/LanguageModel` + +- `LanguageModel.ConstructorParams` -> `none`: V4 inlines this provider-adapter shape in LanguageModel.make. Pass generateText and streamText directly to make, with optional codecTransformer, instead of naming a constructor-parameter type. + +- `LanguageModel.ExtractContext` -> `LanguageModel.ExtractServices`: Renamed in effect/unstable/ai/LanguageModel. ExtractServices infers toolkit handler, result-decoding, and effectful-toolkit service requirements. + +### `@effect/ai/McpSchema` + +- `McpSchema.ContentBlock` -> `McpSchema.ContentBlock`: Moved to effect/unstable/ai/McpSchema. It remains the MCP content-block union, but v4 exports it as a const schema rather than a Schema.Union subclass. Binary image, audio, and blob data still use Uint8Array values with base64 wire encoding. + +- `McpSchema.ElicitResult` -> `McpSchema.ElicitResult`: Moved to effect/unstable/ai/McpSchema. It remains discriminated by action, but accepted content is now optional and, when present, is a record of strings, finite numbers, booleans, or string arrays; declined and canceled responses still omit content. + +- `McpSchema.McpError` -> `McpSchema.McpError`: Moved, but changed from a constructable base class to a union schema of standard tagged protocol errors plus McpErrorBase. Use McpErrorBase to construct a generic MCP error. + +- `McpSchema.ParamAnnotation` -> `McpSchema.isParam / Param.name`: The public symbol annotation was removed. Detect parameter wrappers with McpSchema.isParam and read the narrowed Param.name instead of inspecting AST annotations. + +- `McpSchema.param` -> `McpSchema.param`: Moved to effect/unstable/ai/McpSchema. V4 wraps the schema and exposes Param.name and Param.schema instead of attaching a public symbol annotation. + +### `@effect/ai/McpServer` + +- `McpServer.layer` -> `McpServer.layer`: Moved to effect/unstable/ai/McpServer. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025\_06\_18], imported with McpProtocol from effect/unstable/ai; it still runs over a caller-provided RpcServer.Protocol. + +- `McpServer.layerHttp` -> `McpServer.layerHttp`: Moved to effect/unstable/ai/McpServer and the unified HttpRouter. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025\_06\_18], imported with McpProtocol from effect/unstable/ai. + +- `McpServer.layerHttpRouter` -> `McpServer.layerHttp`: Renamed and consolidated. V4 layerHttp registers the Streamable HTTP endpoint in the unified HttpRouter; pass a non-empty protocols array of adapters, such as [McpProtocol.v2025\_06\_18], imported with McpProtocol from effect/unstable/ai. + +- `McpServer.layerStdio` -> `McpServer.layerStdio`: Moved to effect/unstable/ai/McpServer. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025\_06\_18], imported with McpProtocol from effect/unstable/ai. + +- `McpServer.run` -> `McpServer.run`: Moved to effect/unstable/ai/McpServer. Pass a non-empty protocols array of adapters, such as [McpProtocol.v2025\_06\_18], imported with McpProtocol from effect/unstable/ai; it remains the Effect-level runner over RpcServer.Protocol. + +### `@effect/ai/Model` + +- `Model.TypeId` -> `none`: The Model brand still exists internally, but its TypeId is not exported and v4 has no public isModel guard. Use Model values created by Model.make rather than inspecting or constructing the brand. + +### `@effect/ai/Prompt` + +- `Prompt.FilePart` -> `Prompt.FilePart`: Moved to effect/unstable/ai/Prompt with the same file-part model and schema; update the module import. + +- `Prompt.FromJson` -> `Schema.fromJsonString(Prompt.Prompt)`: The module-specific JSON schema was removed. Compose the general v4 JSON-string codec with the public Prompt codec. + +- `Prompt.MessageContentFromString` -> `Prompt.ContentFromString`: Renamed in effect/unstable/ai/Prompt. It still decodes a string to a non-empty array containing one TextPart and encodes the first part's text. + +- `Prompt.MessageTypeId` -> `Prompt.isMessage`: The message type id is private in v4. Use the public isMessage guard for runtime refinement instead of importing or inspecting the marker. + +- `Prompt.Part` -> `Prompt.Part`: Moved to effect/unstable/ai/Prompt. The union now also includes tool-approval request and response parts. + +- `Prompt.PartEncoded` -> `Prompt.PartEncoded`: Moved to effect/unstable/ai/Prompt. The encoded union now also includes tool-approval request and response parts. + +- `Prompt.PartTypeId` -> `Prompt.isPart`: The part type id is private in v4. Use the public isPart guard for runtime refinement instead of importing or inspecting the marker. + +- `Prompt.PromptFromSelf` -> `Prompt.Prompt`: The standalone declared from-self schema was removed. Use the public Prompt codec for prompt validation and encoding, or Prompt.isPrompt when only runtime refinement is needed. + +- `Prompt.ReasoningPart` -> `Prompt.ReasoningPart`: Moved to effect/unstable/ai/Prompt with the same reasoning-part model and schema; update the module import. + +- `Prompt.TextPart` -> `Prompt.TextPart`: Moved to effect/unstable/ai/Prompt with the same text-part model and schema; update the module import. + +- `Prompt.ToolCallPart` -> `Prompt.ToolCallPart`: Moved to effect/unstable/ai/Prompt with the same tool-call model and schema; update the module import. + +- `Prompt.ToolResultPartEncoded` -> `Prompt.ToolResultPartEncoded`: Moved to effect/unstable/ai/Prompt, but providerExecuted was removed from the encoded prompt tool-result shape. + +- `Prompt.TypeId` -> `Prompt.isPrompt`: The Prompt type id is private in v4 and its internal literal changed. Use the public isPrompt guard instead of importing or inspecting the marker. + +- `Prompt.makePart` -> `Prompt.makePart`: Moved to effect/unstable/ai/Prompt. The generic constructor also supports the new tool-approval request and response part variants. + +- `Prompt.merge` -> `Prompt.concat`: Renamed in v4. concat preserves the old dual API and concatenates the messages from a Prompt with additional raw input. + +### `@effect/ai/Response` + +- `Response.Part` -> `Response.Part`: Moved to effect/unstable/ai/Response. The non-streaming union now also includes ToolApprovalRequestPart. + +- `Response.PartTypeId` -> `Response.isPart`: The public PartTypeId was removed and the marker is internal in v4. Use Response.isPart for runtime refinement. + +- `Response.ToolCallPartEncoded` -> `Response.ToolCallPartEncoded`: Moved to effect/unstable/ai/Response; providerName was removed while providerExecuted remains optional when encoded. + +- `Response.ToolResultPart` -> `Response.ToolResultPart`: The schema selects success or failure using isFailure rather than trying both result schemas. It returns Schema.Codec; supply decoding services when decoding and encoding services when encoding. + +- `Response.ToolResultPartEncoded` -> `Response.ToolResultPartEncoded`: Moved to effect/unstable/ai/Response; providerName was removed and optional preliminary was added to the encoded shape. + +- `Response.documentSourcePart` -> `Response.makePart("source", { ...params, sourceType: "document" })`: The lowercase convenience constructor was removed. The DocumentSourcePart model remains, and the generic constructor now requires the document source discriminator. + +- `Response.errorPart` -> `Response.makePart("error", params)`: The lowercase convenience constructor was removed; construct the retained error part through Response.makePart. + +- `Response.finishPart` -> `Response.makePart("finish", params)`: The lowercase convenience constructor was removed. V4 Usage has nested inputTokens and outputTokens objects, and FinishPart adds optional HTTP response details. + +- `Response.reasoningDeltaPart` -> `Response.makePart("reasoning-delta", params)`: The lowercase convenience constructor was removed; construct the retained ReasoningDeltaPart through Response.makePart. + +- `Response.reasoningEndPart` -> `Response.makePart("reasoning-end", params)`: The lowercase convenience constructor was removed; construct the retained ReasoningEndPart through Response.makePart. + +- `Response.reasoningStartPart` -> `Response.makePart("reasoning-start", params)`: The lowercase convenience constructor was removed; construct the retained ReasoningStartPart through Response.makePart. + +- `Response.responseMetadataPart` -> `Response.makePart("response-metadata", params)`: The lowercase convenience constructor was removed. V4 id, modelId, and timestamp are optional raw values rather than Option values, and optional HTTP request details were added. + +- `Response.textDeltaPart` -> `Response.makePart("text-delta", params)`: The lowercase convenience constructor was removed; construct the retained TextDeltaPart through Response.makePart. + +- `Response.textEndPart` -> `Response.makePart("text-end", params)`: The lowercase convenience constructor was removed; construct the retained TextEndPart through Response.makePart. + +- `Response.textStartPart` -> `Response.makePart("text-start", params)`: The lowercase convenience constructor was removed; construct the retained TextStartPart through Response.makePart. + +- `Response.toolParamsDeltaPart` -> `Response.makePart("tool-params-delta", params)`: The lowercase convenience constructor was removed; construct the retained ToolParamsDeltaPart through Response.makePart. + +- `Response.toolParamsEndPart` -> `Response.makePart("tool-params-end", params)`: The lowercase convenience constructor was removed; construct the retained ToolParamsEndPart through Response.makePart. + +- `Response.toolParamsStartPart` -> `Response.makePart("tool-params-start", params)`: The lowercase convenience constructor was removed; providerName was also removed from ToolParamsStartPart in v4. + +- `Response.toolResultPart` -> `Response.toolResultPart`: Moved to effect/unstable/ai/Response; providerName was removed and decoded tool results now require preliminary, normally false. + +- `Response.urlSourcePart` -> `Response.makePart("source", { ...params, sourceType: "url" })`: The lowercase convenience constructor was removed. The UrlSourcePart model remains, and the generic constructor now requires the URL source discriminator. + +### `@effect/ai/Tool` + +- `Tool.AnyParametersSchema` -> `Schema.Constraint`: The AI-specific alias was removed. V4 Tool parameter schemas use the general Schema.Constraint type and are no longer restricted to the old struct-or-EmptyParams union. + +- `Tool.AnyTaggedRequestSchema` -> `none`: The TaggedRequest-specific Tool adapter contract was removed. Model the operation directly with Tool.make and ordinary v4 Schema.Constraint values. + +- `Tool.Destructive` -> `Tool.Destructive`: Moved to effect/unstable/ai/Tool. It is now a Context.Reference\ value rather than a Reference subclass; its default remains true. + +- `Tool.Failure` -> `Tool.Failure`: Moved to effect/unstable/ai/Tool and remains the utility type that extracts a tool's decoded failure type. + +- `Tool.FromTaggedRequest` -> `Tool.Tool`: The dedicated derived alias was removed. Construct with Tool.make and let Tool.Tool infer the name, parameter, success, and failure schemas. + +- `Tool.Idempotent` -> `Tool.Idempotent`: Moved to effect/unstable/ai/Tool. It is now a Context.Reference\ value rather than a Reference subclass; its default remains false. + +- `Tool.OpenWorld` -> `Tool.OpenWorld`: Moved to effect/unstable/ai/Tool. It is now a Context.Reference\ value rather than a Reference subclass; its default remains true. + +- `Tool.ProviderDefinedTypeId` -> `Tool.ProviderDefinedTypeId`: Moved to effect/unstable/ai/Tool and remains public. Its literal changed, so use the export rather than retaining the old hard-coded string. + +- `Tool.Readonly` -> `Tool.Readonly`: Moved to effect/unstable/ai/Tool. It is now a Context.Reference\ value rather than a Reference subclass; its default remains false. + +- `Tool.Requirements` -> `Tool.HandlerServices`: Renamed and refined. HandlerServices combines parameter-decoding, result-encoding, and request-level dependencies required by a tool handler. + +- `Tool.Result` -> `Tool.Result`: The result includes success, declared failure, and execution-denied or execution-interrupted values in both failure modes. Return mode also includes AiError; handle these variants when narrowing results. + +- `Tool.ResultEncoded` -> `Tool.ResultEncoded`: The encoded result includes execution-denied and execution-interrupted variants in both failure modes, plus encoded AiError in return mode. + +- `Tool.Success` -> `Tool.Success`: Moved to effect/unstable/ai/Tool and remains the utility type that extracts a tool's decoded success type. + +- `Tool.Tool.ProviderDefinedProto` -> `Tool.ProviderDefined`: This implementation-brand interface is no longer public. Use Tool.ProviderDefined for the model type and Tool.isProviderDefined for runtime narrowing. + +- `Tool.Tool.Variance` -> `Tool.Tool / Tool.Any`: This implementation variance interface is no longer public; its requirement marker is inline in Tool.Tool. Constrain generic code with Tool.Tool or Tool.Any. + +- `Tool.Tool.VarianceStruct` -> `Tool.Tool / Tool.Any`: This implementation variance payload is no longer public; the requirements marker is inline in Tool.Tool and should not be named independently. + +- `Tool.TypeId` -> `Tool.TypeId`: Moved to effect/unstable/ai/Tool and remains public. Its literal changed, so use the export rather than retaining the old hard-coded string. + +- `Tool.fromTaggedRequest` -> `Tool.make`: The adapter was removed. Rebuild the tool explicitly with Tool.make(name, { parameters, success, failure }); Toolkit.make no longer converts schema values automatically. + +- `Tool.getDescriptionFromSchemaAst` -> `SchemaAST.resolveDescription`: Moved out of Tool to the general v4 AST annotation resolver. For a Tool value, prefer Tool.getDescription. + +- `Tool.getJsonSchemaFromSchemaAst` -> `Tool.getJsonSchemaFromSchema`: Renamed to accept a Schema.Constraint instead of a raw AST and now emits the v4 JSON Schema model. Wrap a raw AST with Schema.make first. + +### `@effect/ai/Toolkit` + +- `Toolkit.HandlersFrom` -> `Toolkit.HandlersFrom`: Moved to effect/unstable/ai/Toolkit. V4 handlers receive a HandlerContext argument and may fail with the declared failure, AiError, or AiErrorReason while requiring Tool.HandlerServices. + +- `Toolkit.TypeId` -> `Toolkit.Toolkit / Toolkit.Any`: The toolkit nominal id is private in v4. Use Toolkit.Toolkit or Toolkit.Any for typing instead of importing or inspecting the marker. + +### `@effect/cli/Args` + +- `Args.All.ArgsAny` -> `Param.AnyArgument`: Use the shared any-positional-parameter type. + +- `Args.All.Return` -> `Command.Command.Config.Infer`: Infer the output of a command config record; standalone argument collections were removed. + +- `Args.Args` -> `Argument.Argument`: Args was renamed to Argument in effect/unstable/cli. + +- `Args.Args.BaseArgsConfig` -> `name: string`: Argument constructors now take the name as a required first parameter. + +- `Args.Args.FormatArgsConfig` -> `Primitive.FileParseOptions`: Pass the name separately and use the format option with Argument.FileParse or Argument.FileSchema. + +- `Args.Args.PathArgsConfig` -> `Argument.Path(name, { pathType, mustExist })`: Path options are inline; map exists=yes to mustExist=true and either to omission. exists=no has no exact replacement. + +- `Args.Args.Variance` -> `Argument.Argument`: The separate variance artifact was removed; Argument inherits the shared Param variance. + +- `Args.ArgsTypeId` -> `Param.isParam(value) && value.kind === Param.argumentKind`: The public Args type id was removed; use the Param guard and argument kind discriminator. + +- `Args.all` -> `Command.make(name, config)`: Collect arguments in the config record passed to Command.make; there is no standalone Argument.all. + +- `Args.atLeast` -> `Argument.atLeast`: Use the moved combinator; v4 returns ReadonlyArray and does not encode non-emptiness in the type. + +- `Args.atMost` -> `Argument.atMost`: Use the moved combinator. + +- `Args.between` -> `Argument.between`: Use the moved combinator; v4 validates bounds when constructing the parameter. + +- `Args.boolean` -> `Flag.Boolean / Argument.ChoiceWithValue`: Positional booleans were removed as ambiguous; prefer a boolean flag or explicit true/false positional choices. + +- `Args.choice` -> `Argument.Literals`: Use the renamed constructor and pass the argument name explicitly. + +- `Args.date` -> `Argument.Date`: Use the renamed constructor and pass the argument name explicitly. + +- `Args.directory` -> `Argument.Directory`: Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement. + +- `Args.file` -> `Argument.File`: Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement. + +- `Args.fileContent` -> `Argument.File + Argument.mapEffect`: Parse a path and read it with FileSystem.readFile; no binary-content argument constructor remains. + +- `Args.fileParse` -> `Argument.FileParse`: Pass the old format as an options field; v4 returns parsed content rather than a path/content tuple. + +- `Args.fileSchema` -> `Argument.FileSchema`: Pass the old format as an options field and use a v4 Schema constraint decoder. + +- `Args.fileText` -> `Argument.FileText`: Use the renamed constructor; it returns content only. + +- `Args.float` -> `Argument.Finite`: Use the renamed constructor; it rejects non-finite numbers. + +- `Args.getHelp` -> `none`: Per-argument help introspection was removed; Command generates help internally. + +- `Args.getIdentifier` -> `none`: Public argument identifier introspection was removed. + +- `Args.getMaxSize` -> `none`: Public arity introspection was removed; command parsing enforces variadic bounds internally. + +- `Args.getMinSize` -> `none`: Public arity introspection was removed; command parsing enforces variadic bounds internally. + +- `Args.getUsage` -> `none`: The public Usage tree was removed; Command generates a usage string internally. + +- `Args.integer` -> `Argument.Int`: Use the renamed constructor and pass the argument name explicitly. + +- `Args.isArgs` -> `Param.isParam(value) && value.kind === Param.argumentKind`: Arguments now use the shared Param representation and an explicit kind discriminator. + +- `Args.map` -> `Argument.map`: Use the moved combinator. + +- `Args.mapEffect`: TODO: needs guidance + +- `Args.none` -> `omit the config entry`: V4 Argument.Never is an always-failing sentinel, not v3's empty successful argument set. + +- `Args.optional` -> `Argument.optional`: Use the moved combinator; it still returns Option. + +- `Args.path` -> `Argument.Path`: Path options are inline; map exists=yes to mustExist=true and either to omission. exists=no has no exact replacement. + +- `Args.redacted` -> `Argument.Redacted`: Use the renamed constructor and pass the argument name explicitly. + +- `Args.repeated` -> `Argument.variadic`: Renamed to variadic; pass optional min and max bounds. + +- `Args.secret` -> `Argument.Redacted`: Use Redacted-backed positional input. + +- `Args.text` -> `Argument.String`: Renamed to String; pass the argument name explicitly. + +- `Args.validate` -> `argument.parse({ flags: {}, arguments: args })`: Parsing is now a Param method and returns leftover tokens with the value; errors are CliError. + +- `Args.withDefault` -> `Argument.withDefault`: Use the moved combinator; v4 also accepts an Effect fallback. + +- `Args.withDescription` -> `Argument.withDescription`: Use the moved combinator. + +- `Args.withFallbackConfig` -> `Argument.withFallbackConfig`: Use the moved combinator; invalid configuration becomes CliError.InvalidValue. + +- `Args.withSchema` -> `Argument.withSchema`: Use the moved combinator with a v4 Schema constraint decoder. + +### `@effect/cli/BuiltInOptions` + +- `BuiltInOptions.BuiltInOptions` -> `GlobalFlag.BuiltIn`: The parsed directive union became a union of global Action and Setting definitions. + +- `BuiltInOptions.BuiltInOptions.ShellType` -> `Completions.Shell`: The shell union moved to Completions. + +- `BuiltInOptions.SetLogLevel` -> `GlobalFlag.LogLevel`: Log level is now a global Setting whose parsed value is provided through context. + +- `BuiltInOptions.ShowCompletions` -> `GlobalFlag.Completions`: The directive payload was replaced by a global completion action definition. + +- `BuiltInOptions.ShowHelp` -> `GlobalFlag.Help`: The directive payload was replaced by a global help action definition. + +- `BuiltInOptions.ShowVersion` -> `GlobalFlag.Version`: The directive value was replaced by a global version action definition. + +- `BuiltInOptions.ShowWizard` -> `GlobalFlag.Wizard`: The directive payload was replaced by a global wizard action definition. + +- `BuiltInOptions.builtInOptions` -> `GlobalFlag.BuiltIns`: Built-ins are global flag definitions consumed automatically by Command.run and Command.runWith. + +- `BuiltInOptions.isShowCompletions` -> `none`: Parsed ShowCompletions directives were removed; the runner processes GlobalFlag.Completions directly. + +- `BuiltInOptions.isShowHelp` -> `none`: Parsed ShowHelp directives were removed; the runner processes GlobalFlag.Help directly. + +- `BuiltInOptions.isShowVersion` -> `none`: Parsed ShowVersion directives were removed; the runner processes GlobalFlag.Version directly. + +- `BuiltInOptions.isShowWizard` -> `none`: Parsed ShowWizard directives were removed; the runner processes GlobalFlag.Wizard directly. + +- `BuiltInOptions.showCompletions` -> `GlobalFlag.Completions`: Use the built-in completion action; the shell is parsed from --completions. + +- `BuiltInOptions.showHelp` -> `GlobalFlag.Help`: Use the built-in help action; usage and help are derived from the active Command. + +- `BuiltInOptions.showVersion` -> `GlobalFlag.Version`: Use the built-in version action; supply the version to Command.run or Command.runWith. + +- `BuiltInOptions.showWizard` -> `GlobalFlag.Wizard`: Use the built-in wizard action; runner context supplies the active Command. + +### `@effect/cli/CliApp` + +- `CliApp.CliApp` -> `Command.Command`: The separate application wrapper was folded into the runnable v4 Command tree. + +- `CliApp.CliApp.ConstructorArgs` -> `none`: Build the command with Command.make and withDescription, then pass version to Command.run; the old app constructor shape was removed. + +- `CliApp.make` -> `Command.make`: Build the executable Command directly; there is no separate CliApp wrapper. + +- `CliApp.run` -> `Command.run`: The CliApp wrapper was removed. Attach the execute function with Command.withHandler, then run the Command with its version; v4 reads arguments through the CLI environment instead of accepting args and execute at this call. + +### `@effect/cli/CliConfig` + +- `CliConfig.CliConfig` -> `CliConfig.CliConfig.Service`: The service was redesigned to configure built-in global flags; old parser and help switches were removed. + +- `CliConfig.defaultConfig` -> `CliConfig.defaults`: Renamed to defaults with the redesigned service shape. + +- `CliConfig.defaultLayer` -> `CliConfig.layer`: Call CliConfig.layer() to provide the defaults. + +- `CliConfig.layer` -> `CliConfig.layer`: The layer constructor remains, but its options configure the redesigned CliConfig.Service for built-in global flags. + +- `CliConfig.make` -> `CliConfig.make`: The constructor remains but accepts the redesigned service options. + +- `CliConfig.normalizeCase` -> `none`: Case normalization is no longer configurable through CliConfig. + +### `@effect/cli/Command` + +- `Command.Command.Config`: TODO: needs guidance + +- `Command.Command.Context` -> `Command.CommandContext`: Renamed to CommandContext. + +- `Command.Command.ParseConfig` -> `Command.Command.Config.Infer`: Use the v4 command-config inference helper. + +- `Command.Command.ParseConfigValue` -> `Command.Command.Config.InferValue`: Use the v4 command-config value inference helper. + +- `Command.Command.ParsedConfig` -> `none`: The parsed config representation is internal in v4. + +- `Command.Command.ParsedConfigNode` -> `none`: The parsed config node representation is internal in v4. + +- `Command.Command.ParsedConfigTree` -> `none`: The parsed config tree representation is internal in v4. + +- `Command.Command.Transform` -> `none`: The handler transformation type and machinery are internal in v4. + +- `Command.TypeId` -> `Command.isCommand`: The type id is internal in v4; use the public runtime guard. + +- `Command.fromDescriptor` -> `Command.make`: The descriptor layer was folded into Command; define config and handler directly on Command.make. + +- `Command.getBashCompletions` -> `Completions.generate`: Generation now returns one script string; normally use GlobalFlag.Completions through the runner. + +- `Command.getFishCompletions` -> `Completions.generate`: Generation now returns one script string; normally use GlobalFlag.Completions through the runner. + +- `Command.getHelp` -> `none`: Help generation for a command path is internal; use GlobalFlag.Help through Command.run or runWith. + +- `Command.getNames` -> `Command.Command.name / Command.Command.alias`: Read the public name and optional alias fields; no HashSet accessor remains. + +- `Command.getSubcommands` -> `Command.Command.subcommands`: Read the public grouped subcommands field; its shape is no longer a name map. + +- `Command.getUsage` -> `none`: Usage is generated internally as part of structured HelpDoc. + +- `Command.getZshCompletions` -> `Completions.generate`: Generation now returns one script string; normally use GlobalFlag.Completions through the runner. + +- `Command.make` -> `Command.make`: Use the redesigned constructor with one nested config object of Argument and Flag values. + +- `Command.run` -> `Command.runWith`: Use runWith for the v3-style function that accepts an argv array; use run to read arguments from Stdio. + +- `Command.transformHandler` -> `none`: Transform in the handler or use the specific provide combinators; the generic handler transform is internal. + +- `Command.withDescription` -> `Command.withDescription`: Use the retained combinator; v4 descriptions are strings rather than the old HelpDoc ADT. + +### `@effect/cli/CommandDescriptor` + +- `CommandDescriptor.Command.ComputeParsedType` -> `Types.Simplify`: Use the general simplification utility, or Command.Command.Config.Infer for command config. + +- `CommandDescriptor.Command.GetParsedType` -> `none`: No public parsed-input extractor remains; v4 handlers receive inferred config directly. + +- `CommandDescriptor.Command.ParsedStandardCommand` -> `none`: The name/options/args parsed wrapper was removed; handlers receive inferred config directly. + +- `CommandDescriptor.Command.ParsedUserInputCommand` -> `none`: The descriptor-level prompt command was removed; use Prompt APIs or Command.wizard. + +- `CommandDescriptor.Command.Subcommands` -> `none`: Compose independently handled commands with Command.withSubcommands instead of parsing a tuple union. + +- `CommandDescriptor.Command.Variance` -> `Command.Command.Variance`: The command variance helper remains conceptually, now tracking input, error, and requirements. + +- `CommandDescriptor.TypeId` -> `Command.isCommand`: The type id is internal in v4; use the public runtime guard. + +- `CommandDescriptor.getBashCompletions` -> `Completions.generate`: Completion generation moved to one shell-parameterized function; command conversion is internal. + +- `CommandDescriptor.getFishCompletions` -> `Completions.generate`: Completion generation moved to one shell-parameterized function; command conversion is internal. + +- `CommandDescriptor.getHelp` -> `none`: Help generation is internal to the Command runner. + +- `CommandDescriptor.getNames` -> `Command.Command.name / Command.Command.alias`: Read the public fields; no HashSet accessor remains. + +- `CommandDescriptor.getSubcommands` -> `Command.Command.subcommands`: Read the public grouped subcommands field. + +- `CommandDescriptor.getUsage` -> `none`: Usage generation is internal to Command help generation. + +- `CommandDescriptor.getZshCompletions` -> `Completions.generate`: Completion generation moved to one shell-parameterized function; command conversion is internal. + +- `CommandDescriptor.make` -> `Command.make`: The descriptor and executable command layers were merged into one constructor. + +- `CommandDescriptor.map` -> `none`: Map individual Argument or Flag values, or transform inside the command handler. + +- `CommandDescriptor.mapEffect` -> `none`: Use parameter mapEffect where the transformation belongs to an input, or perform the Effect in the handler. + +- `CommandDescriptor.parse` -> `Command.runWith`: Parsing was folded into execution and no intermediate CommandDirective is returned. + +- `CommandDescriptor.withDescription` -> `Command.withDescription`: Use the retained behavior; v4 descriptions are strings. + +### `@effect/cli/CommandDirective` + +- `CommandDirective.BuiltIn` -> `GlobalFlag.Action`: Use the global action definition type; it is processed directly by the runner. + +- `CommandDirective.CommandDirective` -> `none`: The intermediate parse-result model was removed; the runner invokes the selected handler directly. + +- `CommandDirective.UserDefined` -> `none`: The user-defined intermediate directive was removed. + +- `CommandDirective.builtIn` -> `GlobalFlag.Action`: Define a custom action flag; v4 runners no longer return built-in directives. + +- `CommandDirective.isBuiltIn` -> `none`: Intermediate built-in directives were removed. + +- `CommandDirective.map` -> `none`: Map parameters or transform in the handler; there is no intermediate directive to map. + +- `CommandDirective.userDefined` -> `none`: Parsed input is delivered directly to the selected command handler. + +### `@effect/cli/ConfigFile` + +- `ConfigFile.ConfigErrorTypeId` -> `none`: ConfigProvider.SourceError has no public type-id export. + +- `ConfigFile.ConfigFileError` -> `ConfigProvider.SourceError`: Use the general source error when implementing a custom file-backed provider. + +- `ConfigFile.layer` -> `ConfigProvider.layerAdd(customProviderEffect)`: Build the provider explicitly and add it as fallback to preserve the v3 composition order. + +- `ConfigFile.makeProvider` -> `none`: V4 has no API that discovers, parses, and composes config files; use FileSystem, a format parser, and ConfigProvider.fromUnknown explicitly. + +### `@effect/cli/HelpDoc` + +- `HelpDoc.DescriptionList` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.Empty` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.Enumeration` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.Header` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.HelpDoc` -> `HelpDoc.HelpDoc`: The name remains, but v4 is a structured command-help record rather than a tagged document AST. + +- `HelpDoc.Paragraph` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.Sequence` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.blocks` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.descriptionList` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.empty` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.enumeration` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.getSpan` -> `none`: The Span ADT and document-to-span conversion were removed; v4 help fields are strings. + +- `HelpDoc.h1` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.h2` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.h3` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.isDescriptionList` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.isEmpty` -> `none`: The Empty variant was removed when HelpDoc became a structured record; inspect the relevant flags, args, subcommands, and examples arrays when an application-specific emptiness test is needed. + +- `HelpDoc.isEnumeration` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.isHeader` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.isParagraph` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.isSequence` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.mapDescriptionList` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.orElse` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.p` -> `none`: The v3 document-node ADT was removed; v4 uses a structured HelpDoc record rendered by CliOutput. + +- `HelpDoc.toAnsiDoc` -> `none`: CliOutput owns rendering and exposes formatted text rather than an AnsiDoc. + +- `HelpDoc.toAnsiText` -> `CliOutput.defaultFormatter().formatHelpDoc`: Format the structured help record; inside Effect code prefer the CliOutput.Formatter service. + +### `@effect/cli/Options` + +- `Options.All.OptionsAny` -> `Param.AnyFlag`: Use the shared any-flag-parameter type. + +- `Options.All.Return` -> `Command.Command.Config.Infer`: Infer the output of a command config record; standalone flag collections were removed. + +- `Options.Options` -> `Flag.Flag`: Options was renamed to Flag in effect/unstable/cli. + +- `Options.Options.BooleanOptionsConfig` -> `Flag.Boolean + Flag.withAlias + Flag.map`: The config object was removed; aliases and value inversion are combinators, while custom negation names need application logic. + +- `Options.Options.PathOptionsConfig` -> `{ readonly mustExist?: boolean }`: Path options are inline; true replaces exists=yes and omission replaces either. exists=no has no exact replacement. + +- `Options.Options.Variance` -> `Flag.Flag`: The separate variance artifact was removed; Flag inherits the shared Param variance. + +- `Options.OptionsTypeId` -> `Param.isParam(value) && value.kind === Param.flagKind`: The public Options type id was removed; use the Param guard and flag kind discriminator. + +- `Options.all` -> `Command.make(name, config)`: Collect flags in the config record passed to Command.make; there is no standalone Flag.all. + +- `Options.atLeast` -> `Flag.atLeast`: Use the moved combinator; v4 returns ReadonlyArray rather than NonEmptyArray. + +- `Options.atMost` -> `Flag.atMost`: Use the moved combinator. + +- `Options.between` -> `Flag.between`: Use the moved combinator; v4 validates bounds when constructing the parameter. + +- `Options.boolean` -> `Flag.Boolean + Flag.withDefault`: Use Flag.Boolean(name).pipe(Flag.withDefault(false)) to preserve v3's omitted-flag default; bare Flag.Boolean is now required. --no-name is automatic and aliases are added with Flag.withAlias. + +- `Options.choice` -> `Flag.Literals`: Use the moved constructor. + +- `Options.choiceWithValue` -> `Flag.ChoiceWithValue`: Use the moved constructor. + +- `Options.date` -> `Flag.Date`: Use the moved constructor. + +- `Options.directory` -> `Flag.Directory`: Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement. + +- `Options.file` -> `Flag.File`: Use mustExist=true for exists=yes and omit it for either; exists=no has no exact replacement. + +- `Options.fileContent` -> `Flag.File + Flag.mapEffect`: Parse a path and read it with FileSystem.readFile; no binary-content flag constructor remains. + +- `Options.fileParse` -> `Flag.FileParse`: Pass the old format as an options field; v4 returns parsed content rather than a path/content tuple. + +- `Options.fileSchema` -> `Flag.FileSchema`: Pass the old format as an options field and use a v4 Schema constraint decoder. + +- `Options.fileText` -> `Flag.File + Flag.mapEffect`: Flag.FileText returns content only; read after Flag.File when the path/content tuple must be preserved. + +- `Options.filterMap` -> `Flag.filterMap`: Use the moved combinator and replace the fixed message with an onNone function. + +- `Options.float` -> `Flag.Finite`: Use the moved constructor. + +- `Options.getHelp` -> `none`: Per-flag help introspection was removed; Command generates help internally. + +- `Options.getIdentifier` -> `none`: Public flag identifier introspection was removed. + +- `Options.getUsage` -> `none`: The public Usage tree was removed; Command generates a usage string internally. + +- `Options.integer` -> `Flag.Int`: Use the moved constructor. + +- `Options.isBool` -> `none`: No public flag-shape predicate remains; boolean-shape inspection is internal. + +- `Options.isOptions` -> `Param.isParam(value) && value.kind === Param.flagKind`: Flags now use the shared Param representation and an explicit kind discriminator. + +- `Options.keyValueMap` -> `Flag.KeyValuePair`: Renamed and now returns Record\ rather than HashMap. + +- `Options.map` -> `Flag.map`: Use the moved combinator. + +- `Options.mapEffect` -> `Flag.mapEffect`: Use the moved combinator; mapping failures are CliError. + +- `Options.mapTryCatch` -> `Flag.mapTryCatch`: Use the moved combinator; onError now returns a string rather than HelpDoc. + +- `Options.none` -> `omit the config entry`: V4 Flag.Never is an always-failing sentinel, not v3's empty successful option set. + +- `Options.optional` -> `Flag.optional`: Use the moved combinator; it still returns Option. + +- `Options.orElse` -> `Flag.orElse(() => fallback)`: The fallback is now lazy; add explicit exclusivity validation if both flags must be rejected. + +- `Options.orElseEither` -> `Flag.orElseResult(() => fallback)`: Either became Result and the fallback is lazy; v4 no longer rejects both flags being present. + +- `Options.parse` -> `flag.parse({ flags, arguments: [] })`: Parsing is now a Param method over a Record and returns leftover arguments with the value; errors are CliError. + +- `Options.processCommandLine` -> `Command.runWith`: Raw argv processing is now whole-command execution; no public standalone flag tokenizer remains. + +- `Options.redacted` -> `Flag.Redacted`: Use the moved constructor. + +- `Options.repeated` -> `Flag.variadic`: Renamed to variadic; pass optional min and max bounds. + +- `Options.secret` -> `Flag.Redacted`: The deprecated Secret constructor was removed; use Redacted-backed input. + +- `Options.text` -> `Flag.String`: Renamed from text to String. + +- `Options.withAlias` -> `Flag.withAlias`: Use the moved combinator. + +- `Options.withDefault` -> `Flag.withDefault`: Use the moved combinator; v4 also accepts an Effect fallback. + +- `Options.withDescription` -> `Flag.withDescription`: Use the moved combinator. + +- `Options.withFallbackConfig` -> `Flag.withFallbackConfig`: Use the moved combinator; invalid configuration becomes CliError.InvalidValue. + +- `Options.withFallbackPrompt` -> `Flag.withFallbackPrompt`: Use the moved combinator; v4 can construct the Prompt lazily in Effect. + +- `Options.withPseudoName` -> `Flag.withMetavar`: Renamed to withMetavar. + +- `Options.withSchema` -> `Flag.withSchema`: Use the moved combinator with a v4 Schema constraint decoder. + +### `@effect/cli/Primitive` + +- `Primitive.Primitive.PathExists` -> `mustExist?: boolean`: Use true for yes and omit for either; no cannot be represented exactly because false permits existing paths. + +- `Primitive.Primitive.ValueType` -> `P extends Primitive.Primitive ? A : never`: The named helper was removed; infer the value with a local conditional type. + +- `Primitive.Primitive.Variance` -> `Primitive.Primitive.Variance`: The variance interface remains, but its brand key is internal; prefer Primitive\ in user APIs. + +- `Primitive.PrimitiveTypeId` -> `none`: The public Primitive type-id symbol was removed. + +- `Primitive.boolean` -> `Primitive.Boolean`: Boolean is now a singleton value; defaults belong on Flag.Boolean or withDefault. + +- `Primitive.choice` -> `Primitive.Choice`: Use the moved constructor. + +- `Primitive.date` -> `Primitive.Date`: Date is now a singleton Primitive value. + +- `Primitive.float` -> `Primitive.Finite`: Finite is now a singleton Primitive value and rejects non-finite numbers. + +- `Primitive.getChoices` -> `none`: Choice introspection is internal in v4; retain alternatives in application code when needed. + +- `Primitive.getHelp` -> `none`: Primitive-level help generation was removed from the public API. + +- `Primitive.integer` -> `Primitive.Int`: Int is now a singleton Primitive value. + +- `Primitive.isBool` -> `none`: The boolean Primitive predicate is internal in v4. + +- `Primitive.text` -> `Primitive.String`: Renamed from text to String. + +- `Primitive.validate` -> `primitive.parse(value)`: Parsing is now the Primitive.parse method over a string; defaults and case normalization moved out of this layer. + +### `@effect/cli/Prompt` + +- `Prompt.All.PromptAny` -> `Prompt.Any`: The any-prompt alias moved out of the All namespace. + +- `Prompt.All.Return` -> `Prompt.All.Return`: The collection result helper remains under Prompt.All. + +- `Prompt.Prompt` -> `Prompt.Prompt`: The model moved to effect/unstable/cli; quitting now fails with Terminal.QuitError. + +- `Prompt.Prompt.FloatOptions` -> `Prompt.NumberOptions`: Use the renamed public options type for Prompt.Number; it still extends the integer options type, now IntOptions. + +- `Prompt.Prompt.IntegerOptions` -> `Prompt.IntOptions`: Use the renamed public options type for Prompt.Int; option fields are preserved. + +- `Prompt.Prompt.Variance` -> `Prompt.Prompt`: The named variance artifact was removed; use Prompt\. + +- `Prompt.Prompt.VarianceStruct` -> `Prompt.Prompt`: The named variance structure was removed; use Prompt\. + +- `Prompt.PromptTypeId` -> `Prompt.isPrompt`: The public type-id symbol was removed; use the runtime guard. + +- `Prompt.confirm` -> `Prompt.Confirm`: Use the renamed constructor. + +- `Prompt.custom` -> `Prompt.Custom`: Use the renamed constructor; both overloads are preserved. + +- `Prompt.date` -> `Prompt.Date`: Use the moved constructor. + +- `Prompt.file` -> `Prompt.File`: Use the moved constructor; v4 also supports a default selected path. + +- `Prompt.float` -> `Prompt.Number`: Use the moved constructor; v4 also supports a default value. + +- `Prompt.hidden` -> `Prompt.Hidden`: Use the renamed constructor. + +- `Prompt.integer` -> `Prompt.Int`: Use the moved constructor; v4 also supports a default value. + +- `Prompt.list` -> `Prompt.List`: Use the renamed constructor. + +- `Prompt.multiSelect` -> `Prompt.MultiSelect`: Use the renamed constructor. + +- `Prompt.password` -> `Prompt.Password`: Use the renamed constructor. + +- `Prompt.select` -> `Prompt.Select`: Use the renamed constructor. + +- `Prompt.text` -> `Prompt.String`: Use the moved constructor. + +- `Prompt.toggle` -> `Prompt.Toggle`: Use the renamed constructor. + +### `@effect/cli/ValidationError` + +- `ValidationError.CommandMismatch` -> `none`: The root-command mismatch error was removed. + +- `ValidationError.CorrectedFlag` -> `CliError.UnrecognizedOption`: Use the unrecognized-option class and its suggestions field. + +- `ValidationError.HelpRequested` -> `CliError.ShowHelp`: Renamed and redesigned as ShowHelp. + +- `ValidationError.InvalidArgument` -> `CliError.InvalidValue | CliError.UnexpectedArgument`: Argument decoding and leftover operands are separate v4 errors. + +- `ValidationError.InvalidValue` -> `CliError.InvalidValue`: Use the schema-backed v4 error class. + +- `ValidationError.MissingFlag` -> `CliError.MissingOption`: Renamed to MissingOption. + +- `ValidationError.MissingSubcommand` -> `none`: The dedicated missing-subcommand error was removed. + +- `ValidationError.MissingValue` -> `CliError.InvalidValue`: The dedicated tag was folded into InvalidValue. + +- `ValidationError.MultipleValuesDetected` -> `CliError.InvalidValue`: Count violations are summarized as InvalidValue without preserving the old values array. + +- `ValidationError.NoBuiltInMatch` -> `none`: The intermediate built-in matching error was removed. + +- `ValidationError.UnclusteredFlag` -> `none`: The public cluster error was removed. + +- `ValidationError.ValidationError` -> `CliError.CliError`: The validation union was redesigned and renamed to CliError. + +- `ValidationError.ValidationError.Proto` -> `none`: V4 errors are schema-backed classes and expose no shared public prototype type. + +- `ValidationError.ValidationErrorTypeId` -> `none`: The CliError type id is private; use CliError.isCliError. + +- `ValidationError.commandMismatch` -> `none`: V4 runners receive arguments after the root name; unknown child commands use CliError.UnknownSubcommand. + +- `ValidationError.correctedFlag` -> `new CliError.UnrecognizedOption({ option, command, suggestions })`: Suggestions are carried by UnrecognizedOption; there is no separate corrected-flag case. + +- `ValidationError.helpRequested` -> `new CliError.ShowHelp({ commandPath, errors: [] })`: Help requests now carry a command path and optional underlying errors. + +- `ValidationError.invalidArgument` -> `new CliError.InvalidValue({ option, value, expected, kind: "argument" })`: Use InvalidValue for undecodable arguments and UnexpectedArgument for leftover operands. + +- `ValidationError.invalidValue` -> `new CliError.InvalidValue({ option, value, expected, kind })`: Replace the HelpDoc payload with structured option, value, expected, and kind fields. + +- `ValidationError.isCommandMismatch` -> `none`: The root-command mismatch error was removed. + +- `ValidationError.isCorrectedFlag` -> `error._tag === "UnrecognizedOption" && error.suggestions.length > 0`: Check the v4 tag and suggestions array. + +- `ValidationError.isHelpRequested` -> `error._tag === "ShowHelp"`: Narrow the CliError union by its tag. + +- `ValidationError.isInvalidArgument` -> `(error._tag === "InvalidValue" && error.kind === "argument") || error._tag === "UnexpectedArgument"`: Check both v4 argument error forms. + +- `ValidationError.isInvalidValue` -> `error._tag === "InvalidValue"`: Narrow the CliError union by its tag. + +- `ValidationError.isMissingFlag` -> `error._tag === "MissingOption"`: MissingFlag was renamed to MissingOption. + +- `ValidationError.isMissingSubcommand` -> `none`: Missing subcommands now cause ShowHelp rather than a dedicated error. + +- `ValidationError.isMissingValue` -> `error._tag === "InvalidValue" && error.value === ""`: Missing values are represented as InvalidValue with an empty value. + +- `ValidationError.isMultipleValuesDetected` -> `none`: Count violations are summarized as InvalidValue without a stable subtype. + +- `ValidationError.isNoBuiltInMatch` -> `none`: Built-ins are GlobalFlag definitions and the intermediate failure was removed. + +- `ValidationError.isUnclusteredFlag` -> `none`: Cluster expansion is internal and has no public intermediate error. + +- `ValidationError.isValidationError` -> `CliError.isCliError`: Use the renamed union guard. + +- `ValidationError.keyValuesDetected` -> `new CliError.InvalidValue({ option, value, expected, kind: "flag" })`: Represent count violations with structured InvalidValue fields. + +- `ValidationError.missingFlag` -> `new CliError.MissingOption({ option })`: MissingFlag was renamed to MissingOption. + +- `ValidationError.missingSubcommand` -> `none`: A parent without a selected subcommand now shows help rather than emitting a dedicated error. + +- `ValidationError.missingValue` -> `new CliError.InvalidValue({ option, value: "", expected, kind })`: Missing values are represented as InvalidValue with an empty value. + +- `ValidationError.noBuiltInMatch` -> `none`: Built-ins are GlobalFlag definitions and the intermediate failure was removed. + +- `ValidationError.unclusteredFlag` -> `none`: Flag cluster expansion is internal in v4. + +### `@effect/cluster/ClusterCron` + +- `ClusterCron.make` -> `effect/unstable/cluster/ClusterCron#make`: Moved into core Effect. The constructor remains; Duration.DurationInput is now Duration.Input. + +### `@effect/cluster/ClusterError` + +- `ClusterError.TypeId` -> `none`: The shared marker is private in v4. Use the exported tagged error classes, their \_tag fields, or class-specific is guards. + +### `@effect/cluster/ClusterSchema` + +- `ClusterSchema.ClientTracingEnabled` -> `effect/unstable/cluster/ClusterSchema#ClientTracingEnabled`: Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value. + +- `ClusterSchema.Persisted` -> `effect/unstable/cluster/ClusterSchema#Persisted`: Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same false default. + +- `ClusterSchema.ShardGroup` -> `effect/unstable/cluster/ClusterSchema#ShardGroup`: Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value. + +- `ClusterSchema.Uninterruptible` -> `effect/unstable/cluster/ClusterSchema#Uninterruptible`: Now a Context.Reference value. Replace its static methods with ClusterSchema.isUninterruptibleForServer and isUninterruptibleForClient. + +### `@effect/cluster/ClusterWorkflowEngine` + +- `ClusterWorkflowEngine.layer` -> `effect/unstable/cluster/ClusterWorkflowEngine#layer`: Moved into core Effect with the same cluster-backed WorkflowEngine layer composition. + +- `ClusterWorkflowEngine.make` -> `effect/unstable/cluster/ClusterWorkflowEngine#make`: Moved into core Effect; the constructor still uses Sharding and MessageStorage. + +### `@effect/cluster/DeliverAt` + +- `DeliverAt.symbol` -> `effect/unstable/cluster/DeliverAt#symbol`: Moved into core Effect; the protocol key is now the string literal \~effect/cluster/DeliverAt rather than a global symbol. + +### `@effect/cluster/Entity` + +- `Entity.HandlersFrom` -> `effect/unstable/cluster/Entity#HandlersFrom`: Moved into core Effect; handler results now use Rpc.WrapperOr, which accepts either the raw RPC result or its wrapper. + +- `Entity.Replier.Success` -> `effect/unstable/cluster/Entity#Replier.Success`: Moved into core Effect; streaming replies may use Queue.Dequeue with Cause.Done instead of the removed Mailbox type. + +- `Entity.TypeId` -> `none`: The entity marker is private in v4. Use Entity.isEntity for runtime refinement. + +### `@effect/cluster/EntityAddress` + +- `EntityAddress.EntityAddressFromSelf` -> `effect/unstable/cluster/EntityAddress#EntityAddress`: The separate self schema was removed; the v4 Schema.Class is itself the EntityAddress schema. + +- `EntityAddress.TypeId` -> `none`: The marker is private in v4. Use the exported EntityAddress class and schema. + +### `@effect/cluster/EntityProxy` + +- `EntityProxy.ConvertHttpApi` -> `effect/unstable/cluster/EntityProxy#ConvertHttpApi`: Moved into core Effect and updated to the v4 HttpApiEndpoint and Schema types. + +- `EntityProxy.ConvertRpcs` -> `effect/unstable/cluster/EntityProxy#ConvertRpcs`: Moved into core Effect and updated to the v4 Rpc and Schema type parameters. + +### `@effect/cluster/EntityProxyServer` + +- `EntityProxyServer.RpcHandlers` -> `effect/unstable/cluster/EntityProxyServer#RpcHandlers`: Moved into core Effect and updated for the additional v4 Rpc requirements type parameter. + +- `EntityProxyServer.layerHttpApi` -> `effect/unstable/cluster/EntityProxyServer#layerHttpApi`: Moved into core Effect. Use v4 HttpApi identifiers and provide Sharding plus both Rpc.ServicesServer and Rpc.ServicesClient codec requirements. + +- `EntityProxyServer.layerRpcHandlers` -> `effect/unstable/cluster/EntityProxyServer#layerRpcHandlers`: Moved into core Effect; replace Rpc.Context with both Rpc.ServicesServer and Rpc.ServicesClient codec requirements, alongside Sharding. + +### `@effect/cluster/EntityResource` + +- `EntityResource.TypeId` -> `effect/unstable/cluster/EntityResource#TypeId`: Moved into core Effect; its literal changed to \~effect/cluster/EntityResource. + +- `EntityResource.make` -> `effect/unstable/cluster/EntityResource#make`: Moved into core Effect. Acquisition is lazy by default in v4; set acquireEagerly: true to preserve v3 behavior. + +### `@effect/cluster/Envelope` + +- `Envelope.Envelope.Encoded` -> `effect/unstable/cluster/Envelope#Encoded`: The encoded envelope union moved to the module-level Encoded type. + +- `Envelope.Envelope.PartialEncoded` -> `effect/unstable/cluster/Envelope#Partial`: The partially decoded runtime union was renamed to Partial; use PartialJson for its JSON codec. + +- `Envelope.EnvelopeFromSelf` -> `effect/unstable/cluster/Envelope#Envelope`: The self schema was renamed to Envelope and declaration-merges with the envelope type and namespace. + +- `Envelope.PartialEncoded` -> `effect/unstable/cluster/Envelope#PartialJson`: The partially decoded envelope JSON codec was renamed to PartialJson. + +- `Envelope.PartialEncodedArray` -> `effect/unstable/cluster/Envelope#PartialArray`: The mutable array codec was renamed to PartialArray. + +- `Envelope.PartialEncodedFromSelf` -> `effect/unstable/cluster/Envelope#Partial`: The separate self schema was folded into Partial; derive JSON encoding with PartialJson. + +- `Envelope.PartialEncodedRequest` -> `Schema.toCodecJson(Envelope.PartialRequest)`: V4 exports the self schema as PartialRequest and derives its JSON codec with Schema.toCodecJson. + +- `Envelope.PartialEncodedRequestFromSelf` -> `effect/unstable/cluster/Envelope#PartialRequest`: The partially decoded request self schema was renamed to PartialRequest. + +- `Envelope.Request` -> `effect/unstable/cluster/Envelope#Request`: The request interface remains and declaration-merges with the exported Request schema. + +- `Envelope.Request.Encoded` -> `effect/unstable/cluster/Envelope#PartialRequestEncoded`: The JSON request shape moved to the module-level PartialRequestEncoded interface. + +- `Envelope.Request.PartialEncoded` -> `effect/unstable/cluster/Envelope#PartialRequest`: The partially decoded request shape moved to the module-level PartialRequest class and type. + +- `Envelope.RequestFromSelf` -> `effect/unstable/cluster/Envelope#Request`: The request self schema was renamed to Request and declaration-merges with the runtime interface. + +- `Envelope.TypeId` -> `typeof Envelope.TypeId`: The marker value remains, but the type alias was removed and the value is now a string literal; use typeof in type position. + +### `@effect/cluster/MachineId` + +- `MachineId.make` -> `effect/unstable/cluster/MachineId#make`: Moved into core Effect. The v4 helper is an unchecked cast; validate external input with the MachineId schema when needed. + +### `@effect/cluster/Message` + +- `Message.serialize` -> `effect/unstable/cluster/Message#serialize`: Moved into core Effect. Pass the transport's codecFor as the second argument; use serializeEnvelope for the JSON Envelope.Encoded form. + +### `@effect/cluster/MessageStorage` + +- `MessageStorage.Encoded` -> `effect/unstable/cluster/MessageStorage#Encoded`: Moved into core Effect; use the v4 Envelope.Encoded and Reply.Encoded aliases. Custom drivers now implement batched resetAddresses, and unprocessedMessages receives optional limit and address filters. + +- `MessageStorage.make` -> `effect/unstable/cluster/MessageStorage#make`: Moved into core Effect. Context service projections now use the Service property instead of Type. Custom service implementations must also provide resetAddresses for batched mailbox resets. + +- `MessageStorage.makeEncoded` -> `effect/unstable/cluster/MessageStorage#makeEncoded`: Moved into core Effect. Custom encoded drivers must replace resetAddress with resetAddresses and may use the new limit and addresses options passed to unprocessedMessages. + +### `@effect/cluster/Reply` + +- `Reply.Reply` -> `Reply.Reply(rpc, codecFor)`: Pass the transport codec. Decoding replies requires Rpc.ServicesClient; encoding replies requires Rpc.ServicesServer. + +- `Reply.ReplyEncoded` -> `effect/unstable/cluster/Reply#Encoded`: Renamed to Encoded and no longer parameterized by an Rpc; payload fields are unknown and validated by Reply.Reply(rpc, codecFor) with the transport's codec. + +- `Reply.TypeId` -> `none`: The reply marker is private in v4. Use Reply.isReply for runtime refinement. + +- `Reply.serialize` -> `effect/unstable/cluster/Reply#serialize`: Moved into core Effect and now returns the non-generic Reply.Encoded wire union. Pass the transport's codecFor as the second argument. + +### `@effect/cluster/Runner` + +- `Runner.TypeId` -> `none`: The runner marker is private in v4. Use the exported Runner class and schema. + +### `@effect/cluster/RunnerAddress` + +- `RunnerAddress.TypeId` -> `none`: The runner-address marker is private in v4. Use the exported RunnerAddress class and schema. + +### `@effect/cluster/RunnerStorage` + +- `RunnerStorage.makeMemory` -> `effect/unstable/cluster/RunnerStorage#makeMemory`: Moved into core Effect; it still constructs the in-memory RunnerStorage service implementation. + +### `@effect/cluster/Runners` + +- `Runners.make` -> `effect/unstable/cluster/Runners#make`: Moved into core Effect. Its options now require codecFor; pass the codec used by the remote runner transport, such as RpcSerialization.json.codecFor for JSON. Context service projections now use Service instead of Type. + +- `Runners.makeNoop` -> `effect/unstable/cluster/Runners#makeNoop`: Moved into core Effect; it returns the Context.Service implementation through the Service projection instead of Type. + +### `@effect/cluster/ShardId` + +- `ShardId.ShardId` -> `effect/unstable/cluster/ShardId#ShardId`: The class became a merged interface and schema value. Use ShardId.make; former static parsers and printers are module functions. + +- `ShardId.TypeId` -> `none`: The shard marker is private in v4. Use ShardId.isShardId for runtime refinement. + +### `@effect/cluster/ShardingConfig` + +- `ShardingConfig.config` -> `effect/unstable/cluster/ShardingConfig#config`: Moved into core Effect; its Context service value type now uses the Service property instead of Type. + +- `ShardingConfig.defaults` -> `effect/unstable/cluster/ShardingConfig#defaults`: Moved into core Effect; service type projections now use Service instead of Type. V4 also defaults maxResidentEntities to 10,000 and unprocessedMessageBatchSize to 1,024. + +- `ShardingConfig.layer` -> `effect/unstable/cluster/ShardingConfig#layer`: Moved into core Effect with the same shallow default merge; service type projections now use Service instead of Type. + +### `@effect/cluster/ShardingRegistrationEvent` + +- `ShardingRegistrationEvent.match` -> `effect/unstable/cluster/ShardingRegistrationEvent#match`: Moved into core Effect with the same tagged-enum matcher. + +### `@effect/cluster/SingleRunner` + +- `SingleRunner.layer` -> `effect/unstable/cluster/SingleRunner#layer`: Moved into core Effect. V4 additionally requires Crypto.Crypto because SQL message storage hashes long deduplication keys. + +### `@effect/cluster/SingletonAddress` + +- `SingletonAddress.TypeId` -> `none`: The singleton-address marker is private in v4. Use the exported SingletonAddress class and schema. + +### `@effect/cluster/Snowflake` + +- `Snowflake.TypeId` -> `effect/unstable/cluster/Snowflake#TypeId`: Moved into core Effect; the public marker is now the string literal \~effect/cluster/Snowflake. + +### `@effect/cluster/SqlMessageStorage` + +- `SqlMessageStorage.layer` -> `effect/unstable/cluster/SqlMessageStorage#layer`: Moved into core Effect. V4 adds a Crypto.Crypto requirement for hashing long deduplication keys. + +- `SqlMessageStorage.layerWith` -> `effect/unstable/cluster/SqlMessageStorage#layerWith`: Moved into core Effect with the same optional table prefix; v4 additionally requires Crypto.Crypto. + +- `SqlMessageStorage.make` -> `effect/unstable/cluster/SqlMessageStorage#make`: Moved into core Effect with the same prefix option; v4 additionally requires Crypto.Crypto. + +### `@effect/cluster/SqlRunnerStorage` + +- `SqlRunnerStorage.make` -> `effect/unstable/cluster/SqlRunnerStorage#make`: Moved into core Effect with the same prefix option and service requirements. + +### `@effect/experimental/DevTools/Client` + +- `Client.Client` -> `effect/unstable/devtools/DevToolsClient#DevToolsClient`: Client was renamed to the DevToolsClient Context.Service class. + +- `Client.ClientImpl` -> `effect/unstable/devtools/DevToolsClient#DevToolsClient["Service"]`: Use the service shape from DevToolsClient; unsafeAddSpan was replaced by sendUnsafe. + +- `Client.layer` -> `effect/unstable/devtools/DevToolsClient#layer`: Import layer from the v4 unstable DevToolsClient module. + +- `Client.make` -> `effect/unstable/devtools/DevToolsClient#make`: Import make from the v4 unstable DevToolsClient module. + +### `@effect/experimental/DevTools/Domain` + +- `Domain.ExternalSpanFrom` -> `effect/Schema#Codec.Encoded`: The named encoded alias was removed; derive it with Schema.Codec.Encoded from ExternalSpan. + +- `Domain.MetricFrom` -> `effect/Schema#Codec.Encoded`: The named encoded alias was removed; derive it with Schema.Codec.Encoded from Metric. + +- `Domain.MetricsSnapshotFrom` -> `effect/Schema#Codec.Encoded`: The named encoded alias was removed; derive it with Schema.Codec.Encoded from MetricsSnapshot. + +- `Domain.ParentSpanFrom` -> `effect/Schema#Codec.Encoded`: The named encoded alias was removed; derive it with Schema.Codec.Encoded from ParentSpan. + +- `Domain.SpanFrom` -> `effect/Schema#Codec.Encoded`: The named encoded alias was removed; derive it with Schema.Codec.Encoded from Span. + +- `Domain.metric` -> `none`: The metric schema helper is private in v4; use the exported Counter, Frequency, Gauge, Histogram, Summary, or Metric schemas, or build a Schema.Struct. + +### `@effect/experimental/DevTools/Server` + +- `Server.run` -> `effect/unstable/devtools/DevToolsServer#run`: Import run from the v4 unstable DevToolsServer module. + +### `@effect/experimental/Event` + +- `Event.Event.AddError` -> `effect/unstable/eventlog/Event#AddError`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.Any` -> `effect/unstable/eventlog/Event#Any`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.Context` -> `effect/unstable/eventlog/Event#Services`: Event schema context is now represented by decoding and encoding Services. + +- `Event.Event.ContextWithTag` -> `effect/unstable/eventlog/Event#Services>`: Filter with WithTag and derive its decoding and encoding Services. + +- `Event.Event.Error` -> `effect/unstable/eventlog/Event#Error`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.ErrorSchema` -> `effect/unstable/eventlog/Event#ErrorSchema`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.Payload` -> `effect/unstable/eventlog/Event#Payload`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.PayloadSchema` -> `effect/unstable/eventlog/Event#PayloadSchema`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.Success` -> `effect/unstable/eventlog/Event#Success`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.SuccessSchema` -> `effect/unstable/eventlog/Event#SuccessSchema`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.Tag` -> `effect/unstable/eventlog/Event#Tag`: This type moved from the Event namespace to a top-level export. + +- `Event.Event.ToService` -> `effect/unstable/eventlog/Event#ToService`: This type moved from the Event namespace to a top-level export. + +- `Event.TypeId` -> `effect/unstable/eventlog/Event#TypeId`: Import TypeId from the v4 unstable Event module; its runtime representation is now a string brand. + +- `Event.make` -> `effect/unstable/eventlog/Event#make`: Import make from the v4 unstable Event module. + +### `@effect/experimental/EventGroup` + +- `EventGroup.EventGroup.Any` -> `effect/unstable/eventlog/EventGroup#Any`: This type moved from the EventGroup namespace to a top-level export. + +- `EventGroup.EventGroup.AnyWithProps` -> `effect/unstable/eventlog/EventGroup#AnyWithProps`: This type moved from the EventGroup namespace to a top-level export. + +- `EventGroup.EventGroup.Context` -> `effect/unstable/eventlog/EventGroup#ServicesClient | effect/unstable/eventlog/EventGroup#ServicesServer`: Choose the client or server schema services for the required direction. + +- `EventGroup.EventGroup.ToService` -> `effect/unstable/eventlog/EventGroup#ToService`: This type moved from the EventGroup namespace to a top-level export. + +- `EventGroup.TypeId` -> `effect/unstable/eventlog/EventGroup#TypeId`: Import TypeId from the v4 unstable EventGroup module; its runtime representation is now a string brand. + +### `@effect/experimental/EventJournal` + +- `EventJournal.EntryIdTypeId` -> `effect/unstable/eventlog/EventJournal#EntryIdTypeId`: Import EntryIdTypeId from the v4 EventJournal module; it is now a string brand. + +- `EventJournal.ErrorTypeId` -> `none`: The v4 error marker is private; narrow with EventJournalError instead. + +- `EventJournal.RemoteIdTypeId` -> `effect/unstable/eventlog/EventJournal#RemoteIdTypeId`: Import RemoteIdTypeId from the v4 EventJournal module; it is now a string brand. + +- `EventJournal.makeEntryId` -> `effect/unstable/eventlog/EventJournal#makeEntryIdUnsafe`: The unchecked EntryId constructor was renamed to makeEntryIdUnsafe. + +- `EventJournal.makeMemory` -> `effect/unstable/eventlog/EventJournal#makeMemory`: The in-memory constructor moved into core Effect and now returns the Context.Service implementation through its Service projection. + +- `EventJournal.makeRemoteId` -> `effect/unstable/eventlog/EventJournal#makeRemoteIdUnsafe`: The unchecked RemoteId constructor was renamed to makeRemoteIdUnsafe. + +### `@effect/experimental/EventLog` + +- `EventLog.Handlers` -> `effect/unstable/eventlog/EventLog#Handlers`: Import Handlers from the v4 EventLog module; handlers now also receive storeId. + +- `EventLog.HandlersTypeId` -> `effect/unstable/eventlog/EventLog#HandlersTypeId`: Import HandlersTypeId from the v4 EventLog module. + +- `EventLog.SchemaTypeId` -> `effect/unstable/eventlog/EventLog#SchemaTypeId`: Import SchemaTypeId from the v4 EventLog module. + +- `EventLog.group` -> `effect/unstable/eventlog/EventLog#group`: Import group from the v4 EventLog module; it now requires the shared Registry service. + +- `EventLog.layer` -> `effect/unstable/eventlog/EventLog#layer`: The v4 layer takes both the schema and handler layer; use layerEventLog for runtime only. + +- `EventLog.layerIdentityKvs` -> `none`: Compose KeyValueStore.toSchemaStore, EventLog.IdentitySchema, EventLog.makeIdentity, and Layer.effect manually. + +### `@effect/experimental/EventLogRemote` + +- `EventLogRemote.Ack` -> `none`: A write acknowledgement is now the void success of EventLogMessage.WriteSingleRpc or WriteChunkedRpc. + +- `EventLogRemote.Changes` -> `effect/unstable/eventlog/EventLogMessage#ChangesRpc`: ChangesRpc replaces the separate request and response models with one streaming RPC. + +- `EventLogRemote.EventLogRemote` -> `effect/unstable/eventlog/EventLogRemote#EventLogRemote`: Use the v4 Context.Service; methods now take storeId-aware options. + +- `EventLogRemote.Hello` -> `effect/unstable/eventlog/EventLogMessage#HelloResponse`: HelloResponse replaces Hello and includes the v4 authentication challenge; HelloRpc defines the endpoint. + +- `EventLogRemote.Ping`: TODO: needs guidance + +- `EventLogRemote.Pong` -> `none`: The event-log Pong model was removed; heartbeats belong to the generic RPC socket protocol. + +- `EventLogRemote.ProtocolRequest` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: EventLogRemoteRpcs and generic RPC serialization replace the old protocol request union. + +- `EventLogRemote.ProtocolRequestMsgPack` -> `effect/unstable/rpc/RpcSerialization#layerSchemaBinary`: Use the generic SchemaBinary RPC serialization layer instead of a request-specific schema. + +- `EventLogRemote.ProtocolResponse` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: EventLogRemoteRpcs and generic RPC serialization replace the old protocol response union. + +- `EventLogRemote.ProtocolResponseMsgPack` -> `effect/unstable/rpc/RpcSerialization#layerSchemaBinary`: Use the generic SchemaBinary RPC serialization layer instead of a response-specific schema. + +- `EventLogRemote.RemoteAdditions` -> `none`: This unused protocol model has no v4 counterpart. + +- `EventLogRemote.RequestChanges` -> `effect/unstable/eventlog/EventLogMessage#ChangesRpc`: ChangesRpc replaces the separate request model with one streaming RPC. + +- `EventLogRemote.StopChanges` -> `none`: Interrupt the ChangesRpc stream instead of sending a StopChanges message. + +- `EventLogRemote.decodeRequest` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific request decoder. + +- `EventLogRemote.decodeResponse` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific response decoder. + +- `EventLogRemote.encodeRequest` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific request encoder. + +- `EventLogRemote.encodeResponse` -> `effect/unstable/eventlog/EventLogMessage#EventLogRemoteRpcs`: Generic RPC framing and RpcSerialization.layerSchemaBinary replace the module-specific response encoder. + +- `EventLogRemote.fromSocket` -> `effect/unstable/eventlog/EventLogRemote#makeEncrypted + effect/unstable/rpc/RpcClient#makeProtocolSocket`: Construct the encrypted remote separately from its generic RPC socket protocol. + +- `EventLogRemote.layerWebSocket` -> `effect/unstable/eventlog/EventLogRemote#layerEncrypted + effect/unstable/rpc/RpcClient#layerProtocolSocket`: Compose the encrypted remote with the generic socket protocol, SchemaBinary serialization, and a Socket provider. + +- `EventLogRemote.layerWebSocketBrowser` -> `effect/unstable/eventlog/EventLogRemote#layerEncrypted + effect/unstable/rpc/RpcClient#layerProtocolSocket + @effect/platform-browser/BrowserSocket#layerWebSocket`: Compose the encrypted remote and generic RPC socket protocol with the browser WebSocket layer. + +### `@effect/experimental/EventLogServer` + +- `EventLogServer.makeHandler` -> `effect/unstable/eventlog/EventLogServerEncrypted#layer + effect/unstable/rpc/RpcServer#layerProtocolSocketServer`: Compose the encrypted server layer with the generic RPC socket server; there is no per-socket handler factory. + +- `EventLogServer.makeHandlerHttp` -> `effect/unstable/eventlog/EventLogServerEncrypted#layer + effect/unstable/rpc/RpcServer#makeProtocolWithHttpEffectWebsocket`: Use the returned httpEffect for upgrades and provide its protocol to the encrypted server layer. + +- `EventLogServer.makeStorageMemory` -> `effect/unstable/eventlog/EventLogServerEncrypted#makeStorageMemory`: Use the encrypted server memory storage constructor. + +### `@effect/experimental/PersistedCache` + +- `PersistedCache.make` -> `effect/unstable/persistence/PersistedCache#make`: Pass lookup as the first argument and options second; timeToLive now receives exit before request and the service is Persistence.Persistence. + +### `@effect/experimental/PersistedQueue` + +- `PersistedQueue.ErrorTypeId` -> `effect/unstable/persistence/PersistedQueue#ErrorTypeId`: Retained as a string brand; the runtime marker now uses the persistence module path. + +- `PersistedQueue.TypeId` -> `effect/unstable/persistence/PersistedQueue#TypeId`: Import TypeId from the v4 unstable PersistedQueue module; it is now a string brand. + +- `PersistedQueue.make` -> `effect/unstable/persistence/PersistedQueue#make`: Import make from the v4 unstable PersistedQueue module. + +### `@effect/experimental/PersistedQueue/Redis` + +- `Redis.layerStore` -> `effect/unstable/persistence/PersistedQueue#layerStoreRedis`: The Redis adapter was merged into PersistedQueue and now requires the generic Redis.Redis service. + +- `Redis.layerStoreConfig` -> `none`: Configure a Redis provider such as NodeRedis.layerConfig separately, then compose it with PersistedQueue.layerStoreRedis. + +- `Redis.make` -> `effect/unstable/persistence/PersistedQueue#makeStoreRedis`: The Redis adapter was merged into PersistedQueue and now requires the generic Redis.Redis service. + +### `@effect/experimental/Persistence` + +- `Persistence.BackingPersistence` -> `effect/unstable/persistence/Persistence#BackingPersistence`: Use the v4 BackingPersistence Context.Service class. + +- `Persistence.BackingPersistenceTypeId` -> `none`: The BackingPersistence brand is no longer publicly exported in v4. + +- `Persistence.ErrorTypeId` -> `none`: The v4 persistence error identifier is private; narrow with the exported error classes. + +- `Persistence.PersistenceBackingError` -> `effect/unstable/persistence/Persistence#PersistenceError`: PersistenceError now represents failures from the backing persistence implementation. + +- `Persistence.PersistenceError` -> `effect/unstable/persistence/Persistence#PersistenceError | effect/Schema#SchemaError`: The old combined alias was split into backing PersistenceError and schema SchemaError. + +- `Persistence.PersistenceParseError` -> `effect/Schema#SchemaError`: Persistence parsing failures now use the core SchemaError type. + +- `Persistence.ResultPersistence` -> `effect/unstable/persistence/Persistence#Persistence`: ResultPersistence was renamed to Persistence and is now a Context.Service class. + +- `Persistence.ResultPersistence.Key` -> `effect/unstable/persistence/Persistable#Persistable`: Persistable is the v4 schema-backed persistence key contract. + +- `Persistence.ResultPersistence.KeyAny` -> `effect/unstable/persistence/Persistable#Any`: Use Persistable.Any for an arbitrary v4 persistence key contract. + +- `Persistence.ResultPersistence.TimeToLiveArgs` -> `Parameters>`: Derive the tuple from TimeToLiveFn; its order is now exit then request. + +- `Persistence.ResultPersistenceStore` -> `effect/unstable/persistence/Persistence#PersistenceStore`: ResultPersistenceStore was renamed to PersistenceStore. + +- `Persistence.ResultPersistenceTypeId` -> `none`: The ResultPersistence brand is no longer publicly exported in v4. + +- `Persistence.layerKeyValueStore` -> `effect/unstable/persistence/Persistence#layerBackingKvs`: The KeyValueStore backing layer was renamed to layerBackingKvs. + +- `Persistence.layerMemory` -> `effect/unstable/persistence/Persistence#layerBackingMemory`: Use layerBackingMemory for the old backing service; v4 layerMemory creates the higher-level Persistence service. + +- `Persistence.layerResult` -> `effect/unstable/persistence/Persistence#layer`: The ResultPersistence service layer was renamed to layer. + +- `Persistence.layerResultKeyValueStore` -> `effect/unstable/persistence/Persistence#layerKvs`: The combined KeyValueStore-backed result layer was renamed to layerKvs. + +- `Persistence.layerResultMemory` -> `effect/unstable/persistence/Persistence#layerMemory`: The combined memory-backed result layer was renamed to layerMemory. + +### `@effect/experimental/Persistence/Redis` + +- `Redis.layer` -> `effect/unstable/persistence/Persistence#layerBackingRedis`: The Redis backing adapter was merged into Persistence and now requires the generic Redis.Redis service. + +- `Redis.layerConfig` -> `none`: Compose Persistence.layerBackingRedis with a config-driven provider such as NodeRedis.layerConfig. + +- `Redis.layerResult` -> `effect/unstable/persistence/Persistence#layerRedis`: The combined Redis persistence layer was merged into Persistence and now requires Redis.Redis. + +- `Redis.layerResultConfig` -> `none`: Compose Persistence.layerRedis with a config-driven provider such as NodeRedis.layerConfig. + +- `Redis.make` -> `none`: V4 exposes Redis-backed layers over the Redis.Redis service, not a constructor that creates an ioredis client directly. + +### `@effect/experimental/RateLimiter` + +- `RateLimiter.ErrorTypeId` -> `effect/unstable/persistence/RateLimiter#ErrorTypeId`: Retained as a string brand; the runtime marker now uses the persistence module path. + +- `RateLimiter.RateLimiterError` -> `effect/unstable/persistence/RateLimiter#RateLimiterError`: The retained name is now a wrapper error class whose reason is RateLimitExceeded or RateLimitStoreError. + +- `RateLimiter.TypeId` -> `effect/unstable/persistence/RateLimiter#TypeId`: Import TypeId from the v4 unstable RateLimiter module; it is now a string brand. + +- `RateLimiter.makeSleep` -> `effect/unstable/persistence/RateLimiter#sleep`: The accessor Effect was replaced by sleep; obtain the RateLimiter service and pass it to sleep directly or with its curried overload. + +### `@effect/experimental/RateLimiter/Redis` + +- `Redis.layerStore` -> `effect/unstable/persistence/RateLimiter#layerStoreRedis`: The Redis adapter was merged into RateLimiter and now requires the generic Redis.Redis service. + +- `Redis.layerStoreConfig` -> `effect/unstable/persistence/RateLimiter#layerStoreRedisConfig`: Use the merged Redis store config layer; connection configuration belongs to a separate Redis provider. + +- `Redis.make` -> `effect/unstable/persistence/RateLimiter#makeStoreRedis`: The Redis adapter was merged into RateLimiter and now requires the generic Redis.Redis service. + +### `@effect/experimental/Reactivity` + +- `Reactivity.Reactivity` -> `effect/unstable/reactivity/Reactivity#Reactivity`: Use the v4 Reactivity Context.Service; unsafe methods were renamed with an Unsafe suffix. + +- `Reactivity.Reactivity.Service` -> `effect/unstable/reactivity/Reactivity#Reactivity["Service"]`: The named namespace member was removed; derive the service shape from the Context.Service class. + +- `Reactivity.make` -> `effect/unstable/reactivity/Reactivity#make`: Import make from the v4 unstable Reactivity module. + +### `@effect/experimental/RequestResolver` + +- `RequestResolver.PersistedRequest` -> `effect/Request#Request & effect/unstable/persistence/Persistable#Persistable`: Intersect a Request with Persistable or define it with Persistable.Class; there is no combined named export. + +- `RequestResolver.PersistedRequest.Any` -> `effect/Request#Any & effect/unstable/persistence/Persistable#Any`: Intersect the Request and Persistable helper types for an arbitrary persisted request. + +- `RequestResolver.dataLoader` -> `effect/RequestResolver#setDelay + effect/RequestResolver#batchN`: Pipe the resolver through setDelay(options.window) and batchN(options.maxBatchSize ?? Infinity); the transformation is now pure. + +- `RequestResolver.persisted` -> `effect/RequestResolver#persisted`: Retained after moving to core RequestResolver; requests now implement Persistable and use Persistence.Persistence, timeToLive is optional, and staleWhileRevalidate is supported. + +### `@effect/experimental/Sse` + +- `Sse.RetryTypeId` -> `none`: The Retry identifier is private in v4; use effect/unstable/encoding/Sse#Retry and Retry.is instead of inspecting the brand. + +### `@effect/experimental/VariantSchema` + +- `VariantSchema.Extract` -> `effect/unstable/schema/VariantSchema#Extract`: Import the retained helper from the v4 module; its erased schema constraint is Schema.Top. + +- `VariantSchema.Field.Any` -> `effect/unstable/schema/VariantSchema#Field.Any`: Import the retained Field.Any helper type from the v4 unstable VariantSchema module. + +- `VariantSchema.Field.Config` -> `effect/unstable/schema/VariantSchema#Field.Config`: Import the retained Field.Config helper type from the v4 unstable VariantSchema module. + +- `VariantSchema.Field.Fields` -> `effect/unstable/schema/VariantSchema#Field.Fields`: Import the retained Field.Fields helper type from the v4 unstable VariantSchema module. + +- `VariantSchema.Field.ValueAny` -> `effect/Schema#Top`: Use the core Schema.Top constraint for an arbitrary field value schema. + +- `VariantSchema.FieldTypeId` -> `none`: The Field brand is private in v4; use VariantSchema.isField for narrowing. + +- `VariantSchema.Struct.Fields` -> `effect/unstable/schema/VariantSchema#Struct.Fields`: Import the retained Struct.Fields helper type from the v4 unstable VariantSchema module. + +- `VariantSchema.TypeId` -> `effect/unstable/schema/VariantSchema#TypeId`: Use the retained runtime value; in type position use typeof VariantSchema.TypeId. + +- `VariantSchema.fromKey` -> `none`: Field-level fromKey was not ported; for whole-struct encoded-key renaming consider Schema.encodeKeys. + +- `VariantSchema.fromKey.Rename` -> `none`: The fromKey rename helper was not ported; for whole-struct encoded-key renaming consider Schema.encodeKeys. + +- `VariantSchema.make` -> `effect/unstable/schema/VariantSchema#make`: Import make from the v4 module; FieldOnly and FieldExcept take one key array and Union takes one member array. + +### `@effect/opentelemetry/Logger` + +- `Logger.layerLoggerAdd` -> `OtelLogger.layer({ mergeWithExisting: true })`: The Logger module was renamed to OtelLogger; logger installation is now one configurable layer, with true preserving the v3 additive behavior. + +- `Logger.layerLoggerReplace` -> `OtelLogger.layer({ mergeWithExisting: false })`: The Logger module was renamed to OtelLogger; logger installation is now one configurable layer, with false replacing existing loggers. + +- `Logger.make` -> `OtelLogger.make`: The constructor remains in the renamed OtelLogger module. + +### `@effect/opentelemetry/Metrics` + +- `Metrics.layer` -> `OtelMetrics.layer`: The Metrics module was renamed to OtelMetrics; the layer remains and now also accepts an optional temporality setting. + +### `@effect/opentelemetry/NodeSdk` + +- `NodeSdk.Configuration` -> `NodeSdk.Configuration`: The configuration interface remains; v4 adds metricTemporality and loggerMergeWithExisting options. + +### `@effect/opentelemetry/Otlp` + +- `Otlp.layer` -> `Otlp.layer`: Moved to effect/unstable/observability/Otlp; replaceLogger was replaced by loggerMergeWithExisting, and metricsTemporality is now configurable. + +### `@effect/opentelemetry/OtlpLogger` + +- `OtlpLogger.layer` -> `OtlpLogger.layer`: Moved to effect/unstable/observability/OtlpLogger; use mergeWithExisting instead of passing replaceLogger. + +- `OtlpLogger.make` -> `OtlpLogger.make`: The constructor remains in the module moved to effect/unstable/observability/OtlpLogger. + +### `@effect/opentelemetry/OtlpMetrics` + +- `OtlpMetrics.layer` -> `OtlpMetrics.layer`: Moved to effect/unstable/observability/OtlpMetrics; the layer now also accepts optional cumulative or delta temporality. + +- `OtlpMetrics.make` -> `OtlpMetrics.make`: Moved to effect/unstable/observability/OtlpMetrics; the constructor now also accepts optional cumulative or delta temporality. + +### `@effect/opentelemetry/OtlpResource` + +- `OtlpResource.unsafeServiceName` -> `OtlpResource.serviceNameUnsafe`: Moved to effect/unstable/observability/OtlpResource and renamed to follow the v4 unsafe-suffix convention. + +### `@effect/opentelemetry/OtlpTracer` + +- `OtlpTracer.layer` -> `OtlpTracer.layer`: The layer remains in the module moved to effect/unstable/observability/OtlpTracer. + +- `OtlpTracer.make` -> `OtlpTracer.make`: The constructor remains in the module moved to effect/unstable/observability/OtlpTracer. + +### `@effect/opentelemetry/Resource` + +- `Resource.Resource` -> `Resource.Resource`: The service remains in @effect/opentelemetry/Resource but is now a Context.Service class rather than a separate Tag interface and value. + +### `@effect/opentelemetry/Tracer` + +- `Tracer.OtelTraceFlags` -> `OtelTracer.OtelTraceFlags`: The service moved with the module and is now declared as a Context.Service class. + +- `Tracer.OtelTraceState` -> `OtelTracer.OtelTraceState`: The service moved with the module and is now declared as a Context.Service class. + +- `Tracer.OtelTracer` -> `OtelTracer.OtelTracer`: The service moved with the renamed module and is now declared as a Context.Service class. + +- `Tracer.OtelTracerProvider` -> `OtelTracer.OtelTracerProvider`: The service moved with the renamed module and is now declared as a Context.Service class. + +- `Tracer.layer` -> `OtelTracer.layer`: The Tracer module was renamed to OtelTracer; this still creates an OpenTelemetry tracer and installs it as Effect's tracer. + +- `Tracer.layerTracer` -> `OtelTracer.layerTracer`: The Tracer module was renamed to OtelTracer; this layer still creates only the OpenTelemetry tracer service. + +- `Tracer.make` -> `OtelTracer.make`: The constructor remains in the renamed OtelTracer module. + +### `@effect/opentelemetry/WebSdk` + +- `WebSdk.Configuration` -> `WebSdk.Configuration`: The configuration interface remains; v4 adds metricTemporality and loggerMergeWithExisting options. + +### `@effect/platform-browser/BrowserHttpClient` + +- `BrowserHttpClient.currentXHRResponseType` -> `BrowserHttpClient.CurrentXHRResponseType`: The FiberRef became a defaulted Context.Reference; use withXHRArrayBuffer or provide the reference as a service. + +### `@effect/platform-browser/BrowserWorker` + +- `BrowserWorker.layerManager` -> `BrowserWorker.layerPlatform`: WorkerManager was removed. Provide WorkerPlatform directly, or use BrowserWorker.layer(spawn) when a Worker.Spawner is also required. + +- `BrowserWorker.layerWorker` -> `BrowserWorker.layerPlatform`: PlatformWorker became Worker.WorkerPlatform. The platform-only layer no longer takes a spawn callback; BrowserWorker.layer(spawn) combines platform and spawner layers. + +### `@effect/platform-browser/BrowserWorkerRunner` + +- `BrowserWorkerRunner.launch` -> `Layer.launch + RpcServer.layerProtocolWorkerRunner`: The close-latch launcher was removed. Compose BrowserWorkerRunner.layer with the worker RPC server protocol layer and launch the resulting handler layer. + +### `@effect/platform-browser/Clipboard` + +- `Clipboard.Clipboard` -> `Clipboard.Clipboard`: The service remains, now as a Context.Service with a private brand; normal access and provision are unchanged. + +- `Clipboard.ErrorTypeId` -> `none`: The error marker is private in v4; discriminate ClipboardError by its \_tag instead. + +- `Clipboard.TypeId` -> `none`: The service brand is private in v4; use the Clipboard Context.Service value. + +### `@effect/platform-browser/Geolocation` + +- `Geolocation.ErrorTypeId` -> `none`: The error marker is private in v4; discriminate GeolocationError and its tagged reason. + +- `Geolocation.Geolocation` -> `Geolocation.Geolocation`: The service remains, now as a Context.Service with a private brand. + +- `Geolocation.GeolocationError` -> `Geolocation.GeolocationError`: The class remains, but reason is now PositionUnavailable, PermissionDenied, or Timeout, with the cause stored on that tagged reason. + +- `Geolocation.TypeId` -> `none`: The service marker is private in v4; use the Geolocation Context.Service value. + +### `@effect/platform-browser/Permissions` + +- `Permissions.ErrorTypeId` -> `none`: The error marker is private in v4; discriminate PermissionsError and its tagged reason. + +- `Permissions.Permissions` -> `Permissions.Permissions`: The query service remains, now as a Context.Service with a private brand. + +- `Permissions.PermissionsError` -> `Permissions.PermissionsError`: The class remains, but reason is now PermissionsInvalidStateError or PermissionsTypeError, with the cause stored on that tagged reason. + +- `Permissions.TypeId` -> `none`: The service marker is private in v4; use the Permissions Context.Service value. + +### `@effect/platform-bun/BunCommandExecutor` + +- `BunCommandExecutor.layer` -> `BunChildProcessSpawner.layer`: CommandExecutor became effect/unstable/process/ChildProcessSpawner; the Bun adapter was renamed and still requires FileSystem and Path. + +### `@effect/platform-bun/BunContext` + +- `BunContext.BunContext` -> `BunServices.BunServices`: The aggregate was renamed and now provides ChildProcessSpawner, Crypto, FileSystem, Path, Stdio, and Terminal; add BunWorker separately when needed. + +- `BunContext.layer` -> `BunServices.layer`: Use the renamed aggregate layer; worker services are no longer included. + +### `@effect/platform-bun/BunFileSystem/ParcelWatcher` + +- `ParcelWatcher.layer` -> `BunFileSystem.layer`: The Parcel watcher adapter was removed. BunFileSystem.layer uses the built-in node:fs-compatible watcher; provide a custom FileSystem.WatchBackend for specialized behavior. + +### `@effect/platform-bun/BunHttpPlatform` + +- `BunHttpPlatform.make` -> `BunHttpPlatform.layer`: The Bun-specific constructor is private; provide the public layer and consume HttpPlatform.HttpPlatform. + +### `@effect/platform-bun/BunHttpServer` + +- `BunHttpServer.ServeOptions` -> `BunHttpServer.ServeOptions`: The alias remains, but R is now a route-key string union and routes uses Bun.Serve.Routes; update old route-map generic arguments or infer R from routes. + +- `BunHttpServer.layerContext` -> `BunHttpServer.layerHttpServices`: Direct rename; it provides HttpPlatform, Etag.Generator, and BunServices. + +### `@effect/platform-bun/BunHttpServerRequest` + +- `BunHttpServerRequest.toRequest` -> `BunHttpServerRequest.toBunServerRequest`: Direct rename with the more precise Bun.BunRequest result type. + +### `@effect/platform-bun/BunSink` + +- `BunSink.stderr` -> `stdio.stderr() from Stdio.Stdio`: Process stdio moved behind effect/Stdio; provide BunStdio.layer. stderr remains a Sink and can be configured with endOnDone. + +- `BunSink.stdin` -> `stdio.stdin from Stdio.Stdio`: Standard input is correctly modeled as a Stream in v4, not a writable Sink; manually adapt process.stdin only if writing to it was intentional. + +- `BunSink.stdout` -> `stdio.stdout() from Stdio.Stdio`: Process stdio moved behind effect/Stdio; provide BunStdio.layer. stdout remains a Sink. + +### `@effect/platform-bun/BunSocket` + +- `BunSocket.NetSocket` -> `BunSocket.NetSocket`: The identifier remains, but the old interface/tag pair is now one Context.Service for node:net.Socket. + +### `@effect/platform-bun/BunSocketServer` + +- `BunSocketServer.IncomingMessage` -> `BunSocketServer.IncomingMessage`: The identifier remains and is now a Context.Service for node:http.IncomingMessage. + +### `@effect/platform-bun/BunStream` + +- `BunStream.FromReadableOptions` -> `Pick[0], "chunkSize" | "closeOnDone">`: The named interface was inlined into the constructor options; chunkSize is now a number and the full options also contain evaluate and onError. The ignored bufferSize option was removed. + +- `BunStream.FromWritableOptions` -> `Pick[0], "endOnDone" | "encoding">`: The named interface was inlined into BunSink.fromWritable and duplex constructor options. + +- `BunStream.stderr` -> `stdio.stderr() from Stdio.Stdio`: Standard error is correctly modeled as a Sink in v4. Explicitly adapt process.stderr with BunStream.fromReadable only to preserve the old unusual read behavior. + +- `BunStream.stdin` -> `stdio.stdin from Stdio.Stdio`: Standard input moved to the Stdio service; provide BunStdio.layer. Its stream exposes PlatformError instead of dying. + +- `BunStream.stdout` -> `stdio.stdout() from Stdio.Stdio`: Standard output is correctly modeled as a Sink in v4. Explicitly adapt process.stdout with BunStream.fromReadable only to preserve the old unusual read behavior. + +### `@effect/platform-bun/BunWorker` + +- `BunWorker.layerManager` -> `BunWorker.layerPlatform`: WorkerManager was removed. Provide WorkerPlatform directly, or use BunWorker.layer(spawn) when a Worker.Spawner is also required. + +- `BunWorker.layerWorker` -> `BunWorker.layerPlatform`: PlatformWorker became Worker.WorkerPlatform; BunWorker.layer(spawn) combines the platform and spawner layers. + +### `@effect/platform-node-shared/NodeCommandExecutor` + +- `NodeCommandExecutor.layer` -> `NodeChildProcessSpawner.layer`: CommandExecutor became effect/unstable/process/ChildProcessSpawner; the Node adapter was renamed and still requires FileSystem and Path. + +### `@effect/platform-node-shared/NodeFileSystem/ParcelWatcher` + +- `ParcelWatcher.layer` -> `NodeFileSystem.layer`: The Parcel watcher adapter was removed. NodeFileSystem.layer uses node:fs.watch; provide a custom FileSystem.WatchBackend for specialized behavior. + +### `@effect/platform-node-shared/NodeMultipart` + +- `NodeMultipart.fileToReadable` -> `@effect/platform-node/NodeMultipart#fileToReadable`: The Node multipart implementation moved from @effect/platform-node-shared to @effect/platform-node; its behavior remains. + +- `NodeMultipart.stream` -> `@effect/platform-node/NodeMultipart#stream`: The Node multipart implementation moved from @effect/platform-node-shared to @effect/platform-node; the source and headers call shape remains. + +### `@effect/platform-node-shared/NodeSink` + +- `NodeSink.stderr` -> `stdio.stderr() from Stdio.Stdio`: Standard error moved to effect/Stdio; provide NodeStdio.layer or NodeServices.layer. + +- `NodeSink.stdin` -> `NodeSink.fromWritable({ evaluate: () => process.stdin, onError: ... })`: There is no Stdio sink because stdin is a readable stream in v4; use a manual adapter only if writing to process.stdin was intentional. + +- `NodeSink.stdout` -> `stdio.stdout() from Stdio.Stdio`: Standard output moved to effect/Stdio; provide NodeStdio.layer or NodeServices.layer. + +### `@effect/platform-node-shared/NodeSocket` + +- `NodeSocket.NetSocket` -> `NodeSocket.NetSocket`: The identifier remains, but the old interface/tag pair is now one Context.Service; use NodeSocket.NetSocket["Service"] for the node:net.Socket value type. + +### `@effect/platform-node-shared/NodeStream` + +- `NodeStream.FromReadableOptions` -> `{ readonly chunkSize?: number; readonly closeOnDone?: boolean }`: The named interface was removed and its fields were inlined into readable constructor options; chunkSize narrowed from SizeInput to number. + +- `NodeStream.FromWritableOptions` -> `{ readonly endOnDone?: boolean; readonly encoding?: BufferEncoding }`: The named interface was removed and its fields were inlined into NodeSink and duplex constructor options. + +- `NodeStream.stderr` -> `NodeStream.fromReadable({ evaluate: () => process.stderr, closeOnDone: false }).pipe(Stream.orDie)`: This preserves the unusual v3 read behavior; for normal error output use the stdio.stderr() Sink from effect/Stdio. + +- `NodeStream.stdin` -> `stdio.stdin from Stdio.Stdio`: Standard input moved to effect/Stdio; provide NodeStdio.layer or NodeServices.layer. The service stream exposes PlatformError instead of dying. + +- `NodeStream.stdout` -> `NodeStream.fromReadable({ evaluate: () => process.stdout, closeOnDone: false }).pipe(Stream.orDie)`: This preserves the unusual v3 read behavior; for normal output use the stdio.stdout() Sink from effect/Stdio. + +### `@effect/platform-node/NodeCommandExecutor` + +- `NodeCommandExecutor.layer` -> `NodeChildProcessSpawner.layer`: CommandExecutor became ChildProcessSpawner; use the @effect/platform-node/NodeChildProcessSpawner re-export. + +### `@effect/platform-node/NodeContext` + +- `NodeContext.NodeContext` -> `NodeServices.NodeServices`: Use the renamed service union; it replaces CommandExecutor with ChildProcessSpawner, adds Crypto and Stdio, and omits WorkerManager. + +- `NodeContext.layer` -> `NodeServices.layer`: The aggregate was renamed and now provides ChildProcessSpawner, Crypto, FileSystem, Path, Stdio, and Terminal; add NodeWorker separately when needed. + +### `@effect/platform-node/NodeFileSystem/ParcelWatcher` + +- `ParcelWatcher.layer` -> `NodeFileSystem.layer`: The Parcel watcher adapter was removed. Native node:fs.watch support is built in; FileSystem.WatchBackend is the extension point. + +### `@effect/platform-node/NodeHttpClient` + +- `NodeHttpClient.Dispatcher` -> `NodeHttpClient.Dispatcher`: The identifier remains but is now a Context.Service class; use Dispatcher["Service"] for the concrete Undici dispatcher type. + +- `NodeHttpClient.HttpAgent` -> `NodeHttpClient.HttpAgent`: The identifier remains but is now a Context.Service class; use HttpAgent["Service"] for the concrete http/https agent pair. + +- `NodeHttpClient.HttpAgentTypeId` -> `none`: The public marker was removed; the HttpAgent Context.Service class supplies service identity. + +- `NodeHttpClient.UndiciRequestOptions` -> `NodeHttpClient.UndiciOptions`: The required Context.Tag became a defaulted Context.Reference\\>; override it with Effect.provideService. + +- `NodeHttpClient.agentLayer` -> `NodeHttpClient.layerAgent`: Direct rename; it provides the default scoped Node HTTP and HTTPS agents. + +- `NodeHttpClient.dispatcherLayer` -> `NodeHttpClient.layerDispatcher`: Direct rename; the layer owns and finalizes a scoped Undici Agent. + +- `NodeHttpClient.layer` -> `NodeHttpClient.layerNodeHttp`: Use the renamed node:http/node:https backend layer; choose layerUndici only when intentionally changing backends. + +- `NodeHttpClient.layerUndiciWithoutDispatcher` -> `NodeHttpClient.layerUndiciNoDispatcher`: Direct rename; the layer still requires NodeHttpClient.Dispatcher. + +- `NodeHttpClient.layerWithoutAgent` -> `NodeHttpClient.layerNodeHttpNoAgent`: Direct rename; the node:http client layer still requires NodeHttpClient.HttpAgent. + +- `NodeHttpClient.make` -> `NodeHttpClient.makeNodeHttp`: Direct rename of the node:http/node:https client constructor. + +- `NodeHttpClient.makeAgentLayer` -> `NodeHttpClient.layerAgentOptions`: Direct rename; it accepts Https.AgentOptions and scopes both agents. + +### `@effect/platform-node/NodeHttpServer` + +- `NodeHttpServer.layerContext` -> `NodeHttpServer.layerHttpServices`: Direct rename; it provides NodeServices, HttpPlatform, and Etag.Generator, without the removed WorkerManager. + +### `@effect/platform-node/NodeWorker` + +- `NodeWorker.layerManager` -> `NodeWorker.layerPlatform`: WorkerManager was removed. Provide WorkerPlatform directly, or use NodeWorker.layer(spawn) when a Worker.Spawner is also required. + +- `NodeWorker.layerWorker` -> `NodeWorker.layerPlatform`: PlatformWorker became Worker.WorkerPlatform; NodeWorker.layer(spawn) combines the platform and spawner layers. + +### `@effect/platform-node/Undici` + +- `Undici.Agent` -> `undici.Agent`: Import the upstream Agent directly. Undici 8 removes maxRedirections and option-level interceptors, adds maxOrigins, and enables HTTP/2 negotiation unless allowH2 is false. + +- `Undici.Agent.DispatchOptions` -> `undici.Agent.DispatchOptions`: Import the same Agent namespace type from undici; Undici 8 removes maxRedirections. + +- `Undici.Agent.Options` -> `undici.Agent.Options`: Import the same Agent namespace type; Undici 8 removes maxRedirections and option-level interceptors, adds maxOrigins, and uses dispatcher.compose for interceptors. + +- `Undici.Client` -> `undici.Client`: Import the upstream Client directly; custom handlers must use Undici 8's controller-based v2 handler API. + +- `Undici.Client.Options` -> `undici.Client.Options`: Import the same Client namespace type; Undici 8 removes maxRedirections and option-level interceptors and adds WebSocket and HTTP/2 options. + +- `Undici.Client.OptionsInterceptors` -> `undici.Dispatcher.DispatcherComposeInterceptor + dispatcher.compose`: Undici 8 removed option-level interceptor tuples; keep DispatcherComposeInterceptor functions and apply them after construction with dispatcher.compose(...). + +- `Undici.DiagnosticsChannel` -> `undici.DiagnosticsChannel`: Import this type-only namespace from undici; subscribe at runtime through node:diagnostics\_channel using Undici's channel names. + +- `Undici.DiagnosticsChannel.ClientConnectErrorMessage` -> `undici.DiagnosticsChannel.ClientConnectErrorMessage`: Import the same type-only namespace member from undici; runtime delivery uses node:diagnostics\_channel. + +- `Undici.DiagnosticsChannel.Error` -> `Error`: Undici 8 removed this unknown alias; diagnostic error fields now use the built-in Error type. + +- `Undici.DiagnosticsChannel.RequestErrorMessage` -> `undici.DiagnosticsChannel.RequestErrorMessage`: Import the same type-only namespace member; its error field is the built-in Error type in Undici 8. + +- `Undici.Dispatcher` -> `undici.Dispatcher`: Import the upstream Dispatcher directly; custom dispatchers must adopt Undici 8's controller-based v2 handler API. + +- `Undici.Dispatcher.ConnectOptions` -> `undici.Dispatcher.ConnectOptions`: Import the same Dispatcher namespace type; Undici 8 removes maxRedirections and redirectionLimitReached. + +- `Undici.Dispatcher.DispatchHandler` -> `undici.Dispatcher.DispatchHandler`: Use Undici 8's onRequestStart/onResponseStart/onResponseData/onResponseEnd/onResponseError callbacks and controller pause/resume/abort methods. + +- `Undici.Dispatcher.DispatchOptions` -> `undici.Dispatcher.DispatchOptions`: Import the same namespace type; Undici 8 removes throwOnError, adds typeOfService, and handles redirects through composed interceptors. + +- `Undici.Dispatcher.RequestOptions` -> `undici.Dispatcher.RequestOptions`: Import the same namespace type; Undici 8 removes maxRedirections and redirectionLimitReached, so compose a redirect interceptor when needed. + +- `Undici.Dispatcher.UpgradeOptions` -> `undici.Dispatcher.UpgradeOptions`: Import the same namespace type; Undici 8 removes maxRedirections and redirectionLimitReached. + +- `Undici.H2CClient` -> `undici.H2CClient`: Import the upstream cleartext HTTP/2 client directly; callbacks follow Undici 8's handler API. + +- `Undici.H2CClient.Options` -> `undici.H2CClient.Options`: Import the same H2CClient namespace type; Undici 8 removes maxRedirections. + +- `Undici.MessageEvent` -> `undici.MessageEvent`: Import Undici's named constructor/type directly to preserve the installed package identity. + +- `Undici.MessageEventInit` -> `undici.MessageEventInit`: Import the upstream type directly; message ports and source use MessagePort instances in Undici 8. + +- `Undici.MockAgent`: TODO: needs guidance + +- `Undici.Pool` -> `undici.Pool`: Import the upstream Pool directly and apply interceptors after construction with pool.compose(...). + +- `Undici.Pool.Options` -> `undici.Pool.Options`: Import the same Pool namespace type; Undici 8 removes the interceptors option in favor of pool.compose(...). + +- `Undici.ProxyAgent` -> `undici.ProxyAgent`: Import the upstream ProxyAgent directly; inherited Agent options and handlers follow Undici 8. + +- `Undici.ProxyAgent.Options` -> `undici.ProxyAgent.Options`: Import the same ProxyAgent namespace type; Undici 8 types proxy headers as OutgoingHttpHeaders. + +- `Undici.RedirectHandler` -> `undici.RedirectHandler`: Import the upstream class; Undici 8 removes redirectionLimitReached from the constructor and adds static buildDispatch. + +- `Undici.Request` -> `undici.Request`: Import Undici's named Request directly; clone is a method in Undici 8. + +- `Undici.Response` -> `undici.Response`: Import Undici's named Response directly; clone is a method and Response.redirect status is optional in Undici 8. + +- `Undici.SpecIterable` -> `undici.SpecIterable`: Import the upstream type directly; its iterator returns SpecIterableIterator in Undici 8. + +- `Undici.SpecIterableIterator` -> `undici.SpecIterableIterator`: Import the upstream type directly; it extends SpecIteratorObject and includes iterator-helper methods in Undici 8. + +- `Undici.buildConnector` -> `undici.buildConnector`: Import the upstream function directly; Undici 8 expands connector options with preferH2, typeOfService, and socketPath handling. + +- `Undici.buildConnector.BuildOptions` -> `undici.buildConnector.BuildOptions`: Import the same buildConnector namespace type; Undici 8 adds preferH2 and typeOfService. + +- `Undici.default.cacheStores` -> `undici.cacheStores`: Use Undici 8's named cacheStores export instead of reaching through the default aggregate. + +- `Undici.deleteCookie` -> `undici.deleteCookie`: Import the upstream function directly; its optional attributes use path and domain and no longer include name. + +- `Undici.errors` -> `undici.errors`: Import the upstream errors object directly; individual classes follow the Undici 8 API. + +- `Undici.errors.ResponseStatusCodeError` -> `undici.errors.ResponseError`: Undici 8 replaced ResponseStatusCodeError with ResponseError; construct it with message, statusCode, and the headers/body object. + +- `Undici.interceptors` -> `undici.interceptors`: Import the upstream interceptors object and apply returned interceptors with dispatcher.compose(...). + +- `Undici.interceptors.DNSInterceptorOpts` -> `undici.interceptors.DNSInterceptorOpts`: Import the same namespace type; Undici 8 lookup receives an origin URL and supports optional DNS storage. + +- `Undici.interceptors.DNSInterceptorOriginRecords` -> `undici.interceptors.DNSInterceptorOriginRecords`: Import the same namespace type, but adopt Undici 8's shape with IPv4 and IPv6 entries nested under records. + +- `Undici.interceptors.RedirectInterceptorOpts` -> `undici.interceptors.RedirectInterceptorOpts`: Import the same namespace type; Undici 8 adds throwOnMaxRedirect and redirect header-stripping options. + +### `@effect/platform/ChannelSchema` + +- `ChannelSchema.decode` -> `ChannelSchema.decode`: The API moved to effect/ChannelSchema; update the import and adapt the schema to the v4 Schema.Constraint model. + +- `ChannelSchema.duplex` -> `ChannelSchema.duplex`: The API moved to effect/ChannelSchema; update the import and adapt the schema to the v4 Schema.Constraint model. + +- `ChannelSchema.encode` -> `ChannelSchema.encode`: The API moved to effect/ChannelSchema; update the import and adapt the schema to the v4 Schema.Constraint model. + +### `@effect/platform/Command` + +- `Command.Command` -> `ChildProcess.Command`: Commands moved to effect/unstable/process/ChildProcess and are now directly Effectable to spawn a ChildProcessHandle. + +- `Command.Command.Input` -> `ChildProcess.CommandInput`: The standard-input configuration type was flattened out of the Command namespace. + +- `Command.Command.Proto` -> `ChildProcess.StandardCommand | ChildProcess.PipedCommand`: The public command prototype was removed; narrow the Command union to its StandardCommand or PipedCommand interfaces. + +- `Command.CommandTypeId` -> `none`: The command type-id alias is internal in v4; use ChildProcess.Command or ChildProcess.isCommand instead. + +- `Command.env` -> `ChildProcess.setEnv`: Use the renamed command combinator. + +- `Command.exitCode` -> `ChildProcessSpawner.ChildProcessSpawner.exitCode`: Obtain the ChildProcessSpawner service and call exitCode, or spawn the Effectable command and read the handle exitCode. + +- `Command.feed` -> `ChildProcess.CommandOptions["stdin"]`: The feed combinator was removed; pass a Stream as stdin when constructing the command. + +- `Command.flatten` -> `none`: No flatten helper remains; inspect StandardCommand and PipedCommand recursively when command structure is required. + +- `Command.lines` -> `ChildProcessSpawner.ChildProcessSpawner.lines`: Output collection moved onto the ChildProcessSpawner service. + +- `Command.runInShell` -> `ChildProcess.CommandOptions["shell"]`: Set shell when calling ChildProcess.make; there is no post-construction shell combinator. + +- `Command.start` -> `ChildProcessSpawner.ChildProcessSpawner.spawn`: Use the spawner service, or yield the Effectable ChildProcess.Command directly, to obtain a ChildProcessHandle. + +- `Command.stderr` -> `ChildProcess.CommandOptions["stderr"]`: Configure stderr in ChildProcess.make options; the standalone combinator was removed. + +- `Command.stdin` -> `ChildProcess.CommandOptions["stdin"]`: Configure stdin in ChildProcess.make options; the standalone combinator was removed. + +- `Command.stdout` -> `ChildProcess.CommandOptions["stdout"]`: Configure stdout in ChildProcess.make options; the standalone combinator was removed. + +- `Command.stream` -> `ChildProcessSpawner.ChildProcessSpawner.spawn + ChildProcessHandle.stdout`: Spawn within a scope and consume the returned handle's stdout stream. + +- `Command.streamLines` -> `ChildProcessSpawner.ChildProcessSpawner.streamLines`: Text-line streaming moved onto the ChildProcessSpawner service. + +- `Command.string` -> `ChildProcessSpawner.ChildProcessSpawner.string`: Output collection moved onto the ChildProcessSpawner service. + +- `Command.workingDirectory` -> `ChildProcess.setCwd`: Use the renamed command combinator. + +### `@effect/platform/CommandExecutor` + +- `CommandExecutor.CommandExecutor` -> `ChildProcessSpawner.ChildProcessSpawner`: The executor service moved to effect/unstable/process/ChildProcessSpawner and was renamed. + +- `CommandExecutor.Process` -> `ChildProcessSpawner.ChildProcessHandle`: Running-process handles were renamed and moved to ChildProcessSpawner. + +- `CommandExecutor.Process.Id` -> `ChildProcessSpawner.ProcessId`: The process-id brand is now exported directly. + +- `CommandExecutor.ProcessTypeId` -> `none`: The ChildProcessHandle marker is internal in v4; use the ChildProcessHandle interface. + +- `CommandExecutor.TypeId` -> `none`: The Context.Service class replaces the public executor type-id alias. + +- `CommandExecutor.makeExecutor` -> `ChildProcessSpawner.make`: Use the renamed constructor; it derives output helpers from a spawn implementation. + +### `@effect/platform/Cookies` + +- `Cookies.CookieTypeId` -> `Cookies.isCookie`: The cookie brand is private in v4; use the public refinement instead of reading the type-id symbol. + +- `Cookies.CookiesError` -> `Cookies.CookiesError`: The error tag is CookiesError rather than CookieError. Update catchTag calls and \_tag comparisons; validation details are in the reason field. + +- `Cookies.ErrorTypeId` -> `Cookies.CookiesError`: The error brand is private in v4; identify the exported error class instead. + +- `Cookies.TypeId` -> `Cookies.isCookies`: The collection brand is private in v4; use the public refinement instead. + +- `Cookies.unsafeMakeCookie` -> `Cookies.makeCookieUnsafe`: Renamed to put Unsafe last; it still throws on invalid cookie data. + +- `Cookies.unsafeSet` -> `Cookies.setUnsafe`: Renamed to put Unsafe last; the dual throwing behavior is retained. + +- `Cookies.unsafeSetAll` -> `Cookies.setAllUnsafe`: Renamed to put Unsafe last; the dual all-or-throw behavior is retained. + +### `@effect/platform/Error` + +- `Error.Module` -> `string`: The closed module-name Schema was removed; PlatformError reason records accept any module string. + +- `Error.PlatformError` -> `PlatformError.PlatformError`: The module moved to effect/PlatformError and PlatformError became a wrapper class around BadArgument or SystemError. + +- `Error.SystemErrorReason` -> `PlatformError.SystemErrorTag`: The normalized system-error reason union was renamed. + +- `Error.TypeId` -> `none`: The PlatformError runtime marker is internal in v4; use the PlatformError class/tag. + +- `Error.TypeIdError` -> `Data.TaggedError or Schema.Error`: The platform-specific error-class factory was removed; define tagged data errors or schema-backed error classes directly. + +- `Error.isPlatformError` -> `value instanceof PlatformError.PlatformError`: PlatformError is a class in v4; use an instanceof check or match its PlatformError tag. + +### `@effect/platform/Etag` + +- `Etag.GeneratorTypeId` -> `Etag.Generator`: The standalone generator brand was removed; Generator is now a Context.Service class. + +- `Etag.layer` -> `Etag.layer`: Retained; it still provides the strong metadata-based ETag Generator service. + +### `@effect/platform/FetchHttpClient` + +- `FetchHttpClient.Fetch` -> `FetchHttpClient.Fetch`: Retained as a Context.Reference that defaults to globalThis.fetch. + +- `FetchHttpClient.layer` -> `FetchHttpClient.layer`: Retained as the HttpClient layer backed by the configured Fetch reference. + +### `@effect/platform/FileSystem` + +- `FileSystem.AccessFileOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.CopyOptions` -> `NonNullable[2]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.File.Descriptor` -> `none`: Native file descriptors are no longer part of the portable File interface; use the File methods and keep any platform handle private in custom implementations. + +- `FileSystem.FileDescriptor` -> `none`: The descriptor branding constructor was removed with the public fd field; use File operations instead of exposing a native descriptor. + +- `FileSystem.FileTypeId` -> `typeof FileSystem.FileTypeId`: The runtime marker remains exported, but the separate type alias was removed. + +- `FileSystem.GiB` -> `ByteSize.gibibytes`: Use the ByteSize binary unit constructor. + +- `FileSystem.KiB` -> `ByteSize.kibibytes`: Use the ByteSize binary unit constructor. + +- `FileSystem.MakeDirectoryOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.MakeTempDirectoryOptions` -> `NonNullable[0]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.MakeTempFileOptions` -> `NonNullable[0]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.MiB` -> `ByteSize.mebibytes`: Use the ByteSize binary unit constructor. + +- `FileSystem.OpenFileOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.PiB` -> `ByteSize.pebibytes`: Use the ByteSize binary unit constructor. + +- `FileSystem.ReadDirectoryOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.RemoveOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.SinkOptions` -> `NonNullable[1]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.Size` -> `ByteSize.ByteSize`: Use ByteSize.bytes or unit constructors for file sizes. Truncation lengths, buffer sizes, and read/write counts use number. File.seek takes and returns signed bigint positions; it can fail with PlatformError, including BadArgument when seeking before the start. + +- `FileSystem.SizeInput` -> `ByteSize.Input`: File-size and path-backed range inputs use ByteSize.Input. Truncation lengths, Web File ranges, and buffer sizes use number. + +- `FileSystem.StreamOptions` -> `NonNullable[1]>`: Stream options are inline; bufferSize was removed, bytesToRead and offset accept ByteSize inputs, and chunkSize uses number. + +- `FileSystem.TiB` -> `ByteSize.tebibytes`: Use the ByteSize binary unit constructor. + +- `FileSystem.WatchEventCreate` -> `FileSystem.WatchEvent.Create`: The constructor was removed; construct a tagged object with \_tag: "Create" and path. + +- `FileSystem.WatchEventRemove` -> `FileSystem.WatchEvent.Remove`: The constructor was removed; construct a tagged object with \_tag: "Remove" and path. + +- `FileSystem.WatchEventUpdate` -> `FileSystem.WatchEvent.Update`: The constructor was removed; construct a tagged object with \_tag: "Update" and path. + +- `FileSystem.WatchOptions` -> `FileSystem.WatchOptions`: Retained after moving the module to effect/FileSystem; pass `{ recursive: true }` as the optional second argument to FileSystem.watch. + +- `FileSystem.WriteFileOptions` -> `NonNullable[2]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.WriteFileStringOptions` -> `NonNullable[2]>`: Operation option interfaces are inline in the v4 FileSystem service. + +- `FileSystem.layerNoop` -> `FileSystem.layerNoop`: The helper remains after moving the module to effect/FileSystem. + +- `FileSystem.make` -> `FileSystem.make`: The constructor remains after moving the module to effect/FileSystem; adapt the implementation to the v4 service shape. + +### `@effect/platform/Headers` + +- `Headers.Headers` -> `Headers.Headers`: Import Headers from effect/unstable/http; the immutable string-record interface is retained with its v4 TypeId brand. + +- `Headers.HeadersTypeId` -> `Headers.TypeId`: The public Headers type-id symbol was renamed from HeadersTypeId to TypeId. + +- `Headers.currentRedactedNames` -> `Headers.CurrentRedactedNames`: Renamed and changed from FiberRef to Context.Reference; override it with service provisioning. + +- `Headers.remove` -> `Headers.remove / Headers.removeMany`: Use remove for one name or removeMany for an iterable; RegExp removal requires enumerating matching names. + +- `Headers.schema` -> `Schema.Headers`: The encoded-record and self schemas were consolidated and moved to effect/Schema as Schema.Headers. + +- `Headers.schemaFromSelf` -> `Schema.Headers`: The encoded-record and self schemas were consolidated and moved to effect/Schema as Schema.Headers. + +- `Headers.unsafeFromRecord` -> `Headers.fromRecordUnsafe`: Renamed to put Unsafe last; it still skips name normalization. + +### `@effect/platform/HttpApi` + +- `HttpApi.Api` -> `none`: The Context tag carrying the API was removed. Pass the HttpApi value explicitly to builders and clients. + +- `HttpApi.HttpApi.Any` -> `effect/unstable/httpapi/HttpApi#Constraint`: Use the erased marker constraint when only HttpApi identity is needed. + +- `HttpApi.HttpApi.AnyWithProps` -> `effect/unstable/httpapi/HttpApi#Top`: Use the widened HttpApi type that retains runtime properties. + +- `HttpApi.TypeId` -> `none`: The marker is private in v4; use HttpApi.isHttpApi for runtime narrowing and Constraint or Top for types. + +- `HttpApi.make` -> `effect/unstable/httpapi/HttpApi#make`: The constructor remains, but API-wide error and service parameters were removed; declare errors on endpoints and attach middleware. + +### `@effect/platform/HttpApiBuilder` + +- `HttpApiBuilder.Handlers` -> `effect/unstable/httpapi/HttpApiBuilder#Handlers`: Handlers now tracks an endpoint map and handled identifiers. Prefer Handlers.FromGroup\. + +- `HttpApiBuilder.Handlers.Error` -> `effect/unstable/httpapi/HttpApiBuilder#Handlers.Error`: The helper remains and extracts the error channel of an effectful group-builder return. + +- `HttpApiBuilder.Handlers.Middleware` -> `none`: The handler-internal HttpApp middleware alias was removed. Use HttpRouter.middleware inference or HttpRouter.middleware.Fn. + +- `HttpApiBuilder.Handlers.ValidateReturn` -> `effect/unstable/httpapi/HttpApiBuilder#Handlers.ValidateReturn`: The validator remains and now checks the endpoint map against handled identifiers. + +- `HttpApiBuilder.HandlersTypeId` -> `none`: The exported symbol was removed; do not inspect or construct the private Handlers marker. + +- `HttpApiBuilder.Middleware` -> `none`: The API-specific middleware service tag was removed. Declared HttpApiMiddleware services are applied while routes are built; use HttpRouter.middleware for additional global middleware. + +- `HttpApiBuilder.MiddlewareFn` -> `effect/unstable/http/HttpRouter#middleware.Fn`: HTTP apps are Effects in v4; use the router middleware function type or infer it through HttpRouter.middleware. + +- `HttpApiBuilder.Router` -> `effect/unstable/http/HttpRouter#HttpRouter`: The API-specific router tag was removed; API and group layers register with the shared HttpRouter service. + +- `HttpApiBuilder.api` -> `effect/unstable/httpapi/HttpApiBuilder#layer`: Use layer(api) and provide the group layers; it registers the completed API with HttpRouter. + +- `HttpApiBuilder.buildMiddleware` -> `none`: API-wide middleware assembly was removed. Declared HttpApiMiddleware services are applied while routes are built; use HttpRouter.middleware for additional middleware. + +- `HttpApiBuilder.group` -> `effect/unstable/httpapi/HttpApiBuilder#group`: The group layer remains; names are now identifiers and API/group global error channels are gone. + +- `HttpApiBuilder.handler` -> `effect/unstable/httpapi/HttpApiBuilder#handler`: The typed callback helper remains; names are now identifiers and API/group global error channels are gone. Pass the returned callback to handlers.handle. + +- `HttpApiBuilder.httpApp` -> `effect/unstable/http/HttpRouter#toHttpEffect`: Build the application from the assembled API route layer; HTTP apps are Effects in v4. + +- `HttpApiBuilder.middleware` -> `effect/unstable/http/HttpRouter#middleware`: Use router effect middleware and provide its layer to the API route layer; global middleware can target all router routes. + +- `HttpApiBuilder.middlewareCors` -> `effect/unstable/http/HttpRouter#cors`: Use the router CORS layer, or provide route-scoped HttpMiddleware.cors through HttpRouter.middleware. + +- `HttpApiBuilder.middlewareOpenApi` -> `effect/unstable/httpapi/HttpApiBuilder#layer`: Set openapiPath in layer(api, options). The additionalPropertiesStrategy option was removed. + +- `HttpApiBuilder.toWebHandler` -> `effect/unstable/http/HttpRouter#toWebHandler`: Pass the assembled API route layer to HttpRouter.toWebHandler; the handler and dispose lifecycle is retained. + +### `@effect/platform/HttpApiClient` + +- `HttpApiClient.Client.Method` -> `effect/unstable/httpapi/HttpApiClient#Client.Method`: The type remains without GroupError. Requests use params/query and responseMode instead of path/urlParams and withResponse. + +- `HttpApiClient.endpoint` -> `effect/unstable/httpapi/HttpApiClient#endpoint`: The endpoint client remains, selected by group and endpoint identifiers and using v4 request and responseMode fields. + +- `HttpApiClient.make` -> `effect/unstable/httpapi/HttpApiClient#make`: The generated client remains; errors and services are now derived per endpoint and middleware. + +- `HttpApiClient.makeWith` -> `effect/unstable/httpapi/HttpApiClient#makeWith`: The supplied-HttpClient constructor remains and now requires endpoint client-middleware services. + +### `@effect/platform/HttpApiEndpoint` + +- `HttpApiEndpoint.HttpApiEndpoint` -> `effect/unstable/httpapi/HttpApiEndpoint#HttpApiEndpoint`: The model remains, but its generics now carry path literals, schemas, middleware, and middleware services. + +- `HttpApiEndpoint.HttpApiEndpoint.AddContext` -> `effect/unstable/httpapi/HttpApiEndpoint#AddMiddleware`: Use AddMiddleware to add a middleware identifier and compute its service transformation. + +- `HttpApiEndpoint.HttpApiEndpoint.AddError` -> `none`: Declare error schemas in the endpoint constructor options; the type helper and fluent addError method were removed. + +- `HttpApiEndpoint.HttpApiEndpoint.Constructor` -> `none`: The tagged-template constructor type was removed; use HttpApiEndpoint.make(method)(identifier, path, options?). + +- `HttpApiEndpoint.HttpApiEndpoint.Context` -> `effect/unstable/httpapi/HttpApiEndpoint#ServerServices`: Use ServerServices for handler requirements; middleware IDs and extra requirements have separate extractors. + +- `HttpApiEndpoint.HttpApiEndpoint.ContextWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#ServerServicesWithIdentifier`: Name became Identifier; combine with middleware extractors when the complete handler requirement union is needed. + +- `HttpApiEndpoint.HttpApiEndpoint.Error` -> `effect/unstable/httpapi/HttpApiEndpoint#Errors`: Use Errors for the decoded endpoint and middleware error union; v4 Error extracts the schema. + +- `HttpApiEndpoint.HttpApiEndpoint.ErrorContext` -> `effect/unstable/httpapi/HttpApiEndpoint#ErrorServicesEncode / ErrorServicesDecode`: The single schema context split into server encoding and client decoding services. + +- `HttpApiEndpoint.HttpApiEndpoint.ErrorContextWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#ErrorServicesEncode / ErrorServicesDecode`: Select the endpoint with WithIdentifier, then apply the encode or decode service extractor. + +- `HttpApiEndpoint.HttpApiEndpoint.ErrorWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#ErrorsWithIdentifier`: Renamed for identifier and returns the decoded endpoint plus middleware error union. + +- `HttpApiEndpoint.HttpApiEndpoint.ExcludeName` -> `effect/unstable/httpapi/HttpApiEndpoint#ExcludeIdentifier`: Direct rename from name to identifier. + +- `HttpApiEndpoint.HttpApiEndpoint.ExtractPath` -> `none`: Tagged-template path extraction was removed. Put a params schema or field record in constructor option params. + +- `HttpApiEndpoint.HttpApiEndpoint.HandlerRawWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#HandlerRawWithIdentifier`: Direct rename; raw request fields are now params and query. + +- `HttpApiEndpoint.HttpApiEndpoint.HandlerWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#HandlerWithIdentifier`: Direct rename from name to identifier. + +- `HttpApiEndpoint.HttpApiEndpoint.OptionalTypePropertySignature` -> `none`: Removed with the tagged-template path implementation. + +- `HttpApiEndpoint.HttpApiEndpoint.PathEntries` -> `none`: Removed with tagged-template path extraction; declare endpoint params explicitly. + +- `HttpApiEndpoint.HttpApiEndpoint.PathParsed` -> `effect/unstable/httpapi/HttpApiEndpoint#Params`: Path data became params; Params extracts the schema, so use Params\["Type"] for decoded data. + +- `HttpApiEndpoint.HttpApiEndpoint.Payload` -> `effect/unstable/httpapi/HttpApiEndpoint#Payload`: The name remains but now extracts the schema; use Payload\["Type"] for buffered decoded data. + +- `HttpApiEndpoint.HttpApiEndpoint.Success` -> `effect/unstable/httpapi/HttpApiEndpoint#SuccessWithIdentifier`: Use SuccessWithIdentifier for the decoded, stream-aware result; v4 Success extracts the schema. + +- `HttpApiEndpoint.HttpApiEndpoint.SuccessWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#SuccessWithIdentifier`: Direct rename from name to identifier; the result remains decoded and stream-aware. + +- `HttpApiEndpoint.HttpApiEndpoint.UrlParams` -> `effect/unstable/httpapi/HttpApiEndpoint#Query`: urlParams became query; Query extracts the schema, so use Query\["Type"] for decoded data. + +- `HttpApiEndpoint.HttpApiEndpoint.ValidateHeaders` -> `effect/unstable/httpapi/HttpApiEndpoint#HeadersConstraint`: Validation moved from an intersection helper to a constructor generic constraint. + +- `HttpApiEndpoint.HttpApiEndpoint.ValidateParams` -> `none`: Tagged-template interpolation validation was removed; params are declared explicitly in options.params. + +- `HttpApiEndpoint.HttpApiEndpoint.ValidatePath` -> `effect/unstable/httpapi/HttpApiEndpoint#ParamsConstraint`: path became params and validation is now a constructor constraint. + +- `HttpApiEndpoint.HttpApiEndpoint.ValidatePayload` -> `effect/unstable/httpapi/HttpApiEndpoint#PayloadConstraint`: Payload validation is now a method-sensitive constructor constraint. + +- `HttpApiEndpoint.HttpApiEndpoint.ValidateUrlParams` -> `effect/unstable/httpapi/HttpApiEndpoint#QueryConstraint`: urlParams became query and validation is now a constructor constraint. + +- `HttpApiEndpoint.PathSegment` -> `effect/unstable/http/HttpRouter#PathInput`: Path input moved to the shared router and is generalized to slash-prefixed paths or wildcard. + +- `HttpApiEndpoint.TypeId` -> `none`: The endpoint type ID is private; use HttpApiEndpoint.isHttpApiEndpoint for runtime narrowing. + +- `HttpApiEndpoint.get` -> `effect/unstable/httpapi/HttpApiEndpoint#get`: Use get(identifier, path, options?); tagged templates and fluent schema setters were removed. + +- `HttpApiEndpoint.head` -> `effect/unstable/httpapi/HttpApiEndpoint#head`: Use head(identifier, path, options?); tagged templates and fluent schema setters were removed. + +- `HttpApiEndpoint.make` -> `effect/unstable/httpapi/HttpApiEndpoint#make`: The factory remains but now requires identifier, path, and options and applies codecs unless disabled. + +- `HttpApiEndpoint.options` -> `effect/unstable/httpapi/HttpApiEndpoint#options`: Same HTTP method constructor with the new identifier, path, and options signature. + +- `HttpApiEndpoint.patch` -> `effect/unstable/httpapi/HttpApiEndpoint#patch`: Same HTTP method constructor with the new identifier, path, and options signature. + +- `HttpApiEndpoint.post` -> `effect/unstable/httpapi/HttpApiEndpoint#post`: Same HTTP method constructor with the new identifier, path, and options signature. + +- `HttpApiEndpoint.put` -> `effect/unstable/httpapi/HttpApiEndpoint#put`: Same HTTP method constructor with the new identifier, path, and options signature. + +### `@effect/platform/HttpApiError` + +- `HttpApiError.Forbidden` -> `effect/unstable/httpapi/HttpApiError#ForbiddenNoContent`: Use ForbiddenNoContent to preserve the empty 403 wire schema; Forbidden now has a JSON-tagged body. + +- `HttpApiError.HttpApiDecodeError` -> `effect/unstable/httpapi/HttpApiError#HttpApiSchemaError`: Validation now stores kind and a SchemaError cause and is a defect unless transformed by schema-error middleware. + +- `HttpApiError.Issue` -> `effect/SchemaIssue#Issue`: Structured failures now live at HttpApiSchemaError.cause.issue; format them explicitly when a flat external list is needed. + +- `HttpApiError.TypeId` -> `effect/unstable/httpapi/HttpApiError#HttpApiSchemaErrorTypeId`: The old module symbol is gone; prefer HttpApiSchemaError.is for runtime narrowing. + +### `@effect/platform/HttpApiGroup` + +- `HttpApiGroup.ApiGroup` -> `effect/unstable/httpapi/HttpApiGroup#Service`: Renamed; the service field and type parameter are now identifier rather than name. + +- `HttpApiGroup.HttpApiGroup.AddContext` -> `none`: Groups no longer carry arbitrary context. Use AddMiddleware for middleware service transformations. + +- `HttpApiGroup.HttpApiGroup.Any` -> `effect/unstable/httpapi/HttpApiGroup#Constraint`: Renamed widened structural constraint. + +- `HttpApiGroup.HttpApiGroup.AnyWithProps` -> `effect/unstable/httpapi/HttpApiGroup#Top`: Renamed widened runtime-property type. + +- `HttpApiGroup.HttpApiGroup.ClientContext` -> `effect/unstable/httpapi/HttpApiGroup#ClientServices / ErrorServicesDecode / MiddlewareClient`: Client schema services and required client middleware are separate extractors in v4. + +- `HttpApiGroup.HttpApiGroup.Context` -> `none`: Group error and context generics were removed; derive server requirements from the group's endpoints. + +- `HttpApiGroup.HttpApiGroup.ContextWithName` -> `none`: Select with WithIdentifier and derive endpoint server requirements; groups no longer have a context generic. + +- `HttpApiGroup.HttpApiGroup.EndpointsWithName` -> `effect/unstable/httpapi/HttpApiGroup#EndpointsWithIdentifier`: Direct rename from name to identifier. + +- `HttpApiGroup.HttpApiGroup.Error` -> `none`: Group-level errors were removed. Declare shared errors on each endpoint or through middleware. + +- `HttpApiGroup.HttpApiGroup.ErrorContext` -> `effect/unstable/httpapi/HttpApiGroup#ErrorServicesEncode / ErrorServicesDecode`: The closest endpoint-error aggregate splits server encoding from client decoding services. + +- `HttpApiGroup.HttpApiGroup.ErrorWithName` -> `none`: Group-level errors were removed; select with WithIdentifier and inspect Errors over the selected endpoints. + +- `HttpApiGroup.HttpApiGroup.Middleware` -> `effect/unstable/httpapi/HttpApiEndpoint#Middleware`: Middleware is attached to the endpoints present when group.middleware is called; extract it from group endpoints. + +- `HttpApiGroup.HttpApiGroup.MiddlewareWithName` -> `effect/unstable/httpapi/HttpApiEndpoint#Middleware`: Select the group with WithIdentifier, get its endpoints, then apply the endpoint Middleware extractor. + +- `HttpApiGroup.HttpApiGroup.Provides` -> `effect/unstable/httpapi/HttpApiGroup#MiddlewareProvides`: Renamed; derives provided services from endpoint middleware. + +- `HttpApiGroup.HttpApiGroup.ToService` -> `effect/unstable/httpapi/HttpApiGroup#ToService`: Same role and now produces Service\. + +- `HttpApiGroup.HttpApiGroup.WithName` -> `effect/unstable/httpapi/HttpApiGroup#WithIdentifier`: Direct rename from name to identifier. + +- `HttpApiGroup.TypeId` -> `none`: The group type ID is private; use HttpApiGroup.isHttpApiGroup for runtime narrowing. + +- `HttpApiGroup.make` -> `effect/unstable/httpapi/HttpApiGroup#make`: The constructor remains; group error and context generics are gone and add is variadic. + +### `@effect/platform/HttpApiMiddleware` + +- `HttpApiMiddleware.HttpApiMiddleware` -> `effect/unstable/httpapi/HttpApiMiddleware#HttpApiMiddleware`: The model remains but now wraps the response effect and carries provided services, an error schema, and required services. + +- `HttpApiMiddleware.HttpApiMiddleware.Any` -> `effect/unstable/httpapi/HttpApiMiddleware#AnyService`: Renamed widened middleware service-key shape. + +- `HttpApiMiddleware.HttpApiMiddleware.AnyId` -> `effect/unstable/httpapi/HttpApiMiddleware#AnyId`: Same name; metadata now includes provided and required services, error schema, client error, and client requirement. + +- `HttpApiMiddleware.HttpApiMiddleware.Error` -> `effect/unstable/httpapi/HttpApiMiddleware#Error`: Same name and now derives the decoded type from the configured error schema. + +- `HttpApiMiddleware.HttpApiMiddleware.ErrorContext` -> `effect/unstable/httpapi/HttpApiMiddleware#ErrorServicesEncode / ErrorServicesDecode`: The single schema context split into server encoding and client decoding services. + +- `HttpApiMiddleware.HttpApiMiddleware.Only` -> `Extract`: The helper was removed because middleware IDs are explicit; use Extract when the direct filter is still needed. + +- `HttpApiMiddleware.HttpApiMiddleware.Provides` -> `effect/unstable/httpapi/HttpApiMiddleware#Provides`: Same name and reads the expanded v4 middleware ID metadata. + +- `HttpApiMiddleware.SecurityTypeId` -> `none`: The marker is private; use HttpApiMiddleware.isSecurity. + +- `HttpApiMiddleware.Tag` -> `effect/unstable/httpapi/HttpApiMiddleware#Service`: Renamed and redesigned; use error, requires, provides, clientError, and requiredForClient configuration. + +- `HttpApiMiddleware.TagClass` -> `effect/unstable/httpapi/HttpApiMiddleware#ServiceClass`: Renamed class type with the new two-stage type configuration and wrapping service shape. + +- `HttpApiMiddleware.TagClass.BaseSecurity` -> `effect/unstable/httpapi/HttpApiMiddleware#ServiceClass`: Security is conditional metadata on ServiceClass; there is no separate public base interface. + +- `HttpApiMiddleware.TagClass.Failure` -> `effect/unstable/httpapi/HttpApiMiddleware#Error`: failure terminology became error; apply the extractor to the middleware ID. + +- `HttpApiMiddleware.TagClass.FailureContext` -> `effect/unstable/httpapi/HttpApiMiddleware#ErrorServicesEncode / ErrorServicesDecode`: Failure schema services split by server encoding and client decoding direction. + +- `HttpApiMiddleware.TagClass.FailureSchema` -> `effect/unstable/httpapi/HttpApiMiddleware#ErrorSchema`: Renamed and applied to the middleware ID rather than constructor options. + +- `HttpApiMiddleware.TagClass.FailureService` -> `effect/unstable/httpapi/HttpApiMiddleware#Error`: Use the decoded error extractor; optional middleware fallback was removed. + +- `HttpApiMiddleware.TagClass.Optional` -> `none`: Optional declaration and fallback-on-failure behavior were removed; model fallback in the wrapping middleware. + +- `HttpApiMiddleware.TagClass.Provides` -> `effect/unstable/httpapi/HttpApiMiddleware#Provides`: Moved to the module level and applied to the middleware ID. + +- `HttpApiMiddleware.TagClassAny` -> `effect/unstable/httpapi/HttpApiMiddleware#AnyService`: Renamed widened service-key type. + +- `HttpApiMiddleware.TagClassSecurityAny` -> `effect/unstable/httpapi/HttpApiMiddleware#AnyServiceSecurity`: Renamed widened security service-key type. + +- `HttpApiMiddleware.TypeId` -> `none`: The marker is private; use public guards and type extractors. + +### `@effect/platform/HttpApiScalar` + +- `HttpApiScalar.layer` -> `effect/unstable/httpapi/HttpApiScalar#layer`: Pass the HttpApi as the first argument; the layer now contributes directly to HttpRouter. + +- `HttpApiScalar.layerHttpLayerRouter` -> `effect/unstable/httpapi/HttpApiScalar#layer`: The duplicate was removed. Pass options.api as the first layer argument and the remaining Scalar options second. + +- `HttpApiScalar.layerHttpLayerRouterCdn` -> `effect/unstable/httpapi/HttpApiScalar#layerCdn`: Use the explicit-api CDN layer with path, version, and Scalar options. + +### `@effect/platform/HttpApiSchema` + +- `HttpApiSchema.AnnotationEmptyDecodeable` -> `effect/unstable/httpapi/HttpApiSchema#asNoContent`: The public marker was removed; represent no-content decoding structurally with asNoContent({ decode }). + +- `HttpApiSchema.AnnotationEncoding` -> `effect/unstable/httpapi/HttpApiSchema#asJson / asFormUrlEncoded / asText / asUint8Array`: The key is internal; select encoding with a public combinator. + +- `HttpApiSchema.AnnotationMultipart` -> `effect/unstable/httpapi/HttpApiSchema#asMultipart`: The symbol annotation became a brand plus internal encoding metadata; apply the schema combinator. + +- `HttpApiSchema.AnnotationMultipartStream` -> `effect/unstable/httpapi/HttpApiSchema#asMultipartStream`: The symbol annotation became a brand plus internal encoding metadata; apply the schema combinator. + +- `HttpApiSchema.AnnotationParam` -> `effect/unstable/httpapi/HttpApiEndpoint#params`: Path names now live in the router path and schemas in endpoint option params, not schema annotations. + +- `HttpApiSchema.AnnotationStatus` -> `effect/unstable/httpapi/HttpApiSchema#status`: The public symbol was removed; apply status(code), which uses the httpApiStatus schema annotation. + +- `HttpApiSchema.Empty` -> `effect/unstable/httpapi/HttpApiSchema#Empty`: The API remains and returns Schema.Void annotated with the supplied status. + +- `HttpApiSchema.EmptyError` -> `effect/Schema#Error`: Define a normal schema error with httpApiStatus, then derive its no-content wire schema with asNoContent. + +- `HttpApiSchema.EmptyErrorClass` -> `effect/Schema#Error`: The class and no-content codec are separate in v4; combine Schema.Error with HttpApiSchema.asNoContent. + +- `HttpApiSchema.EmptyErrorUnify` -> `none`: Removed with EmptyError; Schema.Error instances already support yieldable-error behavior. + +- `HttpApiSchema.EmptyErrorUnifyIgnore` -> `none`: Removed with EmptyError; do not recreate the old Unify marker. + +- `HttpApiSchema.Encoding` -> `effect/unstable/httpapi/HttpApiSchema#Encoding`: The name remains but is now a discriminated PayloadEncoding or ResponseEncoding union; prefer public as\* combinators. + +- `HttpApiSchema.Multipart` -> `effect/unstable/httpapi/HttpApiSchema#asMultipart`: The type and constructor became a curried schema combinator: schema.pipe(asMultipart(options)). + +- `HttpApiSchema.MultipartStream` -> `effect/unstable/httpapi/HttpApiSchema#asMultipartStream`: The type and constructor became a curried schema combinator. + +- `HttpApiSchema.Text` -> `effect/unstable/httpapi/HttpApiSchema#asText`: Apply the encoding combinator to Schema.String instead of using a dedicated constructor. + +- `HttpApiSchema.Uint8Array` -> `effect/unstable/httpapi/HttpApiSchema#asUint8Array`: Apply the encoding combinator to Schema.Uint8Array instead of using a dedicated constructor. + +- `HttpApiSchema.UnionUnify` -> `effect/Schema#Union`: Use Schema.Union([self, that]); for endpoint alternatives, pass the schema array directly to preserve metadata. + +- `HttpApiSchema.annotations` -> `effect/Schema#annotate`: Schema annotations became annotate; set httpApiStatus directly or prefer HttpApiSchema.status for status only. + +- `HttpApiSchema.asEmpty` -> `effect/unstable/httpapi/HttpApiSchema#asNoContent`: Use schema.pipe(asNoContent({ decode }), status(code)); status is now a separate combinator. + +- `HttpApiSchema.deunionize` -> `none`: Pass schema arrays to endpoint success, error, and body alternatives so each member retains status and content type. + +- `HttpApiSchema.extractAnnotations` -> `none`: The internal symbol-copy helper was removed; HTTP metadata is schema-native and resolved through AST traversal. + +- `HttpApiSchema.getEmptyDecodeable` -> `effect/unstable/httpapi/HttpApiSchema#isNoContent`: Use isNoContent only to test bodylessness; decodeability is structural and has no exact query replacement. + +- `HttpApiSchema.getEncoding` -> `effect/unstable/httpapi/HttpApiSchema#getPayloadEncoding / getResponseEncoding`: Encoding lookup split by direction; application code should normally use public as\* combinators. + +- `HttpApiSchema.getMultipart` -> `effect/unstable/httpapi/HttpApiSchema#getPayloadEncoding`: Narrow the payload encoding to Multipart with buffered mode; multipart limits are on the encoding value. + +- `HttpApiSchema.getMultipartStream` -> `effect/unstable/httpapi/HttpApiSchema#getPayloadEncoding`: Narrow the payload encoding to Multipart with stream mode; multipart limits are on the encoding value. + +- `HttpApiSchema.getParam` -> `none`: Param identity moved out of schema metadata; read endpoint.path and endpoint.params. + +- `HttpApiSchema.getStatus` -> `effect/SchemaAST#resolveAt`: Resolve the httpApiStatus annotation directly, or prefer getStatusSuccess and getStatusError for response logic. + +- `HttpApiSchema.getStatusError` -> `effect/unstable/httpapi/HttpApiSchema#getStatusError`: The helper remains but accepts an AST and defaults to 500. + +- `HttpApiSchema.getStatusErrorAST` -> `effect/unstable/httpapi/HttpApiSchema#getStatusError`: The AST suffix collapsed into the sole helper, which defaults to 500. + +- `HttpApiSchema.getStatusSuccess` -> `effect/unstable/httpapi/HttpApiSchema#getStatusSuccess`: The helper remains but accepts an AST; bare Schema.Void now defaults to 200, so use Empty(204) for 204. + +- `HttpApiSchema.getStatusSuccessAST` -> `effect/unstable/httpapi/HttpApiSchema#getStatusSuccess`: The AST suffix collapsed into the sole helper; bare Schema.Void no longer implies 204. + +- `HttpApiSchema.param` -> `effect/unstable/httpapi/HttpApiEndpoint#params`: Use a literal /:name path and the matching field in endpoint constructor option params. + +- `HttpApiSchema.withEncoding` -> `effect/unstable/httpapi/HttpApiSchema#asJson / asFormUrlEncoded / asUint8Array / asText`: Replace the generic kind with the matching public curried encoding combinator. + +### `@effect/platform/HttpApiSecurity` + +- `HttpApiSecurity.Bearer` -> `effect/unstable/httpapi/HttpApiSecurity#Http`: Bearer was generalized to Http with scheme Bearer; the value-level bearer singleton remains. + +- `HttpApiSecurity.TypeId` -> `none`: The marker is private; use the public union or specific Http, ApiKey, and Basic types. + +- `HttpApiSecurity.annotate` -> `effect/unstable/httpapi/HttpApiSecurity#annotate`: The combinator remains; its key is now the v4 Context.Key abstraction. + +- `HttpApiSecurity.annotateContext` -> `effect/unstable/httpapi/HttpApiSecurity#annotateMerge`: Renamed; it still merges a Context into existing OpenAPI annotations. + +### `@effect/platform/HttpApiSwagger` + +- `HttpApiSwagger.layer` -> `effect/unstable/httpapi/HttpApiSwagger#layer`: Pass the HttpApi as the first argument; the layer now contributes directly to HttpRouter. + +- `HttpApiSwagger.layerHttpLayerRouter` -> `effect/unstable/httpapi/HttpApiSwagger#layer`: The duplicate was removed. Pass options.api first and the path option second. + +### `@effect/platform/HttpApp` + +- `HttpApp.Default` -> `Effect.Effect`: The alias was removed; v4 HTTP applications are ordinary response-producing Effects. + +- `HttpApp.HttpApp` -> `Effect.Effect`: The alias was removed; use the underlying Effect type and HttpEffect boundary combinators. + +- `HttpApp.currentPreResponseHandlers` -> `HttpEffect.appendPreResponseHandler / HttpEffect.withPreResponseHandler`: The FiberRef was removed; register request-local handlers through HttpEffect. + +- `HttpApp.ejectDefaultScopeClose` -> `HttpEffect.scopeDisableClose`: Renamed; it disables automatic request-scope closure, leaving closure to the caller. + +- `HttpApp.toWebHandler` -> `HttpEffect.toWebHandler`: Moved to HttpEffect for converting an HTTP effect to a Web handler. + +- `HttpApp.toWebHandlerRuntime` -> `HttpEffect.toWebHandlerWith(context)`: Runtime was removed in v4; supply a Context with toWebHandlerWith instead. + +- `HttpApp.unsafeEjectStreamScope` -> `HttpEffect.scopeTransferToStream`: Renamed; it transfers request-scope closure to a streaming response. + +### `@effect/platform/HttpBody` + +- `HttpBody.Empty` -> `HttpBody.Empty`: Retained with the same tag, but v4 exports a class rather than an interface. + +- `HttpBody.ErrorReason` -> `HttpBody.ErrorReason`: Retained but reshaped; original causes now live on HttpBodyError.cause. + +- `HttpBody.ErrorTypeId` -> `HttpBody.HttpBodyError`: The error type-id is private in v4; identify the exported error class instead. + +- `HttpBody.HttpBodyError` -> `HttpBody.HttpBodyError`: Changed from a factory/interface to a class constructed with reason and optional cause. + +- `HttpBody.Raw` -> `HttpBody.Raw`: Retained with the same tag and payload, but v4 exports a class. + +- `HttpBody.Stream` -> `HttpBody.Stream`: Retained with the same tag and byte stream, but v4 exports a class. + +- `HttpBody.TypeId` -> `HttpBody.isHttpBody`: The body brand is private in v4; use the public refinement instead. + +- `HttpBody.Uint8Array` -> `HttpBody.Uint8Array`: Retained with the same fields and tag, but v4 exports a class. + +- `HttpBody.file` -> `HttpBody.file`: Retained; bufferSize was replaced by numeric chunkSize. Offset and bytesToRead accept ByteSize.Input. Invalid ranges and a final EOF-clamped content length above Number.MAX\_SAFE\_INTEGER fail with PlatformError / BadArgument. + +- `HttpBody.fileInfo` -> `HttpBody.fileFromInfo`: Renamed; it uses supplied File.Info with ByteSize size metadata and requires FileSystem. Offset and bytesToRead accept ByteSize.Input, while chunkSize is numeric. Invalid ranges and a final EOF-clamped content length above Number.MAX\_SAFE\_INTEGER fail with PlatformError / BadArgument. + +- `HttpBody.unsafeJson` -> `HttpBody.jsonUnsafe`: Renamed to put Unsafe last; serialization failures still throw. + +- `HttpBody.urlParams` -> `HttpBody.urlParams`: Retained and widened to accept UrlParams.Input. + +### `@effect/platform/HttpClient` + +- `HttpClient.SpanNameGenerator` -> `HttpClient.SpanNameGenerator`: The interface became a Context.Reference containing the generator function. + +- `HttpClient.TypeId` -> `HttpClient.isHttpClient`: The brand key is private in v4; use the public runtime refinement. + +- `HttpClient.catchAll` -> `HttpClient.catch`: Renamed to catch; the recovery callback still returns a response effect. + +- `HttpClient.catchTag` -> `HttpClient.catchTag`: Retained and widened to accept one or more error tags. + +- `HttpClient.currentTracerDisabledWhen` -> `HttpClient.TracerDisabledWhen`: Renamed and changed from FiberRef to Context.Reference. + +- `HttpClient.currentTracerPropagation` -> `HttpClient.TracerPropagationEnabled`: Renamed and changed from FiberRef to Context.Reference\. + +- `HttpClient.filterOrFail` -> `HttpClient.filterOrFail`: Retained; v4 also provides refinement overloads. + +- `HttpClient.filterStatus` -> `HttpClient.filterStatus`: Retained; rejection now fails with the HttpClientError wrapper. + +- `HttpClient.filterStatusOk` -> `HttpClient.filterStatusOk`: Retained; non-2xx responses now fail with the HttpClientError wrapper. + +- `HttpClient.make` -> `HttpClient.make`: Retained; the runner receives Fiber.Fiber and failures use the v4 error wrapper. + +- `HttpClient.makeWith` -> `HttpClient.makeWith`: Retained with the preprocess and postprocess constructor pattern. + +- `HttpClient.retry` -> `HttpClient.retry`: Retained; the Schedule error channel is included in the resulting client error type. + +- `HttpClient.tap` -> `effect/unstable/http/HttpClient#tap`: Moved to the v4 HTTP module with the same response-effect callback and client error/service widening. + +- `HttpClient.transform` -> `effect/unstable/http/HttpClient#transform`: Moved to the v4 HTTP module with the same request-aware transformation shape. + +- `HttpClient.withSpanNameGenerator` -> `HttpClient.transformResponse(Effect.provideService(HttpClient.SpanNameGenerator, f))`: The convenience combinator was removed; provide the reference around response effects. + +- `HttpClient.withTracerDisabledWhen` -> `HttpClient.transformResponse(Effect.provideService(HttpClient.TracerDisabledWhen, predicate))`: The convenience combinator was removed; provide the reference around response effects. + +- `HttpClient.withTracerPropagation` -> `HttpClient.transformResponse(Effect.provideService(HttpClient.TracerPropagationEnabled, enabled))`: Provide the renamed propagation reference around response effects. + +### `@effect/platform/HttpClientError` + +- `HttpClientError.HttpClientError` -> `HttpClientError.HttpClientError`: Changed from a union to a tagged wrapper class containing a concrete failure in reason. + +- `HttpClientError.RequestError` -> `HttpClientError.RequestError`: Now a type-only reason union; construct a concrete reason and wrap it in HttpClientError. + +- `HttpClientError.TypeId` -> `HttpClientError.isHttpClientError`: The brand key is private in v4; use the public runtime refinement. + +### `@effect/platform/HttpClientRequest` + +- `HttpClientRequest.Options.NoBody` -> `HttpClientRequest.Options.NoUrl`: NoBody was removed; v4 method helpers uniformly omit only url. + +- `HttpClientRequest.TypeId` -> `HttpClientRequest.isHttpClientRequest`: The request brand is private in v4; use the public runtime refinement. + +- `HttpClientRequest.bodyFileWeb` -> `HttpClientRequest.setBody + HttpBody.stream + Stream.fromReadableStream`: No one-call replacement remains; stream file.stream() and pass file.type and file.size to HttpBody.stream. + +- `HttpClientRequest.bodyUnsafeJson` -> `HttpClientRequest.bodyJsonUnsafe`: Renamed to put Unsafe last; serialization remains synchronous and throwing. + +- `HttpClientRequest.get` -> `HttpClientRequest.get`: Retained; options now use Options.NoUrl and no longer exclude body. + +- `HttpClientRequest.head` -> `HttpClientRequest.head`: Retained; options now use Options.NoUrl and no longer exclude body. + +- `HttpClientRequest.make` -> `HttpClientRequest.make`: Retained; all methods now accept Options.NoUrl without the GET/HEAD body restriction. + +- `HttpClientRequest.setBody` -> `HttpClientRequest.setBody`: Retained and still synchronizes body content metadata into headers. + +- `HttpClientRequest.toUrl` -> `HttpClientRequest.toUrl`: Retained and still returns Option\. + +### `@effect/platform/HttpClientResponse` + +- `HttpClientResponse.HttpClientResponse` -> `HttpClientResponse.HttpClientResponse`: Custom implementations must supply the required url string. It represents the resolved URL including query parameters and excluding the hash, uses the final URL after redirects, and is empty when unknown. + +- `HttpClientResponse.TypeId` -> `typeof HttpClientResponse.TypeId`: TypeId remains public but is now a string constant; use typeof in type position. + +- `HttpClientResponse.filterStatus` -> `HttpClientResponse.filterStatus`: Retained; rejected status now fails with an HttpClientError wrapper. + +- `HttpClientResponse.filterStatusOk` -> `HttpClientResponse.filterStatusOk`: Retained; non-2xx status now fails with an HttpClientError wrapper. + +- `HttpClientResponse.schemaBodyJson` -> `HttpClientResponse.schemaBodyJson`: Retained with v4 Schema constraints and SchemaError failures. + +- `HttpClientResponse.schemaBodyUrlParams` -> `HttpClientResponse.schemaBodyUrlParams`: Retained with ConstraintCodec input and SchemaError failures. + +- `HttpClientResponse.schemaHeaders` -> `HttpClientResponse.schemaHeaders`: Retained with ConstraintCodec input and SchemaError failures. + +- `HttpClientResponse.schemaJson` -> `HttpClientResponse.schemaJson`: Retained with ConstraintCodec input and v4 error types. + +- `HttpClientResponse.schemaNoBody` -> `HttpClientResponse.schemaNoBody`: Retained with Schema.Codec input and SchemaError failures. + +- `HttpClientResponse.stream` -> `HttpClientResponse.stream`: Retained; body failures now use the broader HttpClientError wrapper. + +### `@effect/platform/HttpIncomingMessage` + +- `HttpIncomingMessage.MaxBodySize` -> `HttpIncomingMessage.MaxBodySize`: Changed from a Reference subclass holding Option\ to Context.Reference\. + +- `HttpIncomingMessage.TypeId` -> `typeof HttpIncomingMessage.TypeId`: TypeId remains public but is now a string constant; use typeof in type position. + +- `HttpIncomingMessage.withMaxBodySize` -> `Effect.provideService(HttpIncomingMessage.MaxBodySize, size)`: The helper was removed; provide a ByteSize value or undefined directly. + +### `@effect/platform/HttpLayerRouter` + +- `HttpLayerRouter.FindMyWay.PathInput` -> `FindMyWay.PathInput`: Import FindMyWay from effect/unstable/http. + +- `HttpLayerRouter.FindMyWay.make` -> `FindMyWay.make`: Import FindMyWay from effect/unstable/http. + +- `HttpLayerRouter.MiddlewareTypeId` -> `none`: The middleware type id is internal in v4; use HttpRouter.Middleware. + +- `HttpLayerRouter.PathInput` -> `HttpRouter.PathInput`: Moved to the consolidated v4 router. + +- `HttpLayerRouter.Request.From` -> `HttpRouter.Request.From`: Moved with the layer-oriented router into the consolidated HttpRouter module. + +- `HttpLayerRouter.Request.Only` -> `HttpRouter.Request.Only`: Moved with the layer-oriented router into the consolidated HttpRouter module. + +- `HttpLayerRouter.Route.Context` -> `HttpRouter.Route.Context`: Moved with the Route helper types into the consolidated HttpRouter module. + +- `HttpLayerRouter.Route.Error` -> `HttpRouter.Route.Error`: Moved with the Route helper types into the consolidated HttpRouter module. + +- `HttpLayerRouter.RouteContext` -> `HttpRouter.RouteContext`: Moved to the consolidated v4 router. + +- `HttpLayerRouter.RouteTypeId` -> `none`: Route nominal ids are internal in v4; construct routes with HttpRouter.route. + +- `HttpLayerRouter.RouterConfig` -> `HttpRouter.RouterConfig`: Now a Context.Reference containing Partial\. + +- `HttpLayerRouter.TypeId` -> `none`: The router nominal service id is internal in v4; use HttpRouter.HttpRouter. + +- `HttpLayerRouter.add` -> `HttpRouter.add`: Moved to the consolidated HttpRouter; it still returns a route-registration Layer. + +- `HttpLayerRouter.addAll` -> `HttpRouter.addAll`: Moved to the consolidated HttpRouter; it still registers route values through a Layer and supports a prefix option. + +- `HttpLayerRouter.addHttpApi` -> `HttpApiBuilder.layer`: HTTP API registration moved to effect/unstable/httpapi. + +- `HttpLayerRouter.cors` -> `HttpRouter.cors`: HttpLayerRouter was consolidated into v4 HttpRouter. + +- `HttpLayerRouter.layer` -> `HttpRouter.layer`: Use the layer for the consolidated HttpRouter service. + +- `HttpLayerRouter.make` -> `HttpRouter.make`: The layer-oriented router became the sole v4 HttpRouter implementation. + +- `HttpLayerRouter.schemaJson` -> `HttpRouter.schemaJson`: Moved to the consolidated router with v4 Schema and error types. + +- `HttpLayerRouter.schemaNoBody` -> `HttpRouter.schemaNoBody`: Moved to the consolidated router with v4 Schema types. + +- `HttpLayerRouter.serve` -> `HttpRouter.serve`: Moved to the consolidated router; pass the route-registration layer. + +- `HttpLayerRouter.toHttpEffect` -> `HttpRouter.toHttpEffect`: Moved to the consolidated HttpRouter; route-not-found failures now use HttpServerError.HttpServerError. + +- `HttpLayerRouter.toWebHandler` -> `HttpRouter.toWebHandler`: Moved to the consolidated router for building a Fetch handler and disposer. + +### `@effect/platform/HttpMiddleware` + +- `HttpMiddleware.SpanNameGenerator` -> `HttpMiddleware.SpanNameGenerator`: The branded interface became a Context.Reference containing the generator. + +- `HttpMiddleware.cors` -> `HttpMiddleware.cors`: Retained with the same CORS options and behavior. + +- `HttpMiddleware.currentTracerDisabledWhen` -> `HttpMiddleware.TracerDisabledWhen`: The FiberRef became a Context.Reference containing the request predicate. + +- `HttpMiddleware.loggerDisabled` -> `HttpMiddleware.withLoggerDisabled`: The FiberRef was removed; locally wrap an effect or use HttpRouter.disableLogger. + +- `HttpMiddleware.withSpanNameGenerator` -> `Layer.provide(layer, Layer.succeed(HttpMiddleware.SpanNameGenerator)(f))`: Provide the SpanNameGenerator reference to the target layer. + +- `HttpMiddleware.withTracerDisabledForUrls` -> `Layer.provide(layer, HttpMiddleware.layerTracerDisabledForUrls(urls))`: Provide the new URL-predicate layer to the target layer. + +- `HttpMiddleware.withTracerDisabledWhen` -> `Layer.provide(layer, Layer.succeed(HttpMiddleware.TracerDisabledWhen)(predicate))`: Provide the TracerDisabledWhen reference to the target layer. + +- `HttpMiddleware.withTracerDisabledWhenEffect` -> `Effect.provideService(effect, HttpMiddleware.TracerDisabledWhen, predicate)`: Provide the TracerDisabledWhen reference locally to the effect. + +### `@effect/platform/HttpPlatform` + +- `HttpPlatform.HttpPlatform` -> `HttpPlatform.HttpPlatform`: The service is now a Context.Service class; use its Service member for the implementation type. Path-backed offset and bytesToRead accept ByteSize.Input, while chunkSize and all Web File range options use number. + +- `HttpPlatform.TypeId` -> `none`: The public type id was removed; use the HttpPlatform Context.Service class. + +- `HttpPlatform.layer` -> `HttpPlatform.layer`: Retained as the default file-response layer. + +- `HttpPlatform.make` -> `HttpPlatform.make`: Retained; v4 returns the service implementation. The fileResponse callback receives contentLength as bigint, while start and end remain numbers. Web File range options use number. + +### `@effect/platform/HttpRouter` + +- `HttpRouter.Default` -> `HttpRouter.HttpRouter + HttpRouter.layer`: Custom/default router tags were removed; v4 provides one router service. + +- `HttpRouter.HttpRouter` -> `HttpRouter.HttpRouter`: The name remains, but now denotes the mutable layer-oriented registration service. + +- `HttpRouter.HttpRouter.DefaultServices` -> `none`: The custom tagged-router default-service bundle was removed. + +- `HttpRouter.HttpRouter.Service` -> `HttpRouter.HttpRouter`: Use the consolidated router service interface. + +- `HttpRouter.Route.Middleware` -> `Effect.Effect`: Spell the route response Effect directly, or use HttpRouter.middleware for transforms. + +- `HttpRouter.RouteContextTypeId` -> `none`: The nominal id is internal in v4; access HttpRouter.RouteContext as a service. + +- `HttpRouter.RouteTypeId` -> `none`: The nominal id is internal in v4; construct routes with HttpRouter.route. + +- `HttpRouter.Tag` -> `none`: Custom router tags were removed; use the singleton router service and registration layers. + +- `HttpRouter.TypeId` -> `none`: The router nominal service id is internal in v4; use HttpRouter.HttpRouter. + +- `HttpRouter.all` -> `HttpRouter.add("*", path, handler, options)`: v4 registers a route layer instead of returning an immutable router. + +- `HttpRouter.append` -> `HttpRouter.addAll([route])`: Register the route and merge its layer with other route layers. + +- `HttpRouter.catchAll` -> `HttpRouter.middleware + Effect.catch`: Apply typed-error recovery in route middleware provided to route layers. + +- `HttpRouter.catchAllCause` -> `HttpRouter.middleware + Effect.catchCause`: Apply cause recovery in route middleware provided to route layers. + +- `HttpRouter.catchTag` -> `HttpRouter.middleware + Effect.catchTag`: Apply tagged-error recovery in route middleware provided to route layers. + +- `HttpRouter.concat` -> `Layer.merge`: Routers are now route-registration layers; merge the two layers. + +- `HttpRouter.concatAll` -> `Layer.mergeAll`: Routers are now route-registration layers; merge all layers. + +- `HttpRouter.currentRouterConfig` -> `HttpRouter.RouterConfig`: The FiberRef became a Context.Reference containing Partial\. + +- `HttpRouter.empty` -> `Layer.empty`: There is no immutable empty router; use an empty registration layer. + +- `HttpRouter.fromIterable` -> `HttpRouter.addAll(Array.from(routes))`: Materialize and register the route descriptors as a layer. + +- `HttpRouter.get` -> `HttpRouter.add("GET", path, handler, options)`: Register a route layer; handlers must produce HttpServerResponse. + +- `HttpRouter.head` -> `HttpRouter.addAll([HttpRouter.route("HEAD", path, handler, options)])`: Use route plus addAll because add does not expose HEAD. + +- `HttpRouter.makeRoute` -> `HttpRouter.route`: Renamed to route; v4 route options no longer expose the old prefix field. + +- `HttpRouter.mount` -> `HttpRouter.addAll(routes, { prefix: path })`: Register child routes with a prefix, or use router.prefixed(path). + +- `HttpRouter.mountApp` -> `HttpRouter.use((router) => router.prefixed(path).add("*", "/*", app))`: Register the app on the prefixed router service; no direct mount API remains. + +- `HttpRouter.options` -> `HttpRouter.add("OPTIONS", path, handler, options)`: Register a route layer; handlers must produce HttpServerResponse. + +- `HttpRouter.patch` -> `HttpRouter.add("PATCH", path, handler, options)`: Register a route layer; handlers must produce HttpServerResponse. + +- `HttpRouter.post` -> `HttpRouter.add("POST", path, handler, options)`: Register a route layer; handlers must produce HttpServerResponse. + +- `HttpRouter.prefixAll` -> `HttpRouter.addAll(routes, { prefix })`: Apply the prefix while registering route descriptors. + +- `HttpRouter.put` -> `HttpRouter.add("PUT", path, handler, options)`: Register a route layer; handlers must produce HttpServerResponse. + +- `HttpRouter.setRouterConfig` -> `Layer.succeed(HttpRouter.RouterConfig)(config)`: Provide the RouterConfig Context.Reference as a layer. + +- `HttpRouter.toHttpApp` -> `HttpRouter.toHttpEffect`: Pass the route-registration layer to build the server handler effect. + +- `HttpRouter.transform` -> `HttpRouter.middleware`: Express the route-wide response Effect transform as router middleware. + +- `HttpRouter.withRouterConfig` -> `Effect.provideService(effect, HttpRouter.RouterConfig, config)`: Provide the RouterConfig Context.Reference locally instead of setting a FiberRef. + +### `@effect/platform/HttpServer` + +- `HttpServer.Address` -> `effect/unstable/net/NetAddress#SocketAddress`: Replaced by the shared concrete internet-or-Unix socket address union. + +- `HttpServer.HttpServer` -> `HttpServer.HttpServer`: The interface and tag became one Context.Service class; use its Service member for implementations. + +- `HttpServer.ServeOptions` -> `none`: The unused respond option model was removed with no shared v4 counterpart. + +- `HttpServer.TcpAddress` -> `effect/unstable/net/NetAddress#InetAddress`: Replaced by the shared resolved internet-address model; use address and port instead of hostname and port. + +- `HttpServer.TypeId` -> `none`: The public TypeId was removed; HttpServer is now a Context.Service class. + +- `HttpServer.UnixAddress` -> `effect/unstable/net/NetAddress#UnixPathAddress`: Replaced by the shared Unix filesystem-path address model. + +- `HttpServer.addressWith` -> `HttpServer.HttpServer.use(({ address }) => effect(address))`: The accessor was removed; read the service and pass its Address to the callback. + +- `HttpServer.layerContext` -> `HttpServer.layerServices`: Renamed; it provides the standard HTTP platform services. + +- `HttpServer.make` -> `HttpServer.make`: Retained; it returns the Context.Service implementation. + +- `HttpServer.serve` -> `effect/unstable/http/HttpServer#serve`: Moved to the v4 HTTP module; the application is now an Effect producing HttpServerResponse rather than the separate HttpApp model. + +### `@effect/platform/HttpServerError` + +- `HttpServerError.HttpServerError` -> `HttpServerError.HttpServerError | HttpServerError.ServeError`: Handler failures became a tagged wrapper, while ServeError remains separate. + +- `HttpServerError.RequestError` -> `HttpServerError.RequestParseError (constructor) / HttpServerError.RequestError (type)`: The constructible class became RequestParseError; RequestError is now a broader type union. + +- `HttpServerError.TypeId` -> `HttpServerError.isHttpServerError`: The brand is private in v4; use the public runtime refinement. + +- `HttpServerError.clientAbortFiberId` -> `HttpServerError.ClientAbort.annotation`: Client aborts now use a Cause context annotation rather than a sentinel FiberId. + +- `HttpServerError.isServerError` -> `HttpServerError.isHttpServerError`: Renamed and narrowed to wrapped handler errors; test ServeError separately if needed. + +### `@effect/platform/HttpServerRequest` + +- `HttpServerRequest.ParsedSearchParams` -> `HttpServerRequest.ParsedSearchParams`: The marker and tag became one Context.Service class. + +- `HttpServerRequest.TypeId` -> `typeof HttpServerRequest.TypeId`: TypeId remains public but is now a string constant; use typeof in type position. + +- `HttpServerRequest.fromWeb` -> `HttpServerRequest.fromWeb`: Retained for wrapping a Web Request. + +- `HttpServerRequest.persistedMultipart` -> `HttpServerRequest.HttpServerRequest.use((request) => request.multipart)`: Use the request service's `.use` helper to return its cached multipart effect. + +- `HttpServerRequest.schemaBodyJson` -> `HttpServerRequest.schemaBodyJson`: Retained with v4 Schema constraints and error types. + +- `HttpServerRequest.schemaBodyUrlParams` -> `HttpServerRequest.schemaBodyUrlParams`: Retained with ConstraintCodec input and v4 error types. + +- `HttpServerRequest.schemaHeaders` -> `HttpServerRequest.schemaHeaders`: Retained with ConstraintCodec input and SchemaError failures. + +- `HttpServerRequest.toWeb` -> `HttpServerRequest.toWeb`: Retained and captures the current Context for streamed bodies. + +- `HttpServerRequest.toWebEither` -> `HttpServerRequest.toWebResult`: Either became Result, and the optional Runtime became an optional Context. + +### `@effect/platform/HttpServerRespondable` + +- `HttpServerRespondable.symbol` -> `HttpServerRespondable.symbol`: Retained as a string protocol key rather than a unique symbol. + +### `@effect/platform/HttpServerResponse` + +- `HttpServerResponse.TypeId` -> `HttpServerResponse.isHttpServerResponse`: The response brand is private in v4; use the public runtime refinement. + +- `HttpServerResponse.expireCookie` -> `HttpServerResponse.expireCookie`: Now effectful and safe; use expireCookieUnsafe for synchronous throwing behavior. + +- `HttpServerResponse.file` -> `HttpServerResponse.file`: Retained; offset and bytesToRead accept ByteSize.Input, while chunkSize uses number. Path-backed responses validate ranges and clamp content length to the available bytes. + +- `HttpServerResponse.fileWeb` -> `HttpServerResponse.fileWeb`: Web File offset, bytesToRead, and chunkSize options use number, unlike path-backed ByteSize.Input ranges. + +- `HttpServerResponse.isServerResponse` -> `HttpServerResponse.isHttpServerResponse`: Renamed. + +- `HttpServerResponse.setCookie` -> `HttpServerResponse.setCookie`: Retained as the safe effectful cookie setter. + +- `HttpServerResponse.stream` -> `HttpServerResponse.stream`: Retained; v4 Stream no longer has a service type parameter. + +- `HttpServerResponse.text` -> `HttpServerResponse.text`: Moved unchanged. + +- `HttpServerResponse.toWeb` -> `HttpServerResponse.toWeb`: Retained, but the optional Runtime became an optional Context for stream execution. + +- `HttpServerResponse.uint8Array` -> `HttpServerResponse.uint8Array`: Moved unchanged. + +- `HttpServerResponse.unsafeJson` -> `HttpServerResponse.jsonUnsafe`: Renamed to put Unsafe last; serialization failures still throw. + +- `HttpServerResponse.unsafeSetCookie` -> `HttpServerResponse.setCookieUnsafe`: Renamed to put Unsafe last; invalid cookies still throw. + +- `HttpServerResponse.unsafeSetCookies` -> `HttpServerResponse.setCookiesUnsafe`: Renamed to put Unsafe last; invalid cookies still throw. + +### `@effect/platform/KeyValueStore` + +- `KeyValueStore.KeyValueStore` -> `KeyValueStore.KeyValueStore`: The service moved to effect/unstable/persistence/KeyValueStore; missing values now use undefined and operations fail with KeyValueStoreError. + +- `KeyValueStore.KeyValueStore.AnyStore` -> `KeyValueStore.KeyValueStore | KeyValueStore.SchemaStore`: The convenience namespace alias was removed; write the store union explicitly when needed. + +- `KeyValueStore.SchemaStoreTypeId` -> `none`: The v4 SchemaStore has no public type-id alias; use the SchemaStore interface. + +- `KeyValueStore.TypeId` -> `none`: The KeyValueStore runtime marker is internal in v4; use the service and interface. + +- `KeyValueStore.layerSchema` -> `KeyValueStore.toSchemaStore`: Schema stores are now derived with toSchemaStore; define the desired Context.Service and layer explicitly. + +- `KeyValueStore.make` -> `KeyValueStore.make`: The constructor remains in the moved module with v4 MakeOptions. + +- `KeyValueStore.prefix` -> `KeyValueStore.prefix`: The prefixed-store combinator remains in the moved module. + +### `@effect/platform/MsgPack` + +- `MsgPack.ErrorTypeId` -> `Schema.SchemaError`: MsgPackError was removed with MessagePack. SchemaBinary channels fail with Schema.SchemaError. + +- `MsgPack.MsgPackError` -> `Schema.SchemaError`: MsgPackError was removed with MessagePack. SchemaBinary channels fail with Schema.SchemaError. + +- `MsgPack.duplex` -> `SchemaBinary.duplex`: MessagePack was removed. Wrap a byte channel with SchemaBinary.duplex and explicit input and output schemas. + +- `MsgPack.duplexSchema` -> `SchemaBinary.duplex`: MessagePack was removed. Use SchemaBinary.duplex with the v4 Schema model. + +- `MsgPack.pack` -> `SchemaBinary.encode`: MessagePack was removed. Encode schema values to binary frames with SchemaBinary.encode. + +- `MsgPack.packSchema` -> `SchemaBinary.encode`: MessagePack was removed. Use SchemaBinary.encode with the v4 Schema model. + +- `MsgPack.schema` -> `SchemaBinary.toCodec`: MessagePack was removed. Derive a binary codec from a Schema with SchemaBinary.toCodec. + +- `MsgPack.unpack` -> `SchemaBinary.decode`: MessagePack was removed. Decode binary frames with SchemaBinary.decode. + +- `MsgPack.unpackSchema` -> `SchemaBinary.decode`: MessagePack was removed. Use SchemaBinary.decode with the v4 Schema model. + +### `@effect/platform/Multipart` + +- `Multipart.ErrorTypeId` -> `Multipart.MultipartError`: The public error type-id alias was removed; use the MultipartError class. + +- `Multipart.FieldMimeTypes` -> `Multipart.FieldMimeTypes`: The setting remains but is now a Context.Reference rather than a service class. + +- `Multipart.FileSchema` -> `Multipart.PersistedFileSchema`: The schema for persisted multipart files was renamed. + +- `Multipart.MaxFieldSize` -> `Multipart.MaxFieldSize`: Now a Context.Reference\; provide a value such as ByteSize.bytes(100). + +- `Multipart.MaxFileSize` -> `Multipart.MaxFileSize`: Now a Context.Reference\; provide ByteSize.bytes(100), for example, or undefined for no limit. + +- `Multipart.MaxParts` -> `Multipart.MaxParts`: The setting remains as a Context.Reference; use undefined rather than Option.none for no limit. + +- `Multipart.TypeId` -> `typeof Multipart.TypeId`: The runtime marker remains exported, but the separate type alias was removed. + +- `Multipart.makeChannel` -> `effect/unstable/http/Multipart#makeChannel`: The channel constructor moved and no longer accepts bufferSize; input and output chunks use non-empty readonly arrays. + +- `Multipart.schemaJson` -> `Multipart.schemaJson`: The JSON-field decoder remains in effect/unstable/http/Multipart and uses v4 Schema constraints. + +- `Multipart.withFieldMimeTypes` -> `Effect.provideService(Multipart.FieldMimeTypes, mimeTypes)`: Provide the v4 Context.Reference around the effect. + +- `Multipart.withLimits` -> `Effect.provideContext(effect, Multipart.limitsServices(options))`: Build the multipart limit context and provide it to the effect; Option-valued limits became optional plain values. + +- `Multipart.withLimits.Options` -> `Multipart.withLimits.Options`: Limit fields now use optional plain numbers or ByteSize inputs; convert Option.none to undefined and Option.some(value) to value. + +- `Multipart.withLimitsStream` -> `Stream.provideContext(stream, Multipart.limitsServices(options))`: Build the multipart limit context and provide it to the stream; Option-valued limits became optional plain values. + +- `Multipart.withMaxFieldSize` -> `Effect.provideService(Multipart.MaxFieldSize, size)`: Provide a ByteSize value, such as ByteSize.bytes(100). To normalize ByteSize.Input options, use Multipart.limitsServices. + +- `Multipart.withMaxFileSize` -> `Effect.provideService(Multipart.MaxFileSize, size)`: Replace Option.none with undefined and Option.some(value) with a normalized ByteSize value. To normalize ByteSize.Input options, use Multipart.limitsServices. + +- `Multipart.withMaxParts` -> `Effect.provideService(Multipart.MaxParts, count)`: Provide the v4 Context.Reference around the effect, converting Option.none to undefined. + +### `@effect/platform/Ndjson` + +- `Ndjson.ErrorTypeId` -> `Ndjson.NdjsonError`: The public error marker was removed; use the NdjsonError class. + +- `Ndjson.NdjsonErrorTypeId` -> `Ndjson.NdjsonError`: The public error type-id alias was removed; use the NdjsonError class. + +- `Ndjson.NdjsonOptions` -> `{ readonly ignoreEmptyLines?: boolean }`: The standalone options interface was removed; decoding and duplex APIs accept this inline shape. + +- `Ndjson.duplex` -> `Ndjson.duplex`: The API moved to effect/unstable/encoding/Ndjson. + +- `Ndjson.duplexSchema` -> `Ndjson.duplexSchema`: The API moved to effect/unstable/encoding/Ndjson and uses v4 Schema constraints. + +- `Ndjson.pack` -> `Ndjson.encode`: The NDJSON channel constructor was renamed from pack to encode. + +- `Ndjson.packSchema` -> `Ndjson.encodeSchema`: The schema-aware pack channel was renamed to encodeSchema. + +- `Ndjson.packSchemaString` -> `Ndjson.encodeSchemaString`: The string schema pack channel was renamed to encodeSchemaString. + +- `Ndjson.packString` -> `Ndjson.encodeString`: The string pack channel was renamed to encodeString. + +- `Ndjson.unpack` -> `Ndjson.decode`: The NDJSON channel constructor was renamed from unpack to decode. + +- `Ndjson.unpackSchema` -> `Ndjson.decodeSchema`: The schema-aware unpack channel was renamed to decodeSchema. + +- `Ndjson.unpackSchemaString` -> `Ndjson.decodeSchemaString`: The string schema unpack channel was renamed to decodeSchemaString. + +- `Ndjson.unpackString` -> `Ndjson.decodeString`: The string unpack channel was renamed to decodeString. + +### `@effect/platform/OpenApi` + +- `OpenApi.AdditionalPropertiesStrategy` -> `none`: OpenApi.fromApi no longer accepts generation options; standalone JSON Schema generation has a separate additionalProperties option. + +- `OpenApi.Exclude` -> `effect/unstable/httpapi/OpenApi#Exclude`: Same annotation key and default; it is now a Context.Reference value. + +- `OpenApi.OpenApiSpecContentType` -> `string`: The closed media-type union was removed so custom and streaming media types are supported. + +- `OpenApi.fromApi` -> `effect/unstable/httpapi/OpenApi#fromApi`: The operation remains and returns OpenAPI 3.1, but the signature is now only fromApi(api). + +### `@effect/platform/OpenApiJsonSchema` + +- `OpenApiJsonSchema.Any` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces were consolidated into the open, dialect-neutral JSON Schema object model. + +- `OpenApiJsonSchema.AnyObject` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces were consolidated; construct the required object directly. + +- `OpenApiJsonSchema.AnyOf` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces were consolidated into the general object model. + +- `OpenApiJsonSchema.Array` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces were consolidated into the general object model. + +- `OpenApiJsonSchema.Boolean` -> `effect/JsonSchema#JsonSchema`: The narrow boolean interface was consolidated into the open, dialect-neutral JSON Schema object model. + +- `OpenApiJsonSchema.Empty` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces and special id shapes were removed. + +- `OpenApiJsonSchema.Enum` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces were consolidated into the general object model. + +- `OpenApiJsonSchema.Enums` -> `effect/JsonSchema#JsonSchema`: The Effect-specific comment enum shape has no named v4 interface; use the general object model. + +- `OpenApiJsonSchema.Integer` -> `effect/JsonSchema#JsonSchema`: The narrow numeric interfaces were consolidated into the general object model. + +- `OpenApiJsonSchema.JsonSchema` -> `effect/JsonSchema#JsonSchema`: Use the dialect-neutral open JSON Schema object model. + +- `OpenApiJsonSchema.Never` -> `effect/JsonSchema#JsonSchema`: The special never-schema interface was consolidated into the open JSON Schema object model; represent it with a not constraint. + +- `OpenApiJsonSchema.Number` -> `effect/JsonSchema#JsonSchema`: The narrow number interface was consolidated into the open, dialect-neutral JSON Schema object model. + +- `OpenApiJsonSchema.Numeric` -> `effect/JsonSchema#JsonSchema`: The narrow numeric interfaces were consolidated into the general object model. + +- `OpenApiJsonSchema.Object` -> `effect/JsonSchema#JsonSchema`: The narrow node interfaces were consolidated into the general object model. + +- `OpenApiJsonSchema.Ref` -> `effect/JsonSchema#JsonSchema`: The narrow ref interface was consolidated; OpenAPI conversion rewrites definition references. + +- `OpenApiJsonSchema.Root` -> `effect/JsonSchema#MultiDocument`: OpenAPI generation keeps roots in schemas and shared components in definitions; the inline-definitions root model is gone. + +- `OpenApiJsonSchema.String` -> `effect/JsonSchema#JsonSchema`: The narrow string interface was consolidated into the open, dialect-neutral JSON Schema object model. + +- `OpenApiJsonSchema.Unknown`: TODO: needs guidance + +- `OpenApiJsonSchema.Void` -> `effect/JsonSchema#JsonSchema`: The special void-schema interface was consolidated into the open JSON Schema object model. + +- `OpenApiJsonSchema.make` -> `effect/Schema#toJsonSchemaDocument + effect/JsonSchema#toMultiDocumentOpenApi3_1`: Generate Draft 2020-12, wrap the root in a multi-document, then convert references and definitions to OpenAPI 3.1. + +- `OpenApiJsonSchema.makeWithDefs` -> `effect/SchemaRepresentation#toJsonSchemaMultiDocument + effect/JsonSchema#toMultiDocumentOpenApi3_1`: Definitions are returned separately; build a multi-document representation and convert it to OpenAPI 3.1. + +### `@effect/platform/Path` + +- `Path.TypeId` -> `typeof Path.TypeId`: The module moved to effect/Path; the runtime marker remains exported but the separate type alias was removed. + +### `@effect/platform/PlatformConfigProvider` + +- `PlatformConfigProvider.fromFileTree` -> `ConfigProvider.fromDir`: The provider moved into effect/ConfigProvider and was renamed; rootDirectory is now rootPath. + +- `PlatformConfigProvider.layerDotEnv` -> `ConfigProvider.layer(ConfigProvider.fromDotEnv({ path }))`: Use the v4 dotenv provider effect and install it with ConfigProvider.layer. + +- `PlatformConfigProvider.layerDotEnvAdd` -> `ConfigProvider.layerAdd(ConfigProvider.fromDotEnv({ path }))`: Use the v4 dotenv provider effect and compose it with ConfigProvider.layerAdd. + +- `PlatformConfigProvider.layerFileTree` -> `ConfigProvider.layer(ConfigProvider.fromDir({ rootPath }))`: Use the renamed directory-tree provider and install it with ConfigProvider.layer. + +- `PlatformConfigProvider.layerFileTreeAdd` -> `ConfigProvider.layerAdd(ConfigProvider.fromDir({ rootPath }))`: Use the renamed directory-tree provider and compose it with ConfigProvider.layerAdd. + +### `@effect/platform/Runtime` + +- `Runtime.RunMain` -> `ReturnType`: The standalone interface was removed; derive the runner type from effect/Runtime.makeRunMain. disablePrettyLogger is no longer an option. + +### `@effect/platform/Socket` + +- `Socket.CloseEventTypeId` -> `Socket.CloseEvent`: The close-event marker is internal in v4; use the CloseEvent class or Socket.isCloseEvent. + +- `Socket.SocketError` -> `Socket.SocketError`: The old union became a tagged wrapper around SocketReadError, SocketWriteError, SocketOpenError, or SocketCloseError. + +- `Socket.SocketErrorTypeId` -> `Socket.SocketErrorTypeId`: The error marker remains in effect/unstable/socket/Socket. + +- `Socket.SocketGenericError` -> `Socket.SocketReadError | Socket.SocketWriteError | Socket.SocketOpenError`: The generic reason discriminator was replaced by dedicated read, write, and open error classes. + +- `Socket.TypeId` -> `typeof Socket.TypeId`: The socket marker remains exported, but the separate type alias was removed. + +- `Socket.WebSocket` -> `Socket.WebSocket`: The opaque service moved to effect/unstable/socket/Socket and is now a Context.Service class for globalThis.WebSocket. + +- `Socket.WebSocketConstructor` -> `Socket.WebSocketConstructor`: The service moved to effect/unstable/socket/Socket and is now a Context.Service class. + +- `Socket.currentSendQueueCapacity` -> `none`: The send queue was removed. The v4 Socket is pull-based: acquire socket.reader in a scope and pull frame batches; writes apply the transport's native backpressure. + +- `Socket.defaultCloseCodeIsError` -> `none`: Sockets no longer classify close codes; every close fails the reader's pull with a SocketError wrapping SocketCloseError. Consumers that treat a close as normal catch the error. + +- `Socket.fromTransformStream` -> `Socket.fromTransformStream`: The constructor remains in effect/unstable/socket/Socket but drops closeCodeIsError; every close fails the reader's pull with a SocketError wrapping SocketCloseError. + +- `Socket.layerWebSocket` -> `Socket.layerWebSocket`: The constructor remains in effect/unstable/socket/Socket; its URL may now also be an Effect. + +- `Socket.toChannelMap` -> `none`: The v4 Socket read side is an Effect that never completes via Cause.Done; map frames by acquiring Socket.readerBytes or Socket.readerString, or Effect.map the reader from socket.reader, and use Socket.toChannel or Socket.toChannelString for duplex channels. + +### `@effect/platform/SocketServer` + +- `SocketServer.Address` -> `effect/unstable/net/NetAddress#SocketAddress`: Replaced by the shared concrete internet-or-Unix socket address union. + +- `SocketServer.ErrorTypeId` -> `SocketServer.ErrorTypeId`: The API moved to effect/unstable/socket/SocketServer and retains this name. + +- `SocketServer.TcpAddress` -> `effect/unstable/net/NetAddress#InetAddress`: Replaced by the shared resolved internet-address model; use address and port instead of hostname and port. + +- `SocketServer.UnixAddress` -> `effect/unstable/net/NetAddress#UnixPathAddress`: Replaced by the shared Unix filesystem-path address model. + +### `@effect/platform/Template` + +- `Template.Interpolated.Context` -> `Template.Interpolated.Context`: The API moved to effect/unstable/http/Template; v4 interpolation types also account for Effect values. + +- `Template.Interpolated.Error` -> `Template.Interpolated.Error`: The API moved to effect/unstable/http/Template; v4 interpolation types also account for Effect values. + +### `@effect/platform/Terminal` + +- `Terminal.QuitException` -> `Terminal.QuitError`: The quit sentinel was renamed and moved to effect/Terminal. + +- `Terminal.isQuitException` -> `Terminal.isQuitError`: The quit sentinel was renamed from QuitException to QuitError. + +### `@effect/platform/Transferable` + +- `Transferable.CollectorService` -> `Transferable.Collector["Service"]`: The collector interface is now the service type of the Transferable.Collector Context.Service class. + +- `Transferable.Uint8Array` -> `Transferable.Uint8Array`: The transferable Uint8Array schema remains in the moved module. + +- `Transferable.schema` -> `Transferable.schema`: The schema wrapper moved to effect/unstable/workers/Transferable and uses the v4 Schema model. + +- `Transferable.unsafeMakeCollector` -> `Transferable.makeCollectorUnsafe`: The unsafe collector constructor was renamed. + +### `@effect/platform/Url` + +- `Url.fromString` -> `Url.fromString`: Retained; returns Result with IllegalArgumentError instead of Either with IllegalArgumentException. + +- `Url.setUrlParams` -> `Url.setUrlParams`: Retained and widened to accept UrlParams.Input. + +### `@effect/platform/UrlParams` + +- `UrlParams.CoercibleRecord` -> `UrlParams.CoercibleRecord`: The recursive interface became a generic mapped type preserving the input shape. + +- `UrlParams.Input` -> `UrlParams.Input`: Retained and broadened to include UrlParams itself. + +- `UrlParams.UrlParams` -> `UrlParams.UrlParams`: Import UrlParams from effect/unstable/http. It is now a branded iterable object with a params field rather than a ReadonlyArray; construct it with UrlParams.make or UrlParams.fromInput. + +- `UrlParams.makeUrl` -> `Url.make`: Moved to Url, returns Result, and takes string | undefined for the hash. + +- `UrlParams.schemaFromSelf` -> `Schema.UrlParams`: The declaration schema for the v4 wrapper moved to effect/Schema. + +- `UrlParams.schemaFromString` -> `Schema.String.pipe(Schema.decodeTo(Schema.UrlParams, { decode: SchemaGetter.transform((s) => UrlParams.fromInput(new URLSearchParams(s))), encode: SchemaGetter.transform(UrlParams.toString) }))`: No prebuilt string codec remains; recreate it by transforming between a query string and UrlParams. + +- `UrlParams.schemaJson` -> `Schema.JsonFromUrlParamsField(field).pipe(Schema.decodeTo(schema), Schema.decodeEffect)`: The field codec moved to effect/Schema. Compose it with the target schema, then decode it. + +- `UrlParams.schemaParse` -> `UrlParamsFromString.pipe(Schema.decodeTo(Schema.RecordFromUrlParams.pipe(Schema.decodeTo(schema))))`: Recreate the removed helper by composing the string, record, and target codecs. + +- `UrlParams.schemaRecord` -> `Schema.RecordFromUrlParams.pipe(Schema.decodeTo(schema))`: RecordFromUrlParams is a base codec in effect/Schema; compose it with the target schema. + +- `UrlParams.schemaStruct` -> `Schema.RecordFromUrlParams.pipe(Schema.decodeTo(schema), Schema.decodeEffect)`: Compose the record codec from effect/Schema with the target schema and decode it. + +- `UrlParams.toString` -> `UrlParams.toString`: Retained and broadened to accept any UrlParams.Input. + +### `@effect/platform/Worker` + +- `Worker.BackingWorker` -> `Worker.Worker`: The low-level backing worker became the primary Worker interface with send and run operations. + +- `Worker.PlatformWorker` -> `Worker.WorkerPlatform`: The platform service was renamed and is now a Context.Service class. + +- `Worker.PlatformWorkerTypeId` -> `none`: The Context.Service class replaces the public platform-worker type-id alias. + +- `Worker.SerializedWorker` -> `RpcClient with RpcClient.layerProtocolWorker`: The serialized worker facade was removed; v4 routes schema-defined RPCs through the worker protocol. + +- `Worker.SerializedWorker.Options` -> `RpcWorker.layerInitialMessage`: Use RpcWorker.layerInitialMessage when a worker RPC protocol needs schema-encoded initialization. + +- `Worker.SerializedWorkerPool` -> `RpcClient.makeProtocolWorker`: The worker-backed RPC protocol owns its worker pool in v4. + +- `Worker.SerializedWorkerPool.Options` -> `Parameters[0]`: Pool sizing options moved to the worker RPC protocol; initial messages are provided separately with RpcWorker.layerInitialMessage. + +- `Worker.Worker` -> `Worker.Worker`: The name remains in effect/unstable/workers/Worker, but it is now the low-level send/run abstraction rather than execute/executeEffect. + +- `Worker.Worker.Options` -> `Worker.Worker["run"] options`: Encoding moved to RPC schemas; the low-level run operation only accepts an optional onSpawn effect. + +- `Worker.Worker.Response` -> `none`: The old tagged-request wire response is gone; worker RPC wire messages are internal to RpcClient and RpcServer. + +- `Worker.Worker.Span` -> `none`: The explicit span tuple was removed; the RPC worker protocol handles span propagation internally. + +- `Worker.WorkerManager` -> `Worker.WorkerPlatform`: WorkerPlatform now spawns low-level Worker values directly, replacing WorkerManager. + +- `Worker.WorkerManagerTypeId` -> `none`: The removed WorkerManager has no v4 type-id; WorkerPlatform is a Context.Service class. + +- `Worker.WorkerPool` -> `RpcClient.Protocol`: For serialized request/response workloads use the worker-backed RPC Protocol; for raw messages build a Pool around WorkerPlatform.spawn. + +- `Worker.WorkerPool.Options` -> `Parameters[0]`: Worker RPC pool sizing is configured on makeProtocolWorker or layerProtocolWorker. + +- `Worker.layerManager` -> `Worker.WorkerPlatform`: WorkerManager was removed; provide the adapter's WorkerPlatform layer directly. + +- `Worker.makeManager` -> `Worker.WorkerPlatform`: WorkerManager was removed; obtain WorkerPlatform and call its spawn method. + +- `Worker.makePool` -> `Pool + Worker.WorkerPlatform.spawn`: Generic worker pools are no longer built by this module; build a Pool around WorkerPlatform.spawn, or use RpcClient.makeProtocolWorker for RPC workers. + +- `Worker.makePoolLayer` -> `RpcClient.layerProtocolWorker`: The standard v4 worker-pool layer is the worker-backed RPC client protocol; compose it with the RPC client layer. + +- `Worker.makePoolSerialized` -> `RpcClient.makeProtocolWorker`: Serialized tagged-request workers were replaced by the worker-backed RPC protocol. + +- `Worker.makePoolSerializedLayer` -> `RpcClient.layerProtocolWorker`: Serialized tagged-request worker pools were replaced by the worker-backed RPC protocol layer. + +- `Worker.makeSerialized` -> `RpcClient with RpcClient.layerProtocolWorker`: Serialized tagged-request execution moved to the v4 RPC model; define an RpcGroup and use the worker protocol. + +### `@effect/platform/WorkerError` + +- `WorkerError.WorkerErrorFrom` -> `WorkerError.WorkerError`: The old serializable reason object was replaced by WorkerError wrapping dedicated spawn, send, receive, or unknown reason classes. + +- `WorkerError.WorkerErrorTypeId` -> `WorkerError.TypeId`: The type-level worker error marker was shortened to TypeId in the moved module. + +### `@effect/platform/WorkerRunner` + +- `WorkerRunner.BackingRunner` -> `WorkerRunner.WorkerRunner`: The low-level backing runner became the primary WorkerRunner interface. + +- `WorkerRunner.BackingRunner.Message` -> `WorkerRunner.PlatformMessage`: The request/close wire tuple moved to the top-level PlatformMessage type. + +- `WorkerRunner.CloseLatch` -> `none`: The public close-latch service was removed; WorkerRunner implementations manage lifetime through their run effect and adapter scope. + +- `WorkerRunner.PlatformRunner` -> `WorkerRunner.WorkerRunnerPlatform`: The platform service was renamed and is now a Context.Service class. + +- `WorkerRunner.PlatformRunnerTypeId` -> `none`: The Context.Service class replaces the public platform-runner type-id alias. + +- `WorkerRunner.Runner` -> `WorkerRunner.WorkerRunner`: The namespace-only runner API was replaced by the low-level WorkerRunner interface. + +- `WorkerRunner.Runner.Options` -> `none`: The custom decode/encode callbacks were removed; use raw low-level messages or define schemas in the v4 RPC model. + +- `WorkerRunner.SerializedRunner` -> `RpcServer with RpcGroup handlers`: The serialized runner namespace was removed in favor of typed Rpc definitions and RpcServer. + +- `WorkerRunner.SerializedRunner.Handlers` -> `RpcGroup.HandlersFrom`: Define an RpcGroup and derive its server handler object type with HandlersFrom. + +- `WorkerRunner.SerializedRunner.HandlersContext` -> `RpcGroup.HandlersServices`: Derive services required by an RpcGroup handler object with HandlersServices. + +- `WorkerRunner.SerializedRunner.InitialContext` -> `none`: Initial-message layer outputs are no longer inferred by this helper; model initialization as normal RpcGroup handler layers and services. + +- `WorkerRunner.SerializedRunner.InitialEnv` -> `none`: Initial-message layer inputs are no longer inferred by this helper; model initialization as normal RpcGroup handler layers and services. + +- `WorkerRunner.launch` -> `RpcServer.layerProtocolWorkerRunner`: For schema-defined workers, provide the worker-runner RPC protocol and launch the normal RpcServer layer. + +- `WorkerRunner.layer` -> `WorkerRunner.WorkerRunnerPlatform.start + WorkerRunner.WorkerRunner.run`: The generic processing layer was removed; use the low-level runner directly or the RpcServer worker protocol. + +- `WorkerRunner.layerCloseLatch` -> `none`: The public close-latch layer was removed; adapter runner lifetime is managed internally. + +- `WorkerRunner.layerSerialized` -> `RpcServer.layerProtocolWorkerRunner`: Serialized tagged-request handlers moved to RpcGroup handlers served through the worker-runner RPC protocol. + +- `WorkerRunner.make` -> `WorkerRunner.WorkerRunnerPlatform.start + WorkerRunner.WorkerRunner.run`: Start the platform runner and register the low-level message handler directly. + +- `WorkerRunner.makeSerialized` -> `RpcServer.makeProtocolWorkerRunner`: Serialized tagged-request execution moved to RpcServer with an RpcGroup handler layer. + +### `@effect/rpc/Rpc` + +- `Rpc.AddError` -> `effect/unstable/rpc/Rpc#AddError`: Retained; the added error must now be a Schema.Top and the resulting RPC also preserves its explicit service requirements. + +- `Rpc.AddMiddleware` -> `effect/unstable/rpc/Rpc#AddMiddleware`: Retained; middleware is now an RpcMiddleware.AnyService and its provides/requires metadata updates the RPC service requirements. + +- `Rpc.Any` -> `effect/unstable/rpc/Rpc#Any`: Retained as the erased RPC shape; use AnyWithProps when schema and middleware fields are required. + +- `Rpc.AnySchema` -> `Schema.Top`: The RPC-specific erased schema alias was removed; use the v4 top schema constraint. + +- `Rpc.AnyTaggedRequestSchema` -> `none`: RpcGroup no longer converts Schema.TaggedRequest classes into RPCs; declare the contract explicitly with Rpc.make. + +- `Rpc.Context` -> `effect/unstable/rpc/Rpc#Services`: Schema Context became decoding and encoding services; use Services, or ServicesClient / ServicesServer at the corresponding boundary. + +- `Rpc.ErrorEncoded` -> `Rpc.ErrorSchema["Encoded"]`: The alias was removed; index the v4 error schema's Encoded member directly. + +- `Rpc.ErrorExitEncoded` -> `Rpc.ErrorExitSchema["Encoded"]`: Use the new exit error schema, which includes stream and middleware errors, then select its Encoded member. + +- `Rpc.ErrorSchema` -> `effect/unstable/rpc/Rpc#ErrorSchema`: Retained; middleware errors now come from each service's error metadata. + +- `Rpc.Handler` -> `effect/unstable/rpc/Rpc#Handler`: Retained; handler metadata now supplies ServerClient, RequestId, headers, and the concrete RPC. + +- `Rpc.Middleware` -> `effect/unstable/rpc/Rpc#Middleware`: Retained and extracts Context.Service identifiers from the attached middleware services. + +- `Rpc.MiddlewareClient` -> `effect/unstable/rpc/Rpc#MiddlewareClient`: Retained; required client middleware is derived from services configured with requiredForClient. + +- `Rpc.Payload` -> `effect/unstable/rpc/Rpc#Payload`: Retained as the decoded payload type; use PayloadConstructor for the input accepted by generated clients. + +- `Rpc.Success` -> `effect/unstable/rpc/Rpc#Success`: Retained as the decoded success type. + +- `Rpc.SuccessChunkEncoded` -> `Rpc.SuccessExitSchema["Encoded"]`: The alias was removed; for a streaming RPC the exit success schema is the stream element schema. + +- `Rpc.SuccessEncoded` -> `effect/unstable/rpc/Rpc#SuccessEncoded`: Retained after the module move and now accounts for the RPC's explicit service-requirement parameter. + +- `Rpc.SuccessExitEncoded` -> `Rpc.SuccessExitSchema["Encoded"]`: Use the new exit success schema and select its Encoded member; streaming RPC exits use the element schema separately from the terminal void exit. + +- `Rpc.SuccessSchema` -> `effect/unstable/rpc/Rpc#SuccessSchema`: Retained and uses the v4 Schema.Top constraint. + +- `Rpc.Tag` -> `effect/unstable/rpc/Rpc#Tag`: Retained and also accounts for the v4 RPC service-requirement parameter. + +- `Rpc.TypeId` -> `none`: The RPC marker is private in v4; use Rpc.isRpc for runtime checks and Rpc.Any for type constraints. + +- `Rpc.WrapperTypeId` -> `none`: The wrapper marker is private in v4; use Rpc.isWrapper and the public Wrapper type. + +- `Rpc.fromTaggedRequest` -> `Rpc.make`: Automatic TaggedRequest conversion was removed; pass the tag, payload, success, and error schemas explicitly to Rpc.make. + +- `Rpc.make` -> `effect/unstable/rpc/Rpc#make`: Retained; schemas use v4 Schema.Top constraints and the defect option accepts Rpc.DefectSchema. + +- `Rpc.wrap` -> `effect/unstable/rpc/Rpc#wrap`: Retained after the module move; it still applies fork and uninterruptible handler options, while the return type is now uniformly Rpc.Wrapper. + +### `@effect/rpc/RpcClient` + +- `RpcClient.Protocol` -> `effect/unstable/rpc/RpcClient#Protocol`: Retained as a Context.Service; custom transports now route multiple client ids through run and send. + +- `RpcClient.RpcClient.From` -> `effect/unstable/rpc/RpcClient#RpcClient.From`: Generated clients now preserve full RPC tags as property names, remove the Prefix type parameter, and expose streaming results through the asQueue option instead of asMailbox. + +- `RpcClient.RpcClient.NonPrefixed` -> `none`: The prefix-partition helper was removed; v4 clients map every RPC tag directly to an object property. + +- `RpcClient.RpcClient.Prefixes` -> `none`: Nested prefix client objects were removed; v4 preserves the full RPC tag as the generated client property. + +- `RpcClient.currentHeaders` -> `effect/unstable/rpc/RpcClient#CurrentHeaders`: Renamed and changed from FiberRef to Context.Reference; prefer RpcClient.withHeaders for scoped overrides. + +- `RpcClient.makeProtocolHttp` -> `effect/unstable/rpc/RpcClient#makeProtocolHttp`: Retained; it creates the Protocol service implementation from an HttpClient. + +- `RpcClient.withHeadersEffect` -> `Effect.flatMap(headers, (value) => RpcClient.withHeaders(effect, value))`: withHeaders now accepts Headers.Input synchronously; evaluate effectful headers first and then scope the client effect. + +### `@effect/rpc/RpcClientError` + +- `RpcClientError.TypeId` -> `none`: The marker is private in v4; narrow with instanceof RpcClientError or inspect the public \_tag. + +### `@effect/rpc/RpcGroup` + +- `RpcGroup.Any` -> `effect/unstable/rpc/RpcGroup#Any`: Moved unchanged as the erased RpcGroup constraint. + +- `RpcGroup.HandlerContext` -> `effect/unstable/rpc/RpcGroup#HandlerServices`: Renamed for v4 service terminology and now includes explicit RPC requirements after removing middleware-provided services. + +- `RpcGroup.HandlersContext` -> `effect/unstable/rpc/RpcGroup#HandlersServices`: Renamed; it unions HandlerServices across the handler object. + +- `RpcGroup.TypeId` -> `none`: The group marker is private in v4; use RpcGroup.Any for an erased group constraint. + +### `@effect/rpc/RpcMessage` + +- `RpcMessage.FromServerEncoded` -> `effect/unstable/rpc/RpcMessage#FromServerEncoded`: The union is retained and now also includes RequestEncoded for server-originated requests and notifications. Handle \_tag: "Request" when matching exhaustively; isNotification identifies notifications. + +- `RpcMessage.RequestId` -> `effect/unstable/rpc/RpcMessage#RequestId`: Request ids are now branded string or number values; convert bigint ids before calling the retained RequestId constructor. + +- `RpcMessage.RequestIdTypeId` -> `effect/unstable/rpc/RpcMessage#RequestId`: The public symbol marker was removed; use the branded RequestId type and RequestId constructor rather than inspecting its brand. + +### `@effect/rpc/RpcMiddleware` + +- `RpcMiddleware.RpcMiddlewareWrap` -> `effect/unstable/rpc/RpcMiddleware#RpcMiddleware`: The wrap and non-wrap shapes were unified; implement a function receiving the handler effect and request options. + +- `RpcMiddleware.Tag` -> `effect/unstable/rpc/RpcMiddleware#Service`: Renamed and redesigned with explicit requires, provides, clientError, error, and requiredForClient configuration. + +- `RpcMiddleware.TagClass` -> `effect/unstable/rpc/RpcMiddleware#ServiceClass`: Renamed class type for the v4 Context.Service-based middleware declaration. + +- `RpcMiddleware.TagClass.Failure` -> `effect/unstable/rpc/RpcMiddleware#Error`: Failure terminology became error; apply the extractor to the middleware ID. + +- `RpcMiddleware.TagClass.FailureContext` -> `effect/unstable/rpc/RpcMiddleware#ErrorServicesEncode / ErrorServicesDecode`: The single schema context split into server encoding and client decoding services. + +- `RpcMiddleware.TagClass.FailureSchema` -> `effect/unstable/rpc/RpcMiddleware#ErrorSchema`: Renamed and applied to the middleware ID rather than constructor options. + +- `RpcMiddleware.TagClass.FailureService` -> `effect/unstable/rpc/RpcMiddleware#Error`: Use the decoded error extractor; optional middleware fallback was removed. + +- `RpcMiddleware.TagClass.Optional` -> `none`: Optional declaration and fallback-on-failure behavior were removed; model fallback inside the middleware effect. + +- `RpcMiddleware.TagClass.Provides` -> `effect/unstable/rpc/RpcMiddleware#Provides`: Moved to the module level and applied to the middleware ID metadata. + +- `RpcMiddleware.TagClass.RequiredForClient` -> `RpcMiddleware.ServiceClass["requiredForClient"]`: The standalone options extractor was removed; the boolean is exposed directly by the resulting service class. + +- `RpcMiddleware.TagClassAny` -> `effect/unstable/rpc/RpcMiddleware#AnyService`: Renamed widened middleware service-key shape. + +- `RpcMiddleware.TagClassAnyWithProps` -> `effect/unstable/rpc/RpcMiddleware#AnyServiceWithProps`: Renamed erased service key whose value has the unified server middleware function shape. + +- `RpcMiddleware.TypeId` -> `effect/unstable/rpc/RpcMiddleware#TypeId`: Retained as the public middleware metadata marker and now has a string-literal type. + +- `RpcMiddleware.layerClient` -> `effect/unstable/rpc/RpcMiddleware#layerClient`: Retained; the client function can now modify the typed Request passed to next and carry a client-only error type. + +### `@effect/rpc/RpcSchema` + +- `RpcSchema.Stream` -> `effect/unstable/rpc/RpcSchema#Stream`: Retained as both the stream schema interface and constructor; error is the second argument and schema services are split by direction. + +- `RpcSchema.StreamSchemaId` -> `none`: The stream marker is private in v4; use RpcSchema.isStreamSchema and getStreamSchemas. + +- `RpcSchema.getStreamSchemas` -> `effect/unstable/rpc/RpcSchema#getStreamSchemas`: Retained for internal-style schema inspection; pass the schema itself rather than its AST. + +- `RpcSchema.isStreamSchema` -> `effect/unstable/rpc/RpcSchema#isStreamSchema`: Retained; it accepts a v4 Schema.Constraint. + +- `RpcSchema.isStreamSerializable` -> `RpcSchema.isStreamSchema(schema)`: The separate WithResult serializability predicate was removed; v4 RPC streaming is identified by its explicit Stream schema. + +### `@effect/rpc/RpcSerialization` + +- `RpcSerialization.RpcSerializationError` -> `effect/unstable/rpc/RpcSerialization#MaxBufferSizeExceeded`: Buffer-limit failures now use MaxBufferSizeExceeded. MessagePack-specific decode errors have no counterpart. + +- `RpcSerialization.layerMsgPack` -> `effect/unstable/rpc/RpcSerialization#layerSchemaBinary`: MessagePack RPC serialization was removed. Use SchemaBinary, or layerNdjson when you need newline-delimited JSON framing. + +- `RpcSerialization.layerMsgPackWith` -> `effect/unstable/rpc/RpcSerialization#layerSchemaBinary`: MessagePack RPC serialization was removed. Pass maxFrameSize to layerSchemaBinary; NDJSON buffer limits remain on layerNdjsonWith. + +- `RpcSerialization.makeMsgPack` -> `effect/unstable/rpc/RpcSerialization#layerSchemaBinary`: MessagePack RPC serialization was removed. Construct SchemaBinary serialization with layerSchemaBinary. + +- `RpcSerialization.msgPack` -> `effect/unstable/rpc/RpcSerialization#layerSchemaBinary`: The MessagePack RpcSerialization service value was removed. Provide layerSchemaBinary instead. + +### `@effect/rpc/RpcServer` + +- `RpcServer.Protocol` -> `effect/unstable/rpc/RpcServer#Protocol`: Retained as a Context.Service; custom transports now expose a disconnect queue, explicit capability flags, and codecFor for schema-aware payload and exit encoding. + +- `RpcServer.fiberIdClientInterrupt` -> `effect/unstable/rpc/RpcSchema#ClientAbort`: The sentinel FiberId was replaced by a Cause annotation; inspect ClientAbort in the interruption cause when client cancellation must be distinguished. + +- `RpcServer.fiberIdTransientInterrupt` -> `none`: The internal transient sentinel was removed; protocol shutdown and disconnect now interrupt with the active parent fiber identity. + +- `RpcServer.layer` -> `effect/unstable/rpc/RpcServer#layer`: Moved to core Effect; server requirements are now derived with Rpc.ServicesServer rather than the former combined Rpc.Context alias. + +- `RpcServer.layerHttpRouter` -> `effect/unstable/rpc/RpcServer#layerHttp`: Renamed; it installs an HTTP or WebSocket RPC route into the v4 HttpRouter service. + +- `RpcServer.layerProtocolHttp` -> `effect/unstable/rpc/RpcServer#layerProtocolHttp`: Retained; v4 has one HttpRouter service and no router tag option. + +- `RpcServer.layerProtocolHttpRouter` -> `effect/unstable/rpc/RpcServer#layerProtocolHttp`: The separate layer-router variant was unified with layerProtocolHttp. + +- `RpcServer.layerProtocolWebsocketRouter` -> `effect/unstable/rpc/RpcServer#layerProtocolWebsocket`: Renamed after the HTTP router services were unified. + +- `RpcServer.make` -> `effect/unstable/rpc/RpcServer#make`: Retained; schema encoding services are now explicit server requirements. + +- `RpcServer.makeProtocolHttp` -> `effect/unstable/rpc/RpcServer#makeProtocolHttp`: Retained; it registers a POST route in the current v4 HttpRouter. + +- `RpcServer.makeProtocolHttpRouter` -> `effect/unstable/rpc/RpcServer#makeProtocolHttp`: The separate router constructor was unified with makeProtocolHttp. + +- `RpcServer.makeProtocolWebsocketRouter` -> `effect/unstable/rpc/RpcServer#makeProtocolWebsocket`: Renamed after the HTTP router services were unified. + +- `RpcServer.makeProtocolWithHttpApp` -> `effect/unstable/rpc/RpcServer#makeProtocolWithHttpEffect`: HttpApp became HttpEffect; the result contains protocol and httpEffect. + +- `RpcServer.makeProtocolWithHttpAppWebsocket` -> `effect/unstable/rpc/RpcServer#makeProtocolWithHttpEffectWebsocket`: HttpApp became HttpEffect; the result contains the WebSocket protocol and upgrade effect. + +- `RpcServer.toHttpApp` -> `effect/unstable/rpc/RpcServer#toHttpEffect`: Renamed for the v4 HTTP effect model; it starts the RPC server and returns the request effect. + +- `RpcServer.toHttpAppWebsocket` -> `effect/unstable/rpc/RpcServer#toHttpEffectWebsocket`: Renamed for the v4 HTTP effect model; it returns the WebSocket upgrade effect. + +- `RpcServer.toWebHandler` -> `HttpRouter.toWebHandler(RpcServer.layerHttp(options).pipe(Layer.provide(options.layer)))`: The RPC convenience wrapper was removed; build the RPC route layer and convert it with the generic v4 HttpRouter web-handler adapter. + +### `@effect/rpc/RpcTest` + +- `RpcTest.makeClient` -> `effect/unstable/rpc/RpcTest#makeClient`: Retained; it uses the v4 no-serialization client/server path and requires handlers plus any server and client middleware services. + +### `@effect/sql-clickhouse/ClickhouseClient` + +- `ClickhouseClient.ClickhouseClient` -> `@effect/sql-clickhouse/ClickhouseClient#ClickhouseClient`: Retained; the service value is now a Context.Service rather than a GenericTag. + +- `ClickhouseClient.currentClickhouseSettings` -> `@effect/sql-clickhouse/ClickhouseClient#ClickhouseSettings`: Renamed and changed from FiberRef to Context.Reference; prefer client.withClickhouseSettings or provide the reference as a service. + +- `ClickhouseClient.currentClientMethod` -> `@effect/sql-clickhouse/ClickhouseClient#ClientMethod`: Renamed and changed from FiberRef to Context.Reference; prefer client.asCommand or provide the reference as a service. + +- `ClickhouseClient.currentQueryId` -> `@effect/sql-clickhouse/ClickhouseClient#QueryId`: Renamed and changed from FiberRef to Context.Reference; prefer client.withQueryId or provide the reference as a service. + +### `@effect/sql-clickhouse/ClickhouseMigrator` + +- `ClickhouseMigrator.MigrationError` -> `@effect/sql-clickhouse/ClickhouseMigrator#MigrationError`: Retained via effect/unstable/sql/Migrator; migrate reason and its lowercase values to kind with PascalCase values. + +### `@effect/sql-d1/D1Client` + +- `D1Client.D1ClientConfig` -> `@effect/sql-d1/D1Client#D1ClientConfig`: Retained; prepareCacheTTL now uses Duration.Input. + +### `@effect/sql-drizzle/Mysql` + +- `Mysql.MysqlDrizzle` -> `drizzle-orm/effect-mysql2#EffectMysql2Database`: The service tag was removed; use the database type and define an application Context.Tag if service access is required. + +- `Mysql.layer` -> `Layer.effect(AppDb, MysqlDrizzle.makeWithDefaults())`: The package was removed; import MysqlDrizzle from drizzle-orm/effect-mysql2, define an application service tag, and compose with MysqlClient.layer. + +- `Mysql.layerWithConfig` -> `Layer.effect(AppDb, MysqlDrizzle.makeWithDefaults(config))`: The package was removed; use drizzle-orm/effect-mysql2, port config to EffectDrizzleMySqlConfig, and define an application service tag. + +- `Mysql.make` -> `drizzle-orm/effect-mysql2#makeWithDefaults`: Use Drizzle's Effect 4 integration; it returns EffectMysql2Database and requires MysqlClient. + +- `Mysql.makeWithConfig` -> `drizzle-orm/effect-mysql2#makeWithDefaults`: The constructor split was removed; use makeWithDefaults(config), or make(config) when explicitly providing logger and cache services. + +### `@effect/sql-drizzle/Pg` + +- `Pg.PgDrizzle` -> `drizzle-orm/effect-postgres#EffectPgDatabase`: The service tag was removed; use the database type and define an application Context.Tag if service access is required. + +- `Pg.layer` -> `Layer.effect(AppDb, PgDrizzle.makeWithDefaults())`: The package was removed; import PgDrizzle from drizzle-orm/effect-postgres, define an application service tag, and compose with PgClient.layer. + +- `Pg.layerWithConfig` -> `Layer.effect(AppDb, PgDrizzle.makeWithDefaults(config))`: The package was removed; use drizzle-orm/effect-postgres, port config to EffectDrizzlePgConfig, and define an application service tag. + +- `Pg.make` -> `drizzle-orm/effect-postgres#makeWithDefaults`: Use Drizzle's Effect 4 integration; it returns EffectPgDatabase and requires PgClient. + +- `Pg.makeWithConfig` -> `drizzle-orm/effect-postgres#makeWithDefaults`: The constructor split was removed; use makeWithDefaults(config), or make(config) when explicitly providing logger and cache services. + +### `@effect/sql-drizzle/Sqlite` + +- `Sqlite.SqliteDrizzle` -> `matching drizzle-orm Effect SQLite database type`: The generic service tag was removed; use the backend-specific database type and define an application Context.Tag if needed. + +- `Sqlite.layer` -> `Layer.effect(AppDb, SqliteDrizzle.makeWithDefaults())`: The package was removed; select the matching drizzle-orm Effect backend module, define an application service tag, and compose with its SQL client layer. + +- `Sqlite.layerWithConfig` -> `Layer.effect(AppDb, SqliteDrizzle.makeWithDefaults(config))`: Select the matching drizzle-orm Effect backend, port config to EffectDrizzleSQLiteConfig, and define an application service tag. + +- `Sqlite.make` -> `drizzle-orm/effect-sqlite-node#makeWithDefaults`: SQLite integration is backend-specific; use the module matching sql-sqlite-node, -bun, -do, -wasm, libsql, or d1. + +- `Sqlite.makeWithConfig` -> `matching drizzle-orm Effect SQLite module#makeWithDefaults`: The generic constructor was removed; select the concrete backend and use makeWithDefaults(config), or make(config) with explicit services. + +### `@effect/sql-mssql/MssqlClient` + +- `MssqlClient.MssqlClient` -> `@effect/sql-mssql/MssqlClient#MssqlClient`: Retained; the service value is now a Context.Service rather than a GenericTag. + +- `MssqlClient.MssqlClientConfig` -> `@effect/sql-mssql/MssqlClient#MssqlClientConfig`: Retained; durations use Duration.Input, parameterTypes is keyed by Statement.PrimitiveKind, and v4 adds retry and timeout options. + +### `@effect/sql-mssql/Parameter` + +- `Parameter.Parameter` -> `@effect/sql-mssql/Parameter#Parameter`: Retained; the phantom brand key was renamed from ParameterId to TypeId. + +- `Parameter.ParameterId` -> `@effect/sql-mssql/Parameter#TypeId`: Renamed; use TypeId for direct brand-key and type references. + +### `@effect/sql-mssql/Procedure` + +- `Procedure.Procedure` -> `@effect/sql-mssql/Procedure#Procedure`: Retained with the same generics and fields. + +- `Procedure.Procedure.ParametersRecord` -> `@effect/sql-mssql/Procedure#Procedure.ParametersRecord`: Retained unchanged; from the deep module it is also available as Procedure.ParametersRecord. + +### `@effect/sql-mysql2/MysqlClient` + +- `MysqlClient.MysqlClientConfig` -> `@effect/sql-mysql2/MysqlClient#MysqlClientConfig`: Retained; connectionTTL uses Duration.Input and v4 adds disablePreparedStatements. + +### `@effect/sql-pg/PgClient` + +- `PgClient.PgClient` -> `@effect/sql-pg/PgClient#PgClient`: Retained; the service value is now a Context.Service. + +- `PgClient.PgClientConfig` -> `@effect/sql-pg/PgClient#PgClientConfig / PgPoolConfig`: Use PgClientConfig for base settings and PgPoolConfig for make/layer; pool sizing, idle timeout, and connection TTL moved to PgPoolConfig. + +- `PgClient.PgClientFromPoolOptions` -> `none`: The node-pg Pool wrapper options were removed with fromPool. Use PgClient.PgPoolConfig with PgClient.make or PgClient.layer. + +- `PgClient.fromPool` -> `none`: Wrapping an existing node-pg Pool was removed with the native protocol client. Use PgClient.make or PgClient.layer with connection settings. + +- `PgClient.layerFromPool` -> `PgClient.layer`: Wrapping an existing node-pg Pool was removed. Provide connection settings to PgClient.layer instead. + +### `@effect/sql-sqlite-bun/SqliteClient` + +- `SqliteClient.SqliteClient` -> `@effect/sql-sqlite-bun/SqliteClient#SqliteClient`: Retained; the service value is now a Context.Service. + +### `@effect/sql-sqlite-do/SqliteClient` + +- `SqliteClient.SqliteClientConfig` -> `@effect/sql-sqlite-do/SqliteClient#SqliteClientConfig`: Retained; db is optional and storage may be supplied, but one of db or storage is required at runtime. + +### `@effect/sql-sqlite-node/SqliteClient` + +- `SqliteClient.SqliteClient` -> `@effect/sql-sqlite-node/SqliteClient#SqliteClient`: Retained on node:sqlite, but the byte-export member was removed; use backup(destination) for file backup. + +- `SqliteClient.SqliteClientConfig` -> `@effect/sql-sqlite-node/SqliteClient#SqliteClientConfig`: Retained; prepareCacheTTL now uses Duration.Input. + +### `@effect/sql-sqlite-react-native/SqliteClient` + +- `SqliteClient.asyncQuery` -> `@effect/sql-sqlite-react-native/SqliteClient#AsyncQuery`: Renamed and changed from FiberRef to Context.Reference; prefer withAsyncQuery or provide AsyncQuery as a service. + +### `@effect/sql-sqlite-wasm/OpfsWorker` + +- `OpfsWorker.OpfsWorkerConfig`: TODO: needs guidance + +### `@effect/sql-sqlite-wasm/SqliteClient` + +- `SqliteClient.SqliteClient` -> `@effect/sql-sqlite-wasm/SqliteClient#SqliteClient`: Retained with the same export/import surface; the service value is now a Context.Service. + +- `SqliteClient.currentTransferables` -> `@effect/sql-sqlite-wasm/SqliteClient#Transferables`: Renamed and changed from FiberRef to Context.Reference; prefer withTransferables or provide Transferables as a service. + +### `@effect/sql/Model` + +- `Model.Any` -> `effect/unstable/schema/Model#Any`: Moved; v4 schemas track DecodingServices and EncodingServices separately instead of one Context type. + +- `Model.AnyNoContext` -> `effect/unstable/schema/Model#Any`: The distinct no-context alias was removed; Model.Any propagates decoding and encoding services. Constrain both service types to never when required. + +- `Model.BooleanFromNumber` -> `effect/Schema#BooleanFromBit`: Use the core 0 | 1 to boolean schema; Model.BooleanSqlite is the ready-made model field. + +- `Model.Class` -> `effect/unstable/schema/Model#Class`: Moved; model variants remain select, insert, update, json, jsonCreate, and jsonUpdate. + +- `Model.Date` -> `effect/unstable/schema/Model#Date`: Moved; still serializes DateTime.Utc as a YYYY-MM-DD string. + +- `Model.DateTimeFromDate` -> `effect/Schema#DateTimeUtcFromDate`: Moved to core Schema and retains Date to DateTime.Utc conversion. + +- `Model.Generated` -> `effect/unstable/schema/Model#GeneratedByDb`: Renamed and now read-only, with select and json variants only. Use Model.Field with select, update, and json to preserve writable v3 behavior. + +- `Model.extract` -> `effect/unstable/schema/Model#extract`: Retained after moving the model variant helpers into core Effect's unstable schema package. + +- `Model.fieldFromKey` -> `effect/Schema#encodeKeys`: The field helper was removed; apply encodeKeys to each concrete struct or model-variant schema that crosses the naming boundary. + +- `Model.fields` -> `effect/unstable/schema/Model#fields`: Moved with the variant-model helpers into core Effect's unstable schema package. + +- `Model.makeDataLoaders` -> `effect/unstable/sql/SqlModel#makeResolvers`: Returns RequestResolvers instead of callable loaders; execute with SqlResolver.request and use RequestResolver delay/batch combinators for batching controls. The insert resolver requires model decoding services as well as insert-schema encoding services. + +### `@effect/sql/SqlClient` + +- `SqlClient.SafeIntegers` -> `effect/unstable/sql/SqlClient#SafeIntegers`: Moved and changed from a Reference subclass to a Context.Reference value; provide it as a service. + +- `SqlClient.TransactionConnection` -> `effect/unstable/sql/SqlClient#TransactionConnection`: Now a factory keyed by client id, not a singleton tag. Prefer the client's transactionService; the payload type is TransactionConnection.Service. + +- `SqlClient.TypeId` -> `none`: The brand is private in v4; do not inspect or attach it, and obtain clients through SqlClient or SqlClient.make. + +- `SqlClient.make` -> `effect/unstable/sql/SqlClient#make`: Moved; custom clients rename MakeOptions.reactiveMailbox to reactiveQueue and may supply transactionService. + +### `@effect/sql/SqlConnection` + +- `SqlConnection.Connection` -> `effect/unstable/sql/SqlConnection#Connection`: Moved; Connection.Acquirer is now top-level SqlConnection.Acquirer, and custom connections must implement executeValuesUnprepared. + +### `@effect/sql/SqlError` + +- `SqlError.SqlErrorTypeId` -> `effect/unstable/sql/SqlError#isSqlError`: The type id is private; use isSqlError for runtime narrowing or isSqlErrorReason for structured reason values. + +### `@effect/sql/SqlEventJournal` + +- `SqlEventJournal.layer` -> `effect/unstable/eventlog/SqlEventJournal#layer`: Moved; rename the eventLogTable layer option to entryTable. + +- `SqlEventJournal.make` -> `effect/unstable/eventlog/SqlEventJournal#make`: Moved with the same entryTable and remotesTable options. + +### `@effect/sql/SqlEventLogServer` + +- `SqlEventLogServer.layerStorage` -> `effect/unstable/eventlog/SqlEventLogServerEncrypted#layerStorage`: Moved to the encrypted server module with the same options and EventLogEncryption requirement. + +- `SqlEventLogServer.makeStorage` -> `effect/unstable/eventlog/SqlEventLogServerEncrypted#makeStorage`: Moved to the encrypted server module with the same SQL, encryption, and scope requirements. + +### `@effect/sql/SqlPersistedQueue` + +- `SqlPersistedQueue.layerStore` -> `effect/unstable/persistence/PersistedQueue#layerStoreSql`: Moved into PersistedQueue and renamed with the Sql suffix; options are unchanged. + +- `SqlPersistedQueue.make` -> `effect/unstable/persistence/PersistedQueue#makeStoreSql`: Use the SQL store constructor; PersistedQueue.make creates a typed queue from a store factory and is not equivalent. + +### `@effect/sql/SqlResolver` + +- `SqlResolver.SqlResolver` -> `RequestResolver.RequestResolver>`: The wrapper interface was removed; constructors return RequestResolvers. Execute them with effect/unstable/sql/SqlResolver#request. + +- `SqlResolver.void` -> `effect/unstable/sql/SqlResolver#void`: Moved, but remove the leading tag and withContext arguments; it now returns a RequestResolver synchronously and runs through SqlResolver.request. + +### `@effect/sql/SqlSchema` + +- `SqlSchema.single` -> `effect/unstable/sql/SqlSchema#findOne`: Renamed with the same first-row-or-fail behavior; empty results use Cause.NoSuchElementError and schema failures use Schema.SchemaError. + +- `SqlSchema.void` -> `effect/unstable/sql/SqlSchema#void`: Moved with the same encode, execute, and discard-result pattern; schema failures now use Schema.SchemaError. + +### `@effect/sql/Statement` + +- `Statement.FragmentId` -> `none`: The v4 fragment brand is private; use Fragment, fragment, and isFragment instead of direct type-id access. + +- `Statement.Statement` -> `effect/unstable/sql/Statement#Statement`: Moved; the nested Transformer type is now top-level and its callback receives Fiber.Fiber rather than FiberRefs.FiberRefs. + +- `Statement.currentTransformer` -> `effect/unstable/sql/Statement#CurrentTransformer`: Capitalized and changed from FiberRef\\> to Context.Reference\. + +- `Statement.custom` -> `effect/unstable/sql/Statement#custom`: Retained, but returns a Custom segment and uses paramA/paramB/paramC; wrap it with Statement.fragment when a Fragment is required. + +- `Statement.defaultEscape` -> `effect/unstable/sql/Statement#defaultEscape`: Moved with the same signature. + +- `Statement.join` -> `effect/unstable/sql/Statement#join`: Moved with the same empty, single, and multiple-clause behavior. + +- `Statement.make` -> `effect/unstable/sql/Statement#make`: Moved with the same constructor inputs. + +- `Statement.makeCompiler` -> `effect/unstable/sql/Statement#makeCompiler`: Moved to core Effect; the constructor options are exposed as Statement.CompilerOptions and retain the dialect-specific callbacks. + +- `Statement.setTransformer` -> `Layer.succeed(Statement.CurrentTransformer, transformer)`: The helper was removed; provide the CurrentTransformer reference as a layer. + +- `Statement.unsafeFragment` -> `Statement.fragment([Statement.literal(sql, params)])`: The helper was removed; construct the low-level fragment explicitly, or use the active constructor's sql.unsafe for an executable statement. + +- `Statement.withTransformer` -> `Effect.provideService(Statement.CurrentTransformer, transformer)`: The helper was removed; locally provide the transformer reference around the effect. + +- `Statement.withTransformerDisabled` -> `Effect.provideService(Statement.CurrentTransformer, undefined)`: The helper was removed; locally provide undefined for the transformer reference. + +### `@effect/typeclass/Bounded` + +- `Bounded.Bounded` -> `none`: V4 removed Bounded dictionaries. Keep the Order and minimum/maximum bounds as separate application values. + +- `Bounded.BoundedTypeLambda` -> `none`: V4 removed the @effect/typeclass higher-kinded Bounded instance machinery. + +- `Bounded.between` -> `Order.isBetween(B.compare)`: Use the v4 Order predicate with { minimum: B.minBound, maximum: B.maxBound }; the Bounded dictionary itself was removed. + +- `Bounded.clamp` -> `Order.clamp(B.compare)`: Use the v4 Order combinator with { minimum: B.minBound, maximum: B.maxBound }; the Bounded dictionary itself was removed. + +- `Bounded.max` -> `Reducer.make(Combiner.max(B.compare).combine, B.minBound)`: V4 removed Bounded dictionaries. Build the maximum Reducer from the separately retained Order and minimum bound. + +- `Bounded.min` -> `Reducer.make(Combiner.min(B.compare).combine, B.maxBound)`: V4 removed Bounded dictionaries. Build the minimum Reducer from the separately retained Order and maximum bound. + +- `Bounded.reverse` -> `Order.flip(B.compare)`: Flip the Order and swap the separately stored minimum and maximum bounds; v4 has no bundled Bounded dictionary. + +### `@effect/typeclass/Monoid` + +- `Monoid.Monoid` -> `Reducer.Reducer`: Reducer replaces Monoid in v4; empty is renamed initialValue and combineAll remains available. + +- `Monoid.array` -> `Array.makeReducerConcat`: Use the v4 array concatenation Reducer; Reducer replaces Monoid and names the identity initialValue. + +- `Monoid.fromSemigroup` -> `Reducer.make(S.combine, empty)`: Construct a v4 Reducer from the replacement Combiner operation and identity value. + +- `Monoid.max` -> `Reducer.make(Combiner.max(B.compare).combine, B.minBound)`: Reducer replaces Monoid. Build it from the v4 maximum Combiner and the bounded order's minimum value. + +- `Monoid.min` -> `Reducer.make(Combiner.min(B.compare).combine, B.maxBound)`: Reducer replaces Monoid. Build it from the v4 minimum Combiner and the bounded order's maximum value. + +- `Monoid.reverse` -> `Reducer.flip`: Use the v4 Reducer combinator; it preserves initialValue and reverses combine argument order. + +- `Monoid.struct` -> `Struct.makeReducer`: Pass a record of v4 Reducers to derive a field-wise Reducer. + +- `Monoid.tuple` -> `Tuple.makeReducer`: Pass one array of v4 Reducers instead of rest Monoid arguments. + +### `@effect/typeclass/Semigroup` + +- `Semigroup.Invariant` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. + +- `Semigroup.Product` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. + +- `Semigroup.SemiProduct` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. + +- `Semigroup.Semigroup` -> `Combiner.Combiner`: Combiner replaces Semigroup in v4 and retains the binary combine method; combineMany was removed. + +- `Semigroup.SemigroupTypeLambda` -> `none`: The @effect/typeclass package was removed in v4 with no generic typeclass layer replacement. Rewrite this abstraction against the concrete v4 data type and its module functions. + +- `Semigroup.array` -> `Array.makeReducerConcat`: The v4 concatenation Reducer is also a Combiner and replaces the array Semigroup. + +- `Semigroup.constant` -> `Combiner.constant`: Combiner replaces Semigroup in v4. + +- `Semigroup.first` -> `Combiner.first`: Combiner replaces Semigroup in v4. + +- `Semigroup.imap` -> `Combiner.make`: V4 has no generic invariant instance; build a Combiner that maps both inputs with from, combines them, then maps the result with to. + +- `Semigroup.intercalate` -> `Combiner.intercalate`: Combiner replaces Semigroup; v4 takes the separator first and then the Combiner. + +- `Semigroup.last` -> `Combiner.last`: Combiner replaces Semigroup in v4. + +- `Semigroup.make` -> `Combiner.make`: Combiner replaces Semigroup. V4 accepts only the binary combine function and has no combineMany override. + +- `Semigroup.max` -> `Combiner.max`: Combiner replaces Semigroup; pass the same Order to retain last-maximum tie behavior. + +- `Semigroup.min` -> `Combiner.min`: Combiner replaces Semigroup; pass the same Order to retain last-minimum tie behavior. + +- `Semigroup.reverse` -> `Combiner.flip`: Use the v4 Combiner combinator to reverse combine argument order. + +- `Semigroup.struct` -> `Struct.makeCombiner`: Pass a record of v4 Combiners to derive a field-wise Combiner. + +- `Semigroup.tuple` -> `Tuple.makeCombiner`: Pass one array of v4 Combiners instead of rest Semigroup arguments. + +### `@effect/typeclass/data/Array` + +- `Array.Applicative` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Chainable` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Covariant` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Filterable` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.FlatMap` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Foldable` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Invariant` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Monad` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Of` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Pointed` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Product` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.SemiApplicative` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.SemiProduct` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.Traversable` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.TraversableFilterable` -> `effect/Array`: The @effect/typeclass package and its Array instance dictionaries were removed in v4. Use the concrete effect/Array operations directly. + +- `Array.getMonoid` -> `Array.makeReducerConcat`: Use the v4 concatenation Reducer; Reducer replaces Monoid. + +- `Array.getSemigroup` -> `Array.makeReducerConcat`: The v4 concatenation Reducer is also a Combiner and replaces this Semigroup. + +### `@effect/typeclass/data/BigInt` + +- `BigInt.MonoidMultiply` -> `BigInt.ReducerMultiply`: Renamed and moved to the concrete v4 BigInt module. + +- `BigInt.MonoidSum` -> `BigInt.ReducerSum`: Renamed and moved to the concrete v4 BigInt module. + +- `BigInt.SemigroupMax` -> `BigInt.CombinerMax`: Renamed and moved to the concrete v4 BigInt module. + +- `BigInt.SemigroupMin` -> `BigInt.CombinerMin`: Renamed and moved to the concrete v4 BigInt module. + +- `BigInt.SemigroupMultiply` -> `BigInt.ReducerMultiply`: The v4 Reducer is also a Combiner and preserves multiplication combine semantics. + +- `BigInt.SemigroupSum` -> `BigInt.ReducerSum`: The v4 Reducer is also a Combiner and preserves addition combine semantics. + +### `@effect/typeclass/data/Boolean` + +- `Boolean.MonoidEqv` -> `Reducer.make(Boolean.eqv, true)`: Rebuild the removed instance with the v4 boolean operation and its identity. + +- `Boolean.MonoidEvery` -> `Boolean.ReducerAnd`: Renamed and moved to the concrete v4 Boolean module. + +- `Boolean.MonoidSome` -> `Boolean.ReducerOr`: Renamed and moved to the concrete v4 Boolean module. + +- `Boolean.MonoidXor` -> `Reducer.make(Boolean.xor, false)`: Rebuild the removed instance with the v4 boolean operation and its identity. + +- `Boolean.SemigroupEqv` -> `Combiner.make(Boolean.eqv)`: Rebuild the removed instance as a v4 Combiner. + +- `Boolean.SemigroupEvery` -> `Boolean.ReducerAnd`: The v4 Reducer is also a Combiner and preserves logical-AND combine semantics. + +- `Boolean.SemigroupSome` -> `Boolean.ReducerOr`: The v4 Reducer is also a Combiner and preserves logical-OR combine semantics. + +- `Boolean.SemigroupXor` -> `Combiner.make(Boolean.xor)`: Rebuild the removed instance as a v4 Combiner. + +### `@effect/typeclass/data/Duration` + +- `Duration.Bounded` -> `none`: V4 has no Bounded dictionary; use Duration.Order with Duration.zero and Duration.infinity as separate bounds. + +- `Duration.MonoidMax` -> `Reducer.make(Duration.max, Duration.zero)`: Rebuild the removed maximum Monoid as a v4 Reducer with the same identity. + +- `Duration.MonoidMin` -> `Reducer.make(Duration.min, Duration.infinity)`: Rebuild the removed minimum Monoid as a v4 Reducer with the same identity. + +- `Duration.MonoidSum` -> `Duration.ReducerSum`: Renamed and moved to the concrete v4 Duration module. + +- `Duration.SemigroupMax` -> `Duration.CombinerMax`: Renamed and moved to the concrete v4 Duration module. + +- `Duration.SemigroupMin` -> `Duration.CombinerMin`: Renamed and moved to the concrete v4 Duration module. + +- `Duration.SemigroupSum` -> `Duration.ReducerSum`: The v4 Reducer is also a Combiner and preserves Duration.sum combine semantics. + +### `@effect/typeclass/data/Effect` + +- `Effect.Chainable` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.ConcurrencyOptions` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.Covariant` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.FlatMap` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.Invariant` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.Monad` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.Of` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.Pointed` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.getApplicative` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.getProduct` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.getSemiApplicative` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +- `Effect.getSemiProduct` -> `effect/Effect`: The @effect/typeclass package and its Effect instance dictionaries were removed in v4. Use the concrete effect/Effect operations directly. + +### `@effect/typeclass/data/Either` + +- `Either.Applicative` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Bicovariant` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Chainable` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Covariant` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.FlatMap` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Foldable` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Invariant` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Monad` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Of` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Pointed` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Product` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.SemiAlternative` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.SemiApplicative` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.SemiCoproduct` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.SemiProduct` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +- `Either.Traversable` -> `effect/Either`: The @effect/typeclass package and its Either instance dictionaries were removed in v4. Use the concrete effect/Either operations directly. + +### `@effect/typeclass/data/Micro` + +- `Micro.Chainable` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.ConcurrencyOptions` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.Covariant` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.FlatMap` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.Invariant` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.Monad` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.Of` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.Pointed` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.getApplicative` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.getProduct` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.getSemiApplicative` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +- `Micro.getSemiProduct` -> `effect/Micro`: The @effect/typeclass package and its Micro instance dictionaries were removed in v4. Use the concrete effect/Micro operations directly. + +### `@effect/typeclass/data/Number` + +- `Number.Bounded` -> `none`: V4 has no Bounded dictionary; use Number.Order with -Infinity and Infinity as separate bounds. + +- `Number.MonoidMax` -> `Number.ReducerMax`: Renamed and moved to the concrete v4 Number module. + +- `Number.MonoidMin` -> `Number.ReducerMin`: Renamed and moved to the concrete v4 Number module. + +- `Number.MonoidMultiply` -> `Number.ReducerMultiply`: Renamed and moved to the concrete v4 Number module. + +- `Number.MonoidSum` -> `Number.ReducerSum`: Renamed and moved to the concrete v4 Number module. + +- `Number.SemigroupMax` -> `Number.ReducerMax`: The v4 Reducer is also a Combiner and preserves maximum combine semantics. + +- `Number.SemigroupMin` -> `Number.ReducerMin`: The v4 Reducer is also a Combiner and preserves minimum combine semantics. + +- `Number.SemigroupMultiply` -> `Number.ReducerMultiply`: The v4 Reducer is also a Combiner and preserves multiplication combine semantics. + +- `Number.SemigroupSum` -> `Number.ReducerSum`: The v4 Reducer is also a Combiner and preserves addition combine semantics. + +### `@effect/typeclass/data/Option` + +- `Option.Alternative` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Applicative` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Chainable` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Coproduct` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Covariant` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Filterable` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.FlatMap` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Foldable` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Invariant` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Monad` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Of` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Pointed` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Product` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.SemiAlternative` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.SemiApplicative` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.SemiCoproduct` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.SemiProduct` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.Traversable` -> `effect/Option`: The @effect/typeclass package and its Option instance dictionaries were removed in v4. Use the concrete effect/Option operations directly. + +- `Option.getOptionalMonoid` -> `Option.makeReducer`: Pass the replacement Combiner; the v4 Reducer uses None as initialValue and combines two Some values. + +### `@effect/typeclass/data/Ordering` + +- `Ordering.Monoid` -> `Ordering.Reducer`: Renamed and moved to the concrete v4 Ordering module. + +- `Ordering.Semigroup` -> `Ordering.Reducer`: The v4 Reducer is also a Combiner and preserves Ordering combination semantics. + +### `@effect/typeclass/data/Predicate` + +- `Predicate.Contravariant` -> `effect/Predicate`: The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly. + +- `Predicate.Invariant` -> `effect/Predicate`: The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly. + +- `Predicate.Of` -> `effect/Predicate`: The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly. + +- `Predicate.Product` -> `effect/Predicate`: The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly. + +- `Predicate.SemiProduct` -> `effect/Predicate`: The @effect/typeclass package and its Predicate instance dictionaries were removed in v4. Use the concrete effect/Predicate operations directly. + +- `Predicate.getMonoidEqv` -> `Reducer.make(Predicate.eqv, Predicate.isUnknown)`: Rebuild the removed predicate instance as a v4 Reducer with the always-true predicate as initialValue. + +- `Predicate.getMonoidEvery` -> `Reducer.make(Predicate.and, Predicate.isUnknown)`: Rebuild the removed predicate instance as a v4 Reducer with the always-true predicate as initialValue. + +- `Predicate.getMonoidSome` -> `Reducer.make(Predicate.or, Predicate.isNever)`: Rebuild the removed predicate instance as a v4 Reducer with the always-false predicate as initialValue. + +- `Predicate.getMonoidXor` -> `Reducer.make(Predicate.xor, Predicate.isNever)`: Rebuild the removed predicate instance as a v4 Reducer with the always-false predicate as initialValue. + +- `Predicate.getSemigroupEqv` -> `Combiner.make(Predicate.eqv)`: Rebuild the removed predicate instance as a v4 Combiner. + +- `Predicate.getSemigroupEvery` -> `Combiner.make(Predicate.and)`: Rebuild the removed predicate instance as a v4 Combiner. + +- `Predicate.getSemigroupSome` -> `Combiner.make(Predicate.or)`: Rebuild the removed predicate instance as a v4 Combiner. + +- `Predicate.getSemigroupXor` -> `Combiner.make(Predicate.xor)`: Rebuild the removed predicate instance as a v4 Combiner. + +### `@effect/typeclass/data/Record` + +- `Record.Covariant` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.Filterable` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.Invariant` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.Traversable` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.TraversableFilterable` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.getCovariant` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.getFilterable` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.getInvariant` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.getMonoidUnion` -> `Record.makeReducerUnion`: Pass the replacement value Combiner; the v4 Reducer uses an empty record as initialValue. + +- `Record.getSemigroupIntersection` -> `Record.makeReducerIntersection`: Pass the replacement value Combiner and use the returned Reducer's combine operation for pairwise intersection. + +- `Record.getSemigroupUnion` -> `Record.makeReducerUnion`: The v4 Reducer is also a Combiner and preserves pairwise union semantics. + +- `Record.getTraversable` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +- `Record.getTraversableFilterable` -> `effect/Record`: The @effect/typeclass package and its Record instance dictionaries were removed in v4. Use the concrete effect/Record operations directly. + +### `@effect/typeclass/data/String` + +- `String.Monoid` -> `String.ReducerConcat`: Renamed and moved to the concrete v4 String module. + +- `String.Semigroup` -> `String.ReducerConcat`: The v4 Reducer is also a Combiner and preserves string concatenation. + +### `@effect/typeclass/data/Tuple` + +- `Tuple.Bicovariant` -> `effect/Tuple`: The @effect/typeclass package and its Tuple instance dictionaries were removed in v4. Use the concrete effect/Tuple operations directly. + +### `@effect/vitest/index` + +- `index.ApiConfig` -> `vitest/node#ApiConfig`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.ArgumentsType` -> `T extends (...args: infer A) => any ? A : never`: Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals. + +- `index.Arrayable` -> `T | Array`: Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals. + +- `index.Assertion` -> `vitest#Assertion`: Vitest 5 takes the matcher return type first. Replace Assertion\ with Assertion\ or Assertion\, T\> for asynchronous assertions. + +- `index.Awaitable` -> `T | PromiseLike`: Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals. + +- `index.BaseCoverageOptions` -> `vitest/node#BaseCoverageOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.BenchFactory` -> `vitest#Bench`: Use the Vitest 5 test-context bench fixture type. It is no longer the tinybench factory constructor. + +- `index.BenchFunction` -> `vitest#BenchFn`: Use BenchFn for the callback passed to the Vitest 5 test-context bench fixture. + +- `index.BenchTask` -> `vitest#BenchRegistration`: Migrate to a fixture registration and await its run() method; review its fields instead of treating it as a tinybench task. + +- `index.BenchTaskResult` -> `vitest#BenchResult`: Use the result returned by awaiting the Vitest 5 fixture registration's run() method. + +- `index.Benchmark` -> `vitest#TestBenchmark`: Use TestBenchmark for recorded benchmark data on a test; benchmarks are no longer standalone test tasks. + +- `index.BenchmarkAPI` -> `vitest#Bench`: Use the test-context bench fixture. Move skip, only, and todo to the enclosing test. + +- `index.BenchmarkResult` -> `vitest#BenchResult`: Use the result returned by awaiting the Vitest 5 fixture registration's run() method; review its changed fields. + +- `index.BenchmarkUserOptions` -> `vitest/node#BenchmarkUserOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.BrowserConfigOptions` -> `vitest/node#BrowserConfigOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.BrowserScript` -> `vitest/node#BrowserScript`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.BuiltinEnvironment` -> `vitest/node#BuiltinEnvironment`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CSSModuleScopeStrategy` -> `vitest/node#CSSModuleScopeStrategy`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CollectLineNumbers` -> `vitest/node#TypeCheckCollectLineNumbers`: Vitest 3 deprecated the root alias in favor of this renamed vitest/node type. + +- `index.CollectLines` -> `vitest/node#TypeCheckCollectLines`: Vitest 3 deprecated the root alias in favor of this renamed vitest/node type. + +- `index.Constructable` -> `new (...args: any[]) => any`: Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals. + +- `index.Context` -> `vitest/node#TypeCheckContext`: Vitest 3 deprecated the root alias in favor of this renamed vitest/node type. + +- `index.CoverageIstanbulOptions` -> `vitest/node#CoverageIstanbulOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CoverageOptions` -> `vitest/node#CoverageOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CoverageProvider` -> `vitest/node#CoverageProvider`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CoverageProviderModule` -> `vitest/node#CoverageProviderModule`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CoverageReporter` -> `vitest/node#CoverageReporter`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.CoverageV8Options` -> `vitest/node#CoverageV8Options`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.Custom` -> `vitest#RunnerTestCase`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.CustomProviderOptions` -> `vitest/node#CustomProviderOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.DepsOptimizationOptions` -> `vitest/node#DepsOptimizationOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.DoneCallback` -> `none`: Vitest does not support callback-style tests. Return a Promise or, in @effect/vitest tests, return an Effect. + +- `index.Environment` -> `vitest/runtime#Environment`: This was a deprecated root re-export. Import it from vitest/runtime; Vitest 5 exposes custom environments through vitest/runtime. + +- `index.EnvironmentOptions` -> `vitest/node#EnvironmentOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.EnvironmentReturn` -> `vitest/runtime#EnvironmentReturn`: This was a deprecated root re-export. Import it from vitest/runtime; Vitest 5 exposes custom environments through vitest/runtime. + +- `index.ErrorWithDiff` -> `vitest#TestError`: Vitest 3 deprecated ErrorWithDiff in favor of TestError; review the tightened actual, expected, and cause fields. + +- `index.ExpectPollOptions` -> `NonNullable[1]>`: Vitest 5 removes the named options export; derive the options from the public expect.poll function. + +- `index.ExtendedContext` -> `vitest#TestContext`: The separate context alias was removed. Vitest 5 uses TestContext, which includes the current task and lifecycle methods. + +- `index.File` -> `vitest#RunnerTestFile`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.HappyDOMOptions` -> `NonNullable`: Vitest 5 keeps this shape only as a property of EnvironmentOptions; derive it from the public vitest/node type. + +- `index.HookCleanupCallback` -> `none`: No named Vitest 5 export replaces this alias. Let the hook return type infer, or type the cleanup function locally. + +- `index.HookListener` -> `none`: Infer the callback from the public hook function, or derive it with Parameters\[0] and the corresponding hook name. + +- `index.InlineConfig` -> `vitest/node#InlineConfig`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.JSDOMOptions` -> `NonNullable`: Vitest 5 keeps this shape only as a property of EnvironmentOptions; derive it from the public vitest/node type. + +- `index.Matchers` -> `vitest#Matchers`: Augment vitest.Matchers\ for custom matchers. R is the matcher return type and T is the received value; @vitest/expect no longer shares Vitest's assertion state. + +- `index.Mock` -> `vitest#Mock`: This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route. + +- `index.ModuleCache` -> `none`: Vitest 3 marked this unused internal cache shape deprecated; Vitest 5 has no public replacement. + +- `index.MutableArray` -> `{ -readonly [K in keyof T]: T[K] }`: Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals. + +- `index.Nullable` -> `T | null | undefined`: Vitest 3 marked this root alias as an internal helper. Define the small TypeScript shape locally instead of depending on transitive internals. + +- `index.Pool` -> `vitest/node#Pool`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.PoolOptions` -> `vitest/config#TestUserConfig`: The v3 built-in poolOptions object was removed. Move its fields to Vitest 5 top-level config such as maxWorkers and vmMemoryLimit; vitest/node PoolOptions is a different custom-pool API. + +- `index.ProjectConfig` -> `vitest/node#ProjectConfig`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.RawErrsMap` -> `vitest/node#TypeCheckRawErrorsMap`: Vitest 3 deprecated the root alias in favor of this renamed vitest/node type. + +- `index.ReportContext` -> `vitest/node#ReportContext`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.Reporter` -> `vitest/node#Reporter`: Import Reporter from vitest/node; the deprecated vitest/reporters entrypoint was removed in Vitest 5. + +- `index.ResolveIdFunction` -> `none`: This deprecated vite-node callback was removed. Use Vite environment or module-runner APIs. + +- `index.ResolvedConfig` -> `vitest/node#ResolvedConfig`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.ResolvedCoverageOptions` -> `vitest/node#ResolvedCoverageOptions`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.ResolvedTestEnvironment` -> `none`: Vitest 3 marked this type unsupported. Use Environment from vitest/runtime for custom environments. + +- `index.RootAndTarget` -> `vitest/node#TypeCheckRootAndTarget`: Vitest 3 deprecated the root alias in favor of this renamed vitest/node type. + +- `index.RunnerCustomCase` -> `vitest#RunnerTestCase`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.RuntimeContext` -> `none`: Vitest 5 deprecates @vitest/runner and does not expose this internal state type. Extend TestRunner from vitest and use its public methods instead. + +- `index.SequenceHooks` -> `vitest/node#SequenceHooks`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.SequenceSetupFiles` -> `vitest/node#SequenceSetupFiles`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.SerializableSpec` -> `vitest#SerializedTestSpecification`: Use the non-deprecated Vitest name; SerializableSpec was only an alias. + +- `index.Suite` -> `vitest#RunnerTestSuite`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.SuiteHooks` -> `ReturnType`: Derive the hook collection from Vitest 5's public TestRunner API; ordinary tests should use public hook functions. + +- `index.Task` -> `vitest#RunnerTask`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.TaskBase` -> `vitest#RunnerTaskBase`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.TaskContext` -> `vitest#TestContext`: The separate context alias was removed. Vitest 5 uses TestContext, which includes the current task and lifecycle methods. + +- `index.TaskResult` -> `vitest#RunnerTaskResult`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.TaskResultPack` -> `vitest#RunnerTaskResultPack`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.Test` -> `vitest#RunnerTestCase`: Vitest 4 removed the deprecated unprefixed runner alias. Import the explicit Runner\* type from vitest. + +- `index.TransformModePatterns` -> `none`: This was removed with vite-node transform modes. Configure the Vite environment and its dependency optimizer instead. + +- `index.TscErrorInfo` -> `vitest/node#TypeCheckErrorInfo`: Vitest 3 deprecated the root alias in favor of this renamed vitest/node type. + +- `index.TypecheckConfig` -> `vitest/node#TypecheckConfig`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.UserConfig` -> `vitest/config#TestUserConfig`: Vitest 5 exposes its config as TestUserConfig; ViteUserConfig is the separate Vite configuration type. + +- `index.UserWorkspaceConfig` -> `vitest/config#UserWorkspaceConfig`: Import the type from vitest/config and migrate Vitest workspace configuration to projects. + +- `index.VitestEnvironment` -> `vitest/node#VitestEnvironment`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.VitestRunMode` -> `vitest/node#VitestRunMode`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.VmEnvironmentReturn` -> `vitest/runtime#VmEnvironmentReturn`: This was a deprecated root re-export. Import it from vitest/runtime; Vitest 5 exposes custom environments through vitest/runtime. + +- `index.WorkerContext` -> `vitest/node#WorkerContext`: This was a deprecated Vitest 3 root re-export. Import the type directly from vitest/node and review its Vitest 5 shape. + +- `index.WorkerRPC` -> `none`: The concrete worker RPC composition is internal. Use public Vitest RuntimeRPC, RunnerRPC, ContextRPC, or WorkerRequest types only when their narrower contract fits. + +- `index.bench` -> `vitest#test`: Vitest 5 removes the top-level bench export. Destructure bench from a regular test's context and await bench(name, fn).run(); use skip, only, or todo on the enclosing test. + +- `index.chai.Should` -> `vitest#chai.Should`: This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route. + +- `index.describe` -> `vitest#describe`: Vitest 5 removes describe.sequential and sequential options. Use describe(name, { concurrent: false }, body) for suites that depend on ordering. + +- `index.expect` -> `vitest#expect`: This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route. + +- `index.it` -> `@effect/vitest#it`: Effect helpers retain their calling convention. Vitest 5 removes it.sequential; pass { concurrent: false } to the native test or as the Effect helper's third argument. + +- `index.scoped` -> `@effect/vitest#effect`: V4 effect tests are scoped and provide the test environment. Replace scoped(...) with effect(...), and it.scoped(...) with it.effect(...). + +- `index.scopedLive` -> `@effect/vitest#live`: V4 live tests are scoped automatically. Replace scopedLive(...) with live(...), and it.scopedLive(...) with it.live(...). + +- `index.should` -> `vitest#should`: This was a Vitest re-export, not Effect API. Import it directly from vitest; @effect/vitest/index is not a valid v4 route. + +- `index.test` -> `vitest#test`: Vitest 5 removes test.sequential and sequential options. Pass { concurrent: false } to opt out of inherited concurrency. + +### `@effect/vitest/utils` + +- `utils.assertFailure` -> `assertExitFailure`: For v3 Exit values, rename to assertExitFailure. In v4, assertFailure instead asserts Result.Failure. + +- `utils.assertLeft` -> `assertFailure`: Either became Result in v4: migrate Left to Result.Failure, then use assertFailure; narrowed payload access changes from .left to .failure. + +- `utils.assertMatch` -> `assertMatch`: Unchanged positional helper and behavior; only the parameter spelling changed, so call sites need no change. + +- `utils.assertRight` -> `assertSuccess`: Either became Result in v4: migrate Right to Result.Success, then use assertSuccess; narrowed payload access changes from .right to .success. + +- `utils.assertSuccess` -> `assertExitSuccess`: For v3 Exit values, rename to assertExitSuccess. In v4, assertSuccess instead asserts Result.Success. + +### `@effect/workflow/Activity` + +- `Activity.Any` -> `effect/unstable/workflow/Activity#Any`: Moved into core Effect. V4 Any is minimal; use AnyWithProps when schemas or execution properties are required. + +- `Activity.CurrentAttempt` -> `effect/unstable/workflow/Activity#CurrentAttempt`: Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same default of 1. + +- `Activity.TypeId` -> `none`: The activity marker is private in v4. Use Activity, Activity.Any, or Activity.AnyWithProps constraints. + +- `Activity.make` -> `effect/unstable/workflow/Activity#make`: Moved into core Effect with the same constructor shape, v4 Schema.Constraint service directions, and optional annotations. + +- `Activity.raceAll` -> `effect/unstable/workflow/Activity#raceAll`: Moved into core Effect with the same named durable race behavior. + +### `@effect/workflow/DurableClock` + +- `DurableClock.TypeId` -> `none`: The durable-clock marker is private in v4. Use DurableClock values structurally. + +- `DurableClock.make` -> `effect/unstable/workflow/DurableClock#make`: Moved into core Effect; Duration.DurationInput is now Duration.Input. + +### `@effect/workflow/DurableDeferred` + +- `DurableDeferred.Any` -> `effect/unstable/workflow/DurableDeferred#Any`: Moved into core Effect. V4 Any is minimal; use AnyWithProps when success, error, or exit schemas are required. + +- `DurableDeferred.TypeId` -> `none`: The durable-deferred marker is private in v4. Use DurableDeferred, Any, or AnyWithProps constraints. + +- `DurableDeferred.await` -> `effect/unstable/workflow/DurableDeferred#await`: Moved into core Effect with the same persisted-result and workflow-suspension behavior. + +- `DurableDeferred.done` -> `effect/unstable/workflow/DurableDeferred#done`: Moved into core Effect; schema requirements now use explicit directional encoding services. + +- `DurableDeferred.fail` -> `effect/unstable/workflow/DurableDeferred#fail`: Moved into core Effect and now requires the error schema encoding services. + +- `DurableDeferred.failCause` -> `effect/unstable/workflow/DurableDeferred#failCause`: Moved into core Effect and now requires the error schema encoding services. + +- `DurableDeferred.into` -> `effect/unstable/workflow/DurableDeferred#into`: Moved into core Effect with the same exit recording and suspension propagation behavior. Provide both decoding and encoding services for the success and error schemas; recording the exit requires encoding services. + +- `DurableDeferred.make` -> `effect/unstable/workflow/DurableDeferred#make`: Moved into core Effect with the same name and optional schemas, expressed through v4 Schema.Constraint. + +- `DurableDeferred.raceAll` -> `effect/unstable/workflow/DurableDeferred#raceAll`: Moved into core Effect with the same persisted-winner behavior. + +- `DurableDeferred.succeed` -> `effect/unstable/workflow/DurableDeferred#succeed`: Moved into core Effect and now requires the success schema encoding services. + +### `@effect/workflow/DurableQueue` + +- `DurableQueue.TypeId` -> `effect/unstable/workflow/DurableQueue#TypeId`: Moved into core Effect; the marker literal changed to \~effect/workflow/DurableQueue. Use typeof DurableQueue.TypeId in type position. + +- `DurableQueue.make` -> `effect/unstable/workflow/DurableQueue#make`: Moved into core Effect; queue persistence now comes from effect/unstable/persistence. + +### `@effect/workflow/Workflow` + +- `Workflow.Any` -> `effect/unstable/workflow/Workflow#Any`: Moved into core Effect. Workflow identity changed from name to \_tag and definitions are now class-compatible constructors. + +- `Workflow.AnyTaggedRequestSchema` -> `none`: The TaggedRequest adapter constraint was removed. Define the workflow explicitly with Workflow.make and the request payload, success, error, and PrimaryKey schemas. + +- `Workflow.CaptureDefects` -> `effect/unstable/workflow/Workflow#CaptureDefects`: Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same true default. + +- `Workflow.Complete`: TODO: needs guidance + +- `Workflow.Execution` -> `effect/unstable/workflow/Workflow#Execution`: Moved into core Effect; its workflow discriminator changed from name to \_tag. + +- `Workflow.Requirements` -> `Workflow.RequirementsClient / Workflow.RequirementsHandler`: The schema Context union split by direction: client payload encoding and result decoding versus handler payload decoding and result encoding. + +- `Workflow.Result` -> `effect/unstable/workflow/Workflow#Result`: Moved into core Effect and remains the Complete or Suspended result type and schema constructor. + +- `Workflow.ResultEncoded` -> `effect/unstable/workflow/Workflow#ResultEncoded`: Moved into core Effect and remains both the encoded result type and generic encoded-result codec. + +- `Workflow.ResultTypeId` -> `none`: The result marker is private in v4. Use Workflow.isResult for narrowing. + +- `Workflow.SuspendOnFailure` -> `effect/unstable/workflow/Workflow#SuspendOnFailure`: Moved into core Effect and changed from a Context.Tag subclass to a Context.Reference value with the same false default. + +- `Workflow.TypeId` -> `none`: The workflow marker is private in v4. Use Workflow.Any or Workflow.Workflow constraints. + +- `Workflow.Workflow` -> `effect/unstable/workflow/Workflow#Workflow`: Name and name became Tag and \_tag, schemas use directional services, definitions are constructable, and poll returns Option. + +- `Workflow.Workflow.Error` -> `W["errorSchema"]["Type"]`: The namespace alias was removed. Extract the decoded error type from the public errorSchema property. + +- `Workflow.Workflow.Payload` -> `Schema.Schema.Type>`: The namespace alias was removed. Extract the decoded payload from the exported PayloadSchema helper. + +- `Workflow.Workflow.Success` -> `W["successSchema"]["Type"]`: The namespace alias was removed. Extract the decoded success type from the public successSchema property. + +- `Workflow.fromTaggedRequest` -> `none`: Removed. Expand to Workflow.make(schema.\_tag, { payload: schema, success: schema.success, error: schema.failure, idempotencyKey: PrimaryKey.value }). + +- `Workflow.make` -> `effect/unstable/workflow/Workflow#make`: The signature changed from make({ name, ... }) to make(tag, { ... }); definitions expose \_tag and are class-compatible constructors. + +### `@effect/workflow/WorkflowEngine` + +- `WorkflowEngine.makeUnsafe` -> `effect/unstable/workflow/WorkflowEngine#makeUnsafe`: Moved into core Effect. Context service projections now use Service instead of Type, and absent encoded results use Option. + +### `@effect/workflow/WorkflowProxy` + +- `WorkflowProxy.ConvertHttpApi` -> `effect/unstable/workflow/WorkflowProxy#ConvertHttpApi`: Moved into core Effect and updated to v4 HttpApiEndpoint types and the consolidated HttpApi architecture. + +- `WorkflowProxy.ConvertRpcs` -> `effect/unstable/workflow/WorkflowProxy#ConvertRpcs`: Moved into core Effect; generated execute, discard, and resume RPCs are now keyed from workflow \_tag. + +### `@effect/workflow/WorkflowProxyServer` + +- `WorkflowProxyServer.layerHttpApi` -> `effect/unstable/workflow/WorkflowProxyServer#layerHttpApi`: Moved into core Effect. Use v4 HttpApi group identifiers and Workflow.RequirementsHandler schema services. + +- `WorkflowProxyServer.layerRpcHandlers` -> `effect/unstable/workflow/WorkflowProxyServer#layerRpcHandlers`: Moved into core Effect; generated handlers require Workflow.RequirementsHandler rather than the undirected Requirements union. + +### `effect/Arbitrary` + +- `Arbitrary.ArbitraryAnnotation` -> `Schema.Annotations.ToArbitrary.Declaration`: Arbitrary derivation annotations now live in Schema.Annotations. Attach a toCodecArbitrary declaration callback that returns a Schema Link. + +- `Arbitrary.ArbitraryGenerationContext` -> `Schema.Annotations.ToArbitrary.DeclarationInput`: Native arbitrary callbacks receive DeclarationInput with decoded type-parameter schemas and normalized constraints. + +- `Arbitrary.LazyArbitrary` -> `effect/unstable/arbitrary/Arbitrary#Arbitrary`: The generated-value description is now the native Arbitrary interface from effect/unstable/arbitrary. + +#### `Arbitrary.make` + +**Replacement:** `effect/unstable/arbitrary/Arbitrary#schema` + +Derive a native Arbitrary from a Schema. Effect no longer bridges to fast-check. + +**Example** + +```ts +Arbitrary.schema(schema) +``` + +#### `Arbitrary.makeLazy` + +**Replacement:** `effect/unstable/arbitrary/Arbitrary#schema` + +Lazy and eager Schema derivation are the same native Arbitrary.schema constructor. + +**Example** + +```ts +Arbitrary.schema(schema) +``` + +### `effect/Array` + +- `Array.ReadonlyArray` -> `Array.ReadonlyArray`: The namespace and its Infer, With, OrNonEmpty, AndNonEmpty, and Flatten utility types remain. + +- `Array.filterMapWhile` -> `Array.takeWhileFilter`: Same map-until-first-miss behavior; change the callback from Option.some/none to Result.succeed/fail. + +- `Array.flatMapNullable` -> `Array.flatMapNullishOr`: Direct nullish-terminology rename; null and undefined mapper results are still discarded. + +- `Array.fromNullable` -> `Array.fromNullishOr`: Direct nullish-terminology rename; null and undefined become an empty array and other values become a singleton. + +- `Array.getEquivalence` -> `Array.makeEquivalence`: Direct rename; pass the element Equivalence unchanged. + +- `Array.getLefts` -> `Array.getFailures`: Either became Result; this extracts failure payloads in input order. + +- `Array.getOrder` -> `Array.makeOrder`: Direct rename; pass the element Order unchanged. + +- `Array.getRights` -> `Array.getSuccesses`: Either became Result; this extracts success payloads in input order. + +- `Array.init` -> `Array.init`: The API and Option\\> behavior remain unchanged. + +- `Array.isEmptyArray` -> `Array.isArrayEmpty`: Direct word-order rename; retains the mutable empty-array type guard. + +- `Array.isEmptyReadonlyArray` -> `Array.isReadonlyArrayEmpty`: Direct word-order rename; retains the readonly empty-array type guard. + +- `Array.isNonEmptyArray` -> `Array.isArrayNonEmpty`: Direct word-order rename; retains the mutable NonEmptyArray type guard. + +- `Array.isNonEmptyReadonlyArray` -> `Array.isReadonlyArrayNonEmpty`: Direct word-order rename; retains the NonEmptyReadonlyArray type guard. + +- `Array.liftEither` -> `Array.liftResult`: Either became Result; failures produce an empty array and successes produce a singleton. + +- `Array.liftNullable` -> `Array.liftNullishOr`: Direct nullish-terminology rename; the lifted function still returns zero or one element. + +- `Array.modifyNonEmptyHead` -> `Array.modifyHeadNonEmpty`: Direct word-order rename with the same non-empty-preserving result. + +- `Array.modifyNonEmptyLast` -> `Array.modifyLastNonEmpty`: Direct word-order rename with the same non-empty-preserving result. + +- `Array.modifyOption` -> `Array.modify`: The Option suffix was dropped; an out-of-bounds index still returns Option.none. + +- `Array.partitionMap` -> `Array.partition`: Pass a Result-returning mapper instead of Either; the output remains [failures, successes], corresponding to v3 [lefts, rights]. + +- `Array.removeOption` -> `Array.remove`: The closest API now returns an unchanged copy out of bounds; use Array.get before Array.remove to preserve the old Option result. + +- `Array.replaceOption` -> `Array.replace`: The Option suffix was dropped; an out-of-bounds index still returns Option.none. + +- `Array.setNonEmptyHead` -> `Array.setHeadNonEmpty`: Direct word-order rename with the same non-empty-preserving result. + +- `Array.setNonEmptyLast` -> `Array.setLastNonEmpty`: Direct word-order rename with the same non-empty-preserving result. + +- `Array.splitNonEmptyAt` -> `Array.splitAtNonEmpty`: Direct word-order rename; the left output remains guaranteed non-empty. + +- `Array.tail` -> `Array.tail`: The API and Option\\> behavior remain unchanged. + +- `Array.unsafeGet` -> `Array.getUnsafe`: Direct word-order rename; it still throws for an out-of-bounds index. + +### `effect/BigDecimal` + +- `BigDecimal.BigDecimal` -> `BigDecimal.BigDecimal`: The model interface remains, but its brand key is now internal. + +- `BigDecimal.TypeId` -> `none`: The brand key is internal in v4; use BigDecimal.isBigDecimal for runtime narrowing. + +- `BigDecimal.greaterThan` -> `BigDecimal.isGreaterThan`: Renamed with the v4 is-prefix. + +- `BigDecimal.greaterThanOrEqualTo` -> `BigDecimal.isGreaterThanOrEqualTo`: Renamed with the v4 is-prefix. + +- `BigDecimal.lessThan` -> `BigDecimal.isLessThan`: Renamed with the v4 is-prefix. + +- `BigDecimal.lessThanOrEqualTo` -> `BigDecimal.isLessThanOrEqualTo`: Renamed with the v4 is-prefix. + +- `BigDecimal.safeFromNumber` -> `BigDecimal.fromNumber`: Use the safe v4 constructor, which still returns Option. + +- `BigDecimal.unsafeDivide` -> `BigDecimal.divideUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +- `BigDecimal.unsafeFromNumber` -> `BigDecimal.fromNumberUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +- `BigDecimal.unsafeFromString` -> `BigDecimal.fromStringUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +- `BigDecimal.unsafeRemainder` -> `BigDecimal.remainderUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +- `BigDecimal.unsafeToNumber` -> `BigDecimal.toNumberUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +### `effect/BigInt` + +- `BigInt.fromNumber` -> `BigInt.fromNumber`: Unchanged; it returns Option for safe conversion. + +- `BigInt.greaterThan` -> `BigInt.isGreaterThan`: Renamed with the v4 is-prefix. + +- `BigInt.greaterThanOrEqualTo` -> `BigInt.isGreaterThanOrEqualTo`: Renamed with the v4 is-prefix. + +- `BigInt.lessThan` -> `BigInt.isLessThan`: Renamed with the v4 is-prefix. + +- `BigInt.lessThanOrEqualTo` -> `BigInt.isLessThanOrEqualTo`: Renamed with the v4 is-prefix. + +- `BigInt.unsafeDivide` -> `BigInt.divideUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +- `BigInt.unsafeSqrt` -> `BigInt.sqrtUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +### `effect/Brand` + +- `Brand.Brand` -> `Brand.Brand`: Still exported, but v4 brand keys are strings rather than symbols. + +- `Brand.Brand.BrandErrors` -> `Brand.BrandError`: Validation now returns one BrandError wrapping a SchemaIssue.Issue instead of an error array. + +- `Brand.Brand.RefinementError` -> `Schema.FilterIssue`: Brand.make validators use Schema filter output instead of the old message and meta record. + +- `Brand.BrandTypeId` -> `none`: The public marker was removed; the v4 brand type id is private. + +- `Brand.Branded` -> `Brand.Branded`: Still exported, with the brand key restricted to string. + +- `Brand.RefinedConstructorsTypeId` -> `none`: The public refined-constructor marker was removed. + +- `Brand.all` -> `Brand.all`: Still exported; combines multiple brand constructors and checks. + +- `Brand.error` -> `Brand.make`: Return a string or Schema filter issue directly from a Brand.make validator. + +- `Brand.nominal` -> `Brand.nominal`: Still exported; the constructor's either method is now result. + +- `Brand.refined` -> `Brand.make`: Use Brand.make for custom validation or Brand.check for Schema checks. + +- `Brand.unbranded` -> `Function.cast`: Brands are runtime-identical to their base value; cast explicitly when an unbranded type is required. + +### `effect/Cache` + +- `Cache.Cache` -> `Cache.Cache`: The cache model remains, but v4 exposes a Pipeable value with explicit Cache operations and adds a lookup environment parameter. + +- `Cache.Cache.ConsumerVariance` -> `none`: The ConsumerCache view and its variance marker were removed; expose a narrower application interface around Cache operations when write access must be hidden. + +- `Cache.Cache.Variance` -> `none`: The public variance marker was removed; use Cache.Cache directly and do not depend on its branding internals. + +- `Cache.CacheStats` -> `none`: Built-in hit and miss statistics were removed; instrument the lookup and Cache.get calls explicitly, and use Cache.size for the current entry count. + +- `Cache.CacheTypeId` -> `none`: The cache type id is internal in v4; do not inspect or construct the cache brand directly. + +- `Cache.ConsumerCache` -> `Cache.Cache`: ConsumerCache was removed; use Cache.Cache and expose an application-defined read-only wrapper if capability restriction is required. + +- `Cache.ConsumerCacheTypeId` -> `none`: ConsumerCache and its type id were removed with the read-only cache view. + +- `Cache.EntryStats` -> `none`: Per-entry loaded-time statistics were removed; record lookup timing in application instrumentation if needed. + +- `Cache.Lookup` -> `(key: Key) => Effect.Effect`: The named alias was removed; use an inline lookup function type or Cache.Cache\["lookup"]. + +- `Cache.makeCacheStats` -> `none`: CacheStats and its constructor were removed; define an application metrics record if these counters are still required. + +- `Cache.makeEntryStats` -> `none`: EntryStats and its constructor were removed; capture lookup timing in application instrumentation instead. + +### `effect/Cause` + +- `Cause.Cause` -> `Cause.Cause`: The name remains, but v4 Cause\ is a wrapper with readonly reasons: ReadonlyArray\\>, not the v3 Empty/Fail/Die/Interrupt/Sequential/Parallel tree. + +- `Cause.Cause.Variance` -> `none`: The public variance helper was removed. Cause.Cause is directly branded by Cause.TypeId; application code should not reproduce the old variance member. + +- `Cause.CauseReducer` -> `cause.reasons.reduce`: The six-case tree reducer type was removed with Empty, Sequential, and Parallel. Reduce the flat Reason array and switch on Fail, Die, or Interrupt instead. + +- `Cause.CauseTypeId` -> `Cause.TypeId`: The brand export is Cause.TypeId, a literal-string const. Use typeof Cause.TypeId in type positions; the v3 unique-symbol CauseTypeId alias is gone. + +- `Cause.Die` -> `Cause.Die`: The name remains, but Cause.Die is now a Reason stored in cause.reasons, not a Cause variant. Construct a standalone reason with Cause.makeDieReason or a cause with Cause.die. + +- `Cause.Empty` -> `Cause.empty`: The Empty subtype and \_tag were removed. Empty is Cause.empty, represented by cause.reasons.length === 0. + +- `Cause.ExceededCapacityException` -> `Cause.ExceededCapacityError`: Rename the class/type and update the discriminant from ExceededCapacityException to ExceededCapacityError. + +- `Cause.ExceededCapacityExceptionTypeId` -> `Cause.ExceededCapacityErrorTypeId`: Rename the brand; v4 exports a literal-string const, so use typeof Cause.ExceededCapacityErrorTypeId in type positions. + +- `Cause.Fail` -> `Cause.Fail`: The name remains, but Cause.Fail\ is now a Reason stored in cause.reasons, not a Cause variant. Construct a standalone reason with Cause.makeFailReason or a cause with Cause.fail. + +- `Cause.IllegalArgumentException` -> `Cause.IllegalArgumentError`: Rename the class/type and update the discriminant from IllegalArgumentException to IllegalArgumentError. + +- `Cause.IllegalArgumentExceptionTypeId` -> `Cause.IllegalArgumentErrorTypeId`: Rename the brand; v4 exports a literal-string const, so use typeof Cause.IllegalArgumentErrorTypeId in type positions. + +- `Cause.Interrupt` -> `Cause.Interrupt`: The name remains, but it is now a Reason in cause.reasons rather than a Cause variant, and fiberId changed from FiberId.FiberId to number | undefined. Use Cause.makeInterruptReason or Cause.interrupt. + +- `Cause.InterruptedException` -> `none`: The public exception class was removed. Represent cancellation with Cause.interrupt; Cause.prettyErrors creates an ordinary Error named InterruptError for interrupt-only rendering, but no class is exported. + +- `Cause.InterruptedExceptionTypeId` -> `none`: Removed with InterruptedException; v4 exports no interruption-error brand. Inspect the Cause with Cause.hasInterrupts or Cause.hasInterruptsOnly instead. + +- `Cause.InvalidPubSubCapacityException` -> `Error`: The dedicated public type was removed. Current v4 PubSub capacity validation throws a standard global Error with the capacity message. + +- `Cause.InvalidPubSubCapacityExceptionTypeId` -> `none`: Removed with InvalidPubSubCapacityException; the standard Error now thrown by PubSub has no Effect-specific brand. + +- `Cause.NoSuchElementException` -> `Cause.NoSuchElementError`: Rename the class/type and update the discriminant from NoSuchElementException to NoSuchElementError. + +- `Cause.NoSuchElementExceptionTypeId` -> `Cause.NoSuchElementErrorTypeId`: Rename the brand; v4 exports a literal-string const, so use typeof Cause.NoSuchElementErrorTypeId in type positions. + +- `Cause.Parallel` -> `none`: Parallel cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind. + +- `Cause.PrettyError` -> `Error`: Cause.prettyErrors now returns Array\. The dedicated span field is gone; tracing information is incorporated from Reason annotations into rendered stacks. + +- `Cause.RuntimeException` -> `Error`: The dedicated class was removed and v4 uses global Error for generic defects. Use Data.Error or Data.TaggedError instead when a yieldable typed error is required. + +- `Cause.RuntimeExceptionTypeId` -> `none`: Removed with RuntimeException. Define and guard a custom Data.Error/Data.TaggedError type if nominal branding is required. + +- `Cause.Sequential` -> `none`: Sequential cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind. + +- `Cause.TimeoutException` -> `Cause.TimeoutError`: Rename the class/type and update the discriminant from TimeoutException to TimeoutError. + +- `Cause.TimeoutExceptionTypeId` -> `Cause.TimeoutErrorTypeId`: Rename the brand; v4 exports a literal-string const, so use typeof Cause.TimeoutErrorTypeId in type positions. + +- `Cause.UnknownException` -> `Cause.UnknownError`: Rename the class/type and discriminant. The original unknown value is now exposed through the standard Error.cause property, not v3's .error field. + +- `Cause.UnknownExceptionTypeId` -> `Cause.UnknownErrorTypeId`: Rename the brand; v4 exports a literal-string const, so use typeof Cause.UnknownErrorTypeId in type positions. + +- `Cause.YieldableError` -> `Cause.YieldableError / Data.Error`: Cause.YieldableError remains as the interface/type, but its public constructor value was removed. Extend Data.Error for an untagged yieldable error or Data.TaggedError for a tagged one. + +- `Cause.andThen` -> `Cause.fromReasons(self.reasons.flatMap(...))`: No direct v4 combinator. For each Fail reason, splice either f(reason.error).reasons or the constant cause's reasons; retain Die and Interrupt reasons, then rebuild with Cause.fromReasons. + +- `Cause.as` -> `Cause.map(self, () => error)`: Use Cause.map with a constant function; only Fail errors change and Die/Interrupt reasons remain. + +- `Cause.contains` -> `Equal.equals(Cause.combine(self, that), self)`: There are no subtrees in v4. This tests whether all reasons from that are already present in self under v4 reason equality; use Equal.equals(self, that) when only whole-cause equality is intended. + +- `Cause.defects` -> `self.reasons.filter(Cause.isDieReason).map((reason) => reason.defect)`: Collect defect values from the flat Reason array. The result is a standard array rather than v3 Chunk. + +- `Cause.dieOption` -> `Cause.findDefect`: Cause.findDefect returns Result.Result\\>, not Option. Match the Result or convert it to Option when the old return shape is required. + +- `Cause.failureOption` -> `Cause.findErrorOption`: Direct Option-based replacement for extracting the first typed Fail error value. + +- `Cause.failureOrCause` -> `Cause.findError`: Use the v4 Result-based split: success is the first E and failure is the original Cause\ when no Fail reason exists. + +- `Cause.failures` -> `self.reasons.filter(Cause.isFailReason).map((reason) => reason.error)`: Collect typed error values from the flat Reason array. The result is a standard array rather than v3 Chunk. + +- `Cause.filter` -> `Cause.fromReasons(self.reasons.filter(...))`: No exact tree-level equivalent: v3 predicates selected recursive child causes. Rewrite the predicate for Cause.Reason values, filter cause.reasons, and rebuild with Cause.fromReasons. + +- `Cause.find` -> `Option.firstSomeOf(self.reasons.map(...))`: No recursive nodes remain. Apply the partial function to Reason values and take the first Some, or use Cause.findFail/findError/findDie/findDefect/findInterrupt for standard searches. + +- `Cause.flatMap` -> `Cause.fromReasons(self.reasons.flatMap((reason) => Cause.isFailReason(reason) ? f(reason.error).reasons : [reason]))`: No direct v4 export. Flat-map only Fail reasons into replacement causes, preserve Die/Interrupt reasons, and rebuild from the resulting Reason array. + +- `Cause.flatten` -> `Cause.fromReasons(self.reasons.flatMap((reason) => Cause.isFailReason(reason) ? reason.error.reasons : [reason]))`: No direct v4 export. For Cause\\>, splice each Fail reason's nested cause.reasons and retain Die/Interrupt reasons. + +- `Cause.flipCauseOption` -> `Cause.fromReasons + Option`: Rewrite over reasons: drop Fail(None), replace Fail(Some(e)) with Cause.makeFailReason(e), retain Die/Interrupt, then return None only when a non-empty input loses every reason; preserve Some(Cause.empty) for an empty input. + +- `Cause.interruptOption` -> `Cause.findInterrupt`: The replacement returns Result.Result\\> rather than Option\; on success read reason.fiberId, now number | undefined. + +- `Cause.isDie` -> `Cause.hasDies`: Use the v4 cause-level predicate for the presence of at least one Die reason. + +- `Cause.isDieType` -> `Cause.isDieReason`: Apply this guard to an entry of cause.reasons; Cause itself is no longer a Die union variant. + +- `Cause.isEmpty` -> `self.reasons.length === 0`: V4 represents an empty cause with an empty reasons array and exports no isEmpty function. + +- `Cause.isEmptyType` -> `self.reasons.length === 0`: The check remains possible, but there is no Empty subtype to narrow to because v4 Cause is not a variant union. + +- `Cause.isExceededCapacityException` -> `Cause.isExceededCapacityError`: Rename the guard along with ExceededCapacityError. + +- `Cause.isFailType` -> `Cause.isFailReason`: Apply this guard to an entry of cause.reasons; Cause itself is no longer a Fail union variant. + +- `Cause.isFailure` -> `Cause.hasFails`: Use the v4 cause-level predicate for the presence of at least one Fail reason. + +- `Cause.isIllegalArgumentException` -> `Cause.isIllegalArgumentError`: Rename the guard along with IllegalArgumentError. + +- `Cause.isInterruptType` -> `Cause.isInterruptReason`: Apply this guard to an entry of cause.reasons; Cause itself is no longer an Interrupt union variant. + +- `Cause.isInterrupted` -> `Cause.hasInterrupts`: Use the v4 cause-level predicate for the presence of at least one Interrupt reason. + +- `Cause.isInterruptedException` -> `none`: No v4 InterruptError class or unknown-value guard is exported. When the Cause is available, test Cause.hasInterruptsOnly before squashing or rendering it. + +- `Cause.isInterruptedOnly` -> `Cause.hasInterruptsOnly`: Direct cause-level rename; it is false for Cause.empty and true only when at least one reason exists and every reason is Interrupt. + +- `Cause.isNoSuchElementException` -> `Cause.isNoSuchElementError`: Rename the guard along with NoSuchElementError. + +- `Cause.isParallelType` -> `none`: Parallel cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind. + +- `Cause.isRuntimeException` -> `none`: RuntimeException and its brand were removed. Use instanceof Error for generic errors or define a Data.Error/Data.TaggedError class with its own guard when nominal recognition is required. + +- `Cause.isSequentialType` -> `none`: Sequential cause nodes were removed; v4 stores all reasons in one flat array and does not retain composition kind. + +- `Cause.isTimeoutException` -> `Cause.isTimeoutError`: Rename the guard along with TimeoutError. + +- `Cause.isUnknownException` -> `Cause.isUnknownError`: Rename the guard along with UnknownError. + +- `Cause.keepDefects` -> `Cause.fromReasons(self.reasons.filter(Cause.isDieReason))`: Keep every Die reason, not merely the first defect. Return Option.none when the filtered array is empty and Option.some of the rebuilt cause otherwise; Cause.findDefect alone is not behaviorally equivalent. + +- `Cause.linearize` -> `self.reasons`: No direct replacement: v4 discarded sequential/parallel structure, so there are no parallel branches to linearize. Rewrite the consumer to process the flat Reason array. + +- `Cause.originalError` -> `Function.identity`: V3 used this to unwrap span-capture proxies. V4 stores tracing data on Reason.annotations and no longer proxies errors, so the input is already the original value. + +- `Cause.parallel` -> `Cause.combine`: Combine the two flat reason arrays; v4 intentionally no longer records whether composition was parallel or sequential. + +- `Cause.reduce` -> `self.reasons.reduce`: Reduce the flat Reason array directly. The callback now sees only Fail, Die, and Interrupt reasons, never Empty or composition nodes. + +- `Cause.reduceWithContext` -> `self.reasons.reduce`: Capture the context in the reducer closure and reduce the flat Reason array; sequentialCase and parallelCase have no v4 analogue. + +- `Cause.sequential` -> `Cause.combine`: Combine the two flat reason arrays; v4 intentionally no longer records whether composition was parallel or sequential. + +- `Cause.size` -> `self.reasons.length`: The v3 node count becomes the number of flat reasons in v4. + +- `Cause.squashWith` -> `Result.match(Cause.findError(self), { onSuccess: f, onFailure: Cause.squash })`: Apply f only to the first typed Fail error; if no Fail exists, squash the returned Cause\. This preserves v3's priority and avoids evaluating f for later Fail reasons. + +- `Cause.stripFailures` -> `Cause.fromReasons(self.reasons.filter((reason) => !Cause.isFailReason(reason)))`: Remove Fail reasons and retain Die plus Interrupt reasons, then rebuild the cause. The v3 prose saying interrupts were removed did not match its implementation. + +- `Cause.stripSomeDefects` -> `Cause.fromReasons + Option`: Filter out each Die reason for which pf(reason.defect) is Some, retain all other reasons, and rebuild. Return None only when a non-empty input loses every reason; preserve Some(Cause.empty) for empty input. + +### `effect/Channel` + +- `Channel.Channel` -> `Channel.Channel`: Retained, but reorder type parameters from \ to \. Convert Effect values explicitly with Channel.fromEffect or Channel.fromEffectDone. + +- `Channel.ChannelException` -> `none`: Removed implementation artifact. Channel.pipeToOrFail now handles upstream failures without exposing the v3 exception wrapper. + +- `Channel.ChannelExceptionTypeId` -> `none`: Removed implementation artifact. Channel.pipeToOrFail now handles upstream failures without exposing the v3 exception wrapper. + +- `Channel.ChannelTypeId` -> `Channel.TypeId`: Renamed to TypeId; the brand is now the string literal \~effect/Channel. Prefer Channel.isChannel for runtime checks. + +- `Channel.ChannelUnify` -> `Channel.ChannelUnify`: Retained; update inferred Channel arguments to the v4 generic order. + +- `Channel.ChannelUnifyIgnore` -> `Channel.ChannelUnifyIgnore`: Retained with a new shape: it no longer extends EffectUnifyIgnore and now contains Effect?: true. + +- `Channel.acquireReleaseOut` -> `Channel.acquireRelease`: Renamed to acquireRelease. The v4 release action cannot add environment requirements, so capture or provide any services it needs. + +- `Channel.as` -> `Channel.mapDone`: Replace Channel.as(self, value) with Channel.mapDone(self, () =\> value). + +- `Channel.asVoid` -> `Channel.mapDone`: Replace Channel.asVoid(self) with Channel.mapDone(self, () =\> void 0). + +- `Channel.bufferChunk` -> `none`: The inferred Channel.fromChunk match is not equivalent. Rebuild the buffered upstream-pull transform with Channel.fromTransform and Channel.toTransform. + +- `Channel.catchAll` -> `Channel.catch`: Renamed to catch for typed-error recovery. + +- `Channel.catchAllCause` -> `Channel.catchCause`: Renamed to catchCause for full-cause recovery. + +- `Channel.collect` -> `Channel.filterMap`: Use Channel.filterMap(self, Filter.fromPredicateOption(pf)) to adapt the v3 Option-returning partial function. + +- `Channel.concatAll` -> `Channel.flatten`: Use flatten for sequential emitted-channel flattening. V4 preserves the outer done value and discards child done values. + +- `Channel.concatAllWith` -> `none`: V4 removed child-done accumulation and the outer-done combiner. Use Channel.flatten or Channel.flatMap only when child done values may be discarded; otherwise implement a Pull transform. + +- `Channel.concatMap` -> `Channel.flatMap`: Renamed to flatMap. Sequential flattening is the default; child done values are discarded and the source done value is preserved. + +- `Channel.concatMapWith` -> `none`: V4 removed child-done accumulation and the outer-done combiner. Use Channel.flatten or Channel.flatMap only when child done values may be discarded; otherwise implement a Pull transform. + +- `Channel.concatMapWithCustom` -> `none`: Removed with the channel executor scheduling protocol. Use Channel.flatMap for ordinary sequencing or implement custom scheduling with Channel.fromTransform and Pull. + +- `Channel.concatOut` -> `Channel.flatten`: Use flatten for sequential emitted-channel flattening. V4 preserves the outer done value and discards child done values. + +- `Channel.context` -> `Channel.contextWith`: Use Channel.contextWith((context) =\> Channel.end(context)); the context was the v3 channel done value. + +- `Channel.contextWithChannel` -> `Channel.contextWith`: Renamed to contextWith. + +- `Channel.contextWithEffect` -> `Channel.contextWith`: Use Channel.contextWith((context) =\> Channel.fromEffectDone(f(context))) to preserve the effect result as the done value. + +- `Channel.doneCollect` -> `none`: No exact channel combinator remains. Drive Channel.toPull, collect output elements, and handle Cause.Done to retain both outputs and the done value. + +- `Channel.emitCollect` -> `none`: No exact channel combinator remains. Drive Channel.toPull, collect output elements, and handle Cause.Done to retain both outputs and the done value. + +- `Channel.ensuringWith` -> `Channel.onExit`: Renamed to onExit; the finalizer still receives the channel Exit. + +- `Channel.foldCauseChannel` -> `none`: V4 has no exact two-sided fold over failure and completion. Use catchCause or catch for failure-only handling, concatWith for success-only handling, or match the Pull in a custom transform. + +- `Channel.foldChannel` -> `none`: V4 has no exact two-sided fold over failure and completion. Use catchCause or catch for failure-only handling, concatWith for success-only handling, or match the Pull in a custom transform. + +- `Channel.fromEither` -> `Channel.fromEffectDone`: Either is now Result. Use Channel.fromEffectDone(Effect.fromResult(result)) to preserve success as the done value. + +- `Channel.fromInput` -> `none`: SingleProducerAsyncInput was removed. Model the producer with Queue and Pull; use Channel.fromPull with Queue.take when a typed done value matters. + +- `Channel.fromOption` -> `Channel.fromEffectDone`: Use Channel.fromEffectDone(Effect.fromOption(option, Option.none)) to preserve the v3 Option.none error, or omit onNone for the v4 NoSuchElementError default. + +- `Channel.fromPubSubScoped` -> `Channel.fromPubSubTake`: Change the protocol to PubSub\\> and use Channel.flattenArray(Channel.fromPubSubTake(pubsub)). The v4 constructor owns the scoped subscription and returns a Channel directly. + +- `Channel.interruptWhenDeferred` -> `Channel.interruptWhen`: Use Channel.interruptWhen(self, Deferred.await(deferred)); the Deferred-specific overload was removed. + +- `Channel.isChannelException` -> `none`: Removed implementation artifact. Channel.pipeToOrFail now handles upstream failures without exposing the v3 exception wrapper. + +- `Channel.mapErrorCause` -> `Channel.catchCause`: Use Channel.catchCause(self, (cause) =\> Channel.failCause(f(cause))). + +- `Channel.mapInputContext` -> `Channel.updateContext`: Renamed to updateContext for transforming the channel requirement Context. + +- `Channel.mapInputEffect` -> `none`: V4 removed upstream done/error effect mapping. Adapt Cause.Done or failure on the upstream Pull, then pass it through Channel.toTransform(self). + +- `Channel.mapInputErrorEffect` -> `none`: V4 removed upstream done/error effect mapping. Adapt Cause.Done or failure on the upstream Pull, then pass it through Channel.toTransform(self). + +- `Channel.mapInputIn` -> `Channel.mapInput`: Use Channel.mapInput(self, (value) =\> Effect.succeed(f(value))); v4 consolidated pure and effectful input mapping. + +- `Channel.mapInputInEffect` -> `Channel.mapInput`: Renamed to mapInput; the mapper remains effectful. + +- `Channel.mapOut` -> `Channel.map`: Renamed to map; the v4 mapper also receives the element index. + +- `Channel.mapOutEffect` -> `Channel.mapEffect`: Renamed to mapEffect for sequential effectful output mapping. + +- `Channel.mapOutEffectPar` -> `Channel.mapEffect`: Use Channel.mapEffect(self, f, { concurrency: n }); ordered output remains the default. + +- `Channel.mergeAllUnbounded` -> `Channel.mergeAll`: Use Channel.mergeAll(channels, { concurrency: "unbounded" }); child done values are discarded and the outer done value is preserved. + +- `Channel.mergeAllUnboundedWith` -> `none`: V4 mergeAll removed child-done combining. Use Channel.mergeAll if terminal accumulation can be dropped; otherwise implement a custom Pull merge. + +- `Channel.mergeAllWith` -> `none`: V4 mergeAll removed child-done combining. Use Channel.mergeAll if terminal accumulation can be dropped; otherwise implement a custom Pull merge. + +- `Channel.mergeMap` -> `Channel.flatMap`: Use Channel.flatMap with concurrency and bufferSize for backpressure, or Channel.switchMap with the same options for the v3 sliding strategy. + +- `Channel.mergeOut` -> `Channel.mergeAll`: Use Channel.mergeAll(self, { concurrency: n }) for bounded backpressured flattening when child done values are irrelevant. + +- `Channel.mergeOutWith` -> `none`: V4 mergeAll removed child-done combining. Use Channel.mergeAll if terminal accumulation can be dropped; otherwise implement a custom Pull merge. + +- `Channel.mergeWith` -> `Channel.merge`: Use Channel.merge with haltStrategy left, right, both, or either for standard policies. Custom MergeDecision effects require a Pull-level redesign. + +- `Channel.orDieWith` -> `Channel.catch`: Use Channel.catch(self, (error) =\> Channel.die(f(error))); v4 Channel.orDie has no mapping callback. + +- `Channel.orElse` -> `Channel.catch`: Use Channel.catch(self, () =\> that()) and keep the fallback lazy. + +- `Channel.provideLayer` -> `Channel.provide`: Both collapse into provide. V4 removes services supplied by the layer and retains remaining requirements; use options.local when a fresh layer instance is needed. + +- `Channel.provideSomeLayer` -> `Channel.provide`: Both collapse into provide. V4 removes services supplied by the layer and retains remaining requirements; use options.local when a fresh layer instance is needed. + +- `Channel.read` -> `none`: The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling. + +- `Channel.readOrFail` -> `none`: The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling. + +- `Channel.readWith` -> `none`: The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling. + +- `Channel.readWithCause` -> `none`: The channel instruction AST was removed. Implement a one-step upstream read with Channel.fromTransform and Pull matching, including Cause.Done handling. + +- `Channel.repeated` -> `Channel.forever`: Use forever for infinite repetition. Channel.repeat takes a Schedule and may terminate, so it is not equivalent. + +- `Channel.run` -> `Channel.runDrain`: Use runDrain to consume all emitted elements and return the channel's done value. + +- `Channel.runScoped` -> `Channel.toPull`: No direct scoped runner remains. Use toPull in the caller scope and recover Cause.Done; use runDrain when an internally managed scope is acceptable. + +- `Channel.scopedWith` -> `Channel.unwrap`: Use Channel.unwrap(Effect.map(Effect.scope, (scope) =\> Channel.fromEffect(f(scope)))) so the effect uses the active channel scope. + +- `Channel.toPullIn` -> `Channel.toPullScoped`: Renamed to toPullScoped. The returned Pull emits elements directly and represents completion with Cause.Done instead of Either. + +- `Channel.toSink` -> `Sink.fromChannel`: Constructor moved to Sink. Adapt the channel to non-empty array input, no emitted leftovers, and a Sink.End done value. + +- `Channel.toStream` -> `Stream.fromChannel`: Constructor moved to Stream. Adapt Chunk outputs to non-empty readonly arrays and map the channel done value to void. + +- `Channel.unwrapScoped` -> `Channel.unwrap`: Use unwrap; v4 supplies the active channel scope to the effect and removes Scope from the resulting requirement. + +- `Channel.unwrapScopedWith` -> `Channel.unwrap`: Use Channel.unwrap(Effect.flatMap(Effect.scope, f)) to pass the active channel scope to f. + +- `Channel.void` -> `Channel.empty`: Renamed to empty: emit nothing and end with void. + +- `Channel.write` -> `Channel.succeed`: Renamed to succeed, which emits one element in v4. Use Channel.end when migrating v3 succeed, which produced a done value. + +- `Channel.writeAll` -> `Channel.fromArray`: Replace the variadic writer with Channel.fromArray(outs). + +- `Channel.writeChunk` -> `Channel.fromChunk`: Renamed to fromChunk for emitting every Chunk element. + +- `Channel.zip` -> `Channel.concatWith`: For sequential zip, concatWith the left channel and mapDone the right result to a tuple. Concurrent tuple-done semantics require custom Pull coordination. + +- `Channel.zipLeft` -> `Channel.concatWith`: For sequential zipLeft, concatWith and mapDone the right result back to the left done value. Concurrent done preservation requires custom Pull coordination. + +- `Channel.zipRight` -> `Channel.concat`: Use concat for the sequential form; it preserves the right done value. Concurrent mode has no exact replacement. + +### `effect/Chunk` + +- `Chunk.Chunk` -> `Chunk.Chunk`: The model remains Chunk.Chunk\; continue using Chunk constructors rather than depending on its exposed representation fields. + +- `Chunk.TypeId` -> `none`: The v4 Chunk brand key is private; no public Chunk.TypeId type or value is exported. + +- `Chunk.getEquivalence` -> `Chunk.makeEquivalence`: Direct rename; pass the element Equivalence unchanged. + +- `Chunk.modifyOption` -> `Chunk.modify`: The Option suffix was dropped; an out-of-bounds index still returns Option.none. + +- `Chunk.partitionMap` -> `Chunk.partition`: Pass a Result-returning mapper instead of Either; the output remains [failures, successes]. + +- `Chunk.removeOption` -> `Chunk.remove`: The closest API now returns the unchanged Chunk out of bounds; use Chunk.get before Chunk.remove to preserve the old Option result. + +- `Chunk.replaceOption` -> `Chunk.replace`: The Option suffix was dropped; an out-of-bounds index still returns Option.none. + +- `Chunk.unsafeFromArray` -> `Chunk.fromArrayUnsafe`: Direct word-order rename; it still wraps without copying and is unsafe if the source array is mutated. + +- `Chunk.unsafeFromNonEmptyArray` -> `Chunk.fromNonEmptyArrayUnsafe`: Direct word-order rename; it still wraps without copying and preserves NonEmptyChunk. + +- `Chunk.unsafeGet` -> `Chunk.getUnsafe`: Direct word-order rename; it still throws for an out-of-bounds index. + +- `Chunk.unsafeHead` -> `Chunk.headUnsafe`: Direct word-order rename; it still throws on an empty Chunk. + +- `Chunk.unsafeLast` -> `Chunk.lastUnsafe`: Direct word-order rename; it still throws on an empty Chunk. + +### `effect/Clock` + +- `Clock.CancelToken` -> `none`: The public clock scheduler and cancellation-token protocol were removed. Use Effect.sleep for delays and Effect interruption or Fiber.interrupt for cancellation. + +- `Clock.Clock` -> `Clock.Clock`: The service interface remains, but unsafeCurrentTimeMillis and unsafeCurrentTimeNanos were renamed to currentTimeMillisUnsafe and currentTimeNanosUnsafe, the public type-id field was removed, and custom implementations must add monotonicTimeNanosUnsafe plus monotonicTimeNanos for elapsed-time measurement. + +- `Clock.ClockScheduler` -> `none`: The low-level clock scheduler is no longer public. Express scheduling with Effect.sleep and cancel the running fiber through normal Effect interruption. + +- `Clock.ClockTypeId` -> `none`: The Clock type-id is private in v4. Use the Clock.Clock Context.Reference to access, provide, or identify the clock service. + +- `Clock.Task` -> `none`: The low-level clock task alias was removed with ClockScheduler. Model delayed work as an Effect and run or fork it after Effect.sleep. + +- `Clock.make` -> `Layer.succeed(Clock.Clock, clock)`: The Clock constructor was removed. Implement the v4 Clock interface as a plain service value and provide it through Clock.Clock. + +### `effect/Config` + +- `Config.Config` -> `Config.Config`: The model remains a yieldable Effect and exposes parse(provider). Compose logical lookup paths with Config.schema(..., path) and Config.nested; parsing no longer accepts a public path prefix. + +- `Config.Config.IsPlainObject` -> `none`: This private conditional helper is no longer exposed; use Config.Wrap for the public recursive wrapping contract. + +- `Config.Config.Primitive` -> `Schema.Constraint`: Primitive descriptions and parsers were replaced by Schema codecs consumed through Config.schema. + +- `Config.Config.Variance` -> `none`: Config now carries its result type directly through Effect and has no public variance interface. + +- `Config.ConfigTypeId` -> `Config.isConfig`: The Config marker is private in v4; use the public guard for runtime narrowing. + +- `Config.LiteralValue` -> `SchemaAST.LiteralValue`: Use the literal value type shared by v4 Schema constructors. + +- `Config.all` -> `Config.all`: Combine an iterable or record of Config values. A wholly absent product can use Config.withDefault or Config.option, while a partially supplied product fails. + +- `Config.array` -> `Config.Array(valueSchema, path)`: Array parsing is schema-based in v4; rebuild the element Config as a Schema and pass it with the optional path to Config.Array. + +- `Config.boolean` -> `Config.Boolean`: Direct constructor rename. + +- `Config.branded` -> `Config.schema(schema.pipe(Schema.brand(brand)), path)`: Brand validation moved to Schema; define the branded schema and construct the Config with Config.schema. + +- `Config.chunk` -> `Config.schema(Schema.Chunk(valueSchema), path)`: Collection parsing is schema-based in v4; use Schema.Chunk when a Chunk result is still required. + +- `Config.date` -> `Config.Date`: Direct constructor rename. + +- `Config.duration` -> `Config.Duration`: Direct constructor rename. + +- `Config.fail` -> `Config.fail`: The v4 constructor takes a ConfigProvider.SourceError or Schema.SchemaError instead of a message; wrap the failure in the appropriate cause. + +- `Config.hashMap` -> `Config.schema(Schema.HashMap(Schema.String, valueSchema), path)`: HashMap parsing is schema-based in v4; replace the child Config with its value Schema. + +- `Config.hashSet` -> `Config.schema(Schema.HashSet(valueSchema), path)`: HashSet parsing is schema-based in v4; replace the child Config with its value Schema. + +- `Config.integer` -> `Config.Int`: Renamed to the shorter v4 integer constructor using the PascalCase constructor convention. + +- `Config.literal` -> `Config.Literals(literals, path)`: The v3 curried variadic constructor became Config.Literals with an array and inline path; use Config.Literal for one value. + +- `Config.logLevel` -> `Config.LogLevel`: Direct constructor rename. + +- `Config.mapAttempt` -> `Config.mapEffect`: Catch exceptions explicitly and return an Effect failure containing Config.ConfigError; mapEffect is Effect-based in v4. + +- `Config.mapOrFail` -> `Config.mapEffect`: Renamed to match the effectful mapping convention used throughout the library. + +- `Config.nonEmptyString` -> `Config.NonEmptyString`: Direct constructor rename. + +- `Config.number` -> `Config.Number`: Direct constructor rename; use Config.Finite when NaN and infinities must be rejected. + +- `Config.orElseIf` -> `Config.orElse`: The fallback now receives Config.ConfigError; test it in the callback and re-fail with Config.fail(error.cause) when the predicate is false. + +- `Config.port` -> `Config.Port`: Direct constructor rename. + +- `Config.primitive` -> `Config.schema(customSchema, path)`: Custom primitive parsing moved to Schema codecs; express decoding and diagnostics in a Schema, then pass it to Config.schema. Its canonical StringTree encoding must expose a concrete shape; opaque encodings such as Schema.Any or Schema.Unknown are not supported. + +- `Config.redacted` -> `Config.Redacted`: The string/path overload remains; replace the v3 Config argument overload with Config.map(config, Redacted.make). + +- `Config.repeat` -> `Config.Array(valueSchema, path)`: Repeated values use the Config.Array constructor, which accepts structural arrays and flat separated input. + +- `Config.secret` -> `Config.Redacted`: Secret was removed in favor of Redacted; this constructor returns Redacted\. + +- `Config.string` -> `Config.String`: Direct constructor rename. + +- `Config.succeed` -> `Config.succeed`: Unchanged. + +- `Config.suspend` -> `Config.schema(Schema.suspend(schemaThunk), path)`: General Config suspension was removed; model recursive parsing with a suspended Schema before constructing the Config. + +- `Config.sync` -> `Config.succeed(undefined).pipe(Config.map(() => thunk()))`: The dedicated lazy constant constructor was removed; mapping a constant Config preserves evaluation at parse time. + +- `Config.url` -> `Config.URL`: Direct constructor rename. + +- `Config.validate` -> `Config.schema(schema.check(check), path)`: Validation moved to Schema checks; attach the predicate and message to the Schema used by Config.schema. + +- `Config.withDescription` -> `Config.schema(schema.annotate({ description }), path)`: Config descriptions moved to Schema annotations in v4. + +- `Config.zip` -> `Config.all([self, that])`: Use the tuple overload of Config.all. + +- `Config.zipWith` -> `Config.all([self, that]).pipe(Config.map(([a, b]) => f(a, b)))`: Combine both configs with Config.all, then map the tuple. + +### `effect/ConfigError` + +- `ConfigError.And` -> `SchemaIssue.Composite`: The ConfigError boolean ADT was removed; combined schema failures are represented inside Config.ConfigError.cause as SchemaIssue.Composite. + +- `ConfigError.ConfigError` -> `Config.ConfigError`: Config errors are now a class in effect/Config wrapping either ConfigProvider.SourceError or Schema.SchemaError. + +- `ConfigError.ConfigError.Proto` -> `Config.ConfigError`: The public prototype interface was removed; use the Config.ConfigError class. + +- `ConfigError.ConfigError.Reducer` -> `none`: The ConfigError-specific reducer API was removed; inspect ConfigError.cause and recurse over SchemaError.issue when structured handling is required. + +- `ConfigError.ConfigErrorReducer` -> `none`: The ConfigError-specific reducer API was removed; inspect ConfigError.cause and recurse over SchemaError.issue when structured handling is required. + +- `ConfigError.ConfigErrorTypeId` -> `error instanceof Config.ConfigError`: The marker is gone because ConfigError is a class in v4. + +- `ConfigError.InvalidData` -> `new Config.ConfigError(new Schema.SchemaError(issue))`: Invalid configuration is now expressed as a SchemaIssue wrapped by SchemaError and Config.ConfigError. + +- `ConfigError.MissingData` -> `none`: There is no public missing-data error variant. A required absent config ultimately fails with a SchemaError, while Config.withDefault and Config.option handle semantic absence before it enters the public Effect error channel. + +- `ConfigError.Options` -> `none`: The shared constructor options type was removed; ConfigProvider.SourceError accepts message and optional cause, while Schema issues have issue-specific constructors. + +- `ConfigError.Or` -> `SchemaIssue.AnyOf`: The ConfigError boolean ADT was removed; alternative schema failures are represented inside Config.ConfigError.cause as SchemaIssue.AnyOf. + +- `ConfigError.SourceUnavailable` -> `new ConfigProvider.SourceError({ message, cause })`: Source failures moved to effect/ConfigProvider and are wrapped by Config.ConfigError when a Config is parsed. + +- `ConfigError.Unsupported` -> `none`: The variant was removed; use a SchemaError for unsupported input or ConfigProvider.SourceError for source capability failures. + +- `ConfigError.isAnd` -> `error.cause.issue._tag === "Composite"`: After narrowing cause with Schema.isSchemaError, inspect the SchemaIssue tag; the old And node no longer exists. + +- `ConfigError.isConfigError` -> `error instanceof Config.ConfigError`: ConfigError is a class in v4. + +- `ConfigError.isInvalidData` -> `Schema.isSchemaError(error.cause)`: Parsing and validation failures are SchemaError causes; inspect the contained SchemaIssue for finer classification. + +- `ConfigError.isMissingData` -> `none`: Do not infer semantic absence from a SchemaIssue. Use Config.withDefault or Config.option; they distinguish absent provider input from successful undefined, invalid input, and partial products. + +- `ConfigError.isMissingDataOnly` -> `Config.withDefault / Config.option`: The public classifier was removed. These combinators use provider lookup evidence rather than recursively classifying SchemaIssue values. + +- `ConfigError.isOr` -> `error.cause.issue._tag === "AnyOf"`: After narrowing cause with Schema.isSchemaError, inspect the SchemaIssue tag; the old Or node no longer exists. + +- `ConfigError.isSourceUnavailable` -> `error.cause instanceof ConfigProvider.SourceError`: Provider source failures now use the ConfigProvider.SourceError class. + +- `ConfigError.isUnsupported` -> `none`: The Unsupported variant was removed; report unsupported custom decoding through a SchemaError or source failures through ConfigProvider.SourceError. + +- `ConfigError.prefixed` -> `SchemaIssue.Pointer`: Represent path context by wrapping the underlying SchemaIssue in a Pointer before constructing SchemaError. + +- `ConfigError.reduceWithContext` -> `none`: The specialized fold was removed; branch on ConfigError.cause, then recurse over the public SchemaIssue union if a fold is needed. + +### `effect/ConfigProvider` + +- `ConfigProvider.ConfigProvider` -> `ConfigProvider.ConfigProvider`: The model remains but now exposes `load(path)`, returning `Effect`, and `mapInput(f)` for provider-owned path transformation. `undefined` means the path is missing; a `Node` means it exists. + +- `ConfigProvider.ConfigProvider.Flat` -> `ConfigProvider.ConfigProvider`: Flat providers were removed; implement the unified path-based provider with ConfigProvider.make. + +- `ConfigProvider.ConfigProvider.FromEnvConfig` -> `Parameters[0]`: Options are inline in v4 and contain env plus preserveEmptyStrings; custom path delimiters moved to provider path transforms, while separated sequences and records use Config.Array and Config.Record. + +- `ConfigProvider.ConfigProvider.FromMapConfig` -> `none`: fromMap and its delimiter options were removed; expand delimited keys into a nested value and use ConfigProvider.fromUnknown. + +- `ConfigProvider.ConfigProvider.KeyComponent` -> `ConfigProvider.Path[number]`: Tagged key components became plain string or number path segments. + +- `ConfigProvider.ConfigProvider.KeyIndex` -> `number`: Tagged KeyIndex values became numeric ConfigProvider.Path segments. + +- `ConfigProvider.ConfigProvider.KeyName` -> `string`: Tagged KeyName values became string ConfigProvider.Path segments. + +- `ConfigProvider.ConfigProvider.Proto` -> `ConfigProvider.ConfigProvider`: The public marker prototype was removed; use the provider interface itself. + +- `ConfigProvider.ConfigProviderTypeId` -> `ConfigProvider.ConfigProvider`: The runtime marker is private in v4; providers are created by public constructors and consumed structurally. + +- `ConfigProvider.FlatConfigProviderTypeId` -> `none`: The flat-provider abstraction and marker were removed. + +- `ConfigProvider.fromEnv` -> `ConfigProvider.fromEnv`: The constructor remains; pass env and preserveEmptyStrings options. Paths use underscore semantics, while sequence separators belong on Config schemas. + +- `ConfigProvider.fromFlat` -> `ConfigProvider.make`: Flat providers were unified with ConfigProvider; return a `Value`, `Record`, or `Array` node for a found path, or `undefined` when missing. + +- `ConfigProvider.fromJson` -> `ConfigProvider.fromUnknown`: Renamed to reflect support for any in-memory JavaScript value. + +- `ConfigProvider.fromMap` -> `ConfigProvider.fromUnknown`: Expand the map's delimited keys into a nested object first; v4 removed fromMap and its pathDelim/seqDelim options. + +- `ConfigProvider.kebabCase` -> `ConfigProvider.mapInput((path) => path.map((part) => typeof part === "string" ? String.kebabCase(part) : part))`: Named recasing helpers were removed except constantCase; transform string path segments explicitly. + +- `ConfigProvider.lowerCase` -> `ConfigProvider.mapInput((path) => path.map((part) => typeof part === "string" ? part.toLowerCase() : part))`: Transform string path segments explicitly with mapInput. + +- `ConfigProvider.make` -> `ConfigProvider.make`: The constructor now takes a path lookup returning `Effect`, rather than a full Config loader and flattened provider. Return `undefined` for a missing path and a `Node` for a found path. + +- `ConfigProvider.makeFlat` -> `ConfigProvider.make`: The flat-provider constructor was removed; return a `Value`, `Record`, or `Array` node for a found path, or `undefined` when missing. + +- `ConfigProvider.mapInputPath` -> `ConfigProvider.mapInput`: Renamed and generalized: the callback receives and returns the complete Path, including numeric array indexes. + +- `ConfigProvider.snakeCase` -> `ConfigProvider.mapInput((path) => path.map((part) => typeof part === "string" ? String.snakeCase(part) : part))`: Named recasing helpers were removed except constantCase; transform string path segments explicitly. + +- `ConfigProvider.unnested` -> `ConfigProvider.mapInput((path) => path[0] === name ? path.slice(1) : path)`: The named helper was removed; strip the matching leading segment explicitly. Add custom handling if the v3 mismatch error was significant. + +- `ConfigProvider.upperCase` -> `ConfigProvider.mapInput((path) => path.map((part) => typeof part === "string" ? part.toUpperCase() : part))`: Transform string path segments explicitly with mapInput. + +- `ConfigProvider.within` -> `ConfigProvider.orElse + ConfigProvider.mapInput`: The scoped transform helper was removed; build a provider that transforms paths below the prefix and falls back to the original provider elsewhere. + +### `effect/ConfigProviderPathPatch` + +- `ConfigProviderPathPatch.AndThen` -> `ConfigProvider.mapInput`: PathPatch is no longer public; compose path transformations as ordinary functions passed to mapInput. + +- `ConfigProviderPathPatch.Empty` -> `none`: The PathPatch ADT was removed; an unchanged provider represents the identity transformation. + +- `ConfigProviderPathPatch.MapName` -> `none`: The PathPatch ADT was removed; use a path transformation function with ConfigProvider.mapInput. + +- `ConfigProviderPathPatch.Nested` -> `none`: The PathPatch ADT was removed; use ConfigProvider.nested on the provider. + +- `ConfigProviderPathPatch.PathPatch` -> `(path: ConfigProvider.Path) => ConfigProvider.Path`: Path patches are ordinary full-path transformations in v4 and are installed with ConfigProvider.mapInput. + +- `ConfigProviderPathPatch.Unnested` -> `none`: The PathPatch ADT was removed; express prefix removal as a ConfigProvider.mapInput function. + +- `ConfigProviderPathPatch.empty` -> `ConfigProvider.ConfigProvider`: No identity patch value is needed; leave the provider untransformed. + +- `ConfigProviderPathPatch.mapName` -> `ConfigProvider.mapInput`: Map the string segments of the full ConfigProvider.Path explicitly. + +- `ConfigProviderPathPatch.nested` -> `ConfigProvider.nested`: Apply nesting directly to the provider instead of constructing a patch. + +- `ConfigProviderPathPatch.unnested` -> `ConfigProvider.mapInput`: Strip the expected leading path segment in a mapInput callback; v4 has no named unnested helper. + +### `effect/Console` + +- `Console.Console` -> `Console.Console`: Name retained, but v4 is a Context.Reference whose service methods are synchronous. Rewrite custom implementations from effectful methods plus .unsafe to direct console methods; module accessors such as Console.log still return Effect values. + +- `Console.TypeId` -> `none`: The public console brand was removed; v4 Console.Console is structural. + +- `Console.UnsafeConsole` -> `Console.Console`: The v4 service interface is the old unsafe/direct interface; .unsafe no longer exists. + +- `Console.setConsole` -> `Layer.succeed(Console.Console, console)`: Provide the v4 console reference as a layer. + +- `Console.withConsole` -> `Effect.provideService(effect, Console.Console, console)`: Console overrides now use the reference/service provider pattern. + +- `Console.withGroup` -> `Console.withGroup`: The API and data-first/data-last behavior remain. + +- `Console.withTime` -> `Console.withTime`: The API and data-first/data-last behavior remain. + +### `effect/Context` + +- `Context.Context` -> `Context.Context`: The type remains; unsafeMap is now mapUnsafe and v4 also exposes mutable. + +- `Context.GenericTag` -> `Context.Service(id)`: Use the function-style Context.Service constructor. + +- `Context.ReadonlyTag` -> `Context.Key`: Use the renamed service-key interface. + +- `Context.Reference` -> `Context.Reference`: Use Context.Reference\(id, { defaultValue }); the identifier type parameter was removed. + +- `Context.ReferenceClass` -> `Context.Reference(id, { defaultValue })`: Replace reference subclasses with a constant created by Context.Reference. + +- `Context.ReferenceTypeId` -> `none`: The marker is private in v4; use Context.isReference for runtime discrimination. + +- `Context.Tag` -> `Context.Service`: Use Context.Service\(id), or Context.Service\()(id) for class syntax. + +- `Context.Tag.Service` -> `Context.Service.Shape`: The namespace type helper was renamed with Tag. + +- `Context.TagClass` -> `Context.ServiceClass`: Use the renamed class-style service-key type. + +- `Context.TagClassShape` -> `Context.ServiceClass.Shape`: Use the renamed namespace type helper. + +- `Context.TagTypeId` -> `Context.ServiceTypeId`: The public type identifier was renamed with Tag. + +- `Context.TagUnify` -> `none`: The Context-specific unification hook was removed; Context.Key already extends Effect. + +- `Context.TagUnifyIgnore` -> `none`: The Context-specific Unify-ignore artifact was removed. + +- `Context.TypeId` -> `none`: The Context marker is private in v4; use Context.isContext for runtime checks. + +- `Context.ValidTagsById` -> `(key: Context.Key)`: The alias was removed; express the Context.Key constraint directly. + +- `Context.isTag` -> `Context.isKey`: The service-key guard was renamed. + +- `Context.unsafeGet` -> `Context.getUnsafe`: The unsafe getter was renamed. + +- `Context.unsafeMake` -> `Context.makeUnsafe`: The unsafe constructor was renamed and accepts a ReadonlyMap. + +### `effect/Cron` + +- `Cron.Cron` -> `Cron.Cron`: The model remains; update for the v4 representation and private type id. + +- `Cron.ParseError` -> `Cron.CronParseError`: The parse error was renamed and Cron.parse now returns Result.Result\. + +- `Cron.ParseErrorTypeId` -> `none`: The cron parse-error type id is private in v4. Use Cron.isCronParseError to narrow unknown failures. + +- `Cron.TypeId` -> `none`: The Cron type id is private in v4. Use Cron.isCron to identify cron values. + +- `Cron.isParseError` -> `Cron.isCronParseError`: The parse-error guard was renamed with the error type. + +- `Cron.sequenceReverse` -> `Cron.prev`: The reverse iterator was removed. Build an iterator that repeatedly calls Cron.prev, feeding each returned Date into the next call. + +- `Cron.unsafeParse` -> `Cron.parseUnsafe`: The throwing parser was renamed; it also accepts an optional time zone. + +### `effect/Data` + +- `Data.Case` -> `Data.TaggedEnum.ConstructorFrom`: The Case namespace was removed; its constructor helper moved under TaggedEnum. + +- `Data.Case.Constructor` -> `Data.TaggedEnum.ConstructorFrom`: Use the v4 tagged-enum constructor-function type. + +- `Data.Structural` -> `Data.Class`: Extend Data.Class instead of the removed Structural constructor alias. + +- `Data.TaggedEnum` -> `Data.TaggedEnum`: Still exported with the same record-to-discriminated-union role. + +- `Data.TaggedEnum.GenericMatchers` -> `Data.TaggedEnum.GenericMatchers`: Still exported with $is and $match helpers. + +- `Data.array` -> `none`: Use a normal copied array such as [...values]; v4 compares plain arrays structurally. + +- `Data.case` -> `none`: Use an ordinary typed object or identity constructor; plain objects are structurally equal in v4. + +- `Data.struct` -> `none`: Use an ordinary object or {...value}; plain objects are structurally equal in v4. + +- `Data.tuple` -> `none`: Use a normal tuple literal; plain tuples are structurally equal in v4. + +- `Data.unsafeArray` -> `none`: Use the array directly; v4 no longer needs prototype mutation for structural equality. + +- `Data.unsafeStruct` -> `none`: Use the object directly; v4 no longer needs prototype mutation for structural equality. + +### `effect/DateTime` + +- `DateTime.DateTime` -> `DateTime.DateTime`: The Utc | Zoned model remains; epochMillis fields are now epochMilliseconds and unit/part names use millisecond terminology. + +- `DateTime.DateTime.Input` -> `DateTime.DateTime.Input`: The input type remains and additionally accepts Instant and InstantWithZone objects. + +- `DateTime.DateTime.Parts` -> `DateTime.DateTime.Parts`: Rename millis, seconds, minutes, and hours fields to millisecond, second, minute, and hour. + +- `DateTime.DateTime.PartsForMath` -> `DateTime.DateTime.PartsForMath`: Rename the millis field to milliseconds; the other plural arithmetic fields remain. + +- `DateTime.DateTime.PartsWithWeekday` -> `DateTime.DateTime.PartsWithWeekday`: Rename millis, seconds, minutes, and hours fields to millisecond, second, minute, and hour. + +- `DateTime.DateTime.Proto` -> `DateTime.DateTime.Proto`: The protocol remains, but its marker uses the private v4 TypeId value. + +- `DateTime.DateTime.UnitPlural` -> `DateTime.DateTime.UnitPlural`: Use milliseconds instead of millis; the other plural unit strings remain. + +- `DateTime.DateTime.UnitSingular` -> `DateTime.DateTime.UnitSingular`: Use millisecond instead of milli; the other singular unit strings remain. + +- `DateTime.TimeZone` -> `DateTime.TimeZone`: The Offset | Named model remains; its public type-id marker type was removed. + +- `DateTime.TimeZone.Proto` -> `DateTime.TimeZone.Proto`: The protocol remains, but its marker uses the private v4 TimeZoneTypeId value. + +- `DateTime.TimeZoneTypeId` -> `none`: The time-zone type id is private in v4. Use DateTime.isTimeZone, isTimeZoneOffset, or isTimeZoneNamed. + +- `DateTime.TypeId` -> `none`: The DateTime type id is private in v4. Use DateTime.isDateTime, isUtc, or isZoned. + +- `DateTime.Utc` -> `DateTime.Utc`: The model remains; rename epochMillis to epochMilliseconds. + +- `DateTime.Zoned` -> `DateTime.Zoned`: The model remains; rename epochMillis and adjustedEpochMillis to epochMilliseconds and adjustedEpochMilliseconds. + +- `DateTime.distanceDuration` -> `Duration.millis(Math.abs(DateTime.distance(self, other)))`: DateTime.distance returns signed milliseconds in v4; take the absolute value and construct a Duration to preserve v3 behavior. + +- `DateTime.distanceDurationEither` -> `DateTime.distance + Result`: Compute the signed millisecond distance, wrap its absolute Duration as Result.succeed when positive and Result.fail when non-positive; v4 uses Result instead of Either. + +- `DateTime.greaterThan` -> `DateTime.isGreaterThan`: The comparison was renamed with the is prefix. + +- `DateTime.greaterThanOrEqualTo` -> `DateTime.isGreaterThanOrEqualTo`: The comparison was renamed with the is prefix. + +- `DateTime.lessThan` -> `DateTime.isLessThan`: The comparison was renamed with the is prefix. + +- `DateTime.lessThanOrEqualTo` -> `DateTime.isLessThanOrEqualTo`: The comparison was renamed with the is prefix. + +- `DateTime.unsafeFromDate` -> `DateTime.fromDateUnsafe`: The unsafe suffix moved to the end of the constructor name. + +- `DateTime.unsafeIsFuture` -> `DateTime.isFutureUnsafe`: The unsafe suffix moved to the end of the predicate name. + +- `DateTime.unsafeIsPast` -> `DateTime.isPastUnsafe`: The unsafe suffix moved to the end of the predicate name. + +- `DateTime.unsafeMake` -> `DateTime.makeUnsafe`: The unsafe suffix moved to the end of the constructor name. + +- `DateTime.unsafeMakeZoned` -> `DateTime.makeZonedUnsafe`: The unsafe suffix moved to the end of the constructor name. + +- `DateTime.unsafeNow` -> `DateTime.nowUnsafe`: The unsafe suffix moved to the end of the accessor name. + +- `DateTime.unsafeSetZoneNamed` -> `DateTime.setZoneNamedUnsafe`: The unsafe suffix moved to the end of the zone setter name. + +- `DateTime.zoneUnsafeMakeNamed` -> `DateTime.zoneMakeNamedUnsafe`: The unsafe suffix moved to the end of the named-zone constructor. + +### `effect/DefaultServices` + +- `DefaultServices.DefaultServices` -> `none`: The aggregate type and module were removed; Clock, Console, Random, ConfigProvider, and Tracer are independent defaulted references. + +- `DefaultServices.currentServices` -> `Effect.context() and Context.get(context, reference)`: The aggregate FiberRef was removed; access and override default Context.Reference services individually. + +- `DefaultServices.liveServices` -> `Context.empty() with individual Context.Reference defaults`: There is no live-services bundle; each default service reference supplies its own live default. + +### `effect/Deferred` + +- `Deferred.Deferred` -> `Deferred.Deferred`: The model remains but is now Pipeable rather than an Effect subtype; replace yielding the Deferred itself with Deferred.await. + +- `Deferred.Deferred.Variance` -> `Deferred.Deferred.Variance`: The marker remains under Deferred.Deferred, but its brand uses an internal type id; ordinary code should use Deferred.Deferred directly. + +- `Deferred.DeferredTypeId` -> `none`: The Deferred type id is internal in v4; do not inspect or construct the brand directly. + +- `Deferred.DeferredUnify` -> `none`: Deferred is no longer an Effect subtype, so its Effect unification helper was removed; call Deferred.await explicitly. + +- `Deferred.DeferredUnifyIgnore` -> `none`: Deferred is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +- `Deferred.await` -> `Deferred.await`: The function remains; call it explicitly because Deferred is no longer an Effect subtype in v4. + +- `Deferred.makeAs` -> `Deferred.makeUnsafe`: Use the synchronous v4 constructor; it no longer accepts or records a FiberId. + +- `Deferred.poll` -> `Deferred.poll`: The function remains and returns an Option containing the stored completion Effect. + +- `Deferred.unsafeDone` -> `Deferred.doneUnsafe`: The unsafe suffix moved to the end; the v4 function returns whether this call completed the Deferred. + +- `Deferred.unsafeMake` -> `Deferred.makeUnsafe`: The unsafe suffix moved to the end, and the v4 constructor takes no FiberId argument. + +### `effect/Differ` + +- `Differ.Differ` -> `Differ.Differ`: The interface remains, but is now an unbranded structural interface and patch takes arguments as patch(oldValue, patch). + +- `Differ.Differ.Chunk` -> `JsonPatch.JsonPatch`: The Chunk patch namespace was removed; Schema.toDifferJsonPatch uses the common RFC 6902 patch representation. + +- `Differ.Differ.Chunk.Patch` -> `JsonPatch.JsonPatch`: Use the patch type returned by Schema.toDifferJsonPatch instead of the removed Chunk-specific patch type. + +- `Differ.Differ.Chunk.TypeId` -> `none`: Chunk-specific patches and their public brand were removed; do not inspect a patch type id. + +- `Differ.Differ.Context` -> `Context.Context`: The Context patch namespace was removed; construct and merge Context values explicitly rather than diffing environments. + +- `Differ.Differ.Context.Patch` -> `none`: Context patches were removed; use Context.add, Context.merge, and Context.omit to build the desired Context directly. + +- `Differ.Differ.Context.TypeId` -> `none`: Context patches and their public brand were removed. + +- `Differ.Differ.HashMap` -> `JsonPatch.JsonPatch`: The HashMap patch namespace was removed; derive a JSON Patch differ from a Schema for the complete value. + +- `Differ.Differ.HashMap.Patch` -> `JsonPatch.JsonPatch`: Use the patch type returned by Schema.toDifferJsonPatch instead of the removed HashMap-specific patch type. + +- `Differ.Differ.HashMap.TypeId` -> `none`: HashMap-specific patches and their public brand were removed; do not inspect a patch type id. + +- `Differ.Differ.HashSet.Patch` -> `JsonPatch.JsonPatch`: Use the patch type returned by Schema.toDifferJsonPatch instead of the removed HashSet-specific patch type. + +- `Differ.Differ.HashSet.TypeId` -> `none`: HashSet-specific patches and their public brand were removed; do not inspect a patch type id. + +- `Differ.Differ.Or` -> `JsonPatch.JsonPatch`: The Either patch namespace was removed; derive one JSON Patch differ from the Schema for the union value. + +- `Differ.Differ.Or.Patch` -> `JsonPatch.JsonPatch`: Use the patch type returned by Schema.toDifferJsonPatch instead of the removed Either-specific patch type. + +- `Differ.Differ.Or.TypeId` -> `none`: Either-specific patches and their public brand were removed; do not inspect a patch type id. + +- `Differ.Differ.ReadonlyArray` -> `JsonPatch.JsonPatch`: The ReadonlyArray patch namespace was removed; Schema.toDifferJsonPatch uses the common RFC 6902 patch representation. + +- `Differ.Differ.ReadonlyArray.Patch` -> `JsonPatch.JsonPatch`: Use the patch type returned by Schema.toDifferJsonPatch instead of the removed ReadonlyArray-specific patch type. + +- `Differ.Differ.ReadonlyArray.TypeId` -> `none`: ReadonlyArray-specific patches and their public brand were removed; do not inspect a patch type id. + +- `Differ.TypeId` -> `none`: Differ is an unbranded structural interface in v4; do not inspect or implement a public type id. + +- `Differ.chunk` -> `Schema.toDifferJsonPatch`: Derive a JSON Patch differ from a Schema for the whole Chunk; v4 no longer exposes collection-specific patch constructors. + +- `Differ.combine` -> `differ.combine`: Call the combine method on the Differ value directly; the standalone helper was removed. + +- `Differ.diff` -> `differ.diff`: Call the diff method on the Differ value directly; the standalone helper was removed. + +- `Differ.empty` -> `differ.empty`: Read the empty property from the Differ value directly; the standalone accessor was removed. + +- `Differ.environment` -> `none`: The Context differ was removed; construct the target Context explicitly with Context.add, Context.merge, and Context.omit. + +- `Differ.hashMap` -> `Schema.toDifferJsonPatch`: Derive a JSON Patch differ from a Schema for the whole map; v4 no longer exposes collection-specific patch constructors. + +- `Differ.hashSet` -> `Schema.toDifferJsonPatch`: Derive a JSON Patch differ from a Schema for the whole set; v4 no longer exposes collection-specific patch constructors. + +- `Differ.make` -> `object literal satisfying Differ.Differ`: Differ is structural in v4; provide empty, diff, combine, and patch methods directly, with patch(oldValue, patch) argument order. + +- `Differ.orElseEither` -> `Schema.toDifferJsonPatch`: Derive one differ from the Schema for the Either value; the compositional Either-specific differ and patch type were removed. + +- `Differ.patch` -> `differ.patch`: Call the method directly and reverse the v3 method order: differ.patch(oldValue, patch). + +- `Differ.readonlyArray` -> `Schema.toDifferJsonPatch`: Derive a JSON Patch differ from a Schema for the whole array; v4 no longer exposes collection-specific patch constructors. + +- `Differ.transform` -> `object literal satisfying Differ.Differ`: There is no transform combinator; define a structural Differ that maps values before delegating to the original differ. + +- `Differ.update` -> `object literal satisfying Differ.Differ`: The update constructor was removed; define empty, diff, combine, and patch directly for function patches, or use Schema.toDifferJsonPatch. + +- `Differ.updateWith` -> `object literal satisfying Differ.Differ`: The updateWith constructor was removed; encode the desired merge rule in a structural Differ implementation. + +### `effect/Duration` + +- `Duration.Duration` -> `Duration.Duration`: The model remains and now also supports negative infinity; its type-id value is private. + +- `Duration.DurationInput` -> `Duration.Input`: The input type was renamed and expanded with negative values and Temporal.Duration-like objects. + +- `Duration.DurationValue` -> `Duration.DurationValue`: The tagged value remains and adds NegativeInfinity; its object fields are no longer readonly. + +- `Duration.TypeId` -> `none`: The Duration type id is private in v4. Use Duration.isDuration to narrow unknown values. + +- `Duration.decode` -> `Duration.fromInputUnsafe`: The throwing DurationInput decoder was renamed. + +- `Duration.decodeUnknown` -> `Duration.fromInput`: The safe decoder was renamed and accepts Duration.Input, returning Option\. + +- `Duration.formatIso` -> `none`: ISO 8601 duration formatting was removed from the v4 Duration module. The v4 source and migration guides expose no direct replacement; retain a local formatter when this wire format is required. + +- `Duration.fromIso` -> `none`: ISO 8601 duration parsing was removed from the v4 Duration module. The v4 source and migration guides expose no direct replacement; use a dedicated ISO parser and pass the resulting parts to Duration.fromInput. + +- `Duration.greaterThan` -> `Duration.isGreaterThan`: The comparison was renamed with the is prefix. + +- `Duration.greaterThanOrEqualTo` -> `Duration.isGreaterThanOrEqualTo`: The comparison was renamed with the is prefix. + +- `Duration.lessThan` -> `Duration.isLessThan`: The comparison was renamed with the is prefix. + +- `Duration.lessThanOrEqualTo` -> `Duration.isLessThanOrEqualTo`: The comparison was renamed with the is prefix. + +- `Duration.matchWith` -> `Duration.matchPair`: The two-duration matcher was renamed. + +- `Duration.unsafeDivide` -> `Duration.divideUnsafe`: The unsafe prefix moved to the end of the division function name. + +- `Duration.unsafeFormatIso` -> `none`: ISO 8601 duration formatting was removed from v4. The v4 Duration exports and migration guides contain no direct unsafe formatter; retain a local formatter if required. + +- `Duration.unsafeToNanos` -> `Duration.toNanosUnsafe`: The unsafe prefix moved to the end of the nanosecond conversion name. + +### `effect/Effect` + +- `Effect.Adapter` -> `none`: The generator adapter type was removed; yield Effect values directly inside `Effect.gen`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.All` -> `Effect.All`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.All.ExtractMode` -> `Effect.All.Return`: The `either` extraction helper was removed; use `mode: "result"` and the v4 return helper. Adapt arguments and imports to the v4 API. + +- `Effect.Blocked` -> `none`: The request-runtime blocked model is internal; use public `Request` and `RequestResolver` APIs. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.Effect` -> `Effect.Effect`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.Effect.AsEffect` -> `Effect.Effect`: Use the Effect type directly and extract channels with `Effect.Success`, `Effect.Error`, and `Effect.Services`. Adapt arguments and imports to the v4 API. + +- `Effect.Effect.Context` -> `Effect.Services`: Use the renamed type-level extractor for required services. Adapt arguments and imports to the v4 API. + +- `Effect.Effect.VarianceStruct` -> `Effect.Variance`: Use the v4 variance interface. Adapt arguments and imports to the v4 API. + +- `Effect.EffectGenerator` -> `Effect.EffectIterator`: Use the v4 iterator type used by generator delegation. Adapt arguments and imports to the v4 API. + +- `Effect.EffectTypeId` -> `Effect.TypeId`: Use the v4 type-level Effect identifier. Adapt arguments and imports to the v4 API. + +- `Effect.EffectUnify` -> `Effect.EffectUnify`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.EffectUnifyIgnore` -> `none`: The internal unification-ignore helper is no longer public; rely on v4 Effect inference. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.FunctionWithSpanOptions` -> `Tracer.SpanOptions`: Use the v4 tracing options type when wrapping functions with `Effect.withSpan`. Adapt arguments and imports to the v4 API. + +- `Effect.LatchUnify` -> `Latch.Latch`: Latch moved to the standalone `effect/Latch` module; rely on normal v4 inference. Adapt arguments and imports to the v4 API. + +- `Effect.LatchUnifyIgnore` -> `none`: The internal Latch unification helper was removed; use `Latch.Latch` directly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.Permit` -> `Semaphore.Semaphore`: Use the standalone Semaphore API and its `withPermit` / `withPermits` methods. Adapt arguments and imports to the v4 API. + +- `Effect.Repeat` -> `Effect.Repeat`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.Repeat.Options` -> `Effect.Repeat.Options`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.Retry` -> `Effect.Retry`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.Retry.Options` -> `Effect.Retry.Options`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.Service` -> `Context.Service`: Replace the `effect` constructor option with `make`. V4 does not generate a `Default` layer or wire `dependencies`; define a `Layer.effect` and provide its dependencies explicitly. + +- `Effect.Service.AllowedType` -> `Context.Service`: Service type machinery moved to `Context.Service`; do not reference its internal helper types. Adapt arguments and imports to the v4 API. + +- `Effect.Service.Class` -> `Context.Service`: Service classes are now defined with `Context.Service`. Adapt arguments and imports to the v4 API. + +- `Effect.Service.HasArguments` -> `Context.Service`: Service constructor typing is handled by `Context.Service`. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeAccessors` -> `Context.Service`: Use the generated `.use` helper instead of v3 accessor type machinery. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeArguments` -> `Context.Service`: Pass a `make` Effect in the v4 `Context.Service` options. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeContext` -> `Context.Service`: Service context typing is inferred by `Context.Service`. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeDeps` -> `Layer.provide`: Compose service dependencies explicitly with Layers. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeDepsE` -> `Layer.Error`: Use Layer channel extractors rather than Service internals. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeDepsIn` -> `Layer.Services`: Use Layer channel extractors rather than Service internals. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeDepsOut` -> `Layer.Success`: Use Layer channel extractors rather than Service internals. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeError` -> `Layer.Error`: Use the Layer error-channel extractor. Adapt arguments and imports to the v4 API. + +- `Effect.Service.MakeService` -> `Context.Service`: The service shape is inferred by `Context.Service`. Adapt arguments and imports to the v4 API. + +- `Effect.Service.ProhibitedType` -> `Context.Service`: Do not reference the removed internal validation type. Adapt arguments and imports to the v4 API. + +- `Effect.Tag` -> `Context.Service`: Define services with `Context.Service`; use the generated `.use` helper for accessors. Adapt arguments and imports to the v4 API. + +- `Effect.Tag.AllowedType` -> `Context.Service`: Tag validation internals were removed; use `Context.Service` directly. Adapt arguments and imports to the v4 API. + +- `Effect.Tag.ProhibitedType` -> `Context.Service`: Tag validation internals were removed; use `Context.Service` directly. Adapt arguments and imports to the v4 API. + +- `Effect.Tag.Proxy` -> `Context.Service.use`: Replace proxy accessors with the generated `.use` helper. Adapt arguments and imports to the v4 API. + +- `Effect.acquireReleaseInterruptible` -> `Effect.acquireRelease`: Pass `{ interruptible: true }` in the options object. Adapt arguments and imports to the v4 API. + +- `Effect.allSuccesses` -> `Effect.all`: Run with `{ mode: "result" }`, then retain `Result.Success` values. Adapt arguments and imports to the v4 API. + +- `Effect.allWith` -> `Effect.all`: Wrap `Effect.all(values, options)` in a lambda when a data-last combinator is needed. Adapt arguments and imports to the v4 API. + +- `Effect.allowInterrupt` -> `Effect.yieldNow`: Yield to the scheduler to create an interruptible checkpoint. Adapt arguments and imports to the v4 API. + +- `Effect.annotateLogs` -> `Effect.annotateLogs`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.ap` -> `Effect.zipWith`: Zip the function effect and value effect, then apply the function in the combiner. Adapt arguments and imports to the v4 API. + +- `Effect.asSomeError` -> `Effect.mapError`: Map errors with `Option.some`. Adapt arguments and imports to the v4 API. + +- `Effect.async` -> `Effect.callback`: Use the renamed callback constructor. Adapt arguments and imports to the v4 API. + +- `Effect.asyncEffect` -> `Effect.callback`: The callback registration may return an Effect cleanup action in v4. Adapt arguments and imports to the v4 API. + +- `Effect.bindAll` -> `Effect.bind + Effect.all`: Bind the result of `Effect.all` explicitly in the do-notation pipeline. Adapt arguments and imports to the v4 API. + +- `Effect.blocked` -> `none`: The request-runtime blocked constructor is internal; express work with `Effect.request` and a `RequestResolver`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.cacheRequestResult` -> `none`: Direct request-cache mutation was removed; configure request resolution through `RequestResolver`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.cachedFunction` -> `none`: The function memoizer was removed; use `Cache` for keyed effectful caching. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.catch` -> `Effect.catch`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.catchAll` -> `Effect.catch`: Use the shortened v4 error-handler name. Adapt arguments and imports to the v4 API. + +- `Effect.catchAllCause` -> `Effect.catchCause`: Use the shortened v4 cause-handler name. Adapt arguments and imports to the v4 API. + +- `Effect.catchAllDefect` -> `Effect.catchDefect`: Use the shortened v4 defect-handler name. Adapt arguments and imports to the v4 API. + +- `Effect.catchSome` -> `Effect.catchFilter`: Replace the Option-returning partial function with a `Filter` and handler. Adapt arguments and imports to the v4 API. + +- `Effect.catchSomeCause` -> `Effect.catchCauseFilter`: Replace the Option-returning partial function with a cause `Filter` and handler. Adapt arguments and imports to the v4 API. + +- `Effect.catchSomeDefect` -> `none`: Use `Effect.catchDefect` and branch explicitly, re-dying for unmatched defects. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.cause` -> `Effect.exit`: Inspect `Exit.Failure.cause`; v4 no longer exposes an Effect-only cause extractor. Adapt arguments and imports to the v4 API. + +- `Effect.checkInterruptible` -> `none`: Interruptibility introspection was removed; structure the region explicitly with `Effect.interruptible` or `Effect.uninterruptible`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.clock` -> `Clock.Clock`: Services are Effects in v4; yield or compose `Clock.Clock` directly. Adapt imports to the v4 API. + +- `Effect.configProviderWith` -> `ConfigProvider.ConfigProvider.use`: Use the ConfigProvider reference's `.use` helper to invoke the effectful callback. Adapt arguments and imports to the v4 API. + +- `Effect.console` -> `Console.Console`: Services are Effects in v4; yield or compose `Console.Console` directly. Adapt imports to the v4 API. + +- `Effect.consoleWith` -> `Console.Console.use`: Use the Console reference's `.use` helper to invoke the effectful callback. Adapt arguments and imports to the v4 API. + +- `Effect.contextWithEffect` -> `Effect.contextWith`: `contextWith` accepts an effectful callback in v4. Adapt arguments and imports to the v4 API. + +- `Effect.currentPropagatedSpan` -> `Effect.currentParentSpan`: Use the current parent span representation. Adapt arguments and imports to the v4 API. + +- `Effect.custom` -> `none`: The low-level custom instruction constructor was removed; use public constructors such as `Effect.sync`, `Effect.suspend`, or `Effect.callback`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.daemonChildren` -> `Effect.awaitAllChildren`: Use structured child-fiber waiting, or fork explicitly with `Effect.forkDetach` when detachment is intended. Adapt arguments and imports to the v4 API. + +- `Effect.descriptor` -> `Effect.fiberId`: The full fiber descriptor was removed; retrieve the current numeric fiber id. Adapt arguments and imports to the v4 API. + +- `Effect.descriptorWith` -> `Effect.fiberId + Effect.flatMap`: Read the current fiber id and invoke the callback explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.dieMessage` -> `Effect.die`: Construct the desired defect explicitly and pass it to `Effect.die`. Adapt arguments and imports to the v4 API. + +- `Effect.dieSync` -> `Effect.suspend + Effect.die`: Evaluate the lazy defect inside `Effect.suspend`. Adapt arguments and imports to the v4 API. + +- `Effect.diffFiberRefs` -> `none`: The public FiberRefs diff API was removed; model fiber-local state with context references and scoped `Effect.provideService`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.disconnect` -> `Effect.forkDetach`: Fork explicitly and decide how to await or interrupt the detached Fiber. Adapt arguments and imports to the v4 API. + +- `Effect.dropUntil` -> `none`: Use an explicit `Effect.gen` loop for an effectful stopping predicate. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.dropWhile` -> `none`: Use an explicit `Effect.gen` loop, or `Array.dropWhile` when the predicate is pure. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.either` -> `Effect.result`: V4 represents typed success/failure as `Result` instead of `Either`. Adapt arguments and imports to the v4 API. + +- `Effect.ensureErrorType` -> `Effect.satisfiesErrorType`: Use the renamed compile-time channel constraint. Adapt arguments and imports to the v4 API. + +- `Effect.ensureRequirementsType` -> `Effect.satisfiesServicesType`: Use the renamed compile-time services constraint. Adapt arguments and imports to the v4 API. + +- `Effect.ensureSuccessType` -> `Effect.satisfiesSuccessType`: Use the renamed compile-time channel constraint. Adapt arguments and imports to the v4 API. + +- `Effect.ensuringChild` -> `Effect.ensuring + Fiber APIs`: Track the child Fiber explicitly and run the finalizer with `Effect.ensuring`. Adapt arguments and imports to the v4 API. + +- `Effect.ensuringChildren` -> `Effect.awaitAllChildren + Effect.ensuring`: Use structured child waiting and an explicit finalizer. Adapt arguments and imports to the v4 API. + +- `Effect.every` -> `Effect.forEach`: Evaluate predicates with `Effect.forEach`, then test the resulting booleans with `Array.every`. Adapt arguments and imports to the v4 API. + +- `Effect.exists` -> `Effect.findFirst`: Find the first value satisfying the effectful predicate and test the returned Option. Adapt arguments and imports to the v4 API. + +- `Effect.fiberIdWith` -> `Effect.fiberId + Effect.flatMap`: Read the numeric fiber id and invoke the callback explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.filterEffectOrElse` -> `Effect.flatMap`: Evaluate the effectful predicate and branch to `Effect.succeed` or the fallback. Adapt arguments and imports to the v4 API. + +- `Effect.filterEffectOrFail` -> `Effect.flatMap`: Evaluate the effectful predicate and branch to `Effect.succeed` or `Effect.fail`. Adapt arguments and imports to the v4 API. + +- `Effect.filterOrDie` -> `Effect.filterOrFail + Effect.orDie`: Filter with a typed failure, then convert it to a defect. Adapt arguments and imports to the v4 API. + +- `Effect.filterOrDieMessage` -> `Effect.filterOrFail + Effect.orDie`: Create the message-bearing error in `filterOrFail`, then convert it to a defect. Adapt arguments and imports to the v4 API. + +- `Effect.finalizersMask` -> `none`: Configurable finalizer execution strategies were removed; register ordered finalizers explicitly in a Scope. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.flipWith` -> `Effect.flip`: Flip, apply the transformation, then flip the resulting Effect back. Adapt arguments and imports to the v4 API. + +- `Effect.fn` -> `Effect.fn`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.fn.Gen` -> `Effect.fn.Return`: Use the v4 generator-return helper type. Adapt arguments and imports to the v4 API. + +- `Effect.fn.NonGen` -> `Effect.fn.Untraced`: Use the v4 function helper type for non-generator wrapping. Adapt arguments and imports to the v4 API. + +- `Effect.fork` -> `Effect.forkChild`: Use the renamed structured child-fiber combinator. Adapt arguments and imports to the v4 API. + +- `Effect.forkAll` -> `Effect.forEach + Effect.forkChild`: Fork each effect explicitly, or prefer a higher-level concurrent combinator. Adapt arguments and imports to the v4 API. + +- `Effect.forkDaemon` -> `Effect.forkDetach`: Use the renamed detached-fiber combinator. Adapt arguments and imports to the v4 API. + +- `Effect.forkWithErrorHandler` -> `Effect.forkChild + Fiber.await`: Fork explicitly and observe the Fiber result to handle errors. Adapt arguments and imports to the v4 API. + +- `Effect.fromFiber` -> `Fiber.join`: Join the Fiber to obtain an Effect of its result. Adapt arguments and imports to the v4 API. + +- `Effect.fromFiberEffect` -> `Effect.flatMap + Fiber.join`: FlatMap the effectful Fiber and join it. Adapt arguments and imports to the v4 API. + +- `Effect.fromNullable` -> `Effect.fromOption + Option.fromNullable`: Convert the nullable value to Option, then lift it into Effect. Adapt arguments and imports to the v4 API. + +- `Effect.functionWithSpan` -> `Effect.withSpan`: Wrap the function body with a span whose name/options are derived from its arguments. Adapt arguments and imports to the v4 API. + +- `Effect.gen` -> `Effect.gen`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.getFiberRefs` -> `none`: The FiberRefs collection is no longer public; access individual context references through Effect services. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.getRuntimeFlags` -> `none`: RuntimeFlags are no longer a public Effect service; use supported high-level runtime options. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.if` -> `Effect.suspend`: Select the branch lazily with a JavaScript conditional inside `Effect.suspend`. Adapt arguments and imports to the v4 API. + +- `Effect.ignoreLogged` -> `Effect.ignore`: Pass `{ log: true }` to the consolidated ignore combinator. Adapt arguments and imports to the v4 API. + +- `Effect.inheritFiberRefs` -> `none`: Bulk FiberRef inheritance was removed; propagate required context references explicitly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.interruptWith` -> `Effect.interrupt`: V4 interruption uses the current fiber identity; remove the explicit FiberId argument. Adapt arguments and imports to the v4 API. + +- `Effect.intoDeferred` -> `Deferred.into`: Use the Deferred module combinator. Adapt arguments and imports to the v4 API. + +- `Effect.iterate` -> `none`: Use an explicit stateful `Effect.gen` loop; v4 removed the Effect-specific loop helper. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.labelMetrics` -> `Metric.withAttributes`: Convert labels to metric attributes and scope them around the Effect. Adapt arguments and imports to the v4 API. + +- `Effect.labelMetricsScoped` -> `Metric.withAttributes`: Apply metric attributes to the scoped Effect rather than mutating scoped labels. Adapt arguments and imports to the v4 API. + +- `Effect.let` -> `Effect.let`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.liftPredicate` -> `Effect.filterOrFail`: Lift the input with `Effect.succeed` and apply `filterOrFail`. Adapt arguments and imports to the v4 API. + +- `Effect.linkSpanCurrent` -> `Effect.linkSpans`: Use the v4 span-link combinator. Adapt arguments and imports to the v4 API. + +- `Effect.locally` -> `Effect.provideService`: FiberRef values are context references in v4; provide the reference for the Effect lifetime. Adapt arguments and imports to the v4 API. + +- `Effect.locallyScoped` -> `Effect.provideService`: Provide the context reference around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.locallyScopedWith` -> `Effect.updateServiceScoped`: Context references replace FiberRefs in v4; update the reference for the current scope. Adapt arguments and imports to the v4 API. + +- `Effect.locallyWith` -> `Effect.updateService`: Context references replace FiberRefs in v4; update the reference around the target Effect. Adapt arguments and imports to the v4 API. + +- `Effect.logAnnotations` -> `References.CurrentLogAnnotations`: Context references are Effects in v4; yield or compose `References.CurrentLogAnnotations` directly. Adapt imports to the v4 API. + +- `Effect.loop` -> `none`: Use an explicit `Effect.gen` loop and collect results when needed. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.makeLatch` -> `Latch.make`: Latch constructors moved to `effect/Latch`. Adapt arguments and imports to the v4 API. + +- `Effect.makeSemaphore` -> `Semaphore.make`: Semaphore constructors moved to `effect/Semaphore`. Adapt arguments and imports to the v4 API. + +- `Effect.mapAccum` -> `Effect.reduce`: Carry `[state, output]` through an effectful reduction. Adapt arguments and imports to the v4 API. + +- `Effect.mapErrorCause` -> `Effect.catchCause + Effect.failCause`: Transform the Cause in a cause handler and fail with the mapped Cause. Adapt arguments and imports to the v4 API. + +- `Effect.mapInputContext` -> `Effect.contextWith + Effect.provide`: Build the required context from the incoming context and provide it explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.merge` -> `Effect.catch`: Recover each typed error with `Effect.succeed` so both channels become success values. Adapt arguments and imports to the v4 API. + +- `Effect.mergeAll` -> `Effect.reduce`: Reduce the input effects with an effectful accumulator. Adapt arguments and imports to the v4 API. + +- `Effect.metricLabels` -> `Metric.CurrentMetricAttributes`: Context references are Effects in v4; yield or compose `Metric.CurrentMetricAttributes` directly. Adapt imports to the v4 API. + +- `Effect.negate` -> `Effect.map`: Map the boolean result with logical negation. Adapt arguments and imports to the v4 API. + +- `Effect.none` -> `Effect.flatMap + Option.match`: Fail for `Some` and succeed with void for `None`. Adapt arguments and imports to the v4 API. + +- `Effect.once` -> `Effect.cached`: Create the cached Effect once, then execute the returned Effect repeatedly. Adapt arguments and imports to the v4 API. + +- `Effect.optionFromOptional` -> `Effect.catchTag`: Map success to `Option.some` and recover `NoSuchElementError` with `Option.none`. Adapt arguments and imports to the v4 API. + +- `Effect.orDieWith` -> `Effect.mapError + Effect.orDie`: Map the typed error to the desired defect, then convert failures to defects. Adapt arguments and imports to the v4 API. + +- `Effect.orElse` -> `Effect.catch`: Ignore the caught error and evaluate the fallback Effect. Adapt arguments and imports to the v4 API. + +- `Effect.orElseFail` -> `Effect.mapError`: Replace every typed error with the lazily produced failure value. Adapt arguments and imports to the v4 API. + +- `Effect.parallelErrors` -> `Effect.all`: Use `{ mode: "result", concurrency: "unbounded" }` and collect failures explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.parallelFinalizers` -> `none`: Parallel finalizer strategy mutation was removed; fork independent cleanup explicitly when ordering is irrelevant. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.patchFiberRefs` -> `none`: Bulk FiberRefs patching was removed; update individual context references with `Effect.updateService`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.patchRuntimeFlags` -> `none`: RuntimeFlags patching was removed from the public API; use supported high-level runtime options. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.raceWith` -> `Effect.raceFirst + Fiber APIs`: Use `raceFirst` for first completion, or fork both effects and inspect their Exits for custom finishers. Adapt arguments and imports to the v4 API. + +- `Effect.random` -> `Random.Random`: Services are Effects in v4; yield or compose `Random.Random` directly. Adapt imports to the v4 API. + +- `Effect.randomWith` -> `Random.Random.use`: Use the Random reference's `.use` helper to invoke the effectful callback. Prefer module-level Random operations when possible. + +- `Effect.reduceEffect` -> `Effect.flatMap + Effect.reduce`: Evaluate the initial Effect, then reduce the remaining effects. Adapt arguments and imports to the v4 API. + +- `Effect.reduceRight` -> `Effect.reduce`: Reverse the input first, then perform the effectful reduction. Adapt arguments and imports to the v4 API. + +- `Effect.reduceWhile` -> `none`: Use an explicit `Effect.gen` loop that checks the accumulator before each step. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.repeatN` -> `Effect.repeat`: Pass `{ times: n }` to the consolidated repeat combinator. Adapt arguments and imports to the v4 API. + +- `Effect.runRequestBlock` -> `none`: The request-runtime block runner is internal; submit requests with `Effect.request`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.runtime` -> `Effect.context + Effect.runForkWith`: Capture services as a Context and use the corresponding `run*With` function. Adapt arguments and imports to the v4 API. + +- `Effect.scheduleForked` -> `Effect.schedule + Effect.forkScoped`: Schedule the Effect, then fork it in the current Scope. Adapt arguments and imports to the v4 API. + +- `Effect.scopeWith` -> `Effect.scopedWith`: Use the renamed scoped callback combinator. Adapt arguments and imports to the v4 API. + +- `Effect.sequentialFinalizers` -> `none`: Sequential reverse-order finalization is the normal Scope behavior; remove this wrapper. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.serviceConstants` -> `Context.Service.use`: Expose constants from the service explicitly or through the generated `use` helper. Adapt arguments and imports to the v4 API. + +- `Effect.serviceFunction` -> `Context.Service.use`: Use the service class `.use` helper to build an accessor function. Adapt arguments and imports to the v4 API. + +- `Effect.serviceFunctionEffect` -> `Context.Service.use`: Use the service class `.use` helper for effect-returning methods. Adapt arguments and imports to the v4 API. + +- `Effect.serviceFunctions` -> `Context.Service.use`: Define explicit service accessors with the generated `.use` helper. Adapt arguments and imports to the v4 API. + +- `Effect.serviceMembers` -> `Context.Service.use`: Define explicit service accessors with the generated `.use` helper. Adapt arguments and imports to the v4 API. + +- `Effect.serviceOptional` -> `service`: Services are Effects in v4; yield or compose the service key directly. Use `Effect.serviceOption` only when absence is expected. + +- `Effect.setFiberRefs` -> `none`: Bulk FiberRefs replacement was removed; provide individual context references. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.step` -> `none`: The low-level Effect stepping API was removed from the public surface. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.summarized` -> `Effect.gen`: Run the summary Effect before and after the target Effect and combine the two measurements explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.supervised` -> `FiberSet`: Track explicitly forked Fibers in a scoped `FiberSet` instead of installing a runtime Supervisor. Adapt arguments and imports to the v4 API. + +- `Effect.tagMetrics` -> `Metric.withAttributes`: Convert key/value tags to metric attributes. Adapt arguments and imports to the v4 API. + +- `Effect.tagMetricsScoped` -> `Metric.withAttributes`: Apply attributes around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.takeUntil` -> `none`: Use an explicit `Effect.gen` loop for an effectful stopping predicate. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.takeWhile` -> `none`: Use an explicit `Effect.gen` loop, or `Array.takeWhile` when the predicate is pure. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.tapBoth` -> `Effect.tapError + Effect.tap`: Tap the failure path first, then tap successful values. Adapt arguments and imports to the v4 API. + +- `Effect.tapErrorCause` -> `Effect.tapCause`: Use the shortened v4 cause-tap name. Adapt arguments and imports to the v4 API. + +- `Effect.timedWith` -> `Effect.gen`: Read the supplied clock Effect before and after the target and compute the Duration explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.timeoutFail` -> `Effect.timeoutOrElse`: Use `Effect.fail(onTimeout())` as the timeout fallback. Adapt arguments and imports to the v4 API. + +- `Effect.timeoutFailCause` -> `Effect.timeoutOrElse`: Use `Effect.failCause(onTimeout())` as the timeout fallback. Adapt arguments and imports to the v4 API. + +- `Effect.timeoutTo` -> `Effect.timeoutOrElse + Effect.map`: Map successful values first and use the timeout fallback for `onTimeout`. Adapt arguments and imports to the v4 API. + +- `Effect.tracerWith` -> `Tracer.Tracer.use`: Use the Tracer reference's `.use` helper to invoke the effectful callback. Adapt arguments and imports to the v4 API. + +- `Effect.transplant` -> `none`: Fiber scope grafting was removed; use structured concurrency with `forkChild`, `forkScoped`, or `forkIn`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.transposeMapOption` -> `Option.match`: Return `Effect.succeedNone` for None and map the Effect result to Some. Adapt arguments and imports to the v4 API. + +- `Effect.try` -> `Effect.try`: Use the callback overload for Cause.UnknownError, or the object overload with try and catch to map failures to a custom error. The callback-only overload does not accept a custom error type parameter. + +- `Effect.tryMap` -> `Effect.flatMap + Effect.try`: FlatMap the source value into the v4 synchronous try constructor. Adapt arguments and imports to the v4 API. + +- `Effect.tryMapPromise` -> `Effect.flatMap + Effect.tryPromise`: FlatMap the source value into the v4 Promise try constructor. Adapt arguments and imports to the v4 API. + +- `Effect.unless` -> `Effect.suspend`: Select `Effect.void` or the target Effect with a negated lazy condition. Adapt arguments and imports to the v4 API. + +- `Effect.unlessEffect` -> `Effect.when`: Negate the effectful boolean condition, then use the consolidated `when`. Adapt arguments and imports to the v4 API. + +- `Effect.unsafeMakeLatch` -> `Latch.makeUnsafe`: The unsafe constructor moved to `effect/Latch`. Adapt arguments and imports to the v4 API. + +- `Effect.unsafeMakeSemaphore` -> `Semaphore.makeUnsafe`: The unsafe constructor moved to `effect/Semaphore`. Adapt arguments and imports to the v4 API. + +- `Effect.unsandbox` -> `Effect.catch + Effect.failCause`: Treat the sandboxed Cause as an error and fail with that Cause. Adapt arguments and imports to the v4 API. + +- `Effect.updateFiberRefs` -> `none`: Bulk FiberRefs updates were removed; update individual context references with `Effect.updateService`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.using` -> `Effect.scoped + Effect.flatMap`: Acquire inside a fresh Scope, run the use Effect, and close the Scope afterward. Adapt arguments and imports to the v4 API. + +- `Effect.validateAll` -> `Effect.validate`: Use the consolidated collection validation combinator. Adapt arguments and imports to the v4 API. + +- `Effect.validateFirst` -> `Effect.firstSuccessOf`: Map inputs to validation effects and select the first success; handle accumulated diagnostics explicitly if required. Adapt arguments and imports to the v4 API. + +- `Effect.validateWith` -> `Effect.zipWith`: Zip and combine the Effects; use `mode: "result"` when both failures must be retained. Adapt arguments and imports to the v4 API. + +- `Effect.whenEffect` -> `Effect.when`: The v4 `when` combinator accepts an effectful boolean condition directly. Adapt arguments and imports to the v4 API. + +- `Effect.whenFiberRef` -> `reference.use + Effect.when`: Use the Context.Reference `.use` helper to inspect the value, test it, and branch explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.whenLogLevel` -> `none`: Log-level conditional execution was removed; configure Logger filtering and guard optional work explicitly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.whenRef` -> `Ref.get + Effect.flatMap`: Read the Ref, test it, and branch explicitly. Adapt arguments and imports to the v4 API. + +- `Effect.withClock` -> `Effect.provideService`: Provide `Clock.Clock` for the target Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withClockScoped` -> `Effect.provideService`: Provide `Clock.Clock` around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withConcurrency` -> `none`: Ambient concurrency was removed; pass `concurrency` directly to `Effect.all`, `Effect.forEach`, and related combinators. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withConfigProvider` -> `Effect.provideService`: Provide `ConfigProvider.ConfigProvider` for the target Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withConfigProviderScoped` -> `Effect.provideService`: Provide the ConfigProvider around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withConsole` -> `Effect.provideService`: Provide `Console.Console` for the target Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withConsoleScoped` -> `Effect.provideService`: Provide the Console service around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withEarlyRelease` -> `Scope.make + Scope.close`: Create a Scope explicitly, provide it to acquisition, and retain a close action. Adapt arguments and imports to the v4 API. + +- `Effect.withFiberRuntime` -> `none`: Direct FiberRuntime access was removed; use public Effect, Fiber, and Context operations. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withLogSpan` -> `Effect.withLogSpan`: Still exported in v4; update call sites for the revised signature, options, and channel inference. + +- `Effect.withMaxOpsBeforeYield` -> `none`: The scheduler operation budget is no longer configurable through Effect. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withMetric` -> `Effect.tap + Metric.update`: Update the Metric explicitly from the Effect success value. Adapt arguments and imports to the v4 API. + +- `Effect.withRandom` -> `Effect.provideService`: Provide `Random.Random` for the target Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withRandomFixed` -> `Effect.provideService`: Provide a custom deterministic `Random.Random` implementation. Adapt arguments and imports to the v4 API. + +- `Effect.withRandomScoped` -> `Effect.provideService`: Provide the Random service around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withRequestBatching` -> `none`: Ambient request batching configuration was removed; configure batching in the `RequestResolver`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withRequestCache` -> `none`: Ambient request-cache replacement was removed; model keyed caching explicitly with `Cache`. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withRequestCaching` -> `none`: Ambient request caching was removed; configure resolution or use `Cache` explicitly. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withRuntimeFlagsPatch` -> `none`: RuntimeFlags patching was removed from the public API. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withRuntimeFlagsPatchScoped` -> `none`: Scoped RuntimeFlags patching was removed from the public API. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withScheduler` -> `none`: Ambient scheduler replacement was removed; use supported runtime run options or explicit scheduling combinators. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withSchedulingPriority` -> `none`: Ambient fiber scheduling priority was removed from the public API. No direct public replacement exists in v4; rewrite the call site around the stated v4 primitive. + +- `Effect.withTracerScoped` -> `Effect.provideService`: Provide the Tracer service around the scoped Effect. Adapt arguments and imports to the v4 API. + +- `Effect.withUnhandledErrorLogLevel` -> `Effect.ignore`: Handle or explicitly ignore child-fiber failures, selecting the desired log behavior at the boundary. Adapt arguments and imports to the v4 API. + +- `Effect.zipLeft` -> `Effect.zip + Effect.map`: Zip the Effects and select the first tuple element. Adapt arguments and imports to the v4 API. + +- `Effect.zipRight` -> `Effect.andThen`: Sequence the Effects and retain the second result. Adapt arguments and imports to the v4 API. + +### `effect/Effectable` + +- `Effectable.ChannelTypeId` -> `Channel.TypeId`: The public channel brand moved to its owning module; v4 uses a string TypeId rather than the v3 Symbol. + +- `Effectable.Class` -> `Effectable.Class`: Still available; replace commit() with an asEffect() method returning the Effect. The intermediate v4 override property/getter is no longer supported. + +- `Effectable.CommitPrimitive` -> `new() => Effect.Effect`: The named constructor interface was removed; inline the constructor type when needed. + +- `Effectable.CommitPrototype` -> `Effectable.Prototype`: Use Effectable.Prototype({ label, evaluate(fiber) { ... } }) and move the old commit body into evaluate. + +- `Effectable.EffectPrototype` -> `Effectable.Prototype`: The raw multi-branded prototype was removed; use Prototype with an explicit evaluate callback. + +- `Effectable.EffectTypeId` -> `Effect.TypeId`: The public Effect brand moved to Effect; v4 uses a string TypeId rather than the v3 Symbol. + +- `Effectable.SinkTypeId` -> `Sink.isSink`: Sink's TypeId is private in v4; use the public guard for runtime checks and public Sink constructors for values. + +- `Effectable.StreamTypeId` -> `Stream.TypeId`: The public stream brand moved to its owning module; v4 uses a string TypeId rather than the v3 Symbol. + +- `Effectable.StructuralClass` -> `Effectable.Class`: Use Class and migrate commit() to asEffect(); v4 equality is structural by default. + +- `Effectable.StructuralCommitPrototype` -> `Effectable.Prototype`: Use Prototype with evaluate; a separate structural prototype is unnecessary because v4 equality is structural by default. + +### `effect/Either` + +- `Either.Do` -> `Result.Do`: The empty successful do-notation value moved to Result. + +- `Either.Either` -> `Result.Result`: Either\ became Result\; Right and Left became Success and Failure. + +- `Either.Either.Left` -> `Result.Result.Failure`: Use the Result namespace extractor for the failure variant. + +- `Either.Either.Right` -> `Result.Result.Success`: Use the Result namespace extractor for the success variant. + +- `Either.EitherTypeLambda` -> `Result.ResultTypeLambda`: Moved and renamed with Result. + +- `Either.EitherUnify` -> `Result.ResultUnify`: Moved and renamed with Result. + +- `Either.EitherUnifyIgnore` -> `Result.ResultUnifyIgnore`: Moved and renamed with Result. + +- `Either.Left` -> `Result.Failure`: Left became Failure; .left became .failure. + +- `Either.Right` -> `Result.Success`: Right became Success; .right became .success. + +- `Either.TypeId` -> `none`: Result keeps its brand private and exports no public TypeId. + +- `Either.all` -> `Result.all`: Either moved to Result; collection behavior is retained with Failure and Success terminology. + +- `Either.ap` -> `Result.flatMap`: Use Result.flatMap(self, (f) =\> Result.map(that, f)); v4 has no Result.ap. + +- `Either.bind` -> `Result.bind`: The do-notation combinator moved to Result. + +- `Either.bindTo` -> `Result.bindTo`: The do-notation combinator moved to Result. + +- `Either.filterOrLeft` -> `Result.filterOrFail`: Left is now Failure, so the predicate combinator is filterOrFail. + +- `Either.flatMap`: TODO: needs guidance + +- `Either.flip` -> `Result.flip`: The channel-swapping combinator moved to Result. + +- `Either.fromNullable` -> `Result.fromNullishOr`: Renamed with v4 nullish-or terminology. + +- `Either.getEquivalence` -> `Result.makeEquivalence`: Pass success and failure equivalences positionally instead of a right and left object. + +- `Either.getLeft` -> `Result.getFailure`: Extract the Result failure as an Option. + +- `Either.getOrElse` -> `Result.getOrElse`: Moved unchanged to Result. + +- `Either.getOrThrow` -> `Result.getOrThrow`: V4 throws the raw Failure value; use getOrThrowWith when a custom Error is required. + +- `Either.getOrThrowWith` -> `Result.getOrThrowWith`: Moved to Result; the callback receives the Failure value. + +- `Either.getOrUndefined` -> `Result.getOrUndefined`: Moved unchanged to Result. + +- `Either.getRight` -> `Result.getSuccess`: Extract the Result success as an Option. + +- `Either.isEither` -> `Result.isResult`: Renamed with the data type. + +- `Either.isLeft` -> `Result.isFailure`: Left is now the Failure variant. + +- `Either.isRight` -> `Result.isSuccess`: Right is now the Success variant. + +- `Either.left` -> `Result.fail`: Construct a Failure with Result.fail. + +- `Either.let` -> `Result.let`: The do-notation combinator moved to Result. + +- `Either.map` -> `Result.map`: Map now transforms the Success channel. + +- `Either.mapLeft` -> `Result.mapError`: Left mapping became failure-channel error mapping. + +- `Either.match` -> `Result.match`: Rename handlers from onLeft and onRight to onFailure and onSuccess. + +- `Either.right` -> `Result.succeed`: Construct a Success with Result.succeed. + +- `Either.try` -> `Result.try`: The synchronous throwable constructor moved to Result. + +- `Either.void` -> `Result.void`: Use the prebuilt successful Result\. + +### `effect/Encoding` + +- `Encoding.DecodeException` -> `Encoding.EncodingError`: Use the unified error class with kind Decode. + +- `Encoding.DecodeExceptionTypeId` -> `Encoding.EncodingErrorTypeId`: Decode and encode failures now share one marker. + +- `Encoding.EncodeException` -> `Encoding.EncodingError`: Use the unified error class with kind Encode. + +- `Encoding.EncodeExceptionTypeId` -> `Encoding.EncodingErrorTypeId`: Decode and encode failures now share one marker. + +- `Encoding.decodeUriComponent` -> `Result.try`: Wrap decodeURIComponent in Result.try and map failure to EncodingError, or decode Schema.StringFromUriComponent. + +- `Encoding.encodeUriComponent` -> `Result.try`: Wrap encodeURIComponent in Result.try and map failure to EncodingError, or encode Schema.StringFromUriComponent. + +- `Encoding.isDecodeException` -> `Encoding.isEncodingError`: Use the unified guard and test kind === Decode when decode-only narrowing is required. + +- `Encoding.isEncodeException` -> `Encoding.isEncodingError`: Use the unified guard and test kind === Encode when encode-only narrowing is required. + +### `effect/Equal` + +- `Equal.equivalence` -> `Equal.asEquivalence`: Direct rename. The returned equivalence now follows v4 structural equality, including NaN equality and cached comparisons for immutable objects. + +### `effect/Equivalence` + +- `Equivalence.Equivalence` -> `Equivalence.Equivalence`: The callable type is retained but is now a type alias, so declaration merging is no longer supported. + +- `Equivalence.all` -> `Equivalence.Tuple([...collection])`: Materialize the comparator iterable for Tuple. Unlike v3 prefix comparison, v4 requires equal input lengths; use Equivalence.make for intentional prefix semantics. + +- `Equivalence.array` -> `Equivalence.Array`: Capitalized constructor name; positional equal-length array comparison is unchanged. + +- `Equivalence.bigint` -> `Equivalence.BigInt`: Capitalized instance name; strict bigint equality is unchanged. + +- `Equivalence.boolean` -> `Equivalence.Boolean`: Capitalized instance name; strict boolean equality is unchanged. + +- `Equivalence.combineMany` -> `Equivalence.combine(self, Equivalence.combineAll(collection))`: Compose combine with combineAll; the dedicated dual combineMany helper was removed. + +- `Equivalence.number` -> `Equivalence.Number`: Capitalized instance name. V4 considers NaN equivalent to NaN; use Equivalence.strictEqual\() for exact v3 strict-equality behavior. + +- `Equivalence.product` -> `Equivalence.Tuple([self, that])`: Replace the dual two-comparator helper with the single-array Tuple constructor. + +- `Equivalence.productMany` -> `Equivalence.Tuple([self, ...collection])`: Materialize the comparator iterable in one Tuple call; v4 rejects unequal input lengths instead of using v3 prefix semantics. + +- `Equivalence.strict` -> `Equivalence.strictEqual`: Renamed strict-equality constructor; call as Equivalence.strictEqual\(). + +- `Equivalence.string` -> `Equivalence.String`: Capitalized instance name; case-sensitive strict equality is unchanged. + +- `Equivalence.struct` -> `Equivalence.Struct`: Capitalized constructor name. V4 also compares configured symbol and non-enumerable keys via Reflect.ownKeys. + +- `Equivalence.symbol` -> `Equivalence.strictEqual()`: There is no Symbol instance export; strictEqual preserves the v3 symbol comparison. + +- `Equivalence.tuple` -> `Equivalence.Tuple([eqA, eqB, ...])`: Capitalized constructor now takes one comparator array instead of rest arguments and rejects unequal input lengths. + +### `effect/ExecutionPlan` + +- `ExecutionPlan.ExecutionPlan` -> `ExecutionPlan.ExecutionPlan`: The plan type remains; withRequirements was renamed to captureRequirements. + +- `ExecutionPlan.TypesBase` -> `ExecutionPlan.ConfigBase`: The base type for execution-plan step configuration was renamed. + +- `ExecutionPlan.make` -> `ExecutionPlan.make`: The variadic execution-plan constructor remains unchanged. + +### `effect/ExecutionStrategy` + +- `ExecutionStrategy.ExecutionStrategy` -> `Types.Concurrency | Scope.ExecutionStrategy`: The ADT was removed; use number | unbounded for operation concurrency, or sequential | parallel for Scope finalizers. + +- `ExecutionStrategy.Parallel` -> `"parallel" | "unbounded"`: The tagged case was removed; use the consumer-specific primitive value. + +- `ExecutionStrategy.ParallelN` -> `number`: The tagged case was removed; bounded operation concurrency is represented directly by a number. + +- `ExecutionStrategy.Sequential` -> `"sequential" | 1`: The tagged case was removed; use the consumer-specific primitive value. + +- `ExecutionStrategy.isParallel` -> `strategy === "parallel"`: Compare the Scope strategy directly; for concurrency options compare with unbounded. + +- `ExecutionStrategy.isParallelN` -> `typeof concurrency === "number"`: Bounded parallelism is represented directly by a numeric concurrency value. + +- `ExecutionStrategy.isSequential` -> `strategy === "sequential"`: Compare the Scope strategy directly; for operation concurrency use the value 1. + +- `ExecutionStrategy.match` -> `switch`: Use ordinary branching over the consumer-specific concurrency or Scope strategy primitive. + +- `ExecutionStrategy.parallel` -> `"parallel" | "unbounded"`: Use parallel for Scope finalizers or unbounded for operation concurrency. + +- `ExecutionStrategy.parallelN` -> `number`: Pass the parallelism directly as a numeric concurrency option; Scope has no bounded parallel strategy. + +- `ExecutionStrategy.sequential` -> `"sequential" | 1`: Use sequential for Scope finalizers or 1 for operation concurrency. + +### `effect/Exit` + +- `Exit.Exit` -> `Exit.Exit`: Still exported as Exit.Exit\ = Exit.Success\ | Exit.Failure\; v4 variants share Exit.Exit.Proto and remain Effect values. + +- `Exit.ExitUnify` -> `none`: Removed type-level implementation hook; delete direct references. V4 Success and Failure inherit Exit.Exit.Proto, but no exported Exit-specific Unify interface replaces this API. + +- `Exit.ExitUnifyIgnore` -> `none`: Removed type-level implementation hook; delete direct references. V4 Success and Failure inherit Exit.Exit.Proto, but no exported Exit-specific Unify interface replaces this API. + +- `Exit.Failure` -> `Exit.Failure`: Still exported with \_tag Failure and cause; it now extends Exit.Exit.Proto and no longer exposes the v3 \_op, effect\_instruction\_i0, or Exit-specific Unify fields. + +- `Exit.Success` -> `Exit.Success`: Still exported with \_tag Success and value; it now extends Exit.Exit.Proto, defaults E to never, and no longer exposes the v3 \_op, effect\_instruction\_i0, or Exit-specific Unify fields. + +- `Exit.all` -> `Exit.asVoidAll + Exit.isSuccess + Option.some / Option.none`: No direct value-collecting v4 equivalent. Materialize the iterable once; return Option.none for empty input, use Exit.asVoidAll to combine every failure, and otherwise collect each Success.value into Exit.succeed and wrap it in Option.some. The parallel option is gone because v4 Cause flattens sequential and parallel composition. + +- `Exit.as` -> `Exit.map`: Replace with Exit.map(self, () =\> value); this preserves a failed Exit and returns Exit data rather than a general Effect. + +- `Exit.causeOption` -> `Exit.getCause`: Direct rename; still returns Option.some(cause) for Failure and Option.none for Success. + +- `Exit.exists` -> `Exit.isSuccess`: No direct v4 combinator; use Exit.isSuccess(self) && predicate(self.value). If callers rely on the refinement overload, retain an explicitly typed wrapper returning self is Exit.Exit\. + +- `Exit.flatMap`: TODO: needs guidance + +- `Exit.flatMapEffect` -> `Effect.matchCauseEffectEager`: Use Effect.matchCauseEffectEager(self, { onFailure: cause =\> Effect.succeed(Exit.failCause(cause)), onSuccess: f }). The explicit failure branch is required because v3 preserved an input Failure as a successful outer Effect; plain Effect.flatMap would instead fail the outer Effect. + +- `Exit.flatten` -> `Exit.match`: No direct v4 Exit flatten; use Exit.match(self, { onFailure: Exit.failCause, onSuccess: identity }) to return the inner Exit on success and preserve an outer failure as Exit data. + +- `Exit.forEachEffect` -> `Effect.flatMapEager + Effect.exit`: Use Effect.exit(Effect.flatMapEager(self, f)). This captures both the original Exit failure and failures from f into the returned Exit while keeping the outer Effect infallible; flatMapEager preserves v3's eager callback selection for an already-resolved Exit. + +- `Exit.fromEither` -> `Result.match + Exit.fail / Exit.succeed`: V3 Either is v4 Result. Convert with Result.match(result, { onFailure: Exit.fail, onSuccess: Exit.succeed }); there is no v4 Exit.fromResult constructor. + +- `Exit.fromOption` -> `Option.match + Exit.fail / Exit.succeed`: Use Option.match(option, { onNone: () =\> Exit.fail(undefined), onSome: Exit.succeed }) to preserve v3's Exit\ contract. Exit.findErrorOption is an accessor and is not a replacement. + +- `Exit.getOrElse` -> `Exit.match`: Use Exit.match(self, { onFailure: orElse, onSuccess: identity }); onFailure still receives the full Cause. + +- `Exit.isInterrupted` -> `Exit.hasInterrupts`: Direct semantic rename; true for a Failure whose Cause contains at least one Interrupt reason, false for Success. + +- `Exit.mapErrorCause` -> `Exit.match + Exit.failCause / Exit.succeed`: No direct v4 combinator. Use Exit.match(self, { onFailure: cause =\> Exit.failCause(f(cause)), onSuccess: Exit.succeed }); f now receives the flattened v4 Cause representation. Cause.map is only equivalent when f merely maps typed errors. + +- `Exit.matchEffect` -> `Effect.matchCauseEffectEager`: Direct cause-aware migration because Exit is an Effect in v4. Use the same onFailure/onSuccess handlers; the Eager variant preserves v3's immediate branch selection for resolved Exit values. + +- `Exit.zipLeft` -> `Exit.asVoidAll`: Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : self. This retains the left success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition. + +- `Exit.zipPar` -> `Exit.asVoidAll + Exit.succeed`: No direct v4 Exit pair combinator. Check Exit.asVoidAll([self, that]); return its Failure, or after narrowing both inputs to Success return Exit.succeed([self.value, that.value]). V4 Cause.combine has no parallel marker. + +- `Exit.zipParLeft` -> `Exit.asVoidAll`: Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : self. This retains the left success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition. + +- `Exit.zipParRight` -> `Exit.asVoidAll`: Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : that. This retains the right success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition. + +- `Exit.zipRight` -> `Exit.asVoidAll`: Use const checked = Exit.asVoidAll([self, that]); return Exit.isFailure(checked) ? checked : that. This retains the right success and combines dual failures; v4 Cause no longer distinguishes sequential from parallel composition. + +- `Exit.zipWith` -> `Exit.match`: No direct v4 equivalent. Nested-match both Exits: preserve a lone failure cause, call options.onFailure and Exit.failCause only when both fail, and call Exit.succeed(options.onSuccess(a, b)) when both succeed. + +### `effect/FastCheck` + +- `FastCheck.Arbitrary`: TODO: needs guidance + +- `FastCheck.ArrayConstraints`: TODO: needs guidance + +- `FastCheck.AsyncCommand`: TODO: needs guidance + +- `FastCheck.AsyncPropertyHookFunction`: TODO: needs guidance + +- `FastCheck.BigIntArrayConstraints`: TODO: needs guidance + +- `FastCheck.BigIntConstraints`: TODO: needs guidance + +- `FastCheck.BigUintConstraints` -> `FastCheck.BigIntConstraints`: Depend on fast-check and import it directly. Unsigned bigint constraints were consolidated into BigIntConstraints with min: 0n. + +- `FastCheck.CloneValue`: TODO: needs guidance + +- `FastCheck.Command`: TODO: needs guidance + +- `FastCheck.CommandsContraints`: TODO: needs guidance + +- `FastCheck.ContextValue`: TODO: needs guidance + +- `FastCheck.DateConstraints`: TODO: needs guidance + +- `FastCheck.DepthContext`: TODO: needs guidance + +- `FastCheck.DepthIdentifier`: TODO: needs guidance + +- `FastCheck.DepthSize`: TODO: needs guidance + +- `FastCheck.DictionaryConstraints`: TODO: needs guidance + +- `FastCheck.DomainConstraints`: TODO: needs guidance + +- `FastCheck.DoubleConstraints`: TODO: needs guidance + +- `FastCheck.EmailAddressConstraints`: TODO: needs guidance + +- `FastCheck.ExecutionStatus`: TODO: needs guidance + +- `FastCheck.ExecutionTree`: TODO: needs guidance + +- `FastCheck.FalsyContraints`: TODO: needs guidance + +- `FastCheck.FalsyValue`: TODO: needs guidance + +- `FastCheck.Float32ArrayConstraints`: TODO: needs guidance + +- `FastCheck.Float64ArrayConstraints`: TODO: needs guidance + +- `FastCheck.FloatConstraints`: TODO: needs guidance + +- `FastCheck.GeneratorValue`: TODO: needs guidance + +- `FastCheck.GlobalAsyncPropertyHookFunction`: TODO: needs guidance + +- `FastCheck.GlobalParameters`: TODO: needs guidance + +- `FastCheck.GlobalPropertyHookFunction`: TODO: needs guidance + +- `FastCheck.IAsyncProperty`: TODO: needs guidance + +- `FastCheck.IAsyncPropertyWithHooks`: TODO: needs guidance + +- `FastCheck.ICommand`: TODO: needs guidance + +- `FastCheck.IProperty`: TODO: needs guidance + +- `FastCheck.IPropertyWithHooks`: TODO: needs guidance + +- `FastCheck.IRawProperty`: TODO: needs guidance + +- `FastCheck.IntArrayConstraints`: TODO: needs guidance + +- `FastCheck.IntegerConstraints`: TODO: needs guidance + +- `FastCheck.JsonSharedConstraints`: TODO: needs guidance + +- `FastCheck.LetrecLooselyTypedBuilder`: TODO: needs guidance + +- `FastCheck.LetrecLooselyTypedTie`: TODO: needs guidance + +- `FastCheck.LetrecTypedBuilder`: TODO: needs guidance + +- `FastCheck.LetrecTypedTie`: TODO: needs guidance + +- `FastCheck.LetrecValue`: TODO: needs guidance + +- `FastCheck.LoremConstraints`: TODO: needs guidance + +- `FastCheck.MaybeWeightedArbitrary`: TODO: needs guidance + +- `FastCheck.Memo`: TODO: needs guidance + +- `FastCheck.MixedCaseConstraints`: TODO: needs guidance + +- `FastCheck.ModelRunAsyncSetup`: TODO: needs guidance + +- `FastCheck.ModelRunSetup`: TODO: needs guidance + +- `FastCheck.NatConstraints`: TODO: needs guidance + +- `FastCheck.ObjectConstraints`: TODO: needs guidance + +- `FastCheck.OneOfConstraints`: TODO: needs guidance + +- `FastCheck.OneOfValue`: TODO: needs guidance + +- `FastCheck.OptionConstraints`: TODO: needs guidance + +- `FastCheck.Parameters`: TODO: needs guidance + +- `FastCheck.PreconditionFailure`: TODO: needs guidance + +- `FastCheck.PropertyHookFunction`: TODO: needs guidance + +- `FastCheck.Random`: TODO: needs guidance + +- `FastCheck.RandomType`: TODO: needs guidance + +- `FastCheck.RecordConstraints`: TODO: needs guidance + +- `FastCheck.RecordValue`: TODO: needs guidance + +- `FastCheck.RunDetails`: TODO: needs guidance + +- `FastCheck.RunDetailsCommon`: TODO: needs guidance + +- `FastCheck.RunDetailsFailureInterrupted`: TODO: needs guidance + +- `FastCheck.RunDetailsFailureProperty`: TODO: needs guidance + +- `FastCheck.RunDetailsFailureTooManySkips`: TODO: needs guidance + +- `FastCheck.RunDetailsSuccess`: TODO: needs guidance + +- `FastCheck.SchedulerAct`: TODO: needs guidance + +- `FastCheck.SchedulerConstraints`: TODO: needs guidance + +- `FastCheck.SchedulerReportItem`: TODO: needs guidance + +- `FastCheck.SchedulerSequenceItem`: TODO: needs guidance + +- `FastCheck.ShuffledSubarrayConstraints`: TODO: needs guidance + +- `FastCheck.Size`: TODO: needs guidance + +- `FastCheck.SizeForArbitrary`: TODO: needs guidance + +- `FastCheck.SparseArrayConstraints`: TODO: needs guidance + +- `FastCheck.StringConstraints`: TODO: needs guidance + +- `FastCheck.StringMatchingConstraints`: TODO: needs guidance + +- `FastCheck.StringSharedConstraints`: TODO: needs guidance + +- `FastCheck.SubarrayConstraints`: TODO: needs guidance + +- `FastCheck.UnicodeJsonSharedConstraints` -> `FastCheck.JsonSharedConstraints`: Depend on fast-check and import it directly. Unicode JSON generation was consolidated into JsonSharedConstraints.stringUnit. + +- `FastCheck.UniqueArrayConstraints`: TODO: needs guidance + +- `FastCheck.UniqueArrayConstraintsCustomCompare`: TODO: needs guidance + +- `FastCheck.UniqueArrayConstraintsCustomCompareSelect`: TODO: needs guidance + +- `FastCheck.UniqueArrayConstraintsRecommended`: TODO: needs guidance + +- `FastCheck.UniqueArraySharedConstraints`: TODO: needs guidance + +- `FastCheck.UuidConstraints`: TODO: needs guidance + +- `FastCheck.Value`: TODO: needs guidance + +- `FastCheck.VerbosityLevel`: TODO: needs guidance + +- `FastCheck.WebAuthorityConstraints`: TODO: needs guidance + +- `FastCheck.WebFragmentsConstraints`: TODO: needs guidance + +- `FastCheck.WebPathConstraints`: TODO: needs guidance + +- `FastCheck.WebQueryParametersConstraints`: TODO: needs guidance + +- `FastCheck.WebSegmentConstraints`: TODO: needs guidance + +- `FastCheck.WebUrlConstraints`: TODO: needs guidance + +- `FastCheck.WeightedArbitrary`: TODO: needs guidance + +- `FastCheck.WithAsyncToStringMethod`: TODO: needs guidance + +- `FastCheck.WithCloneMethod`: TODO: needs guidance + +- `FastCheck.WithToStringMethod`: TODO: needs guidance + +- `FastCheck.__commitHash`: TODO: needs guidance + +- `FastCheck.__type`: TODO: needs guidance + +- `FastCheck.__version`: TODO: needs guidance + +- `FastCheck.anything`: TODO: needs guidance + +- `FastCheck.array`: TODO: needs guidance + +#### `FastCheck.ascii` + +**Replacement:** `FastCheck.string` + +Depend on fast-check and import it directly. fast-check v4 replaced character arbitraries with string units. + +**Example** + +```ts +FastCheck.string({ unit: "binary-ascii", minLength: 1, maxLength: 1 }) +``` + +#### `FastCheck.asciiString` + +**Replacement:** `FastCheck.string` + +Depend on fast-check and import it directly. Use the binary-ascii string unit. + +**Example** + +```ts +FastCheck.string({ ...constraints, unit: "binary-ascii" }) +``` + +- `FastCheck.assert`: TODO: needs guidance + +- `FastCheck.asyncDefaultReportMessage`: TODO: needs guidance + +- `FastCheck.asyncModelRun`: TODO: needs guidance + +- `FastCheck.asyncProperty`: TODO: needs guidance + +- `FastCheck.asyncStringify`: TODO: needs guidance + +- `FastCheck.asyncToStringMethod`: TODO: needs guidance + +#### `FastCheck.base64` + +**Replacement:** `FastCheck.constantFrom` + +Depend on fast-check and import it directly. Generate one base64 alphabet character; base64String remains for complete encoded strings. + +**Example** + +```ts +FastCheck.constantFrom(..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") +``` + +- `FastCheck.base64String`: TODO: needs guidance + +- `FastCheck.bigInt`: TODO: needs guidance + +- `FastCheck.bigInt64Array`: TODO: needs guidance + +- `FastCheck.bigIntN` -> `FastCheck.bigInt`: Depend on fast-check and import it directly. Express the signed bit range with min and max constraints. + +#### `FastCheck.bigUint` + +**Replacement:** `FastCheck.bigInt` + +Depend on fast-check and import it directly. Use a minimum of 0n and the previous maximum. + +**Example** + +```ts +FastCheck.bigInt({ min: 0n, max }) +``` + +- `FastCheck.bigUint64Array`: TODO: needs guidance + +#### `FastCheck.bigUintN` + +**Replacement:** `FastCheck.bigInt` + +Depend on fast-check and import it directly. Express the unsigned bit range with min and max constraints. + +**Example** + +```ts +FastCheck.bigInt({ min: 0n, max: (1n << BigInt(n)) - 1n }) +``` + +- `FastCheck.boolean`: TODO: needs guidance + +#### `FastCheck.char` + +**Replacement:** `FastCheck.string` + +Depend on fast-check and import it directly. Use a one-unit printable ASCII string. + +**Example** + +```ts +FastCheck.string({ unit: "grapheme-ascii", minLength: 1, maxLength: 1 }) +``` + +#### `FastCheck.char16bits` + +**Replacement:** `FastCheck.nat` + +Depend on fast-check and import it directly. Map a 16-bit natural number through String.fromCharCode. + +**Example** + +```ts +FastCheck.nat({ max: 0xffff }).map(String.fromCharCode) +``` + +- `FastCheck.check` -> `FastCheck.check`: Depend on fast-check and import it directly. The runner remains, but RunDetails.error was replaced by errorInstance in fast-check v4. + +- `FastCheck.clone`: TODO: needs guidance + +- `FastCheck.cloneIfNeeded`: TODO: needs guidance + +- `FastCheck.cloneMethod`: TODO: needs guidance + +- `FastCheck.commands`: TODO: needs guidance + +- `FastCheck.compareBooleanFunc`: TODO: needs guidance + +- `FastCheck.compareFunc`: TODO: needs guidance + +- `FastCheck.configureGlobal`: TODO: needs guidance + +- `FastCheck.constant` -> `FastCheck.constant`: Depend on fast-check and import it directly. The API remains; v4 infers literal types by default. + +- `FastCheck.constantFrom`: TODO: needs guidance + +- `FastCheck.context` -> `FastCheck.context`: Depend on fast-check and import it directly. The API is otherwise unchanged. + +- `FastCheck.createDepthIdentifier`: TODO: needs guidance + +- `FastCheck.date`: TODO: needs guidance + +- `FastCheck.defaultReportMessage`: TODO: needs guidance + +- `FastCheck.dictionary`: TODO: needs guidance + +- `FastCheck.domain`: TODO: needs guidance + +- `FastCheck.double`: TODO: needs guidance + +- `FastCheck.emailAddress`: TODO: needs guidance + +- `FastCheck.falsy`: TODO: needs guidance + +- `FastCheck.float`: TODO: needs guidance + +- `FastCheck.float32Array`: TODO: needs guidance + +- `FastCheck.float64Array`: TODO: needs guidance + +#### `FastCheck.fullUnicode` + +**Replacement:** `FastCheck.string` + +Depend on fast-check and import it directly. Use a one-unit binary Unicode string. + +**Example** + +```ts +FastCheck.string({ unit: "binary", minLength: 1, maxLength: 1 }) +``` + +#### `FastCheck.fullUnicodeString` + +**Replacement:** `FastCheck.string` + +Depend on fast-check and import it directly. Use the binary string unit. + +**Example** + +```ts +FastCheck.string({ ...constraints, unit: "binary" }) +``` + +- `FastCheck.func`: TODO: needs guidance + +- `FastCheck.gen`: TODO: needs guidance + +- `FastCheck.getDepthContextFor`: TODO: needs guidance + +- `FastCheck.hasAsyncToStringMethod`: TODO: needs guidance + +- `FastCheck.hasCloneMethod`: TODO: needs guidance + +- `FastCheck.hasToStringMethod`: TODO: needs guidance + +- `FastCheck.hash`: TODO: needs guidance + +- `FastCheck.hexa` -> `FastCheck.integer`: Depend on fast-check and import it directly. Map an integer from 0 through 15 to a hexadecimal character. + +- `FastCheck.hexaString` -> `FastCheck.string`: Depend on fast-check and import it directly. Pass a hexadecimal-character arbitrary as the string unit. + +- `FastCheck.infiniteStream`: TODO: needs guidance + +- `FastCheck.int16Array`: TODO: needs guidance + +- `FastCheck.int32Array`: TODO: needs guidance + +- `FastCheck.int8Array`: TODO: needs guidance + +- `FastCheck.integer`: TODO: needs guidance + +- `FastCheck.ipV4`: TODO: needs guidance + +- `FastCheck.ipV4Extended`: TODO: needs guidance + +- `FastCheck.ipV6`: TODO: needs guidance + +- `FastCheck.json`: TODO: needs guidance + +- `FastCheck.jsonValue`: TODO: needs guidance + +- `FastCheck.letrec`: TODO: needs guidance + +- `FastCheck.limitShrink`: TODO: needs guidance + +- `FastCheck.lorem`: TODO: needs guidance + +- `FastCheck.mapToConstant`: TODO: needs guidance + +- `FastCheck.maxSafeInteger`: TODO: needs guidance + +- `FastCheck.maxSafeNat`: TODO: needs guidance + +- `FastCheck.memo`: TODO: needs guidance + +- `FastCheck.mixedCase`: TODO: needs guidance + +- `FastCheck.modelRun`: TODO: needs guidance + +- `FastCheck.nat`: TODO: needs guidance + +- `FastCheck.noBias`: TODO: needs guidance + +- `FastCheck.noShrink`: TODO: needs guidance + +- `FastCheck.object`: TODO: needs guidance + +- `FastCheck.oneof`: TODO: needs guidance + +- `FastCheck.option`: TODO: needs guidance + +- `FastCheck.pre`: TODO: needs guidance + +- `FastCheck.property`: TODO: needs guidance + +- `FastCheck.readConfigureGlobal`: TODO: needs guidance + +- `FastCheck.record`: TODO: needs guidance + +- `FastCheck.resetConfigureGlobal` -> `Undici.install`: TODO: needs guidance + +- `FastCheck.sample`: TODO: needs guidance + +- `FastCheck.scheduledModelRun`: TODO: needs guidance + +- `FastCheck.scheduler`: TODO: needs guidance + +- `FastCheck.schedulerFor`: TODO: needs guidance + +- `FastCheck.shuffledSubarray`: TODO: needs guidance + +- `FastCheck.sparseArray`: TODO: needs guidance + +- `FastCheck.statistics`: TODO: needs guidance + +- `FastCheck.stream` -> `FastCheck.stream`: Depend on fast-check and import it directly. The API remains; update custom generator and Random implementations for fast-check v4 typings. + +- `FastCheck.string`: TODO: needs guidance + +- `FastCheck.string16bits` -> `FastCheck.string`: Depend on fast-check and import it directly. Pass a char16bits-compatible arbitrary as the string unit. + +- `FastCheck.stringMatching`: TODO: needs guidance + +#### `FastCheck.stringOf` + +**Replacement:** `FastCheck.string` + +Depend on fast-check and import it directly. Pass the former character arbitrary as the unit constraint. + +**Example** + +```ts +FastCheck.string({ ...constraints, unit: arbitrary }) +``` + +- `FastCheck.subarray`: TODO: needs guidance + +- `FastCheck.toStringMethod`: TODO: needs guidance + +- `FastCheck.tuple`: TODO: needs guidance + +- `FastCheck.uint16Array`: TODO: needs guidance + +- `FastCheck.uint32Array`: TODO: needs guidance + +- `FastCheck.uint8Array`: TODO: needs guidance + +- `FastCheck.uint8ClampedArray`: TODO: needs guidance + +- `FastCheck.ulid`: TODO: needs guidance + +- `FastCheck.unicode` -> `FastCheck.integer`: Depend on fast-check and import it directly. Map BMP code points while excluding surrogate code points; prefer the binary string unit for full Unicode. + +#### `FastCheck.unicodeJson` + +**Replacement:** `FastCheck.json` + +Depend on fast-check and import it directly. Select binary or grapheme strings with stringUnit. + +**Example** + +```ts +FastCheck.json({ stringUnit: "binary" }) +``` + +#### `FastCheck.unicodeJsonValue` + +**Replacement:** `FastCheck.jsonValue` + +Depend on fast-check and import it directly. Select binary or grapheme strings with stringUnit. + +**Example** + +```ts +FastCheck.jsonValue({ stringUnit: "binary" }) +``` + +- `FastCheck.unicodeString` -> `FastCheck.string`: Depend on fast-check and import it directly. Pass a BMP-code-point arbitrary as the unit constraint; prefer unit: binary for full Unicode. + +- `FastCheck.uniqueArray`: TODO: needs guidance + +- `FastCheck.uuid`: TODO: needs guidance + +#### `FastCheck.uuidV` + +**Replacement:** `FastCheck.uuid` + +Depend on fast-check and import it directly. Specify the UUID version through constraints. + +**Example** + +```ts +FastCheck.uuid({ version: 4 }) +``` + +- `FastCheck.webAuthority`: TODO: needs guidance + +- `FastCheck.webFragments`: TODO: needs guidance + +- `FastCheck.webPath`: TODO: needs guidance + +- `FastCheck.webQueryParameters`: TODO: needs guidance + +- `FastCheck.webSegment`: TODO: needs guidance + +- `FastCheck.webUrl`: TODO: needs guidance + +### `effect/Fiber` + +- `Fiber.Fiber` -> `Fiber.Fiber`: The v4 Fiber is the concrete runtime handle and is no longer itself an Effect; use Fiber.join or Fiber.await. + +- `Fiber.Fiber.Descriptor` -> `none`: Descriptors were removed; use Effect.withFiber for the current Fiber and read its id or runtime fields directly. + +- `Fiber.Fiber.Dump` -> `none`: Fiber dumps were removed; retain explicit Fiber handles and inspect their public runtime fields. + +- `Fiber.Fiber.Runtime` -> `Fiber.Fiber`: RuntimeFiber and the Fiber.Runtime alias were collapsed into the single v4 Fiber type. + +- `Fiber.Fiber.RuntimeVariance` -> `Fiber.Variance`: RuntimeFiber was collapsed into Fiber, leaving one variance encoding. + +- `Fiber.Fiber.Variance` -> `Fiber.Variance`: Retained as the variance encoding on the v4 Fiber interface. + +- `Fiber.FiberTypeId` -> `Fiber.isFiber`: The type-id symbol is private in v4; use the public Fiber.isFiber guard. + +- `Fiber.FiberUnify` -> `none`: Fiber no longer extends Effect, so its Effect unification helper was removed. + +- `Fiber.FiberUnifyIgnore` -> `none`: Fiber no longer extends Effect, so its Effect unification helper was removed. + +- `Fiber.Order` -> `Order.mapInput(Order.Number, (fiber) => fiber.id)`: The built-in Fiber order was removed; derive an order from the numeric id when ordering is actually required. + +- `Fiber.RuntimeFiber` -> `Fiber.Fiber`: RuntimeFiber and Fiber were collapsed into the single v4 Fiber interface. + +- `Fiber.RuntimeFiberTypeId` -> `Fiber.isFiber`: The separate RuntimeFiber marker was removed; use the public Fiber guard. + +- `Fiber.RuntimeFiberUnify` -> `none`: RuntimeFiber was collapsed into Fiber, which no longer participates in Effect unification. + +- `Fiber.RuntimeFiberUnifyIgnore` -> `none`: RuntimeFiber was collapsed into Fiber, which no longer participates in Effect unification. + +- `Fiber.all` -> `Fiber.joinAll`: Composite fibers were removed; join the iterable directly to obtain an Effect of all results. + +- `Fiber.await` -> `Fiber.await`: Unchanged; it returns an Effect containing the fiber Exit. + +- `Fiber.children` -> `none`: V4 fibers do not expose child-fiber enumeration; keep explicit handles in FiberSet or FiberMap when tracking is required. + +- `Fiber.done` -> `Effect.runFork`: Exit is an Effect in v4, so pass the Exit to Effect.runFork when a completed Fiber handle is required. + +- `Fiber.dumpAll` -> `none`: Fiber dump and global diagnostic APIs were removed; retain explicit fibers and inspect id and pollUnsafe where needed. + +- `Fiber.fail` -> `Effect.runFork(Effect.fail(error))`: Synthetic Fiber constructors were removed; run the corresponding Effect when a Fiber handle is required. + +- `Fiber.failCause` -> `Effect.runFork(Effect.failCause(cause))`: Synthetic Fiber constructors were removed; run the corresponding Effect when a Fiber handle is required. + +- `Fiber.fromEffect` -> `Effect.runFork`: V4 uses concrete runtime fibers; run the Effect directly, or keep using the Effect when no handle is needed. + +- `Fiber.getCurrentFiber` -> `Fiber.getCurrent`: Renamed and now returns Fiber | undefined synchronously instead of Option. + +- `Fiber.id` -> `fiber.id`: Fiber IDs are numbers exposed by the readonly id field. + +- `Fiber.inheritAll` -> `none`: FiberRef inheritance was removed with FiberRef; Context.Reference values are inherited through fiber context automatically. + +- `Fiber.interruptAsFork` -> `fiber.interruptUnsafe(fiberId)`: For fire-and-forget interruption use the immediate runtime hook; use Fiber.interruptAs when cleanup must be awaited. + +- `Fiber.interruptFork` -> `fiber.interruptUnsafe()`: Use the immediate runtime hook for fire-and-forget interruption; Fiber.interrupt waits for cleanup. + +- `Fiber.interrupted` -> `Effect.runFork(Exit.interrupt(fiberId))`: Synthetic Fiber constructors were removed; Exit is an Effect and can be run to obtain an interrupted Fiber. + +- `Fiber.isRuntimeFiber` -> `Fiber.isFiber`: All v4 Fiber values are concrete runtime fibers, so only the general guard remains. + +- `Fiber.map` -> `Effect.runFork(Effect.map(Fiber.join(fiber), f))`: Fiber transformation combinators were removed; transform its joined Effect and fork only if another handle is required. + +- `Fiber.mapEffect` -> `Effect.runFork(Effect.flatMap(Fiber.join(fiber), f))`: Fiber transformation combinators were removed; transform its joined Effect and fork only if another handle is required. + +- `Fiber.mapFiber` -> `Effect.flatMap(Fiber.join(fiber), (a) => Fiber.join(f(a)))`: Flatten through Fiber.join; fork the resulting Effect if another Fiber handle is required. + +- `Fiber.match` -> `none`: The virtual Fiber versus RuntimeFiber distinction no longer exists, so branch-specific matching is unnecessary. + +- `Fiber.never` -> `Effect.runFork(Effect.never)`: Synthetic Fiber constants were removed; run Effect.never when a never-completing Fiber is required. + +- `Fiber.orElse` -> `Effect.runFork(Effect.catchCause(Fiber.join(self), () => Fiber.join(that)))`: Compose joined Effects and fork the result only if another Fiber handle is required. + +- `Fiber.orElseEither` -> `Effect.catchCause`: Compose Fiber.join Effects explicitly and map each successful branch to your own tagged union; Either was also removed in v4. + +- `Fiber.poll` -> `fiber.pollUnsafe()`: Polling is now synchronous and returns Exit | undefined; wrap in Effect.sync and Option.fromUndefinedOr if the old shape is required. + +- `Fiber.pretty` -> `none`: Runtime fiber pretty-printing was removed; format the public id and polled Exit explicitly. + +- `Fiber.roots` -> `none`: The runtime no longer exposes a global root-fiber registry; track application fibers explicitly. + +- `Fiber.scoped` -> `Fiber.runIn`: Register the Fiber in an explicit Scope with Fiber.runIn; acquire the current Scope when migrating the old effectful form. + +- `Fiber.status` -> `fiber.pollUnsafe()`: FiberStatus was removed; undefined means not completed and an Exit means completed, with no public running/suspended distinction. + +- `Fiber.succeed` -> `Effect.runFork(Effect.succeed(value))`: Synthetic Fiber constructors were removed; run the corresponding Effect when a Fiber handle is required. + +- `Fiber.unsafeRoots` -> `none`: The runtime no longer exposes a global root-fiber registry; track application fibers explicitly. + +- `Fiber.void` -> `Effect.runFork(Effect.void)`: Synthetic Fiber constants were removed; run Effect.void when a completed Fiber\ is required. + +- `Fiber.zip` -> `Effect.runFork(Effect.zip(Fiber.join(self), Fiber.join(that)))`: Compose joined Effects and fork the result only if another Fiber handle is required. + +- `Fiber.zipLeft` -> `Effect.runFork(Effect.map(Effect.zip(Fiber.join(self), Fiber.join(that)), ([left]) => left))`: V4 has no Effect.zipLeft; zip joined Effects, project the left value, and fork only if another handle is required. + +- `Fiber.zipRight` -> `Effect.runFork(Effect.map(Effect.zip(Fiber.join(self), Fiber.join(that)), ([, right]) => right))`: V4 has no Effect.zipRight; zip joined Effects, project the right value, and fork only if another handle is required. + +- `Fiber.zipWith` -> `Effect.runFork(Effect.zipWith(Fiber.join(self), Fiber.join(that), f))`: Compose joined Effects and fork the result only if another Fiber handle is required. + +### `effect/FiberHandle` + +- `FiberHandle.FiberHandle` -> `FiberHandle.FiberHandle`: Retained; contained runtime fibers now use the unified Fiber type. + +- `FiberHandle.TypeId` -> `FiberHandle.isFiberHandle`: The type-id symbol is private in v4; use the public guard. + +- `FiberHandle.get` -> `FiberHandle.get`: Retained, but v4 returns Effect\\> instead of failing with NoSuchElementException when empty. + +- `FiberHandle.unsafeGet` -> `FiberHandle.getUnsafe`: Renamed to put the Unsafe suffix last. + +- `FiberHandle.unsafeSet` -> `FiberHandle.setUnsafe`: Renamed to put the Unsafe suffix last; the interruptAs option was removed because IDs are now numeric runtime details. + +### `effect/FiberId` + +- `FiberId.Composite` -> `none`: Composite FiberId values were removed; v4 uses a single numeric fiber id. + +- `FiberId.FiberId` -> `number`: V4 represents a fiber identity as the numeric Fiber.id field. + +- `FiberId.FiberIdTypeId` -> `none`: Fiber IDs are primitive numbers in v4 and have no type-id symbol. + +- `FiberId.None` -> `undefined`: Optional interruptor IDs use undefined rather than a sentinel FiberId type. + +- `FiberId.Runtime` -> `number`: Runtime fiber IDs are primitive numbers in v4. + +- `FiberId.Single` -> `number | undefined`: Use a number, with undefined only where the old None case was meaningful. + +- `FiberId.combine` -> `none`: Composite FiberId values were removed; v4 uses a single numeric fiber id. + +- `FiberId.combineAll` -> `none`: Composite FiberId values were removed; v4 uses a single numeric fiber id. + +- `FiberId.composite` -> `none`: Composite FiberId values were removed; v4 uses a single numeric fiber id. + +- `FiberId.getOrElse` -> `fiberId ?? fallback`: Represent absence as undefined when migrating code that previously used FiberId.none. + +- `FiberId.ids` -> `new Set([fiberId])`: A v4 fiber has one numeric id; composite-id flattening is no longer required. + +- `FiberId.isComposite` -> `none`: Composite FiberId values do not exist in v4. + +- `FiberId.isFiberId` -> `Number.isNumber`: Fiber IDs are primitive numbers in v4. + +- `FiberId.isNone` -> `fiberId === undefined`: Use undefined for an absent optional interruptor id; there is no sentinel FiberId.none. + +- `FiberId.isRuntime` -> `Number.isNumber`: Every v4 fiber id is a runtime numeric id. + +- `FiberId.make` -> `id`: Use the numeric id directly; startTimeSeconds is no longer part of fiber identity. + +- `FiberId.none` -> `undefined`: Optional interruptor IDs use undefined rather than a sentinel FiberId value. + +- `FiberId.runtime` -> `id`: Use the numeric id directly; startTimeMillis is no longer part of fiber identity. + +- `FiberId.threadName` -> `String(fiberId)`: There is no built-in thread-name formatter; format the numeric id at the presentation boundary. + +- `FiberId.toSet` -> `new Set([fiberId])`: A v4 fiber has one numeric id, so composite-id flattening is unnecessary. + +- `FiberId.unsafeMake` -> `none`: There is no public fiber-id allocator; obtain the current id with Effect.fiberId or from Fiber.id. + +### `effect/FiberMap` + +- `FiberMap.FiberMap` -> `FiberMap.FiberMap`: Retained; contained runtime fibers now use the unified Fiber type. + +- `FiberMap.TypeId` -> `FiberMap.isFiberMap`: The type-id symbol is private in v4; use the public guard. + +- `FiberMap.unsafeGet` -> `FiberMap.getUnsafe`: Renamed to put the Unsafe suffix last. + +- `FiberMap.unsafeHas` -> `FiberMap.hasUnsafe`: Renamed to put the Unsafe suffix last. + +- `FiberMap.unsafeSet` -> `FiberMap.setUnsafe`: Renamed to put the Unsafe suffix last; the interruptAs option was removed because IDs are now numeric runtime details. + +### `effect/FiberRef` + +- `FiberRef.FiberRef` -> `Context.Reference`: Fiber-local values and services share Context.Reference in v4; references have a defaultValue and no fork/join patching. + +- `FiberRef.FiberRefTypeId` -> `Context.isReference`: Use the public Context.Reference guard instead of a FiberRef type-id symbol. + +- `FiberRef.FiberRefUnify` -> `none`: Context.Reference is a service key and does not require the old FiberRef Effect-unification helper. + +- `FiberRef.FiberRefUnifyIgnore` -> `none`: Context.Reference is a service key and does not require the old FiberRef Effect-unification helper. + +- `FiberRef.Variance` -> `Context.Reference`: The FiberRef-specific variance interface was removed with FiberRef. + +- `FiberRef.currentConcurrency` -> `none`: Inherited concurrency was removed; pass concurrency explicitly to each v4 combinator that supports it. + +- `FiberRef.currentContext` -> `Effect.context`: Fiber services are stored directly in Context; use Effect.context to read them and Effect.provideContext to override them. + +- `FiberRef.currentLogAnnotations` -> `References.CurrentLogAnnotations`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentLogLevel` -> `References.CurrentLogLevel`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentLogSpan` -> `References.CurrentLogSpans`: Renamed and represented as a Context.Reference containing a readonly span array. + +- `FiberRef.currentLoggers` -> `References.CurrentLoggers`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentMaxOpsBeforeYield` -> `Scheduler.MaxOpsBeforeYield`: The scheduler setting is now a Context.Reference; yield it or provide it with Effect.provideService. + +- `FiberRef.currentMetricLabels` -> `Metric.CurrentMetricAttributes`: Metric labels became metric attributes stored in a Context.Reference. + +- `FiberRef.currentMinimumLogLevel` -> `References.MinimumLogLevel`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentRequestBatchingEnabled` -> `none`: The request batching FiberRef was removed; batching is defined by the v4 RequestResolver runAll implementation. + +- `FiberRef.currentRequestCache` -> `RequestResolver.withCache`: The ambient request cache was removed; wrap a RequestResolver with an explicit bounded cache. + +- `FiberRef.currentRequestCacheEnabled` -> `RequestResolver.withCache`: There is no ambient cache toggle; choose an explicitly cached or uncached RequestResolver. + +- `FiberRef.currentRuntimeFlags` -> `none`: RuntimeFlags and their FiberRef were removed; use specific v4 runtime options such as interruptibility and scheduler settings. + +- `FiberRef.currentScheduler` -> `Scheduler.Scheduler`: The scheduler is now a Context.Reference; yield it or provide it with Effect.provideService. + +- `FiberRef.currentSchedulingPriority` -> `none`: The ambient scheduling-priority FiberRef was removed; use explicit scheduler operations where priority is needed. + +- `FiberRef.currentSupervisor` -> `none`: The Supervisor and ambient supervisor FiberRef APIs were removed; track fibers explicitly with FiberSet or FiberMap. + +- `FiberRef.currentTracerEnabled` -> `References.TracerEnabled`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentTracerSpanAnnotations` -> `References.TracerSpanAnnotations`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentTracerSpanLinks` -> `References.TracerSpanLinks`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.currentTracerTimingEnabled` -> `References.TracerTimingEnabled`: Built-in FiberRefs are now Context.Reference values; yield the reference or provide it with Effect.provideService. + +- `FiberRef.delete` -> `Effect.provideService`: Context.Reference has no in-place delete; scope the default or desired value around the target Effect. + +- `FiberRef.get` -> `reference`: Context.Reference is yieldable as a service; yield it directly to read the current value. + +- `FiberRef.getAndUpdateSome` -> `Ref.getAndUpdateSome`: Use Ref for mutable state; for fiber-local configuration compute the value first and scope it with Effect.provideService. + +- `FiberRef.getWith` -> `Effect.flatMap(reference, f)`: Yield or flatMap the Context.Reference directly. + +- `FiberRef.interruptedCause` -> `none`: The pending interruption cause is no longer exposed as public fiber-local state; inspect completed failure Causes from Fiber.await. + +- `FiberRef.make` -> `Context.Reference`: Define a stable Context.Reference key with defaultValue; custom fork and join behavior is not supported. + +- `FiberRef.makeContext` -> `Context.Reference`: Define a Context.Reference whose defaultValue returns the Context; custom context diffing is no longer required. + +- `FiberRef.makeRuntimeFlags` -> `none`: RuntimeFlags and specialized FiberRef constructors were removed; migrate each flag to its explicit v4 runtime option. + +- `FiberRef.makeWith` -> `Context.Reference`: Use the lazy defaultValue option on a stable Context.Reference key. + +- `FiberRef.modify` -> `Ref.modify`: Use Ref for mutable state; Context.Reference updates are scoped with Effect.provideService rather than mutated in place. + +- `FiberRef.modifySome` -> `Ref.modifySome`: Use Ref for mutable state; Context.Reference updates are scoped with Effect.provideService rather than mutated in place. + +- `FiberRef.reset` -> `Effect.provideService`: Context.Reference has no in-place reset; scope its default value around the target Effect. + +- `FiberRef.set` -> `Effect.provideService`: Context.Reference values are overridden for an Effect scope instead of mutating the current fiber. + +- `FiberRef.unhandledErrorLogLevel` -> `References.UnhandledLogLevel`: Renamed and represented as a Context.Reference using Severity | undefined instead of Option\. + +- `FiberRef.unsafeMake` -> `Context.Reference`: Context.Reference construction is synchronous; provide a stable identifier and defaultValue. + +- `FiberRef.unsafeMakeContext` -> `Context.Reference`: Define a Context.Reference whose defaultValue returns the Context; there is no specialized unsafe constructor. + +- `FiberRef.unsafeMakeHashSet` -> `Context.Reference`: Define a normal Context.Reference with a readonly set default; specialized differ constructors were removed. + +- `FiberRef.unsafeMakePatch` -> `Context.Reference`: Define a normal Context.Reference; custom Differ, fork patches, and join behavior are not supported in v4. + +- `FiberRef.unsafeMakeSupervisor` -> `none`: Supervisor and FiberRef were removed; track managed fibers explicitly with FiberSet or FiberMap. + +- `FiberRef.update` -> `Ref.update`: Use Ref for mutable state; for fiber-local configuration compute a value and scope it with Effect.provideService. + +- `FiberRef.updateSome` -> `Ref.updateSome`: Use Ref for mutable state; for fiber-local configuration compute a value and scope it with Effect.provideService. + +- `FiberRef.updateSomeAndGet` -> `Ref.updateSomeAndGet`: Use Ref for mutable state; for fiber-local configuration compute a value and scope it with Effect.provideService. + +- `FiberRef.versionMismatchErrorLogLevel` -> `none`: The version-mismatch logging FiberRef was removed and no public v4 Context.Reference replaces it. + +### `effect/FiberRefs` + +- `FiberRefs.FiberRefs` -> `Context.Context`: Fiber-local services and reference overrides are stored directly in the Fiber context in v4. + +- `FiberRefs.FiberRefsSym` -> `none`: FiberRefs and its marker symbol were removed. + +- `FiberRefs.delete` -> `Context.omit`: FiberRefs became fiber Context; omit a Reference key when constructing the replacement Context. + +- `FiberRefs.empty` -> `Context.empty`: Use an empty Context as the starting collection of services and reference overrides. + +- `FiberRefs.fiberRefs` -> `none`: Context does not expose public enumeration of its Reference keys; retain the keys explicitly if enumeration is required. + +- `FiberRefs.forkAs` -> `none`: Context is inherited automatically when a v4 child fiber is forked; custom per-reference fork patches were removed. + +- `FiberRefs.get` -> `Context.getOption`: Read the service as an Option. Context.Reference defaults also produce Some; use Context.getOrUndefined when only stored overrides should count. + +- `FiberRefs.getOrDefault` -> `Context.get`: Reads an override or the Context.Reference default value. + +- `FiberRefs.joinAs` -> `none`: Child-to-parent FiberRef joining was removed; pass results explicitly or merge ordinary Context values where appropriate. + +- `FiberRefs.setAll` -> `Effect.provideContext`: Provide the replacement Context around the Effect that should observe its services and reference overrides. + +- `FiberRefs.unsafeMake` -> `Context.empty().pipe(Context.add(...))`: Build a Context from explicit Reference keys and values; FiberId histories and unsafe local maps no longer exist. + +- `FiberRefs.updateAs` -> `Context.add`: Add or replace a Reference value in Context; the FiberId parameter and history are removed. + +- `FiberRefs.updateManyAs` -> `Context.add`: Apply explicit Context.add calls for each Reference value; FiberId histories and forkAs are removed. + +### `effect/FiberRefsPatch` + +- `FiberRefsPatch.Add` -> `Context.add`: FiberRefsPatch was removed; apply Reference overrides directly to Context. + +- `FiberRefsPatch.AndThen` -> `Context.merge`: FiberRefsPatch was removed; compose Context updates directly, with later values overriding earlier ones. + +- `FiberRefsPatch.Empty` -> `Context.Context`: The empty patch model was removed; an empty Context represents no overrides. + +- `FiberRefsPatch.FiberRefsPatch` -> `none`: The patch data type was removed with FiberRefs; construct or merge Context values directly. + +- `FiberRefsPatch.combine` -> `Context.merge`: FiberRefsPatch was removed; merge the resulting Context values instead of combining patches. + +- `FiberRefsPatch.diff` -> `none`: There is no generic Context diff because FiberRef fork and join patch semantics were removed. + +- `FiberRefsPatch.empty` -> `Context.empty`: Use an empty Context when no services or Reference overrides are applied. + +- `FiberRefsPatch.patch` -> `Context.merge`: Merge explicit Context overrides into the base Context; FiberId-aware patch application no longer exists. + +### `effect/FiberSet` + +- `FiberSet.FiberSet` -> `FiberSet.FiberSet`: Retained; contained runtime fibers now use the unified Fiber type. + +- `FiberSet.TypeId` -> `FiberSet.isFiberSet`: The type-id symbol is private in v4; use the public guard. + +- `FiberSet.unsafeAdd` -> `FiberSet.addUnsafe`: Renamed to put the Unsafe suffix last; the interruptAs option was removed because IDs are now numeric runtime details. + +### `effect/FiberStatus` + +- `FiberStatus.Done` -> `Exit.Exit`: FiberStatus was removed; a defined fiber.pollUnsafe() result indicates completion and contains the Exit. + +- `FiberStatus.FiberStatus` -> `Exit.Exit | undefined`: Use fiber.pollUnsafe(); undefined means incomplete and Exit means completed, with no running/suspended distinction. + +- `FiberStatus.FiberStatusTypeId` -> `none`: FiberStatus and its type-id symbol were removed. + +- `FiberStatus.Running` -> `none`: The public runtime no longer models running status as a value. + +- `FiberStatus.Suspended` -> `none`: The public runtime no longer models suspended status as a value. + +- `FiberStatus.isDone` -> `fiber.pollUnsafe() !== undefined`: Completion is observable by synchronously polling the Fiber. + +- `FiberStatus.isFiberStatus` -> `none`: FiberStatus values no longer exist; inspect a Fiber with pollUnsafe instead. + +- `FiberStatus.isRunning` -> `fiber.pollUnsafe() === undefined`: V4 only exposes incomplete versus completed; it does not distinguish running from suspended. + +- `FiberStatus.isSuspended` -> `none`: The public runtime no longer exposes suspended status. + +- `FiberStatus.running` -> `none`: FiberStatus constructors were removed; keep the Fiber and poll it instead. + +- `FiberStatus.suspended` -> `none`: FiberStatus constructors and public suspended status were removed. + +### `effect/Function` + +- `Function.FunctionN` -> `Function.FunctionN`: No call-site migration; v4 keeps the same function shape as a type alias. + +- `Function.LazyArg` -> `Function.LazyArg`: No call-site migration; v4 keeps the same lazy function shape as a type alias. + +- `Function.isFunction` -> `Predicate.isFunction`: The function refinement moved to Predicate. + +- `Function.unsafeCoerce` -> `Function.cast`: Renamed type-only cast; runtime behavior remains identity with no validation. + +### `effect/GlobalValue` + +- `GlobalValue.globalValue` -> `module-scoped const`: The global registry helper was removed; use a module singleton, or explicitly own a globalThis and Symbol.for registry when cross-bundle identity is required. + +### `effect/Graph` + +- `Graph.Edge` -> `Graph.Edge`: The type remains as a structural interface, but its Data.Class constructor/value export was removed. Replace new Graph.Edge({ source, target, data }) with an object literal. + +- `Graph.Graph` -> `Graph.Graph`: The immutable type remains, but storage is opaque; replace field access with Graph nodes, edges, count, lookup, neighbor, and acyclicity APIs. + +- `Graph.MutableGraph` -> `Graph.MutableGraph`: The mutable type remains but no longer shares a public base interface with immutable Graph; obtain it through Graph.mutate or Graph.beginMutation and use public mutation/query functions. + +- `Graph.Proto` -> `none`: The common graph protocol was removed. Use Graph.Graph or Graph.MutableGraph as appropriate and replace storage-field access with public graph query and mutation functions. + +- `Graph.SearchConfig` -> `Graph.SearchConfig`: The type remains; direction is now Graph.TraversalDirection and also accepts undirected, while radius limits traversal depth. + +### `effect/GroupBy` + +- `GroupBy.GroupBy` -> `Stream]>`: The GroupBy datatype is removed in v4; Stream.groupBy/groupByKey now return an ordinary Stream of readonly [key, substream] pairs, processed with regular Stream operators. + +- `GroupBy.GroupBy.Variance` -> `none`: Variance plumbing for the removed GroupBy datatype; v4 has no GroupBy type, so there is no variance interface to migrate to. + +- `GroupBy.GroupByTypeId` -> `none`: Brand symbol for the removed GroupBy datatype; v4 groupBy results are plain Streams, discriminated with Stream.isStream if needed. + +#### `GroupBy.evaluate` + +**Replacement:** `Stream.flatMap` + +Apply the per-group function over the [key, stream] pairs with Stream.flatMap (or Stream.mapEffect for an effectful result per group), using { concurrency: "unbounded" } to reproduce v3's parallel-groups/arbitrary-merge-order behavior; the v3 bufferSize option moved onto Stream.groupBy itself. + +**Example** + +```ts +// v3: stream.pipe(Stream.groupByKey(f), GroupBy.evaluate((key, s) => g(key, s))) +stream.pipe( + Stream.groupByKey(f), + Stream.flatMap(([key, s]) => g(key, s), { concurrency: "unbounded" }) +) + +``` + +- `GroupBy.filter` -> `Stream.filter`: Filter the groups by key with an ordinary Stream.filter on the pairs: Stream.filter(([key]) =\> predicate(key)). + +- `GroupBy.first` -> `Stream.take`: Keep only the first n groups with an ordinary Stream.take(n) on the [key, stream] pair stream. + +- `GroupBy.make` -> `none`: No wrapper to construct in v4: a grouped stream is just any Stream\]\>, so build the pair stream directly (Stream.groupBy/groupByKey produce it); the v3 shape Stream\<[K, Dequeue\\>]\> is gone along with the queue-of-Take encoding. + +### `effect/Hash` + +- `Hash.cached` -> `none`: Delete Hash.cached wrappers and return the computed value from Hash.symbol; Hash.hash now caches objects automatically in a private WeakMap without mutating them. + +### `effect/HashMap` + +- `HashMap.HashMap` -> `HashMap.HashMap`: The immutable two-parameter model remains; use public operations rather than depending on its representation. + +- `HashMap.TypeId` -> `HashMap.isHashMap`: The brand is private; use HashMap.isHashMap for runtime refinement and HashMap.HashMap\ in type positions. + +- `HashMap.countBy` -> `HashMap.reduce`: Count matches with HashMap.reduce(self, 0, (count, value, key) =\> count + (predicate(value, key) ? 1 : 0)). + +- `HashMap.keySet` -> `HashSet.fromIterable + HashMap.keys`: Construct the set with HashSet.fromIterable(HashMap.keys(self)); no direct keySet helper remains. + +- `HashMap.unsafeGet` -> `HashMap.getUnsafe`: Direct word-order rename; it still throws for a missing key. + +### `effect/HashSet` + +- `HashSet.HashSet` -> `HashSet.HashSet`: The immutable model remains, but the brand is private and transient mutation helpers were removed. + +- `HashSet.TypeId` -> `HashSet.isHashSet`: The brand is private; use HashSet.isHashSet for runtime refinement and HashSet.HashSet\ in type positions. + +- `HashSet.beginMutation` -> `none`: Transient mutation mode was removed; reassign immutable add/remove results or build a batch with HashSet.fromIterable. + +- `HashSet.endMutation` -> `none`: There is no mutation window to finalize; remove this call and use the latest immutable HashSet value. + +- `HashSet.flatMap` -> `HashSet.fromIterable + Iterable.flatMap`: Preserve set deduplication with HashSet.fromIterable(Iterable.flatMap(self, f)); no direct flatMap remains. + +- `HashSet.forEach` -> `Iterable.forEach`: HashSet remains Iterable, so Iterable.forEach(self, f) preserves eager side-effecting traversal. + +- `HashSet.mutate` -> `none`: Transient mutation was removed; reassign immutable HashSet.add/remove results or build a complete replacement with HashSet.fromIterable. + +- `HashSet.partition` -> `HashSet.filter`: Build [excluded, satisfying] with complementary HashSet.filter calls, or use one reduction when the predicate is expensive. + +- `HashSet.toValues` -> `Array.from`: HashSet remains iterable; Array.from(self) produces the former Array result. + +- `HashSet.toggle` -> `HashSet.has + HashSet.remove / HashSet.add`: Use HashSet.has(self, value) ? HashSet.remove(self, value) : HashSet.add(self, value). + +- `HashSet.values` -> `none`: The HashSet itself is iterable; iterate it directly or call self[Symbol.iterator]() when an iterator object is required. + +### `effect/Inspectable` + +- `Inspectable.format` -> `Formatter.formatJson`: Use Formatter.formatJson(input, { space: 2 }) to preserve the v3 helper's pretty-printed JSON output. + +- `Inspectable.redact` -> `Redactable.redact`: The redaction protocol moved to Redactable and now receives the current fiber Context. + +- `Inspectable.stringifyCircular` -> `Formatter.formatJson`: Use Formatter.formatJson(input, { space: whitespace }); it handles redaction and ancestor cycles. + +- `Inspectable.toJSON` -> `Inspectable.toJson`: Renamed to lower-camel toJson with the same recursive conversion role. + +- `Inspectable.withRedactableContext` -> `none`: Manual FiberRefs scoping was removed; Redactable.redact uses the current fiber Context automatically. + +### `effect/Iterable` + +- `Iterable.flatMapNullable` -> `Iterable.flatMapNullishOr`: Direct nullish-terminology rename; it remains lazy and drops null or undefined mapper results. + +- `Iterable.getLefts` -> `Iterable.getFailures`: Either became Result; this lazily extracts failure payloads. + +- `Iterable.getRights` -> `Iterable.getSuccesses`: Either became Result; this lazily extracts success payloads. + +- `Iterable.unsafeHead` -> `Iterable.headUnsafe`: Direct word-order rename; it still throws on an empty Iterable. + +### `effect/JSONSchema` + +- `JSONSchema.JsonSchema7` -> `JsonSchema.JsonSchema`: The draft-07-specific union was replaced by the dialect-neutral JSON Schema model. + +- `JSONSchema.JsonSchema7Any` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7AnyOf` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Array` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Boolean` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Enum` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Enums` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Integer` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Never` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Null` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Number` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Numeric` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Object` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Ref` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Root` -> `JsonSchema.Document<"draft-07">`: Use a typed JSON Schema document for a draft-07 root and definitions. + +- `JSONSchema.JsonSchema7String` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Unknown` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7Void` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7empty` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchema7object` -> `JsonSchema.JsonSchema`: Individual draft-07 node interfaces were consolidated into JsonSchema.JsonSchema. + +- `JSONSchema.JsonSchemaAnnotations` -> `Schema.Annotations.Documentation`: Schema metadata now uses string-keyed Schema annotations; JSON Schema-specific checks use toJsonSchema annotations. + +#### `JSONSchema.fromAST` + +**Replacement:** `Schema.toJsonSchemaDocument` + +Wrap a low-level AST with Schema.make, then generate a document; v4 generation targets draft 2020-12. + +**Example** + +```ts +Schema.toJsonSchemaDocument(Schema.make(ast)) +``` + +#### `JSONSchema.make` + +**Replacement:** `Schema.toJsonSchemaDocument` + +Generate draft 2020-12, then call JsonSchema.toDocumentDraft07 when draft-07 output is required. + +**Example** + +```ts +JsonSchema.toDocumentDraft07(Schema.toJsonSchemaDocument(schema)) +``` + +### `effect/KeyedPool` + +- `KeyedPool.KeyedPool` -> `RcMap.RcMap>`: Model keyed pools as an RcMap whose scoped lookup creates one Pool per key. + +- `KeyedPool.KeyedPool.Variance` -> `none`: KeyedPool and its variance marker were removed; use the RcMap and Pool public models without depending on branding internals. + +- `KeyedPool.KeyedPoolTypeId` -> `none`: KeyedPool was removed, so its runtime type id has no v4 equivalent. + +- `KeyedPool.get` -> `RcMap.get + Pool.get`: KeyedPool was removed; acquire the per-key Pool from an RcMap, then borrow an item with Pool.get in the current Scope. + +- `KeyedPool.invalidate` -> `RcMap.get + Pool.invalidate`: KeyedPool was removed; retain the key, get its Pool from RcMap, and call Pool.invalidate for the item. + +- `KeyedPool.make` -> `RcMap.make + Pool.make`: Create an RcMap with lookup key =\> Pool.make({ acquire: acquire(key), size }); RcMap.get followed by Pool.get replaces keyed borrowing. + +- `KeyedPool.makeWith` -> `RcMap.make + Pool.make`: Create an RcMap whose lookup uses Pool.make with size: size(key). + +- `KeyedPool.makeWithTTL` -> `RcMap.make + Pool.makeWithTTL`: Create an RcMap whose lookup uses Pool.makeWithTTL with min(key), max(key), and the shared timeToLive. + +- `KeyedPool.makeWithTTLBy` -> `RcMap.make + Pool.makeWithTTL`: Create an RcMap whose lookup uses Pool.makeWithTTL with min(key), max(key), and timeToLive(key). + +### `effect/Layer` + +- `Layer.CurrentMemoMap` -> `Layer.CurrentMemoMap`: The service remains but is now a Context.Service class with forkOrCreate. + +- `Layer.Layer` -> `Layer.Layer`: The type remains with Layer\ parameter order. + +- `Layer.Layer.Context` -> `Layer.Services`: The input-services extractor moved to the module level and was renamed. + +- `Layer.LayerTypeId` -> `none`: The marker is private in v4; use Layer.Any or Layer.Variance for type constraints. + +- `Layer.MemoMap` -> `Layer.MemoMap`: The interface remains and now supports parent-child ambient maps. + +- `Layer.MemoMapTypeId` -> `none`: The MemoMap marker is private in v4. + +- `Layer.annotateLogs` -> `Layer.fromBuild((memoMap, scope) => Effect.annotateLogs(Layer.buildWithMemoMap(self, memoMap, scope), ...annotations))`: Apply Effect.annotateLogs to the layer acquisition effect. + +- `Layer.annotateSpans` -> `Layer.fromBuild((memoMap, scope) => Effect.annotateSpans(Layer.buildWithMemoMap(self, memoMap, scope), ...annotations))`: Apply Effect.annotateSpans to the layer acquisition effect. + +- `Layer.catchAll` -> `Layer.catch`: The typed-error handler was renamed. + +- `Layer.catchAllCause` -> `Layer.catchCause`: The cause handler was renamed. + +- `Layer.context` -> `Layer.effectContext(Effect.context())`: Capture and return the current service context. + +- `Layer.die` -> `Layer.unwrap(Effect.die(defect))`: Lift the Effect defect constructor. + +- `Layer.dieSync` -> `Layer.unwrap(Effect.suspend(() => Effect.die(evaluate())))`: Suspend evaluation and lift Effect.die; Effect.dieSync was also removed. + +- `Layer.discard` -> `Layer.flatMap(self, () => Layer.empty)`: Build the layer while dropping its output context. + +- `Layer.ensureErrorType` -> `Layer.satisfiesErrorType`: The type constraint helper was renamed. + +- `Layer.ensureRequirementsType` -> `Layer.satisfiesServicesType`: The requirements type constraint was renamed to services. + +- `Layer.ensureSuccessType` -> `Layer.satisfiesSuccessType`: The type constraint helper was renamed. + +- `Layer.extendScope` -> `Layer.buildWithScope(self, outerScope) and Effect.provideContext(program, context)`: Explicitly build against the desired outer scope and provide the resulting context. + +- `Layer.fail` -> `Layer.unwrap(Effect.fail(error))`: Lift the Effect failure constructor. + +- `Layer.failCause` -> `Layer.unwrap(Effect.failCause(cause))`: Lift the Effect cause-failure constructor. + +- `Layer.failCauseSync` -> `Layer.unwrap(Effect.failCauseSync(evaluate))`: Lift the retained Effect constructor. + +- `Layer.failSync` -> `Layer.unwrap(Effect.failSync(evaluate))`: Lift the retained Effect constructor. + +- `Layer.fiberRefLocallyScopedWith` -> `Layer.effect(reference, Effect.map(reference, f))`: FiberRef was removed; compute and provide a transformed Context.Reference value. + +- `Layer.flatten` -> `Layer.flatMap(self, (context) => Context.get(context, key))`: Expand the removed convenience combinator with flatMap and Context.get. + +- `Layer.function` -> `Layer.effect(keyB, Effect.map(keyA, f))`: Read the input service through its Context.Key and provide the transformed service. + +- `Layer.isFresh` -> `none`: Layer.fresh remains, but its wrapper has no public freshness predicate. + +- `Layer.locally` -> `Layer.updateService(self, reference, () => value)`: Replace FiberRef-local configuration with Context.Reference provision. + +- `Layer.locallyEffect` -> `Layer.fromBuild((memoMap, scope) => f(Layer.buildWithMemoMap(self, memoMap, scope)))`: Transform the public layer acquisition effect directly. + +- `Layer.locallyScoped` -> `Layer.succeed(reference, value)`: Provide a v4 Context.Reference value as a configuration layer. + +- `Layer.locallyWith` -> `Layer.updateService(self, reference, f)`: Transform a Context.Reference during layer acquisition. + +- `Layer.map` -> `Layer.flatMap(self, (context) => Layer.succeedContext(f(context)))`: Expand the removed output-context mapping combinator. + +- `Layer.mapError` -> `Layer.fromBuild((memoMap, scope) => Effect.mapError(Layer.buildWithMemoMap(self, memoMap, scope), f))`: Transform the typed error of layer acquisition. + +- `Layer.match` -> `Layer.fromBuild with Effect.matchEffect over Layer.buildWithMemoMap`: Fold the source acquisition effect, then build the selected failure or success layer. + +- `Layer.matchCause` -> `Layer.fromBuild with Effect.matchCauseEffect over Layer.buildWithMemoMap`: Fold the source acquisition cause, then build the selected failure or success layer. + +- `Layer.memoize` -> `automatic shared memoization under Effect.provide`: Reuse the same Layer value; use { local: true } or Layer.fresh to opt out, or MemoMap APIs for manual control. + +- `Layer.orElse` -> `Layer.catch(self, () => fallback())`: Expand the removed lazy fallback alias with Layer.catch. + +- `Layer.passthrough` -> `Layer.merge(Layer.effectContext(Effect.context()), self)`: Capture required input services and merge them into the layer output. + +- `Layer.project` -> `Layer.flatMap(self, (context) => Layer.succeed(keyB, f(Context.get(context, keyA))))`: Project one derived service and drop the other outputs. + +- `Layer.retry` -> `Effect.retry(acquire, schedule) before Layer.effect or Layer.effectContext`: Retry the acquisition Effect; for an arbitrary layer, rebuild a fresh layer for each attempt through Layer.fromBuild. + +- `Layer.scope` -> `Layer.effect(Scope.Scope, Effect.acquireRelease(Scope.make(), Scope.close))`: Construct and close a child scope explicitly. + +- `Layer.scoped` -> `Layer.effect`: Scoped acquisition was merged into Layer.effect, which supplies and excludes the layer Scope. + +- `Layer.scopedContext` -> `Layer.effectContext`: Scoped context acquisition was merged into Layer.effectContext. + +- `Layer.scopedDiscard` -> `Layer.effectDiscard`: Scoped discard acquisition was merged into Layer.effectDiscard. + +- `Layer.service` -> `Layer.effect(key, key)`: A Context.Key is an Effect that reads and passes through its service. + +- `Layer.setClock` -> `Layer.succeed(Clock.Clock, clock)`: Clock.Clock is now a Context.Reference; provide it directly. + +- `Layer.setConfigProvider` -> `ConfigProvider.layer(configProvider)`: Use the dedicated ConfigProvider layer constructor. + +- `Layer.setRandom` -> `Layer.succeed(Random.Random, random)`: Random.Random is now a Context.Reference; provide it directly. + +- `Layer.setRequestBatching` -> `none`: Requests now use resolver-driven batching and expose no batching switch. + +- `Layer.setRequestCache` -> `none`: The public Request.Cache and its configuration API were removed. + +- `Layer.setRequestCaching` -> `none`: The public request-caching toggle was removed. + +- `Layer.setScheduler` -> `Layer.succeed(Scheduler.Scheduler, scheduler)`: Scheduler.Scheduler is now a Context.Reference; provide it directly. + +- `Layer.setTracer` -> `Layer.succeed(Tracer.Tracer, tracer)`: Tracer.Tracer is now a Context.Reference; provide it directly. + +- `Layer.setTracerEnabled` -> `Layer.succeed(References.TracerEnabled, enabled)`: Provide the v4 Reference instead of setting a FiberRef. + +- `Layer.setTracerTiming` -> `Layer.succeed(References.TracerTimingEnabled, enabled)`: Provide the renamed v4 Reference instead of setting a FiberRef. + +- `Layer.setUnhandledErrorLogLevel` -> `Layer.succeed(References.UnhandledLogLevel, severityOrUndefined)`: Provide LogLevel.Severity or undefined instead of Option\. + +- `Layer.setVersionMismatchErrorLogLevel` -> `none`: No version-mismatch log-level Reference or public replacement exists. + +- `Layer.tapErrorCause` -> `Layer.tapCause`: The cause observer was renamed. Its callback must accept the source layer's full error cause; a callback narrowed to only part of the error union is rejected. + +- `Layer.toRuntime` -> `Layer.build(self), then Effect.runForkWith, Effect.runPromiseWith, or Effect.runSyncWith`: Runtime\ was removed; build a Context, or use ManagedRuntime.make for a reusable managed runner. + +- `Layer.toRuntimeWithMemoMap` -> `Layer.buildWithMemoMap(self, memoMap, scope), then Effect.run*With(context)`: Explicit memo-map building now yields a Context rather than a Runtime. + +- `Layer.unwrapEffect` -> `Layer.unwrap`: The Effect-based unwrap constructor was renamed and generalized. + +- `Layer.unwrapScoped` -> `Layer.unwrap`: Scoped and unscoped unwrap were merged; Layer.unwrap supplies and excludes the layer Scope. + +- `Layer.updateService` -> `Layer.updateService`: The combinator remains and now accepts any Context.Key. + +- `Layer.zipWith` -> `Layer.fromBuild with concurrent Effect.zipWith over Layer.buildWithMemoMap`: Combine acquisition effects directly; use Layer.merge when the function only merged Context values. + +### `effect/LayerMap` + +- `LayerMap.LayerMap` -> `LayerMap.LayerMap`: The type remains; runtime(key) became contextEffect(key) and returns Context. + +- `LayerMap.Service` -> `LayerMap.Service`: Use layer instead of Default, layerNoDeps instead of DefaultWithoutDependencies, and contextEffect instead of runtime. Preloading does not remove acquisition errors from later lookups, which can reacquire expired or invalidated entries. + +- `LayerMap.Service.Context` -> `LayerMap.Service.Services`: The input-services extractor was renamed. + +- `LayerMap.TagClass` -> `LayerMap.TagClass`: The type remains and now extends Context.ServiceClass; use the renamed layer and contextEffect members. + +- `LayerMap.TypeId` -> `none`: The LayerMap marker is private in v4 and no public guard exists. + +### `effect/List` + +- `List.Cons` -> `Array.NonEmptyReadonlyArray`: Use the immutable non-empty array type; constructors may return the assignable mutable NonEmptyArray subtype. + +- `List.List` -> `ReadonlyArray`: Replace the persistent linked-list representation with ReadonlyArray\. + +- `List.List.AndNonEmpty` -> `Array.ReadonlyArray.AndNonEmpty`: Use the corresponding readonly-array utility type. + +- `List.List.OrNonEmpty` -> `Array.ReadonlyArray.OrNonEmpty`: Use the corresponding readonly-array utility type. + +- `List.List.With` -> `Array.ReadonlyArray.With`: Use the corresponding readonly-array utility type. + +- `List.Nil` -> `none`: Represent this case as readonly []; there is no tagged Nil interface in v4. + +- `List.TypeId` -> `none`: Arrays have no List runtime marker; remove TypeId inspection. + +- `List.append` -> `Array.append`: List was removed; use Array.append. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.appendAll` -> `Array.appendAll`: List was removed; use Array.appendAll. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.cons` -> `Array.prepend`: List was removed; change List.cons(head, tail) to Array.prepend(tail, head). + +- `List.empty` -> `Array.empty`: List was removed; use Array.empty. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.every` -> `Array.every`: List was removed; run the predicate against the replacement array with Array.every. + +- `List.filter` -> `Array.filter`: List was removed; use Array.filter. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.filterMap` -> `Array.filterMap`: List was removed; use Array.filterMap and change the callback from Option to Result. + +- `List.flatMap`: TODO: needs guidance + +- `List.fromIterable` -> `Array.fromIterable`: List was removed; use Array.fromIterable. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.getEquivalence` -> `Array.makeEquivalence`: List was removed; compare the replacement arrays with Array.makeEquivalence. + +- `List.head` -> `Array.head`: List was removed; use Array.head. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.isCons` -> `Array.isReadonlyArrayNonEmpty`: List was removed; this checks that the replacement readonly array is non-empty. + +- `List.isList` -> `Array.isArray`: The List brand is gone; this now recognizes the replacement JavaScript array representation. + +- `List.isNil` -> `Array.isReadonlyArrayEmpty`: List was removed; this checks that the replacement readonly array is empty. + +- `List.last` -> `Array.last`: List was removed; use Array.last. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.make` -> `Array.make`: List was removed; use Array.make. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.map` -> `Array.map`: List was removed; use Array.map. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.nil` -> `Array.empty`: List was removed; represent Nil with an empty array. + +- `List.of` -> `Array.of`: List was removed; use Array.of. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.partition` -> `Array.partition`: Use a Result-returning callback: failure values form the first array and success values the second. + +- `List.partitionMap` -> `Array.partition`: Migrate the Either-returning mapper to Result; failures form the first array and successes the second. + +- `List.prependAll` -> `Array.prependAll`: List was removed; use Array.prependAll. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.prependAllReversed` -> `Array.prependAll + Array.reverse`: Use Array.prependAll(self, Array.reverse(prefix)) to preserve the old ordering. + +- `List.reduce` -> `Array.reduce`: List was removed; use Array.reduce. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.reduceRight` -> `Array.reduceRight`: List was removed; use Array.reduceRight. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.reverse` -> `Array.reverse`: List was removed; use Array.reverse. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.size` -> `Array.length`: List was removed; use the replacement array length helper or the .length property. + +- `List.some` -> `Array.some`: List was removed; run the predicate against the replacement array with Array.some. + +- `List.splitAt` -> `Array.splitAt`: List was removed; use Array.splitAt. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.tail` -> `Array.tail`: List was removed; use Array.tail. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.take` -> `Array.take`: List was removed; use Array.take. It preserves ordering but returns arrays rather than persistent linked lists. + +- `List.toArray` -> `Array.fromIterable`: After migrating the representation this is usually unnecessary; use Array.fromIterable when a fresh mutable array is required. + +- `List.toChunk` -> `Chunk.fromIterable`: Convert the replacement array or other iterable with Chunk.fromIterable. + +- `List.unsafeHead` -> `Array.headNonEmpty`: Use a NonEmptyReadonlyArray proof before accessing the head; the v4 helper does not accept an empty array. + +- `List.unsafeLast` -> `Array.lastNonEmpty`: Use a NonEmptyReadonlyArray proof before accessing the last element; the v4 helper does not accept an empty array. + +- `List.unsafeTail` -> `Array.tailNonEmpty`: Use a NonEmptyReadonlyArray proof before taking the tail; the v4 helper does not accept an empty array. + +### `effect/LogLevel` + +- `LogLevel.All` -> `"All"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Debug` -> `"Debug"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Error` -> `"Error"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Fatal` -> `"Fatal"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Info` -> `"Info"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Literal` -> `LogLevel.LogLevel`: This is the all-level replacement after renaming Warning to Warn. LogLevel.Severity is narrower because it excludes All and None. + +- `LogLevel.LogLevel` -> `LogLevel.LogLevel`: The name remains, but the representation is a string union and object fields such as \_tag, label, syslog, and ordinal are gone. Use toUpperCase() for labels and LogLevel.getOrdinal for ordering. + +- `LogLevel.None` -> `"None"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Trace` -> `"Trace"`: V4 levels are string literals rather than branded objects; use the literal as both value and singleton type. + +- `LogLevel.Warning` -> `"Warn"`: V4 renamed both the value and singleton type from Warning to the string literal Warn. + +- `LogLevel.allLevels` -> `LogLevel.values`: Use the ordered v4 array of all levels, including All and None. + +- `LogLevel.fromLiteral` -> `literal === "Warning" ? "Warn" : literal`: No constructor is needed because v4 levels are strings. Normalize the renamed Warning literal to Warn; all other v3 literals pass through. + +- `LogLevel.greaterThan` -> `LogLevel.isGreaterThan`: Direct rename; ordering remains severity ordering. + +- `LogLevel.greaterThanEqual` -> `LogLevel.isGreaterThanOrEqualTo`: Direct rename. + +- `LogLevel.lessThan` -> `LogLevel.isLessThan`: Direct rename. + +- `LogLevel.lessThanEqual` -> `LogLevel.isLessThanOrEqualTo`: Direct rename. + +- `LogLevel.locally` -> `Effect.provideService(effect, References.CurrentLogLevel, level)`: Current log level is now a reference. For threshold configuration, including All or None, provide References.MinimumLogLevel instead. + +### `effect/LogSpan` + +- `LogSpan.LogSpan` -> `readonly [label: string, timestamp: number]`: The module was removed. Active log spans are tuples in References.CurrentLogSpans; ordinary callers should prefer Effect.withLogSpan. + +- `LogSpan.make` -> `[label, startTime] as const`: Construct the tuple directly, or use Effect.withLogSpan so Effect obtains the timestamp and scopes the span. + +- `LogSpan.render` -> `custom tuple formatter`: No public standalone renderer remains. Built-in loggers format span tuples internally; custom formatters can render label and elapsed milliseconds themselves. + +### `effect/Logger` + +- `Logger.Logger` -> `Logger.Logger`: The name remains. Logger.Options now has fiber instead of fiberId; read the id from fiber.id and annotations or spans through fiber references. + +- `Logger.Logger.Variance` -> `none`: Public variance metadata was removed; use Logger.Logger\ directly. + +- `Logger.LoggerTypeId` -> `Logger.isLogger`: The brand is private in v4; use the public runtime guard. + +- `Logger.add` -> `Logger.layer([logger], { mergeWithExisting: true })`: Logger installation is whole-set based; mergeWithExisting reproduces add. + +- `Logger.addEffect` -> `Logger.layer([loggerEffect], { mergeWithExisting: true })`: Logger.layer accepts effects that construct loggers. + +- `Logger.addScoped` -> `Logger.layer([scopedLoggerEffect], { mergeWithExisting: true })`: Layer acquisition handles the scoped effect; the separate scoped constructor is gone. + +- `Logger.batched` -> `Logger.batched(logger, { window, flush })`: The trailing arguments moved into one options object. Provide any services needed by flush before constructing it. + +- `Logger.filterLogLevel` -> `Logger.make(options => predicate(options.logLevel) ? Option.some(logger.log(options)) : Option.none())`: No named combinator remains; rebuild the wrapper with Logger.make. Prefer References.MinimumLogLevel for ordinary threshold filtering. + +- `Logger.json` -> `Logger.layer([Logger.consoleJson, Logger.tracerLogger])`: Logger.layer replaces the active set. Include tracerLogger to preserve v3 built-in layer behavior, or omit it when trace log events are intentionally disabled. + +- `Logger.jsonLogger` -> `Logger.formatJson`: Formatter rename; v4 JSON output uses level rather than logLevel. + +- `Logger.logFmt` -> `Logger.layer([Logger.consoleLogFmt, Logger.tracerLogger])`: Logger.layer replaces the active set; include tracerLogger to preserve v3 built-in layer behavior. + +- `Logger.logfmtLogger` -> `Logger.formatLogFmt`: Formatter rename and capitalization change. + +- `Logger.map` -> `Logger.map`: Retained with the same output-mapping behavior. + +- `Logger.mapInput` -> `Logger.make(options => logger.log({ ...options, message: f(options.message) }))`: No named input contramap remains; rebuild it with Logger.make. + +- `Logger.mapInputOptions` -> `Logger.make(options => logger.log(f(options)))`: No named options contramap remains; rebuild it with Logger.make and adapt f to the v4 Logger.Options shape. + +- `Logger.minimumLogLevel` -> `Layer.succeed(References.MinimumLogLevel, level)`: Minimum log level is now a context reference. + +- `Logger.none` -> `Logger.make(() => undefined)`: Rebuild the no-op logger with Logger.make. + +- `Logger.pretty` -> `Logger.layer([Logger.consolePretty(), Logger.tracerLogger])`: Logger.layer replaces the active set; include tracerLogger to preserve v3 built-in layer behavior. + +- `Logger.prettyLogger` -> `Logger.consolePretty`: Renamed to consolePretty. Remove the stderr option; provide Logger.LogToStderr with true to route TTY output to console.error. Colors, formatDate, and mode remain constructor options. + +- `Logger.prettyLoggerDefault` -> `Logger.consolePretty()`: The prebuilt singleton became a constructor call. + +- `Logger.remove` -> `Logger.layer([...desiredLoggers])`: No named removal combinator remains. Declare the complete desired logger set; transform Logger.CurrentLoggers only when removing from an unknown inherited set is unavoidable. + +- `Logger.replace` -> `Logger.layer([...desiredLoggers])`: V4 replaces the whole active set. When replacing the old default logger, include Logger.tracerLogger explicitly if it must survive. + +- `Logger.replaceEffect` -> `Logger.layer([loggerEffect, ...otherLoggers])`: Logger.layer accepts effects. Explicitly list every logger that must remain active. + +- `Logger.replaceScoped` -> `Logger.layer([scopedLoggerEffect, ...otherLoggers])`: Logger.layer acquisition supplies the scope; explicitly list every logger that must remain active. + +- `Logger.simple` -> `Logger.make(({ message }) => log(message))`: V3 simple was a message-only custom logger constructor; rebuild it with Logger.make. + +- `Logger.stringLogger` -> `Logger.formatSimple`: The prebuilt string formatter was renamed. + +- `Logger.structured` -> `Logger.layer([Logger.consoleStructured, Logger.tracerLogger])`: Logger.layer replaces the active set; include tracerLogger to preserve v3 built-in layer behavior. + +- `Logger.structuredLogger` -> `Logger.formatStructured`: Formatter rename; its output field logLevel is now named level. + +- `Logger.succeed` -> `Logger.make(() => value)`: Rebuild the constant-output logger with Logger.make. + +- `Logger.sync` -> `Logger.make(() => evaluate())`: Rebuild the lazy-output logger; evaluate still runs once per log event. + +- `Logger.test` -> `Effect.log(input).pipe(Effect.provide(Logger.layer([capturingLogger])))`: No synthetic-options helper remains. Exercise the logger through the runtime and capture its output so it receives a real Fiber, cause, level, and date. + +- `Logger.withMinimumLogLevel` -> `Effect.provideService(effect, References.MinimumLogLevel, level)`: Replace the FiberRef-local helper with reference provisioning. + +- `Logger.withSpanAnnotations` -> `custom Logger.make wrapper using options.fiber.cache.span`: No transparent generic equivalent remains. Read span identity from options.fiber.cache.span and add it to custom output as needed. + +- `Logger.zip` -> `Logger.make(options => [left.log(options), right.log(options)])`: No named combinator remains; invoke both loggers and return their output tuple. + +- `Logger.zipLeft` -> `Logger.make(options => { const output = left.log(options); right.log(options); return output })`: Rebuild explicitly, preserving evaluation of both loggers and returning the left output. + +- `Logger.zipRight` -> `Logger.make(options => { left.log(options); return right.log(options) })`: Rebuild explicitly, preserving evaluation order and returning the right output. + +### `effect/Mailbox` + +- `Mailbox.Mailbox` -> `Queue.Queue`: Mailbox was folded into Queue; include Cause.Done in the error channel when normal end signaling is used. + +- `Mailbox.ReadonlyMailbox` -> `Queue.Dequeue`: Use explicit Queue taking operations; Queue.Dequeue is not itself an Effect yielding message chunks. + +- `Mailbox.ReadonlyTypeId` -> `Queue.isDequeue`: The public Mailbox type id was removed; use the Queue.isDequeue guard instead. + +- `Mailbox.TypeId` -> `Queue.isQueue`: The public Mailbox type id was removed; use the Queue.isQueue guard instead. + +- `Mailbox.fromStream` -> `Stream.toQueue`: Mailbox was renamed and folded into Queue; Stream.toQueue returns a scoped Queue.Dequeue whose error includes Cause.Done. + +- `Mailbox.into` -> `Queue.into`: Use Queue.into with a Queue.Enqueue whose error channel includes Cause.Done. + +- `Mailbox.isMailbox` -> `Queue.isQueue`: Mailbox became the completion-aware v4 Queue model. + +- `Mailbox.isReadonlyMailbox` -> `Queue.isDequeue`: ReadonlyMailbox became Queue.Dequeue. + +- `Mailbox.make` -> `Queue.make`: Pass the v4 options object with optional capacity and strategy; a numeric capacity argument must become { capacity }. + +- `Mailbox.toStream` -> `Stream.fromQueue`: Convert a Queue.Dequeue to a Stream; Cause.Done is excluded from the resulting stream error type. + +### `effect/ManagedRuntime` + +- `ManagedRuntime.ManagedRuntime` -> `ManagedRuntime.ManagedRuntime`: The handle remains but is no longer an Effect; runtimeEffect/runtime became contextEffect/context, and make accepts { memoMap }. + +- `ManagedRuntime.ManagedRuntime.Context` -> `ManagedRuntime.ManagedRuntime.Services`: The context extractor was renamed to Services. + +- `ManagedRuntime.ManagedRuntimeUnify` -> `none`: ManagedRuntime no longer extends Effect, so its unification artifact was removed; call run methods or contextEffect explicitly. + +- `ManagedRuntime.ManagedRuntimeUnifyIgnore` -> `none`: ManagedRuntime no longer extends Effect, so the Unify-ignore artifact was removed. + +- `ManagedRuntime.TypeId` -> `ManagedRuntime.isManagedRuntime`: The marker is private; use the public guard for runtime narrowing. + +### `effect/Match` + +- `Match.Matcher` -> `Match.Matcher`: The type is retained, but its fifth argument is now a flavor marker (ValueFlavor for Match.value and never for Match.type or Match.fn) rather than the provided value; an optional seventh Args tuple tracks Match.fn selector arguments. Prefer inference from Match.type, Match.value, or Match.fn and update hand-written Matcher annotations. + +- `Match.MatcherTypeId` -> `none`: The public matcher brand was internalized. Obtain matchers from Match.type or Match.value and use their public \_tag when discrimination is required. + +- `Match.Not` -> `Match.Not`: The case type is retained. Its evaluate method now receives any Match.fn selector arguments after the selected input; update custom case implementations that consume those arguments. + +- `Match.SafeRefinementId` -> `none`: The public safe-refinement brand was internalized. Use Predicate.Refinement, Predicate.Predicate, or a built-in Match refinement instead of constructing the brand. + +- `Match.TypeMatcher` -> `Match.TypeMatcher`: The public type is retained, but its brand is private; create values with Match.type rather than implementing the interface. + +- `Match.Types` -> `Match.Types`: The public type-level namespace is retained with no call-site migration. + +- `Match.Types.ExtractAndNarrow` -> `Match.Types.ExtractAndNarrow`: The type-only matching helper is retained unchanged. + +- `Match.Types.MaybeReplace` -> `Match.Types.MaybeReplace`: The type-only matching helper is retained unchanged. + +- `Match.Types.NonFailKeys` -> `Match.Types.NonFailKeys`: The type-only matching helper is retained unchanged. + +- `Match.Types.PForNotMatch` -> `Match.Types.PForNotMatch`: The type-only matching helper is retained unchanged. + +- `Match.Types.ResolvePred` -> `Match.Types.ResolvePred`: The type-only matching helper is retained unchanged. + +- `Match.Types.SafeRefinementR` -> `Match.Types.SafeRefinementR`: The type-only matching helper is retained unchanged. + +- `Match.Types.ToInvertedRefinement` -> `Match.Types.ToInvertedRefinement`: The type-only matching helper is retained unchanged. + +- `Match.Types.ToSafeRefinement` -> `Match.Types.ToSafeRefinement`: The type-only matching helper is retained unchanged. + +- `Match.ValueMatcher` -> `Match.ValueMatcher`: The type is retained, but value now uses Result instead of Either and the brand is private; create values with Match.value. + +- `Match.When` -> `Match.When`: The case type is retained. Its evaluate method now receives any Match.fn selector arguments after the selected input; update custom case implementations that consume those arguments. + +- `Match.either` -> `Match.result`: Renamed finalizer with a container change: matched Right and unmatched Left become Result.Success and Result.Failure. + +### `effect/MergeDecision` + +- `MergeDecision.Await` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.AwaitConst` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.Done` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.MergeDecision` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.MergeDecision.Variance` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.MergeDecisionTypeId` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.isMergeDecision` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +- `MergeDecision.match` -> `Channel.merge`: Replace Channel.mergeWith decision callbacks with Channel.merge and haltStrategy. V4 has no MergeDecision values; custom effectful exit folding must be restructured. + +### `effect/MergeStrategy` + +- `MergeStrategy.BackPressure` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.BufferSliding` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.MergeStrategy` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.MergeStrategy.Proto` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.MergeStrategyTypeId` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.isBackPressure` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.isBufferSliding` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.isMergeStrategy` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +- `MergeStrategy.match` -> `Channel.mergeAll`: Remove the MergeStrategy value and configure the v4 merge directly: backpressure is the default; sliding replacement uses Channel.mergeAll with switch: true or Channel.switchMap. + +### `effect/Metric` + +- `Metric.Metric` -> `Metric.Metric`: Drop the v3 key-type parameter. Metrics are no longer callable; use Effect.trackSuccesses for instrumentation and Metric.update or Metric.value for operations. + +- `Metric.Metric.Variance` -> `none`: The public variance interface was removed; Metric\ carries variance markers directly. + +- `Metric.MetricApply` -> `none`: Removed with Metric.make; v4 has no public low-level custom-metric constructor type. + +- `Metric.MetricTypeId` -> `Metric.isMetric`: The public unique-symbol type id was removed; use Metric.isMetric for runtime refinement. + +- `Metric.fiberActive` -> `Metric.enableRuntimeMetrics + Metric.snapshot`: The concrete metric is no longer exported. Enable runtime metrics, then read the Gauge snapshot whose id is child\_fibers\_active. + +- `Metric.fiberFailures` -> `Metric.enableRuntimeMetrics + Metric.snapshot`: The concrete metric is no longer exported. Enable runtime metrics, then read the Counter snapshot whose id is child\_fiber\_failures. + +- `Metric.fiberLifetimes` -> `none`: The built-in lifetime histogram was removed. Define a Metric.timer and instrument selected effects with Effect.trackDuration when lifetime data is required. + +- `Metric.fiberStarted` -> `Metric.enableRuntimeMetrics + Metric.snapshot`: The concrete metric is no longer exported. Enable runtime metrics, then read the Counter snapshot whose id is child\_fibers\_started. + +- `Metric.fiberSuccesses` -> `Metric.enableRuntimeMetrics + Metric.snapshot`: The concrete metric is no longer exported. Enable runtime metrics, then read the Counter snapshot whose id is child\_fiber\_successes. + +- `Metric.fromMetricKey` -> `Metric.counter / Metric.gauge / Metric.frequency / Metric.histogram / Metric.summary`: MetricKey and MetricKeyType were removed. Construct the required primitive metric directly. + +- `Metric.globalMetricRegistry` -> `Metric.MetricRegistry`: The process-global registry became a Context.Reference whose service is a Map. Access it in context or provide a fresh Map for isolation; use Metric.snapshot for normal reads. + +- `Metric.increment` -> `Metric.update / Metric.modify`: Use Metric.update(counter, 1 or 1n) for counters and Metric.modify(gauge, 1 or 1n) for gauges; gauge update sets an absolute value while modify adds a delta. + +- `Metric.incrementBy` -> `Metric.update / Metric.modify`: Use Metric.update(counter, amount) for counters and Metric.modify(gauge, amount) for gauges. + +- `Metric.make` -> `none`: The low-level arbitrary metric constructor was removed. Use a public primitive constructor and compose with mapInput, withConstantInput, and withAttributes. + +- `Metric.map` -> `Metric.value + Effect.map`: Metric-level state mapping was removed. Transform a read with Effect.map(Metric.value(metric), f). + +- `Metric.mapType` -> `none`: Drop this call. V4 Metric has no key-type type parameter and exposes a fixed runtime type discriminator. + +- `Metric.set` -> `Metric.update`: Use Metric.update(gauge, value); v4 update replaces a gauge's current value. + +- `Metric.succeed` -> `none`: Constant synthetic metrics were removed. Keep constants outside the metric and use Effect.succeed when an Effect value is required. + +- `Metric.summaryTimestamp` -> `Metric.summaryWithTimestamp`: Renamed and called as Metric.summaryWithTimestamp(name, options). Remove the v3 error option; inputs remain value/timestamp pairs. + +- `Metric.sync` -> `none`: Lazy synthetic metrics were removed. Keep the computation outside the metric and use Effect.sync when an Effect value is required. + +- `Metric.tagged` -> `Metric.withAttributes`: Replace tags with attributes, for example Metric.withAttributes(metric, { [key]: value }). + +- `Metric.taggedWithLabels` -> `Metric.withAttributes`: Replace MetricLabel objects with a string record or array of string tuples passed to Metric.withAttributes. + +- `Metric.taggedWithLabelsInput` -> `Metric.withAttributes + Metric.update`: No dynamic-attribute transform remains. Compute attributes at each update or tracking site, wrap with Metric.withAttributes, then update the metric. + +- `Metric.timerWithBoundaries` -> `Metric.timer`: Use Metric.timer(name, { boundaries, description }); boundaries moved into the options object. + +- `Metric.trackAll` -> `Effect.track`: Moved to Effect; use effect.pipe(Effect.track(metric, () =\> input)). + +- `Metric.trackDefect` -> `Effect.trackDefects`: Moved to Effect; use effect.pipe(Effect.trackDefects(metric)). + +- `Metric.trackDefectWith` -> `Effect.trackDefects`: Moved to Effect; pass the mapper as the optional second argument. + +- `Metric.trackDurationWith` -> `Effect.trackDuration`: Moved to Effect; pass the mapper as the optional second argument. V4 records duration on every Exit, whereas v3 updated only after success. + +- `Metric.trackError` -> `Effect.trackErrors`: Moved to Effect; use effect.pipe(Effect.trackErrors(metric)). + +- `Metric.trackErrorWith` -> `Effect.trackErrors`: Moved to Effect; pass the mapper as the optional second argument. + +- `Metric.trackSuccess` -> `Effect.trackSuccesses`: Moved to Effect; use effect.pipe(Effect.trackSuccesses(metric)). + +- `Metric.trackSuccessWith` -> `Effect.trackSuccesses`: Moved to Effect; pass the mapper as the optional second argument. + +- `Metric.unsafeSnapshot` -> `Metric.snapshotUnsafe`: Renamed and now requires an explicit Context.Context\. It returns structural snapshots rather than MetricPair values. + +- `Metric.withNow` -> `Metric.summary`: Metric.summary reads the current Clock automatically; use Metric.summaryWithTimestamp when timestamps are supplied explicitly. The generic timestamp-injecting combinator was removed. + +- `Metric.zip` -> `Effect.all + Metric.update / Metric.value`: Composite metrics were removed. Use Effect.all to update both metrics or combine their Metric.value reads. + +### `effect/MetricBoundaries` + +- `MetricBoundaries.MetricBoundaries` -> `ReadonlyArray`: The wrapper was removed; Metric.histogram accepts plain boundaries in its options. + +- `MetricBoundaries.MetricBoundariesTypeId` -> `none`: Boundaries are unbranded arrays, so the guard and public type-id symbol have no replacement. + +- `MetricBoundaries.exponential` -> `Metric.exponentialBoundaries`: Moved into effect/Metric and now returns ReadonlyArray\. V4 also filters non-positive boundaries. + +- `MetricBoundaries.fromIterable` -> `Metric.boundariesFromIterable`: Moved into effect/Metric and now returns an unbranded ReadonlyArray\; v4 removes non-positive values before appending Infinity. + +- `MetricBoundaries.isMetricBoundaries` -> `none`: Boundaries are unbranded arrays, so the guard and public type-id symbol have no replacement. + +- `MetricBoundaries.linear` -> `Metric.linearBoundaries`: Moved into effect/Metric, but the compared v4 implementation uses start + i + width rather than v3's start + i \* width. Preserve the v3 formula manually when width is not 1. + +### `effect/MetricHook` + +- `MetricHook.MetricHook` -> `Metric.Metric.Hooks`: The closest public structural interface is Metric.Metric.Hooks\; get, update, and modify also receive a Context. + +- `MetricHook.MetricHook.Root` -> `Metric.Metric.Hooks`: The named aliases were removed; specialize the public Hooks interface directly when low-level typing is unavoidable. + +- `MetricHook.MetricHook.Untyped` -> `Metric.Metric.Hooks`: The named aliases were removed; specialize the public Hooks interface directly when low-level typing is unavoidable. + +- `MetricHook.MetricHook.Variance` -> `none`: Hooks are structural and unbranded; the variance helper and public symbol were removed. + +- `MetricHook.MetricHookTypeId` -> `none`: Hooks are structural and unbranded; the variance helper and public symbol were removed. + +- `MetricHook.counter` -> `Metric.counter`: Hook construction was folded into the complete Metric.counter constructor; hooks are internal. + +- `MetricHook.frequency` -> `Metric.frequency`: Hook construction was folded into the complete Metric.frequency constructor; hooks are internal. + +- `MetricHook.gauge` -> `Metric.gauge`: Hook construction was folded into the complete Metric.gauge constructor; hooks are internal. + +- `MetricHook.histogram` -> `Metric.histogram`: Hook construction was folded into the complete Metric.histogram constructor; hooks are internal. + +- `MetricHook.make` -> `none`: There is no public hook constructor; metric classes create and attach hooks internally. + +- `MetricHook.onModify` -> `none`: The operation-specific hook decorators were removed. Metric.mapInput cannot distinguish update from modify. + +- `MetricHook.onUpdate` -> `none`: The operation-specific hook decorators were removed. Metric.mapInput cannot distinguish update from modify. + +- `MetricHook.summary` -> `Metric.summary`: Hook construction was folded into the complete Metric.summary constructor; hooks are internal. + +### `effect/MetricKey` + +- `MetricKey.MetricKey` -> `Metric.Metric`: Key identity, metadata, and operations are combined in Metric\. + +- `MetricKey.MetricKey.Untyped` -> `Metric.Metric`: The separate untyped key alias was removed; use an untyped complete Metric only where required. + +- `MetricKey.MetricKey.Variance` -> `Metric.Metric`: There is no separate key variance interface; variance is carried by Metric's Input and State phantom fields. + +- `MetricKey.MetricKeyTypeId` -> `none`: The key brand was removed; Metric's protocol key is internal. + +- `MetricKey.counter` -> `Metric.counter`: The key and key type were merged into the complete Metric.counter constructor. + +- `MetricKey.frequency` -> `Metric.frequency`: The key and key type were merged into the complete Metric.frequency constructor. + +- `MetricKey.gauge` -> `Metric.gauge`: The key and key type were merged into the complete Metric.gauge constructor. + +- `MetricKey.histogram` -> `Metric.histogram`: The key and key type were merged into the complete Metric.histogram constructor. + +- `MetricKey.isMetricKey` -> `Metric.isMetric`: Keys became complete metrics; use the complete-metric runtime guard. + +- `MetricKey.summary` -> `Metric.summary`: The key and key type were merged into the complete Metric.summary constructor. + +- `MetricKey.taggedWithLabels` -> `Metric.withAttributes`: Labels became attributes. Pass a string record or array of string tuples. + +### `effect/MetricKeyType` + +- `MetricKeyType.CounterKeyTypeTypeId` -> `none`: All public key-type symbols were removed; use a complete metric's string type discriminant. + +- `MetricKeyType.FrequencyKeyTypeTypeId` -> `none`: All public key-type symbols were removed; use a complete metric's string type discriminant. + +- `MetricKeyType.GaugeKeyTypeTypeId` -> `none`: All public key-type symbols were removed; use a complete metric's string type discriminant. + +- `MetricKeyType.HistogramKeyTypeTypeId` -> `none`: All public key-type symbols were removed; use a complete metric's string type discriminant. + +- `MetricKeyType.MetricKeyType` -> `Metric.Metric`: Input/state typing and kind configuration now live on the complete Metric\. + +- `MetricKeyType.MetricKeyType.InType` -> `Metric.Metric.Input`: Use Metric.Metric.Input\ to extract a metric's input type. + +- `MetricKeyType.MetricKeyType.OutType` -> `Metric.Metric.State`: Use Metric.Metric.State\ to extract a metric's state type. + +- `MetricKeyType.MetricKeyType.Untyped` -> `Metric.Metric`: The key-type descriptor no longer exists independently of a metric. + +- `MetricKeyType.MetricKeyType.Variance` -> `none`: The descriptor variance interface was removed; complete Metric carries Input and State variance. + +- `MetricKeyType.MetricKeyTypeTypeId` -> `none`: All public key-type symbols were removed; use a complete metric's string type discriminant. + +- `MetricKeyType.SummaryKeyTypeTypeId` -> `none`: All public key-type symbols were removed; use a complete metric's string type discriminant. + +- `MetricKeyType.counter` -> `Metric.counter`: The standalone descriptor was folded into the complete Metric.counter constructor. + +- `MetricKeyType.frequency` -> `Metric.frequency`: The standalone descriptor was folded into the complete Metric.frequency constructor. + +- `MetricKeyType.gauge` -> `Metric.gauge`: The standalone descriptor was folded into the complete Metric.gauge constructor. + +- `MetricKeyType.histogram` -> `Metric.histogram`: The standalone descriptor was folded into the complete Metric.histogram constructor. + +- `MetricKeyType.isCounterKey` -> `Metric.isMetric + metric.type`: Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant. + +- `MetricKeyType.isFrequencyKey` -> `Metric.isMetric + metric.type`: Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant. + +- `MetricKeyType.isGaugeKey` -> `Metric.isMetric + metric.type`: Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant. + +- `MetricKeyType.isHistogramKey` -> `Metric.isMetric + metric.type`: Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant. + +- `MetricKeyType.isMetricKeyType` -> `Metric.isMetric`: Standalone key-type values were removed; test complete metrics instead. + +- `MetricKeyType.isSummaryKey` -> `Metric.isMetric + metric.type`: Standalone refinements were removed. Complete metrics expose a Counter, Frequency, Gauge, Histogram, or Summary string discriminant. + +- `MetricKeyType.summary` -> `Metric.summary`: The standalone descriptor was folded into the complete Metric.summary constructor. + +### `effect/MetricLabel` + +- `MetricLabel.MetricLabel` -> `[string, string]`: A label is now an ordinary attribute tuple; collections are Metric.Metric.Attributes or Metric.Metric.AttributeSet. + +- `MetricLabel.MetricLabelTypeId` -> `none`: Attributes are plain tuples or records, so there is no branded guard or type-id symbol. + +- `MetricLabel.isMetricLabel` -> `none`: Attributes are plain tuples or records, so there is no branded guard or type-id symbol. + +- `MetricLabel.make` -> `[key, value]`: Construct an ordinary tuple, or place the pair in an attribute record passed to Metric.withAttributes or a metric constructor. + +### `effect/MetricPair` + +- `MetricPair.MetricPair` -> `Metric.Metric.Snapshot`: Registry key/state pairs became discriminated snapshots containing id, type, description, attributes, and state. + +- `MetricPair.MetricPair.Untyped` -> `Metric.Metric.Snapshot`: Registry key/state pairs became discriminated snapshots containing id, type, description, attributes, and state. + +- `MetricPair.MetricPair.Variance` -> `none`: Snapshots are structural, so the pair variance helper and brand symbol were removed. + +- `MetricPair.MetricPairTypeId` -> `none`: Snapshots are structural, so the pair variance helper and brand symbol were removed. + +- `MetricPair.make` -> `Metric.snapshot`: There is no pair constructor. Obtain snapshots with Metric.snapshot or Metric.snapshotUnsafe; manually constructed data can satisfy Metric.Metric.SnapshotProto. + +- `MetricPair.unsafeMake` -> `Metric.snapshot`: There is no pair constructor. Obtain snapshots with Metric.snapshot or Metric.snapshotUnsafe; manually constructed data can satisfy Metric.Metric.SnapshotProto. + +### `effect/MetricPolling` + +- `MetricPolling.MetricPolling` -> `local { metric, poll } record`: The module was removed. Keep a local record pairing a Metric with its polling Effect when this abstraction is still useful. + +- `MetricPolling.MetricPollingTypeId` -> `none`: The polling wrapper and its brand were removed. + +- `MetricPolling.collectAll` -> `Effect.forEach + Metric.update/value`: No combined metric replacement exists. Poll records, update each metric, and collect states explicitly. + +- `MetricPolling.launch` -> `Effect.repeat + Effect.forkScoped`: Repeat polling, updating, and reading with the schedule, then forkScoped. + +- `MetricPolling.make` -> `({ metric, poll })`: No public constructor remains; use the local record directly. + +- `MetricPolling.poll` -> `self.poll`: Access the polling Effect from the local record. + +- `MetricPolling.pollAndUpdate` -> `Effect.flatMap(self.poll, input => Metric.update(self.metric, input))`: Compose polling and metric update directly. + +- `MetricPolling.retry` -> `Effect.retry`: Retry the poll Effect and retain the same metric in the local record. + +### `effect/MetricRegistry` + +- `MetricRegistry.MetricRegistry` -> `Metric.MetricRegistry`: The method-bearing registry became a Context.Reference whose service is a Map. Metrics register metadata and hooks lazily. + +- `MetricRegistry.MetricRegistryTypeId` -> `none`: The registry service is an ordinary Map behind a Context.Reference and has no public brand. + +- `MetricRegistry.make` -> `new Map>()`: Provide a fresh Map to Metric.MetricRegistry for isolation. Read it through Metric.snapshot or snapshotUnsafe. + +### `effect/MetricState` + +- `MetricState.CounterStateTypeId` -> `none`: All state brand symbols were removed; v4 state interfaces are structural. + +- `MetricState.FrequencyStateTypeId` -> `none`: All state brand symbols were removed; v4 state interfaces are structural. + +- `MetricState.GaugeStateTypeId` -> `none`: All state brand symbols were removed; v4 state interfaces are structural. + +- `MetricState.HistogramStateTypeId` -> `none`: All state brand symbols were removed; v4 state interfaces are structural. + +- `MetricState.MetricState` -> `Metric.Metric.State`: The common branded state model was removed; extract a complete metric's state with Metric.Metric.State\ or use a concrete state interface. + +- `MetricState.MetricState.Untyped` -> `Metric.Metric.Snapshot['state']`: Use the state union from Metric.Metric.Snapshot, or explicitly union the five structural state interfaces. + +- `MetricState.MetricState.Variance` -> `none`: States are structural objects and no longer carry a variance brand. + +- `MetricState.MetricStateTypeId` -> `none`: All state brand symbols were removed; v4 state interfaces are structural. + +- `MetricState.SummaryStateTypeId` -> `none`: All state brand symbols were removed; v4 state interfaces are structural. + +- `MetricState.counter` -> `Metric.CounterState`: There is no state constructor. Obtain the structural state with Metric.value(Metric.counter(...)). + +- `MetricState.frequency` -> `Metric.FrequencyState`: There is no state constructor. Obtain the structural state with Metric.value(Metric.frequency(...)). + +- `MetricState.gauge` -> `Metric.GaugeState`: There is no state constructor. Obtain the structural state with Metric.value(Metric.gauge(...)). + +- `MetricState.histogram` -> `Metric.HistogramState`: There is no state constructor. Obtain the structural state with Metric.value(Metric.histogram(...)). + +- `MetricState.isCounterState` -> `none`: Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination. + +- `MetricState.isFrequencyState` -> `none`: Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination. + +- `MetricState.isGaugeState` -> `none`: Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination. + +- `MetricState.isHistogramState` -> `none`: Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination. + +- `MetricState.isMetricState` -> `none`: Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination. + +- `MetricState.isSummaryState` -> `none`: Standalone state guards were removed. Retain the enclosing snapshot and switch on snapshot.type for runtime discrimination. + +- `MetricState.summary` -> `Metric.SummaryState`: There is no state constructor. Obtain the structural state with Metric.value(Metric.summary(...)). + +### `effect/Micro` + +- `Micro.All.IsDiscard` -> `Effect.All.IsDiscard`: Type-level helper moved to the Effect.All namespace. + +- `Micro.All.MicroAny` -> `Effect.All.EffectAny`: Renamed: MicroAny becomes EffectAny in the Effect.All namespace. + +- `Micro.All.Return` -> `Effect.All.Return`: Type-level helper moved to the Effect.All namespace. + +- `Micro.CurrentConcurrency` -> `none`: Removed in v4 (no fiber-wide concurrency reference). Pass a { concurrency } option directly to the operations that fan out, e.g. Effect.all or Effect.forEach. + +- `Micro.CurrentScheduler` -> `References.Scheduler`: The scheduler reference lives in effect/References (also exported from effect/Scheduler as Scheduler.Scheduler). Override it with Effect.provideService/Effect.updateService. + +- `Micro.Do` -> `Effect.Do`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.Error` -> `Data.Error`: The yieldable error base class constructor is Data.Error from effect/Data in v4. + +- `Micro.MaxOpsBeforeYield` -> `References.MaxOpsBeforeYield`: The reference lives in effect/References (also exported from effect/Scheduler). Override it with Effect.updateService. + +- `Micro.Micro` -> `Effect.Effect`: The Micro\ type is Effect.Effect\ in v4; the v4 Effect runtime is itself lightweight. + +- `Micro.Micro.Context` -> `Effect.Services`: Type extractor renamed: Micro.Context\ becomes Effect.Services\ in v4. + +- `Micro.Micro.Error` -> `Effect.Error`: Type extractor: Micro.Error\ becomes Effect.Error\ in v4. + +- `Micro.Micro.Success` -> `Effect.Success`: Type extractor: Micro.Success\ becomes Effect.Success\ in v4. + +- `Micro.MicroCause` -> `Cause.Cause`: MicroCause\ becomes Cause.Cause\. Note v4 Cause holds a list of failure reasons (Fail | Die | Interrupt) rather than being a single tagged variant. + +- `Micro.MicroCause.Die` -> `Cause.Die`: The Die variant is a Reason in v4: Cause.Die from effect/Cause. + +- `Micro.MicroCause.Error` -> `Cause.Cause.Error`: Type extractor: use the Error helper in the Cause.Cause namespace to extract the error type. + +- `Micro.MicroCause.Fail` -> `Cause.Fail`: The Fail variant is a Reason in v4: Cause.Fail\ from effect/Cause. + +- `Micro.MicroCause.Interrupt` -> `Cause.Interrupt`: The Interrupt variant is a Reason in v4: Cause.Interrupt from effect/Cause. + +- `Micro.MicroCause.Proto` -> `Cause.Cause.ReasonProto`: Internal prototype type; the closest v4 equivalent is the ReasonProto interface in the Cause.Cause namespace. Rarely needed directly. + +- `Micro.MicroCauseTypeId` -> `Cause.TypeId`: Use Cause.TypeId from effect/Cause (value is "\~effect/Cause"). + +- `Micro.MicroExit` -> `Exit.Exit`: MicroExit\ becomes Exit.Exit\ from effect/Exit. In v4 Exit is a subtype of Effect. + +- `Micro.MicroExit.Failure` -> `Exit.Failure`: MicroExit.Failure becomes Exit.Failure\ from effect/Exit. + +- `Micro.MicroExit.Proto` -> `Exit.Exit.Proto`: Internal prototype type; v4 exposes the shared base as Proto in the Exit.Exit namespace. Rarely needed directly. + +- `Micro.MicroExit.Success` -> `Exit.Success`: MicroExit.Success becomes Exit.Success\ from effect/Exit. + +- `Micro.MicroExitTypeId` -> `none`: v4 Exit is a subtype of Effect and has no dedicated TypeId; use Exit.isExit to identify exits. + +- `Micro.MicroFiber` -> `Fiber.Fiber`: MicroFiber\ becomes Fiber.Fiber\ from effect/Fiber. + +- `Micro.MicroFiber.Variance` -> `none`: Type-level variance helper with no public v4 equivalent; the v4 Fiber.Fiber interface carries variance directly. + +- `Micro.MicroFiberTypeId` -> `none`: No public TypeId on v4 fibers; use Fiber.isFiber to identify fibers. + +- `Micro.MicroIterator` -> `Effect.EffectIterator`: Renamed: MicroIterator becomes Effect.EffectIterator (generator support for Effect.gen). + +- `Micro.MicroSchedule` -> `Schedule.Schedule`: v3 MicroSchedule was a plain function (attempt, elapsedMillis) =\> Option\; v4 uses the first-class Schedule.Schedule type from effect/Schedule. + +- `Micro.MicroScheduler` -> `Scheduler.Scheduler`: The scheduler interface lives in effect/Scheduler in v4. + +- `Micro.MicroSchedulerDefault` -> `Scheduler.MixedScheduler`: The default task scheduler implementation in v4 is Scheduler.MixedScheduler from effect/Scheduler. + +- `Micro.MicroScope` -> `Scope.Scope`: MicroScope becomes Scope.Scope from effect/Scope; the closeable variant is Scope.Closeable. + +- `Micro.MicroScopeTypeId` -> `none`: No public TypeId on v4 scopes; use the Scope.Scope service key to access the current scope. + +- `Micro.MicroTypeLambda` -> `Effect.EffectTypeLambda`: Renamed: MicroTypeLambda becomes Effect.EffectTypeLambda. + +- `Micro.MicroUnify` -> `Effect.EffectUnify`: Renamed: MicroUnify becomes Effect.EffectUnify. + +- `Micro.MicroUnifyIgnore` -> `none`: Removed; v4 Effect declares its unify-ignore slot inline and exposes no named UnifyIgnore interface. + +- `Micro.NoSuchElementException` -> `Cause.NoSuchElementError`: Renamed and moved: NoSuchElementException becomes Cause.NoSuchElementError from effect/Cause. + +- `Micro.TaggedError` -> `Data.TaggedError`: The yieldable tagged error class constructor is Data.TaggedError from effect/Data in v4. + +- `Micro.TimeoutException` -> `Cause.TimeoutError`: Renamed and moved: TimeoutException becomes Cause.TimeoutError from effect/Cause (raised by Effect.timeout). + +- `Micro.TypeId` -> `Effect.TypeId`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.YieldableError` -> `Cause.YieldableError`: Moved: YieldableError lives in effect/Cause in v4. + +- `Micro.acquireUseRelease` -> `Effect.acquireUseRelease`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.all` -> `Effect.all`: Micro was removed in v4; use Effect.all with the same iterable-or-record input and concurrency/discard options. + +- `Micro.as` -> `Effect.as`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.asSome` -> `Effect.asSome`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.asVoid` -> `Effect.asVoid`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +#### `Micro.async` + +**Replacement:** `Effect.callback` + +Renamed: the async constructor is Effect.callback in v4. Same resume/AbortSignal semantics. + +**Example** + +```ts +Effect.callback((resume) => resume(Effect.succeed(1))) +``` + +- `Micro.bind` -> `Effect.bind`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.bindTo` -> `Effect.bindTo`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.catchAll` -> `Effect.catch`: Renamed: catchAll is Effect.catch in v4. + +- `Micro.catchAllCause` -> `Effect.catchCause`: Renamed: catchAllCause is Effect.catchCause in v4. + +- `Micro.catchAllDefect` -> `Effect.catchDefect`: Renamed: catchAllDefect is Effect.catchDefect in v4. + +- `Micro.catchIf` -> `Effect.catchIf`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.catchTag` -> `Effect.catchTag`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.causeDie` -> `Cause.die`: MicroCause was replaced by the unified effect/Cause module in v4. + +- `Micro.causeFail` -> `Cause.fail`: MicroCause was replaced by the unified effect/Cause module in v4. + +- `Micro.causeInterrupt` -> `Cause.interrupt`: MicroCause was replaced by the unified effect/Cause module in v4. Takes an optional fiber id. + +- `Micro.causeIsDie` -> `Cause.hasDies`: v4 Cause aggregates multiple reasons, so tag refinements become reason queries: Cause.hasDies checks for Die reasons; use Cause.isDieReason for a single Reason value. + +- `Micro.causeIsFail` -> `Cause.hasFails`: v4 Cause aggregates multiple reasons, so tag refinements become reason queries: Cause.hasFails checks for Fail reasons; use Cause.isFailReason for a single Reason value. + +- `Micro.causeIsInterrupt` -> `Cause.hasInterrupts`: v4 Cause aggregates multiple reasons, so tag refinements become reason queries: Cause.hasInterrupts checks for Interrupt reasons (see also Cause.hasInterruptsOnly). + +- `Micro.causeSquash` -> `Cause.squash`: Same behavior in the unified effect/Cause module. + +- `Micro.causeWithTrace` -> `Cause.annotate`: v4 causes carry structured annotations instead of a traces array; attach trace data with Cause.annotate (e.g. the Cause.StackTrace service). v4 also captures failure stack traces automatically. + +- `Micro.context` -> `Effect.context`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.delay` -> `Effect.delay`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.either` -> `Effect.result`: Either was replaced by Result in v4: Effect.result yields Result.Result\ instead of Either\. + +- `Micro.ensuring` -> `Effect.ensuring`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.exit` -> `Effect.exit`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.exitDie` -> `Exit.die`: MicroExit was replaced by the unified effect/Exit module in v4. + +- `Micro.exitFail` -> `Exit.fail`: MicroExit was replaced by the unified effect/Exit module in v4. + +- `Micro.exitFailCause` -> `Exit.failCause`: MicroExit was replaced by the unified effect/Exit module in v4. + +- `Micro.exitInterrupt` -> `Exit.interrupt`: MicroExit was replaced by the unified effect/Exit module in v4. Takes an optional fiber id. + +- `Micro.exitIsDie` -> `Exit.hasDies`: v4 exits carry a multi-reason Cause, so tag refinements become reason queries: Exit.hasDies checks the failure cause for Die reasons. + +- `Micro.exitIsFail` -> `Exit.hasFails`: v4 exits carry a multi-reason Cause, so tag refinements become reason queries: Exit.hasFails checks the failure cause for Fail reasons. + +- `Micro.exitIsFailure` -> `Exit.isFailure`: Same refinement in the unified effect/Exit module. + +- `Micro.exitIsInterrupt` -> `Exit.hasInterrupts`: v4 exits carry a multi-reason Cause, so tag refinements become reason queries: Exit.hasInterrupts checks the failure cause for Interrupt reasons. + +- `Micro.exitIsSuccess` -> `Exit.isSuccess`: Same refinement in the unified effect/Exit module. + +- `Micro.exitSucceed` -> `Exit.succeed`: MicroExit was replaced by the unified effect/Exit module in v4. + +- `Micro.exitVoid` -> `Exit.void`: MicroExit was replaced by the unified effect/Exit module in v4. + +- `Micro.exitVoidAll` -> `Exit.asVoidAll`: Renamed: exitVoidAll becomes Exit.asVoidAll in the unified effect/Exit module. + +- `Micro.fail` -> `Effect.fail`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.failCause` -> `Effect.failCause`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.failCauseSync` -> `Effect.failCauseSync`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.failSync` -> `Effect.failSync`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.fiberAwait` -> `Fiber.await`: Fiber operations moved to the effect/Fiber module in v4. + +- `Micro.fiberInterrupt` -> `Fiber.interrupt`: Fiber operations moved to the effect/Fiber module in v4. + +- `Micro.fiberInterruptAll` -> `Fiber.interruptAll`: Fiber operations moved to the effect/Fiber module in v4. + +- `Micro.fiberJoin` -> `Fiber.join`: Fiber operations moved to the effect/Fiber module in v4. + +- `Micro.filter` -> `Effect.filter`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.filterMap` -> `Effect.filterMap`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.filterOrFail` -> `Effect.filterOrFail`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +#### `Micro.filterOrFailCause` + +**Replacement:** `Effect.filterOrElse` + +No direct equivalent; use Effect.filterOrElse and fail with a cause in the fallback. + +**Example** + +```ts +Effect.filterOrElse(effect, predicate, { orElse: () => Effect.failCause(Cause.die("invalid")) }) +``` + +- `Micro.flatten` -> `Effect.flatten`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.flip` -> `Effect.flip`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.forkDaemon` -> `Effect.forkDetach`: Renamed: forkDaemon becomes Effect.forkDetach (fork detached from the parent's lifetime). + +- `Micro.forkIn` -> `Effect.forkIn`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.forkScoped` -> `Effect.forkScoped`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.fromEither` -> `Effect.fromResult`: Either was replaced by Result in v4: convert Result.Result values with Effect.fromResult. + +- `Micro.fromOption` -> `Effect.fromOption`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.ignore` -> `Effect.ignore`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +#### `Micro.ignoreLogged` + +**Replacement:** `Effect.ignore` + +Removed; log explicitly before ignoring. + +**Example** + +```ts +effect.pipe(Effect.tapCause((cause) => Effect.logError(cause)), Effect.ignore) +``` + +- `Micro.interrupt` -> `Effect.interrupt`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.interruptible` -> `Effect.interruptible`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.isMicro` -> `Effect.isEffect`: Micro values are plain Effects in v4; use Effect.isEffect. + +- `Micro.isMicroCause` -> `Cause.isCause`: MicroCause was replaced by the unified effect/Cause module in v4. + +- `Micro.isMicroExit` -> `Exit.isExit`: MicroExit was replaced by the unified effect/Exit module in v4. + +- `Micro.let` -> `Effect.let`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.map` -> `Effect.map`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +#### `Micro.mapErrorCause` + +**Replacement:** `Effect.catchCause` + +No direct equivalent; transform the cause by catching it and re-failing. + +**Example** + +```ts +Effect.catchCause(effect, (cause) => Effect.failCause(Cause.map(cause, transformError))) +``` + +- `Micro.match` -> `Effect.match`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.matchCause` -> `Effect.matchCause`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.matchCauseEffect` -> `Effect.matchCauseEffect`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.onExit` -> `Effect.onExit`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.onInterrupt` -> `Effect.onInterrupt`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.option` -> `Effect.option`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.orDie` -> `Effect.orDie`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.orElseSucceed` -> `Effect.orElseSucceed`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.promise` -> `Effect.promise`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.race` -> `Effect.race`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.raceAll` -> `Effect.raceAll`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.raceFirst` -> `Effect.raceFirst`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +#### `Micro.repeatExit` + +**Replacement:** `Effect.repeat` + +Removed; use Effect.repeat with while/until/times/schedule options. To inspect failures while looping, run the body through Effect.exit and repeat on the Exit value. + +**Example** + +```ts +Effect.repeat(Effect.exit(effect), { while: (exit) => Exit.isFailure(exit), times: 3 }) +``` + +- `Micro.replicate` -> `Effect.replicate`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.replicateEffect` -> `Effect.replicateEffect`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.retry` -> `Effect.retry`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.runFork` -> `Effect.runFork`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.runPromise` -> `Effect.runPromise`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.runPromiseExit` -> `Effect.runPromiseExit`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.runSync` -> `Effect.runSync`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.runSyncExit` -> `Effect.runSyncExit`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.sandbox` -> `Effect.sandbox`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.scheduleAddDelay` -> `Schedule.addDelay`: Moved to effect/Schedule; the callback now receives schedule Metadata and returns a Duration (optionally effectful). + +- `Micro.scheduleExponential` -> `Schedule.exponential`: Moved to effect/Schedule; takes Duration input instead of raw millis. + +- `Micro.scheduleIntersect` -> `Schedule.max`: Intersection (recur while both recur, waiting for the slower) is Schedule.max([self, that]) in v4. + +- `Micro.scheduleRecurs` -> `Schedule.recurs`: Moved to effect/Schedule. + +- `Micro.scheduleSpaced` -> `Schedule.spaced`: Moved to effect/Schedule; takes Duration input instead of raw millis. + +- `Micro.scheduleUnion` -> `Schedule.min`: Union (recur while either recurs, waiting for the faster) is Schedule.min([self, that]) in v4. + +#### `Micro.scheduleWithMaxDelay` + +**Replacement:** `Schedule.modifyDelay` + +No direct equivalent; clamp the delay with Schedule.modifyDelay. + +**Example** + +```ts +Schedule.modifyDelay(schedule, ({ delay }) => Duration.min(delay, "10 seconds")) +``` + +#### `Micro.scheduleWithMaxElapsed` + +**Replacement:** `Schedule.upTo` + +Renamed: cap total elapsed time with Schedule.upTo({ duration }). + +**Example** + +```ts +Schedule.upTo(schedule, { duration: "30 seconds" }) +``` + +- `Micro.scopeMake` -> `Scope.make`: Moved to effect/Scope: Scope.make returns Effect\ and accepts an optional finalizer strategy. + +- `Micro.scopeUnsafeMake` -> `Scope.makeUnsafe`: Renamed and moved: scopeUnsafeMake becomes Scope.makeUnsafe from effect/Scope. + +- `Micro.scoped` -> `Effect.scoped`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.service` -> `service`: Micro was removed in v4, and services are Effects; yield or compose the service key directly in the rewritten Effect runtime. + +- `Micro.succeed` -> `Effect.succeed`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.sync` -> `Effect.sync`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.tap` -> `Effect.tap`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.tapDefect` -> `Effect.tapDefect`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.tapError` -> `Effect.tapError`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.tapErrorCause` -> `Effect.tapCause`: Renamed: tapErrorCause becomes Effect.tapCause. + +- `Micro.tapErrorCauseIf` -> `Effect.tapCauseIf`: Renamed: tapErrorCauseIf becomes Effect.tapCauseIf (see also Effect.tapCauseFilter for Filter-based matching). + +- `Micro.timeout` -> `Effect.timeout`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.timeoutOption` -> `Effect.timeoutOption`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.try` -> `Effect.try`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.tryPromise` -> `Effect.tryPromise`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.uninterruptibleMask` -> `Effect.uninterruptibleMask`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.updateContext` -> `Effect.updateContext`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.updateService` -> `Effect.updateService`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.void` -> `Effect.void`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.when` -> `Effect.when`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +- `Micro.whileLoop` -> `Effect.whileLoop`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +#### `Micro.withConcurrency` + +**Replacement:** `none` + +Removed in v4 along with "inherit" concurrency; pass a { concurrency } option directly to each concurrent operation. + +**Example** + +```ts +Effect.forEach(items, handle, { concurrency: 10 }) +``` + +- `Micro.withMicroFiber` -> `Effect.withFiber`: Renamed: withMicroFiber becomes Effect.withFiber, giving access to the current fiber. + +- `Micro.withTrace` -> `Effect.withSpan`: Removed; v4 captures failure stack traces automatically and cause annotations replace the traces array. For named tracing regions use Effect.withSpan. + +- `Micro.yieldFlush` -> `none`: Removed; access the current scheduler via the References.Scheduler service and call its flush() method directly if deterministic draining is needed. + +- `Micro.yieldNow` -> `Effect.yieldNow`: Micro was removed in v4; the rewritten Effect runtime is itself lightweight and replaces it. Same-name equivalent on effect/Effect. + +### `effect/MutableHashMap` + +- `MutableHashMap.MutableHashMap` -> `MutableHashMap.MutableHashMap`: The MutableHashMap model remains; use its public operations rather than depending on internal representation fields. + +- `MutableHashMap.TypeId` -> `none`: The public MutableHashMap.TypeId was removed; the v4 marker is private. + +### `effect/MutableHashSet` + +- `MutableHashSet.MutableHashSet` -> `MutableHashSet.MutableHashSet`: The MutableHashSet model remains; use its public operations rather than depending on internal representation fields. + +- `MutableHashSet.TypeId` -> `none`: The public MutableHashSet.TypeId was removed; the v4 marker is private. + +### `effect/MutableList` + +- `MutableList.MutableList` -> `MutableList.MutableList`: The model remains but was redesigned from an iterable doubly linked list into a bucketed FIFO structure. + +- `MutableList.TypeId` -> `none`: V4 MutableList has no public runtime marker. + +- `MutableList.empty` -> `MutableList.make`: Constructor rename; MutableList.Empty is the take sentinel, not a constructor. + +- `MutableList.forEach` -> `MutableList.toArray + Array.forEach`: No direct traversal helper remains; iterate a snapshot produced by MutableList.toArray. + +- `MutableList.fromIterable` -> `MutableList.make + MutableList.appendAll`: Create an empty list with MutableList.make, then append the iterable with MutableList.appendAll. + +- `MutableList.head` -> `MutableList.toArrayN`: Use MutableList.toArrayN(self, 1)[0]; the redesigned FIFO exposes buckets rather than the old Option-returning accessor. + +- `MutableList.isEmpty` -> `none`: Read self.length === 0; no named isEmpty helper remains. + +- `MutableList.length` -> `none`: Read the public self.length field; no named length helper remains. + +- `MutableList.pop` -> `none`: The bucketed FIFO has no remove-last operation; migrate code to front draining or use a different mutable collection. + +- `MutableList.reset` -> `MutableList.clear`: Direct behavioral replacement; the return type is now void. + +- `MutableList.shift` -> `MutableList.take`: Front removal remains synchronous, but emptiness is reported with MutableList.Empty instead of undefined. + +- `MutableList.tail` -> `MutableList.toArray`: Use MutableList.toArray(self).at(-1); the public self.tail field is an internal bucket, not the old last-element accessor. + +### `effect/MutableQueue` + +- `MutableQueue.EmptyMutableQueue` -> `none`: Queue.poll reports emptiness with Option.none, so no default sentinel is required. + +- `MutableQueue.MutableQueue` -> `Queue.Queue`: The replacement Queue is effectful, lifecycle-aware, and not Iterable. + +- `MutableQueue.MutableQueue.Empty` -> `none`: Use the Option returned by Queue.poll; the old empty sentinel was removed. + +- `MutableQueue.TypeId` -> `none`: The MutableQueue module and its public marker were removed. + +- `MutableQueue.bounded` -> `Queue.dropping`: The replacement constructor is effectful; dropping preserves the old immediate rejection when a bounded queue is full. + +- `MutableQueue.capacity` -> `none`: Read queue.capacity on the replacement Queue; unbounded queues expose Infinity. + +- `MutableQueue.isEmpty` -> `Queue.sizeUnsafe`: Use Queue.sizeUnsafe(queue) === 0, or map the effectful Queue.size result. + +- `MutableQueue.isFull` -> `Queue.isFullUnsafe`: Use Queue.isFullUnsafe for synchronous inspection or Queue.isFull for an Effect result. + +- `MutableQueue.length` -> `Queue.sizeUnsafe`: Use Queue.sizeUnsafe for synchronous inspection or Queue.size for an Effect result. + +- `MutableQueue.offer` -> `Queue.offerUnsafe`: Use with Queue.dropping to preserve the old synchronous boolean rejection at capacity; Queue.offer is the effectful form. + +- `MutableQueue.offerAll` -> `Queue.offerAllUnsafe`: The synchronous replacement returns the rejected remainder as an Array; Queue.offerAll is the effectful form. + +- `MutableQueue.poll` -> `Queue.poll`: Polling is now effectful and returns Option\ rather than accepting a default; Queue.takeUnsafe is the low-level synchronous alternative. + +- `MutableQueue.pollUpTo` -> `Queue.takeUnsafe`: No direct non-blocking take-up-to helper remains; repeatedly call Queue.takeUnsafe and collect successful exits without waiting. + +- `MutableQueue.unbounded` -> `Queue.unbounded`: The unbounded replacement constructor is effectful. + +### `effect/MutableRef` + +- `MutableRef.MutableRef` -> `MutableRef.MutableRef`: The MutableRef model remains; use its public operations rather than depending on internal representation fields. + +- `MutableRef.TypeId` -> `none`: The public MutableRef.TypeId was removed; the v4 marker is private. + +### `effect/Number` + +- `Number.greaterThan` -> `Number.isGreaterThan`: Renamed with the v4 is-prefix. + +- `Number.greaterThanOrEqualTo` -> `Number.isGreaterThanOrEqualTo`: Renamed with the v4 is-prefix. + +- `Number.lessThan` -> `Number.isLessThan`: Renamed with the v4 is-prefix. + +- `Number.lessThanOrEqualTo` -> `Number.isLessThanOrEqualTo`: Renamed with the v4 is-prefix. + +- `Number.negate` -> `Number.multiply(-1)`: Use Number.multiply(n, -1), or Number.multiply(-1) as the equivalent unary function. + +- `Number.unsafeDivide` -> `Number.divideUnsafe`: Renamed; v4 throws for zero whereas v3 raw division returned Infinity or NaN. + +### `effect/Option` + +- `Option.None` -> `Option.None`: The variant remains, but Option is no longer an Effect or STM subtype. + +- `Option.Option` -> `Option.Option`: The union type remains, but Option is no longer an Effect or STM subtype. + +- `Option.OptionUnify` -> `Option.OptionUnify`: The unification hook remains under the same name. + +- `Option.OptionUnifyIgnore` -> `Option.OptionUnifyIgnore`: The marker remains, without the v3 Effect, Tag, and Either augmentation fields. + +- `Option.Some` -> `Option.Some`: The variant remains, but Option is no longer an Effect or STM subtype. + +- `Option.TypeId` -> `none`: The v4 Option brand is private and no public TypeId is exported. + +- `Option.ap` -> `Option.zipWith`: Use Option.zipWith(self, that, (f, a) =\> f(a)); v4 has no Option.ap. + +- `Option.flatMapNullable` -> `Option.flatMapNullishOr`: Renamed with v4 nullish-or terminology. + +- `Option.fromNullable` -> `Option.fromNullishOr`: Renamed with v4 nullish-or terminology. + +- `Option.getEquivalence` -> `Option.makeEquivalence`: Renamed from getEquivalence to makeEquivalence. + +- `Option.getLeft` -> `Option.getFailure`: Either input became Result input, and Left became Failure. + +- `Option.getOrder` -> `Option.makeOrder`: Renamed from getOrder to makeOrder. + +- `Option.getRight` -> `Option.getSuccess`: Either input became Result input, and Right became Success. + +- `Option.liftNullable` -> `Option.liftNullishOr`: Renamed with v4 nullish-or terminology. + +- `Option.orElseEither` -> `Option.orElseResult`: Either was replaced by Result; source tracking now uses Failure and Success. + +### `effect/Order` + +- `Order.Order` -> `Order.Order`: The callable type is retained; its return type remains the -1 | 0 | 1 Ordering union. + +- `Order.all` -> `Order.Tuple([...collection])`: Materialize the comparator iterable for Tuple. V4 evaluates the configured tuple instead of stopping at the shorter input; use Order.make for intentional v3 prefix semantics. + +- `Order.array` -> `Order.Array`: Capitalized constructor name; lexicographic array ordering and the length tie-break are unchanged. + +- `Order.between` -> `Order.isBetween`: Renamed with the v4 is-prefix; inclusive bounds and call forms are unchanged. + +- `Order.bigint` -> `Order.BigInt`: Capitalized instance name; bigint ordering is unchanged. + +- `Order.boolean` -> `Order.Boolean`: Capitalized instance name; false remains ordered before true. + +- `Order.combineAll` -> `Order.combineAll`: Retained with the same left-to-right tie-breaking and empty-iterable result. + +- `Order.combineMany` -> `Order.combine(self, Order.combineAll(collection))`: Compose combine with combineAll; the dedicated dual combineMany helper was removed. + +- `Order.empty` -> `Order.alwaysEqual`: Renamed constructor; call as Order.alwaysEqual\() to produce an order that always returns zero. + +- `Order.greaterThan` -> `Order.isGreaterThan`: Renamed with the v4 is-prefix; curried and uncurried comparisons are retained. + +- `Order.greaterThanOrEqualTo` -> `Order.isGreaterThanOrEqualTo`: Renamed with the v4 is-prefix; curried and uncurried comparisons are retained. + +- `Order.lessThan` -> `Order.isLessThan`: Renamed with the v4 is-prefix; curried and uncurried comparisons are retained. + +- `Order.lessThanOrEqualTo` -> `Order.isLessThanOrEqualTo`: Renamed with the v4 is-prefix; curried and uncurried comparisons are retained. + +- `Order.make` -> `Order.make`: Retained with the same comparator contract and reference-equality fast path. + +- `Order.number` -> `Order.Number`: Capitalized instance name. V4 orders NaN below non-NaN values and all NaNs equally; use a custom Order.make to preserve v3 edge behavior. + +- `Order.product` -> `Order.Tuple([self, that])`: Replace the dual two-order helper with the single-array Tuple constructor. + +- `Order.productMany` -> `Order.Tuple([self, ...collection])`: Materialize the order iterable in one Tuple call; v4 evaluates every configured comparator for short inputs. + +- `Order.reverse` -> `Order.flip`: Direct rename; the replacement reverses comparison by swapping the operands. + +- `Order.string` -> `Order.String`: Capitalized instance name; case-sensitive JavaScript lexicographic ordering is unchanged. + +- `Order.struct` -> `Order.Struct`: Capitalized constructor name; field-order tie-breaking is unchanged. + +- `Order.tuple` -> `Order.Tuple([orderA, orderB, ...])`: Capitalized constructor now takes one comparator array instead of rest arguments and evaluates every configured position. + +### `effect/Ordering` + +- `Ordering.combineAll` -> `Ordering.Reducer.combineAll`: The combination operation moved to the exported Reducer; first-nonzero and empty-input behavior are unchanged. + +- `Ordering.combineMany` -> `Ordering.Reducer.combineAll(Iterable.prepend(collection, self))`: Prepend the initial ordering before reducing to preserve v3 short-circuiting without consuming collection when self is nonzero. + +### `effect/ParseResult` + +#### `ParseResult.ArrayFormatter` + +**Replacement:** `SchemaIssue.makeFormatterStandardSchemaV1` + +Format error.issue with the Standard Schema formatter. + +**Example** + +```ts +SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues +``` + +- `ParseResult.ArrayFormatterIssue` -> `StandardSchemaV1.FailureResult["issues"][number]`: Use the Standard Schema issue shape returned by makeFormatterStandardSchemaV1. + +- `ParseResult.Composite` -> `SchemaIssue.Composite`: Composite parse failures moved to SchemaIssue. The v4 constructor takes the failing AST and an array of nested issues; input is retained only when reportInput is enabled. + +- `ParseResult.DeclarationDecodeUnknown` -> `SchemaGetter.Getter`: Custom declaration decoding now uses SchemaGetter values and Schema.declare annotations. + +- `ParseResult.DecodeUnknown` -> `Schema.decodeUnknownEffect`: Use the function type returned by Schema.decodeUnknownEffect. + +- `ParseResult.Forbidden` -> `SchemaIssue.Forbidden`: Forbidden failures use the v4 SchemaIssue class; its constructor takes issue annotations plus optional input and parse options, retaining input only when reportInput is true. + +- `ParseResult.Missing` -> `SchemaIssue.MissingKey`: Missing-key failures use the v4 SchemaIssue class. + +- `ParseResult.ParseError`: TODO: needs guidance + +- `ParseResult.ParseErrorTypeId` -> `none`: The public symbol was removed; use Schema.isSchemaError for runtime narrowing. + +- `ParseResult.ParseIssue` -> `SchemaIssue.Issue`: The structured parse issue union moved to SchemaIssue. + +- `ParseResult.ParseResultFormatter` -> `SchemaIssue.Formatter`: Issue formatter types moved to SchemaIssue. + +- `ParseResult.Pointer` -> `SchemaIssue.Pointer`: Path-qualified failures moved to SchemaIssue. Construct them with the property path and nested issue; rejected input belongs to the nested issue when reportInput is enabled. + +- `ParseResult.Refinement` -> `SchemaIssue.Filter`: Refinement failures are represented as filter issues in v4. + +- `ParseResult.SingleOrNonEmpty` -> `ReadonlyArray`: This ParseResult helper type was removed; use an explicit value-or-non-empty-array type when still needed. + +- `ParseResult.Transformation` -> `SchemaIssue.Encoding`: Transformation-stage failures are Encoding issues in v4. They retain the failing AST and nested issue; the old Encoded, Transformation, and Type kind discriminator was removed. + +#### `ParseResult.TreeFormatter` + +**Replacement:** `SchemaIssue.defaultFormatter` + +Use the default SchemaIssue string formatter. + +**Example** + +```ts +SchemaIssue.defaultFormatter(issue) +``` + +- `ParseResult.Type` -> `SchemaIssue.InvalidType`: Type mismatches use the v4 SchemaIssue class. + +- `ParseResult.Unexpected` -> `SchemaIssue.UnexpectedKey`: Unexpected object keys use the v4 SchemaIssue class. + +- `ParseResult.decode`: TODO: needs guidance + +- `ParseResult.decodeEither` -> `Schema.decodeExit`: Either parsing was replaced by Exit parsing. + +- `ParseResult.decodePromise` -> `Schema.decodePromise`: Parsing helpers moved onto Schema and now fail with SchemaError. + +- `ParseResult.decodeSync` -> `Schema.decodeSync`: Parsing helpers moved onto Schema and now throw SchemaError. + +- `ParseResult.decodeUnknownEither` -> `Schema.decodeUnknownExit`: Either parsing was replaced by Exit parsing. + +- `ParseResult.decodeUnknownPromise` -> `Schema.decodeUnknownPromise`: Parsing helpers moved onto Schema and now reject with SchemaError. + +- `ParseResult.decodeUnknownSync` -> `Schema.decodeUnknownSync`: Parsing helpers moved onto Schema and now throw SchemaError. + +- `ParseResult.eitherOrUndefined` -> `none`: This ParseResult internal optimization was removed; use Effect, Exit, Option, or Result combinators directly. + +- `ParseResult.encode`: TODO: needs guidance + +- `ParseResult.encodeEither` -> `Schema.encodeExit`: Either encoding was replaced by Exit encoding. + +- `ParseResult.encodeSync` -> `Schema.encodeSync`: Encoding helpers moved onto Schema and now throw SchemaError. + +- `ParseResult.encodeUnknownEither` -> `Schema.encodeUnknownExit`: Either encoding was replaced by Exit encoding. + +- `ParseResult.encodeUnknownSync` -> `Schema.encodeUnknownSync`: Encoding helpers moved onto Schema and now throw SchemaError. + +- `ParseResult.fail` -> `Effect.fail`: Schema transformations now use Effect and fail with SchemaIssue.Issue. + +- `ParseResult.flatMap` -> `Effect.flatMap`: Schema transformations now use Effect combinators. + +- `ParseResult.isComposite` -> `SchemaIssue.Composite`: Narrow with instanceof SchemaIssue.Composite or inspect the issue \_tag. + +- `ParseResult.isParseError` -> `Schema.isSchemaError`: ParseError was replaced by SchemaError. + +- `ParseResult.map` -> `Effect.map`: Schema transformations now use Effect combinators. + +- `ParseResult.orElse` -> `Effect.orElse`: Schema transformations now use Effect combinators. + +#### `ParseResult.parseError` + +**Replacement:** `Schema.SchemaError` + +Construct a SchemaError from a SchemaIssue.Issue. + +**Example** + +```ts +new Schema.SchemaError(issue) +``` + +- `ParseResult.succeed` -> `Effect.succeed`: Schema transformations now use Effect. + +- `ParseResult.try` -> `Effect.try`: Schema transformations now use Effect and map thrown errors to SchemaIssue values. + +#### `ParseResult.validate` + +**Replacement:** `Schema.decodeEffect + Schema.toType` + +Validation-only parsers were removed; decode the type-side schema instead. + +**Example** + +```ts +Schema.decodeEffect(Schema.toType(schema)) +``` + +#### `ParseResult.validateEither` + +**Replacement:** `Schema.decodeExit + Schema.toType` + +Validation-only parsers were removed; decode the type-side schema instead. + +**Example** + +```ts +Schema.decodeExit(Schema.toType(schema)) +``` + +#### `ParseResult.validateOption` + +**Replacement:** `Schema.decodeOption + Schema.toType` + +Validation-only parsers were removed; decode the type-side schema instead. + +**Example** + +```ts +Schema.decodeOption(Schema.toType(schema)) +``` + +#### `ParseResult.validatePromise` + +**Replacement:** `Schema.decodePromise + Schema.toType` + +Validation-only parsers were removed; decode the type-side schema instead. + +**Example** + +```ts +Schema.decodePromise(Schema.toType(schema)) +``` + +#### `ParseResult.validateSync` + +**Replacement:** `Schema.decodeSync + Schema.toType` + +Validation-only parsers were removed; decode the type-side schema instead. + +**Example** + +```ts +Schema.decodeSync(Schema.toType(schema)) +``` + +### `effect/PartitionedSemaphore` + +- `PartitionedSemaphore.PartitionedSemaphore` -> `PartitionedSemaphore.PartitionedSemaphore`: The model remains and now also exposes capacity, available, take, release, withPermit, and conditional permit operations. + +- `PartitionedSemaphore.TypeId` -> `PartitionedSemaphore.PartitionedTypeId`: The public type id was renamed to distinguish it from the regular Semaphore type id. + +### `effect/Pipeable` + +- `Pipeable.PipeableConstructor` -> `Pipeable.PipeableConstructor`: Still exported; its rest arguments are ReadonlyArray\ in v4, so make explicit constructor typings readonly-compatible. + +### `effect/Pool` + +- `Pool.Pool` -> `Pool.Pool`: The model remains but is now Pipeable rather than an Effect subtype; borrow resources explicitly with Pool.get. + +- `Pool.Pool.Variance` -> `none`: The public Pool variance marker was removed; use Pool.Pool directly. + +- `Pool.PoolTypeId` -> `none`: The Pool type id is internal in v4; use Pool.isPool for runtime refinement. + +- `Pool.PoolUnify` -> `none`: Pool is no longer an Effect subtype, so its Effect unification helper was removed; call Pool.get explicitly. + +- `Pool.PoolUnifyIgnore` -> `none`: Pool is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +### `effect/Predicate` + +- `Predicate.Predicate` -> `Predicate.Predicate`: The callable interface is retained. Predicate.Any now uses any rather than never, which can affect generic inference. + +- `Predicate.Refinement` -> `Predicate.Refinement`: The refinement interface and its In, Out, and Any namespace types are retained. + +- `Predicate.all` -> `Predicate.Tuple(Array.from(collection))`: Use positional Tuple, materializing an Iterable when needed. V4 checks every configured position instead of accepting missing input values. + +- `Predicate.every` -> `Predicate.every`: Retained with the same AND semantics, short-circuiting, and true result for an empty collection. + +- `Predicate.isBigInt` -> `Predicate.isBigInt`: Retained with the same bigint refinement. + +- `Predicate.isBoolean` -> `Predicate.isBoolean`: Retained with the same boolean refinement. + +- `Predicate.isDate` -> `Predicate.isDate`: Retained with the same instanceof Date check. + +- `Predicate.isError` -> `Predicate.isError`: Retained with the same instanceof Error check. + +- `Predicate.isFunction` -> `Predicate.isFunction`: Retained with the same function refinement. + +- `Predicate.isIterable` -> `Predicate.isIterable`: Retained; strings and values exposing Symbol.iterator are still accepted. + +- `Predicate.isMap` -> `Predicate.isMap`: Retained with the same instanceof Map check. + +- `Predicate.isNever` -> `Predicate.isNever`: Retained as the always-false refinement. + +- `Predicate.isNotNull` -> `Predicate.isNotNull`: Retained; undefined still passes while null is excluded. + +- `Predicate.isNotNullable` -> `Predicate.isNotNullish`: Renamed to use nullish terminology; it still excludes null and undefined. + +- `Predicate.isNotUndefined` -> `Predicate.isNotUndefined`: Retained; null still passes while undefined is excluded. + +- `Predicate.isNull` -> `Predicate.isNull`: Retained with the same strict null refinement. + +- `Predicate.isNullable` -> `Predicate.isNullish`: Renamed to use nullish terminology. The guard now narrows with A & (null | undefined), including unknown inputs correctly. + +- `Predicate.isNumber` -> `Predicate.isNumber`: Retained; NaN and infinite numbers still pass. + +- `Predicate.isObject` -> `Predicate.isObjectKeyword`: Use isObjectKeyword to preserve v3 behavior accepting arrays and functions. V4 isObject has the former record-like semantics instead. + +- `Predicate.isPromise` -> `Predicate.isPromise`: Retained as the structural check for callable then and catch properties. + +- `Predicate.isPromiseLike` -> `Predicate.isPromiseLike`: Retained as the structural check for a callable then property. + +- `Predicate.isReadonlyRecord` -> `Predicate.isReadonlyObject`: Renamed; runtime behavior is unchanged and the index-key type now explicitly includes numbers. + +- `Predicate.isRecord` -> `Predicate.isObject`: Renamed; it still accepts non-null, non-array objects and now narrows with PropertyKey indexes. + +- `Predicate.isRegExp` -> `Predicate.isRegExp`: Retained with the same instanceof RegExp check. + +- `Predicate.isSet` -> `Predicate.isSet`: Retained with the same instanceof Set check. + +- `Predicate.isString` -> `Predicate.isString`: Retained with the same primitive string refinement. + +- `Predicate.isSymbol` -> `Predicate.isSymbol`: Retained with the same symbol refinement. + +- `Predicate.isTruthy` -> `Predicate.isTruthy`: Retained as a plain boolean predicate using JavaScript truthiness. + +- `Predicate.isUint8Array` -> `Predicate.isUint8Array`: Retained with the same instanceof Uint8Array check. + +- `Predicate.isUndefined` -> `Predicate.isUndefined`: Retained with the same strict undefined refinement. + +- `Predicate.isUnknown` -> `Predicate.isUnknown`: Retained as the always-true refinement. + +- `Predicate.not` -> `Predicate.not`: Retained with the same boolean negation; refinements still become plain predicates. + +- `Predicate.product` -> `Predicate.Tuple([self, that])`: Replace the two-position product helper with the Tuple constructor. + +- `Predicate.productMany` -> `Predicate.Tuple([self, ...Array.from(collection)])`: Materialize the predicate iterable in one Tuple call; v4 checks missing tail positions as undefined. + +- `Predicate.some` -> `Predicate.some`: Retained with the same OR semantics, short-circuiting, and false result for an empty collection. + +- `Predicate.struct` -> `Predicate.Struct`: Capitalized constructor name; field checks and refinement-aware typing are retained. + +- `Predicate.tuple` -> `Predicate.Tuple([p1, p2, ...])`: Capitalized constructor now takes one predicate array instead of rest arguments and checks missing positions as undefined. + +### `effect/Pretty` + +- `Pretty.Pretty` -> `Formatter.Formatter`: The formatter function type is now exported by Formatter. + +- `Pretty.PrettyAnnotation` -> `Schema.Annotations.ToFormatter.Declaration`: Custom declaration formatter annotations now use the toFormatter key in Schema.Annotations. + +#### `Pretty.make` + +**Replacement:** `Schema.toFormatter` + +Formatter derivation moved onto Schema. + +**Example** + +```ts +Schema.toFormatter(schema) +``` + +- `Pretty.match` -> `Schema.toFormatter`: The compiler match table was removed; customize traversal with the toFormatter onBefore option. + +### `effect/PubSub` + +- `PubSub.PubSub` -> `PubSub.PubSub`: The model remains but no longer extends Queue.Enqueue; replace Queue operations with explicit PubSub.publish, PubSub.publishAll, and PubSub.subscribe calls. + +### `effect/Queue` + +- `Queue.BackingQueue` -> `none`: Custom backing queues were removed by the v4 Queue rewrite; use Queue.make and its built-in capacity and strategy options. + +- `Queue.BackingQueueTypeId` -> `none`: BackingQueue and its public type id were removed. + +- `Queue.BaseQueue` -> `Queue.Enqueue | Queue.Dequeue`: The shared BaseQueue interface was removed; accept the required enqueue or dequeue capability and call Queue operations explicitly. + +- `Queue.Dequeue` -> `Queue.Dequeue`: The model remains and gains an error parameter, but is no longer an Effect subtype; use Queue.take explicitly. + +- `Queue.DequeueTypeId` -> `Queue.isDequeue`: The dequeue type id is internal in v4; use Queue.isDequeue for runtime refinement. + +- `Queue.DequeueUnify` -> `none`: Queue.Dequeue is no longer an Effect subtype, so its Effect unification helper was removed. + +- `Queue.DequeueUnifyIgnore` -> `none`: Queue.Dequeue is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +- `Queue.Enqueue` -> `Queue.Enqueue`: The write-side model remains, gains an error parameter, and is operated through Queue.offer and related functions. + +- `Queue.EnqueueTypeId` -> `Queue.isEnqueue`: The enqueue type id is internal in v4; use Queue.isEnqueue for runtime refinement. + +- `Queue.Queue` -> `Queue.Queue`: The model remains, gains an error parameter and completion signaling, and is no longer an Effect subtype; use Queue.take explicitly. + +- `Queue.Queue.BackingQueueVariance` -> `none`: BackingQueue and its variance marker were removed by the v4 Queue rewrite. + +- `Queue.Queue.DequeueVariance` -> `Queue.Dequeue.Variance`: The read-side variance marker moved under the Queue.Dequeue namespace and now includes the error type. + +- `Queue.Queue.EnqueueVariance` -> `Queue.Enqueue.Variance`: The write-side variance marker moved under the Queue.Enqueue namespace and now includes the error type. + +- `Queue.Queue.StrategyVariance` -> `none`: Public Strategy values and their variance marker were removed; select a string strategy when constructing the Queue. + +- `Queue.QueueStrategyTypeId` -> `none`: Public Strategy values and their type id were removed. + +- `Queue.QueueUnify` -> `none`: Queue is no longer an Effect subtype, so its Effect unification helper was removed; call Queue.take explicitly. + +- `Queue.QueueUnifyIgnore` -> `none`: Queue is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +- `Queue.Strategy` -> `"suspend" | "dropping" | "sliding"`: The pluggable Strategy interface was removed; choose one of the built-in strategy strings in Queue.make. + +- `Queue.awaitShutdown` -> `Queue.await`: Queue completion now includes normal end and failure; Queue.await waits for Done and propagates non-Done terminal causes. + +- `Queue.backPressureStrategy` -> `Queue.make({ strategy: "suspend" })`: Strategies are now constructor options rather than public Strategy values; suspend is the default. + +- `Queue.capacity` -> `queue.capacity`: Capacity is now a property on Queue.Enqueue and Queue.Dequeue rather than a module function. + +- `Queue.droppingStrategy` -> `Queue.make({ strategy: "dropping" })`: Strategies are now constructor options rather than public Strategy values; Queue.dropping is the bounded convenience constructor. + +- `Queue.isEmpty` -> `Effect.map(Queue.size(self), (size) => size === 0)`: The dedicated helper was removed; derive emptiness from Queue.size. + +- `Queue.isShutdown` -> `queue.state._tag === "Done"`: The dedicated helper was removed; inspect the public queue lifecycle state. Done includes normal completion and failure, not only explicit shutdown. + +- `Queue.slidingStrategy` -> `Queue.make({ strategy: "sliding" })`: Strategies are now constructor options rather than public Strategy values; Queue.sliding is the bounded convenience constructor. + +- `Queue.takeUpTo` -> `Queue.poll`: No direct bounded batch helper remains; repeatedly call non-blocking Queue.poll up to the limit, or use Queue.clear when taking every buffered value is acceptable. + +- `Queue.unsafeOffer` -> `Queue.offerUnsafe`: The unsafe suffix moved to the end. + +### `effect/Random` + +- `Random.Random` -> `Random.Random`: The context key is now a Context.Reference whose low-level service only has nextIntUnsafe and nextDoubleUnsafe. Prefer module operations; custom providers implement those two primitives. + +- `Random.RandomTypeId` -> `none`: The service is structural and no longer carries a public RandomTypeId brand. + +- `Random.fixed` -> `Effect.provideService(Random.Random, customRandom)`: No exact built-in equivalent remains. For deterministic tests, provide a cycling service implementing nextIntUnsafe and nextDoubleUnsafe; map non-number sequences explicitly. + +- `Random.make` -> `Random.withSeed`: Replace service construction and withRandom with Random.withSeed(seed)(program). V4 accepts string or number, returns an Effect transformation, and uses a different PRNG, so sequences are not v3-compatible. + +- `Random.nextRange` -> `Random.nextBetween`: Direct rename; both produce a floating-point value in the half-open range [min, max). + +- `Random.randomWith` -> `Random.Random.use`: Use Random.Random.use for raw service access. Prefer replacing callbacks that selected an old method with the corresponding module-level Random operation. + +### `effect/RcMap` + +- `RcMap.RcMap` -> `RcMap.RcMap`: The model remains as a Pipeable reference-counted resource map; use RcMap.get explicitly inside a Scope. + +- `RcMap.RcMap.Variance` -> `none`: The public variance marker was removed; use RcMap.RcMap directly. + +- `RcMap.TypeId` -> `none`: The RcMap type id is internal in v4; do not inspect or construct the brand directly. + +### `effect/RcRef` + +- `RcRef.RcRef` -> `RcRef.RcRef`: The model remains but is now only Pipeable; replace yielding or reading the RcRef directly with RcRef.get in a Scope. + +- `RcRef.RcRefUnify` -> `none`: RcRef is no longer an Effect subtype, so its Effect unification helper was removed; call RcRef.get explicitly. + +- `RcRef.RcRefUnifyIgnore` -> `none`: RcRef is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +- `RcRef.TypeId` -> `none`: The RcRef type id is internal in v4; do not inspect or construct the brand directly. + +### `effect/Readable` + +- `Readable.Readable` -> `Effect.Effect`: The branded wrapper was removed; represent read access directly as Effect.Effect\. + +- `Readable.TypeId` -> `Effect.TypeId`: The Readable brand was removed; use Effect.TypeId only when branding checks remain necessary after collapsing to Effect. + +- `Readable.isReadable` -> `Effect.isEffect`: Readable was removed; after representing reads directly as Effect, use the Effect guard. + +- `Readable.make` -> `Effect.Effect`: Use the supplied Effect directly; the v3 constructor only wrapped it as a get property. + +- `Readable.map` -> `Effect.map`: Represent Readable as Effect and map it directly. + +- `Readable.mapEffect` -> `Effect.flatMap`: Represent Readable as Effect and flatMap it directly. + +- `Readable.unwrap` -> `Effect.flatten`: After replacing the inner Readable with Effect, flatten the nested Effect directly. + +### `effect/Record` + +- `Record.ReadonlyRecord` -> `Record.ReadonlyRecord`: The public type and parameter order are unchanged. + +- `Record.ReadonlyRecord.IsFiniteString` -> `Record.ReadonlyRecord.IsFiniteString`: The namespace utility type is unchanged. + +- `Record.getEquivalence` -> `Record.makeEquivalence`: Direct rename; pass the value equivalence unchanged. + +- `Record.getLefts` -> `Record.getFailures`: Extract Result.Failure values while preserving keys. + +- `Record.getRights` -> `Record.getSuccesses`: Extract Result.Success values while preserving keys. + +- `Record.modifyOption` -> `Record.modify`: The Option suffix was dropped; missing keys still return Option.none. + +- `Record.partitionMap` -> `Record.partition`: Pass a mapper returning Result; failures and successes form the two output records. + +- `Record.replaceOption` -> `Record.replace`: The Option suffix was dropped; missing keys still return Option.none. + +### `effect/RedBlackTree` + +- `RedBlackTree.Direction` -> `none`: The tree direction type was removed; use normal array order or Array.reverse. + +- `RedBlackTree.RedBlackTree` -> `ReadonlyArray`: The core tree was removed; use sorted immutable entries for small collections or an external persistent ordered multimap when complexity or duplicate-key semantics matter. + +- `RedBlackTree.RedBlackTree.Direction` -> `none`: The nested direction type was removed; use normal array order or Array.reverse. + +- `RedBlackTree.TypeId` -> `none`: The RedBlackTree module and brand symbol were removed. + +- `RedBlackTree.at` -> `Array.drop`: Represent the removed tree as sorted entries; for a non-negative index, Array.drop(entries, index) traverses forward from that absolute position. + +- `RedBlackTree.atReversed` -> `Array.take + Array.reverse`: For a valid absolute index, reverse Array.take(entries, index + 1) to traverse backward from it. + +- `RedBlackTree.empty` -> `Array.empty`: The module was removed; use an empty Array\ and retain the Order separately. + +- `RedBlackTree.first` -> `Array.head`: On a sorted entry array, Array.head returns the same optional minimum entry. + +- `RedBlackTree.forEachBetween` -> `Array.filter + Array.forEach`: Filter sorted entries to min \<= key \< max with the retained Order, then visit them with Array.forEach. + +- `RedBlackTree.forEachGreaterThanEqual` -> `Array.filter + Array.forEach`: Filter sorted entries to key \>= min with the retained Order, then visit them in ascending order. + +- `RedBlackTree.forEachLessThan` -> `Array.filter + Array.forEach`: Filter sorted entries to key \< max with the retained Order, then visit them in ascending order. + +- `RedBlackTree.fromIterable` -> `Array.sortWith`: Sort the entry iterable by key and retain the Order separately; this does not preserve logarithmic tree operations. + +- `RedBlackTree.getAt` -> `Array.get`: Array.get on sorted entries preserves the optional index lookup behavior. + +- `RedBlackTree.getOrder` -> `none`: No replacement collection stores an Order; retain and pass the Order explicitly. + +- `RedBlackTree.greaterThan` -> `Array.filter`: Filter sorted entries with the retained Order for key \> bound. + +- `RedBlackTree.greaterThanEqual` -> `Array.filter`: Filter sorted entries with the retained Order for key \>= bound. + +- `RedBlackTree.greaterThanEqualReversed` -> `Array.filter + Array.reverse`: Filter sorted entries with the retained Order for key \>= bound, then reverse for descending traversal. + +- `RedBlackTree.greaterThanReversed` -> `Array.filter + Array.reverse`: Filter sorted entries with the retained Order for key \> bound, then reverse for descending traversal. + +- `RedBlackTree.has` -> `Array.some`: Use Array.some on sorted entries with Equal.equals for key membership; this is linear rather than logarithmic. + +- `RedBlackTree.insert` -> `Array.prepend + Array.sortWith`: Prepend the entry and sort by key to preserve newest-first comparator ties; use an external ordered multimap if logarithmic updates matter. + +- `RedBlackTree.isRedBlackTree` -> `Array.isArray`: The brand was removed; Array.isArray only checks the replacement representation and cannot prove its sorted invariant. + +- `RedBlackTree.keys` -> `Array.map`: Map sorted entries to keys and iterate the resulting array. + +- `RedBlackTree.keysReversed` -> `Array.reverse + Array.map`: Reverse sorted entries, map them to keys, and iterate the resulting array. + +- `RedBlackTree.last` -> `Array.last`: On a sorted entry array, Array.last returns the same optional maximum entry. + +- `RedBlackTree.lessThan` -> `Array.filter`: Filter sorted entries with the retained Order for key \< bound. + +- `RedBlackTree.lessThanEqual` -> `Array.filter`: Filter sorted entries with the retained Order for key \<= bound. + +- `RedBlackTree.lessThanEqualReversed` -> `Array.filter + Array.reverse`: Filter sorted entries with the retained Order for key \<= bound, then reverse for descending traversal. + +- `RedBlackTree.lessThanReversed` -> `Array.filter + Array.reverse`: Filter sorted entries with the retained Order for key \< bound, then reverse for descending traversal. + +- `RedBlackTree.make` -> `Array.sortWith`: Sort the supplied entries by key and retain the Order separately; this is not a balanced tree. + +- `RedBlackTree.reduce` -> `Array.reduce`: Reduce sorted entries in ascending order, adapting the callback to receive [key, value]. + +- `RedBlackTree.removeFirst` -> `Array.findFirstIndex + Array.remove`: Find the first entry whose key is Equal.equals to the target, then remove that index; leave the array unchanged when absent. + +- `RedBlackTree.reversed` -> `Array.reverse`: Reverse the sorted entry array for descending traversal. + +- `RedBlackTree.size` -> `Array.length`: Use Array.length or the .length property on the replacement entry array. + +- `RedBlackTree.values` -> `Array.map`: Map sorted entries to values and iterate the resulting array to preserve key order. + +- `RedBlackTree.valuesReversed` -> `Array.reverse + Array.map`: Reverse sorted entries, map them to values, and iterate to preserve reverse key order. + +### `effect/Redacted` + +- `Redacted.Redacted` -> `Redacted.Redacted`: The sensitive-value wrapper remains and now optionally carries a label. + +- `Redacted.Redacted.Variance` -> `Redacted.Redacted.Variance`: The type-level variance member remains. + +- `Redacted.RedactedTypeId` -> `Redacted.isRedacted`: The marker is private in v4; use the public guard for runtime narrowing. + +- `Redacted.getEquivalence` -> `Redacted.makeEquivalence`: Renamed to the v4 make-prefix convention. + +- `Redacted.unsafeWipe` -> `Redacted.wipeUnsafe`: Renamed to use the v4 Unsafe suffix convention. + +### `effect/Ref` + +- `Ref.Ref` -> `Ref.Ref`: The model remains but is now Pipeable rather than an Effect or Readable subtype; read it explicitly with Ref.get. + +- `Ref.Ref.Variance` -> `Ref.Ref.Variance`: The marker remains under Ref.Ref, but its brand uses an internal type id; ordinary code should use Ref.Ref directly. + +- `Ref.RefTypeId` -> `none`: The Ref type id is internal in v4; do not inspect or construct the brand directly. + +- `Ref.RefUnify` -> `none`: Ref is no longer an Effect subtype, so its Effect unification helper was removed; call Ref.get explicitly. + +- `Ref.RefUnifyIgnore` -> `none`: Ref is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +- `Ref.getAndSet` -> `Ref.getAndSet`: The operation remains with data-first and data-last forms. + +- `Ref.getAndUpdate` -> `Ref.getAndUpdate`: The operation remains with data-first and data-last forms. + +- `Ref.getAndUpdateSome` -> `Ref.getAndUpdateSome`: The operation remains; Option.none leaves the value unchanged. + +- `Ref.modify` -> `Ref.modify`: The operation remains with data-first and data-last forms. + +- `Ref.set` -> `Ref.set`: The operation remains with data-first and data-last forms. + +- `Ref.setAndGet` -> `Ref.setAndGet`: The operation remains with data-first and data-last forms. + +- `Ref.unsafeMake` -> `Ref.makeUnsafe`: The unsafe suffix moved to the end. + +- `Ref.update` -> `Ref.update`: The operation remains with data-first and data-last forms. + +- `Ref.updateAndGet` -> `Ref.updateAndGet`: The operation remains with data-first and data-last forms. + +- `Ref.updateSome` -> `Ref.updateSome`: The operation remains; Option.none leaves the value unchanged. + +- `Ref.updateSomeAndGet` -> `Ref.updateSomeAndGet`: The operation remains; Option.none leaves the value unchanged and returns the current value. + +### `effect/Reloadable` + +- `Reloadable.Reloadable` -> `LayerRef.LayerRef`: LayerRef is the v4 refreshable layer-context abstraction. + +- `Reloadable.Reloadable.Variance` -> `none`: The exported variance artifact was removed and LayerRef has no public counterpart. + +- `Reloadable.ReloadableTypeId` -> `none`: Reloadable was removed and LayerRef's marker is private. + +- `Reloadable.auto` -> `LayerRef.Service(..., { layer, invalidationSchedule: schedule, preload: true }).layer`: Use LayerRef for scheduled refresh; add idleTimeToLive: Duration.infinity to preserve an always-resident instance. + +- `Reloadable.autoFromConfig` -> `Layer.unwrap with Effect.contextWith and LayerRef.make`: Compute the schedule from the current context, then construct a preloaded LayerRef; no config-specific constructor remains. + +- `Reloadable.get` -> `ServiceRef.get or Effect.map(ServiceRef.contextEffect, Context.get(Service))`: LayerRef.get provides the current context as a layer; contextEffect gives scoped direct access. + +- `Reloadable.manual` -> `LayerRef.Service(..., { layer, preload: true }).layer`: Refresh with the generated service's refresh effect; use infinite idleTimeToLive for v3's resident lifecycle. + +- `Reloadable.reload` -> `ServiceRef.refresh`: Refresh invalidates and immediately reacquires; invalidate alone rebuilds on the next borrow. + +- `Reloadable.reloadFork` -> `ServiceRef.refresh.pipe(Effect.ignore({ log: true }), Effect.forkDetach({ startImmediately: true }), Effect.asVoid)`: This recreates logged, ignored background refresh; forkDaemon became forkDetach. + +- `Reloadable.tag` -> `LayerRef.Service()(id, options)`: The generated LayerRef service class is itself the Context.Service key. + +### `effect/Request` + +- `Request.Cache` -> `RequestResolver.asCache`: The runtime request cache type was removed; expose resolver results through a first-class Cache, or use RequestResolver.withCache to retain a resolver. + +- `Request.Entry` -> `Request.Entry`: Entry remains but now carries request, context, uninterruptible, and completeUnsafe fields; Deferred, listener, owner, and state fields were removed. + +- `Request.EntryTypeId` -> `none`: Request entries are unbranded structural values in v4; do not inspect or construct an entry type id. + +- `Request.Listeners` -> `none`: Request listener accounting is no longer public; cancellation and shared request lifecycle are managed by the v4 runtime and resolver caching. + +- `Request.Request` -> `Request.Request`: The request model remains and adds a third R parameter for services required while resolving the request. + +- `Request.Request.OptionalResult` -> `Exit.Exit>, Request.Error>`: The named alias was removed; write the optional request exit type directly when it is still required. + +- `Request.RequestTypeId` -> `none`: The request type id is internal in v4; define requests by extending Request.Request or with Request.Class and do not depend on branding internals. + +- `Request.interruptWhenPossible` -> `none`: Request cancellation is managed by the v4 batching runtime; resolver code should complete the entries it receives and not wrap work with this internal listener helper. + +- `Request.isEntry` -> `none`: The entry guard was removed; entries are supplied structurally to RequestResolver callbacks. + +- `Request.makeCache` -> `RequestResolver.asCache`: Create a cache from a resolver with capacity and timeToLive options, or use RequestResolver.withCache for a cached resolver. + +### `effect/RequestBlock` + +- `RequestBlock.Empty` -> `none`: The public blocked-request graph was removed; application code should compose Effect.request computations instead of inspecting Empty nodes. + +- `RequestBlock.Par` -> `none`: The public blocked-request graph was removed; express parallel request execution with Effect concurrency combinators. + +- `RequestBlock.RequestBlock` -> `none`: RequestBlock is no longer public in v4; compose Effect.request values directly and let the runtime batch requests by resolver. + +- `RequestBlock.Seq` -> `none`: The public blocked-request graph was removed; express sequencing in the Effect program instead of constructing Seq nodes. + +- `RequestBlock.empty` -> `Effect.void`: RequestBlock was removed; represent an empty computation as Effect.void and let Effect.request perform batching. + +- `RequestBlock.mapRequestResolvers` -> `Effect.request`: Pass the selected resolver to each Effect.request call; the runtime request graph can no longer be traversed to rewrite resolvers. + +- `RequestBlock.parallel` -> `Effect.all`: Compose request effects with Effect.all and explicit concurrency; v4 batching is performed by resolver and batch key rather than RequestBlock nodes. + +- `RequestBlock.reduce` -> `none`: The public blocked-request graph and reducer were removed; structure analysis is now internal to the request runtime. + +- `RequestBlock.sequential` -> `Effect.andThen`: Sequence request effects with Effect.andThen, flatMap, or generator syntax; RequestBlock sequencing nodes were removed. + +- `RequestBlock.single` -> `Effect.request`: Construct the request effect directly with its Request value and RequestResolver; the runtime creates pending entries internally. + +### `effect/RequestResolver` + +- `RequestResolver.RequestResolver` -> `RequestResolver.RequestResolver`: The interface remains as RequestResolver\; remove its R parameter and move service requirements to Request\. + +- `RequestResolver.RequestResolver.Variance` -> `RequestResolver.RequestResolver.Variance`: The variance marker remains but tracks only the accepted Request type; resolver environment variance was removed. + +- `RequestResolver.RequestResolverTypeId` -> `none`: The resolver type id is internal in v4; use RequestResolver constructors and isRequestResolver rather than depending on its brand. + +- `RequestResolver.aroundRequests` -> `RequestResolver.around`: around now receives Request.Entry batches; map entries to entry.request in before and after when hooks need raw request values. + +- `RequestResolver.contextFromEffect` -> `Request.Request`: Resolvers no longer carry an environment parameter; declare R on each Request and use entry.context inside the resolver callback. + +- `RequestResolver.contextFromServices` -> `Request.Request`: Declare the selected services in the Request R parameter and read them from each entry.context; resolver-level context capture was removed. + +- `RequestResolver.eitherWith` -> `RequestResolver.fromEffectTagged`: Define one resolver for the combined tagged request union, or use RequestResolver.make to partition entries manually; resolver routing combinators were removed. + +- `RequestResolver.locally` -> `Effect.provideService`: FiberRef-based resolver localization was removed; migrate the FiberRef to Context.Reference and provide its value around the request effect or resolver work. + +- `RequestResolver.makeBatched` -> `RequestResolver.make`: make now receives a non-empty batch of Request.Entry values; read entry.request and complete every entry with completeUnsafe or Request completion helpers. + +- `RequestResolver.makeWithEntry` -> `RequestResolver.make`: Use make for entry-level handling; v4 supplies one non-empty batch and key instead of nested sequential and parallel entry arrays. + +- `RequestResolver.mapInputContext` -> `Request.Request`: Resolver environments were removed; put required services on the Request R parameter and transform or provide each entry.context explicitly when needed. + +- `RequestResolver.provideContext` -> `Effect.provideService`: Provide services to Effect.request so they are captured in entry.context; RequestResolver itself no longer has an environment parameter. + +### `effect/Resource` + +- `Resource.Resource` -> `Resource.Resource`: The type remains but no longer extends Effect; use Resource.get(resource). + +- `Resource.Resource.Variance` -> `none`: The exported variance artifact was removed. + +- `Resource.ResourceTypeId` -> `Resource.isResource`: The marker is private; use the public runtime guard. + +- `Resource.ResourceUnify` -> `none`: Resource no longer extends Effect; use Resource.get explicitly. + +- `Resource.ResourceUnifyIgnore` -> `none`: The Effect-unification implementation detail was removed. + +### `effect/Runtime` + +- `Runtime.AsyncFiberException` -> `Cause.AsyncFiberError`: The error thrown when synchronous execution encounters an async boundary was renamed. + +- `Runtime.Cancel` -> `ReturnType`: Use the cancellation function returned by runCallback; the named type was removed. + +- `Runtime.FiberFailure` -> `none`: The runner error wrapper was removed; use an Exit-returning runner to retain and inspect a structured Cause. + +- `Runtime.FiberFailureCauseId` -> `none`: The FiberFailure wrapper and its cause marker were removed; inspect Cause through Exit instead. + +- `Runtime.FiberFailureId` -> `none`: The FiberFailure wrapper and its brand were removed; inspect Cause through Exit instead. + +- `Runtime.RunCallbackOptions` -> `Effect.RunOptions & { readonly onExit: (exit: Exit.Exit) => void }`: The callback runner now combines Effect.RunOptions with an onExit callback; no named options type is exported. + +- `Runtime.RunForkOptions` -> `Effect.RunOptions`: Use common runner options; express scoped forking with Effect.forkIn or Effect.forkScoped. + +- `Runtime.Runtime` -> `Context.Context`: Runtime values were removed; carry a Context and invoke the corresponding Effect.run\*With function. + +- `Runtime.Runtime.Context` -> `none`: The Runtime context extractor was removed; carry the service union directly on Context.Context. + +- `Runtime.defaultRuntime` -> `Context.empty()`: Runtime values were removed; call Effect.run\* directly or use an empty Context with an Effect.run\*With function. + +- `Runtime.defaultRuntimeFlags` -> `none`: Runtime flags were removed; configure scheduler yielding, interruptibility, and runtime metrics independently. + +- `Runtime.deleteFiberRef` -> `Context.omit`: FiberRefs became Context.Reference values; omit the Reference override from the Context. + +- `Runtime.disableRuntimeFlag` -> `none`: Runtime flags were removed; disable the corresponding scheduler, interruptibility, or metric behavior directly. + +- `Runtime.enableRuntimeFlag` -> `none`: Runtime flags were removed; enable the corresponding scheduler, interruptibility, or metric behavior directly. + +- `Runtime.isAsyncFiberException` -> `Cause.isAsyncFiberError`: Use the renamed guard from Cause. + +- `Runtime.isFiberFailure` -> `none`: FiberFailure no longer exists; use an Exit-returning runner and inspect Exit or Cause. + +- `Runtime.make` -> `Context.make`: Runtime values were removed; construct the service Context passed to Effect.run\*With instead. + +- `Runtime.makeFiberFailure` -> `Cause.squash`: Use Cause.squash only when a Cause must become the value thrown or rejected by a runner. + +- `Runtime.runCallback` -> `Effect.runCallbackWith`: Run with the former Runtime's Context; use Effect.runCallback when no services are required. + +- `Runtime.runFork` -> `Effect.runForkWith`: Run with the former Runtime's Context; use Effect.runFork when no services are required. + +- `Runtime.runPromise` -> `Effect.runPromiseWith`: Run with the former Runtime's Context; use Effect.runPromise when no services are required. + +- `Runtime.runPromiseExit` -> `Effect.runPromiseExitWith`: Run with the former Runtime's Context; use Effect.runPromiseExit when no services are required. + +- `Runtime.runSync` -> `Effect.runSyncWith`: Run with the former Runtime's Context; use Effect.runSync when no services are required. + +- `Runtime.runSyncExit` -> `Effect.runSyncExitWith`: Run with the former Runtime's Context; use Effect.runSyncExit when no services are required. + +- `Runtime.setFiberRef` -> `Context.add`: FiberRefs became Context.Reference values; add the Reference override to the Context. + +- `Runtime.updateContext` -> `Context transformation + Effect.run*With`: Runtime values were removed; transform the carried Context directly, then pass the result to the corresponding Effect.run\*With function. + +- `Runtime.updateFiberRefs` -> `Context.add`: There is no aggregate FiberRefs update; add targeted Context.Reference overrides to the carried Context explicitly. + +- `Runtime.updateRuntimeFlags` -> `none`: Runtime flags and aggregate patches were removed; configure each semantic behavior independently. + +### `effect/RuntimeFlags` + +- `RuntimeFlags.CooperativeYielding` -> `References.PreventSchedulerYield`: Use the scheduler Reference with inverse boolean meaning. + +- `RuntimeFlags.Interruption` -> `Effect.interruptible | Effect.uninterruptible`: The bit flag was removed; control interruptibility with Effect regions. + +- `RuntimeFlags.None` -> `none`: The empty runtime-flags value and its type were removed. + +- `RuntimeFlags.OpSupervision` -> `none`: Operation supervision and its runtime flag were removed. + +- `RuntimeFlags.RuntimeFlag` -> `none`: Individual bit flags were removed; use the corresponding semantic API. + +- `RuntimeFlags.RuntimeFlags` -> `none`: The aggregate runtime-flags bitset was removed. + +- `RuntimeFlags.RuntimeMetrics` -> `Metric.FiberRuntimeMetrics`: Runtime metrics are now configured through a Context.Reference service rather than a bit flag. + +- `RuntimeFlags.WindDown` -> `none`: The wind-down flag is no longer public. + +- `RuntimeFlags.cooperativeYielding` -> `!References.PreventSchedulerYield`: Read the scheduler Reference and negate it; the aggregate flags value was removed. + +- `RuntimeFlags.diff` -> `none`: The runtime-flags bitset was removed; configure each semantic behavior directly. + +- `RuntimeFlags.differ` -> `none`: The runtime-flags bitset and patch differ were removed. + +- `RuntimeFlags.disable` -> `none`: The generic flag operation was removed; disable the corresponding behavior directly. + +- `RuntimeFlags.disableAll` -> `none`: The aggregate flags value was removed; configure scheduler yielding, interruptibility, and metrics independently. + +- `RuntimeFlags.disableCooperativeYielding` -> `Effect.provideService(References.PreventSchedulerYield, true)`: Prevent scheduler yielding through its Context.Reference. + +- `RuntimeFlags.disableInterruption` -> `Effect.uninterruptible`: Use an uninterruptible region instead of changing a runtime flag. + +- `RuntimeFlags.disableOpSupervision` -> `none`: Operation supervision and its runtime flag were removed. + +- `RuntimeFlags.disableRuntimeMetrics` -> `Metric.disableRuntimeMetrics`: Disable fiber runtime metrics directly; use disableRuntimeMetricsLayer when providing a Layer. + +- `RuntimeFlags.disableWindDown` -> `none`: The wind-down flag is runtime-internal in v4; use normal scoped finalizers and explicit interruptibility regions. + +- `RuntimeFlags.enable` -> `none`: The generic flag operation was removed; enable the corresponding behavior directly. + +- `RuntimeFlags.enableAll` -> `none`: The aggregate flags value was removed; configure scheduler yielding, interruptibility, and metrics independently. + +- `RuntimeFlags.enableCooperativeYielding` -> `Effect.provideService(References.PreventSchedulerYield, false)`: Allow scheduler yielding through its Context.Reference. + +- `RuntimeFlags.enableInterruption` -> `Effect.interruptible`: Use an interruptible region instead of changing a runtime flag. + +- `RuntimeFlags.enableOpSupervision` -> `none`: Operation supervision and its runtime flag were removed. + +- `RuntimeFlags.enableRuntimeMetrics` -> `Metric.enableRuntimeMetrics`: Enable fiber runtime metrics directly; use enableRuntimeMetricsLayer when providing a Layer. + +- `RuntimeFlags.enableWindDown` -> `none`: The wind-down flag is runtime-internal in v4; use normal scoped finalizers and explicit interruptibility regions. + +- `RuntimeFlags.interruptible` -> `none`: There is no public current-interruptibility getter; structure the program with Effect.interruptible or Effect.uninterruptible. + +- `RuntimeFlags.interruption` -> `none`: Interruptibility is controlled by Effect regions rather than queried from a flags value. + +- `RuntimeFlags.isDisabled` -> `none`: There is no aggregate flags value to query; inspect or control the corresponding semantic facility. + +- `RuntimeFlags.make` -> `none`: The runtime-flags bitset was removed; do not recreate it in v4. + +- `RuntimeFlags.none` -> `none`: The runtime-flags bitset was removed; configure each semantic behavior independently. + +- `RuntimeFlags.opSupervision` -> `none`: Operation supervision and its runtime flag were removed. + +- `RuntimeFlags.patch` -> `none`: Aggregate runtime-flags patches were removed; configure each semantic behavior directly. + +- `RuntimeFlags.render` -> `none`: The runtime-flags bitset and its renderer were removed. + +- `RuntimeFlags.runtimeMetrics` -> `Metric.FiberRuntimeMetrics`: Read the Context.Reference and test for undefined instead of querying a bit flag. + +- `RuntimeFlags.toSet` -> `none`: The runtime-flags bitset was removed; there is no set conversion. + +- `RuntimeFlags.windDown` -> `none`: The wind-down flag is no longer public. + +### `effect/STM` + +- `STM.Adapter` -> `none`: The STM.gen adapter was removed; Effect.gen accepts yielded Effects directly. + +- `STM.All.IsDiscard` -> `Effect.All.IsDiscard`: The helper moved to Effect.All because STM.all is now Effect.all. + +- `STM.All.Narrow` -> `none`: Effect.all uses a const generic directly, so the separate tuple-narrowing helper was removed. + +- `STM.All.Options` -> `none`: Effect.all inlines its options type; use its concurrency, discard, and mode options directly. + +- `STM.All.STMAny` -> `Effect.All.EffectAny`: STM inputs are ordinary Effects in v4, so use the Effect.All helper. + +- `STM.All.Signature` -> `typeof Effect.all`: The named STM all signature was removed; refer to Effect.all directly. + +- `STM.Do` -> `Effect.Do`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.STM` -> `Effect.Effect`: The distinct STM instruction type was removed. Tx APIs return Effect values; wrap the complete transaction in Effect.tx. + +- `STM.STM.Variance` -> `Effect.Variance`: The distinct STM variance marker was removed with STM; use the Effect marker. + +- `STM.STMTypeId` -> `Effect.TypeId`: The distinct STM type id was removed because transactions are represented by Effect values. + +- `STM.STMTypeLambda` -> `Effect.EffectTypeLambda`: Use the Effect type lambda; transactional requirements are represented by Effect.Transaction. + +- `STM.STMUnify` -> `Effect.EffectUnify`: STM unification moved to ordinary Effect unification. + +- `STM.STMUnifyIgnore` -> `none`: The STM-specific unification ignore marker was removed; rely on Effect inference. + +- `STM.acquireUseRelease` -> `Effect.acquireUseRelease + Effect.tx`: Wrap acquire, use, and release in separate Effect.tx calls to preserve the v3 separately committed phases; v4 release also receives the use Exit. + +- `STM.all` -> `Effect.all`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.as` -> `Effect.as`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.asSome` -> `Effect.asSome`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.asSomeError` -> `Effect.mapError(self, Option.some)`: The dedicated helper was removed; map the error into Option.some. + +- `STM.asVoid` -> `Effect.asVoid`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.attempt` -> `Effect.try`: The constructor was renamed; transaction programs are ordinary Effects in v4. + +- `STM.bind` -> `Effect.bind`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.bindTo` -> `Effect.bindTo`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.catchAll` -> `Effect.catch`: Use Effect.catch for typed failures. It does not catch Effect.txRetry or restore a transactional savepoint. + +- `STM.catchSome` -> `Effect.catch + Option.match`: Use Effect.catch and re-fail the original error when the partial handler returns None. + +- `STM.catchTag` -> `Effect.catchTag`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.check` -> `Effect.suspend + Effect.txRetry`: Evaluate the predicate lazily and return Effect.void when true or Effect.txRetry when false, inside Effect.tx. + +- `STM.collect` -> `Effect.flatMap + Option.match + Effect.txRetry`: Map Some to success and None to Effect.txRetry inside the surrounding Effect.tx transaction. + +- `STM.collectSTM` -> `Effect.flatMap + Option.match + Effect.txRetry`: Return the Effect held by Some and use Effect.txRetry for None, inside the surrounding Effect.tx transaction. + +- `STM.commit` -> `Effect.tx`: Effect.tx runs an Effect transaction and removes its Effect.Transaction requirement. + +- `STM.commitEither` -> `Effect.tx + Effect.result + Effect.fromResult`: Run Effect.tx(Effect.result(body)) before Effect.fromResult so journal changes commit even when the original transaction had a typed failure. + +- `STM.cond` -> `Effect.suspend`: Lazily branch to Effect.succeed or Effect.fail based on the predicate. + +- `STM.context` -> `Effect.context`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.contextWith` -> `Effect.contextWith`: The name remains, but the v4 callback returns an Effect directly. + +- `STM.contextWithSTM` -> `Effect.contextWith`: The Effect-returning context constructor no longer needs an STM suffix. + +- `STM.dieMessage` -> `Effect.die(new Error(message))`: The message-specific helper was removed; construct a message-bearing defect explicitly. + +- `STM.dieSync` -> `Effect.suspend(() => Effect.die(evaluate()))`: The lazy defect helper was removed; suspend construction and then die. + +- `STM.either` -> `Effect.result`: V4 uses Result instead of Either for materialized typed failures. + +- `STM.ensuring` -> `Effect.ensuring`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.eventually` -> `Effect.eventually`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.every` -> `Effect.findFirst + Option.isNone`: Search sequentially for the first false effectful predicate; no match means every element passed. + +- `STM.exists` -> `Effect.findFirst + Option.isSome`: Search sequentially for the first true effectful predicate. + +- `STM.fail` -> `Effect.fail`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.failSync` -> `Effect.failSync`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.fiberId` -> `Effect.fiberId`: The operation remains on Effect, but v4 yields the fiber id as a number. + +- `STM.filter` -> `Effect.filter`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.filterNot` -> `Effect.filter`: Negate the effectful predicate result and use Effect.filter. + +- `STM.filterOrDie` -> `Effect.filterOrFail + Effect.orDie`: Fail with the lazy defect when the predicate rejects, then convert that failure to a defect. + +- `STM.filterOrDieMessage` -> `Effect.filterOrFail + Effect.orDie`: Fail with a new Error carrying the message when the predicate rejects, then convert it to a defect. + +- `STM.filterOrFail` -> `Effect.filterOrFail`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.firstSuccessOf` -> `Effect.firstSuccessOf`: This only preserves typed-failure fallback. V4 has no equivalent for v3 retry-aware alternatives with journal savepoints. + +- `STM.flatMap` -> `Effect.flatMap`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.flatten` -> `Effect.flatten`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.flip` -> `Effect.flip`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.flipWith` -> `Effect.flip(self).pipe(f, Effect.flip)`: Compose the retained Effect.flip operation around the transforming function. + +- `STM.fromEither` -> `Effect.fromResult`: V4 replaced Either with Result; migrate the value and use Effect.fromResult. + +- `STM.head` -> `Effect.matchEffect`: Map source failures to Option.some, return the first iterable element, and fail with Option.none when empty. + +- `STM.if` -> `Effect.suspend or Effect.flatMap`: Select the true or false branch lazily; use flatMap when the condition is effectful. + +- `STM.ignore` -> `Effect.ignore`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.interrupt` -> `Effect.interrupt`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.interruptAs` -> `Effect.interrupt`: V4 exposes interruption of the current fiber only; remove the explicit FiberId argument. + +- `STM.isFailure` -> `Effect.isFailure`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.isSTM` -> `Effect.isEffect`: STM no longer has a distinct runtime representation; transaction programs are Effects. + +- `STM.isSuccess` -> `Effect.isSuccess`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.iterate` -> `Effect.gen loop`: No direct Effect iterate helper remains; carry state in an explicit sequential Effect.gen loop inside Effect.tx. + +- `STM.let` -> `Effect.let`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.loop` -> `Effect.gen loop`: No direct Effect loop helper remains; implement the state loop explicitly and collect values unless discard was requested. + +- `STM.map` -> `Effect.map`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.mapAttempt` -> `Effect.flatMap(self, (a) => Effect.try(() => f(a)))`: Use Effect.try in flatMap so thrown exceptions remain typed failures rather than defects. + +- `STM.mapInputContext` -> `Effect.updateContext`: The context-input mapping operation was renamed on Effect. + +- `STM.match` -> `Effect.match`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.matchSTM` -> `Effect.matchEffect`: The Effect-returning match combinator no longer has an STM suffix. + +- `STM.mergeAll` -> `Effect.reduce`: Reduce the input Effects sequentially and combine each produced value with the accumulator. + +- `STM.none` -> `Effect.matchEffect + Option.match`: Recreate the Option success/error shuffle explicitly; no dedicated helper remains. + +- `STM.option` -> `Effect.option`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.orDie` -> `Effect.orDie`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.orDieWith` -> `Effect.mapError + Effect.orDie`: Map the typed error to the desired defect and then use Effect.orDie. + +- `STM.orElse` -> `none`: V4 has no exact retry-aware transactional alternative with journal savepoint restoration. Effect.catch is only a failure-only approximation. + +- `STM.orElseEither` -> `none`: V4 has no exact retry-aware alternative. For typed failures only, compose Effect.catch and Result tagging manually. + +- `STM.orElseFail` -> `Effect.mapError`: Map typed failures to the replacement error; this does not preserve v3 retry fallback semantics. + +- `STM.orElseOptional` -> `Effect.catch + Option.match`: Run the fallback for None and re-fail Some errors explicitly. + +- `STM.orElseSucceed` -> `Effect.orElseSucceed`: The name remains for typed failures, but v4 does not preserve v3 retry fallback or journal savepoints. + +- `STM.orTry` -> `none`: V4 exposes no recoverable retry signal or public transactional savepoint; restructure branch selection before Effect.txRetry. + +- `STM.partition` -> `Effect.partition`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.provideServiceSTM` -> `Effect.provideServiceEffect`: The effectful service provider was renamed on Effect. + +- `STM.provideSomeContext` -> `Effect.provideContext or Effect.updateContext`: The dedicated partial-context helper was removed; provide or update the Effect context explicitly. + +- `STM.reduce` -> `Effect.reduce`: The combinator remains, but v4 takes the initial state lazily and also passes the element index. + +- `STM.reduceAll` -> `Effect.flatMap + Effect.reduce`: Evaluate the initial Effect, then reduce the remaining Effects sequentially. + +- `STM.reduceRight` -> `Effect.reduce over a reversed Array`: Materialize and reverse the iterable, then reduce while preserving the old state/element callback order. + +- `STM.refineOrDie` -> `Effect.catch + Option.match`: Re-fail Some refined errors and die with the original error for None. + +- `STM.refineOrDieWith` -> `Effect.catch + Option.match`: Re-fail Some refined errors and map None to the requested defect. + +- `STM.reject` -> `Effect.flatMap + Option.match`: Fail when the partial rejection returns Some; otherwise keep the original success. + +- `STM.rejectSTM` -> `Effect.flatMap + Option.match`: Run and fail with the Effect held by Some; otherwise keep the original success. + +- `STM.repeatUntil` -> `Effect.repeat(self, { until: predicate })`: The dedicated combinator moved to Effect.repeat options. + +- `STM.repeatWhile` -> `Effect.repeat(self, { while: predicate })`: The dedicated combinator moved to Effect.repeat options. + +- `STM.replicate` -> `Effect.replicate`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.replicateSTM` -> `Effect.replicateEffect`: Use the effectful replication combinator and keep execution sequential inside Effect.tx. + +- `STM.replicateSTMDiscard` -> `Effect.replicateEffect(self, n, { discard: true })`: Use effectful replication with discard enabled and keep execution sequential inside Effect.tx. + +- `STM.retry` -> `Effect.txRetry`: Do not use Effect.retry, which retries typed failures by schedule; Effect.txRetry waits for an accessed Tx value to change. + +- `STM.retryUntil` -> `Effect.flatMap + Effect.txRetry`: Succeed when the predicate passes; otherwise return Effect.txRetry inside Effect.tx. + +- `STM.retryWhile` -> `Effect.flatMap + Effect.txRetry`: Return Effect.txRetry while the predicate passes; otherwise succeed inside Effect.tx. + +- `STM.some` -> `Effect.matchEffect + Option.match`: Recreate the Option success/error shuffle explicitly; no dedicated helper remains. + +- `STM.succeed` -> `Effect.succeed`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.summarized` -> `Effect.gen`: Run the summary Effect before and after the body, then return the computed summary and body value. + +- `STM.sync` -> `Effect.sync`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.tap` -> `Effect.tap`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.tapBoth` -> `Effect.tapError + Effect.tap`: Compose the separate failure and success taps. + +- `STM.tapError` -> `Effect.tapError`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.try` -> `Effect.try`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.unless` -> `Effect.when(self, Effect.sync(() => !predicate()))`: V4 Effect.when takes an effectful condition; suspend and negate the old lazy boolean. + +- `STM.unlessSTM` -> `Effect.when(self, Effect.map(condition, (b) => !b))`: Negate the effectful condition and use Effect.when. + +- `STM.unsome` -> `Effect.matchEffect + Option.match`: Recreate the Option error/success shuffle explicitly; no dedicated helper remains. + +- `STM.validateAll` -> `Effect.validate`: The validation combinator was renamed and now returns a NonEmptyArray of errors. + +- `STM.validateFirst` -> `Effect.flip + Effect.forEach`: Flip each candidate result, traverse sequentially, then flip the aggregate to preserve all errors when every candidate fails. + +- `STM.void` -> `Effect.void`: The combinator keeps its name, but STM values are now ordinary Effects. Run the complete transaction with Effect.tx. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `STM.when` -> `Effect.when(self, Effect.sync(predicate))`: V4 Effect.when takes an effectful boolean, so suspend the old lazy predicate. + +- `STM.whenSTM` -> `Effect.when`: The effectful-condition form is now the only Effect.when form. + +- `STM.zipLeft` -> `Effect.zipWith(self, that, (left) => left)`: Use sequential Effect.zipWith and retain the left result. + +- `STM.zipRight` -> `Effect.andThen`: Use Effect.andThen for sequential composition that retains the right result. + +### `effect/Schedule` + +- `Schedule.CurrentIterationMetadata` -> `Schedule.CurrentMetadata`: The Context.Reference was renamed and now provides Schedule.Metadata with input, output, attempt, duration, and elapsed timing fields. + +- `Schedule.IterationMetadata` -> `Schedule.Metadata`: The metadata model now includes duration and uses attempt instead of recurrence; elapsed fields are millisecond numbers. + +- `Schedule.Schedule` -> `Schedule.Schedule`: The model remains but now has Schedule\; its public initial/step fields were replaced by Schedule.toStep and fromStep. + +- `Schedule.Schedule.DriverVariance` -> `none`: ScheduleDriver was removed in v4, so its variance marker has no replacement. Use the Schedule type parameters or the function returned by Schedule.toStepWithSleep. + +- `Schedule.Schedule.Variance` -> `Schedule.Schedule.Variance`: The variance marker remains and now tracks Output, Input, Error, and Env through the private Schedule TypeId. + +- `Schedule.ScheduleDriver` -> `Schedule.toStepWithSleep`: ScheduleDriver was removed. The acquired step function provides manual next calls with automatic sleeping; Schedule.toStep exposes raw delays. + +- `Schedule.ScheduleDriverTypeId` -> `none`: ScheduleDriver and its public type id were removed. Use the step function returned by Schedule.toStepWithSleep. + +- `Schedule.ScheduleTypeId` -> `none`: The Schedule type id is private in v4. Use Schedule.isSchedule to narrow unknown values. + +- `Schedule.addDelayEffect` -> `Schedule.addDelay`: The v4 function is effectful by default and its callback receives full Schedule.Metadata; read metadata.output when only the prior output is needed. + +- `Schedule.andThen` -> `Schedule.concat`: The sequencing combinator was renamed to Schedule.concat. + +- `Schedule.andThenEither` -> `Schedule.concatResult`: Sequential phase tagging now uses Result: self outputs are Result.fail and the following schedule outputs are Result.succeed. + +- `Schedule.as` -> `Schedule.map`: Map the metadata to the constant output; Schedule.map accepts either a plain value or an Effect. + +- `Schedule.asVoid` -> `Schedule.map`: Map every output to undefined. + +- `Schedule.bothInOut` -> `none`: There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done. + +- `Schedule.check` -> `Schedule.while`: Continue while a predicate over metadata.input and metadata.output returns true. + +- `Schedule.checkEffect` -> `Schedule.while`: Schedule.while accepts an effectful metadata predicate in v4. + +- `Schedule.collectAllInputs` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.collectAllOutputs` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.collectUntil` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.collectUntilEffect` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.collectWhile` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.collectWhileEffect` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.compose` -> `none`: There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done. + +- `Schedule.count` -> `Schedule.forever`: The forever schedule outputs the zero-based recurrence count. + +- `Schedule.dayOfMonth` -> `Schedule.cron`: Express the calendar constraint as a cron expression, for example `0 0 * *`, and map its Duration output if a numeric output is required. + +- `Schedule.dayOfWeek` -> `Schedule.cron`: Express the weekday constraint as a cron expression, for example `0 0 * * `, and map its Duration output if a numeric output is required. + +- `Schedule.delayed` -> `Schedule.modifyDelay`: Return Effect.succeed(f(metadata.duration)); delay transformations are effectful and receive full metadata in v4. + +- `Schedule.delayedEffect` -> `Schedule.modifyDelay`: The v4 delay modifier is effectful by default and receives full Schedule.Metadata. + +- `Schedule.delayedSchedule` -> `Schedule.modifyDelay`: Replace each delay with metadata.output, converting that Duration output through Effect.succeed. + +- `Schedule.delays` -> `Schedule.map`: Map each decision to metadata.duration to expose the selected recurrence delay. + +- `Schedule.driver` -> `Schedule.toStepWithSleep`: Acquire the sleeping step function and call it for each input; use Schedule.toStep when delay handling must remain manual. + +- `Schedule.either` -> `Schedule.min`: Use Schedule.min for fastest-delay composition. It outputs the selected Duration rather than a tuple of both outputs. + +- `Schedule.eitherWith` -> `Schedule.min`: Schedule.min implements the standard fastest-delay composition; custom interval merging requires a Schedule.fromStep implementation. + +- `Schedule.elapsed` -> `Schedule.map`: Map metadata.elapsed through Duration.millis. + +- `Schedule.ensuring` -> `Schedule.during`: Use the duration-bounded v4 schedule constructor. + +- `Schedule.fromDelay` -> `Schedule.duration`: The duration constructor recurs once after the supplied delay. + +- `Schedule.fromDelays` -> `Schedule.duration + Schedule.concat`: Build one Schedule.duration per delay and sequence them with Schedule.concat. + +- `Schedule.fromFunction` -> `Schedule.identity + Schedule.map`: Start with Schedule.identity\() and map metadata.input through the function. + +- `Schedule.hourOfDay` -> `Schedule.cron`: Express the hour constraint as a cron expression such as `0 * * *`. + +- `Schedule.intersect` -> `Schedule.max`: Use Schedule.max for slowest-delay composition. It outputs the selected Duration rather than a tuple of both outputs. + +- `Schedule.intersectWith` -> `Schedule.max`: Schedule.max implements the standard slowest-delay composition; custom interval merging requires a Schedule.fromStep implementation. + +- `Schedule.jitteredWith` -> `Schedule.modifyDelay`: For custom bounds, scale metadata.duration using Random.next inside the effectful delay callback; Schedule.jittered supplies the fixed v4 0.8-1.2 range. + +- `Schedule.linear` -> `Schedule.forever + Schedule.map + Schedule.modifyDelay`: Map the recurrence attempt to the linearly increasing Duration, then use that output as the recurrence delay. + +- `Schedule.makeWithState` -> `Schedule.fromStep`: Move mutable state into the acquired step closure; return [output, Duration] for recurrence and Cause.done(output) for termination. + +- `Schedule.mapBoth` -> `Schedule.fromStep + Schedule.toStep`: Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step. + +- `Schedule.mapBothEffect` -> `Schedule.fromStep + Schedule.toStep`: Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step. Apply the effectful output mapping to the returned tuple. + +- `Schedule.mapEffect` -> `Schedule.map`: Schedule.map accepts an Effect result and receives full Schedule.Metadata. + +- `Schedule.mapInput` -> `Schedule.fromStep + Schedule.toStep`: Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step. + +- `Schedule.mapInputContext` -> `Schedule.fromStep + Effect.provide`: Provide the transformed service context to both Schedule.toStep acquisition and each returned step Effect. + +- `Schedule.mapInputEffect` -> `Schedule.fromStep + Schedule.toStep`: Input transformation is no longer a standalone combinator. Wrap Schedule.toStep(self) with Schedule.fromStep and transform the input before invoking the underlying step. Evaluate the input mapping Effect before the underlying step. + +- `Schedule.minuteOfHour` -> `Schedule.cron`: Express the minute constraint as a cron expression such as ` * * * *`. + +- `Schedule.modifyDelayEffect` -> `Schedule.modifyDelay`: The v4 delay modifier is effectful by default and receives full Schedule.Metadata. + +- `Schedule.onDecision` -> `Schedule.tap`: Use Schedule.tap for effects on recurrence metadata. To also observe final completion, wrap Schedule.toStep with Pull.matchEffect in Schedule.fromStep. + +- `Schedule.once` -> `Schedule.duration(Duration.zero)`: A zero-duration schedule recurs once and then completes; map its Duration output to void if needed. + +- `Schedule.provideContext` -> `Schedule.fromStep + Effect.provide`: Provide the Context to both Schedule.toStep acquisition and each Effect returned by the acquired step. + +- `Schedule.provideService` -> `Schedule.fromStep + Effect.provideService`: Provide the service to both Schedule.toStep acquisition and each Effect returned by the acquired step. + +- `Schedule.recurUntil` -> `Schedule.identity + Schedule.while`: Continue while the predicate over metadata.input is false. + +- `Schedule.recurUntilEffect` -> `Schedule.identity + Schedule.while`: Continue while the effectful predicate over metadata.input is false. + +- `Schedule.recurUntilOption` -> `Schedule.fromStep`: Use a custom step to evaluate the Option-producing function, emit Option.none while recurring, and terminate with the first Option.some result. + +- `Schedule.recurUpTo` -> `Schedule.during`: Use the duration-bounded schedule constructor. + +- `Schedule.recurWhile` -> `Schedule.identity + Schedule.while`: Continue while the predicate over metadata.input is true. + +- `Schedule.recurWhileEffect` -> `Schedule.identity + Schedule.while`: Continue while the effectful predicate over metadata.input is true. + +- `Schedule.reduce` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.reduceEffect` -> `none`: This stateful collection combinator was removed during the v4 Schedule simplification. Rebuild it with Schedule.fromStep and Schedule.toStep, keeping accumulation state inside the acquired step closure. + +- `Schedule.repeatForever` -> `Schedule.forever`: The infinite zero-delay counter schedule was renamed. + +- `Schedule.repetitions` -> `Schedule.map`: Map metadata.attempt to the required recurrence count, adjusting by one where the v3 zero-based value is expected. + +- `Schedule.resetAfter` -> `none`: Automatic schedule reset was removed. Wrap Schedule.toStep(self) with Schedule.fromStep and reacquire the inner step when the reset condition is met. + +- `Schedule.resetWhen` -> `none`: Automatic schedule reset was removed. Wrap Schedule.toStep(self) with Schedule.fromStep and reacquire the inner step when the reset condition is met. + +- `Schedule.run` -> `Schedule.toStep`: Acquire the step and traverse inputs manually, supplying each timestamp and collecting successful outputs until Cause.done. + +- `Schedule.secondOfMinute` -> `Schedule.cron`: Use the six-field cron form to express a seconds constraint, for example ` * * * * *`. + +- `Schedule.stop` -> `Schedule.fromStep`: Create a step that immediately returns Cause.done(undefined). + +- `Schedule.succeed` -> `Schedule.forever + Schedule.map`: Map every recurrence to the constant value. + +- `Schedule.sync` -> `Schedule.forever + Schedule.map`: Map every recurrence by lazily evaluating the thunk. + +- `Schedule.tapInput` -> `Schedule.tap`: Use the unified tap callback and read metadata.input. + +- `Schedule.tapOutput` -> `Schedule.tap`: Use the unified tap callback and read metadata.output. + +- `Schedule.unfold` -> `Schedule.fromStep`: Keep the evolving value inside the acquired step closure and emit each value with the desired Duration. + +- `Schedule.union` -> `Schedule.min`: Use Schedule.min for fastest-delay composition. It outputs the selected Duration rather than both schedule outputs. + +- `Schedule.unionWith` -> `Schedule.min`: Schedule.min covers the standard union behavior; a custom interval merge requires Schedule.fromStep. + +- `Schedule.untilInput` -> `Schedule.while`: Continue while the predicate over metadata.input is false. + +- `Schedule.untilInputEffect` -> `Schedule.while`: Continue while the effectful predicate over metadata.input is false. + +- `Schedule.untilOutput` -> `Schedule.while`: Continue while the predicate over metadata.output is false. + +- `Schedule.untilOutputEffect` -> `Schedule.while`: Continue while the effectful predicate over metadata.output is false. + +- `Schedule.whileInput` -> `Schedule.while`: Continue while the predicate over metadata.input is true. + +- `Schedule.whileInputEffect` -> `Schedule.while`: Continue while the effectful predicate over metadata.input is true. + +- `Schedule.whileOutput` -> `Schedule.while`: Continue while the predicate over metadata.output is true. + +- `Schedule.whileOutputEffect` -> `Schedule.while`: Continue while the effectful predicate over metadata.output is true. + +- `Schedule.zipLeft` -> `none`: There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done. + +- `Schedule.zipRight` -> `none`: There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done. + +- `Schedule.zipWith` -> `none`: There is no direct v4 combinator preserving this output shape. Rebuild it with Schedule.fromStep and Schedule.toStep; schedule steps now return [output, Duration] and terminate with Cause.done. + +### `effect/ScheduleDecision` + +- `ScheduleDecision.Done` -> `Cause.Done`: ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). + +- `ScheduleDecision.ScheduleDecision` -> `none`: ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). + +- `ScheduleDecision.continue` -> `Effect.succeed([output, duration])`: ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). + +- `ScheduleDecision.continueWith` -> `Effect.succeed([output, duration])`: ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). + +- `ScheduleDecision.isContinue` -> `none`: ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). Branch on the Pull result instead of inspecting a decision value. + +- `ScheduleDecision.isDone` -> `Cause.isDone`: ScheduleDecision was removed from the v4 public model. A Schedule.fromStep step recurs by returning [output, Duration] and terminates with Cause.done(output). + +### `effect/Scheduler` + +- `Scheduler.ControlledScheduler` -> `none`: No public step-controlled scheduler remains; implement Scheduler and SchedulerDispatcher for exact controlled stepping. + +- `Scheduler.MixedScheduler` -> `Scheduler.MixedScheduler`: The class remains with a redesigned constructor and makeDispatcher-based task API. + +- `Scheduler.PriorityBuckets` -> `none`: Priority buckets are now an internal Scheduler implementation detail. + +- `Scheduler.Scheduler` -> `Scheduler.Scheduler`: The interface remains but dispatch moved to SchedulerDispatcher returned by makeDispatcher. + +- `Scheduler.SchedulerRunner` -> `Scheduler.SchedulerDispatcher`: Task scheduling and flushing moved to the dispatcher returned by Scheduler.makeDispatcher. + +- `Scheduler.SyncScheduler` -> `new Scheduler.MixedScheduler("sync")`: Use a synchronous MixedScheduler and its dispatcher; call flush when directly driving queued tasks. + +- `Scheduler.Task` -> `() => void`: The named alias was removed; dispatcher APIs inline the task callback type. + +- `Scheduler.defaultScheduler` -> `Scheduler.Scheduler`: The default scheduler is now a Context.Reference; yield it to read or provide it to override the current scheduler. + +- `Scheduler.defaultShouldYield` -> `Scheduler.MixedScheduler#shouldYield`: The standalone function was removed; yielding is implemented by each Scheduler instance. + +- `Scheduler.make` -> `none`: Implement the redesigned Scheduler interface and return task dispatch through makeDispatcher. + +- `Scheduler.makeBatched` -> `new Scheduler.MixedScheduler("async", schedule)`: Pass a cancellable scheduling function; the dispatcher performs priority batching. + +- `Scheduler.makeMatrix` -> `none`: Matrix routing was removed; implement routing in a custom Scheduler and SchedulerDispatcher if still required. + +- `Scheduler.timer` -> `Effect.delay`: Use Effect delay or sleep for effect timing; implement a custom dispatcher for exact per-task scheduler timing. + +- `Scheduler.timerBatched` -> `new Scheduler.MixedScheduler("async", scheduleWithTimer)`: Use a setTimeout-based cancellable scheduling function; the dispatcher batches queued tasks. + +### `effect/Schema` + +- `Schema.Annotable` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Annotable.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Annotable.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Annotable.Self` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.AnnotableClass` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.AnnotableDeclare` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Annotations` -> `Schema.Annotations`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Annotations.Doc` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Annotations.Filter` -> `Schema.Annotations.Filter`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Annotations.GenericSchema` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Annotations.Schema` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Any` -> `Schema.Any`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Array$` -> `Schema.$Array`: Use the renamed v4 constructor result interface. + +- `Schema.ArrayEnsure` -> `Schema.ArrayEnsure`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.ArrayFormatterIssue` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.BetweenBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.BetweenBigIntSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.BetweenDateSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.BetweenDurationSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.BetweenSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.BigDecimal` -> `Schema.BigDecimalFromString`: Use the string-to-BigDecimal codec; v4 `BigDecimal` is the self schema. + +- `Schema.BigDecimalFromNumber` -> `none`: No built-in number-to-BigDecimal codec remains; compose `decodeTo` with a `SchemaGetter` conversion. + +- `Schema.BigDecimalFromSelf` -> `Schema.BigDecimal`: The self schema dropped the `FromSelf` suffix. + +- `Schema.BigInt` -> `Schema.BigIntFromString`: Use the string-to-bigint codec; v4 `BigInt` is the self schema. + +- `Schema.BigIntFromNumber` -> `none`: No built-in number-to-bigint codec remains; compose `decodeTo` with a checked `SchemaGetter` conversion. + +- `Schema.BigIntFromSelf` -> `Schema.BigInt`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Boolean` -> `Schema.Boolean`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.BooleanFromString` -> `none`: No built-in string-to-boolean codec remains; use `decodeTo` with an explicit `SchemaGetter` transformation. + +- `Schema.BooleanFromUnknown` -> `Schema.Boolean`: Use the boolean schema and perform any coercion explicitly before decoding. + +- `Schema.BrandSchema` -> `Schema.brand`: Use the schema returned by the v4 `brand` combinator and infer its concrete type. + +- `Schema.BrandSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Capitalize` -> `Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isCapitalized()), SchemaTransformation.capitalize()))`: Rebuild the capitalization transformation with `decodeTo`. + +- `Schema.Capitalized` -> `Schema.String.check(Schema.isCapitalized())`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.CapitalizedSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Cause` -> `Schema.toCodecJson(Schema.Cause(error, defect))`: Use the derived JSON codec to preserve v3's encoded Cause representation; v4 `Cause` itself is the self schema. + +- `Schema.CauseEncoded` -> `Schema.CauseIso`: Use the v4 Cause JSON/iso representation type. + +- `Schema.CauseFromSelf` -> `Schema.Cause`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Char` -> `Schema.Char`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Chunk` -> `Schema.toCodecJson(Schema.Chunk(value))`: Use the derived JSON codec to preserve v3's array-to-Chunk behavior; v4 `Chunk` itself is the self schema. + +- `Schema.ChunkFromSelf` -> `Schema.Chunk`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Class` -> `Schema.Class`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Config` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.Data` -> `none`: Remove this wrapper. v4 structural equality works on ordinary decoded objects. + +- `Schema.DataFromSelf` -> `none`: Remove this wrapper. v4 structural equality works on ordinary decoded objects. + +- `Schema.Date` -> `Schema.DateFromString`: Use `DateFromString`; v4 `Date` is the self schema. + +- `Schema.DateFromNumber` -> `Schema.DateFromMillis`: Rename the milliseconds-to-Date codec. + +- `Schema.DateFromSelf` -> `Schema.Date`: The self schema dropped the `FromSelf` suffix. + +- `Schema.DateFromSelfSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.DateFromString` -> `Schema.DateFromString`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.DateTimeUtc` -> `Schema.DateTimeUtcFromString`: Use the string codec; v4 `DateTimeUtc` is the self schema. + +- `Schema.DateTimeUtcFromDate` -> `Schema.DateTimeUtcFromDate`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.DateTimeUtcFromNumber` -> `Schema.DateTimeUtcFromMillis`: Rename the milliseconds-to-DateTime codec. + +- `Schema.DateTimeUtcFromSelf` -> `Schema.DateTimeUtc`: The self schema dropped the `FromSelf` suffix. + +- `Schema.DateTimeZoned` -> `Schema.DateTimeZonedFromString`: Use the string codec; v4 `DateTimeZoned` is the self schema. + +- `Schema.DateTimeZonedFromSelf` -> `Schema.DateTimeZoned`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Defect` -> `Schema.Defect`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Duration` -> `Schema.DurationFromString`: Use the string codec; v4 `Duration` is the self schema. + +- `Schema.DurationEncoded` -> `Schema.Duration["Iso"]`: Use the v4 Duration iso representation type. + +- `Schema.DurationFromMillis` -> `Schema.DurationFromMillis`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.DurationFromNanos` -> `Schema.DurationFromNanos`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.DurationFromSelf` -> `Schema.Duration`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Either` -> `Schema.Result`: `Either` was renamed to `Result`; pass success and failure schemas positionally. + +- `Schema.EitherEncoded` -> `Schema.ResultIso`: Use the v4 Result iso representation type. + +- `Schema.EitherFromSelf` -> `Schema.Result`: `Either` was renamed to `Result` in v4. + +- `Schema.EitherFromUnion` -> `Schema.Result`: `Either` was renamed to `Result`; use its tagged Result representation. + +- `Schema.Element` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Element.Token` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.EndsWithSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Enums` -> `Schema.Enum`: Rename the enum constructor and pass the enum object. + +- `Schema.EnumsDefinition` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.Exit` -> `Schema.toCodecJson(Schema.Exit(value, error, defect))`: Use the derived JSON codec to preserve v3's encoded Exit representation; v4 `Exit` itself is the self schema. + +- `Schema.ExitEncoded` -> `Schema.ExitIso`: Use the v4 Exit iso representation type. + +- `Schema.ExitFromSelf` -> `Schema.Exit`: The self schema dropped the `FromSelf` suffix. + +- `Schema.FiberId` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.FiberIdEncoded` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.FiberIdFromSelf` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.FilterIssue` -> `Schema.FilterIssue`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.FilterOutput` -> `Schema.FilterOutput`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Finite` -> `Schema.Finite`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.FiniteSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.FromPropertySignature` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.GreaterThanBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanBigIntSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanDateSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanDurationSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanOrEqualToBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanOrEqualToBigIntSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanOrEqualToDateSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanOrEqualToDurationSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanOrEqualToSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.GreaterThanSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.HashMap` -> `Schema.toCodecJson(Schema.HashMap(key, value))`: Pass key and value positionally and use the derived JSON codec to preserve v3's entry-array encoding. + +- `Schema.HashMapFromSelf` -> `Schema.HashMap`: The self schema dropped the `FromSelf` suffix; pass key and value positionally. + +- `Schema.HashSet` -> `Schema.toCodecJson(Schema.HashSet(value))`: Use the derived JSON codec to preserve v3's array-to-HashSet behavior. + +- `Schema.HashSetFromSelf` -> `Schema.HashSet`: The self schema dropped the `FromSelf` suffix. + +- `Schema.IncludesSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.IndexSignature` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.IndexSignature.Context` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.IndexSignature.Encoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.IndexSignature.NonEmptyRecords` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.IndexSignature.Record` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.IndexSignature.Type` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.InstanceOfSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Int` -> `Schema.Int`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.IntSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.ItemsCountSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.JsonNumber` -> `Schema.Finite`: Use the finite-number schema for JSON-compatible numbers. + +- `Schema.JsonNumberSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LeftEncoded` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.LengthSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanBigIntSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanDateSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanDurationSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanOrEqualToBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanOrEqualToBigIntSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanOrEqualToDateSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanOrEqualToDurationSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanOrEqualToSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.LessThanSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.List` -> `none`: The List schema was removed; migrate the model to `Schema.Array` or declare a custom List codec. + +- `Schema.ListFromSelf` -> `none`: The List self schema was removed; migrate to arrays or use `Schema.declare` for List values. + +- `Schema.Literal` -> `Schema.Literal / Schema.Literals`: Use `Literal(value)` for one non-null literal, `Null` for null, and `Literals([...])` for several literals. + +- `Schema.Lowercase` -> `Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isLowercased()), SchemaTransformation.toLowerCase()))`: Rebuild the lowercase transformation with `decodeTo`. + +- `Schema.Lowercased` -> `Schema.String.check(Schema.isLowercased())`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.LowercasedSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.MakeOptions` -> `Schema.MakeOptions`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Map` -> `Schema.toCodecJson(Schema.ReadonlyMap(key, value))`: Use `ReadonlyMap` with positional arguments and derive its JSON codec; mutable Map-specific schema types were removed. + +- `Schema.Map$` -> `Schema.$ReadonlyMap`: Use the renamed v4 constructor result interface. + +- `Schema.MapFromRecord` -> `none`: No direct record-to-Map codec remains; compose `Record` and `ReadonlyMap` with an explicit `decodeTo` transformation. + +- `Schema.MapFromSelf` -> `Schema.ReadonlyMap`: Use the readonly Map self schema with positional key and value arguments. + +- `Schema.MaxItemsSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.MaxLengthSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.MinItemsSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.MinLengthSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.MultipleOfSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Negative` -> `Schema.Number.check(Schema.isLessThan(0))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NegativeBigDecimalFromSelf` -> `Schema.BigDecimal.check(Schema.isLessThanBigDecimal(BigDecimal.fromNumber(0)))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NegativeBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.NegativeBigInt` -> `Schema.BigIntFromString.check(Schema.isLessThanBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NegativeBigIntFromSelf` -> `Schema.BigInt.check(Schema.isLessThanBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.Never` -> `Schema.Never`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.NonEmptyArray` -> `Schema.NonEmptyArray`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.NonEmptyArrayEnsure` -> `none`: No direct replacement remains; explicitly decode a single value or array to `Schema.NonEmptyArray`. + +- `Schema.NonEmptyChunk` -> `Schema.toCodecJson(Schema.Chunk(value).check(Schema.isMinLength(1)))`: Use a checked Chunk JSON codec. + +- `Schema.NonEmptyChunkFromSelf` -> `Schema.Chunk(value).check(Schema.isMinLength(1))`: Use the Chunk self schema with a minimum-length check. + +- `Schema.NonEmptyString` -> `Schema.NonEmptyString`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.NonEmptyTrimmedString` -> `Schema.Trimmed.check(Schema.isNonEmpty())`: Compose the trimmed schema with the non-empty check. + +- `Schema.NonNaN` -> `Schema.Number.check(Schema.makeFilter((n) => !Number.isNaN(n)))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonNaNSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.NonNegative` -> `Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonNegativeBigDecimalFromSelf` -> `Schema.BigDecimal.check(Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.fromNumber(0)))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonNegativeBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.NonNegativeBigInt` -> `Schema.BigIntFromString.check(Schema.isGreaterThanOrEqualToBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonNegativeBigIntFromSelf` -> `Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonNegativeInt` -> `Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonPositive` -> `Schema.Number.check(Schema.isLessThanOrEqualTo(0))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonPositiveBigDecimalFromSelf` -> `Schema.BigDecimal.check(Schema.isLessThanOrEqualToBigDecimal(BigDecimal.fromNumber(0)))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonPositiveBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.NonPositiveBigInt` -> `Schema.BigIntFromString.check(Schema.isLessThanOrEqualToBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.NonPositiveBigIntFromSelf` -> `Schema.BigInt.check(Schema.isLessThanOrEqualToBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.Not` -> `none`: The exclusion constructor was removed; express the accepted alternatives directly or add a `Schema.check`. + +- `Schema.Null` -> `Schema.Null`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.NullOr` -> `Schema.NullOr`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.NullishOr` -> `Schema.NullishOr`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Number` -> `Schema.Number`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.NumberFromString` -> `Schema.NumberFromString`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Object` -> `Schema.ObjectKeyword`: Rename the object keyword schema. + +- `Schema.Option` -> `Schema.toCodecJson(Schema.Option(value))`: Use the derived JSON codec to preserve v3's tagged Option encoding; v4 `Option` itself is the self schema. + +- `Schema.OptionEncoded` -> `Schema.OptionIso`: Use the v4 Option iso representation type. + +- `Schema.OptionFromNonEmptyTrimmedString` -> `Schema.Trimmed.check(Schema.isNonEmpty()).pipe(Schema.decodeTo(Schema.Option(Schema.String), ...))`: Rebuild the empty-string-to-None conversion explicitly with `decodeTo`. + +- `Schema.OptionFromNullOr` -> `Schema.OptionFromNullOr`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.OptionFromNullishOr` -> `Schema.OptionFromNullishOr`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.OptionFromSelf` -> `Schema.Option`: The self schema dropped the `FromSelf` suffix. + +- `Schema.OptionFromUndefinedOr` -> `Schema.OptionFromUndefinedOr`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.OptionalOptions` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.ParseJsonOptions` -> `none`: The old parse-json options type was removed; configure `fromJsonString` and its underlying getter directly. + +- `Schema.PatternSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Positive` -> `Schema.Number.check(Schema.isGreaterThan(0))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.PositiveBigDecimalFromSelf` -> `Schema.BigDecimal.check(Schema.isGreaterThanBigDecimal(BigDecimal.fromNumber(0)))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.PositiveBigDecimalSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.PositiveBigInt` -> `Schema.BigIntFromString.check(Schema.isGreaterThanBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.PositiveBigIntFromSelf` -> `Schema.BigInt.check(Schema.isGreaterThanBigInt(0n))`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.PropertyKey` -> `Schema.PropertyKey`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.PropertySignature` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignature.AST` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignature.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignature.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignature.Token` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignatureDeclaration` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignatureTransformation` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.PropertySignatureTypeId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.ReadonlyMap` -> `Schema.toCodecJson(Schema.ReadonlyMap(key, value))`: Pass key and value positionally and use the derived JSON codec to preserve v3's entry-array encoding. + +- `Schema.ReadonlyMap$` -> `Schema.$ReadonlyMap`: Use the renamed v4 constructor result interface. + +- `Schema.ReadonlyMapFromRecord` -> `none`: No direct record-to-ReadonlyMap codec remains; compose `Record` and `ReadonlyMap` with an explicit `decodeTo` transformation. + +- `Schema.ReadonlyMapFromSelf` -> `Schema.ReadonlyMap`: The self schema dropped the `FromSelf` suffix; pass key and value positionally. + +- `Schema.ReadonlySet` -> `Schema.toCodecJson(Schema.ReadonlySet(value))`: Use the derived JSON codec to preserve v3's array-to-ReadonlySet behavior. + +- `Schema.ReadonlySet$` -> `Schema.$ReadonlySet`: Use the renamed v4 constructor result interface. + +- `Schema.ReadonlySetFromSelf` -> `Schema.ReadonlySet`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Record` -> `Schema.Record(key, value)`: Pass key and value as separate arguments. + +- `Schema.Record$` -> `Schema.$Record`: Use the renamed v4 constructor result interface. + +- `Schema.Redacted` -> `Schema.RedactedFromValue`: Use `RedactedFromValue` to wrap decoded raw values; v4 `Redacted` is the self schema. + +- `Schema.RedactedFromSelf` -> `Schema.Redacted`: The self schema dropped the `FromSelf` suffix. + +- `Schema.RefineSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.RightEncoded` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.Schema` -> `Schema.Schema`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Schema.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.AnyNoContext` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.AsSchema` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.Context` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.Encoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.ToAsserts` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Schema.Variance` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.SchemaClass` -> `Schema.Codec`: The concrete SchemaClass abstraction was removed; accept the appropriate v4 `Codec` or constraint type. + +- `Schema.Serializable` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Serializable.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Serializable.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Serializable.Context` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Serializable.Encoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Serializable.Type` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.SerializableWithResult` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.SerializableWithResult.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.SerializableWithResult.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.SerializableWithResult.Context` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Set` -> `Schema.toCodecJson(Schema.ReadonlySet(value))`: Use the readonly Set schema and derive its JSON codec; mutable Set-specific schema types were removed. + +- `Schema.Set$` -> `Schema.$ReadonlySet`: Use the renamed v4 constructor result interface. + +- `Schema.SetFromSelf` -> `Schema.ReadonlySet`: Use the readonly Set self schema. + +- `Schema.SimplifyMutable` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.SortedSet` -> `none`: The SortedSet schema was removed; migrate to `ReadonlySet` or declare a custom codec that applies the required ordering. + +- `Schema.SortedSetFromSelf` -> `none`: The SortedSet self schema was removed; use `Schema.declare` if SortedSet values must remain in the model. + +- `Schema.StartsWithSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.String` -> `Schema.String`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Struct` -> `Schema.Struct`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Struct.Constructor` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Struct.Context` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Struct.Encoded` -> `Schema.Struct.Encoded`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Struct.EncodedOptionalKeys` -> `Schema.Struct.EncodedOptionalKeys`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Struct.Field` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Struct.Key` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Struct.OptionalEncodedPropertySignature` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Struct.OptionalTypePropertySignature` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Struct.PropertySignatureWithDefault` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.Symbol` -> `none`: v4 `Symbol` is the self schema and has no built-in string-to-symbol codec; rebuild the conversion explicitly with `decodeTo`. + +- `Schema.SymbolFromSelf` -> `Schema.Symbol`: The self schema dropped the `FromSelf` suffix. + +- `Schema.TaggedClass` -> `Schema.TaggedClass`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.TaggedErrorClass` -> `Schema.TaggedError`: The exported helper interface was removed; use the class returned by Schema.TaggedError and infer its types. + +- `Schema.TaggedRequest` -> `effect/unstable/rpc/Rpc.make`: The Schema request/serialization protocol was removed; migrate RPC requests to the v4 Rpc APIs. + +- `Schema.TaggedRequest.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TaggedRequest.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TaggedRequestClass` -> `effect/unstable/rpc/Rpc.make`: The Schema request/serialization protocol was removed; migrate RPC requests to the v4 Rpc APIs. + +- `Schema.TaggedStruct` -> `Schema.TaggedStruct`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.TemplateLiteral` -> `Schema.TemplateLiteral(parts)`: Pass template literal parts as one array. Parts must not contain encodings, including inside unions and nested templates. Transformations whose decoded and encoded types are equal are also rejected. Use Schema.TemplateLiteralParser(parts) for transformed parts. + +- `Schema.TemplateLiteralParser` -> `Schema.TemplateLiteralParser(parts)`: Pass template literal parts directly as one array. Transformed parts are supported, and their decoding and encoding services are required in the corresponding direction. + +- `Schema.TimeZone` -> `Schema.TimeZoneFromString`: Use the string codec; v4 `TimeZone` is the self schema. + +- `Schema.TimeZoneFromSelf` -> `Schema.TimeZone`: The self schema dropped the `FromSelf` suffix. + +- `Schema.TimeZoneNamed` -> `Schema.TimeZoneNamedFromString`: Use the string codec; v4 `TimeZoneNamed` is the self schema. + +- `Schema.TimeZoneNamedFromSelf` -> `Schema.TimeZoneNamed`: The self schema dropped the `FromSelf` suffix. + +- `Schema.TimeZoneOffset` -> `Schema.toCodecJson(Schema.TimeZoneOffset)`: Use the derived JSON codec to preserve v3's encoded offset representation; v4 `TimeZoneOffset` is the self schema. + +- `Schema.TimeZoneOffsetFromSelf` -> `Schema.TimeZoneOffset`: The self schema dropped the `FromSelf` suffix. + +- `Schema.ToPropertySignature` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.Trim` -> `Schema.Trim`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Trimmed` -> `Schema.Trimmed`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.TrimmedSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Tuple` -> `Schema.Tuple(elements)`: Pass tuple elements as one array. + +- `Schema.Tuple2` -> `Schema.Tuple`: Use the array-based tuple constructor. + +- `Schema.TupleType.ElementsEncoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TupleType.ElementsType` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TupleType.Encoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TupleType.Type` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TypeId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.TypeLiteral` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TypeLiteral.Constructor` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TypeLiteral.Encoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.TypeLiteral.Type` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.ULID` -> `Schema.String.check(Schema.isULID())`: Build the string schema with the ULID check. + +- `Schema.ULIDSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.URL` -> `Schema.URLFromString`: Use the string-to-URL codec; v4 `URL` is the self schema. + +- `Schema.URLFromSelf` -> `Schema.URL`: The self schema dropped the `FromSelf` suffix. + +- `Schema.UUID` -> `Schema.String.check(Schema.isUUID())`: Build the string schema with the UUID check. + +- `Schema.UUIDSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Uint8` -> `Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 0, maximum: 255 }))`: Rebuild the unsigned-byte schema from integer and range checks. + +- `Schema.Uint8Array` -> `Schema.toCodecJson(Schema.Uint8Array)`: Use the derived JSON codec to preserve v3's number-array encoding; v4 `Uint8Array` is the self schema. + +- `Schema.Uint8ArrayFromSelf` -> `Schema.Uint8Array`: The self schema dropped the `FromSelf` suffix. + +- `Schema.Uncapitalize` -> `Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isUncapitalized()), SchemaTransformation.uncapitalize()))`: Rebuild the uncapitalization transformation with `decodeTo`. + +- `Schema.Uncapitalized` -> `Schema.String.check(Schema.isUncapitalized())`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.UncapitalizedSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Undefined` -> `Schema.Undefined`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.UndefinedOr` -> `Schema.UndefinedOr`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Union` -> `Schema.Union(members)`: Pass union members as one array. + +- `Schema.UniqueSymbolFromSelf` -> `Schema.UniqueSymbol`: Use the v4 unique-symbol schema constructor. + +- `Schema.Unknown` -> `Schema.Unknown`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.Uppercase` -> `Schema.String.pipe(Schema.decodeTo(Schema.String.check(Schema.isUppercased()), SchemaTransformation.toUpperCase()))`: Rebuild the uppercase transformation with `decodeTo`. + +- `Schema.Uppercased` -> `Schema.String.check(Schema.isUppercased())`: Rebuild the removed convenience schema from the v4 base schema and check APIs. + +- `Schema.UppercasedSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.ValidDateFromSelf` -> `Schema.Date`: Use the v4 Date self schema, which rejects invalid Date values. + +- `Schema.ValidDateSchemaId` -> `none`: The v3 schema-id symbol was removed. Use the corresponding public v4 constructor/check instead of inspecting schema ids. + +- `Schema.Void` -> `Schema.Void`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.WithResult` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.All` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.Any` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.Context` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.Failure` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.FailureEncoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.Success` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.WithResult.SuccessEncoded` -> `none`: The v3 helper/protocol type was removed by the v4 Schema model rewrite. Use the public v4 constructor and infer its result types instead. + +- `Schema.annotations` -> `Schema.annotate`: Rename `annotations` to `annotate`. + +- `Schema.asSchema` -> `Schema.revealCodec`: Use `revealCodec` to expose a schema's codec type. + +- `Schema.asSerializable` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.asSerializableWithResult` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.asWithResult` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.attachPropertySignature` -> `Schema.tagDefaultOmit`: Map the struct fields and add `key: Schema.tagDefaultOmit(value)`; the old combinator was removed. + +- `Schema.between` -> `Schema.isBetween`: Rename the predicate to `isBetween` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.betweenBigDecimal` -> `Schema.isBetweenBigDecimal`: Rename the predicate to `isBetweenBigDecimal` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.betweenBigInt` -> `Schema.isBetweenBigInt`: Rename the predicate to `isBetweenBigInt` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.betweenDate` -> `Schema.isBetweenDate`: Rename the predicate to `isBetweenDate` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.betweenDuration` -> `Schema.isBetween`: Rename the predicate to `isBetween` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.brand` -> `Schema.brand`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.capitalized` -> `Schema.isCapitalized`: Rename the string predicate to `isCapitalized` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.clamp` -> `Schema.decodeTo + SchemaGetter.transform(Number.clamp(...))`: Rebuild clamping as an explicit reversible transformation. + +- `Schema.clampBigDecimal` -> `Schema.decodeTo + SchemaGetter.transform(BigDecimal.clamp(...))`: Rebuild BigDecimal clamping as an explicit reversible transformation. + +- `Schema.clampBigInt` -> `Schema.decodeTo + SchemaGetter.transform(BigInt.clamp(...))`: Rebuild bigint clamping as an explicit reversible transformation. + +- `Schema.clampDuration` -> `Schema.decodeTo + SchemaGetter.transform(Duration.clamp(...))`: Rebuild Duration clamping as an explicit reversible transformation. + +- `Schema.declare` -> `Schema.declare`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.decode` -> `Schema.decodeEffect`: Rename the effectful decoder. + +- `Schema.decodeEither` -> `Schema.decodeExit`: Rename the decoder returning an `Exit`. + +- `Schema.decodeUnknown` -> `Schema.decodeUnknownEffect`: Rename the effectful unknown-input decoder. + +- `Schema.decodeUnknownEither` -> `Schema.decodeUnknownExit`: Rename the unknown-input decoder returning an `Exit`. + +- `Schema.decodeUnknownPromise` -> `Schema.decodeUnknownPromise`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.deserialize` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.deserializeExit` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.deserializeFailure` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.deserializeSuccess` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.element` -> `none`: The tuple element wrapper was removed; express elements directly in `Tuple([...])` or use `TupleWithRest` for rest elements. + +- `Schema.encode` -> `Schema.encodeEffect`: Rename the effectful encoder. + +- `Schema.encodeEither` -> `Schema.encodeExit`: Rename the encoder returning an `Exit`. + +- `Schema.encodeUnknown` -> `Schema.encodeUnknownEffect`: Rename the effectful unknown-input encoder. + +- `Schema.encodeUnknownEither` -> `Schema.encodeUnknownExit`: Rename the unknown-input encoder returning an `Exit`. + +- `Schema.encodeUnknownPromise` -> `Schema.encodeUnknownPromise`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.encodedBoundSchema` -> `Schema.toEncoded`: Use the encoded side of the codec; service bounds are modeled by v4 codec service types. + +- `Schema.encodedSchema` -> `Schema.toEncoded`: Rename the encoded-side projection. + +- `Schema.endsWith` -> `Schema.isEndsWith`: Rename the string predicate to `isEndsWith` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.equivalence` -> `Schema.toEquivalence`: Rename the equivalence derivation utility. + +- `Schema.exitSchema` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.extend` -> `schema.mapFields(Struct.assign(fields))`: Replace struct extension with `mapFields(Struct.assign(...))` or `Schema.fieldsAssign`; map union members explicitly. + +- `Schema.failureSchema` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.filter` -> `Schema.check(Schema.makeFilter(predicate)) / Schema.refine(refinement)`: Use `check(makeFilter(...))` for predicates and `refine` for type refinements. + +- `Schema.filterEffect` -> `Schema.decode({ decode: SchemaGetter.checkEffect(...), encode: SchemaGetter.passthrough() })`: Rebuild effectful validation as a decode step with `SchemaGetter.checkEffect`. + +- `Schema.finite` -> `Schema.isFinite`: Rename the predicate to `isFinite` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.format` -> `SchemaRepresentation.toCodeDocument`: Build a representation with `SchemaRepresentation.toRepresentation`, `toMultiDocument`, then `toCodeDocument`. + +- `Schema.fromBrand` -> `Schema.fromBrand`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.fromKey` -> `Schema.encodeKeys`: Use `encodeKeys` to map decoded property names to encoded keys. + +- `Schema.getNumberIndexedAccess` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.greaterThan` -> `Schema.isGreaterThan`: Rename the predicate to `isGreaterThan` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanBigDecimal` -> `Schema.isGreaterThanBigDecimal`: Rename the predicate to `isGreaterThanBigDecimal` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanBigInt` -> `Schema.isGreaterThanBigInt`: Rename the predicate to `isGreaterThanBigInt` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanDate` -> `Schema.isGreaterThanDate`: Rename the predicate to `isGreaterThanDate` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanDuration` -> `Schema.isGreaterThan`: Rename the predicate to `isGreaterThan` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanOrEqualTo` -> `Schema.isGreaterThanOrEqualTo`: Rename the predicate to `isGreaterThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanOrEqualToBigDecimal` -> `Schema.isGreaterThanOrEqualToBigDecimal`: Rename the predicate to `isGreaterThanOrEqualToBigDecimal` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanOrEqualToBigInt` -> `Schema.isGreaterThanOrEqualToBigInt`: Rename the predicate to `isGreaterThanOrEqualToBigInt` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanOrEqualToDate` -> `Schema.isGreaterThanOrEqualToDate`: Rename the predicate to `isGreaterThanOrEqualToDate` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.greaterThanOrEqualToDuration` -> `Schema.isGreaterThanOrEqualTo`: Rename the predicate to `isGreaterThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.head` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.headNonEmpty` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.headOrElse` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.includes` -> `Schema.isIncludes`: Rename the string predicate to `isIncludes` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.instanceOf` -> `Schema.instanceOf`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.int` -> `Schema.isInt`: Rename the predicate to `isInt` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.isPropertySignature` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.isSchema` -> `Schema.isSchema`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.itemsCount` -> `Schema.isLengthBetween`: Use the v4 collection-size check and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.keyof` -> `none`: Removed with the schema model rewrite; derive keys from struct fields or use `Schema.Literals` explicitly. + +- `Schema.length` -> `Schema.isLengthBetween`: Use `isLengthBetween` with equal minimum and maximum values for an exact string length. + +- `Schema.lessThan` -> `Schema.isLessThan`: Rename the predicate to `isLessThan` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanBigDecimal` -> `Schema.isLessThanBigDecimal`: Rename the predicate to `isLessThanBigDecimal` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanBigInt` -> `Schema.isLessThanBigInt`: Rename the predicate to `isLessThanBigInt` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanDate` -> `Schema.isLessThanDate`: Rename the predicate to `isLessThanDate` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanDuration` -> `Schema.isLessThan`: Rename the predicate to `isLessThan` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanOrEqualTo` -> `Schema.isLessThanOrEqualTo`: Rename the predicate to `isLessThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanOrEqualToBigDecimal` -> `Schema.isLessThanOrEqualToBigDecimal`: Rename the predicate to `isLessThanOrEqualToBigDecimal` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanOrEqualToBigInt` -> `Schema.isLessThanOrEqualToBigInt`: Rename the predicate to `isLessThanOrEqualToBigInt` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanOrEqualToDate` -> `Schema.isLessThanOrEqualToDate`: Rename the predicate to `isLessThanOrEqualToDate` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lessThanOrEqualToDuration` -> `Schema.isLessThanOrEqualTo`: Rename the predicate to `isLessThanOrEqualTo` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.lowercased` -> `Schema.isLowercased`: Rename the string predicate to `isLowercased` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.make` -> `Schema.make`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.makePropertySignature` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.maxItems` -> `Schema.isMaxLength`: Use the v4 collection-size check and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.maxLength` -> `Schema.isMaxLength`: Rename the string predicate to `isMaxLength` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.minItems` -> `Schema.isMinLength`: Use the v4 collection-size check and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.minLength` -> `Schema.isMinLength`: Rename the string predicate to `isMinLength` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.multipleOf` -> `Schema.isMultipleOf`: Rename the predicate to `isMultipleOf` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.mutable` -> `Schema.mutable`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.negative` -> `Schema.isLessThan(0)`: Use `isLessThan(0)` as a v4 check. + +- `Schema.negativeBigDecimal` -> `Schema.isLessThanBigDecimal(BigDecimal.fromNumber(0))`: Use `isLessThanBigDecimal` as a v4 check. + +- `Schema.negativeBigInt` -> `Schema.isLessThanBigInt(0n)`: Use `isLessThanBigInt(0n)` as a v4 check. + +- `Schema.nonEmptyString` -> `Schema.isNonEmpty`: Rename the string predicate to `isNonEmpty` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.nonNaN` -> `Schema.makeFilter((n) => !Number.isNaN(n))`: Use an explicit filter because v4 has no dedicated non-NaN check. + +- `Schema.nonNegative` -> `Schema.isGreaterThanOrEqualTo(0)`: Use `isGreaterThanOrEqualTo(0)` as a v4 check. + +- `Schema.nonNegativeBigDecimal` -> `Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.fromNumber(0))`: Use `isGreaterThanOrEqualToBigDecimal` as a v4 check. + +- `Schema.nonNegativeBigInt` -> `Schema.isGreaterThanOrEqualToBigInt(0n)`: Use `isGreaterThanOrEqualToBigInt(0n)` as a v4 check. + +- `Schema.nonPositive` -> `Schema.isLessThanOrEqualTo(0)`: Use `isLessThanOrEqualTo(0)` as a v4 check. + +- `Schema.nonPositiveBigDecimal` -> `Schema.isLessThanOrEqualToBigDecimal(BigDecimal.fromNumber(0))`: Use `isLessThanOrEqualToBigDecimal` as a v4 check. + +- `Schema.nonPositiveBigInt` -> `Schema.isLessThanOrEqualToBigInt(0n)`: Use `isLessThanOrEqualToBigInt(0n)` as a v4 check. + +- `Schema.omit` -> `schema.mapFields(Struct.omit([keys]))`: Use `mapFields` with `Struct.omit`; pass keys as an array. + +- `Schema.optional` -> `Schema.optional`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.optionalElement` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.optionalToOptional` -> `Schema.decodeTo + SchemaGetter.transformOptional`: Rebuild optional-field transformations with `decodeTo` and `SchemaGetter.transformOptional`. + +- `Schema.optionalToRequired` -> `Schema.decodeTo + SchemaGetter.transformOptional`: Rebuild optional-field transformations with `decodeTo` and `SchemaGetter.transformOptional`. + +- `Schema.optionalWith` -> `Schema.optional / Schema.optionalKey / Schema.withDecodingDefaultType`: Choose `optional` or `optionalKey`; use the decoding-default helpers and an explicit nullable transformation as required by the old options. + +- `Schema.parseJson` -> `Schema.UnknownFromJsonString / Schema.fromJsonString(schema)`: Use `UnknownFromJsonString` without an inner schema or `fromJsonString(schema)` with one. + +- `Schema.parseNumber` -> `Schema.NumberFromString`: Use the built-in string-to-number codec. + +- `Schema.partial` -> `schema.mapFields(Struct.map(Schema.optional))`: Map struct fields with `Schema.optional`. + +- `Schema.partialWith` -> `schema.mapFields(Struct.map(Schema.optionalKey))`: For `{ exact: true }`, map struct fields with `Schema.optionalKey`; choose field helpers explicitly for other options. + +- `Schema.pattern` -> `Schema.isPattern`: Rename the string predicate to `isPattern` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.pick` -> `schema.mapFields(Struct.pick([keys]))`: Use `mapFields` with `Struct.pick`; pass keys as an array. + +- `Schema.pickLiteral` -> `Schema.Literals(values).pick(selected)`: Build a `Literals` schema from an array and call its `pick` method. + +- `Schema.pluck` -> `none`: No direct replacement remains; pick the field then use `decodeTo` with `SchemaGetter.transform` to map between the field and enclosing object. + +- `Schema.positive` -> `Schema.isGreaterThan(0)`: Use `isGreaterThan(0)` as a v4 check. + +- `Schema.positiveBigDecimal` -> `Schema.isGreaterThanBigDecimal(BigDecimal.fromNumber(0))`: Use `isGreaterThanBigDecimal` as a v4 check. + +- `Schema.positiveBigInt` -> `Schema.isGreaterThanBigInt(0n)`: Use `isGreaterThanBigInt(0n)` as a v4 check. + +- `Schema.propertySignature` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.refine` -> `Schema.refine`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.rename` -> `Schema.encodeKeys`: Use `encodeKeys` for encoded-key renaming. + +- `Schema.requiredToOptional` -> `Schema.decodeTo + SchemaGetter.transformOptional`: Rebuild optional-field transformations with `decodeTo` and `SchemaGetter.transformOptional`. + +- `Schema.serializableSchema` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.serialize` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.serializeExit` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.serializeFailure` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.serializeSuccess` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.split` -> `Schema.String.pipe(Schema.decodeTo(Schema.Array(Schema.String), SchemaTransformation.transform(...)))`: Rebuild splitting as an explicit reversible string/array transformation. + +- `Schema.standardSchemaV1` -> `Schema.toStandardSchemaV1`: Rename the Standard Schema adapter. + +- `Schema.startsWith` -> `Schema.isStartsWith`: Rename the string predicate to `isStartsWith` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.successSchema` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.suspend` -> `Schema.suspend`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.symbolSerializable` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.symbolWithResult` -> `none`: Compared the v3 declaration with v4 Schema and the schema migration guide; no direct public replacement remains. Rebuild the behavior from public v4 codecs/getters where still required. + +- `Schema.tag` -> `Schema.tag`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.transform` -> `schema.pipe(Schema.decodeTo(target, SchemaTransformation.transform({ decode, encode })))`: Replace the constructor with `decodeTo` and a `SchemaTransformation`. + +- `Schema.transformLiteral` -> `Schema.Literal(from).transform(to)`: Use the literal schema's `transform` method. + +- `Schema.transformLiterals` -> `Schema.Literals(fromValues).transform(toValues)`: Split the pairs into parallel arrays and use `Literals(...).transform(...)`. + +- `Schema.transformOrFail` -> `schema.pipe(Schema.decodeTo(target, { decode: SchemaGetter.transformOrFail(...), encode: ... }))`: Replace the constructor with `decodeTo` and fallible `SchemaGetter` transformations. + +- `Schema.trimmed` -> `Schema.isTrimmed`: Rename the string predicate to `isTrimmed` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.typeSchema` -> `Schema.toType`: Rename the type-side projection. + +- `Schema.uncapitalized` -> `Schema.isUncapitalized`: Rename the string predicate to `isUncapitalized` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.uppercased` -> `Schema.isUppercased`: Rename the string predicate to `isUppercased` and apply it with `Schema.check` or a schema's `check` method. + +- `Schema.validDate` -> `Schema.Date`: Use the v4 Date self schema, which rejects invalid Date values. + +- `Schema.validate` -> `Schema.decodeEffect(Schema.toType(schema))`: Validation was removed; decode through the schema's type side. + +- `Schema.validateEither` -> `Schema.decodeExit(Schema.toType(schema))`: Validation was removed; decode through the schema's type side. + +- `Schema.validatePromise` -> `Schema.decodePromise(Schema.toType(schema))`: Validation was removed; decode through the schema's type side. + +- `Schema.withConstructorDefault` -> `Schema.withConstructorDefault`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.withDecodingDefault` -> `Schema.withDecodingDefault`: The API remains public in v4, but its type/value declaration was consolidated; use the v4 declaration and update inferred types/signature as needed. + +- `Schema.withDefaults` -> `none`: Removed; choose `withConstructorDefault` and decoding-default helpers explicitly for each side. + +### `effect/SchemaAST` + +- `SchemaAST.AST` -> `SchemaAST.AST`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.Annotated` -> `SchemaAST.AST`: The public base type was removed. Use the AST union; every variant still exposes annotations, checks, encoding, and context. + +- `SchemaAST.AnyKeyword` -> `SchemaAST.Any`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.ArbitraryAnnotationId` -> `Schema.Annotations.ToArbitrary`: Symbol annotation IDs were removed. Declarations use the toCodecArbitrary annotation; filters use arbitraryConstraint. + +- `SchemaAST.BatchingAnnotation` -> `none`: Per-schema batching annotations were removed; control asynchronous parsing with ParseOptions.concurrency. + +- `SchemaAST.BatchingAnnotationId` -> `none`: Symbol annotation IDs were removed and batching is no longer a schema annotation. + +- `SchemaAST.BigIntKeyword` -> `SchemaAST.BigInt`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.BooleanKeyword` -> `SchemaAST.Boolean`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.BrandAnnotation` -> `Schema.Annotations.Bottom["brands"]`: Brands are stored under the string-keyed brands annotation and normally added with Schema.brand. + +- `SchemaAST.BrandAnnotationId` -> `Schema.brand`: Symbol annotation IDs were removed; add brands through Schema.brand. + +- `SchemaAST.Compiler` -> `none`: The generic AST compiler abstraction was removed; traverse the discriminated SchemaAST.AST union directly or use a higher-level Schema derivation API. + +- `SchemaAST.ComposeTransformation` -> `SchemaAST.Encoding`: The marker transformation was replaced by explicit SchemaAST.Link encoding chains. + +- `SchemaAST.ConcurrencyAnnotation` -> `SchemaAST.ParseOptions["concurrency"]`: Concurrency is now a parse option rather than its own annotation type. + +- `SchemaAST.ConcurrencyAnnotationId` -> `Schema.Annotations.Bottom["parseOptions"]`: Symbol annotation IDs were removed; put concurrency inside the parseOptions annotation. + +- `SchemaAST.Declaration` -> `SchemaAST.Declaration`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.DecodingFallbackAnnotation` -> `Schema.catchDecoding`: Fallbacks are now encoding middleware added with Schema.catchDecoding. + +- `SchemaAST.DecodingFallbackAnnotationId` -> `Schema.catchDecoding`: The symbol annotation was removed; attach decoding recovery with Schema.catchDecoding. + +- `SchemaAST.DefaultAnnotation` -> `Schema.Annotations.Documentation["default"]`: Defaults are string-keyed schema annotations in v4. + +- `SchemaAST.DefaultAnnotationId` -> `Schema.Annotations.Documentation["default"]`: Symbol annotation IDs were removed; use the default key. + +- `SchemaAST.DescriptionAnnotation` -> `Schema.Annotations.Augment["description"]`: Descriptions are string-keyed schema annotations in v4. + +- `SchemaAST.DescriptionAnnotationId` -> `Schema.Annotations.Augment["description"]`: Symbol annotation IDs were removed; use the description key. + +- `SchemaAST.DocumentationAnnotation` -> `Schema.Annotations.Augment["documentation"]`: Documentation is a string-keyed schema annotation in v4. + +- `SchemaAST.DocumentationAnnotationId` -> `Schema.Annotations.Augment["documentation"]`: Symbol annotation IDs were removed; use the documentation key. + +- `SchemaAST.Enums` -> `SchemaAST.Enum`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.EquivalenceAnnotation` -> `Schema.Annotations.ToEquivalence.Declaration`: Equivalence derivation annotations now use the toEquivalence key in Schema.Annotations. + +- `SchemaAST.EquivalenceAnnotationId` -> `Schema.overrideToEquivalence`: The symbol annotation was removed; attach custom equivalence derivation with Schema.overrideToEquivalence. + +- `SchemaAST.ExamplesAnnotation` -> `Schema.Annotations.Documentation["examples"]`: Examples are string-keyed schema annotations in v4. + +- `SchemaAST.ExamplesAnnotationId` -> `Schema.Annotations.Documentation["examples"]`: Symbol annotation IDs were removed; use the examples key. + +- `SchemaAST.FinalTransformation` -> `SchemaTransformation.Transformation`: Transformations moved to SchemaTransformation and are stored in SchemaAST.Link values. + +- `SchemaAST.IdentifierAnnotation` -> `Schema.Annotations.Bottom["identifier"]`: Identifiers are string-keyed schema annotations in v4. + +- `SchemaAST.IdentifierAnnotationId` -> `Schema.Annotations.Bottom["identifier"]`: Symbol annotation IDs were removed; use the identifier key. + +- `SchemaAST.IndexSignature` -> `SchemaAST.IndexSignature`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.JSONIdentifierAnnotationId` -> `Schema.Annotations.Bottom["identifier"]`: The separate JSON identifier symbol was removed; use identifier. + +- `SchemaAST.JSONSchemaAnnotation` -> `JsonSchema.JsonSchema`: JSON Schema values use the v4 JsonSchema model; generation hooks use Schema representation annotations. + +- `SchemaAST.JSONSchemaAnnotationId` -> `Schema.Annotations.Filter["toJsonSchema"]`: The symbol annotation was replaced by the toJsonSchema key on check annotations. + +- `SchemaAST.Literal` -> `SchemaAST.Literal`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.LiteralValue` -> `SchemaAST.LiteralValue`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.Match` -> `none`: The Match compiler table was removed; traverse the discriminated SchemaAST.AST union directly. + +- `SchemaAST.Members` -> `ReadonlyArray`: Union members are ordinary readonly arrays in v4. + +- `SchemaAST.MessageAnnotation` -> `Schema.Annotations.Bottom["message"]`: Messages are string-keyed annotations and no longer receive the old ParseIssue callback shape. + +- `SchemaAST.MessageAnnotationId` -> `Schema.Annotations.Bottom["message"]`: Symbol annotation IDs were removed; use the message key. + +- `SchemaAST.MissingMessageAnnotation` -> `Schema.Annotations.Key["messageMissingKey"]`: Missing-key messages use the messageMissingKey key. + +- `SchemaAST.MissingMessageAnnotationId` -> `Schema.Annotations.Key["messageMissingKey"]`: Symbol annotation IDs were removed; use messageMissingKey. + +- `SchemaAST.NeverKeyword` -> `SchemaAST.Never`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.NumberKeyword` -> `SchemaAST.Number`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.ObjectKeyword` -> `SchemaAST.ObjectKeyword`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.OptionalType` -> `SchemaAST.Context`: Element and property optionality moved into per-node Context. + +- `SchemaAST.Parameter` -> `SchemaAST.AST`: The dedicated index-parameter union was removed; v4 validates supported key AST variants when building an IndexSignature. + +- `SchemaAST.ParseIssueTitleAnnotation` -> `none`: Issue-title callbacks were removed; use message or expected annotations and SchemaIssue formatters. + +- `SchemaAST.ParseIssueTitleAnnotationId` -> `none`: The symbol annotation was removed; use message or expected annotations. + +- `SchemaAST.ParseJsonSchemaId` -> `Schema.UnknownFromJsonString`: Use the built-in JSON string codec instead of checking the old schema ID. + +- `SchemaAST.ParseOptions` -> `SchemaAST.ParseOptions`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.ParseOptionsAnnotationId` -> `Schema.Annotations.Bottom["parseOptions"]`: Symbol annotation IDs were removed; use the parseOptions key. + +- `SchemaAST.PrettyAnnotationId` -> `Schema.overrideToFormatter`: The symbol annotation was removed; attach custom formatters with Schema.overrideToFormatter. + +- `SchemaAST.PropertySignature` -> `SchemaAST.PropertySignature`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.PropertySignatureTransformation` -> `Schema.encodeKeys`: Property-key transformations are now encoding links, normally built with Schema.encodeKeys. + +- `SchemaAST.Refinement` -> `SchemaAST.Check`: Refinements became Filter or FilterGroup checks attached to an AST node. + +- `SchemaAST.SchemaIdAnnotation` -> `Schema.Annotations.Bottom["identifier"]`: Schema IDs were consolidated into identifier annotations. + +- `SchemaAST.SchemaIdAnnotationId` -> `Schema.Annotations.Bottom["identifier"]`: Symbol annotation IDs were removed; use identifier. + +- `SchemaAST.StringKeyword` -> `SchemaAST.String`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.SurrogateAnnotation` -> `SchemaRepresentation.RepresentationAnnotation`: Surrogate AST metadata was replaced by schema representation annotations and declaration codec hooks. + +- `SchemaAST.SurrogateAnnotationId` -> `Schema.Annotations.Declaration["representation"]`: The symbol annotation was replaced by the representation key. + +- `SchemaAST.Suspend` -> `SchemaAST.Suspend`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.SymbolKeyword` -> `SchemaAST.Symbol`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.TemplateLiteral` -> `SchemaAST.TemplateLiteral`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. Parts must not contain encodings, including inside unions or nested templates; use Schema.TemplateLiteralParser for transformed parts. + +- `SchemaAST.TemplateLiteralSpan` -> `SchemaAST.TemplateLiteral`: Template literal parts are represented directly as AST values in v4. + +- `SchemaAST.TitleAnnotation` -> `Schema.Annotations.Augment["title"]`: Titles are string-keyed schema annotations in v4. + +- `SchemaAST.TitleAnnotationId` -> `Schema.Annotations.Augment["title"]`: Symbol annotation IDs were removed; use title. + +- `SchemaAST.Transformation` -> `SchemaAST.Link`: Transformations are links in the Base.encoding chain in v4. + +- `SchemaAST.TransformationKind` -> `SchemaTransformation.Transformation`: Transformation implementations moved to SchemaTransformation and are stored on SchemaAST.Link. + +- `SchemaAST.TupleType` -> `SchemaAST.Arrays`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.Type` -> `SchemaAST.AST`: The tuple-element Type wrapper was removed; optionality and mutability moved to Context. + +- `SchemaAST.TypeConstructorAnnotation` -> `Schema.Annotations.Declaration["toCodec"]`: Type-constructor behavior moved to declaration codec annotations. + +- `SchemaAST.TypeConstructorAnnotationId` -> `Schema.Annotations.Declaration["toCodec"]`: Symbol annotation IDs were removed; use declaration codec annotation keys. + +- `SchemaAST.TypeLiteral` -> `SchemaAST.Objects`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.TypeLiteralTransformation` -> `SchemaAST.Encoding`: Object transformations are encoding links; use Schema.encodeKeys for key mappings. + +- `SchemaAST.UndefinedKeyword` -> `SchemaAST.Undefined`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.Union` -> `SchemaAST.Union`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.UniqueSymbol` -> `SchemaAST.UniqueSymbol`: The name remains, but its constructor and fields changed in the v4 Base/check/context/encoding model. + +- `SchemaAST.UnknownKeyword` -> `SchemaAST.Unknown`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.VoidKeyword` -> `SchemaAST.Void`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.annotations` -> `SchemaAST.annotate`: Use the v4 annotation helper and string-keyed Schema.Annotations. + +- `SchemaAST.anyKeyword` -> `SchemaAST.any`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.bigIntKeyword` -> `SchemaAST.bigInt`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.booleanKeyword` -> `SchemaAST.boolean`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.composeTransformation` -> `SchemaAST.Encoding`: V4 transformations are SchemaAST.Link values in an encoding chain; compose by adding links with SchemaAST.decodeTo. + +- `SchemaAST.defaultParseOption` -> `SchemaAST.defaultParseOptions`: The default parse options constant was pluralized. + +- `SchemaAST.encodedAST` -> `SchemaAST.toEncoded`: The encoded projection helper was renamed. + +- `SchemaAST.encodedBoundAST` -> `SchemaAST.toEncoded`: The separate encoded-bound projection was removed; use the encoded projection and v4 encoding links. + +- `SchemaAST.getAnnotation` -> `SchemaAST.resolveAt`: Resolve string-keyed annotations with resolveAt, or use resolveIdentifier, resolveTitle, and resolveDescription. + +- `SchemaAST.getBatchingAnnotation` -> `none`: Batching annotations were removed; read ParseOptions.concurrency when controlling asynchronous parsing. + +- `SchemaAST.getBrandAnnotation` -> `SchemaAST.resolveAt("brands")`: Resolve the string-keyed brands annotation. + +- `SchemaAST.getCompiler` -> `none`: The Match-based compiler was removed; traverse SchemaAST.AST directly or use the relevant Schema derivation API. + +- `SchemaAST.getConcurrencyAnnotation` -> `SchemaAST.resolveAt("parseOptions")`: Resolve parseOptions and read concurrency from it. + +- `SchemaAST.getDecodingFallbackAnnotation` -> `none`: Fallbacks are encoding middleware in v4, not readable annotations; attach them with Schema.catchDecoding. + +- `SchemaAST.getDefaultAnnotation` -> `SchemaAST.resolveAt("default")`: Resolve the string-keyed default annotation. + +- `SchemaAST.getDescriptionAnnotation` -> `SchemaAST.resolveDescription`: Use the dedicated resolved-description helper. + +- `SchemaAST.getDocumentationAnnotation` -> `SchemaAST.resolveAt("documentation")`: Resolve the string-keyed documentation annotation. + +- `SchemaAST.getExamplesAnnotation` -> `SchemaAST.resolveAt("examples")`: Resolve the string-keyed examples annotation. + +- `SchemaAST.getIdentifierAnnotation` -> `SchemaAST.resolveIdentifier`: Use the dedicated resolved-identifier helper. + +- `SchemaAST.getJSONIdentifier` -> `SchemaAST.resolveIdentifier`: JSON Schema references now use the normal resolved identifier. + +- `SchemaAST.getJSONIdentifierAnnotation` -> `SchemaAST.resolveIdentifier`: The separate JSON identifier annotation was removed; use identifier. + +- `SchemaAST.getJSONSchemaAnnotation` -> `SchemaAST.resolveAt("toJsonSchema")`: JSON Schema generation hooks use the string-keyed toJsonSchema annotation on checks. + +- `SchemaAST.getMessageAnnotation` -> `SchemaAST.resolveAt("message")`: Resolve the string-keyed message annotation. + +- `SchemaAST.getMissingMessageAnnotation` -> `SchemaAST.resolveAt("messageMissingKey")`: Missing-key messages use the messageMissingKey key. + +- `SchemaAST.getParseIssueTitleAnnotation` -> `none`: Issue-title callbacks were removed; use message or expected annotations and SchemaIssue formatters. + +- `SchemaAST.getParseOptionsAnnotation` -> `SchemaAST.resolveAt("parseOptions")`: Resolve the string-keyed parseOptions annotation. + +- `SchemaAST.getPropertySignatures` -> `SchemaAST.Objects.propertySignatures`: Narrow to Objects and read propertySignatures directly. + +- `SchemaAST.getSchemaIdAnnotation` -> `SchemaAST.resolveIdentifier`: Schema IDs were consolidated into the identifier annotation. + +- `SchemaAST.getSurrogateAnnotation` -> `SchemaAST.resolveAt("representation")`: Surrogate AST annotations were replaced by representation annotations and declaration codec hooks. + +- `SchemaAST.getTemplateLiteralCapturingRegExp` -> `none`: The low-level RegExp compiler was removed; use Schema.TemplateLiteral and schema parsing instead. + +- `SchemaAST.getTemplateLiteralRegExp` -> `none`: The low-level RegExp compiler was removed; use Schema.TemplateLiteral and schema parsing instead. + +- `SchemaAST.getTitleAnnotation` -> `SchemaAST.resolveTitle`: Use the dedicated resolved-title helper. + +- `SchemaAST.getTypeConstructorAnnotation` -> `SchemaAST.resolveAt("toCodec")`: Type-constructor behavior moved to declaration codec annotations. + +- `SchemaAST.isAnyKeyword` -> `SchemaAST.isAny`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isBigIntKeyword` -> `SchemaAST.isBigInt`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isBooleanKeyword` -> `SchemaAST.isBoolean`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isComposeTransformation` -> `none`: Compose transformation markers were replaced by explicit SchemaAST.Link encoding chains. + +- `SchemaAST.isEnums` -> `SchemaAST.isEnum`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isFinalTransformation` -> `SchemaTransformation.Transformation`: Use SchemaTransformation guards or the transformation object stored on a SchemaAST.Link. + +- `SchemaAST.isNeverKeyword` -> `SchemaAST.isNever`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isNumberKeyword` -> `SchemaAST.isNumber`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isParameter` -> `SchemaAST.isString`: The Parameter union was removed; inspect the v4 key AST variants directly. + +- `SchemaAST.isRefinement` -> `SchemaAST.Check`: Refinement AST nodes became checks attached to Base.checks. + +- `SchemaAST.isStringKeyword` -> `SchemaAST.isString`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isSymbolKeyword` -> `SchemaAST.isSymbol`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isTransformation` -> `SchemaAST.Encoding`: Transformation AST nodes became encoding links attached to Base.encoding. + +- `SchemaAST.isTupleType` -> `SchemaAST.isArrays`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isTypeLiteral` -> `SchemaAST.isObjects`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isTypeLiteralTransformation` -> `Schema.encodeKeys`: Property-key transformations are represented by encoding links and normally built with Schema.encodeKeys. + +- `SchemaAST.isUndefinedKeyword` -> `SchemaAST.isUndefined`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isUnknownKeyword` -> `SchemaAST.isUnknown`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.isVoidKeyword` -> `SchemaAST.isVoid`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.keyof` -> `none`: Low-level SchemaAST.keyof was removed; model the desired key literals explicitly. + +- `SchemaAST.mutable` -> `Schema.mutable`: Apply mutability at the Schema level; AST property mutability is represented by Context. + +- `SchemaAST.neverKeyword` -> `SchemaAST.never`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.null` -> `SchemaAST.null`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.numberKeyword` -> `SchemaAST.number`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.omit` -> `Schema.mapFields + Struct.omit`: Object projection moved to schema field transforms. + +- `SchemaAST.partial` -> `Schema.mapFields + Struct.map(Schema.optional)`: Partial object transforms moved to schema field transforms. + +- `SchemaAST.pick` -> `none`: The low-level AST picker was removed. Keep field selection at the Schema.Struct level with mapFields and Struct.pick, or discriminate and rebuild custom AST nodes explicitly. + +- `SchemaAST.required` -> `Schema.mapFields + Struct.map(Schema.requiredKey)`: Required object transforms moved to schema field transforms. + +- `SchemaAST.stringKeyword` -> `SchemaAST.string`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.symbolKeyword` -> `SchemaAST.symbol`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.typeAST` -> `SchemaAST.toType`: The type-side projection helper was renamed. + +- `SchemaAST.undefinedKeyword` -> `SchemaAST.undefined`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.unknownKeyword` -> `SchemaAST.unknown`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +- `SchemaAST.voidKeyword` -> `SchemaAST.void`: The v4 SchemaAST redesign renamed this primitive, collection, or guard while preserving its role. + +### `effect/Scope` + +- `Scope.CloseableScope` -> `Scope.Closeable`: Use the renamed type and close it with Scope.close(scope, exit). + +- `Scope.CloseableScopeTypeId` -> `none`: The closeable-scope marker is private in v4. + +- `Scope.Scope` -> `Scope.Scope`: The type remains; use module functions instead of the removed instance methods. + +- `Scope.Scope.Closeable` -> `Scope.Closeable`: The nested alias is now the top-level Closeable interface. + +- `Scope.Scope.Finalizer` -> `(exit: Exit.Exit) => Effect.Effect`: No alias is exported; inline the Scope.addFinalizerExit callback type. + +- `Scope.ScopeTypeId` -> `none`: The Scope marker is private in v4 and has no public guard. + +- `Scope.extend` -> `Scope.provide`: The operation was renamed with the same data-first and curried forms. + +### `effect/ScopedCache` + +- `ScopedCache.Lookup` -> `(key: Key) => Effect.Effect`: The named alias was removed; use an inline lookup type or ScopedCache.ScopedCache\["lookup"]. + +- `ScopedCache.ScopedCache` -> `ScopedCache.ScopedCache`: The model remains as a Pipeable scoped cache; construct and use it inside a Scope with explicit ScopedCache operations. + +- `ScopedCache.ScopedCache.Variance` -> `none`: The public variance marker was removed; use ScopedCache.ScopedCache directly. + +- `ScopedCache.ScopedCacheTypeId` -> `none`: The ScopedCache type id is internal in v4; do not inspect or construct the brand directly. + +### `effect/ScopedRef` + +- `ScopedRef.ScopedRef` -> `ScopedRef.ScopedRef`: The type remains but no longer extends Effect; use ScopedRef.get or ScopedRef.getUnsafe. + +- `ScopedRef.ScopedRef.Variance` -> `none`: The exported variance artifact was removed. + +- `ScopedRef.ScopedRefTypeId` -> `none`: The marker is private in v4 and no public guard exists. + +- `ScopedRef.ScopedRefUnify` -> `none`: ScopedRef no longer extends Effect; use ScopedRef.get explicitly. + +- `ScopedRef.ScopedRefUnifyIgnore` -> `none`: The Effect-unification implementation detail was removed. + +### `effect/Secret` + +- `Secret.Secret` -> `Redacted.Redacted`: Secret was deprecated in v3 and removed in v4; use the generic Redacted wrapper. + +- `Secret.Secret.Proto` -> `none`: The Secret-specific prototype was removed with the module; use Redacted.Redacted\. + +- `Secret.SecretTypeId` -> `Redacted.isRedacted`: The Secret marker was removed; use the Redacted runtime guard. + +- `Secret.fromIterable` -> `Redacted.make(Array.from(iterable).join(""))`: Secret was removed; join the character iterable and wrap the resulting string in Redacted. + +- `Secret.isSecret` -> `Redacted.isRedacted`: Secret was removed in favor of Redacted. + +- `Secret.make` -> `Redacted.make(bytes.map((byte) => String.fromCharCode(byte)).join(""))`: Secret was removed; preserve the v3 byte-to-code-unit conversion explicitly, then wrap the string in Redacted. + +- `Secret.unsafeWipe` -> `Redacted.wipeUnsafe`: Redacted.wipeUnsafe removes the registry entry but, unlike v3 Secret, cannot zero a retained mutable byte array; zero external buffers separately when required. + +### `effect/SingleProducerAsyncInput` + +- `SingleProducerAsyncInput.AsyncInputConsumer` -> `Queue.Dequeue`: Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one. + +- `SingleProducerAsyncInput.AsyncInputProducer` -> `Queue.Enqueue`: Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one. + +- `SingleProducerAsyncInput.SingleProducerAsyncInput` -> `Queue.Queue`: Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one. + +- `SingleProducerAsyncInput.make` -> `Queue.make`: Use v4 Queue producer and consumer views and bridge to Channel with Channel.fromQueue, Channel.callback, or Channel.fromPull. Queue.make({ capacity: 0 }) is the closest rendezvous backpressure but is not one-for-one. + +### `effect/Sink` + +- `Sink.Sink` -> `Sink`: Interface kept as Sink\ with the same type parameters; the internal representation changed from a Channel wrapper to a `transform: (upstream: Pull>, scope) => Effect>` function, and completion is the tuple `Sink.End = readonly [value, leftover?]`. + +- `Sink.Sink.Variance` -> `Sink.Variance`: Still exists as the namespace interface Sink.Variance\ (with Sink.VarianceStruct); the variance key is now the internal string "\~effect/Sink" instead of the SinkTypeId symbol. + +- `Sink.SinkTypeId` -> `none`: The type id is the unexported internal string "\~effect/Sink" (no unique symbol, no export); use the new guard Sink.isSink(u) instead of checking the symbol. + +- `Sink.SinkUnify` -> `SinkUnify`: Kept with the same name and shape (extends Effect.EffectUnify, preserves all five Sink type parameters). + +- `Sink.SinkUnifyIgnore` -> `SinkUnifyIgnore`: Kept with the same name; now a standalone `{ Effect?: true }` interface instead of extending Effect.EffectUnifyIgnore. + +- `Sink.collectAll` -> `Sink.collect`: Renamed; returns Sink\, In\> collecting into a plain mutable Array instead of Chunk. + +- `Sink.collectAllFrom` -> `none`: Repeated-run result accumulation was removed; checked the v4 export list (no collectAllFrom/repeatedly). Re-implement with Sink.fromTransform, looping self.transform on the upstream pull (feeding leftovers back) until the upstream ends, accumulating results in an array. + +- `Sink.collectAllN` -> `Sink.take`: Sink.take(n) returns Sink\, In, In\> collecting up to n elements (Array instead of Chunk), emitting the unconsumed remainder as leftovers. + +- `Sink.collectAllToMap` -> `Sink.reduce`: Built-in HashMap collector removed; build a plain Map in the reducer: Sink.reduce(() =\> new Map\(), (m, in\_) =\> { const k = key(in\_); return m.set(k, m.has(k) ? merge(m.get(k)!, in\_) : in\_) }). + +- `Sink.collectAllToMapN` -> `none`: Removed; approximating with Sink.reduceWhile((...) , (m) =\> m.size \< n, ...) consumes (merges) the element that introduces the (n+1)-th key, whereas v3 left it as leftover — exact v3 leftover behavior needs a custom Sink.fromTransform that checks the key before consuming. + +- `Sink.collectAllToSet` -> `Sink.reduce`: Built-in HashSet collector removed; build a plain Set: Sink.reduce(() =\> new Set\(), (s, in\_) =\> s.add(in\_)). + +- `Sink.collectAllToSetN` -> `Sink.reduceWhile`: Removed; equivalent via Sink.reduceWhile(() =\> new Set\(), (s) =\> s.size \< n, (s, in\_) =\> s.add(in\_)) — stops with leftovers once n distinct values are collected (plain Set instead of HashSet). + +- `Sink.collectAllUntil` -> `Sink.takeUntil`: Renamed; Sink.takeUntil(predicate) collects into Array\ until the predicate matches, including the matching element, like v3. + +- `Sink.collectAllUntilEffect` -> `Sink.takeUntilEffect`: Renamed; Sink.takeUntilEffect(p) collects into Array\ until the effectful predicate returns true, including the matching element. + +- `Sink.collectAllWhile` -> `Sink.takeWhile`: Renamed; Sink.takeWhile(predicate) collects the matching prefix into Array\, keeps the refinement overload, and excludes the first failing element (returned via leftovers). + +- `Sink.collectAllWhileEffect` -> `Sink.takeWhileEffect`: Renamed; Sink.takeWhileEffect(p) collects into Array\ while the effectful predicate returns true. + +- `Sink.collectAllWhileWith` -> `none`: Repeatedly-run-and-fold was removed (no v4 counterpart in the export list); re-implement with Sink.fromTransform looping self.transform while the `while` predicate holds on each result, folding results with `body` and feeding leftovers back into the next run. + +- `Sink.collectLeftover` -> `Sink.mapEnd`: Use Sink.mapEnd to move the leftovers into the result: Sink.mapEnd(self, ([a, leftover]) =\> [[a, leftover ?? []] as const]); leftovers are NonEmptyReadonlyArray\ | undefined instead of Chunk\. + +- `Sink.context` -> `Sink.fromEffect(Effect.context())`: Sink.context was removed; Sink.fromEffect(Effect.context\()) yields the same Sink\, unknown, never, never, R\>. + +- `Sink.contextWith` -> `Sink.fromEffect(Effect.contextWith(f))`: Removed; compose Sink.fromEffect with Effect.contextWith to derive a value from the context. + +- `Sink.contextWithEffect` -> `Sink.fromEffect(Effect.flatMap(Effect.context(), f))`: Removed, and v4 Effect has no contextWithEffect; use Sink.fromEffect(Effect.flatMap(Effect.context\(), f)). + +- `Sink.contextWithSink` -> `Sink.unwrap(Effect.contextWith(f))`: Removed; Sink.unwrap(Effect.contextWith((ctx: Context.Context\) =\> f(ctx))) builds the sink from the context. + +- `Sink.dieMessage` -> `Sink.die`: Removed (v4 has no RuntimeException-based dieMessage anywhere); use Sink.die(new Error(message)). + +- `Sink.dieSync` -> `Sink.failCauseSync`: Removed; use Sink.failCauseSync(() =\> Cause.die(evaluate())) to defer defect evaluation, or Sink.die(defect) when eager is fine. + +- `Sink.dimap` -> `Sink.mapInput + Sink.map`: Removed; compose the two halves: self.pipe(Sink.mapInput(f), Sink.map(g)). + +- `Sink.dimapChunks` -> `Sink.mapInputArray + Sink.map`: Removed; compose self.pipe(Sink.mapInputArray(f), Sink.map(g)) — f now maps NonEmptyReadonlyArray instead of Chunk and must return a non-empty array. + +- `Sink.dimapChunksEffect` -> `Sink.mapInputArrayEffect + Sink.mapEffect`: Removed; compose self.pipe(Sink.mapInputArrayEffect(f), Sink.mapEffect(g)) — f maps NonEmptyReadonlyArray instead of Chunk and must return a non-empty array. + +- `Sink.dimapEffect` -> `Sink.mapInputEffect + Sink.mapEffect`: Removed; compose self.pipe(Sink.mapInputEffect(f), Sink.mapEffect(g)). + +- `Sink.drop` -> `none`: The drop\* sinks were removed (nothing in the v4 export list); drop on the stream side instead with Stream.drop(n) before running the sink, or write a Sink.fromTransform that discards the first n pulled elements. + +- `Sink.dropUntil` -> `none`: Removed with the other drop\* sinks; use Stream.dropUntil(predicate) on the stream before running the sink. + +- `Sink.dropUntilEffect` -> `none`: Removed; use Stream.dropUntilEffect(p) on the stream before running the sink. + +- `Sink.dropWhile` -> `none`: Removed; use Stream.dropWhile(predicate) on the stream before running the sink. + +- `Sink.dropWhileEffect` -> `none`: Removed; use Stream.dropWhileEffect(p) on the stream before running the sink. + +- `Sink.ensuringWith` -> `Sink.onExit`: Renamed; Sink.onExit(self, (exit: Exit\) =\> finalizer) runs after completion, failure, or interruption — the exit now carries the sink's result value A (v3 passed Exit\). Plain Sink.ensuring(effect) also still exists for the exit-independent case. + +- `Sink.filterInput` -> `none`: Removed, and not expressible via Sink.mapInputArray because its function must return a non-empty array (a fully-filtered batch is illegal); filter on the stream with Stream.filter(predicate) before running the sink, or write a Sink.fromTransform that skips empty filtered batches. + +- `Sink.filterInputEffect` -> `none`: Removed (same non-empty-array constraint as filterInput); use Stream.filterEffect(p) on the stream before running the sink. + +- `Sink.foldChunks` -> `Sink.reduceWhileArray`: Sink.reduceWhileArray(() =\> s, contFn, f) folds whole input batches; initial state is now a lazy thunk and f receives NonEmptyReadonlyArray\ instead of Chunk\. + +- `Sink.foldChunksEffect` -> `Sink.reduceWhileArrayEffect`: Sink.reduceWhileArrayEffect(() =\> s, contFn, f) is the effectful array-level fold with continuation predicate; lazy initial state, NonEmptyReadonlyArray instead of Chunk. Sink.foldArray has the same shape but does not check contFn on the initial state. + +- `Sink.foldEffect` -> `Sink.reduceWhileEffect`: Sink.reduceWhileEffect(() =\> s, contFn, f) folds element-by-element with an effectful step and continuation predicate (checked on the initial state, like v3); initial state is now a lazy thunk. v4 Sink.fold has the same signature but skips the initial-state check. + +- `Sink.foldLeft` -> `Sink.reduce`: Renamed; Sink.reduce(() =\> s, f) — initial state is now a lazy thunk, semantics otherwise identical. + +- `Sink.foldLeftChunks` -> `Sink.reduceArray`: Renamed; Sink.reduceArray(() =\> s, f) folds whole batches — lazy initial state, f receives NonEmptyReadonlyArray\ instead of Chunk\. + +- `Sink.foldLeftChunksEffect` -> `Sink.reduceWhileArrayEffect`: No plain reduceArrayEffect exists in v4; use Sink.reduceWhileArrayEffect(() =\> s, () =\> true, f) (constant-true predicate) — f receives NonEmptyReadonlyArray\ instead of Chunk\ and the result has L = never like v3. + +- `Sink.foldLeftEffect` -> `Sink.reduceEffect`: Renamed; Sink.reduceEffect(() =\> s, f) — lazy initial state, effectful step, no termination predicate. + +- `Sink.foldSink` -> `Sink.orElse + Sink.flatMap`: The two-channel match was removed; compose self.pipe(Sink.orElse((e) =\> options.onFailure(e)), Sink.flatMap((a) =\> options.onSuccess(a))) — orElse switches to the failure sink (resuming the same upstream), flatMap feeds leftovers to the success sink first. + +- `Sink.foldUntilEffect` -> `Sink.foldUntil`: v4 Sink.foldUntil(() =\> s, max, f) takes the effectful step function directly (f returns Effect\), so it covers v3 foldUntilEffect; initial state is now a lazy thunk. For the pure v3 foldUntil wrap the step in Effect.succeed. + +- `Sink.foldWeighted` -> `none`: The whole foldWeighted family was removed from v4 Sink (checked the export list); re-implement with Sink.fold carrying the accumulated cost in the state (cont while cost \< max), returning leftovers automatically when stopping mid-batch. + +- `Sink.foldWeightedDecompose` -> `none`: Removed with no decompose mechanism in v4; splitting oversized elements must happen upstream (transform the stream before the sink) or inside a custom Sink.fromTransform. + +- `Sink.foldWeightedDecomposeEffect` -> `none`: Removed; same as foldWeightedDecompose — no effectful weighted/decompose fold exists, re-implement via Sink.fromTransform or restructure upstream. + +- `Sink.foldWeightedEffect` -> `none`: Removed; re-implement with Sink.fold (its step is effectful in v4) tracking accumulated cost in the state. + +- `Sink.forEachChunk` -> `Sink.forEachArray`: Renamed; f receives NonEmptyReadonlyArray\ instead of Chunk\. + +- `Sink.forEachChunkWhile` -> `Sink.forEachWhileArray`: Renamed; f: (NonEmptyReadonlyArray\) =\> Effect\ continues while true, stops on false, as in v3. + +- `Sink.fromPush` -> `Sink.fromTransform`: The push-based protocol (Option\ push function failing with [Either, leftovers]) is gone; v4's low-level constructor is pull-based: Sink.fromTransform((upstream: Pull\\>, scope) =\> Effect\\>) — pull inputs from upstream and finish by succeeding with the [value, leftover?] tuple. + +- `Sink.leftover` -> `Sink.succeed`: Removed as a standalone constructor; Sink.succeed now takes optional leftovers: Sink.succeed(void 0, leftovers) where leftovers is a NonEmptyReadonlyArray\ instead of Chunk\. + +- `Sink.mapInputChunks` -> `Sink.mapInputArray`: Renamed; f maps NonEmptyReadonlyArray\ =\> NonEmptyReadonlyArray\ (must stay non-empty) instead of Chunk =\> Chunk. + +- `Sink.mapInputChunksEffect` -> `Sink.mapInputArrayEffect`: Renamed; f maps NonEmptyReadonlyArray\ =\> Effect\\> (must stay non-empty) instead of Chunk =\> Effect\. + +- `Sink.mkString` -> `Sink.reduceArray`: Removed as a built-in; equivalent one-liner: Sink.reduceArray(() =\> "", (s, arr) =\> s + arr.join("")). + +- `Sink.race` -> `none`: Sink racing (race/raceBoth/raceWith) was removed from v4; broadcast the stream (Stream.broadcast) into two consumers and race the resulting run effects with Effect.race, or write a custom Channel. + +- `Sink.raceBoth` -> `none`: Removed with the race family; broadcast the stream and use Effect.raceBoth (or Effect.race) on the two Stream.run effects to learn which side won. + +- `Sink.raceWith` -> `none`: Removed, along with the MergeDecision type it depended on; the closest is broadcasting the stream and combining the two run effects manually (Effect.raceWith on the run effects). + +- `Sink.refineOrDie` -> `Sink.catch`: Removed; rebuild with the typed-error handler: Sink.catch(self, (e) =\> Option.match(pf(e), { onSome: Effect.fail, onNone: () =\> Effect.die(e) })) — note Sink.catch replaces the result on recovery, so refined errors must be re-failed as shown. + +- `Sink.refineOrDieWith` -> `Sink.catch`: Removed; same pattern as refineOrDie but die with the mapped defect: Sink.catch(self, (e) =\> Option.match(pf(e), { onSome: Effect.fail, onNone: () =\> Effect.die(f(e)) })). + +- `Sink.splitWhere` -> `none`: Removed; it re-chunked input so the sink stopped before the first later element matching the predicate — closest v4 options are pre-splitting the stream (Stream.split / Stream.takeWhile) or a custom Sink.fromTransform that cuts pulled arrays at the predicate boundary and returns the rest as leftovers. + +- `Sink.unwrapScoped` -> `Sink.unwrap`: Folded into Sink.unwrap, whose signature now excludes Scope from R (Sink\<..., Exclude\ | R2\>), so scoped effects are accepted directly; resources stay open for the sink's lifetime. + +- `Sink.unwrapScopedWith` -> `Sink.unwrap`: Folded into Sink.unwrap — obtain the scope inside the effect via Effect.scope (Sink.unwrap(Effect.flatMap(Effect.scope, f))); for direct scope access use Sink.fromTransform, whose transform receives (upstream, scope). + +- `Sink.zip` -> `Sink.flatMap`: The zip family was removed; sequential zip is self.pipe(Sink.flatMap((a) =\> Sink.map(that, (a2) =\> [a, a2] as const))) — leftovers of the first sink feed the second. The { concurrent: true } racing mode has no v4 equivalent. + +- `Sink.zipLeft` -> `Sink.flatMap`: Removed; use self.pipe(Sink.flatMap((a) =\> Sink.as(that, a))) to run both sequentially and keep the first result (no concurrent option). + +- `Sink.zipRight` -> `Sink.flatMap`: Removed; use self.pipe(Sink.flatMap(() =\> that)) to run both sequentially and keep the second result (no concurrent option). + +- `Sink.zipWith` -> `Sink.flatMap`: Removed; use self.pipe(Sink.flatMap((a) =\> Sink.map(that, (a2) =\> f(a, a2)))) — sequential only, the { concurrent: true } option has no v4 equivalent. + +### `effect/SortedMap` + +- `SortedMap.SortedMap` -> `HashMap.HashMap`: Use HashMap as the immutable core model and retain Order externally; ordered iteration and range seeks require sorting on observation. + +- `SortedMap.TypeId` -> `none`: The SortedMap brand was removed and HashMap.TypeId is private; use HashMap.isHashMap when a guard is needed. + +- `SortedMap.empty` -> `HashMap.empty`: SortedMap was removed; use an immutable HashMap and retain the key Order separately. + +- `SortedMap.entries` -> `HashMap.entries + Array.sortWith`: Materialize HashMap.entries and sort by key with the retained Order when ordered traversal is required. + +- `SortedMap.fromIterable` -> `HashMap.fromIterable`: Use HashMap.fromIterable and retain the key Order separately; duplicate keys collapse. + +- `SortedMap.get` -> `HashMap.get`: Direct optional lookup on the replacement immutable map. + +- `SortedMap.getOrder` -> `none`: HashMap does not store an Order; retain and pass the key Order explicitly. + +- `SortedMap.has` -> `HashMap.has`: Use direct membership testing on the replacement HashMap; retain the key Order separately for sorted observations. + +- `SortedMap.headOption` -> `HashMap.entries + Array.sortWith + Array.head`: Sort entries by key with the retained Order, then take the optional first entry. + +- `SortedMap.isEmpty` -> `HashMap.isEmpty`: Direct emptiness check on the replacement immutable map. + +- `SortedMap.isNonEmpty` -> `HashMap.isEmpty`: Use !HashMap.isEmpty(self); no dedicated HashMap.isNonEmpty helper exists. + +- `SortedMap.isSortedMap` -> `HashMap.isHashMap`: Use the replacement model guard; it does not prove that observations were sorted. + +- `SortedMap.keys` -> `HashMap.entries + Array.sortWith + Array.map`: Sort entries by key, map to keys, and iterate the resulting array. + +- `SortedMap.lastOption` -> `HashMap.entries + Array.sortWith + Array.last`: Sort entries by key with the retained Order, then take the optional last entry. + +- `SortedMap.make` -> `HashMap.make`: Remove the outer order-curried constructor and pass entries directly to HashMap.make. + +- `SortedMap.map` -> `HashMap.map`: The value-and-key callback remains, but result iteration is unordered until explicitly sorted. + +- `SortedMap.partition` -> `HashMap.filter`: Build [excluded, satisfying] with complementary HashMap.filter calls; adapt the callback to the old key predicate. + +- `SortedMap.reduce` -> `HashMap.reduce`: Reduce the replacement HashMap, but explicitly sort entries first if the old key-order traversal affected the result. + +- `SortedMap.remove` -> `HashMap.remove`: Direct persistent removal; explicitly sort only when observing entries. + +- `SortedMap.set` -> `HashMap.set`: Direct persistent insert or update; explicitly sort only when observing entries. + +- `SortedMap.size` -> `HashMap.size`: Direct size query on the replacement immutable map. + +- `SortedMap.values` -> `HashMap.entries + Array.sortWith + Array.map`: Sort entries by key, map to values, and iterate the resulting array. + +### `effect/SortedSet` + +- `SortedSet.SortedSet` -> `HashSet.HashSet`: Use HashSet as the immutable core model and retain Order externally; ordered iteration requires sorting on observation. + +- `SortedSet.TypeId` -> `none`: The SortedSet brand was removed and HashSet.TypeId is private; use HashSet.isHashSet when a guard is needed. + +- `SortedSet.add` -> `HashSet.add`: Direct persistent add on the replacement set; sort only when traversing. + +- `SortedSet.difference` -> `HashSet.difference + HashSet.fromIterable`: Convert the old general iterable argument to HashSet before taking the difference. + +- `SortedSet.empty` -> `HashSet.empty`: SortedSet was removed; use an immutable HashSet and retain the element Order separately. + +- `SortedSet.every` -> `HashSet.every`: Run the predicate against the replacement HashSet; sort first only if traversal order has observable effects. + +- `SortedSet.filter` -> `HashSet.filter`: Direct persistent filtering on the replacement set; traversal is unordered until explicitly sorted. + +- `SortedSet.flatMap`: TODO: needs guidance + +- `SortedSet.fromIterable` -> `HashSet.fromIterable`: Use HashSet.fromIterable and retain the element Order separately. + +- `SortedSet.getEquivalence` -> `Equal.asEquivalence`: HashSet implements Effect equality by set content; use Equal.asEquivalence\\>(). + +- `SortedSet.has` -> `HashSet.has`: Use direct membership testing on the replacement HashSet. + +- `SortedSet.intersection` -> `HashSet.intersection + HashSet.fromIterable`: Convert the old general iterable argument to HashSet before taking the intersection. + +- `SortedSet.isSortedSet` -> `HashSet.isHashSet`: Use the replacement model guard; it does not prove that observations were sorted. + +- `SortedSet.make` -> `HashSet.make`: Remove the outer order-curried constructor and pass values directly to HashSet.make. + +- `SortedSet.map` -> `HashSet.map`: Remove the output Order argument; retain it externally and sort only when traversing. + +- `SortedSet.partition` -> `HashSet.filter`: Build [excluded, satisfying] with complementary HashSet.filter calls. + +- `SortedSet.remove` -> `HashSet.remove`: Direct persistent removal on the replacement set. + +- `SortedSet.size` -> `HashSet.size`: Direct size query on the replacement immutable set. + +- `SortedSet.some` -> `HashSet.some`: Run the predicate against the replacement HashSet; sort first only if traversal order has observable effects. + +- `SortedSet.toggle` -> `HashSet.has + HashSet.add / HashSet.remove`: HashSet has no toggle; branch on membership and add or remove the element. + +- `SortedSet.union` -> `HashSet.union + HashSet.fromIterable`: Convert the old general iterable argument to HashSet before taking the union. + +- `SortedSet.values` -> `Array.sort`: Sort the replacement HashSet with the retained Order and iterate the resulting array. + +### `effect/Stream` + +- `Stream.Stream` -> `Stream`: The Stream\ interface is unchanged in shape and keeps the effect/Stream import path; the type-id key is now the string literal "\~effect/Stream" instead of a unique symbol. + +- `Stream.Stream.Context` -> `Stream.Services`: Type-level extractor of the R parameter renamed from Stream.Context\ to Stream.Services\; identical conditional-infer semantics. + +- `Stream.Stream.DynamicTuple` -> `Types.TupleOf`: Already deprecated in v3 in favor of Types.TupleOf; removed in v4. Use Types.TupleOf\ (v4 Stream.broadcastN uses it for its return type). + +- `Stream.Stream.DynamicTupleOf` -> `Types.TupleOf`: Recursive tuple-builder helper removed with Stream.DynamicTuple; Types.TupleOf\ is the v4 equivalent. + +- `Stream.StreamTypeId` -> `Stream.TypeId`: Renamed StreamTypeId -\> TypeId and changed from a unique symbol to the string literal "\~effect/Stream" (both the const and the type). + +- `Stream.StreamUnify` -> `Stream.StreamUnify`: Still exported under the same name in v4 (extends Effect.EffectUnify); no change needed besides any Chunk-related element types. + +- `Stream.StreamUnifyIgnore` -> `Stream.StreamUnifyIgnore`: Still exported under the same name in v4 (extends Effect.EffectUnifyIgnore with Effect ignored); unchanged. + +- `Stream.accumulateChunks` -> `none`: v3 accumulateChunks only rewrote the internal chunk layout (each chunk cumulatively contained all prior elements) without changing the emitted element sequence; v4 has no chunk-layout twin. Stream.accumulate emits the cumulative NonEmptyArray values, and Stream.rechunk controls chunk sizing. + +- `Stream.acquireRelease` -> `Stream.scoped(Stream.fromEffect(Effect.acquireRelease(acquire, release)))`: Dedicated constructor removed; compose Effect.acquireRelease (same (resource, exit) release signature) with Stream.fromEffect, then Stream.scoped to tie the finalizer to the stream's lifetime. + +- `Stream.aggregateWithinEither` -> `Stream.aggregateWithin`: Either-emitting variant removed; v4 aggregateWithin(sink, schedule) emits only the sink outputs B (schedule outputs are no longer surfaced as Either.right). + +- `Stream.as` -> `Stream.map(() => value)`: Stream.as was removed; replace each element with a constant via Stream.map. + +- `Stream.async` -> `Stream.callback`: Stream.callback((queue) =\> Effect | void, { bufferSize?, strategy? }) replaces the Emit-based async; push with Queue.offer/offerAll, end with Queue.end, fail with Queue.fail. + +- `Stream.asyncEffect` -> `Stream.callback`: The register function of Stream.callback may return an Effect (run before the stream starts pulling), covering asyncEffect; signal end/failure through the provided Queue. + +- `Stream.asyncPush` -> `Stream.callback`: Stream.callback's register effect can use Scope for acquire/release of the external subscription, replacing asyncPush; the Emit ops helpers become plain Queue operations. + +- `Stream.asyncScoped` -> `Stream.callback`: Stream.callback's register effect may use Scope (Scope is excluded from the resulting R), replacing asyncScoped; the Option\ end signal becomes Queue.end. + +#### `Stream.branchAfter` + +**Replacement:** `Stream.peel` + +Removed; the closest v4 primitive is Stream.peel(self, Sink.take(n)), a scoped Effect yielding [firstN, restStream] from which you build the continuation stream and re-wrap with Stream.unwrap. + +**Example** + +```ts +Stream.unwrap(Effect.map(Stream.peel(self, Sink.take(n)), ([head, rest]) => f(head)(rest))) +``` + +- `Stream.broadcastDynamic` -> `Stream.broadcast`: v4 Stream.broadcast({ capacity, strategy?, replay? }) is the dynamic-subscriber fan-out returning Effect\, never, Scope | R\> (v3 fixed-arity broadcast(n) became Stream.broadcastN); Stream.share adds refcounted/idleTimeToLive semantics. + +- `Stream.broadcastedQueues` -> `none`: Queue-of-Take fan-out surface removed. Use Stream.broadcastN({ n, capacity }) for a fixed tuple of mirror streams, or Stream.toPubSubTake to obtain a PubSub of Take values and subscribe consumers to it. + +- `Stream.broadcastedQueuesDynamic` -> `none`: Removed with broadcastedQueues. Use Stream.broadcast (dynamic mirror streams) or Stream.toPubSubTake + PubSub subscriptions when raw Take-level consumers are needed. + +- `Stream.bufferChunks` -> `Stream.bufferArray`: Chunk-\>Array rename; buffers whole arrays (chunks) up to capacity with the same strategy options. + +- `Stream.catchAll` -> `Stream.catch`: Renamed to Stream.catch (exported keyword-style); same (error) =\> Stream handler for all typed failures. + +- `Stream.catchAllCause` -> `Stream.catchCause`: Renamed; handler receives the full Cause\ and returns a recovery stream, identical semantics. + +- `Stream.catchSome` -> `Stream.catchFilter`: Option-returning partial handler replaced by the Filter API: Stream.catchFilter(filter, f, orElse?) recovers matched errors, unmatched failures pass through (Stream.catchIf for refinement/predicate matching). + +- `Stream.catchSomeCause` -> `Stream.catchCauseFilter`: Option-returning cause handler replaced by Stream.catchCauseFilter(filter, f, orElse?) using a Filter on the Cause (Stream.catchCauseIf for refinements). + +- `Stream.chunksWith` -> `Stream.flattenArray(f(Stream.chunks(self)))`: No dedicated combinator; expose chunk structure with Stream.chunks (Stream\\>), transform, then re-flatten with Stream.flattenArray. + +- `Stream.combineChunks` -> `Stream.combineArray`: Chunk-\>Array rename of the pull-level combining primitive; pulls now yield NonEmptyReadonlyArray values and halt via Cause.Done-failing Pull effects instead of Option-typed errors. + +- `Stream.concatAll` -> `Stream.flatten`: Chunk-of-streams constructor removed; sequential concatenation of many streams is Stream.flatten(Stream.fromIterable(streams)) (default concurrency 1 preserves order). + +- `Stream.context` -> `Stream.fromEffect(Effect.context())`: Dedicated accessor removed; lift Effect.context\() into a single-element stream. + +- `Stream.contextWith` -> `Stream.fromEffect(Effect.contextWith(f))`: Dedicated accessor removed; Effect.contextWith still exists in v4, lift it with Stream.fromEffect. + +- `Stream.contextWithEffect` -> `Stream.fromEffect(Effect.flatMap(Effect.context(), f))`: Removed; read the Context with Effect.context, feed it to the effectful function, and lift the result with Stream.fromEffect. + +- `Stream.contextWithStream` -> `Stream.unwrap(Effect.contextWith(f))`: Removed; build the dependent stream inside Effect.contextWith and flatten with Stream.unwrap. + +- `Stream.crossLeft` -> `Stream.crossWith(that, (a, _) => a)`: Removed; cartesian product keeping only left elements is expressed with Stream.crossWith and a left-projecting combiner. + +- `Stream.crossRight` -> `Stream.crossWith(that, (_, b) => b)`: Removed; cartesian product keeping only right elements is Stream.crossWith with a right-projecting combiner (equivalently Stream.flatMap(self, () =\> that)). + +- `Stream.dieMessage` -> `Stream.die(new Error(message))`: Removed along with RuntimeException; die with an explicit defect value via Stream.die. + +- `Stream.dieSync` -> `Stream.failCauseSync(() => Cause.die(evaluate()))`: Removed; lazily construct the defect cause with Cause.die inside Stream.failCauseSync. + +- `Stream.distributedWith` -> `none`: Predicate-routed fixed fan-out to Take queues removed (no v4 counterpart found among broadcast/broadcastN/share/toPubSub/partition). Closest patterns: Stream.broadcastN + Stream.filter per branch, Stream.partition for two-way splits, or manual routing by running the stream into per-consumer Queues. + +- `Stream.distributedWithDynamic` -> `none`: Dynamic predicate-routed fan-out removed with distributedWith. Use Stream.broadcast/Stream.share for dynamic mirrors plus per-subscriber Stream.filter, or hand-roll routing into Queues via Stream.runForEach. + +- `Stream.either` -> `Stream.result`: Either is replaced by Result in v4: Stream.result yields Stream\, never, R\> (element -\> Result.succeed, first error -\> Result.fail and the stream ends, as before). + +- `Stream.ensuringWith` -> `Stream.onExit`: Renamed; Stream.onExit runs the finalizer with the Exit\ of the stream, identical shape. + +- `Stream.execute` -> `Stream.fromEffectDrain`: Renamed; runs the effect for its side effects and emits nothing (Stream\). + +- `Stream.filterMapWhile` -> `Stream.takeWhileFilter`: Option-returning partial function replaced by the Filter API: Stream.takeWhileFilter(filter) maps and emits while the filter passes, ending the stream at the first miss. + +- `Stream.filterMapWhileEffect` -> `none`: No effectful takeWhileFilter variant in v4. Recreate by using Stream.takeWhileFilter with a Filter that selects the Effect\ to run, followed by Stream.mapEffect((eff) =\> eff) to execute it. + +- `Stream.finalizer` -> `Stream.ensuring`: One-element finalizer-registering stream removed; attach finalizers directly with Stream.ensuring/Stream.onExit, or register in the stream scope via Stream.scoped(Stream.fromEffect(Effect.addFinalizer(fin))) when the v3 concat-a-finalizer pattern must be preserved. + +- `Stream.find` -> `Stream.take(Stream.filter(self, predicate), 1)`: Removed; first-match semantics are Stream.filter followed by Stream.take(1). + +- `Stream.findEffect` -> `Stream.take(Stream.filterEffect(self, f), 1)`: Removed; Stream.filterEffect takes an effectful (a, index) =\> Effect\ predicate, then Stream.take(1) stops at the first match. + +- `Stream.flattenChunks` -> `Stream.flattenArray`: Chunk-\>Array rename; flattens a Stream of ReadonlyArray values into their elements. + +- `Stream.flattenExitOption` -> `Stream.flattenTake`: The Exit\\> end-of-stream encoding is gone; v4 uses Take\ = NonEmptyReadonlyArray\ | Exit and Stream.flattenTake unwraps it (emit arrays, end/fail on Exit). + +- `Stream.flattenIterables` -> `Stream.flattenIterable`: Renamed (singular); flattens a Stream of Iterables into their elements. + +- `Stream.fromChunk` -> `Stream.fromArray`: Chunk-\>Array rename; takes a ReadonlyArray and emits it as one chunk. + +- `Stream.fromChunkPubSub` -> `Stream.fromPubSub`: Chunked PubSub constructors are gone; v4 Stream.fromPubSub(pubsub) consumes PubSub\ directly (batched internally). For a PubSub carrying arrays use Stream.flattenArray(Stream.fromPubSub(pubsub)); the scoped/shutdown options were dropped (Stream.fromSubscription consumes an existing subscription). + +- `Stream.fromChunkQueue` -> `Stream.fromQueue`: Chunked Queue constructor gone; v4 Stream.fromQueue consumes Queue.Dequeue\ whose done/failure signals end the stream (no shutdown option). For array payloads wrap with Stream.flattenArray. + +- `Stream.fromChunks` -> `Stream.fromArrays`: Chunk-\>Array rename; variadic arrays, each emitted as one chunk. + +- `Stream.fromEffectOption` -> `none`: The Effect\\> encoding (fail None = empty stream) is removed; v4 signals early end with Cause.Done in Pull-level code. Rebuild with Stream.unwrap: map the success to Stream.succeed and match the Option error to Stream.empty (None) or Stream.fail (Some). + +- `Stream.fromReadableStreamByob` -> `Stream.fromReadableStream`: BYOB reader variant removed (no byob support in v4 source); Stream.fromReadableStream({ evaluate, onError, releaseLockOnEnd? }) consumes any ReadableStream with a default reader, without byte-buffer allocation control. + +- `Stream.fromTPubSub` -> `none`: STM TPubSub was replaced by the transactional TxPubSub module and v4 Stream has no Tx\* constructors; subscribe and repeatedly TxQueue.take from the subscription (e.g. inside Stream.fromPull/Stream.callback), or bridge through a regular PubSub and Stream.fromPubSub. + +- `Stream.fromTQueue` -> `none`: STM TQueue was replaced by TxQueue and v4 Stream has no Tx\* constructors; drain by repeatedly calling TxQueue.take inside a custom loop (Stream.fromPull/Stream.callback), or bridge into a regular Queue and use Stream.fromQueue. + +- `Stream.haltAfter` -> `Stream.haltWhen(Effect.sleep(duration))`: Duration-specialized halt removed; v3 documented it as haltWhen with a sleep — completes the stream after the duration without interrupting an in-flight pull. + +- `Stream.haltWhenDeferred` -> `Stream.haltWhen(Deferred.await(deferred))`: Deferred-specialized variant removed; Deferred.await is an Effect, so plain Stream.haltWhen covers it. + +- `Stream.identity` -> `Channel.identity`: The identity-pipeline Stream is gone; for pipeThrough-style plumbing use Stream.pipeThroughChannel(Channel.identity()), or simply the identity function where a Stream=\>Stream transform is expected. + +- `Stream.interruptAfter` -> `Stream.interruptWhen(Effect.sleep(duration))`: Duration-specialized interrupt removed; interruptWhen forks the sleep and also interrupts an in-progress pull, matching v3 semantics. + +- `Stream.interruptWhenDeferred` -> `Stream.interruptWhen(Deferred.await(deferred))`: Deferred-specialized variant removed; pass Deferred.await to Stream.interruptWhen (a Deferred failure surfaces as the stream's failure, as before). + +- `Stream.mapChunks` -> `Stream.mapArray`: Chunk-\>Array rename; transforms each emitted chunk as a NonEmptyReadonlyArray. + +- `Stream.mapChunksEffect` -> `Stream.mapArrayEffect`: Chunk-\>Array rename of the effectful per-chunk transform. + +- `Stream.mapConcat` -> `Stream.flattenIterable(Stream.map(self, f))`: Removed; map each element to an Iterable and flatten with Stream.flattenIterable. + +- `Stream.mapConcatChunk` -> `Stream.flattenArray(Stream.map(self, f))`: Chunk variant removed with Chunk itself; map to a ReadonlyArray and flatten with Stream.flattenArray. + +- `Stream.mapConcatChunkEffect` -> `Stream.flattenArray(Stream.mapEffect(self, f))`: Removed; effectfully map each element to a ReadonlyArray and flatten with Stream.flattenArray. + +- `Stream.mapConcatEffect` -> `Stream.flattenIterable(Stream.mapEffect(self, f))`: Removed; effectfully map each element to an Iterable and flatten with Stream.flattenIterable. + +- `Stream.mapErrorCause` -> `Stream.catchCause((cause) => Stream.failCause(f(cause)))`: Removed; transform the full Cause by catching it and re-failing with the mapped cause. + +- `Stream.mapInputContext` -> `Stream.updateContext`: Renamed; same contravariant (Context\) =\> Context\ mapping of the required services. + +- `Stream.mergeEither` -> `Stream.mergeResult`: Either replaced by Result: Stream.mergeResult(self, that) yields Result.Result\ with self -\> Result.succeed and that -\> Result.fail (v3 put self in Either.left and that in Either.right, so the success/left roles swap sides). + +- `Stream.mergeWith` -> `Stream.merge(Stream.map(self, onSelf), Stream.map(that, onOther), { haltStrategy })`: Removed; pre-map both streams to the common type and use Stream.merge, whose options accept the same haltStrategy union ("left" | "right" | "both" | "either"). + +#### `Stream.mergeWithTag` + +**Replacement:** `none` + +Struct-to-tagged-union merge removed. Recreate with Stream.mergeAll over the entries, tagging each stream first. + +**Example** + +```ts +Stream.mergeAll(Object.entries(streams).map(([_tag, s]) => Stream.map(s, (value) => ({ _tag, value }))), { concurrency }) +``` + +- `Stream.onDone` -> `Stream.onEnd`: Renamed; v4 onEnd takes an Effect value (not a () =\> Effect thunk) run when the stream ends successfully, and its error type may add to the stream's. + +- `Stream.orDieWith` -> `Stream.orDie`: orDieWith removed; transform the error first, then convert failures to defects: `self.pipe(Stream.mapError(f), Stream.orDie)`. + +- `Stream.orElse` -> `Stream.catch`: v3 catchAll was renamed to Stream.catch in v4; orElse ignored the error, so write `Stream.catch(self, () => that())`. + +- `Stream.orElseEither` -> `Stream.catch`: Removed; Either is replaced by Result in v4. Emulate: `Stream.map(self, Result.succeed).pipe(Stream.catch(() => Stream.map(that(), Result.fail)))` (same encoding v4 Stream.mergeResult uses). + +- `Stream.orElseFail` -> `Stream.mapError`: Removed; it only replaced the failure value: `Stream.mapError(self, () => error())` or `Stream.catch(self, () => Stream.fail(error()))`. + +- `Stream.orElseIfEmptyChunk` -> `Stream.orElseIfEmpty`: Folded into Stream.orElseIfEmpty, which now takes a lazy fallback Stream: `Stream.orElseIfEmpty(self, () => Stream.fromArray(array))`; Chunk is replaced by plain arrays. + +- `Stream.orElseIfEmptyStream` -> `Stream.orElseIfEmpty`: Direct rename: v4 Stream.orElseIfEmpty takes a LazyArg\ fallback, identical semantics. + +- `Stream.paginateChunk` -> `Stream.paginate`: v4 Stream.paginate is effectful and array-based: `paginate(s, (s) => Effect, Option]>)`; wrap the pure step in Effect.succeed and use an array instead of a Chunk. + +- `Stream.paginateChunkEffect` -> `Stream.paginate`: v4 Stream.paginate has exactly this shape; only Chunk becomes ReadonlyArray. + +- `Stream.paginateEffect` -> `Stream.paginate`: v4 Stream.paginate emits a batch per step; wrap the single value in an array: `(s) => Effect.map(step(s), ([a, next]) => [[a], next])`. + +- `Stream.partitionEither` -> `Stream.partitionEffect`: Either-based split replaced by Filter.FilterEffect: the function now returns Effect\\> (Result.succeed/Result.fail instead of Either.right/left). Returns Effect\<[passes, fails], never, R | Scope\> — note the tuple is [passes, fails], v3 was [left, right]; options are { capacity?, concurrency? }. + +- `Stream.provideLayer` -> `Stream.provide`: v4 Stream.provide accepts a Layer or a Context; behavior identical. + +- `Stream.provideServiceStream` -> `none`: Removed; v4 has provideService/provideServiceEffect but no stream-valued variant. Emulate with `Stream.flatMap(services, (s) => Stream.provideService(self, tag, s))` over the service stream, or use Stream.provideServiceEffect for effectful acquisition. + +- `Stream.provideSomeContext` -> `Stream.provideContext`: v4 Stream.provideContext is the single Context provider with `Exclude` semantics — same behavior as v3 provideSomeContext. + +- `Stream.provideSomeLayer` -> `Stream.provide`: v4 Stream.provide accepts a Layer (or Context) and excludes only the provided services from R — same partial-provision semantics. + +- `Stream.refineOrDie` -> `Stream.catch`: Removed; emulate with `Stream.catch(self, (e) => { const r = pf(e); return Option.isSome(r) ? Stream.fail(r.value) : Stream.die(e) })` — refail refined errors, die on the rest. + +- `Stream.refineOrDieWith` -> `Stream.catch`: Removed; same as refineOrDie but die with the mapped defect: `Stream.die(f(e))` for unrefined errors. + +- `Stream.repeatEffect` -> `Stream.fromEffectRepeat`: Renamed; repeats the effect forever emitting each result. + +- `Stream.repeatEffectChunk` -> `Stream.fromIterableEffectRepeat`: Renamed; the effect now produces an Iterable/array instead of a Chunk, repeated forever. + +- `Stream.repeatEffectChunkOption` -> `Stream.fromIterableEffectRepeat`: The Option\ error encoding is gone: end the stream by failing the effect with `Cause.done()` (a Cause.Done failure); Done is excluded from the resulting stream's error type (Pull.ExcludeDone\). + +- `Stream.repeatEffectOption` -> `Stream.fromEffectRepeat`: The Option\ error encoding is gone: fail the effect with `Cause.done()` instead of Option.none() to end the stream; other failures propagate as stream errors. + +- `Stream.repeatEffectWithSchedule` -> `Stream.fromEffectSchedule`: Renamed; runs the effect once, then repeats it per the schedule, emitting each result. + +- `Stream.repeatEither` -> `none`: Removed; v4 Stream.repeat(schedule) repeats the stream but never emits the schedule outputs, and no Either/unification variant exists. If schedule outputs must be observed, hand-roll with Channel or track them via a schedule that taps into a Ref. + +- `Stream.repeatElementsWith` -> `none`: Removed; v4 Stream.repeatElements(schedule) repeats each element per the schedule but never emits schedule outputs — the onElement/onSchedule unification is gone. Use repeatElements if only element repetition is needed. + +- `Stream.repeatValue` -> `Stream.fromEffectRepeat`: Removed; use `Stream.fromEffectRepeat(Effect.succeed(value))` or `Stream.forever(Stream.succeed(value))`. + +- `Stream.repeatWith` -> `none`: Removed; v4 Stream.repeat(schedule) covers the repetition but drops the schedule outputs and the onElement/onSchedule unification. Hand-roll if schedule outputs must appear in the stream. + +- `Stream.runFoldScoped` -> `Stream.runFold`: Scoped run variants are gone; v4 run functions manage the stream's scope internally and the initial value is now a LazyArg: `Stream.runFold(self, () => s, f)`. For enclosing-scope control, pull manually via `Stream.toPull` (Effect\). + +- `Stream.runFoldScopedEffect` -> `Stream.runFoldEffect`: Scoped run variants are gone; use `Stream.runFoldEffect(self, () => s, f)` — scope is managed internally, initial value is a LazyArg. Use Stream.toPull for manual scoped consumption. + +#### `Stream.runFoldWhile` + +**Replacement:** `none` + +v4 runFold has no early-exit predicate; emulate with Stream.runForEachWhile and a mutable accumulator. + +**Example** + +```ts +// v3: Stream.runFoldWhile(self, init, cont, f) +Effect.suspend(() => { + let acc = init + return Stream.runForEachWhile(self, (a) => { + acc = f(acc, a) + return Effect.succeed(cont(acc)) + }).pipe(Effect.map(() => acc)) +}) + +``` + +- `Stream.runFoldWhileEffect` -> `none`: v4 runFoldEffect has no early-exit predicate; emulate with Stream.runForEachWhile and a mutable accumulator, mapping the effectful step to Effect\ via cont(acc) (see runFoldWhile example). + +- `Stream.runFoldWhileScoped` -> `none`: Both the while-predicate and the scoped run variants are gone in v4; emulate the predicate with Stream.runForEachWhile plus a mutable accumulator (see runFoldWhile); scope is managed internally by v4 run functions. + +- `Stream.runFoldWhileScopedEffect` -> `none`: Both the while-predicate and the scoped run variants are gone in v4; emulate with Stream.runForEachWhile plus a mutable accumulator and effectful step; scope is managed internally by v4 run functions. + +- `Stream.runForEachChunk` -> `Stream.runForEachArray`: Renamed; the callback receives a NonEmptyReadonlyArray instead of a Chunk. + +- `Stream.runForEachChunkScoped` -> `Stream.runForEachArray`: Scoped run variants are gone; v4 runForEachArray manages the stream scope internally. Use Stream.toPull for manual scoped consumption. + +- `Stream.runForEachScoped` -> `Stream.runForEach`: Scoped run variants are gone; v4 runForEach manages the stream scope internally. Use Stream.toPull for manual scoped consumption. + +- `Stream.runForEachWhileScoped` -> `Stream.runForEachWhile`: Scoped run variants are gone; v4 runForEachWhile (callback returns Effect\) manages the stream scope internally. + +- `Stream.runIntoPubSubScoped` -> `Stream.runIntoPubSub`: Scoped variant removed; v4 runIntoPubSub(pubsub, { shutdownOnEnd? }) publishes plain values (the Take wrapper is gone) and does not require Scope — fork the returned effect (Effect.forkIn/Effect.forkScoped) to reproduce the background scoped behavior. + +- `Stream.runIntoQueueElementsScoped` -> `Stream.runIntoQueue`: The per-element Exit\\> encoding is gone; v4 runIntoQueue targets a Queue\ — elements are offered plainly and failure/end are signalled through the queue's error/done channel. Fork with Effect.forkIn for scoped background running. + +- `Stream.runIntoQueueScoped` -> `Stream.runIntoQueue`: Scoped variant removed; v4 runIntoQueue offers plain values to a Queue\ (Take wrapper gone) and requires no Scope — fork the returned effect into a scope (Effect.forkIn) if needed. + +- `Stream.runScoped` -> `Stream.run`: Scoped variant removed; v4 Stream.run(sink) manages the stream's scope internally. For consumption tied to an enclosing Scope, use Stream.toPull and drive the Pull manually. + +- `Stream.scanReduce` -> `Stream.mapAccum`: Removed; emulate first-element-as-seed with `Stream.mapAccum(self, () => undefined as A | undefined, (acc, a) => { const next = acc === undefined ? a : f(acc, a); return [next, [next]] })`. + +- `Stream.scanReduceEffect` -> `Stream.mapAccumEffect`: Removed; same first-element-as-seed emulation as scanReduce but with Stream.mapAccumEffect and an effectful step. + +- `Stream.scheduleWith` -> `none`: Removed; v4 Stream.schedule(schedule) only paces elements and never emits schedule outputs — the onElement/onSchedule unification is gone. Use Stream.schedule if only pacing is needed. + +- `Stream.scopedWith` -> `Stream.scoped`: Removed; v4 Stream.scoped scopes a Stream (provides a Scope kept open for the stream's lifetime). Emulate: `Stream.scoped(Stream.fromEffect(Effect.flatMap(Effect.scope, f)))` — Effect.scope accesses the ambient Scope. + +- `Stream.some` -> `none`: Removed along with Option\ error encodings. To drop None values use `Stream.filterMap(self, Filter.fromPredicateOption((o) => o))`; to fail on None use Stream.mapEffect with Option.match into Effect.fail/Effect.succeed. + +- `Stream.someOrElse` -> `Stream.map`: Removed; use `Stream.map(self, Option.getOrElse(() => fallback()))`. + +- `Stream.someOrFail` -> `Stream.mapEffect`: Removed; use `Stream.mapEffect(self, Option.match({ onNone: () => Effect.fail(error()), onSome: Effect.succeed }))`. + +- `Stream.splitOnChunk` -> `none`: Delimiter-subsequence splitting was removed; v4 keeps only Stream.split (predicate/refinement, emitting NonEmptyReadonlyArray segments) and Stream.splitLines. Hand-roll multi-element delimiter splitting with Stream.mapAccumArray. + +- `Stream.tapErrorCause` -> `Stream.tapCause`: Renamed; taps the full Cause on failure. + +- `Stream.timeoutFail` -> `Stream.timeoutOrElse`: Use `Stream.timeoutOrElse(self, { duration, orElse: () => Stream.fail(error()) })`; the timeout resets on every emitted value as before. + +- `Stream.timeoutFailCause` -> `Stream.timeoutOrElse`: Use `Stream.timeoutOrElse(self, { duration, orElse: () => Stream.failCause(cause()) })`. + +- `Stream.timeoutTo` -> `Stream.timeoutOrElse`: Renamed into an options form: `Stream.timeoutOrElse(self, { duration, orElse: () => that })` — the fallback stream is now lazy. + +- `Stream.toAsyncIterableRuntime` -> `Stream.toAsyncIterableWith`: Renamed; takes a `Context.Context` instead of a Runtime (v4 removed Runtime — a services Context is the execution environment). toAsyncIterable/toAsyncIterableEffect also still exist. + +- `Stream.toQueueOfElements` -> `Stream.toQueue`: The Exit\\>-per-element queue is gone; v4 toQueue(options: { capacity, strategy? }) returns Effect\, never, R | Scope\> — elements are plain values and failure/end arrive through the queue's error/done channel. + +- `Stream.toReadableStreamRuntime` -> `Stream.toReadableStreamWith`: Renamed; takes a `Context.Context` instead of a Runtime (v4 removed Runtime); options `{ strategy?: QueuingStrategy }` unchanged. + +- `Stream.transduce` -> `Stream.transduce`: Unchanged name and Sink-based shape; chunks are plain arrays in v4. + +- `Stream.unfoldChunk` -> `Stream.paginate`: Removed; v4 Stream.paginate(s, (s) =\> Effect\<[ReadonlyArray\, Option\]\>) is the array-emitting unfold — wrap the pure step in Effect.succeed; to end without emitting return `[[], Option.none()]`. + +- `Stream.unfoldChunkEffect` -> `Stream.paginate`: Removed; v4 Stream.paginate has the effectful array-step shape — map v3's Option\<[Chunk, S]\> result to `[array, Option]`, returning `[[], Option.none()]` to end without emitting. + +- `Stream.unfoldEffect` -> `Stream.unfold`: v4 Stream.unfold is effectful: `unfold(s, (s) => Effect)` — return the pair or `undefined` to end instead of Option. + +- `Stream.unwrapScoped` -> `Stream.unwrap`: v4 Stream.unwrap accepts scoped effects (`Exclude` built in); the scope stays open for the stream's lifetime — it replaces both unwrap and unwrapScoped. + +- `Stream.unwrapScopedWith` -> `Stream.unwrap`: Removed; access the ambient Scope explicitly: `Stream.unwrap(Effect.flatMap(Effect.scope, f))` — v4 unwrap keeps the scope open for the stream's lifetime. + +- `Stream.void` -> `Stream.succeed(void 0)`: The `Stream.void` constant (single void element) was removed; use `Stream.succeed(void 0)` or `Stream.make(void 0)`. + +- `Stream.whenCase` -> `none`: Removed; emulate with `Stream.suspend(() => Option.match(pf(evaluate()), { onNone: () => Stream.empty, onSome: (s) => s }))`. + +- `Stream.whenCaseEffect` -> `none`: Removed; emulate with `Stream.unwrap(Effect.map(self, (a) => Option.match(pf(a), { onNone: () => Stream.empty, onSome: (s) => s })))`. + +- `Stream.whenEffect` -> `Stream.when`: Folded into Stream.when, which now takes an `Effect` test directly (wrap a pure condition with Effect.sync). + +- `Stream.zipAll` -> `none`: The entire zipAll family was removed in v4 (only zip/zipLatest/zipLatestAll exist; zip ends at the shorter side, zipLatest\* combine latest values — different semantics). Pad-with-default zipping must be hand-rolled, e.g. with Stream.combineArray pulling both sides. + +- `Stream.zipAllLeft` -> `none`: Removed with the zipAll family; no default-padding zip exists in v4. Hand-roll with Stream.combineArray (or concat the remainder after a plain Stream.zipLeft) if needed. + +- `Stream.zipAllRight` -> `none`: Removed with the zipAll family; no default-padding zip exists in v4. Hand-roll with Stream.combineArray if needed. + +- `Stream.zipAllSortedByKey` -> `none`: Removed; the sorted-by-key merge-join family has no v4 equivalent (checked v4 Stream exports — only zip/zipLatest/zipLatestAll/zipWithArray). Hand-roll a keyed merge with Stream.combineArray. + +- `Stream.zipAllSortedByKeyLeft` -> `none`: Removed; see zipAllSortedByKey — no keyed merge-join in v4, hand-roll with Stream.combineArray. + +- `Stream.zipAllSortedByKeyRight` -> `none`: Removed; see zipAllSortedByKey — no keyed merge-join in v4, hand-roll with Stream.combineArray. + +- `Stream.zipAllSortedByKeyWith` -> `none`: Removed; see zipAllSortedByKey — no keyed merge-join in v4, hand-roll with Stream.combineArray. + +- `Stream.zipAllWith` -> `none`: Removed with the zipAll family; v4 has no zip that pads the shorter side with defaults. Hand-roll with Stream.combineArray. + +- `Stream.zipWithChunks` -> `Stream.zipWithArray`: Renamed; the combiner now receives two NonEmptyReadonlyArrays and returns `[output: NonEmptyReadonlyArray, leftoverLeft: ReadonlyArray, leftoverRight: ReadonlyArray]` — the Either-wrapped leftover (ZipChunksResult) is replaced by the two explicit leftover arrays. + +### `effect/StreamEmit` + +#### `StreamEmit.Emit` + +**Replacement:** `Queue.Queue` + +The StreamEmit module is gone; v4 Stream.callback hands the callback a Queue\ instead of an Emit function. Emit values with Queue.offer/Queue.offerAll, end with Queue.end, fail with Queue.fail/Queue.failCause. + +**Example** + +```ts +// v3: Stream.async((emit) => { emit.single(1); emit.end() }) +Stream.callback((queue) => + Effect.gen(function*() { + yield* Queue.offer(queue, 1) + yield* Queue.end(queue) + })) + +``` + +- `StreamEmit.EmitOps` -> `Queue.offer / Queue.offerAll / Queue.end / Queue.fail / Queue.failCause`: Method-by-method mapping onto the Queue passed to Stream.callback: single(a) -\> Queue.offer(queue, a); chunk(c) -\> Queue.offerAll(queue, c); end() -\> Queue.end(queue); fail(e) -\> Queue.fail(queue, e); halt(cause) -\> Queue.failCause(queue, cause); die(d)/dieMessage(m) -\> Queue.failCause(queue, Cause.die(d)); done(exit) -\> Queue.offer then Queue.end on success, Queue.failCause on failure; fromEffect(eff) -\> run eff and offer its value (Effect.flatMap(eff, (a) =\> Queue.offer(queue, a))). + +- `StreamEmit.EmitOpsPush` -> `Queue.offerUnsafe / Queue.offerAllUnsafe / Queue.endUnsafe / Queue.failCauseUnsafe`: The synchronous push interface of v3 Stream.asyncPush maps to the \*Unsafe Queue operations on the Queue given to Stream.callback: single/array -\> Queue.offerUnsafe/Queue.offerAllUnsafe, end -\> Queue.endUnsafe, fail/halt/die -\> Queue.failCauseUnsafe (wrap plain errors with Cause.fail, defects with Cause.die). + +### `effect/StreamHaltStrategy` + +- `StreamHaltStrategy.Both` -> `"both"`: The tagged constructor is replaced by the plain string literal "both" passed directly to haltStrategy options. + +- `StreamHaltStrategy.Either` -> `"either"`: The tagged constructor is replaced by the plain string literal "either" passed directly to haltStrategy options. + +- `StreamHaltStrategy.HaltStrategy` -> `Stream.HaltStrategy`: The StreamHaltStrategy module is gone; v4 HaltStrategy is the string-literal union "left" | "right" | "both" | "either" (defined in Channel, re-exported as Stream.HaltStrategy) instead of tagged objects. + +- `StreamHaltStrategy.HaltStrategyInput` -> `Stream.HaltStrategy`: The Input widening (tagged object OR string) is obsolete; v4 only ever uses the string literals, so haltStrategy options take Stream.HaltStrategy directly. + +- `StreamHaltStrategy.Left` -> `"left"`: The tagged constructor is replaced by the plain string literal "left" passed directly to haltStrategy options. + +- `StreamHaltStrategy.Right` -> `"right"`: The tagged constructor is replaced by the plain string literal "right" passed directly to haltStrategy options. + +- `StreamHaltStrategy.fromInput` -> `none`: Remove the call; there is no conversion step in v4 because strategies already are the string literals, so pass the value through unchanged. + +- `StreamHaltStrategy.isBoth` -> `strategy === "both"`: Refinements on the tagged union become plain string comparison against the literal. + +- `StreamHaltStrategy.isEither` -> `strategy === "either"`: Refinements on the tagged union become plain string comparison against the literal. + +- `StreamHaltStrategy.isLeft` -> `strategy === "left"`: Refinements on the tagged union become plain string comparison against the literal. + +- `StreamHaltStrategy.isRight` -> `strategy === "right"`: Refinements on the tagged union become plain string comparison against the literal. + +#### `StreamHaltStrategy.match` + +**Replacement:** `switch (strategy)` + +Fold over the strategy with an ordinary switch (or ternary chain) on the string literal; TypeScript exhaustiveness-checks the four cases. + +**Example** + +```ts +// v3: HaltStrategy.match(s, { onLeft, onRight, onBoth, onEither }) +switch (strategy) { + case "left": return onLeft() + case "right": return onRight() + case "both": return onBoth() + case "either": return onEither() +} + +``` + +### `effect/Struct` + +- `Struct.entries` -> `Object.entries`: Use the native helper, adding a cast when the old precise key and value type is required. + +- `Struct.getEquivalence` -> `Struct.makeEquivalence`: Direct rename; the fields object call shape is unchanged. + +- `Struct.getOrder` -> `Struct.makeOrder`: Direct rename; the fields object call shape is unchanged. + +### `effect/Subscribable` + +- `Subscribable.Subscribable` -> `custom { readonly get: Effect.Effect; readonly changes: Stream.Stream }`: No renamed generic model exists; prefer concrete SubscriptionRef APIs or own this unbranded structural type locally. + +- `Subscribable.TypeId` -> `none`: The Subscribable brand has no public replacement; use a concrete model guard or an application structural guard. + +- `Subscribable.isSubscribable` -> `none`: The common brand was removed; use a concrete guard such as SubscriptionRef.isSubscriptionRef or an application structural guard. + +- `Subscribable.make` -> `object literal { get, changes }`: No generic constructor remains; retain a local structural pair only when both the current read and change stream are needed. + +- `Subscribable.map` -> `Effect.map + Stream.map`: For a retained get and changes pair, map the Effect and Stream separately. + +- `Subscribable.mapEffect` -> `Effect.flatMap + Stream.mapEffect`: For a retained get and changes pair, flatMap the Effect and mapEffect the Stream separately. + +- `Subscribable.unwrap` -> `Effect.flatMap + Stream.unwrap`: Build get with Effect.flatMap and changes with Stream.unwrap; no single v4 helper remains. + +### `effect/SubscriptionRef` + +- `SubscriptionRef.SubscriptionRef` -> `SubscriptionRef.SubscriptionRef`: The model remains but no longer extends SynchronizedRef or Subscribable; use SubscriptionRef.get and SubscriptionRef.changes explicitly. + +- `SubscriptionRef.SubscriptionRef.Variance` -> `SubscriptionRef.SubscriptionRef.Variance`: The marker remains under SubscriptionRef.SubscriptionRef, but its brand uses an internal type id. + +- `SubscriptionRef.SubscriptionRefTypeId` -> `SubscriptionRef.isSubscriptionRef`: The type id is internal in v4; use the public runtime guard instead. + +- `SubscriptionRef.SubscriptionRefUnify` -> `none`: SubscriptionRef is no longer an Effect subtype, so its unification helper was removed; call SubscriptionRef.get explicitly. + +- `SubscriptionRef.SubscriptionRefUnifyIgnore` -> `none`: SubscriptionRef is no longer a SynchronizedRef or Effect subtype, so its unification ignore marker was removed. + +### `effect/Supervisor` + +- `Supervisor.AbstractSupervisor` -> `none`: The ambient Supervisor abstraction and runtime event hooks were removed. + +- `Supervisor.Supervisor` -> `none`: Ambient fiber supervision was removed; use structured concurrency or explicit FiberSet and FiberMap tracking. + +- `Supervisor.Supervisor.Variance` -> `none`: The Supervisor abstraction and its variance marker were removed. + +- `Supervisor.SupervisorTypeId` -> `none`: The Supervisor abstraction and its type identifier were removed. + +- `Supervisor.addSupervisor` -> `none`: Layer-installed ambient supervision was removed; use structured concurrency and explicit FiberSet or FiberMap tracking. + +- `Supervisor.fibersIn` -> `FiberSet`: Use a scoped FiberSet and explicitly run or add fibers; it does not ambiently observe every descendant. + +- `Supervisor.fromEffect` -> `none`: The Supervisor abstraction and its effect-valued observation hook were removed. + +- `Supervisor.none` -> `none`: The Supervisor abstraction was removed; normal structured concurrency needs no no-op supervisor. + +- `Supervisor.unsafeTrack` -> `FiberSet`: Use scoped FiberSet.make and explicitly run or add fibers; there is no unsafe unscoped ambient tracker. + +### `effect/Symbol` + +- `Symbol.Equivalence` -> `Equivalence.strictEqual()`: The dedicated symbol instance was removed; it used strict equality. + +### `effect/SynchronizedRef` + +- `SynchronizedRef.SynchronizedRef` -> `SynchronizedRef.SynchronizedRef`: The model remains but no longer extends Ref; read and update it through explicit SynchronizedRef operations. The curried v4 modifySomeEffect takes only the callback, which returns an Effect of [result, Option\]; remove the v3 fallback and outer Option. + +- `SynchronizedRef.SynchronizedRef.Variance` -> `none`: The public nested variance marker was removed. SynchronizedRef uses an internal brand in v4, so do not refer to a variance interface directly. + +- `SynchronizedRef.SynchronizedRefTypeId` -> `none`: The SynchronizedRef type id is internal in v4; do not inspect or construct the brand directly. + +- `SynchronizedRef.SynchronizedRefUnify` -> `none`: SynchronizedRef is no longer an Effect subtype, so its Effect unification helper was removed; call SynchronizedRef.get explicitly. + +- `SynchronizedRef.SynchronizedRefUnifyIgnore` -> `none`: SynchronizedRef is no longer an Effect subtype, so its Effect unification ignore marker was removed. + +- `SynchronizedRef.unsafeMake` -> `SynchronizedRef.makeUnsafe`: The unsafe suffix moved to the end. + +### `effect/TArray` + +- `TArray.TArray` -> `TxChunk.TxChunk`: TArray has no direct v4 counterpart; TxChunk is the closest rewrite target but uses whole-Chunk operations. + +- `TArray.TArray.Variance` -> `none`: TArray was removed and TxChunk exposes no public variance marker. + +- `TArray.TArrayTypeId` -> `TxChunk.isTxChunk`: TArray and its public type id were removed; use the TxChunk runtime guard after rewriting the data structure. + +- `TArray.collectFirst` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.collectFirstSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.contains` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.count` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.countSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.empty` -> `TxChunk.empty`: TArray was removed; TxChunk is the closest v4 transactional indexed collection. + +- `TArray.every` -> `Effect.map(TxChunk.get(self), Chunk.every(predicate))`: TArray was removed; read the TxChunk snapshot and test every element inside the surrounding Effect.tx transaction. + +- `TArray.everySTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.findFirstIndex` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findFirstIndexFrom` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findFirstIndexWhere` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findFirstIndexWhereFrom` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findFirstIndexWhereFromSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.findFirstIndexWhereSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.findFirstSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.findLast` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findLastIndex` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findLastIndexFrom` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.findLastSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.fromIterable` -> `TxChunk.fromIterable`: TArray was removed; construct the v4 TxChunk rewrite target from the iterable. + +- `TArray.get` -> `Effect.map(TxChunk.get(self), Chunk.get(index))`: TxChunk.get returns the whole Chunk, so apply Chunk.get to preserve indexed optional lookup. + +- `TArray.headOption` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.lastOption` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.make` -> `TxChunk.fromIterable(elements)`: TxChunk.make takes one Chunk rather than variadic elements; TxChunk.fromIterable preserves the old call shape after collecting arguments. + +- `TArray.maxOption` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.minOption` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.reduce` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.reduceOption` -> `TxChunk.get + Chunk/Array operation`: TArray was removed. Read the TxChunk snapshot and perform the equivalent pure collection query inside the surrounding Effect.tx transaction. + +- `TArray.reduceOptionSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.reduceSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.size` -> `TxChunk.size`: TxChunk is the closest v4 rewrite target. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TArray.some` -> `Effect.map(TxChunk.get(self), Chunk.some(predicate))`: TArray was removed; read the TxChunk snapshot and test for a matching element inside the surrounding Effect.tx transaction. + +- `TArray.someSTM` -> `Effect.tx + TxChunk.get + Effect traversal`: TArray was removed. Read the TxChunk snapshot and perform the effectful traversal explicitly within the same Effect.tx transaction. + +- `TArray.toArray` -> `Effect.map(TxChunk.get(self), Chunk.toArray)`: TxChunk.get returns a Chunk; convert that snapshot to an Array explicitly. + +- `TArray.transform` -> `TxChunk.update(self, Chunk.map(f))`: TArray was removed; transform the whole TxChunk snapshot with a Chunk mapping function. + +- `TArray.transformSTM` -> `Effect.tx + TxChunk.get/TxChunk.set`: Read the snapshot, traverse it effectfully, and write the rebuilt Chunk within one Effect.tx transaction. + +- `TArray.update` -> `TxChunk.modify`: TxChunk updates the whole Chunk; use modify to update the indexed element and preserve the old optional-index behavior. + +- `TArray.updateSTM` -> `Effect.tx + TxChunk.get/TxChunk.set`: Read, effectfully update the indexed element, and write the rebuilt Chunk within one Effect.tx transaction. + +### `effect/TDeferred` + +- `TDeferred.TDeferred` -> `TxDeferred.TxDeferred`: Rename the type and import from "effect/TxDeferred". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TDeferred.TDeferred.Variance` -> `none`: TxDeferred exposes no public variance marker. + +- `TDeferred.TDeferredTypeId` -> `TxDeferred.isTxDeferred`: The type id is internal in v4; use the public runtime guard. + +- `TDeferred.await` -> `TxDeferred.await`: Import TxDeferred from "effect/TxDeferred"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TDeferred.make` -> `TxDeferred.make`: Import TxDeferred from "effect/TxDeferred"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +### `effect/TMap` + +- `TMap.TMap` -> `TxHashMap.TxHashMap`: Rename the type and import from "effect/TxHashMap". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.TMap.Variance` -> `none`: TxHashMap exposes no public variance marker. + +- `TMap.TMapTypeId` -> `TxHashMap.isTxHashMap`: The type id is internal in v4; use the public runtime guard. + +- `TMap.empty` -> `TxHashMap.empty`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.findAllSTM` -> `TxHashMap.entries + Effect traversal`: No effectful mapped-find helper remains; traverse the entry snapshot explicitly inside Effect.tx. + +- `TMap.findSTM` -> `TxHashMap.entries + Effect.findFirst`: No effectful mapped-find helper remains; traverse entries explicitly inside Effect.tx. + +- `TMap.fromIterable` -> `TxHashMap.fromIterable`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.get` -> `TxHashMap.get`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.getOrElse` -> `Effect.map(TxHashMap.get(self, key), Option.getOrElse(fallback))`: Compose the retained optional get operation with Option.getOrElse. + +- `TMap.has` -> `TxHashMap.has`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.isEmpty` -> `TxHashMap.isEmpty`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.keys` -> `TxHashMap.keys`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.make` -> `TxHashMap.make`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.reduce` -> `TxHashMap.reduce`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.reduceSTM` -> `TxHashMap.entries + Effect.reduce`: Snapshot entries and reduce them effectfully inside the surrounding Effect.tx transaction. + +- `TMap.remove` -> `TxHashMap.remove`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.removeAll` -> `TxHashMap.removeMany`: The bulk removal operation was renamed. + +- `TMap.setIfAbsent` -> `Effect.tx + TxHashMap.get/TxHashMap.set`: No direct helper remains; check and conditionally set under one outer transaction. + +- `TMap.size` -> `TxHashMap.size`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TMap.takeFirst` -> `none`: No atomic take-and-match helper exists in TxHashMap; implement explicit selection and removal inside Effect.tx. + +- `TMap.takeFirstSTM` -> `none`: No effectful atomic take-and-match helper exists; implement explicit traversal and removal inside Effect.tx. + +- `TMap.takeSome` -> `none`: No atomic multi-take helper exists in TxHashMap; implement explicit selection and removals inside Effect.tx. + +- `TMap.takeSomeSTM` -> `none`: No effectful atomic multi-take helper exists; implement explicit traversal and removals inside Effect.tx. + +- `TMap.toArray` -> `TxHashMap.entries`: Use the entry snapshot; it replaces the old array conversion. + +- `TMap.toChunk` -> `Effect.map(TxHashMap.entries(self), Chunk.fromIterable)`: Convert the entry snapshot to Chunk explicitly. + +- `TMap.toHashMap` -> `TxHashMap.snapshot`: The immutable HashMap snapshot operation was renamed. + +- `TMap.toMap` -> `Effect.map(TxHashMap.entries(self), (entries) => new Map(entries))`: Build a JavaScript Map from the entry snapshot. + +- `TMap.transform` -> `TxHashMap.map`: V4 map returns a new map rather than mutating self; key-changing transforms require snapshot and rebuild logic. + +- `TMap.transformSTM` -> `TxHashMap.entries + Effect traversal + TxHashMap.fromIterable`: No in-place effectful transform remains; traverse a snapshot and rebuild inside Effect.tx. + +- `TMap.transformValues` -> `TxHashMap.map`: V4 map transforms values but returns a new map rather than mutating self. + +- `TMap.transformValuesSTM` -> `TxHashMap.entries + Effect traversal + TxHashMap.fromIterable`: No effectful map remains; traverse a snapshot and rebuild inside Effect.tx. + +- `TMap.updateWith` -> `TxHashMap.modifyAt`: modifyAt is the closest atomic keyed update, but returns void; preserve any old return value explicitly if needed. + +- `TMap.values` -> `TxHashMap.values`: Import TxHashMap from "effect/TxHashMap"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +### `effect/TPriorityQueue` + +- `TPriorityQueue.TPriorityQueue` -> `TxPriorityQueue.TxPriorityQueue`: Rename the type and import from "effect/TxPriorityQueue". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.TPriorityQueue.Variance` -> `none`: TxPriorityQueue exposes no public variance marker. + +- `TPriorityQueue.TPriorityQueueTypeId` -> `TxPriorityQueue.isTxPriorityQueue`: The type id is internal in v4; use the public runtime guard. + +- `TPriorityQueue.empty` -> `TxPriorityQueue.empty`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.fromIterable` -> `TxPriorityQueue.fromIterable`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.isEmpty` -> `TxPriorityQueue.isEmpty`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.isNonEmpty` -> `TxPriorityQueue.isNonEmpty`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.make` -> `TxPriorityQueue.make`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.peek` -> `TxPriorityQueue.peek`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.size` -> `TxPriorityQueue.size`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.take` -> `TxPriorityQueue.take`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation now returns an ordinary Effect, so compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.takeAll` -> `TxPriorityQueue.takeAll`: Import TxPriorityQueue from "effect/TxPriorityQueue"; it returns an ordinary Effect containing the priority-ordered Array. + +- `TPriorityQueue.toArray` -> `TxPriorityQueue.toArray`: Import TxPriorityQueue from "effect/TxPriorityQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPriorityQueue.toChunk` -> `Effect.map(TxPriorityQueue.toArray(self), Chunk.fromIterable)`: The direct Chunk conversion was removed; convert the retained Array snapshot explicitly. + +### `effect/TPubSub` + +- `TPubSub.TPubSub` -> `TxPubSub.TxPubSub`: Rename the type and import from "effect/TxPubSub". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.TPubSubTypeId` -> `TxPubSub.isTxPubSub`: The type id is internal in v4; use the public runtime guard. + +- `TPubSub.bounded` -> `TxPubSub.bounded`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.capacity` -> `TxPubSub.capacity`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.dropping` -> `TxPubSub.dropping`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.isEmpty` -> `TxPubSub.isEmpty`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.isFull` -> `TxPubSub.isFull`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.isShutdown` -> `TxPubSub.isShutdown`: Import TxPubSub from "effect/TxPubSub"; the operation now returns an ordinary Effect. + +- `TPubSub.shutdown` -> `TxPubSub.shutdown`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.size` -> `TxPubSub.size`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.sliding` -> `TxPubSub.sliding`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TPubSub.subscribeScoped` -> `TxPubSub.subscribe`: The scoped subscription constructor lost its Scoped suffix; it still requires Scope and returns a TxQueue. + +- `TPubSub.unbounded` -> `TxPubSub.unbounded`: Import TxPubSub from "effect/TxPubSub"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +### `effect/TQueue` + +- `TQueue.BaseTQueue` -> `TxQueue.TxQueueState`: The shared queue state model was renamed and now includes the richer open, closing, and done lifecycle. + +- `TQueue.TDequeue` -> `TxQueue.TxDequeue`: Rename the read-side type; it now carries an error channel. + +- `TQueue.TDequeueTypeId` -> `TxQueue.isTxDequeue`: The type id is internal in v4; use the public runtime guard. + +- `TQueue.TEnqueue` -> `TxQueue.TxEnqueue`: Rename the write-side type; it now carries an error channel. + +- `TQueue.TEnqueueTypeId` -> `TxQueue.isTxEnqueue`: The type id is internal in v4; use the public runtime guard. + +- `TQueue.TQueue` -> `TxQueue.TxQueue`: Rename the type; it now carries an error channel and completion lifecycle. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.TQueue.TDequeueVariance` -> `TxQueue.TxDequeue.Variance`: The read-side variance marker moved under TxDequeue and now includes the error type. + +- `TQueue.TQueue.TEnqueueVariance` -> `TxQueue.TxEnqueue.Variance`: The write-side variance marker moved under TxEnqueue and now includes the error type. + +- `TQueue.bounded` -> `TxQueue.bounded`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.capacity` -> `queue.capacity`: Capacity is now a property on TxQueue handles rather than a module function. + +- `TQueue.dropping` -> `TxQueue.dropping`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.isEmpty` -> `TxQueue.isEmpty`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.isFull` -> `TxQueue.isFull`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.isShutdown` -> `TxQueue.isShutdown`: Import TxQueue from "effect/TxQueue"; it checks the richer done lifecycle and returns an ordinary Effect. + +- `TQueue.isTDequeue` -> `TxQueue.isTxDequeue`: The runtime guard was renamed with the TxDequeue type. + +- `TQueue.isTEnqueue` -> `TxQueue.isTxEnqueue`: The runtime guard was renamed with the TxEnqueue type. + +- `TQueue.isTQueue` -> `TxQueue.isTxQueue`: The runtime guard was renamed with the TxQueue type. + +- `TQueue.offerAll` -> `TxQueue.offerAll`: The operation remains, but now returns rejected elements rather than a boolean. + +- `TQueue.peek` -> `TxQueue.peek`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.poll` -> `TxQueue.poll`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.seek` -> `none`: TxQueue has no seek helper; repeat TxQueue.take under Effect.tx until the predicate matches. + +- `TQueue.shutdown` -> `TxQueue.shutdown`: The operation remains, but now returns whether shutdown changed the queue state. + +- `TQueue.size` -> `TxQueue.size`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.sliding` -> `TxQueue.sliding`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.take` -> `TxQueue.take`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.takeAll` -> `TxQueue.takeAll`: The operation now blocks until at least one item is available, returns a NonEmptyArray, and propagates the queue error channel through an ordinary Effect. + +- `TQueue.takeBetween` -> `TxQueue.takeBetween`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.takeN` -> `TxQueue.takeN`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TQueue.unbounded` -> `TxQueue.unbounded`: Import TxQueue from "effect/TxQueue"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +### `effect/TRandom` + +- `TRandom.TRandom` -> `none`: The transactional random service was deliberately removed; use Random outside retried transactions where possible. + +- `TRandom.TRandomTypeId` -> `none`: TRandom and its public type id were removed; v4 has no TxRandom module. + +- `TRandom.Tag` -> `Random.Random`: Use the v4 Random Context.Reference; the transactional random service was removed. + +- `TRandom.next` -> `Random.next`: TxRandom was removed. Random.next is an ordinary Effect and may be re-executed if used inside a retried transaction. + +- `TRandom.nextBoolean` -> `Random.nextBoolean`: TxRandom was removed. This ordinary Effect is not rollback-safe under transaction retry. + +- `TRandom.nextInt` -> `Random.nextInt`: TxRandom was removed. This ordinary Effect is not rollback-safe under transaction retry. + +- `TRandom.nextIntBetween` -> `Random.nextIntBetween(low, high, { halfOpen: true })`: TxRandom was removed; request half-open bounds explicitly to preserve the v3 range behavior. + +- `TRandom.nextRange` -> `Random.nextBetween`: The operation was renamed and is no longer backed by rollback-safe transactional random state. + +- `TRandom.shuffle` -> `Random.shuffle`: TxRandom was removed. This ordinary Effect is not rollback-safe under transaction retry. + +### `effect/TReentrantLock` + +- `TReentrantLock.TReentrantLock` -> `TxReentrantLock.TxReentrantLock`: Rename the type and import from "effect/TxReentrantLock". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TReentrantLock.TReentrantLock.Proto` -> `none`: The public prototype interface was removed. + +- `TReentrantLock.TReentrantLockTypeId` -> `TxReentrantLock.isTxReentrantLock`: The type id is internal in v4; use the public runtime guard. + +- `TReentrantLock.fiberReadLocks` -> `none`: Per-fiber read-lock counts were removed; TxReentrantLock.readLocks reports only the total count. + +- `TReentrantLock.fiberWriteLocks` -> `none`: Per-fiber write-lock counts were removed; TxReentrantLock.writeLocks reports only the total count. + +- `TReentrantLock.lock` -> `TxReentrantLock.writeLock`: The generic lock helper was renamed to make write-lock acquisition explicit. + +- `TReentrantLock.make` -> `TxReentrantLock.make()`: The constructor keeps its name but is now a function call rather than a constant STM value. + +### `effect/TRef` + +- `TRef.TRef` -> `TxRef.TxRef`: Rename the type and import from "effect/TxRef". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TRef.TRef.Variance` -> `none`: TxRef exposes no public variance marker. + +- `TRef.TRefTypeId` -> `TxRef.isTxRef`: The type id is internal in v4; use the public runtime guard. + +- `TRef.get` -> `TxRef.get`: Import TxRef from "effect/TxRef"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TRef.getAndUpdateSome` -> `TxRef.modify`: Use one atomic modify and keep the old value when the partial update returns None. + +- `TRef.make` -> `TxRef.make`: Import TxRef from "effect/TxRef"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TRef.modifySome` -> `TxRef.modify`: Use one atomic modify and return the fallback result when the partial function returns None. + +- `TRef.setAndGet` -> `TxRef.modify`: Use one atomic modify that returns and stores the new value. + +- `TRef.updateSome` -> `TxRef.modify`: Use one atomic modify and retain the old value when the partial update returns None. + +- `TRef.updateSomeAndGet` -> `TxRef.modify`: Use one atomic modify that returns the resulting value, retaining the old value for None. + +### `effect/TSemaphore` + +- `TSemaphore.TSemaphore` -> `TxSemaphore.TxSemaphore`: Rename the type and import from "effect/TxSemaphore". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSemaphore.TSemaphore.Proto` -> `none`: The public prototype interface was removed. + +- `TSemaphore.TSemaphoreTypeId` -> `TxSemaphore.isTxSemaphore`: The type id is internal in v4; use the public runtime guard. + +- `TSemaphore.available` -> `TxSemaphore.available`: Import TxSemaphore from "effect/TxSemaphore"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSemaphore.make` -> `TxSemaphore.make`: Import TxSemaphore from "effect/TxSemaphore"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSemaphore.release` -> `TxSemaphore.release`: Import TxSemaphore from "effect/TxSemaphore"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSemaphore.unsafeMake` -> `none`: The unsafe constructor was removed; use TxSemaphore.make. + +- `TSemaphore.withPermit` -> `TxSemaphore.withPermit`: The helper remains, but data-first calls now pass the semaphore before the Effect. + +- `TSemaphore.withPermits` -> `TxSemaphore.withPermits`: The helper remains, but data-first calls now pass semaphore, permit count, then Effect. + +- `TSemaphore.withPermitsScoped` -> `TxSemaphore.acquireN + Effect.addFinalizer(TxSemaphore.releaseN)`: No scoped multi-permit helper remains; acquire and register release explicitly in a Scope. + +### `effect/TSet` + +- `TSet.TSet` -> `TxHashSet.TxHashSet`: Rename the type and import from "effect/TxHashSet". V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.TSet.Variance` -> `none`: TxHashSet exposes no public variance marker. + +- `TSet.TSetTypeId` -> `TxHashSet.isTxHashSet`: The type id is internal in v4; use the public runtime guard. + +- `TSet.difference` -> `TxHashSet.difference`: The name remains, but v4 returns a new set instead of mutating self. + +- `TSet.empty` -> `TxHashSet.empty`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.fromIterable` -> `TxHashSet.fromIterable`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.has` -> `TxHashSet.has`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.intersection` -> `TxHashSet.intersection`: The name remains, but v4 returns a new set instead of mutating self. + +- `TSet.isEmpty` -> `TxHashSet.isEmpty`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.make` -> `TxHashSet.make`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.reduce` -> `TxHashSet.reduce`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.reduceSTM` -> `TxHashSet.toHashSet + Effect.reduce`: Snapshot the set and reduce effectfully inside the surrounding Effect.tx transaction. + +- `TSet.remove` -> `TxHashSet.remove`: The name remains, but v4 returns whether the value existed. + +- `TSet.removeAll` -> `Effect.forEach(values, (value) => TxHashSet.remove(self, value))`: No bulk removal helper remains; remove each value inside one outer Effect.tx transaction. + +- `TSet.size` -> `TxHashSet.size`: Import TxHashSet from "effect/TxHashSet"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSet.takeFirst` -> `none`: No atomic take-and-match helper exists in TxHashSet; select and remove explicitly inside Effect.tx. + +- `TSet.takeFirstSTM` -> `none`: No effectful atomic take-and-match helper exists; traverse and remove explicitly inside Effect.tx. + +- `TSet.takeSome` -> `none`: No atomic multi-take helper exists in TxHashSet; select and remove explicitly inside Effect.tx. + +- `TSet.takeSomeSTM` -> `none`: No effectful atomic multi-take helper exists; traverse and remove explicitly inside Effect.tx. + +- `TSet.toArray` -> `Effect.map(TxHashSet.toHashSet(self), Array.from)`: Convert the immutable HashSet snapshot to an Array explicitly. + +- `TSet.toChunk` -> `Effect.map(TxHashSet.toHashSet(self), (set) => Chunk.fromIterable(set))`: Convert the immutable HashSet snapshot to Chunk explicitly. + +- `TSet.toReadonlySet` -> `Effect.map(TxHashSet.toHashSet(self), (set) => new Set(set))`: Convert the immutable HashSet snapshot to a JavaScript ReadonlySet explicitly. + +- `TSet.transform` -> `TxHashSet.map`: The closest helper returns a new set instead of mutating self. + +- `TSet.transformSTM` -> `TxHashSet.toHashSet + Effect traversal + TxHashSet.fromIterable`: No effectful transform remains; traverse a snapshot and rebuild inside Effect.tx. + +- `TSet.union` -> `TxHashSet.union`: The name remains, but v4 returns a new set instead of mutating self. + +### `effect/TSubscriptionRef` + +- `TSubscriptionRef.TSubscriptionRef` -> `TxSubscriptionRef.TxSubscriptionRef`: Rename the type; it no longer extends TxRef. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSubscriptionRef.TSubscriptionRef.Variance` -> `none`: TxSubscriptionRef exposes no public variance marker. + +- `TSubscriptionRef.TSubscriptionRefTypeId` -> `TxSubscriptionRef.isTxSubscriptionRef`: The type id is internal in v4; use the public runtime guard. + +- `TSubscriptionRef.changes` -> `none`: The old unscoped transactional subscription was removed; use scoped TxSubscriptionRef.changes. + +- `TSubscriptionRef.changesScoped` -> `TxSubscriptionRef.changes`: The scoped changes operation lost its Scoped suffix and returns a scoped TxQueue. + +- `TSubscriptionRef.get` -> `TxSubscriptionRef.get`: Import TxSubscriptionRef from "effect/TxSubscriptionRef"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSubscriptionRef.getAndUpdateSome` -> `TxSubscriptionRef.modify`: Use one atomic modify so successful updates are still published; retain the old value for None. + +- `TSubscriptionRef.make` -> `TxSubscriptionRef.make`: Import TxSubscriptionRef from "effect/TxSubscriptionRef"; the operation keeps its name. V4 Tx operations return ordinary Effects; compose multiple operations under one outer Effect.tx to keep them atomic. + +- `TSubscriptionRef.modifySome` -> `TxSubscriptionRef.modify`: Use one atomic modify so successful updates are still published; use the fallback result for None. + +- `TSubscriptionRef.setAndGet` -> `TxSubscriptionRef.modify`: Use one atomic modify that publishes and returns the newly stored value. + +- `TSubscriptionRef.updateSome` -> `TxSubscriptionRef.modify`: Use one atomic modify so updates are published, retaining the old value for None. + +- `TSubscriptionRef.updateSomeAndGet` -> `TxSubscriptionRef.modify`: Use one atomic modify that publishes and returns the resulting value, retaining the old value for None. + +### `effect/Take` + +- `Take.Take` -> `Take.Take`: v4 Take is the plain union NonEmptyReadonlyArray\ | Exit.Exit\ — no wrapper object or Pipeable: a value batch is a non-empty array, a failure is a failed Exit, and end-of-stream is a successful Exit carrying the Done value (void by default). The module keeps the effect/Take path but exports only the type and toPull. + +- `Take.Take.Variance` -> `none`: Variance plumbing removed; v4 Take is a plain union type with no branded interface, so there is nothing to migrate to. + +- `Take.TakeTypeId` -> `none`: No brand symbol in v4; discriminate the union with Exit.isExit(take) (Exit branch) vs the non-empty array branch (Array.isReadonlyArrayNonEmpty). + +- `Take.chunk` -> `NonEmptyReadonlyArray`: No constructor needed: a value-batch Take is just the non-empty array of values itself (convert a v3 Chunk with Array.fromIterable); empty batches are not representable and must be skipped. + +- `Take.dieMessage` -> `Exit.die(new Error(message))`: A defect Take is a died Exit; wrap the message in an Error yourself since there is no dedicated dieMessage helper. + +- `Take.done` -> `Take.toPull`: Take.toPull(take) converts a Take into a Pull (Effect succeeding with the batch); end-of-stream surfaces as Cause.Done in the error channel instead of v3's Option.none, and failures keep their cause. + +- `Take.fail` -> `Exit.fail`: A failing Take is simply the failed Exit: Exit.fail(error). + +- `Take.failCause` -> `Exit.failCause`: A failing Take with a full cause is simply Exit.failCause(cause). + +#### `Take.fromEffect` + +**Replacement:** `Effect.exit + Exit.isSuccess` + +Run the effect with Effect.exit and convert the result: a successful exit value a becomes the single-element batch [a], a failed exit is used directly as the Take. + +**Example** + +```ts +Effect.map(Effect.exit(effect), (exit) => Exit.isSuccess(exit) ? [exit.value] as const : exit) +``` + +- `Take.fromExit` -> `Exit.isSuccess(exit) ? [exit.value] : exit`: A success exit becomes the single-element batch [a]; a failure exit is already a valid v4 Take and is used as-is. + +#### `Take.fromPull` + +**Replacement:** `Effect.matchCause + Pull.doneExitFromCause` + +Convert one v4 Pull step into a Take: the success batch is the Take itself, and Pull.doneExitFromCause turns the failure cause into the Exit branch (Cause.Done becomes a successful end Exit, real failures become a failed Exit). + +**Example** + +```ts +Effect.matchCause(pull, { onSuccess: (arr) => arr, onFailure: Pull.doneExitFromCause }) +``` + +- `Take.isDone` -> `Exit.isExit(take) && Exit.isSuccess(take)`: End-of-stream is the successful-Exit branch of the union. + +- `Take.isFailure` -> `Exit.isExit(take) && Exit.isFailure(take)`: A failure Take is the failed-Exit branch of the union. + +- `Take.isSuccess` -> `!Exit.isExit(take)`: A value batch is the non-Exit branch; use Array.isReadonlyArrayNonEmpty(take) when a positive refinement to NonEmptyReadonlyArray\ is needed. + +- `Take.make` -> `none`: No wrapper constructor: build the union value directly — a non-empty array for values, Exit.fail/Exit.failCause for errors, Exit.succeed(done) (or Exit.void) for end-of-stream; the v3 Exit\, Option\\> encoding is gone. + +- `Take.map` -> `Exit.isExit(take) ? take : Array.map(take, f)`: Only the value batch is mapped; effect's Array.map preserves the NonEmptyReadonlyArray type, and Exit branches (failure/end) pass through unchanged. + +#### `Take.match` + +**Replacement:** `Exit.isExit + Exit.match` + +Branch on the union: the array branch is v3's onSuccess(chunk), and Exit.match splits the Exit branch into onFailure(cause) and end-of-stream (v3 onEnd, success value = Done). + +**Example** + +```ts +// v3: Take.match(take, { onEnd, onFailure, onSuccess }) +Exit.isExit(take) + ? Exit.match(take, { onSuccess: () => onEnd(), onFailure: (cause) => onFailure(cause) }) + : onSuccess(take) + +``` + +- `Take.matchEffect` -> `Pull.matchEffect(Take.toPull(take), { onSuccess, onFailure, onDone })`: Convert with Take.toPull and fold with Pull.matchEffect: onSuccess receives the batch (v3 onSuccess), onFailure the cause, onDone the completion value (v3 onEnd); alternatively branch manually with Exit.isExit as for match. + +- `Take.of` -> `[value]`: A single-value Take is just the one-element non-empty array literal. + +- `Take.tap` -> `Exit.isExit(take) ? Exit.asVoid(take) : Effect.asVoid(f(take))`: Peek at the value batch with f; Exit branches pass through as effects (a failed Exit re-propagates its cause, an end Exit becomes a void success), matching v3 tap semantics. + +### `effect/TestClock` + +- `TestClock.Data` -> `TestClock.TestClock.State`: The nearest state model is State, with timestamp and a private latch-based sleep queue. V4 exposes no full state getter or setter. + +- `TestClock.adjustWith` -> `Effect.zipWith(effect, TestClock.adjust(duration), (result) => result, { concurrent: true })`: V4 removed adjustWith. Run the tested effect and clock adjustment concurrently and retain the tested effect's result. + +- `TestClock.currentTimeMillis` -> `Clock.currentTimeMillis`: Read time from the active Clock reference; under it.effect or TestClock.layer() this is virtual time. + +- `TestClock.defaultTestClock` -> `TestClock.layer()`: The v4 layer creates an epoch-based test clock and captures the surrounding live Clock automatically; it no longer needs TestAnnotations or TestLive. + +- `TestClock.makeData` -> `TestClock.layer() + TestClock.setTime(instant)`: State injection was removed. Build the layer, then set initial time; seeded pending sleeps cannot migrate because the queue is private. + +- `TestClock.save` -> `none`: Full clock snapshots including pending sleeps are no longer public. For timestamp-only restoration, read Clock.currentTimeMillis and later call TestClock.setTime(savedMillis). + +- `TestClock.sleeps` -> `none`: The pending-sleep queue is private. Test observable behavior by forking sleepers, adjusting time, and joining or asserting the fibers. + +- `TestClock.testClock` -> `TestClock.testClockWith(Effect.succeed)`: V4 exposes callback-based access to the active test clock; use testClockWith directly when possible. + +### `effect/TestConfig` + +- `TestConfig.TestConfig` -> `none`: There is no v4 TestConfig service. Move runner settings to Vitest and native Arbitrary check options, or define an application-specific Context.Reference if runtime access is needed. + +- `TestConfig.make` -> `{ repeats, retries, samples, shrinks }`: The v3 constructor only returned its parameter object. The TestConfig service was removed; keep a plain object only for application-owned configuration. + +### `effect/TestContext` + +- `TestContext.LiveContext` -> `@effect/vitest#live`: Default runtime references are live in v4. Use it.live for a whole live test; no LiveContext layer is required. + +- `TestContext.TestContext` -> `Layer.mergeAll(TestConsole.layer, TestClock.layer())`: This is the v4 test layer used by @effect/vitest. Prefer it.effect, which provides it automatically. + +### `effect/TestLive` + +- `TestLive.TestLive` -> `none`: There is no grouped live-default-services object. Use Context.Context plus Effect.provideContext, TestClock.withLive for live time, or it.live for the whole test. + +- `TestLive.TestLiveTypeId` -> `none`: The TestLive nominal wrapper was removed, so its type id has no replacement. + +- `TestLive.make` -> `Effect.provideContext`: The wrapper was removed. Apply a captured Context directly with Effect.provideContext; for live time inside it.effect, prefer TestClock.withLive. + +### `effect/TestServices` + +- `TestServices.TestServices` -> `TestClock.TestClock | TestConsole.TestConsole`: This is the v4 @effect/vitest test-environment union. it.effect provides both automatically; it.live provides neither override. + +- `TestServices.annotate` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.annotations` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.annotationsLayer` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.annotationsWith` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.currentServices` -> `Effect.context()`: The separate FiberRef\\> was removed. Test services now live in the ordinary Effect Context; override individual references with Effect.provideService. + +- `TestServices.get` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.liveLayer` -> `none`: The standalone TestLive service and layer were removed. TestClock.layer() captures its surrounding live Clock itself. + +- `TestServices.liveServices` -> `none`: There is no prebuilt aggregate test-service Context. @effect/vitest constructs TestClock and TestConsole layers per test; live references are defaults. + +- `TestServices.liveWith` -> `TestClock.withLive`: There is no TestLive callback object. Refactor to the effect ultimately run and apply TestClock.withLive, or use it.live for whole-test live execution. + +- `TestServices.provideLive` -> `TestClock.withLive`: For live time, run the effect with the Clock captured by TestClock.layer(). Use it.live when the entire test should omit all test-service overrides. + +- `TestServices.provideWithLive` -> `TestClock.testClockWith + TestClock.withLive + Effect.provideService`: To retain test time for the inner effect while its transformer uses live time, combine testClockWith, withLive, and provideService. Other v3 default services have no aggregate equivalent. + +- `TestServices.repeats` -> `Vitest TestOptions.repeats`: Configure repeats in the Vitest options passed to it.effect or it.live; it is no longer an Effect service value. + +- `TestServices.retries` -> `Vitest TestOptions.retry`: Configure retry in Vitest test options. To retry an Effect inside a test, use Effect.retry. + +- `TestServices.samples` -> `{ arbitrary: { runs } }`: Pass the run count through @effect/vitest property-test options, for example it.effect.prop(..., { arbitrary: { runs: samples } }). + +- `TestServices.shrinks` -> `{ arbitrary: { maxShrinks } }`: Pass maxShrinks through @effect/vitest property-test options, for example it.effect.prop(..., { arbitrary: { maxShrinks } }). + +- `TestServices.size` -> `CurrentSize`: Define a custom Context.Reference\ and yield it to read the current size. + +- `TestServices.sized` -> `CurrentSize`: TestSized was removed. Use a custom Context.Reference\ directly instead of a wrapper object. + +- `TestServices.sizedLayer` -> `Layer.succeed(CurrentSize, size)`: Provide the custom size reference as a layer. + +- `TestServices.sizedWith` -> `CurrentSize.use`: Use the custom reference's callback, or preferably yield CurrentSize in Effect.gen. + +- `TestServices.supervisedFibers` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.testConfig` -> `none`: The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference. + +- `TestServices.testConfigLayer` -> `none`: The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference. + +- `TestServices.testConfigWith` -> `none`: The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference. + +- `TestServices.withAnnotations` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.withAnnotationsScoped` -> `none`: The annotation service was removed. Use Vitest metadata/options for runner concerns, an ordinary Ref or Context.Reference for application-owned test state, and FiberSet for explicit fiber tracking. + +- `TestServices.withLiveScoped` -> `none`: There is no scoped TestLive service override. Apply TestClock.withLive to a specific effect, or choose it.live at test declaration time. + +- `TestServices.withSize` -> `Effect.provideService(effect, CurrentSize, size)`: Provide a custom size Context.Reference for the duration of the wrapped effect. + +- `TestServices.withSized` -> `Effect.provideService(effect, CurrentSize, size)`: Collapse the old TestSized wrapper to its numeric value and provide the custom reference. + +- `TestServices.withSizedScoped` -> `Effect.updateServiceScoped(CurrentSize, () => size)`: For a scope-bounded override use updateServiceScoped; otherwise prefer wrapping the workflow with Effect.provideService. + +- `TestServices.withTestConfig` -> `none`: The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference. + +- `TestServices.withTestConfigScoped` -> `none`: The runner no longer reads an Effect TestConfig service. Use Vitest TestOptions and property-test arbitrary options; model application state as a custom Context.Reference. + +### `effect/TestSized` + +- `TestSized.TestSized` -> `Context.Reference`: Collapse the wrapper service to the reference itself; yield the reference to read the current size. + +- `TestSized.TestSizedTypeId` -> `none`: The wrapper's nominal type id is unnecessary; Context.Reference supplies stable key identity. + +- `TestSized.fromFiberRef` -> `Context.Reference`: FiberRef and TestSized were removed. Define one stable Context.Reference\ instead of wrapping a FiberRef. + +- `TestSized.make` -> `Context.Reference`: Define a module-level reference with defaultValue; do not create a fresh key at each call site. + +### `effect/Tracer` + +- `Tracer.DisablePropagation` -> `Tracer.DisablePropagation`: Keep the reference value. The separate phantom interface is gone; the Context.Reference directly stores boolean. + +- `Tracer.ExternalSpan` -> `Tracer.ExternalSpan`: Keep the type, but rename the context field to annotations. Apply the same rename to Tracer.externalSpan options. + +- `Tracer.ParentSpan` -> `Tracer.ParentSpan`: Keep the API. It is now a Context.Service class for AnySpan rather than a separate phantom interface plus Context.Tag. + +- `Tracer.Span` -> `Tracer.Span`: Keep the type and rename span.context to span.annotations; the other public fields and methods remain. + +- `Tracer.SpanLink` -> `Tracer.SpanLink`: Keep the type but remove the \_tag property; v4 links contain only span and attributes. + +- `Tracer.SpanOptions` -> `Tracer.SpanOptions`: Keep the type and rename context to annotations. V4 splits trace options and additionally accepts sampled and level. + +- `Tracer.Tracer` -> `Tracer.Tracer`: The service is now a defaulted Context.Reference. Custom implementations are structural and receive one span options object; context is optional and now receives an Effect primitive plus Fiber. + +- `Tracer.TracerTypeId` -> `none`: Tracer implementations are structural and no longer carry a public type-id brand. + +- `Tracer.tracerWith` -> `Tracer.Tracer.use`: Replace tracerWith(f) with Tracer.Tracer.use(f); do not use TracerKey, which is only the raw string key. + +### `effect/Trie` + +- `Trie.TypeId` -> `none`: The Trie brand is private and there is no public Trie runtime guard; use Trie.Trie\ in type positions. + +- `Trie.unsafeGet` -> `Trie.getUnsafe`: Direct word-order rename; it still throws for a missing key. + +### `effect/Tuple` + +- `Tuple.TupleTypeLambda` -> `none`: Removed with tuple Bicovariant support; use the concrete tuple type or a local HKT TypeLambda. + +- `Tuple.at` -> `Tuple.get`: Renamed for indexed access; v4 constrains the index to a valid tuple position. + +- `Tuple.getEquivalence` -> `Tuple.makeEquivalence`: Pass equivalences as one array instead of variadic arguments. + +- `Tuple.getFirst` -> `Tuple.get(0)`: Use Tuple.get(self, 0), or Tuple.get(0) in a pipe. + +- `Tuple.getOrder` -> `Tuple.makeOrder`: Pass orders as one array instead of variadic arguments. + +- `Tuple.getSecond` -> `Tuple.get(1)`: Use Tuple.get(self, 1), or Tuple.get(1) in a pipe. + +- `Tuple.mapBoth` -> `Tuple.evolve`: Use Tuple.evolve(self, [options.onFirst, options.onSecond]). + +- `Tuple.mapFirst` -> `Tuple.evolve`: Use Tuple.evolve(self, [f]); unspecified positions are preserved. + +- `Tuple.mapSecond` -> `Tuple.evolve`: Use Tuple.evolve(self, [undefined, f]); undefined preserves the first position. + +- `Tuple.swap` -> `Tuple.renameIndices`: Swap a pair with Tuple.renameIndices(self, ["1", "0"]). + +### `effect/Types` + +- `Types.Concurrency` -> `Types.Concurrency`: Still exported, but v4 removes inherit; replace it with an explicit number or unbounded. + +- `Types.Contravariant` -> `Types.Contravariant`: Unchanged contravariant type helper. + +- `Types.Covariant` -> `Types.Covariant`: Unchanged covariant type helper. + +- `Types.Ctor` -> `new (...args: Array) => T`: The named alias was removed; inline the construct signature or define a local alias. + +- `Types.Invariant` -> `Types.Invariant`: Unchanged invariant type helper. + +- `Types.MatchRecord` -> `{} extends S ? onTrue : onFalse`: The alias was removed; inline its conditional because Types.VoidIfEmpty has different optional-record behavior. + +- `Types.MergeRecord` -> `Types.MergeLeft`: MergeRecord was an alias for the retained left-biased MergeLeft helper. + +- `Types.NoExcessProperties` -> `Types.NoExcessProperties`: Retained with equivalent excess-key checking. + +### `effect/Utils` + +- `Utils.Adapter` -> `none`: The generator-adapter type was removed; type generator bodies to yield v4 yieldable values directly. + +- `Utils.Gen` -> `Utils.Gen`: Still exported; drop the adapter type and resume parameter, then yield yieldable Kind values directly. + +- `Utils.GenKind` -> `none`: The adapter wrapper was removed; custom yieldable Kinds should implement Symbol.iterator and return Utils.SingleShotGen. + +- `Utils.GenKindImpl` -> `none`: The wrapper implementation was removed; implement direct yieldability with Symbol.iterator and Utils.SingleShotGen. + +- `Utils.GenKindTypeId` -> `none`: The GenKind runtime marker was removed with the wrapper infrastructure. + +- `Utils.OptionalNumber` -> `number | null | undefined`: The unused named alias was removed; inline its union. + +- `Utils.PCGRandom` -> `Random.withSeed + Random.next / Random.nextIntBetween`: Use the effectful Random service for seeded generation; v4 is not PCG-compatible. + +- `Utils.PCGRandomState` -> `none`: No public PCG state snapshot or restore API remains; Random.withSeed is reproducible but not state-compatible. + +- `Utils.SingleShotGen` -> `Utils.SingleShotGen`: Still exported; v4 removes its concrete return and throw methods, so do not call those optional iterator hooks. + +- `Utils.Variance` -> `Utils.Variance`: Still exported; remove the v3 GenKindTypeId marker from implementations. + +- `Utils.YieldWrap` -> `none`: The internal generator transport wrapper was removed; yieldable values are yielded directly. + +- `Utils.YieldWrapTypeId` -> `none`: The internal wrapper marker was removed with YieldWrap. + +- `Utils.adapter` -> `none`: Remove the adapter and resume parameter; v4 generators yield yieldable values directly. + +- `Utils.internalCall` -> `none`: This was internal and has no public replacement; application code should invoke its thunk directly. + +- `Utils.isGenKind` -> `none`: Removed with GenKind; v4 generator drivers consume directly yielded values. + +- `Utils.isGeneratorFunction` -> `none`: The unused constructor-identity predicate was removed; accept an explicit generator contract instead. + +- `Utils.makeGenKind` -> `none`: The wrapper constructor was removed; make custom Kinds yieldable with Symbol.iterator and Utils.SingleShotGen. + +- `Utils.structuralRegion` -> `none`: Remove the wrapper because v4 Equal.equals is structural by default; use a custom Equivalence for custom comparison. + +- `Utils.structuralRegionState` -> `none`: The mutable test hook was removed; v4 equality is structural by default. + +- `Utils.yieldWrapGet` -> `none`: The internal unwrapper was removed; generator drivers read the directly yielded value. + +### `effect/index` + +- `index.Context` -> `Context`: Keep importing Context from effect; v4 removes declaration merges that made tags and references STM subtypes. + +- `index.Effect` -> `Effect`: Keep importing Effect from effect; v4 removes declaration merges that made Effects structural Sink, Stream, and Channel subtypes. + +- `index.Either` -> `Result`: Either was renamed to Result; Right and Left became Success and Failure, with Result.succeed and Result.fail constructors. + +- `index.Option` -> `Option`: Keep importing Option from effect; Option is no longer an Effect or STM subtype, so use Effect.fromOption when needed. diff --git a/package.json b/package.json index d7cd5b113..e51910a17 100644 --- a/package.json +++ b/package.json @@ -1,106 +1,83 @@ { "private": true, "type": "module", - "packageManager": "pnpm@10.17.1", + "packageManager": "pnpm@11.20.0", "scripts": { + "prepare": "node scripts/setup-agents.mjs && effect-tsgo patch", "clean": "node scripts/clean.mjs", "codegen": "pnpm --recursive --parallel --filter \"./packages/**/*\" run codegen", "codemod": "node scripts/codemod.mjs", - "build": "tspc -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build", - "build:tsgo": "tsgo -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build:tsgo", + "build": "tsc -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build && node scripts/copy-ai-docs.mjs", "bundle-analyze": "bash scripts/bundle-analyze.sh", "bundle-compare": "bash scripts/bundle-compare.sh", "bundle-compare-selected": "bash scripts/bundle-compare-selected.sh", "circular": "node scripts/circular.mjs", "test": "vitest", + "test-cluster": "vitest run --project cluster-integration", + "doctest": "vitest --config vitest.docs.ts", "coverage": "vitest --coverage", - "check": "tspc -b tsconfig.json", - "check:tsgo": "tsgo -b tsconfig.json", + "check": "tsc -b tsconfig.json", "typeperf": "pnpm --dir packages/effect exec node typeperf/run.mjs", - "check-recursive": "pnpm --recursive --filter \"./packages/**/*\" exec tspc -b tsconfig.json", + "typeperf-compare": "pnpm --dir packages/effect exec node typeperf/compare.mjs", + "runtimeperf": "pnpm --dir packages/effect exec node runtimeperf/run.mts", + "runtimeperf-compare": "pnpm --dir packages/effect exec node runtimeperf/compare.mts", + "check-recursive": "pnpm --recursive --filter \"./packages/**/*\" exec tsc -b tsconfig.json", "jsdocs": "effect-jsdocs", - "lint": "pnpm jsdocs && oxlint -f unix && dprint check", - "lint-fix": "pnpm jsdocs && oxlint --fix && dprint fmt", - "docgen": "pnpm --recursive --filter \"./packages/**/*\" exec docgen && node scripts/docs.mjs", + "lint": "oxlint -f unix && dprint check", + "lint-fix": "oxlint --fix && dprint fmt", "ai-docgen": "effect-ai-docgen ai-docs/src -o LLMS.md", "ai-docgen:watch": "pnpm ai-docgen --watch", - "test-types": "tstyche", + "api-diff": "pnpm --dir packages/tools/api-diff exec node src/bin.ts", + "test-types": "tstyche --target '>=5.9'", "changeset-version": "changeset version", - "changeset-publish": "pnpm codemod && pnpm build && changeset publish", - "prepare": "effect-tsgo patch" + "changeset-publish": "node scripts/set-strip-internal.mjs && pnpm codemod && pnpm build && changeset publish" }, "devDependencies": { - "@babel/cli": "^7.28.6", - "@babel/core": "^7.29.0", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@changesets/changelog-github": "^0.7.0", - "@changesets/cli": "^2.31.0", + "@babel/cli": "^8.0.4", + "@babel/core": "^8.0.1", + "@babel/plugin-transform-export-namespace-from": "^8.0.1", + "@babel/plugin-transform-modules-commonjs": "^8.0.1", + "@changesets/changelog-github": "1.0.1", + "@changesets/cli": "3.0.2", "@effect/ai-docgen": "workspace:^", "@effect/bundle": "workspace:^", - "@effect/docgen": "https://pkg.pr.new/Effect-TS/docgen/@effect/docgen@e57e5f5", + "@effect/docgen": "workspace:^", + "@effect/doctest": "workspace:^", "@effect/jsdocs": "workspace:^", - "@effect/language-service": "^0.85.1", "@effect/oxc": "workspace:^", - "@effect/tsgo": "^0.7.2", + "@effect/tsgo": "^0.41.0", "@effect/utils": "workspace:^", "@effect/vitest": "workspace:^", - "@faker-js/faker": "^10.4.0", + "@faker-js/faker": "^10.6.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^6.0.3", "@rollup/plugin-terser": "^1.0.0", "@types/jscodeshift": "^17.3.0", - "@types/node": "^25.7.0", - "@typescript/native-preview": "7.0.0-dev.20260512.1", - "@vitest/browser": "4.1.6", - "@vitest/coverage-v8": "4.1.6", - "@vitest/expect": "4.1.6", - "@vitest/web-worker": "4.1.6", - "ast-types": "^0.14.2", + "@types/node": "^26.4.1", + "@vitest/coverage-v8": "^5.0.0", + "@vitest/web-worker": "^5.0.0", + "ast-types": "^0.16.3", "babel-plugin-annotate-pure-calls": "^0.5.0", - "dprint": "^0.54.0", + "dprint": "^0.57.3", + "fast-check": "^4.9.0", "glob": "^13.0.6", - "happy-dom": "^20.9.0", - "jscodeshift": "^17.3.0", - "lalph": "^0.3.137", + "happy-dom": "^20.14.0", + "jscodeshift": "^17.4.0", + "lalph": "^0.3.139", "madge": "^8.0.0", - "oxlint": "1.42.0", - "playwright": "^1.60.0", - "rollup": "^4.60.3", - "rollup-plugin-bundle-stats": "^4.22.1", + "oxlint": "^1.81.0", + "pkg-pr-new": "0.0.88", + "playwright": "^1.63.0", + "rollup": "^4.63.1", + "rollup-plugin-bundle-stats": "^4.22.3", "rollup-plugin-esbuild": "^6.2.1", - "rollup-plugin-visualizer": "^7.0.1", - "terser": "^5.47.1", - "ts-patch": "^4.0.1", - "tstyche": "^7.1.0", - "typescript": "^6.0.3", - "vite": "^7.3.2", - "vite-tsconfig-paths": "^6.1.1", - "vitest": "4.1.4", - "vitest-websocket-mock": "^0.5.0", - "zod": "^4.4.3" - }, - "pnpm": { - "patchedDependencies": { - "@changesets/assemble-release-plan": "patches/@changesets__assemble-release-plan.patch", - "@changesets/get-github-info": "patches/@changesets__get-github-info.patch" - }, - "ignoredBuiltDependencies": [ - "core-js", - "cpu-features", - "dprint", - "esbuild", - "lmdb", - "msgpackr-extract", - "msw", - "protobufjs", - "sharp", - "ssh2", - "unrs-resolver", - "workerd" - ], - "onlyBuiltDependencies": [ - "better-sqlite3" - ] + "rollup-plugin-visualizer": "^7.1.1", + "terser": "^5.51.2", + "tstyche": "^7.2.4", + "typescript": "^7.0.2", + "vite": "^8.2.2", + "vitest": "^5.0.0", + "vitest-websocket-mock": "^0.7.0", + "zod": "^4.5.4" } } diff --git a/packages/ai/anthropic/CHANGELOG.md b/packages/ai/anthropic/CHANGELOG.md index b75b6cb70..d1b848563 100644 --- a/packages/ai/anthropic/CHANGELOG.md +++ b/packages/ai/anthropic/CHANGELOG.md @@ -1,5 +1,192 @@ # @effect/ai-anthropic +## 4.0.0-rc.112 + +### Patch Changes + +- [#7437](https://github.com/Effect-TS/effect/pull/7437) [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb) Thanks @wmaurer! - Add an optional `description` to `AiError.AuthenticationError`, rendered after the kind-based suggestion, and pass the provider's own error text through it on HTTP 401 and 403, so authentication failures report what actually went wrong instead of only a category. +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- [#7208](https://github.com/Effect-TS/effect/pull/7208) [`0303139`](https://github.com/Effect-TS/effect/commit/03031395d3ddee197217f826e7d9ef68b0674823) Thanks @moishinetzer! - Default new Anthropic models to modern capabilities while preserving the limits of legacy Claude models. + + Unknown models now default to native structured outputs and 128K output tokens, so future model releases do not require capability-table updates. Use the new `structuredOutputs` model config option to override capability detection when needed. +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7129](https://github.com/Effect-TS/effect/pull/7129) [`d0baed9`](https://github.com/Effect-TS/effect/commit/d0baed9d72c5191d5ab3945ffe464b9515d0ac53) Thanks @mkdynamic! - Correct the maximum output tokens for Claude Opus 4.6, 4.7, 4.8 and Sonnet 4.6. + + These models were grouped with the 4.5 family at 64000 output tokens, half of the 128000 the API actually allows, so requests defaulted to a cap far below the model's real limit. The 4.5 models keep 64000, which is correct for them. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- [#7070](https://github.com/Effect-TS/effect/pull/7070) [`a5404d4`](https://github.com/Effect-TS/effect/commit/a5404d4f4361350b6ecbb1a2f601365852141f60) Thanks @fubhy! - Decode byte-backed plain-text attachments as UTF-8 text in Anthropic requests. +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6918](https://github.com/Effect-TS/effect/pull/6918) [`a913136`](https://github.com/Effect-TS/effect/commit/a9131368c1347ad4c409c9df80245d765e96a4fe) Thanks @fubhy! - Fix malformed JSON in streamed Anthropic code-execution tool parameters. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6650](https://github.com/Effect-TS/effect/pull/6650) [`acd385e`](https://github.com/Effect-TS/effect/commit/acd385ebb3f9edee37ab6715607119ee9762a615) Thanks @IMax153! - Redact the Anthropic API key from client error context. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- [#6371](https://github.com/Effect-TS/effect/pull/6371) [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd) Thanks @polRk! - Fix client-executed provider tools (Memory, Text Editor, Computer Use, Bash) which were unusable on the wire. + - `makeResponse` (and the streaming equivalents) now map a provider `tool_use` wire name (e.g. `"memory"`) back to the tool's custom name (e.g. `"AnthropicMemory"`) that the toolkit is keyed by, instead of raising `ToolNotFoundError`. + - `AnthropicTool.MemoryCreateCommand` now includes the required `file_text` field, so a `create` command no longer drops the file body. + - Optional parameters on client-executed provider tools now use `Schema.optionalKey` instead of `Schema.optional`, which the Anthropic codec rejected with "Unsupported AST Undefined": `Memory`/`TextEditor` `view_range`, `ComputerUse` `coordinate`, and `Bash` `restart`. + + Closes [#2615](https://github.com/Effect-TS/effect/issues/2615). + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- [#2568](https://github.com/Effect-TS/effect-smol/pull/2568) [`97a4138`](https://github.com/Effect-TS/effect-smol/commit/97a4138f9e9d31d38026f935b52d423f5ccadc4f) Thanks @K-Mistele! - Widen the Anthropic `Model` schema to accept both known model identifiers as well + as any string to allow for newer models +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- [#2531](https://github.com/Effect-TS/effect-smol/pull/2531) [`bdca354`](https://github.com/Effect-TS/effect-smol/commit/bdca35449d5dfce5b4433da75ec0a88d0a9b2b27) Thanks @polRk! - Fix `Memory_20250818` provider-defined tool missing `requiresHandler: true`. Like the other client-executed tools (`TextEditor_20250728`, `Bash_2025*`, `ComputerUse_2025*`), the memory tool requires the application to implement its execution (view/create/str_replace/insert/delete/rename over `/memories/*`). Without this flag, `Tool.HandlersFor` excluded it from the required handlers, making it impossible to type-check a handler for `Memory_20250818` in `Toolkit.toLayer`. + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/ai/anthropic/README.md b/packages/ai/anthropic/README.md new file mode 100644 index 000000000..da2336285 --- /dev/null +++ b/packages/ai/anthropic/README.md @@ -0,0 +1,14 @@ +# @effect/ai-anthropic + +An [Anthropic](https://www.anthropic.com) provider for the Effect AI modules. Includes a typed Anthropic API client, language model layers, tools, and telemetry helpers. + +## Installation + +```sh +npm install effect@rc @effect/ai-anthropic@rc +``` + +## Documentation + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/ai-anthropic) diff --git a/packages/ai/anthropic/codegen.yml b/packages/ai/anthropic/codegen.yml index a39a97db8..e112fc77a 100644 --- a/packages/ai/anthropic/codegen.yml +++ b/packages/ai/anthropic/codegen.yml @@ -5,8 +5,6 @@ spec: output: src/Generated.ts name: AnthropicClient patches: - # Remove string from Model schema - only keep literal model IDs - - '[{"op":"remove","path":"/components/schemas/Model/anyOf/0"}]' # Make iterations and speed optional in BetaUsage, iterations optional in BetaMessageDeltaUsage (API may omit these fields) - '[{"op":"remove","path":"/components/schemas/BetaUsage/required/9"},{"op":"remove","path":"/components/schemas/BetaUsage/required/5"},{"op":"remove","path":"/components/schemas/BetaMessageDeltaUsage/required/3"}]' header: | @@ -15,6 +13,7 @@ header: | */ excludeAnnotations: - examples +disableAdditionalProperties: true replacements: # Schema.Unknown doesn't work with Schema.toCodecJson (used by HttpClientResponse.schemaBodyJson) # Replace with Schema.Json which properly handles arbitrary JSON values @@ -26,38 +25,38 @@ replacements: - from: "readonly [x: string]: unknown" to: "readonly [x: string]: Schema.Json" # Make citations optional in ResponseTextBlock (API may omit this field) - - from: "export type ResponseTextBlock = { readonly \"citations\":" - to: "export type ResponseTextBlock = { readonly \"citations\"?:" + - from: 'export type ResponseTextBlock = { readonly "citations":' + to: 'export type ResponseTextBlock = { readonly "citations"?:' - from: "export const ResponseTextBlock = Schema.Struct({ \"citations\": Schema.Union([Schema.Array(Schema.Union([ResponseCharLocationCitation, ResponsePageLocationCitation, ResponseContentBlockLocationCitation, ResponseWebSearchResultLocationCitation, ResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null }), \"text\":" to: "export const ResponseTextBlock = Schema.Struct({ \"citations\": Schema.optionalKey(Schema.Union([Schema.Array(Schema.Union([ResponseCharLocationCitation, ResponsePageLocationCitation, ResponseContentBlockLocationCitation, ResponseWebSearchResultLocationCitation, ResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null })), \"text\":" # Make citations optional in BetaResponseTextBlock (API may omit this field) - - from: "export type BetaResponseTextBlock = { readonly \"citations\":" - to: "export type BetaResponseTextBlock = { readonly \"citations\"?:" + - from: 'export type BetaResponseTextBlock = { readonly "citations":' + to: 'export type BetaResponseTextBlock = { readonly "citations"?:' - from: "export const BetaResponseTextBlock = Schema.Struct({ \"citations\": Schema.Union([Schema.Array(Schema.Union([BetaResponseCharLocationCitation, BetaResponsePageLocationCitation, BetaResponseContentBlockLocationCitation, BetaResponseWebSearchResultLocationCitation, BetaResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null }), \"text\":" to: "export const BetaResponseTextBlock = Schema.Struct({ \"citations\": Schema.optionalKey(Schema.Union([Schema.Array(Schema.Union([BetaResponseCharLocationCitation, BetaResponsePageLocationCitation, BetaResponseContentBlockLocationCitation, BetaResponseWebSearchResultLocationCitation, BetaResponseSearchResultLocationCitation], { mode: \"oneOf\" })), Schema.Null]).annotate({ \"title\": \"Citations\", \"description\": \"Citations supporting the text block.\\n\\nThe type of citation returned will depend on the type of document being cited. Citing a PDF results in `page_location`, plain text results in `char_location`, and content document results in `content_block_location`.\", \"default\": null })), \"text\":" # Make server_tool_use optional in usage (API may omit this field) - - from: "readonly \"server_tool_use\": ServerToolUsage | null" - to: "readonly \"server_tool_use\"?: ServerToolUsage | null" - - from: "readonly \"server_tool_use\": BetaServerToolUsage | null" - to: "readonly \"server_tool_use\"?: BetaServerToolUsage | null" - - from: "\"server_tool_use\": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })," - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }))," - - from: "\"server_tool_use\": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })," - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }))," + - from: 'readonly "server_tool_use": ServerToolUsage | null' + to: 'readonly "server_tool_use"?: ServerToolUsage | null' + - from: 'readonly "server_tool_use": BetaServerToolUsage | null' + to: 'readonly "server_tool_use"?: BetaServerToolUsage | null' + - from: '"server_tool_use": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }),' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })),' + - from: '"server_tool_use": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }),' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })),' # Without trailing comma (for last field in struct) - - from: "\"server_tool_use\": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }) })" - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })) })" - - from: "\"server_tool_use\": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null }) })" - to: "\"server_tool_use\": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ \"description\": \"The number of server tool requests.\", \"default\": null })) })" + - from: '"server_tool_use": Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }) })' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([BetaServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })) })' + - from: '"server_tool_use": Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null }) })' + to: '"server_tool_use": Schema.optionalKey(Schema.Union([ServerToolUsage, Schema.Null]).annotate({ "description": "The number of server tool requests.", "default": null })) })' # Make context_management and container optional in BetaMessage (API may omit these fields) - - from: "readonly \"context_management\": BetaResponseContextManagement | null" - to: "readonly \"context_management\"?: BetaResponseContextManagement | null" - - from: "readonly \"container\": BetaContainer | null" - to: "readonly \"container\"?: BetaContainer | null" + - from: 'readonly "context_management": BetaResponseContextManagement | null' + to: 'readonly "context_management"?: BetaResponseContextManagement | null' + - from: 'readonly "container": BetaContainer | null' + to: 'readonly "container"?: BetaContainer | null' - from: "\"context_management\": Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Context management response.\\n\\nInformation about context management strategies applied during the request.\", \"default\": null })," to: "\"context_management\": Schema.optionalKey(Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Context management response.\\n\\nInformation about context management strategies applied during the request.\", \"default\": null }))," - from: "\"container\": Schema.Union([BetaContainer, Schema.Null]).annotate({ \"description\": \"Information about the container used in this request.\\n\\nThis will be non-null if a container tool (e.g. code execution) was used.\", \"default\": null })" to: "\"container\": Schema.optionalKey(Schema.Union([BetaContainer, Schema.Null]).annotate({ \"description\": \"Information about the container used in this request.\\n\\nThis will be non-null if a container tool (e.g. code execution) was used.\", \"default\": null }))" # Make context_management optional in BetaMessageDeltaEvent (API may omit this field) - - from: "\"context_management\": Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Information about context management strategies applied during the request\", \"default\": null })," - to: "\"context_management\": Schema.optionalKey(Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ \"description\": \"Information about context management strategies applied during the request\", \"default\": null }))," \ No newline at end of file + - from: '"context_management": Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ "description": "Information about context management strategies applied during the request", "default": null }),' + to: '"context_management": Schema.optionalKey(Schema.Union([BetaResponseContextManagement, Schema.Null]).annotate({ "description": "Information about context management strategies applied during the request", "default": null })),' diff --git a/packages/ai/anthropic/docgen.json b/packages/ai/anthropic/docgen.json deleted file mode 100644 index bc0548cd3..000000000 --- a/packages/ai/anthropic/docgen.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/anthropic/src/", - "exclude": ["src/internal/**/*.ts", "src/Generated.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "exactOptionalPropertyTypes": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "types": ["node"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/ai/anthropic/package.json b/packages/ai/anthropic/package.json index f1bf19556..6e60982cb 100644 --- a/packages/ai/anthropic/package.json +++ b/packages/ai/anthropic/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-anthropic", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "An Anthropic provider integration for Effect AI SDK", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/anthropic" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/packages/ai/anthropic/src/AnthropicClient.ts b/packages/ai/anthropic/src/AnthropicClient.ts index 646feadc9..93288554f 100644 --- a/packages/ai/anthropic/src/AnthropicClient.ts +++ b/packages/ai/anthropic/src/AnthropicClient.ts @@ -36,7 +36,7 @@ import * as Errors from "./internal/errors.ts" * Represents the Anthropic client service with methods for the Messages API, including regular and streaming message * creation. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { @@ -52,7 +52,7 @@ export interface Service { schema: S ) => (request: HttpClientRequest.HttpClientRequest) => Stream.Stream< S["Type"], - HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry, + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, S["DecodingServices"] > @@ -194,6 +194,11 @@ const RedactedAnthropicHeaders = { AnthropicApiKey: "x-api-key" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedAnthropicHeaders)) +) + /** * Creates an Anthropic client service with the given options. * @@ -254,7 +259,7 @@ export const make = Effect.fnUntraced( (schema: S) => (request: HttpClientRequest.HttpClientRequest): Stream.Stream< S["Type"], - HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry, + HttpClientError.HttpClientError | Schema.SchemaError | Sse.Retry | Sse.SseError, S["DecodingServices"] > => httpClientOk.execute(request).pipe( @@ -276,7 +281,8 @@ export const make = Effect.fnUntraced( BetaMessagesPost4XX: (error) => Effect.fail(Errors.mapClientError(error, "createMessage")), HttpClientError: (error) => Errors.mapHttpClientError(error, "createMessage"), SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createMessage")) - }) + }), + withRedactedHeaders ) const PingEvent = Schema.Struct({ @@ -306,6 +312,7 @@ export const make = Effect.fnUntraced( Stream.catchTags({ // TODO: handle SSE retries Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createMessageStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createMessageStream")), SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createMessageStream")) }) @@ -329,7 +336,8 @@ export const make = Effect.fnUntraced( Effect.catchTag( "HttpClientError", (error) => Errors.mapHttpClientError(error, "createMessageStream") - ) + ), + withRedactedHeaders ) } @@ -340,10 +348,7 @@ export const make = Effect.fnUntraced( createMessageStream }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedAnthropicHeaders)) - ) + withRedactedHeaders ) // ============================================================================= diff --git a/packages/ai/anthropic/src/AnthropicConfig.ts b/packages/ai/anthropic/src/AnthropicConfig.ts index 26e3b21ad..f4578de36 100644 --- a/packages/ai/anthropic/src/AnthropicConfig.ts +++ b/packages/ai/anthropic/src/AnthropicConfig.ts @@ -35,7 +35,7 @@ export class AnthropicConfig extends Context.Service< */ static readonly getOrUndefined: Effect.Effect = Effect.map( Effect.context(), - (services) => services.mapUnsafe.get(AnthropicConfig.key) + Context.getOrUndefined(AnthropicConfig) ) } @@ -52,7 +52,7 @@ export declare namespace AnthropicConfig { * * Use `transformClient` to wrap or replace the `HttpClient` used by generated Anthropic API requests. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/packages/ai/anthropic/src/AnthropicError.ts b/packages/ai/anthropic/src/AnthropicError.ts index d7a521907..590176e2f 100644 --- a/packages/ai/anthropic/src/AnthropicError.ts +++ b/packages/ai/anthropic/src/AnthropicError.ts @@ -77,7 +77,7 @@ declare module "effect/unstable/ai/AiError" { * * Includes request identifiers, Anthropic error types, and parsed request or token limit headers when the provider rejects a request due to rate limits. * - * @category configuration + * @category models * @since 4.0.0 */ export interface RateLimitErrorMetadata { @@ -91,7 +91,7 @@ declare module "effect/unstable/ai/AiError" { * * Captures the Anthropic error type and request identifier for failures where the account or workspace has exhausted its available quota. * - * @category configuration + * @category models * @since 4.0.0 */ export interface QuotaExhaustedErrorMetadata { @@ -105,7 +105,7 @@ declare module "effect/unstable/ai/AiError" { * * Preserves Anthropic error details for missing, invalid, or unauthorized API credentials while keeping the error in the shared AI error model. * - * @category configuration + * @category models * @since 4.0.0 */ export interface AuthenticationErrorMetadata { @@ -119,7 +119,7 @@ declare module "effect/unstable/ai/AiError" { * * Records Anthropic error details returned when a request or response is rejected by Anthropic safety or content policy enforcement. * - * @category configuration + * @category models * @since 4.0.0 */ export interface ContentPolicyErrorMetadata { @@ -133,7 +133,7 @@ declare module "effect/unstable/ai/AiError" { * * Provides the Anthropic error type and request identifier for malformed or unsupported requests rejected before model execution. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidRequestErrorMetadata { @@ -147,7 +147,7 @@ declare module "effect/unstable/ai/AiError" { * * Preserves Anthropic request correlation data for provider-side failures that should be reported or investigated with Anthropic support. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InternalProviderErrorMetadata { @@ -161,7 +161,7 @@ declare module "effect/unstable/ai/AiError" { * * Describes Anthropic-specific context for responses that could not be decoded or interpreted as valid AI output. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidOutputErrorMetadata { @@ -175,7 +175,7 @@ declare module "effect/unstable/ai/AiError" { * * Captures Anthropic error details for structured-output failures, including request correlation data useful when diagnosing schema-related responses. * - * @category configuration + * @category models * @since 4.0.0 */ export interface StructuredOutputErrorMetadata { @@ -189,7 +189,7 @@ declare module "effect/unstable/ai/AiError" { * * Provides Anthropic error details for schemas that cannot be represented by or submitted to the Anthropic API. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnsupportedSchemaErrorMetadata { @@ -203,7 +203,7 @@ declare module "effect/unstable/ai/AiError" { * * Retains the Anthropic error type and request identifier when a provider response cannot be classified as a more specific AI error. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnknownErrorMetadata { diff --git a/packages/ai/anthropic/src/AnthropicLanguageModel.ts b/packages/ai/anthropic/src/AnthropicLanguageModel.ts index 516fae12a..a0eae898c 100644 --- a/packages/ai/anthropic/src/AnthropicLanguageModel.ts +++ b/packages/ai/anthropic/src/AnthropicLanguageModel.ts @@ -42,16 +42,10 @@ import * as InternalUtilities from "./internal/utilities.ts" /** * Known Anthropic Claude model identifiers exposed by the generated Anthropic schema. * - * **Details** - * - * The Anthropic language model constructors accept `Model` values and custom - * string model ids, so this type is best used for autocomplete and type checking - * of known Claude ids. - * * @category models * @since 4.0.0 */ -export type Model = typeof Generated.Model.Type +export type Model = (typeof Generated.Model)["members"][1]["Encoded"] // ============================================================================= // Configuration @@ -71,7 +65,7 @@ export type Model = typeof Generated.Model.Type * requests. Scoped configuration overrides defaults supplied to `model`, * `make`, or `layer`. * - * @category configuration + * @category services * @since 4.0.0 */ export class Config extends Context.Service< @@ -91,6 +85,12 @@ export class Config extends Context.Service< * Disables Claude's ability to use multiple tools to respond to a query. */ readonly disableParallelToolCalls?: boolean | undefined + /** + * Whether the model supports native structured outputs. + * + * Overrides automatic capability detection based on the model identifier. + */ + readonly structuredOutputs?: boolean | undefined /** * Whether to use strict JSON schema validation for tool calls. * @@ -117,7 +117,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating system messages into Anthropic * request content. * - * @category request + * @category models * @since 4.0.0 */ export interface SystemMessageOptions extends ProviderOptions { @@ -137,7 +137,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating user messages into Anthropic * request content. * - * @category request + * @category models * @since 4.0.0 */ export interface UserMessageOptions extends ProviderOptions { @@ -157,7 +157,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when replaying assistant messages in Anthropic * conversation history. * - * @category request + * @category models * @since 4.0.0 */ export interface AssistantMessageOptions extends ProviderOptions { @@ -177,7 +177,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when converting tool results into Anthropic user * content blocks. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolMessageOptions extends ProviderOptions { @@ -196,7 +196,7 @@ declare module "effect/unstable/ai/Prompt" { * * Use when you use these options to control how text blocks are sent to Anthropic. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -216,7 +216,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves Claude thinking metadata when reasoning content is sent back to * Anthropic in later turns. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -251,7 +251,7 @@ declare module "effect/unstable/ai/Prompt" { * Controls document metadata, citations, and prompt caching for files sent to * Anthropic. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -289,7 +289,7 @@ declare module "effect/unstable/ai/Prompt" { * Carries Anthropic tool caller metadata, MCP metadata, and cache control for * tool use blocks. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -319,13 +319,23 @@ declare module "effect/unstable/ai/Prompt" { * * **Details** * - * Controls Anthropic prompt caching for tool result content. + * Carries Anthropic MCP metadata and controls prompt caching for tool result + * content. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { readonly anthropic?: { + /** + * Contains details about the MCP tool that produced the result. + */ + readonly mcp_tool?: { + /** + * The name of the MCP server + */ + readonly server: string + } | null /** * A breakpoint which marks the end of reusable content eligible for caching. */ @@ -340,7 +350,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls prompt caching for human approval requests in conversations. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolApprovalRequestPartOptions extends ProviderOptions { @@ -359,7 +369,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls prompt caching for human approval responses in conversations. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolApprovalResponsePartOptions extends ProviderOptions { @@ -381,7 +391,7 @@ declare module "effect/unstable/ai/Response" { * Includes Claude thinking metadata needed to continue reasoning-aware * conversations. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -411,7 +421,7 @@ declare module "effect/unstable/ai/Response" { * * Includes the signature for streamed Claude thinking content when available. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -434,7 +444,7 @@ declare module "effect/unstable/ai/Response" { * * Preserves Claude thinking or redacted thinking information for later turns. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -465,7 +475,7 @@ declare module "effect/unstable/ai/Response" { * Identifies Anthropic caller details and MCP tool metadata emitted by the * provider. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -494,7 +504,7 @@ declare module "effect/unstable/ai/Response" { * Identifies MCP tool metadata associated with provider-executed tool * results. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolResultPartMetadata extends ProviderMetadata { @@ -518,7 +528,7 @@ declare module "effect/unstable/ai/Response" { * * Records the cited document span by character position or page number. * - * @category response + * @category models * @since 4.0.0 */ export interface DocumentSourcePartMetadata extends ProviderMetadata { @@ -562,7 +572,7 @@ declare module "effect/unstable/ai/Response" { * * Records cited URL text or web-search source freshness information. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -592,7 +602,7 @@ declare module "effect/unstable/ai/Response" { * Includes container state, context management information, stop details, and * token usage reported by Anthropic. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -611,7 +621,7 @@ declare module "effect/unstable/ai/Response" { * * Includes the provider request identifier when Anthropic returns one. * - * @category response + * @category models * @since 4.0.0 */ export interface ErrorPartMetadata extends ProviderMetadata { @@ -671,10 +681,13 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }): Effect.fn.Return { const client = yield* AnthropicClient - const makeConfig: Effect.Effect = Effect.gen(function*() { - const services = yield* Effect.context() - return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) } - }) + const makeConfig: Effect.Effect = Effect.contextWith((services) => + Effect.succeed({ + model, + ...providerConfig, + ...Context.getOrUndefined(services, Config) + }) + ) const makeRequest = Effect.fnUntraced( function*>({ config, options, toolNameMapper }: { @@ -686,7 +699,10 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig readonly payload: typeof Generated.BetaCreateMessageParams.Encoded }, AiError.AiError> { const betas = new Set() - const capabilities = getModelCapabilities(config.model!) + const modelCapabilities = getModelCapabilities(config.model!) + const capabilities = Predicate.isNotUndefined(config.structuredOutputs) + ? { ...modelCapabilities, supportsStructuredOutput: config.structuredOutputs } + : modelCapabilities const { messages, system } = yield* prepareMessages({ betas, options, toolNameMapper }) const outputFormat = yield* getOutputFormat({ capabilities, options }) const { tools, toolChoice } = yield* prepareTools({ betas, capabilities, config, options }) @@ -694,7 +710,13 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig if (betas.size > 0) { params["anthropic-beta"] = Array.from(betas).join(",") } - const { disableParallelToolCalls: _, output_config, ...requestConfig } = config + const { + disableParallelToolCalls: _, + output_config, + strictJsonSchema: _strictJsonSchema, + structuredOutputs: _structuredOutputs, + ...requestConfig + } = config const payload: Mutable = { ...requestConfig, max_tokens: requestConfig.max_tokens ?? capabilities.maxOutputTokens, @@ -872,7 +894,13 @@ const prepareMessages = Effect.fnUntraced( const source = isUrlData(part.data) ? { type: "url", url: getUrlString(part.data) } as const - : { type: "base64", media_type: mediaType, data: Encoding.encodeBase64(part.data) } as const + : { + type: "base64", + media_type: mediaType, + data: typeof part.data === "string" + ? part.data.replace(/^data:[^;]+;base64,/, "") + : Encoding.encodeBase64(part.data) + } as const content.push({ type: "image", source, cache_control: cacheControl }) } else if (part.mediaType === "application/pdf" || part.mediaType === "text/plain") { @@ -895,7 +923,7 @@ const prepareMessages = Effect.fnUntraced( : { type: "text", media_type: "text/plain", - data: typeof part.data === "string" ? part.data : Encoding.encodeBase64(part.data) + data: typeof part.data === "string" ? part.data : new TextDecoder().decode(part.data) } as const content.push({ @@ -945,7 +973,7 @@ const prepareMessages = Effect.fnUntraced( content.push({ type: "tool_result", tool_use_id: part.id, - content: JSON.stringify(part.result), + content: typeof part.result === "string" ? part.result : JSON.stringify(part.result), is_error: part.isFailure, cache_control: cacheControl }) @@ -1270,10 +1298,6 @@ const prepareTools = Effect.fnUntraced( readonly tools: ReadonlyArray | undefined readonly toolChoice: typeof Generated.BetaToolChoice.Encoded | undefined }, AiError.AiError> { - if (options.tools.length === 0 || options.toolChoice === "none") { - return { tools: undefined, toolChoice: undefined } - } - // Return a JSON response tool when using non-native structured outputs if (options.responseFormat.type === "json" && !capabilities.supportsStructuredOutput) { const input_schema = yield* tryJsonSchema(options.responseFormat.schema, "prepareTools") @@ -1293,6 +1317,10 @@ const prepareTools = Effect.fnUntraced( } } + if (options.tools.length === 0 || options.toolChoice === "none") { + return { tools: undefined, toolChoice: undefined } + } + const userTools: Array = [] const providerTools: Array = [] @@ -1547,6 +1575,9 @@ const makeResponse = Effect.fnUntraced( const mcpToolCalls: Map = new Map() const serverToolCalls: Map = new Map() const citableDocuments = extractCitableDocuments(options.prompt) + const responseFormat = options.responseFormat + const hasStructuredOutputTool = responseFormat.type === "json" && + rawResponse.content.some((part) => part.type === "tool_use" && part.name === responseFormat.objectName) parts.push({ type: "response-metadata", @@ -1559,10 +1590,12 @@ const makeResponse = Effect.fnUntraced( for (const part of rawResponse.content) { switch (part.type) { case "text": { - // Text parts are added for both text and json response formats. - // For native structured output (json_schema), the JSON comes directly - // in a text content block. For tool-based structured output, text may - // also be present alongside the tool_use. + // The response tool supplies the JSON payload. Accompanying prose + // must not be concatenated with it during structured output decoding. + if (hasStructuredOutputTool) { + break + } + parts.push({ type: "text", text: part.text @@ -1609,7 +1642,7 @@ const makeResponse = Effect.fnUntraced( case "tool_use": { // When the `"json"` response format is requested, the JSON we need // is returned by a tool call injected into the request - if (options.responseFormat.type === "json") { + if (responseFormat.type === "json" && part.name === responseFormat.objectName) { parts.push({ type: "text", text: JSON.stringify(part.input) @@ -1624,12 +1657,15 @@ const makeResponse = Effect.fnUntraced( } : undefined - const params = yield* transformToolCallParams(options.tools, part.name, part.input) + // Map the provider wire name (e.g. "memory") back to the tool's + // custom name (e.g. "AnthropicMemory") that the toolkit is keyed by + const toolName = toolNameMapper.getCustomName(part.name) + const params = yield* transformToolCallParams(options.tools, toolName, part.input) parts.push({ type: "tool-call", id: part.id, - name: part.name, + name: toolName, params, ...(Predicate.isNotUndefined(callerInfo) ? { metadata: { anthropic: { caller: callerInfo } } } @@ -2042,10 +2078,15 @@ const makeStreamResponse = Effect.fnUntraced( } : undefined + // Map the provider wire name (e.g. "memory") back to the + // tool's custom name (e.g. "AnthropicMemory") that the toolkit + // is keyed by + const toolName = toolNameMapper.getCustomName(part.name) + parts.push({ type: "tool-params-start", id: part.id, - name: part.name + name: toolName }) parts.push({ @@ -2059,12 +2100,12 @@ const makeStreamResponse = Effect.fnUntraced( id: part.id }) - const params = yield* transformToolCallParams(options.tools, part.name, part.input) + const params = yield* transformToolCallParams(options.tools, toolName, part.input) parts.push({ type: "tool-call", id: part.id, - name: part.name, + name: toolName, params, ...(Predicate.isNotUndefined(callerInfo) ? { metadata: { anthropic: { caller: callerInfo } } } @@ -2212,10 +2253,15 @@ const makeStreamResponse = Effect.fnUntraced( const hasParams = Object.keys(part.input).length > 0 const initialParams = hasParams ? JSON.stringify(part.input) : "" + // Map the provider wire name (e.g. "memory") back to the tool's + // custom name (e.g. "AnthropicMemory") that the toolkit is keyed + // by. The mapped name flows to the finalized tool-call via + // `contentBlock.name` on `content_block_stop`. + const toolName = toolNameMapper.getCustomName(part.name) contentBlocks.set(event.index, { type: "tool-call", id: part.id, - name: part.name, + name: toolName, params: initialParams, firstDelta: initialParams.length > 0, ...(Predicate.isNotUndefined(caller) ? { caller } : undefined) @@ -2224,7 +2270,7 @@ const makeStreamResponse = Effect.fnUntraced( parts.push({ type: "tool-params-start", id: part.id, - name: part.name + name: toolName }) break @@ -2568,7 +2614,7 @@ const makeStreamResponse = Effect.fnUntraced( (contentBlock.providerName === "bash_code_execution" || contentBlock.providerName === "text_editor_code_execution") ) { - delta = `{"type":${contentBlock.providerName},${delta.substring(1)}}` + delta = `{"type":${JSON.stringify(contentBlock.providerName)},${delta.substring(1)}` } parts.push({ @@ -2643,7 +2689,7 @@ const makeStreamResponse = Effect.fnUntraced( } const params = contentBlock.providerExecuted === true - ? finalParams + ? Tool.unsafeSecureJsonParse(finalParams) : yield* transformToolCallParams( options.tools, contentBlock.name, @@ -2953,11 +2999,11 @@ const processCitation = Effect.fnUntraced( interface ModelCapabilities { readonly maxOutputTokens: number readonly supportsStructuredOutput: boolean - readonly isKnownModel: boolean } /** * Returns the capabilities of a Claude model that are used for defaults and feature selection. + * Legacy models are listed as exceptions so newly released models inherit modern defaults. * * @see https://docs.claude.com/en/docs/about-claude/models/overview#model-comparison-table * @see https://platform.claude.com/docs/en/build-with-claude/structured-outputs @@ -2966,55 +3012,48 @@ const getModelCapabilities = (modelId: string): ModelCapabilities => { if ( modelId.includes("claude-sonnet-4-5") || modelId.includes("claude-opus-4-5") || - modelId.includes("claude-haiku-4-5") || - modelId.includes("claude-opus-4-6") || - modelId.includes("claude-sonnet-4-6") || - modelId.includes("claude-opus-4-7") || - modelId.includes("claude-opus-4-8") + modelId.includes("claude-haiku-4-5") ) { return { maxOutputTokens: 64000, - supportsStructuredOutput: true, - isKnownModel: true + supportsStructuredOutput: true } } else if (modelId.includes("claude-opus-4-1")) { return { maxOutputTokens: 32000, - supportsStructuredOutput: true, - isKnownModel: true + supportsStructuredOutput: true } } else if ( - modelId.includes("claude-sonnet-4-") || + modelId.includes("claude-sonnet-4-0") || + modelId.includes("claude-sonnet-4-20250514") || modelId.includes("claude-3-7-sonnet") ) { return { maxOutputTokens: 64000, - supportsStructuredOutput: false, - isKnownModel: true + supportsStructuredOutput: false } - } else if (modelId.includes("claude-opus-4-")) { + } else if ( + modelId.includes("claude-opus-4-0") || + modelId.includes("claude-opus-4-20250514") + ) { return { maxOutputTokens: 32000, - supportsStructuredOutput: false, - isKnownModel: true + supportsStructuredOutput: false } } else if (modelId.includes("claude-3-5-haiku")) { return { maxOutputTokens: 8192, - supportsStructuredOutput: false, - isKnownModel: true + supportsStructuredOutput: false } - } else if (modelId.includes("claude-3-haiku")) { + } else if (modelId.includes("claude-3-")) { return { maxOutputTokens: 4096, - supportsStructuredOutput: false, - isKnownModel: true + supportsStructuredOutput: false } } else { return { - maxOutputTokens: 4096, - supportsStructuredOutput: false, - isKnownModel: false + maxOutputTokens: 128000, + supportsStructuredOutput: true } } } @@ -3080,19 +3119,13 @@ const transformToolCallParams = Effect.fnUntraced(function* - ).pipe(Effect.mapError((error) => - AiError.make({ - module: "AnthropicLanguageModel", - method: "makeResponse", - reason: new AiError.ToolParameterValidationError({ - toolName, - toolParams, - description: error.issue.toString() - }) - }) - )) + Schema.decodeEffect(codec)(toolParams) as Effect.Effect + ).pipe( + Effect.flatMap((decoded) => + Schema.encodeUnknownEffect(tool.parametersSchema)(decoded) as Effect.Effect + ), + Effect.orElseSucceed(() => toolParams) + ) }) diff --git a/packages/ai/anthropic/src/AnthropicTelemetry.ts b/packages/ai/anthropic/src/AnthropicTelemetry.ts index d100551a8..ce99b637b 100644 --- a/packages/ai/anthropic/src/AnthropicTelemetry.ts +++ b/packages/ai/anthropic/src/AnthropicTelemetry.ts @@ -113,7 +113,7 @@ const addAnthropicResponseAttributes = Telemetry.addSpanAttributes("gen_ai.anthr * * This method mutates the `Span` in place. * - * @category annotations + * @category tracing * @since 4.0.0 */ export const addGenAIAnnotations: { diff --git a/packages/ai/anthropic/src/AnthropicTool.ts b/packages/ai/anthropic/src/AnthropicTool.ts index b62314da6..e0f975120 100644 --- a/packages/ai/anthropic/src/AnthropicTool.ts +++ b/packages/ai/anthropic/src/AnthropicTool.ts @@ -65,7 +65,7 @@ export type AnthropicTool = * * @see {@link Bash_20250124} for the newer 2025-01-24 version of the bash tool * - * @category Bash + * @category tools * @since 4.0.0 */ export const Bash_20241022 = Tool.providerDefined({ @@ -76,7 +76,7 @@ export const Bash_20241022 = Tool.providerDefined({ success: Schema.String, parameters: Schema.Struct({ command: Schema.String, - restart: Schema.optional(Schema.Boolean) + restart: Schema.optionalKey(Schema.Boolean) }) }) @@ -95,7 +95,7 @@ export const Bash_20241022 = Tool.providerDefined({ * * @see {@link Bash_20241022} for the older 2024-10-22 version of the bash tool * - * @category Bash + * @category tools * @since 4.0.0 */ export const Bash_20250124 = Tool.providerDefined({ @@ -106,7 +106,7 @@ export const Bash_20250124 = Tool.providerDefined({ success: Schema.String, parameters: Schema.Struct({ command: Schema.String, - restart: Schema.optional(Schema.Boolean) + restart: Schema.optionalKey(Schema.Boolean) }) }) @@ -128,7 +128,7 @@ export const Bash_20250124 = Tool.providerDefined({ * * @see {@link CodeExecution_20250522} for the parent tool definition * - * @category Code Execution + * @category schemas * @since 4.0.0 */ export const CodeExecutionProgrammaticToolCall = Schema.Struct({ @@ -141,7 +141,7 @@ export const CodeExecutionProgrammaticToolCall = Schema.Struct({ /** * Input payload for a programmatic code execution tool call, including the source code to execute. * - * @category Code Execution + * @category models * @since 4.0.0 */ export type CodeExecutionProgrammaticToolCall = typeof CodeExecutionProgrammaticToolCall.Type @@ -161,7 +161,7 @@ export type CodeExecutionProgrammaticToolCall = typeof CodeExecutionProgrammatic * * @see {@link CodeExecution_20250522} for the provider-defined tool that consumes this input variant * - * @category Code Execution + * @category schemas * @since 4.0.0 */ export const CodeExecutionBashCommand = Schema.Struct({ @@ -191,7 +191,7 @@ export const CodeExecutionBashCommand = Schema.Struct({ * @see {@link CodeExecutionTextEditorStrReplace} for replacing text through text editor code execution * @see {@link CodeExecution_20250522} for the provider-defined tool that consumes this payload * - * @category Code Execution + * @category models * @since 4.0.0 */ export type CodeExecutionBashCommand = typeof CodeExecutionBashCommand.Type @@ -212,7 +212,7 @@ export type CodeExecutionBashCommand = typeof CodeExecutionBashCommand.Type * @see {@link CodeExecutionTextEditorCreate} for the command that creates a file * @see {@link CodeExecutionTextEditorStrReplace} for the command that replaces text in a file * - * @category Code Execution + * @category schemas * @since 4.0.0 */ export const CodeExecutionTextEditorView = Schema.Struct({ @@ -245,7 +245,7 @@ export const CodeExecutionTextEditorView = Schema.Struct({ * @see {@link CodeExecution_20250522} for the provider-defined code execution tool that includes this payload * @see {@link TextEditorViewCommand} for the standalone text editor view payload * - * @category Code Execution + * @category models * @since 4.0.0 */ export type CodeExecutionTextEditorView = typeof CodeExecutionTextEditorView.Type @@ -268,7 +268,7 @@ export type CodeExecutionTextEditorView = typeof CodeExecutionTextEditorView.Typ * @see {@link CodeExecutionTextEditorView} for the matching view request * @see {@link CodeExecutionTextEditorStrReplace} for the matching replace request * - * @category Code Execution + * @category schemas * @since 4.0.0 */ export const CodeExecutionTextEditorCreate = Schema.Struct({ @@ -286,7 +286,7 @@ export const CodeExecutionTextEditorCreate = Schema.Struct({ /** * Input payload for creating a file through the text editor code execution tool, optionally including initial file text. * - * @category Code Execution + * @category models * @since 4.0.0 */ export type CodeExecutionTextEditorCreate = typeof CodeExecutionTextEditorCreate.Type @@ -307,7 +307,7 @@ export type CodeExecutionTextEditorCreate = typeof CodeExecutionTextEditorCreate * @see {@link CodeExecutionTextEditorView} for reading file contents before choosing the replacement text * @see {@link CodeExecution_20250522} for the provider-defined tool that consumes this payload * - * @category Code Execution + * @category schemas * @since 4.0.0 */ export const CodeExecutionTextEditorStrReplace = Schema.Struct({ @@ -329,7 +329,7 @@ export const CodeExecutionTextEditorStrReplace = Schema.Struct({ /** * Input payload for replacing text in a file through the text editor code execution tool. * - * @category Code Execution + * @category models * @since 4.0.0 */ export type CodeExecutionTextEditorStrReplace = typeof CodeExecutionTextEditorStrReplace.Type @@ -356,7 +356,7 @@ const CodeExecution_20250522_Parameters = Schema.Union([ * * @see {@link CodeExecution_20250825} for the provider-defined tool that consumes this schema * - * @category Code Execution + * @category schemas * @since 4.0.0 */ export const CodeExecution_20250825_Parameters = Schema.Struct({ @@ -380,7 +380,7 @@ export const CodeExecution_20250825_Parameters = Schema.Struct({ * * @see {@link CodeExecution_20250825} for the provider-defined tool that consumes this payload * - * @category Code Execution + * @category models * @since 4.0.0 */ export type CodeExecution_20250825_Parameters = typeof CodeExecution_20250825_Parameters.Type @@ -405,7 +405,7 @@ export type CodeExecution_20250825_Parameters = typeof CodeExecution_20250825_Pa * * @see {@link CodeExecutionProgrammaticToolCall} for the programmatic tool call schema * - * @category Code Execution + * @category tools * @since 4.0.0 */ export const CodeExecution_20250522 = Tool.providerDefined({ @@ -433,7 +433,7 @@ export const CodeExecution_20250522 = Tool.providerDefined({ * @see {@link CodeExecution_20250522} for the older 2025-05-22 code execution tool * @see {@link CodeExecution_20250825_Parameters} for the input schema consumed by this tool * - * @category Code Execution + * @category tools * @since 4.0.0 */ export const CodeExecution_20250825 = Tool.providerDefined({ @@ -480,14 +480,14 @@ export const CodeExecution_20250825 = Tool.providerDefined({ * * This schema validates tuple shape only and does not check display bounds. * - * @category computer use + * @category schemas * @since 4.0.0 */ -export const Coordinate = Schema.Tuple([Schema.Number, Schema.Number]) +export const Coordinate = Schema.Tuple([Schema.Int, Schema.Int]) /** * An `[x, y]` screen coordinate in pixels. * - * @category computer use + * @category models * @since 4.0.0 */ export type Coordinate = typeof Coordinate.Type @@ -509,14 +509,14 @@ export type Coordinate = typeof Coordinate.Type * This schema validates four numbers only and does not check coordinate ordering * or display bounds. * - * @category computer use + * @category schemas * @since 4.0.0 */ -export const Region = Schema.Tuple([Schema.Number, Schema.Number, Schema.Number, Schema.Number]) +export const Region = Schema.Tuple([Schema.Int, Schema.Int, Schema.Int, Schema.Int]) /** * An `[x1, y1, x2, y2]` screen region in pixels, from top-left to bottom-right. * - * @category computer use + * @category models * @since 4.0.0 */ export type Region = typeof Region.Type @@ -526,14 +526,14 @@ export type Region = typeof Region.Type * * @see {@link ComputerUseScrollAction} for the action payload that consumes this schema * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ScrollDirection = Schema.Literals(["up", "down", "left", "right"]) /** * Direction used by computer-use scroll actions: `"up"`, `"down"`, `"left"`, or `"right"`. * - * @category computer use + * @category models * @since 4.0.0 */ export type ScrollDirection = typeof ScrollDirection.Type @@ -545,7 +545,7 @@ export type ScrollDirection = typeof ScrollDirection.Type * * Allowed values are `"alt"`, `"ctrl"`, `"meta"`, and `"shift"`. * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ModifierKey = Schema.Literals(["alt", "ctrl", "meta", "shift"]) @@ -556,7 +556,7 @@ export const ModifierKey = Schema.Literals(["alt", "ctrl", "meta", "shift"]) * * Allowed values are `"alt"`, `"ctrl"`, `"meta"`, and `"shift"`. * - * @category computer use + * @category models * @since 4.0.0 */ export type ModifierKey = typeof ModifierKey.Type @@ -569,19 +569,19 @@ const ComputerUse_20241022_Args = Schema.Struct({ /** * The width of the display being controlled by the model in pixels. */ - displayWidthPx: Schema.Number, + displayWidthPx: Schema.Int, /** * The height of the display being controlled by the model in pixels. */ - displayHeightPx: Schema.Number, + displayHeightPx: Schema.Int, /** * The display number to control (only relevant for X11 environments). If * specified, the tool will be provided a display number in the tool * definition. */ - displayNumber: Schema.optional(Schema.Number) + displayNumber: Schema.optional(Schema.Int) }) const ComputerUse_20251124_Args = Schema.Struct({ @@ -605,7 +605,7 @@ const ComputerUse_20251124_Args = Schema.Struct({ * @see {@link TypeAction} for entering ordinary text strings * @see {@link ComputerUseHoldKeyAction} for holding a key for a duration * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseKeyAction = Schema.Struct({ @@ -634,7 +634,7 @@ export const ComputerUseKeyAction = Schema.Struct({ * `text` is typed as `string`; the paired schema does not validate * provider-specific key names or key combinations. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseKeyAction = typeof ComputerUseKeyAction.Type @@ -662,7 +662,7 @@ export type ComputerUseKeyAction = typeof ComputerUseKeyAction.Type * @see {@link ComputerUseDoubleClickAction} for performing a double click * @see {@link ComputerUseMouseMoveAction} for moving the mouse without clicking * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseLeftClickAction = Schema.Struct({ @@ -671,12 +671,12 @@ export const ComputerUseLeftClickAction = Schema.Struct({ * The `[x, y]` coordinate on the screen to left click (defaults to the current * mouse position if omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for performing a left click, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseLeftClickAction = typeof ComputerUseLeftClickAction.Type @@ -701,7 +701,7 @@ export type ComputerUseLeftClickAction = typeof ComputerUseLeftClickAction.Type * does not validate that the point falls within the configured display * dimensions. * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseMouseMoveAction = Schema.Struct({ @@ -714,7 +714,7 @@ export const ComputerUseMouseMoveAction = Schema.Struct({ /** * Computer-use action payload for moving the mouse cursor to a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseMouseMoveAction = typeof ComputerUseMouseMoveAction.Type @@ -734,7 +734,7 @@ export type ComputerUseMouseMoveAction = typeof ComputerUseMouseMoveAction.Type * * @see {@link ComputerUseZoomAction} for requesting a zoomed-in screenshot of a specific screen region with the 2025-11-24 computer-use tool * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseScreenshotAction = Schema.Struct({ @@ -743,7 +743,7 @@ export const ComputerUseScreenshotAction = Schema.Struct({ /** * Computer-use action payload for capturing the current display. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseScreenshotAction = typeof ComputerUseScreenshotAction.Type @@ -763,7 +763,7 @@ export type ComputerUseScreenshotAction = typeof ComputerUseScreenshotAction.Typ * * @see {@link ComputerUseKeyAction} for key presses and keyboard shortcuts * - * @category computer use + * @category schemas * @since 4.0.0 */ export const TypeAction = Schema.Struct({ @@ -781,7 +781,7 @@ export const TypeAction = Schema.Struct({ * The payload uses `action: "type"` and a `text` string containing the text to * enter. * - * @category computer use + * @category models * @since 4.0.0 */ export type TypeAction = typeof TypeAction.Type @@ -820,7 +820,7 @@ const ComputerUse_20241022_Actions = Schema.Union([ * * @see {@link ComputerUseLeftClickAction} for performing a single left click * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseDoubleClickAction = Schema.Struct({ @@ -829,12 +829,12 @@ export const ComputerUseDoubleClickAction = Schema.Struct({ * The coordinate to double click (defaults to the current mouse position if * omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for performing a double click, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseDoubleClickAction = typeof ComputerUseDoubleClickAction.Type @@ -861,7 +861,7 @@ export type ComputerUseDoubleClickAction = typeof ComputerUseDoubleClickAction.T * @see {@link ComputerUseKeyAction} for pressing a key or key combination without holding it * @see {@link ComputerUseWaitAction} for pausing between actions without holding a key * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseHoldKeyAction = Schema.Struct({ @@ -873,7 +873,7 @@ export const ComputerUseHoldKeyAction = Schema.Struct({ /** * The number of seconds to hold the key. */ - duration: Schema.Number + duration: Schema.Finite }) /** * Computer-use action payload for holding a key for a specified duration. @@ -889,7 +889,7 @@ export const ComputerUseHoldKeyAction = Schema.Struct({ * * @see {@link ComputerUseKeyAction} for a single key press or key combination without a hold duration * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseHoldKeyAction = typeof ComputerUseHoldKeyAction.Type @@ -916,7 +916,7 @@ export type ComputerUseHoldKeyAction = typeof ComputerUseHoldKeyAction.Type * @see {@link ComputerUseLeftMouseDownAction} for starting a manual drag sequence * @see {@link ComputerUseLeftMouseUpAction} for ending a manual drag sequence * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseLeftClickDragAction = Schema.Struct({ @@ -933,7 +933,7 @@ export const ComputerUseLeftClickDragAction = Schema.Struct({ /** * Computer-use action payload for dragging from a start coordinate to an end coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseLeftClickDragAction = typeof ComputerUseLeftClickDragAction.Type @@ -946,7 +946,7 @@ export type ComputerUseLeftClickDragAction = typeof ComputerUseLeftClickDragActi * Use when constructing a manual click or drag sequence that should press and * hold the left mouse button before a later release. * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseLeftMouseDownAction = Schema.Struct({ @@ -955,12 +955,12 @@ export const ComputerUseLeftMouseDownAction = Schema.Struct({ * The coordinate at which the left mouse button should be held down (defaults * to the current mouse position if omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for pressing and holding the left mouse button, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseLeftMouseDownAction = typeof ComputerUseLeftMouseDownAction.Type @@ -973,7 +973,7 @@ export type ComputerUseLeftMouseDownAction = typeof ComputerUseLeftMouseDownActi * Use when constructing a manual click or drag sequence that should release the * left mouse button after it was previously held down. * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseLeftMouseUpAction = Schema.Struct({ @@ -982,12 +982,12 @@ export const ComputerUseLeftMouseUpAction = Schema.Struct({ * The coordinate at which the left mouse button should be released (defaults * to the current mouse position if omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for releasing the left mouse button, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseLeftMouseUpAction = typeof ComputerUseLeftMouseUpAction.Type @@ -1014,7 +1014,7 @@ export type ComputerUseLeftMouseUpAction = typeof ComputerUseLeftMouseUpAction.T * @see {@link ComputerUseLeftClickAction} for primary-button clicks * @see {@link ComputerUseRightClickAction} for secondary-button clicks * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseMiddleClickAction = Schema.Struct({ @@ -1023,12 +1023,12 @@ export const ComputerUseMiddleClickAction = Schema.Struct({ * The coordinate to middle click (defaults to the current mouse position if * omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for performing a middle click, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseMiddleClickAction = typeof ComputerUseMiddleClickAction.Type @@ -1051,7 +1051,7 @@ export type ComputerUseMiddleClickAction = typeof ComputerUseMiddleClickAction.T * @see {@link ComputerUseLeftClickAction} for the corresponding left-click action * @see {@link ComputerUseMiddleClickAction} for the corresponding middle-click action * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseRightClickAction = Schema.Struct({ @@ -1060,12 +1060,12 @@ export const ComputerUseRightClickAction = Schema.Struct({ * The coordinate to right click (defaults to the current mouse position if * omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for performing a right click, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseRightClickAction = typeof ComputerUseRightClickAction.Type @@ -1085,12 +1085,12 @@ export type ComputerUseRightClickAction = typeof ComputerUseRightClickAction.Typ * **Gotchas** * * `coordinate` only checks a two-number tuple, and `scroll_amount` is only - * `Schema.Number`. + * `Schema.Int`. * * @see {@link ComputerUse_20250124} for the tool version that accepts this action * @see {@link ScrollDirection} for the accepted direction literals * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseScrollAction = Schema.Struct({ @@ -1099,7 +1099,7 @@ export const ComputerUseScrollAction = Schema.Struct({ * The coordinate to start scrolling from (defaults to the current mouse * position if omitted). */ - coordinate: Schema.optional(Coordinate), + coordinate: Schema.optionalKey(Coordinate), /** * The direction to scroll. */ @@ -1107,12 +1107,12 @@ export const ComputerUseScrollAction = Schema.Struct({ /** * The amount to scroll (in pixels or scroll units). */ - scroll_amount: Schema.Number + scroll_amount: Schema.Int }) /** * Computer-use action payload for scrolling by a specified amount in a specified direction, optionally from a coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseScrollAction = typeof ComputerUseScrollAction.Type @@ -1139,7 +1139,7 @@ export type ComputerUseScrollAction = typeof ComputerUseScrollAction.Type * @see {@link ComputerUseDoubleClickAction} for the two-click variant * @see {@link ComputerUseLeftClickAction} for a single left click * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseTripleClickAction = Schema.Struct({ @@ -1148,12 +1148,12 @@ export const ComputerUseTripleClickAction = Schema.Struct({ * The coordinate to triple click (defaults to the current mouse position if * omitted). */ - coordinate: Schema.optional(Coordinate) + coordinate: Schema.optionalKey(Coordinate) }) /** * Computer-use action payload for performing a triple click, optionally at a specific coordinate. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseTripleClickAction = typeof ComputerUseTripleClickAction.Type @@ -1173,13 +1173,13 @@ export type ComputerUseTripleClickAction = typeof ComputerUseTripleClickAction.T * * **Gotchas** * - * `duration` is only `Schema.Number`; it is not constrained to positive or - * finite values. + * `duration` is only `Schema.Finite`; it is not constrained to positive + * values. * * @see {@link ComputerUseHoldKeyAction} for another duration-based computer-use action * @see {@link ComputerUse_20250124} for the tool version that accepts this action * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseWaitAction = Schema.Struct({ @@ -1187,12 +1187,12 @@ export const ComputerUseWaitAction = Schema.Struct({ /** * The number of seconds to wait. */ - duration: Schema.Number + duration: Schema.Finite }) /** * Computer-use action payload for pausing for a specified duration. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseWaitAction = typeof ComputerUseWaitAction.Type @@ -1235,7 +1235,7 @@ const ComputerUse_20250124_Actions = Schema.Union([ * @see {@link ComputerUse_20251124} for the tool version that accepts this action * @see {@link ComputerUseScreenshotAction} for capturing the full screen instead * - * @category computer use + * @category schemas * @since 4.0.0 */ export const ComputerUseZoomAction = Schema.Struct({ @@ -1255,7 +1255,7 @@ export const ComputerUseZoomAction = Schema.Struct({ * `region` is only a four-number tuple and does not validate corner ordering or * display bounds. * - * @category computer use + * @category models * @since 4.0.0 */ export type ComputerUseZoomAction = typeof ComputerUseZoomAction.Type @@ -1277,7 +1277,7 @@ const ComputerUse_20251124_Actions = Schema.Union([ * Requires the "computer-use-2024-10-22" beta header. * Basic actions only: screenshot, left_click, type, key, mouse_move. * - * @category computer use + * @category tools * @since 4.0.0 */ export const ComputerUse_20241022 = Tool.providerDefined({ @@ -1308,7 +1308,7 @@ export const ComputerUse_20241022 = Tool.providerDefined({ * @see {@link ComputerUse_20241022} for the older basic action set * @see {@link ComputerUse_20251124} for the newer zoom-capable version * - * @category computer use + * @category tools * @since 4.0.0 */ export const ComputerUse_20250124 = Tool.providerDefined({ @@ -1342,7 +1342,7 @@ export const ComputerUse_20250124 = Tool.providerDefined({ * @see {@link ComputerUse_20250124} for the previous action set without zoom * @see {@link ComputerUseZoomAction} for the zoom action payload * - * @category computer use + * @category tools * @since 4.0.0 */ export const ComputerUse_20251124 = Tool.providerDefined({ @@ -1380,10 +1380,10 @@ export const ComputerUse_20251124 = Tool.providerDefined({ * @see {@link MemoryViewCommand} for memory view payloads that use this range * @see {@link TextEditorViewCommand} for text editor view payloads that use this range * - * @category memory + * @category schemas * @since 4.0.0 */ -export const ViewRange = Schema.Tuple([Schema.Number, Schema.Number]) +export const ViewRange = Schema.Tuple([Schema.Int, Schema.Int]) /** * A `[start, end]` 1-indexed line range for viewing file contents, using `-1` as the end value to read through the end of the file. * @@ -1391,7 +1391,7 @@ export const ViewRange = Schema.Tuple([Schema.Number, Schema.Number]) * * Use when typing `view_range` for memory or text editor view commands. * - * @category memory + * @category models * @since 4.0.0 */ export type ViewRange = typeof ViewRange.Type @@ -1405,9 +1405,10 @@ export type ViewRange = typeof ViewRange.Type * * **Details** * - * The payload contains `command: "create"` and a `path` string. + * The payload contains `command: "create"`, a `path` string, and the + * `file_text` content to write to the file. * - * @category memory + * @category schemas * @since 4.0.0 */ export const MemoryCreateCommand = Schema.Struct({ @@ -1415,12 +1416,16 @@ export const MemoryCreateCommand = Schema.Struct({ /** * The path to the file that should be created. */ - path: Schema.String + path: Schema.String, + /** + * The content to write to the file. + */ + file_text: Schema.String }) /** * Memory tool command payload for creating a new file at a path. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryCreateCommand = typeof MemoryCreateCommand.Type @@ -1428,7 +1433,7 @@ export type MemoryCreateCommand = typeof MemoryCreateCommand.Type /** * Schema for a memory command that deletes a file or directory. * - * @category memory + * @category schemas * @since 4.0.0 */ export const MemoryDeleteCommand = Schema.Struct({ @@ -1441,7 +1446,7 @@ export const MemoryDeleteCommand = Schema.Struct({ /** * Memory tool command payload for deleting a file or directory at a path. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryDeleteCommand = typeof MemoryDeleteCommand.Type @@ -1461,7 +1466,7 @@ export type MemoryDeleteCommand = typeof MemoryDeleteCommand.Type * @see {@link Memory_20250818} for the provider-defined tool that consumes this command * @see {@link MemoryStrReplaceCommand} for replacing existing text instead * - * @category memory + * @category schemas * @since 4.0.0 */ export const MemoryInsertCommand = Schema.Struct({ @@ -1473,7 +1478,7 @@ export const MemoryInsertCommand = Schema.Struct({ /** * The line at which the text should be inserted. */ - insert_line: Schema.Number, + insert_line: Schema.Int, /** * The text to insert. */ @@ -1482,7 +1487,7 @@ export const MemoryInsertCommand = Schema.Struct({ /** * Memory tool command payload for inserting text at a specific line in a file. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryInsertCommand = typeof MemoryInsertCommand.Type @@ -1495,7 +1500,7 @@ export type MemoryInsertCommand = typeof MemoryInsertCommand.Type * The payload uses `command: "rename"` and requires `old_path` as the current * path plus `new_path` as the new destination path. * - * @category memory + * @category schemas * @since 4.0.0 */ export const MemoryRenameCommand = Schema.Struct({ @@ -1512,7 +1517,7 @@ export const MemoryRenameCommand = Schema.Struct({ /** * Memory tool command payload for renaming or moving a file or directory. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryRenameCommand = typeof MemoryRenameCommand.Type @@ -1532,7 +1537,7 @@ export type MemoryRenameCommand = typeof MemoryRenameCommand.Type * * @see {@link Memory_20250818} for the provider-defined tool that consumes this command * - * @category memory + * @category schemas * @since 4.0.0 */ export const MemoryStrReplaceCommand = Schema.Struct({ @@ -1553,7 +1558,7 @@ export const MemoryStrReplaceCommand = Schema.Struct({ /** * Memory tool command payload for replacing text in a file. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryStrReplaceCommand = typeof MemoryStrReplaceCommand.Type @@ -1566,7 +1571,7 @@ export type MemoryStrReplaceCommand = typeof MemoryStrReplaceCommand.Type * When used on a file, returns file contents optionally limited by `view_range`. * When used on a directory, lists contents. * - * @category memory + * @category schemas * @since 4.0.0 */ export const MemoryViewCommand = Schema.Struct({ @@ -1578,12 +1583,12 @@ export const MemoryViewCommand = Schema.Struct({ /** * The specific lines to view. */ - view_range: Schema.optional(ViewRange) + view_range: Schema.optionalKey(ViewRange) }) /** * Memory tool command payload for viewing a file or directory, optionally with a file line range. * - * @category memory + * @category models * @since 4.0.0 */ export type MemoryViewCommand = typeof MemoryViewCommand.Type @@ -1609,13 +1614,14 @@ const Memory_20250818_Commands = Schema.Union([ * Provides commands for creating, viewing, editing, renaming, and deleting * files within the model's memory space. * - * @category memory + * @category tools * @since 4.0.0 */ export const Memory_20250818 = Tool.providerDefined({ id: "anthropic.memory_20250818", customName: "AnthropicMemory", providerName: "memory", + requiresHandler: true, parameters: Memory_20250818_Commands, success: Schema.String }) @@ -1645,7 +1651,7 @@ export const Memory_20250818 = Tool.providerDefined({ * * @see {@link CodeExecutionTextEditorView} for the code-execution variant without `view_range` * - * @category text editor + * @category schemas * @since 4.0.0 */ export const TextEditorViewCommand = Schema.Struct({ @@ -1658,7 +1664,7 @@ export const TextEditorViewCommand = Schema.Struct({ * Optional line range to view (only applies to files, not directories). * Lines are 1-indexed. Use -1 for end to read to end of file. */ - view_range: Schema.optional(ViewRange) + view_range: Schema.optionalKey(ViewRange) }) /** * Text editor command payload for viewing file contents or listing directory contents. @@ -1668,7 +1674,7 @@ export const TextEditorViewCommand = Schema.Struct({ * `view_range` is a 1-indexed `[start, end]` tuple where `-1` means through * the end of the file. * - * @category text editor + * @category models * @since 4.0.0 */ export type TextEditorViewCommand = typeof TextEditorViewCommand.Type @@ -1690,7 +1696,7 @@ export type TextEditorViewCommand = typeof TextEditorViewCommand.Type * * Fails if the file already exists. Parent directories must exist. * - * @category text editor + * @category schemas * @since 4.0.0 */ export const TextEditorCreateCommand = Schema.Struct({ @@ -1716,7 +1722,7 @@ export const TextEditorCreateCommand = Schema.Struct({ * * The command fails if the file already exists or if parent directories are missing. * - * @category text editor + * @category models * @since 4.0.0 */ export type TextEditorCreateCommand = typeof TextEditorCreateCommand.Type @@ -1742,7 +1748,7 @@ export type TextEditorCreateCommand = typeof TextEditorCreateCommand.Type * @see {@link TextEditorViewCommand} for reading contents before choosing `old_str` * @see {@link CodeExecutionTextEditorStrReplace} for the code-execution variant * - * @category text editor + * @category schemas * @since 4.0.0 */ export const TextEditorStrReplaceCommand = Schema.Struct({ @@ -1773,7 +1779,7 @@ export const TextEditorStrReplaceCommand = Schema.Struct({ * The `old_str` must match exactly, including whitespace and indentation, and * must be unique in the file. * - * @category text editor + * @category models * @since 4.0.0 */ export type TextEditorStrReplaceCommand = typeof TextEditorStrReplaceCommand.Type @@ -1786,7 +1792,7 @@ export type TextEditorStrReplaceCommand = typeof TextEditorStrReplaceCommand.Typ * Inserts the new text after the specified line number. Use `0` to insert at * the beginning of the file; other values are 1-indexed. * - * @category text editor + * @category schemas * @since 4.0.0 */ export const TextEditorInsertCommand = Schema.Struct({ @@ -1798,7 +1804,7 @@ export const TextEditorInsertCommand = Schema.Struct({ /** * The line number after which to insert (0 = beginning, 1-indexed). */ - insert_line: Schema.Number, + insert_line: Schema.Int, /** * The text to insert. */ @@ -1807,7 +1813,7 @@ export const TextEditorInsertCommand = Schema.Struct({ /** * Text editor command payload for inserting text after a specific line number in a file. * - * @category text editor + * @category models * @since 4.0.0 */ export type TextEditorInsertCommand = typeof TextEditorInsertCommand.Type @@ -1826,7 +1832,7 @@ export type TextEditorInsertCommand = typeof TextEditorInsertCommand.Type * `text_editor_20250124`, but not in `text_editor_20250429` or * `text_editor_20250728`. * - * @category text editor + * @category schemas * @since 4.0.0 */ export const TextEditorUndoEditCommand = Schema.Struct({ @@ -1844,7 +1850,7 @@ export const TextEditorUndoEditCommand = Schema.Struct({ * Available for `text_editor_20241022` and `text_editor_20250124`, but not for * `text_editor_20250429` or `text_editor_20250728`. * - * @category text editor + * @category models * @since 4.0.0 */ export type TextEditorUndoEditCommand = typeof TextEditorUndoEditCommand.Type @@ -1873,7 +1879,7 @@ const TextEditor_StrReplaceBasedEdit_Args = Schema.Struct({ * Maximum number of characters to return when viewing large files. * When a file exceeds this limit, it will be truncated. */ - max_characters: Schema.optional(Schema.Number) + max_characters: Schema.optional(Schema.Int) }) // ----------------------------------------------------------------------------- @@ -1896,7 +1902,7 @@ const TextEditor_StrReplaceBasedEdit_Args = Schema.Struct({ * @see {@link TextEditor_20250124} for the newer `str_replace_editor` version * @see {@link TextEditor_20250728} for the Claude 4 `str_replace_based_edit_tool` line * - * @category text editor + * @category tools * @since 4.0.0 */ export const TextEditor_20241022 = Tool.providerDefined({ @@ -1924,7 +1930,7 @@ export const TextEditor_20241022 = Tool.providerDefined({ * @see {@link TextEditor_20241022} for the older `str_replace_editor` version * @see {@link TextEditor_20250429} for the Claude 4 `str_replace_based_edit_tool` line * - * @category text editor + * @category tools * @since 4.0.0 */ export const TextEditor_20250124 = Tool.providerDefined({ @@ -1955,7 +1961,7 @@ export const TextEditor_20250124 = Tool.providerDefined({ * @see {@link TextEditor_20250124} for the previous `str_replace_editor` version * @see {@link TextEditor_20250728} for the later Claude 4 text editor version * - * @category text editor + * @category tools * @since 4.0.0 */ export const TextEditor_20250429 = Tool.providerDefined({ @@ -1980,7 +1986,7 @@ export const TextEditor_20250429 = Tool.providerDefined({ * * This version does not support the `undo_edit` command. * - * @category text editor + * @category tools * @since 4.0.0 */ export const TextEditor_20250728 = Tool.providerDefined({ @@ -2017,7 +2023,7 @@ export const TextEditor_20250728 = Tool.providerDefined({ * * @see {@link WebSearch_20250305_Args} for the argument schema that consumes this location * - * @category Web Search + * @category schemas * @since 4.0.0 */ export const WebSearchUserLocation = Schema.Struct({ @@ -2067,14 +2073,14 @@ export const WebSearchUserLocation = Schema.Struct({ * @see {@link WebSearch_20250305} for the provider-defined tool that consumes these arguments * @see {@link WebSearchUserLocation} for localizing search results * - * @category Web Search + * @category schemas * @since 4.0.0 */ export const WebSearch_20250305_Args = Schema.Struct({ /** * Maximum number of searches allowed per API request. */ - maxUses: Schema.optional(Schema.Number), + maxUses: Schema.optional(Schema.Int), /** * Restrict search results to only these domains. * @@ -2099,7 +2105,7 @@ export const WebSearch_20250305_Args = Schema.Struct({ * * `allowedDomains` and `blockedDomains` are mutually exclusive. * - * @category Web Search + * @category models * @since 4.0.0 */ export type WebSearch_20250305_Args = typeof WebSearch_20250305_Args.Type @@ -2118,7 +2124,7 @@ export type WebSearch_20250305_Args = typeof WebSearch_20250305_Args.Type * * @see {@link WebSearch_20250305} for the provider-defined tool that consumes this payload * - * @category Web Search + * @category schemas * @since 4.0.0 */ export const WebSearchParameters = Schema.Struct({ @@ -2136,7 +2142,7 @@ export const WebSearchParameters = Schema.Struct({ * * @see {@link WebSearch_20250305} for the provider-defined tool that consumes this payload * - * @category Web Search + * @category models * @since 4.0.0 */ export type WebSearchParameters = typeof WebSearchParameters.Type @@ -2160,7 +2166,7 @@ export type WebSearchParameters = typeof WebSearchParameters.Type * * @see {@link WebFetch_20250910} for retrieving known URLs after discovery * - * @category Web Search + * @category tools * @since 4.0.0 */ export const WebSearch_20250305 = Tool.providerDefined({ @@ -2195,7 +2201,7 @@ export const WebSearch_20250305 = Tool.providerDefined({ * * @see {@link WebFetch_20250910_Args} for the argument schema that consumes this configuration * - * @category Web Fetch + * @category schemas * @since 4.0.0 */ export const WebFetchCitationsConfig = Schema.Struct({ @@ -2219,7 +2225,7 @@ export const WebFetchCitationsConfig = Schema.Struct({ * * @see {@link WebFetch_20250910_Args} for the argument schema that consumes this configuration * - * @category Web Fetch + * @category models * @since 4.0.0 */ export type WebFetchCitationsConfig = typeof WebFetchCitationsConfig.Type @@ -2250,14 +2256,14 @@ export type WebFetchCitationsConfig = typeof WebFetchCitationsConfig.Type * @see {@link WebFetch_20250910} for the provider-defined tool that consumes these arguments * @see {@link WebFetchCitationsConfig} for configuring citations * - * @category Web Fetch + * @category schemas * @since 4.0.0 */ export const WebFetch_20250910_Args = Schema.Struct({ /** * Maximum number of fetches allowed per API request. */ - maxUses: Schema.optional(Schema.Number), + maxUses: Schema.optional(Schema.Int), /** * Restrict fetches to only these domains. * @@ -2277,7 +2283,7 @@ export const WebFetch_20250910_Args = Schema.Struct({ /** * Maximum content length in tokens. */ - maxContentTokens: Schema.optional(Schema.Number) + maxContentTokens: Schema.optional(Schema.Int) }) /** * Configuration arguments for the Anthropic web fetch tool, including usage limits, domain filters, citation settings, and token limits. @@ -2293,7 +2299,7 @@ export const WebFetch_20250910_Args = Schema.Struct({ * `maxContentTokens` is approximate and does not apply to binary content such * as PDFs. * - * @category Web Fetch + * @category models * @since 4.0.0 */ export type WebFetch_20250910_Args = typeof WebFetch_20250910_Args.Type @@ -2321,7 +2327,7 @@ export type WebFetch_20250910_Args = typeof WebFetch_20250910_Args.Type * * @see {@link WebFetch_20250910} for the provider-defined tool that consumes this payload * - * @category Web Fetch + * @category schemas * @since 4.0.0 */ export const WebFetchParameters = Schema.Struct({ @@ -2348,7 +2354,7 @@ export const WebFetchParameters = Schema.Struct({ * The URL must be user-provided or from prior search/fetch results. Maximum URL * length is 250 characters. * - * @category Web Fetch + * @category models * @since 4.0.0 */ export type WebFetchParameters = typeof WebFetchParameters.Type @@ -2373,7 +2379,7 @@ export type WebFetchParameters = typeof WebFetchParameters.Type * * @see {@link WebSearch_20250305} for discovering URLs before fetching specific content * - * @category Web Fetch + * @category tools * @since 4.0.0 */ export const WebFetch_20250910 = Tool.providerDefined({ @@ -2402,7 +2408,7 @@ export const WebFetch_20250910 = Tool.providerDefined({ * Claude constructs regex patterns using Python's `re.search()` syntax. * Maximum query length: 200 characters. * - * @category tool search + * @category schemas * @since 4.0.0 */ export const ToolSearchRegexParameters = Schema.Struct({ @@ -2419,7 +2425,7 @@ export const ToolSearchRegexParameters = Schema.Struct({ * Claude constructs regex patterns using Python's `re.search()` syntax. * Maximum query length: 200 characters. * - * @category tool search + * @category models * @since 4.0.0 */ export type ToolSearchRegexParameters = typeof ToolSearchRegexParameters.Type @@ -2439,7 +2445,7 @@ export type ToolSearchRegexParameters = typeof ToolSearchRegexParameters.Type * * @see {@link ToolSearchBM25_20251119} for the provider-defined tool that consumes these parameters * - * @category tool search + * @category schemas * @since 4.0.0 */ export const ToolSearchBM25Parameters = Schema.Struct({ @@ -2451,7 +2457,7 @@ export const ToolSearchBM25Parameters = Schema.Struct({ /** * Type of the parameters Claude supplies when invoking BM25 natural-language Anthropic tool search. * - * @category tool search + * @category models * @since 4.0.0 */ export type ToolSearchBM25Parameters = typeof ToolSearchBM25Parameters.Type @@ -2470,7 +2476,7 @@ export type ToolSearchBM25Parameters = typeof ToolSearchBM25Parameters.Type * argument names, and argument descriptions. * Requires the "advanced-tool-use-2025-11-20" beta header. * - * @category tool search + * @category tools * @since 4.0.0 */ export const ToolSearchRegex_20251119 = Tool.providerDefined({ @@ -2499,7 +2505,7 @@ export const ToolSearchRegex_20251119 = Tool.providerDefined({ * * @see {@link ToolSearchRegex_20251119} for the regex-pattern alternative * - * @category tool search + * @category tools * @since 4.0.0 */ export const ToolSearchBM25_20251119 = Tool.providerDefined({ diff --git a/packages/ai/anthropic/src/Generated.ts b/packages/ai/anthropic/src/Generated.ts index 7a7a9470c..319aeb25c 100644 --- a/packages/ai/anthropic/src/Generated.ts +++ b/packages/ai/anthropic/src/Generated.ts @@ -2872,55 +2872,47 @@ export const BetaStopReason = Schema.Literals([ "model_context_window_exceeded" ]) export type Model = + | string + | "claude-sonnet-5" + | "claude-fable-5" + | "claude-mythos-5" + | "claude-opus-4-8" + | "claude-opus-4-7" + | "claude-mythos-preview" | "claude-opus-4-6" | "claude-sonnet-4-6" - | "claude-opus-4-5-20251101" - | "claude-opus-4-5" - | "claude-3-7-sonnet-latest" - | "claude-3-7-sonnet-20250219" - | "claude-3-5-haiku-latest" - | "claude-3-5-haiku-20241022" | "claude-haiku-4-5" | "claude-haiku-4-5-20251001" - | "claude-sonnet-4-20250514" - | "claude-sonnet-4-0" - | "claude-4-sonnet-20250514" + | "claude-opus-4-5" + | "claude-opus-4-5-20251101" | "claude-sonnet-4-5" | "claude-sonnet-4-5-20250929" - | "claude-opus-4-0" - | "claude-opus-4-20250514" - | "claude-4-opus-20250514" + | "claude-opus-4-1" | "claude-opus-4-1-20250805" - | "claude-3-opus-latest" - | "claude-3-opus-20240229" - | "claude-3-haiku-20240307" -export const Model = Schema.Literals([ - "claude-opus-4-6", - "claude-sonnet-4-6", - "claude-opus-4-5-20251101", - "claude-opus-4-5", - "claude-3-7-sonnet-latest", - "claude-3-7-sonnet-20250219", - "claude-3-5-haiku-latest", - "claude-3-5-haiku-20241022", - "claude-haiku-4-5", - "claude-haiku-4-5-20251001", - "claude-sonnet-4-20250514", - "claude-sonnet-4-0", - "claude-4-sonnet-20250514", - "claude-sonnet-4-5", - "claude-sonnet-4-5-20250929", - "claude-opus-4-0", - "claude-opus-4-20250514", - "claude-4-opus-20250514", - "claude-opus-4-1-20250805", - "claude-3-opus-latest", - "claude-3-opus-20240229", - "claude-3-haiku-20240307" +export const Model = Schema.Union([ + Schema.String, + Schema.Literals([ + "claude-sonnet-5", + "claude-fable-5", + "claude-mythos-5", + "claude-opus-4-8", + "claude-opus-4-7", + "claude-mythos-preview", + "claude-opus-4-6", + "claude-sonnet-4-6", + "claude-haiku-4-5", + "claude-haiku-4-5-20251001", + "claude-opus-4-5", + "claude-opus-4-5-20251101", + "claude-sonnet-4-5", + "claude-sonnet-4-5-20250929", + "claude-opus-4-1", + "claude-opus-4-1-20250805" + ]) ]).annotate({ "title": "Model", "description": - "The model that will complete your prompt.\\n\\nSee [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options." + "The model that will complete your prompt.\n\nSee [models](https://docs.anthropic.com/en/docs/models-overview) for additional details and options." }) export type BetaMemoryTool_20250818_ViewCommand = { readonly "command": "view" diff --git a/packages/ai/anthropic/src/internal/errors.ts b/packages/ai/anthropic/src/internal/errors.ts index 3745bccab..e0b2e95a9 100644 --- a/packages/ai/anthropic/src/internal/errors.ts +++ b/packages/ai/anthropic/src/internal/errors.ts @@ -8,6 +8,7 @@ import * as Redactable from "effect/Redactable" import * as Schema from "effect/Schema" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -52,6 +53,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "AnthropicClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapClientError = dual< (method: string) => (error: Generated.AnthropicClientError) => AiError.AiError, @@ -248,43 +260,6 @@ export const buildHttpContext = (params: { // HTTP Status Code // ============================================================================= -const buildInvalidRequestDescription = (params: { - readonly status: number - readonly message: string | undefined - readonly method: string - readonly url: string - readonly errorType: string | null - readonly requestId: string | null - readonly body: string | undefined -}): string => { - const parts: Array = [] - - if (params.message) { - parts.push(params.message) - } else { - parts.push(`HTTP ${params.status}`) - } - - parts.push(`(${params.method} ${params.url})`) - - if (params.errorType) { - parts.push(`[type: ${params.errorType}]`) - } - - if (params.requestId) { - parts.push(`[requestId: ${params.requestId}]`) - } - - if (!params.message && params.body) { - const truncated = params.body.length > 200 - ? params.body.slice(0, 200) + "..." - : params.body - parts.push(`Response: ${truncated}`) - } - - return parts.join(" ") -} - /** @internal */ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { readonly status: number @@ -293,7 +268,7 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http readonly metadata: AnthropicErrorMetadata readonly http: typeof AiError.HttpContext.Type }): AiError.AiErrorReason => { - const invalidRequestDescription = buildInvalidRequestDescription({ + const errorDescription = AiError.buildErrorDescription({ status, message, method: http.request.method, @@ -306,31 +281,33 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http switch (status) { case 400: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { anthropic: metadata }, http }) case 401: return new AiError.AuthenticationError({ kind: "InvalidKey", + description: errorDescription, metadata: { anthropic: metadata }, http }) case 403: return new AiError.AuthenticationError({ kind: "InsufficientPermissions", + description: errorDescription, metadata: { anthropic: metadata }, http }) case 404: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { anthropic: metadata }, http }) case 422: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { anthropic: metadata }, http }) diff --git a/packages/ai/anthropic/src/internal/utilities.ts b/packages/ai/anthropic/src/internal/utilities.ts index 320754c4d..322b035ac 100644 --- a/packages/ai/anthropic/src/internal/utilities.ts +++ b/packages/ai/anthropic/src/internal/utilities.ts @@ -15,7 +15,7 @@ export const resolveFinishReason = ( finishReason: string, isJsonResponse: boolean = false ): Response.FinishReason => { - const reason = finishReasonMap[finishReason] + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined if (Predicate.isUndefined(reason)) { return "unknown" } diff --git a/packages/ai/anthropic/test/AnthropicClient.test.ts b/packages/ai/anthropic/test/AnthropicClient.test.ts new file mode 100644 index 000000000..7eeb0ea36 --- /dev/null +++ b/packages/ai/anthropic/test/AnthropicClient.test.ts @@ -0,0 +1,227 @@ +import { AnthropicClient } from "@effect/ai-anthropic" +import * as Errors from "@effect/ai-anthropic/internal/errors" +import { assert, describe, it } from "@effect/vitest" +import { Context, Effect, Layer, Redacted, type Schema } from "effect" +import { + Headers, + HttpClient, + type HttpClientError, + type HttpClientRequest, + HttpClientResponse +} from "effect/unstable/http" + +describe("AnthropicClient", () => { + it.effect("redacts the API key in AI error context", () => + Effect.gen(function*() { + const client = yield* AnthropicClient.AnthropicClient + + const result = yield* client.createMessage({ + payload: { + model: "claude-sonnet-4-20250514", + max_tokens: 1, + messages: [{ role: "user", content: "hello" }] + } + }).pipe( + Effect.flip, + Effect.updateService(Headers.CurrentRedactedNames, () => []) + ) + + assert.strictEqual(result.reason._tag, "InvalidRequestError") + if (result.reason._tag !== "InvalidRequestError" || result.reason.http === undefined) { + return yield* Effect.die(new Error("Expected InvalidRequestError with HTTP context")) + } + const requests = yield* MockHttpClient.requests + assert.include(requests[0]?.url, "/v1/messages") + assert.strictEqual(String(result.reason.http.request.headers["x-api-key"]), "") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 400, + body: { + type: "error", + error: { + type: "invalid_request_error", + message: "Bad request" + }, + request_id: null + } + })))) + + it.effect("surfaces the provider message on 401 AuthenticationError", () => + Effect.gen(function*() { + const client = yield* AnthropicClient.AnthropicClient + + const result = yield* client.createMessage({ + payload: { + model: "claude-sonnet-4-20250514", + max_tokens: 1, + messages: [{ role: "user", content: "hello" }] + } + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "AuthenticationError") + if (result.reason._tag !== "AuthenticationError") { + return yield* Effect.die(new Error("Expected AuthenticationError")) + } + assert.strictEqual(result.reason.kind, "InvalidKey") + assert.strictEqual( + result.reason.description, + "invalid x-api-key (POST https://api.anthropic.com/v1/messages?beta=true) [type: authentication_error] [requestId: req_anthropic]" + ) + assert.include(result.reason.message, "invalid x-api-key") + assert.strictEqual( + result.reason.message, + "InvalidKey: Verify your API key is correct. invalid x-api-key (POST https://api.anthropic.com/v1/messages?beta=true) [type: authentication_error] [requestId: req_anthropic]" + ) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 401, + body: { + type: "error", + error: { type: "authentication_error", message: "invalid x-api-key" }, + request_id: "req_anthropic" + } + })))) + + it("preserves and truncates a fallback HTTP response", () => { + const body = `${"a".repeat(200)}b` + const reason = Errors.mapStatusCodeToReason({ + status: 400, + headers: {}, + message: undefined, + metadata: { errorType: null, requestId: null }, + http: makeHttpContext("https://api.anthropic.com/v1/messages?beta=true", body) + }) + + assert.strictEqual(reason._tag, "InvalidRequestError") + if (reason._tag !== "InvalidRequestError") { + throw new Error("Expected InvalidRequestError") + } + assert.strictEqual( + reason.description, + `HTTP 400 (POST https://api.anthropic.com/v1/messages?beta=true) Response: ${"a".repeat(200)}...` + ) + }) + + it.effect("surfaces the provider message on 403 AuthenticationError", () => + Effect.gen(function*() { + const client = yield* AnthropicClient.AnthropicClient + + const result = yield* client.createMessage({ + payload: { + model: "claude-sonnet-4-20250514", + max_tokens: 1, + messages: [{ role: "user", content: "hello" }] + } + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "AuthenticationError") + if (result.reason._tag !== "AuthenticationError") { + return yield* Effect.die(new Error("Expected AuthenticationError")) + } + assert.strictEqual(result.reason.kind, "InsufficientPermissions") + assert.include(result.reason.description ?? "", "not available for this account") + assert.include(result.reason.message, "not available for this account") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 403, + body: { + type: "error", + error: { + type: "permission_error", + message: "claude-sonnet-4-20250514 is not available for this account" + }, + request_id: null + } + })))) +}) + +type MockResponse = + | { + readonly _tag: "Json" + readonly body: Schema.Json + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "Sse" + readonly events: ReadonlyArray + readonly status?: number | undefined + readonly headers?: Record | undefined + } + +class MockAnthropicResponse extends Context.Service()("MockAnthropicResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = MockHttpClient.use((client) => client.requests) +} + +const makeHttpClientContext = Effect.gen(function*() { + const capturedRequests: Array = [] + const mock = yield* MockAnthropicResponse + + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + capturedRequests.push(request) + return makeResponse(request, mock.response) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + const mockHttpClient: MockHttpClient["Service"] = { + requests: Effect.sync(() => capturedRequests) + } + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, mockHttpClient) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClientContext) + +const makeTestLayer = ( + response: MockResponse, + options: AnthropicClient.Options = { apiKey: Redacted.make("sk-test-key") } +) => + AnthropicClient.layer(options).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockAnthropicResponse, { response })) + ) + +const makeResponse = ( + request: HttpClientRequest.HttpClientRequest, + response: MockResponse +): HttpClientResponse.HttpClientResponse => { + const contentType = response._tag === "Json" + ? "application/json" + : "text/event-stream" + const body = response._tag === "Json" + ? JSON.stringify(response.body) + : response.events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + + return HttpClientResponse.fromWeb( + request, + new Response(body, { + status: response.status ?? 200, + headers: { + "content-type": contentType, + ...response.headers + } + }) + ) +} + +const makeHttpContext = (url: string, body: string) => ({ + request: { + method: "POST" as const, + url, + urlParams: [], + hash: undefined, + headers: {} + }, + body +}) diff --git a/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts b/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts index 0fd033452..56bab6b2d 100644 --- a/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts +++ b/packages/ai/anthropic/test/AnthropicLanguageModel.test.ts @@ -1,7 +1,15 @@ -import { AnthropicClient, AnthropicLanguageModel } from "@effect/ai-anthropic" +import { AnthropicClient, AnthropicLanguageModel, AnthropicTool } from "@effect/ai-anthropic" import { assert, describe, it } from "@effect/vitest" import { Effect, Layer, Redacted, Schema, Stream } from "effect" -import { LanguageModel, Tool, Toolkit } from "effect/unstable/ai" +import { + type AiError, + AnthropicStructuredOutput, + LanguageModel, + Prompt, + Response as AiResponse, + Tool, + Toolkit +} from "effect/unstable/ai" import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("AnthropicLanguageModel", () => { @@ -111,9 +119,493 @@ describe("AnthropicLanguageModel", () => { assert.strictEqual(toolCall.name, "GlobTool") assert.deepStrictEqual(toolCall.params, toolParams) })) + + it.effect("routes invalid tool call params through failureMode: return without failing the stream", () => + Effect.gen(function*() { + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + { + type: "message_start", + message: { + id: "msg_test_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 10, + output_tokens: 0, + service_tier: null + } + } + }, + { + type: "content_block_start", + index: 0, + content_block: { + type: "tool_use", + id: "toolu_test_1", + name: "GlobTool", + input: {} + } + }, + { + type: "content_block_delta", + index: 0, + delta: { + type: "input_json_delta", + partial_json: JSON.stringify({ pattern: 123 }) + } + }, + { + type: "content_block_stop", + index: 0 + }, + { + type: "message_delta", + delta: { + stop_reason: "tool_use", + stop_sequence: null + }, + usage: { + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + input_tokens: null, + output_tokens: 5 + } + }, + { + type: "message_stop" + } + ])) + ) + )) + ) + + const GlobTool = Tool.make("GlobTool", { + description: "Search for files", + failureMode: "return", + parameters: Schema.Struct({ pattern: Schema.String }), + success: Schema.String, + failure: Schema.String + }) + + const toolkit = Toolkit.make(GlobTool) + const toolkitLayer = toolkit.toLayer({ + GlobTool: () => Effect.succeed("found.ts") + }) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "find ts files", + toolkit + }).pipe( + Stream.runCollect, + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + const toolResult = parts.find((part) => part.type === "tool-result") + assert.isDefined(toolResult) + if (toolResult?.type !== "tool-result") { + return + } + + assert.strictEqual(toolResult.isFailure, true) + const failure = toolResult.result as AiError.AiError + assert.strictEqual(failure._tag, "AiError") + assert.strictEqual(failure.reason._tag, "ToolParameterValidationError") + })) + + const codeExecutionCases = [ + { + providerName: "bash_code_execution", + toolParams: { command: "pwd" }, + expectedParams: { type: "bash_code_execution", command: "pwd" } + }, + { + providerName: "text_editor_code_execution", + toolParams: { command: "view", path: "/tmp/example.txt" }, + expectedParams: { + type: "text_editor_code_execution", + command: "view", + path: "/tmp/example.txt" + } + } + ] as const + + for (const { expectedParams, providerName, toolParams } of codeExecutionCases) { + it.effect(`emits valid JSON for streamed ${providerName} parameters`, () => + Effect.gen(function*() { + const toolkit = Toolkit.make(AnthropicTool.CodeExecution_20250522()) + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + { + type: "message_start", + message: { + id: "msg_test_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 1, + output_tokens: 0, + service_tier: null + } + } + }, + { + type: "content_block_start", + index: 0, + content_block: { + type: "server_tool_use", + id: "srvtoolu_test_1", + name: providerName, + input: {} + } + }, + { + type: "content_block_delta", + index: 0, + delta: { + type: "input_json_delta", + partial_json: JSON.stringify(toolParams) + } + }, + { + type: "content_block_stop", + index: 0 + }, + { + type: "message_delta", + delta: { + stop_reason: "tool_use", + stop_sequence: null + }, + usage: { + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + input_tokens: null, + output_tokens: 1 + } + }, + { + type: "message_stop" + } + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "run pwd", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + + const delta = parts.find((part) => part.type === "tool-params-delta") + assert.isDefined(delta) + if (delta?.type === "tool-params-delta") { + assert.deepStrictEqual(JSON.parse(delta.delta), expectedParams) + } + + const toolCall = parts.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type === "tool-call") { + assert.deepStrictEqual(toolCall.params, expectedParams) + } + })) + } + + // `Model` is an open enum in Anthropic's spec (`anyOf: [{ type: string }, ...consts]`), and it is + // $ref'd by response schemas. Responses must therefore decode for model ids that are newer than the + // generated literals, and the id must survive decoding unchanged. + it.effect("decodes responses for a model id that is not a known literal", () => + Effect.gen(function*() { + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + { + type: "message_start", + message: { + id: "msg_test_1", + type: "message", + role: "assistant", + model: "claude-not-a-known-model-id", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 10, + output_tokens: 0, + service_tier: null + } + } + }, + { + type: "content_block_start", + index: 0, + content_block: { type: "text", text: "" } + }, + { + type: "content_block_delta", + index: 0, + delta: { type: "text_delta", text: "Hello" } + }, + { + type: "content_block_stop", + index: 0 + }, + { + type: "message_delta", + delta: { + stop_reason: "end_turn", + stop_sequence: null + }, + usage: { + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + input_tokens: null, + output_tokens: 5 + } + }, + { + type: "message_stop" + } + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "say hello" + }).pipe( + Stream.runCollect, + Effect.provide(AnthropicLanguageModel.model("claude-not-a-known-model-id")), + Effect.provide(layer) + ) + + const parts = globalThis.Array.from(partsChunk) + const metadata = parts.find((part) => part.type === "response-metadata") + assert.isDefined(metadata) + if (metadata?.type !== "response-metadata") { + return + } + + assert.strictEqual(metadata.modelId, "claude-not-a-known-model-id") + + const text = parts.find((part) => part.type === "text-delta") + assert.isDefined(text) + if (text?.type !== "text-delta") { + return + } + + assert.strictEqual(text.delta, "Hello") + })) }) describe("generateText", () => { + it.effect("omits strictJsonSchema from the request while preserving tool strictness", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined + const model = "claude-sonnet-4-5" + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + id: "msg_test_1", + type: "message", + role: "assistant", + model, + content: [{ type: "text", text: "Hello" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 1, + output_tokens: 1, + service_tier: null + } + })) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: "Hello", + toolkit: Toolkit.make(Tool.make("Search", { + parameters: Schema.Struct({ query: Schema.String }), + success: Schema.String + })), + disableToolCallResolution: true + }).pipe( + Effect.provide(AnthropicLanguageModel.model(model, { strictJsonSchema: false })), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + const body = yield* getRequestBody(capturedRequest) + assert.strictEqual(body.model, model) + assert.strictEqual(body.tools[0].name, "Search") + assert.strictEqual(body.tools[0].strict, false) + assert.notProperty(body, "strictJsonSchema") + })) + + it.effect("preserves string tool results", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + id: "msg_test_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "text", text: "Done" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 10, + output_tokens: 5, + service_tier: null + } + })) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Use the tool" }, + { + role: "assistant", + content: [Prompt.toolCallPart({ + id: "call_text", + name: "text_tool", + params: {}, + providerExecuted: false + })] + }, + { + role: "tool", + content: [Prompt.toolResultPart({ + id: "call_text", + name: "text_tool", + result: "PLAIN_TEXT_SENTINEL\n", + isFailure: false, + providerExecuted: false + })] + } + ]), + disableToolCallResolution: true + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const body = yield* getRequestBody(capturedRequest) + const toolResult = body.messages + .flatMap((message: any) => Array.isArray(message.content) ? message.content : []) + .find((block: any) => block.type === "tool_result") + + assert.isDefined(toolResult) + assert.strictEqual(toolResult.content, "PLAIN_TEXT_SENTINEL\n") + })) + + it.effect("preserves base64 image string payloads", () => + Effect.gen(function*() { + const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=" + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + id: "msg_test_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "text", text: "Done" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 1, + output_tokens: 1, + service_tier: null + } + })) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([Prompt.userMessage({ + content: [Prompt.filePart({ mediaType: "image/png", data: base64 })] + })]) + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + const body = yield* getRequestBody(capturedRequest) + assert.strictEqual(body.messages[0].content[0].source.data, base64) + })) + it.effect("encodes dynamic tools", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined @@ -184,12 +676,163 @@ describe("AnthropicLanguageModel", () => { assert.strictEqual(dynamicTool.description, "A dynamic tool") assert.deepStrictEqual(dynamicTool.input_schema, inputSchema) })) + + it.effect("serializes provider-executed web_search parts from the assistant message", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, { + id: "msg_test_2", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "text", text: "You're welcome" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 10, + output_tokens: 5, + service_tier: null + } + })) + }) + )) + ) + + const searchResults = [{ + type: "web_search_result", + url: "https://example.com/gold", + title: "Gold price", + encrypted_content: "encrypted", + page_age: null + }] + + const history = Prompt.fromResponseParts([ + AiResponse.makePart("tool-call", { + id: "srvtoolu_1", + name: "AnthropicWebSearch", + params: { query: "gold price today" }, + providerExecuted: true + }), + AiResponse.makePart("tool-result", { + id: "srvtoolu_1", + name: "AnthropicWebSearch", + isFailure: false, + result: searchResults, + encodedResult: searchResults, + preliminary: false, + providerExecuted: true + }), + AiResponse.makePart("text", { text: "Gold is around $4,000." }) + ]) + + const prompt = Prompt.concat( + Prompt.concat(Prompt.make("what is the gold price?"), history), + Prompt.make("thanks") + ) + + yield* LanguageModel.generateText({ + prompt, + toolkit: Toolkit.make(AnthropicTool.WebSearch_20250305({})), + disableToolCallResolution: true + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const body = yield* getRequestBody(capturedRequest) + const assistantMessage = body.messages.find((message: any) => message.role === "assistant") + assert.isDefined(assistantMessage) + + const serverToolUse = assistantMessage.content.find((block: any) => block.type === "server_tool_use") + assert.isDefined(serverToolUse) + assert.strictEqual(serverToolUse.id, "srvtoolu_1") + assert.strictEqual(serverToolUse.name, "web_search") + + const searchResult = assistantMessage.content.find((block: any) => block.type === "web_search_tool_result") + assert.isDefined(searchResult) + assert.strictEqual(searchResult.tool_use_id, "srvtoolu_1") + + const clientToolResults = body.messages.flatMap((message: any) => + Array.isArray(message.content) + ? message.content.filter((block: any) => block.type === "tool_result") + : [] + ) + assert.strictEqual(clientToolResults.length, 0) + })) + + it.effect("encodes plaintext bytes as UTF-8 text", () => + Effect.gen(function*() { + let body: any + const client = AnthropicClient.layer({ apiKey: Redacted.make("test") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + body = JSON.parse(new TextDecoder().decode((request.body as any).body)) + return HttpClientResponse.fromWeb( + request, + new Response( + JSON.stringify({ + id: "msg_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "text", text: "ok" }], + stop_reason: "end_turn", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 1, + output_tokens: 1, + service_tier: null + } + }), + { status: 200 } + ) + ) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([{ + role: "user", + content: [Prompt.filePart({ mediaType: "text/plain", data: new TextEncoder().encode("hello") })] + }]) + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(client) + ) + + assert.strictEqual(body.messages[0].content[0].source.data, "hello") + })) }) describe("generateObject", () => { - // A model that supports native structured output requests it via `output_config.format` (json_schema) - // rather than falling back to a forced JSON tool. - const assertNativeStructuredOutput = (model: string) => + const getRequest = ( + model: string, + config?: { readonly structuredOutputs?: boolean | undefined } + ) => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined = undefined const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( @@ -224,25 +867,282 @@ describe("AnthropicLanguageModel", () => { prompt: "Give me a person", schema: Schema.Struct({ name: Schema.String, age: Schema.Number }) }).pipe( - Effect.provide(AnthropicLanguageModel.model(model)), + Effect.provide(AnthropicLanguageModel.model(model, config)), Effect.provide(layer), Effect.ignore ) assert.isDefined(capturedRequest) if (capturedRequest === undefined) { - return + return yield* Effect.die(new Error("Expected a captured request")) } - const body = yield* getRequestBody(capturedRequest) + return yield* getRequestBody(capturedRequest) + }) + + it.effect("uses native structured output and 128K for Claude 4.6", () => + Effect.gen(function*() { + const body = yield* getRequest("claude-opus-4-6") + + assert.strictEqual(body.max_tokens, 128000) + assert.strictEqual(body.output_config?.format?.type, "json_schema") + })) + + it.effect("uses optimistic modern defaults for an unknown future model", () => + Effect.gen(function*() { + const body = yield* getRequest("claude-sonnet-6-0") + + assert.strictEqual(body.max_tokens, 128000) + assert.strictEqual(body.output_config?.format?.type, "json_schema") + })) + + it.effect("preserves frozen legacy model exceptions", () => + Effect.gen(function*() { + const body = yield* getRequest("claude-sonnet-4-20250514") + + assert.strictEqual(body.max_tokens, 64000) + assert.isUndefined(body.output_config) + })) + + it.effect("can disable structured outputs for a modern model", () => + Effect.gen(function*() { + const body = yield* getRequest("claude-sonnet-6-0", { structuredOutputs: false }) + + assert.strictEqual(body.max_tokens, 128000) + assert.isUndefined(body.output_config) + assert.notProperty(body, "structuredOutputs") + })) + + it.effect("can enable structured outputs for a legacy model", () => + Effect.gen(function*() { + const body = yield* getRequest("claude-sonnet-4-20250514", { structuredOutputs: true }) + + assert.strictEqual(body.max_tokens, 64000) assert.strictEqual(body.output_config?.format?.type, "json_schema") + assert.notProperty(body, "structuredOutputs") + })) + + const summaryResponse = (request: HttpClientRequest.HttpClientRequest, content: ReadonlyArray) => + jsonResponse(request, { + id: "msg_summary", + type: "message", + role: "assistant", + model: "claude-sonnet-4-6", + content, + stop_reason: "tool_use", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 1, + output_tokens: 1, + service_tier: null + } }) - it.effect("uses native json_schema output for claude-opus-4-6", () => - assertNativeStructuredOutput("claude-opus-4-6")) + it.effect("forces the fallback response tool and decodes its input alongside prose", () => + Effect.gen(function*() { + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.gen(function*() { + const body = yield* getRequestBody(request) + assert.deepStrictEqual(body.tools?.map((tool: { name: string }) => tool.name), ["summary"]) + assert.deepStrictEqual(body.tool_choice, { + type: "tool", + name: "summary", + disable_parallel_tool_use: true + }) + return summaryResponse(request, [ + { type: "text", text: "Here is the summary." }, + { type: "tool_use", id: "toolu_summary", name: "summary", input: { title: "Rain" } } + ]) + }) + ) + )) + ) + + const response = yield* LanguageModel.generateObject({ + prompt: "Give a title for a story about rain.", + objectName: "summary", + schema: Schema.Struct({ title: Schema.String }) + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-6", { structuredOutputs: false })), + Effect.provide(layer) + ) + + assert.deepStrictEqual(response.value, { title: "Rain" }) + })) - it.effect("uses native json_schema output for claude-sonnet-4-6", () => - assertNativeStructuredOutput("claude-sonnet-4-6")) + it.effect("decodes native JSON alongside an ordinary tool call", () => + Effect.gen(function*() { + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(summaryResponse(request, [ + { type: "text", text: JSON.stringify({ title: "Rain" }) }, + { type: "tool_use", id: "toolu_weather", name: "Weather", input: { city: "SF" } } + ])) + ) + )) + ) + const toolkit = Toolkit.make(Tool.make("Weather", { + parameters: Schema.Struct({ city: Schema.String }), + success: Schema.String + })) + + const response = yield* LanguageModel.generateObject({ + prompt: "Give a title for a story about rain.", + objectName: "summary", + schema: Schema.Struct({ title: Schema.String }), + toolkit + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-6")), + Effect.provide(toolkit.toLayer({ Weather: () => Effect.succeed("Rain") })), + Effect.provide(layer) + ) + + assert.deepStrictEqual(response.value, { title: "Rain" }) + assert.strictEqual(response.toolCalls[0]?.name, "Weather") + })) + }) + + // The packaged `Memory_20250818` tool ships `customName: "AnthropicMemory"` / + // `providerName: "memory"`, and is a client-executed provider tool. These + // tests cover the round-trip that was broken on beta.98 (see #2615): + // - the provider wire name ("memory") must resolve to the toolkit's custom + // name ("AnthropicMemory"), otherwise `makeResponse` raises ToolNotFound + // - `view_range` uses `Schema.optionalKey`, otherwise the Anthropic codec + // rejects the tool schema with "Unsupported AST Undefined" + // - `create` must carry `file_text`, otherwise the file body is dropped + describe("Memory tool", () => { + const memoryResponse = (request: HttpClientRequest.HttpClientRequest, input: unknown) => + jsonResponse(request, { + id: "msg_test_1", + type: "message", + role: "assistant", + model: "claude-sonnet-4-20250514", + content: [{ type: "tool_use", id: "toolu_mem_1", name: "memory", input }], + stop_reason: "tool_use", + stop_sequence: null, + usage: { + cache_creation: null, + cache_creation_input_tokens: null, + cache_read_input_tokens: null, + inference_geo: null, + input_tokens: 10, + output_tokens: 5, + service_tier: null + } + }) + + it.effect("resolves the provider wire name to the tool's custom name (view)", () => + Effect.gen(function*() { + let receivedParams: unknown = undefined + const toolkit = Toolkit.make(AnthropicTool.Memory_20250818({})) + const toolkitLayer = toolkit.toLayer({ + AnthropicMemory: (params) => { + receivedParams = params + return Effect.succeed("memory listing") + } + }) + + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => Effect.succeed(memoryResponse(request, { command: "view", path: "/memories" }))) + )) + ) + + const response = yield* LanguageModel.generateText({ + prompt: "check memory", + toolkit + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + // Handler was resolved under the custom name and received the decoded command + assert.deepStrictEqual(receivedParams, { command: "view", path: "/memories" }) + + const toolResult = response.toolResults[0] + assert.isDefined(toolResult) + assert.strictEqual(toolResult.name, "AnthropicMemory") + assert.strictEqual(toolResult.result, "memory listing") + })) + + it.effect("decodes the create command including file_text", () => + Effect.gen(function*() { + let receivedParams: unknown = undefined + const toolkit = Toolkit.make(AnthropicTool.Memory_20250818({})) + const toolkitLayer = toolkit.toLayer({ + AnthropicMemory: (params) => { + receivedParams = params + return Effect.succeed("File created successfully at: /memories/notes.txt") + } + }) + + const layer = AnthropicClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(memoryResponse(request, { + command: "create", + path: "/memories/notes.txt", + file_text: "hello world" + })) + ) + )) + ) + + const response = yield* LanguageModel.generateText({ + prompt: "save a note", + toolkit + }).pipe( + Effect.provide(AnthropicLanguageModel.model("claude-sonnet-4-20250514")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + assert.deepStrictEqual(receivedParams, { + command: "create", + path: "/memories/notes.txt", + file_text: "hello world" + }) + assert.strictEqual(response.toolResults[0]?.name, "AnthropicMemory") + })) + }) + + // Client-executed (`requiresHandler`) provider tools have their `parameters` + // decoded via `toCodecAnthropic` when the model calls them. Optional + // parameters must use `Schema.optionalKey` (not `Schema.optional`), otherwise + // the codec rejects the schema with "Unsupported AST Undefined" (see #2615). + describe("client provider tool parameters compile with the Anthropic codec", () => { + const displayArgs = { displayWidthPx: 800, displayHeightPx: 600 } + const clientTools: ReadonlyArray = [ + ["Bash_20241022", AnthropicTool.Bash_20241022({})], + ["Bash_20250124", AnthropicTool.Bash_20250124({})], + ["ComputerUse_20241022", AnthropicTool.ComputerUse_20241022(displayArgs)], + ["ComputerUse_20250124", AnthropicTool.ComputerUse_20250124(displayArgs)], + ["ComputerUse_20251124", AnthropicTool.ComputerUse_20251124(displayArgs)], + ["Memory_20250818", AnthropicTool.Memory_20250818({})], + ["TextEditor_20241022", AnthropicTool.TextEditor_20241022({})], + ["TextEditor_20250124", AnthropicTool.TextEditor_20250124({})], + ["TextEditor_20250429", AnthropicTool.TextEditor_20250429({})], + ["TextEditor_20250728", AnthropicTool.TextEditor_20250728({})] + ] + + for (const [name, tool] of clientTools) { + it(name, () => { + const codec = AnthropicStructuredOutput.toCodecAnthropic(tool.parametersSchema) + assert.isDefined(codec) + }) + } }) }) diff --git a/packages/ai/anthropic/tsconfig.json b/packages/ai/anthropic/tsconfig.json index 19a2f5dbc..e2a8ca19a 100644 --- a/packages/ai/anthropic/tsconfig.json +++ b/packages/ai/anthropic/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts b/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts new file mode 100644 index 000000000..79d86a5ed --- /dev/null +++ b/packages/ai/anthropic/typetest/AnthropicLanguageModel.tst.ts @@ -0,0 +1,14 @@ +import { AnthropicLanguageModel } from "@effect/ai-anthropic" +import { describe, expect, it } from "tstyche" + +declare const acceptsKnownModel: (model: AnthropicLanguageModel.Model) => void + +describe("AnthropicLanguageModel", () => { + describe("Model", () => { + it("keeps the known model ids as literals, while the constructors still accept custom ids", () => { + expect(acceptsKnownModel).type.toBeCallableWith("claude-opus-4-8") + expect(acceptsKnownModel).type.not.toBeCallableWith("not-a-real-model") + expect(AnthropicLanguageModel.model).type.toBeCallableWith("not-a-real-model") + }) + }) +}) diff --git a/packages/ai/anthropic/vitest.config.ts b/packages/ai/anthropic/vitest.config.ts deleted file mode 100644 index c8a52c182..000000000 --- a/packages/ai/anthropic/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/packages/ai/openai-compat/CHANGELOG.md b/packages/ai/openai-compat/CHANGELOG.md index f3c62d635..363f05ad6 100644 --- a/packages/ai/openai-compat/CHANGELOG.md +++ b/packages/ai/openai-compat/CHANGELOG.md @@ -1,5 +1,186 @@ # @effect/ai-openai-compat +## 4.0.0-rc.112 + +### Patch Changes + +- [#7437](https://github.com/Effect-TS/effect/pull/7437) [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb) Thanks @wmaurer! - Add an optional `description` to `AiError.AuthenticationError`, rendered after the kind-based suggestion, and pass the provider's own error text through it on HTTP 401 and 403, so authentication failures report what actually went wrong instead of only a category. +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- [#7269](https://github.com/Effect-TS/effect/pull/7269) [`ee06c9c`](https://github.com/Effect-TS/effect/commit/ee06c9c1eed73ebcf282541ceb1615ff1ba1730d) Thanks @tim-smart! - Preserve streamed text from OpenAI-compatible providers that send `tool_calls: null` on text-only chunks. +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7126](https://github.com/Effect-TS/effect/pull/7126) [`16b94c7`](https://github.com/Effect-TS/effect/commit/16b94c702419c318e0f3515c902c39cf3871ccce) Thanks @fubhy! - Fix OpenAI response telemetry attribute types to use the emitted response namespace. + +- [#7127](https://github.com/Effect-TS/effect/pull/7127) [`b588640`](https://github.com/Effect-TS/effect/commit/b588640b4f5ee8b000acf8275364852bf79fe426) Thanks @fubhy! - Fix the OpenAI-compatible telemetry response attribute namespace. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6667](https://github.com/Effect-TS/effect/pull/6667) [`5283841`](https://github.com/Effect-TS/effect/commit/52838418db2e04db6aaed2fa01b280f2aad4032a) Thanks @tim-smart! - Surface parsed chat completion stream events that do not match the expected schema as `UnknownChatCompletionEvent`. + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6882](https://github.com/Effect-TS/effect/pull/6882) [`25a029c`](https://github.com/Effect-TS/effect/commit/25a029ccf2f6478dc2ae1fca96ceed9c394deeb3) Thanks @tim-smart! - Decode streaming and non-streaming tool call parameters with the provider-facing OpenAI schema codec. + +- [#6719](https://github.com/Effect-TS/effect/pull/6719) [`20b9660`](https://github.com/Effect-TS/effect/commit/20b9660d42ae4afc00bb4251b57657e6363c8808) Thanks @IMax153! - Group consecutive tool calls into one assistant message when using Chat Completions APIs. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6650](https://github.com/Effect-TS/effect/pull/6650) [`acd385e`](https://github.com/Effect-TS/effect/commit/acd385ebb3f9edee37ab6715607119ee9762a615) Thanks @IMax153! - Redact OpenAI organization and project headers from client errors. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- [#2608](https://github.com/Effect-TS/effect-smol/pull/2608) [`5f142a2`](https://github.com/Effect-TS/effect-smol/commit/5f142a2136e62274ec121273fecb5d248c19f159) Thanks @tim-smart! - Preserve autocomplete for known OpenAI-compatible model config properties while allowing provider-specific properties. + +- [#2576](https://github.com/Effect-TS/effect-smol/pull/2576) [`0c2ed5e`](https://github.com/Effect-TS/effect-smol/commit/0c2ed5ee0ef5a437c5e6a2de456a5ddf214c281f) Thanks @tim-smart! - Normalize empty assistant message content to an empty string for OpenAI-compatible providers that reject null content values. + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/ai/openai-compat/README.md b/packages/ai/openai-compat/README.md new file mode 100644 index 000000000..68bcbb776 --- /dev/null +++ b/packages/ai/openai-compat/README.md @@ -0,0 +1,14 @@ +# @effect/ai-openai-compat + +Connects the Effect AI modules to any OpenAI-compatible API, with support for chat completions and embeddings. + +## Installation + +```sh +npm install effect@rc @effect/ai-openai-compat@rc +``` + +## Documentation + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/ai-openai-compat) diff --git a/packages/ai/openai-compat/docgen.json b/packages/ai/openai-compat/docgen.json deleted file mode 100644 index 2734de358..000000000 --- a/packages/ai/openai-compat/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/openai-compat/src/", - "exclude": ["src/internal/**"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/ai/openai-compat/package.json b/packages/ai/openai-compat/package.json index a0b9f252d..e68e72f5c 100644 --- a/packages/ai/openai-compat/package.json +++ b/packages/ai/openai-compat/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-openai-compat", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "An OpenAI compat integration for Effect", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/openai-compat" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -31,6 +31,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -38,7 +39,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -48,17 +52,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/packages/ai/openai-compat/src/OpenAiClient.ts b/packages/ai/openai-compat/src/OpenAiClient.ts index c1fe575a9..c8ecad004 100644 --- a/packages/ai/openai-compat/src/OpenAiClient.ts +++ b/packages/ai/openai-compat/src/OpenAiClient.ts @@ -11,7 +11,7 @@ import * as Array from "effect/Array" import type * as Config from "effect/Config" import * as Context from "effect/Context" import * as Effect from "effect/Effect" -import { identity, pipe } from "effect/Function" +import { identity } from "effect/Function" import * as Layer from "effect/Layer" import * as Redacted from "effect/Redacted" import * as Schema from "effect/Schema" @@ -34,7 +34,7 @@ import { OpenAiConfig } from "./OpenAiConfig.ts" * completions, streaming chat completions, and embeddings. Transport and * schema decoding failures are mapped to `AiError`. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { @@ -99,10 +99,15 @@ export type Options = { } const RedactedOpenAiHeaders = { - OpenAiOrganization: "OpenAI-Organization", - OpenAiProject: "OpenAI-Project" + OpenAiOrganization: "openai-organization", + OpenAiProject: "openai-project" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + /** * Constructs an OpenAI-compatible client service from explicit options. * @@ -175,24 +180,27 @@ export const make = Effect.fnUntraced( [body: CreateResponse200, response: HttpClientResponse.HttpClientResponse], AiError.AiError > => - Effect.flatMap(resolveHttpClient, (client) => - pipe( - HttpClientRequest.post("/chat/completions"), - HttpClientRequest.bodyJsonUnsafe(payload), - HttpClient.filterStatusOk(client).execute, - Effect.flatMap((response) => - Effect.map(decodeResponse(response), ( - body - ): [CreateResponse200, HttpClientResponse.HttpClientResponse] => [ - body, - response - ]) - ), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) - }) - )) + resolveHttpClient.pipe( + Effect.flatMap((client) => + HttpClientRequest.post("/chat/completions").pipe( + HttpClientRequest.bodyJsonUnsafe(payload), + HttpClient.filterStatusOk(client).execute, + Effect.flatMap((response) => + Effect.map(decodeResponse(response), ( + body + ): [CreateResponse200, HttpClientResponse.HttpClientResponse] => [ + body, + response + ]) + ), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) + }) + ) + ), + withRedactedHeaders + ) const buildResponseStream = ( response: HttpClientResponse.HttpClientResponse @@ -210,6 +218,7 @@ export const make = Effect.fnUntraced( Stream.takeUntil((event) => event === "[DONE]"), Stream.catchTags({ Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createResponseStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")) }) ) as any @@ -217,40 +226,46 @@ export const make = Effect.fnUntraced( } const createResponseStream: Service["createResponseStream"] = (payload) => - Effect.flatMap(resolveHttpClient, (client) => - pipe( - HttpClientRequest.post("/chat/completions"), - HttpClientRequest.bodyJsonUnsafe({ - ...payload, - stream: true, - stream_options: { - include_usage: true - } - }), - HttpClient.filterStatusOk(client).execute, - Effect.map(buildResponseStream), - Effect.catchTag( - "HttpClientError", - (error) => Errors.mapHttpClientError(error, "createResponseStream") + resolveHttpClient.pipe( + Effect.flatMap((client) => + HttpClientRequest.post("/chat/completions").pipe( + HttpClientRequest.bodyJsonUnsafe({ + ...payload, + stream: true, + stream_options: { + include_usage: true + } + }), + HttpClient.filterStatusOk(client).execute, + Effect.map(buildResponseStream), + Effect.catchTag( + "HttpClientError", + (error) => Errors.mapHttpClientError(error, "createResponseStream") + ) ) - )) + ), + withRedactedHeaders + ) const decodeEmbedding = HttpClientResponse.schemaBodyJson(CreateEmbeddingResponseSchema) const createEmbedding = ( payload: CreateEmbeddingRequestJson ): Effect.Effect => - Effect.flatMap(resolveHttpClient, (client) => - pipe( - HttpClientRequest.post("/embeddings"), - HttpClientRequest.bodyJsonUnsafe(payload), - HttpClient.filterStatusOk(client).execute, - Effect.flatMap(decodeEmbedding), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) - }) - )) + resolveHttpClient.pipe( + Effect.flatMap((client) => + HttpClientRequest.post("/embeddings").pipe( + HttpClientRequest.bodyJsonUnsafe(payload), + HttpClient.filterStatusOk(client).execute, + Effect.flatMap(decodeEmbedding), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) + }) + ) + ), + withRedactedHeaders + ) return OpenAiClient.of({ client: httpClient, @@ -259,10 +274,7 @@ export const make = Effect.fnUntraced( createEmbedding }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedOpenAiHeaders)) - ) + withRedactedHeaders ) /** @@ -340,7 +352,7 @@ type JsonObject = { readonly [x: string]: Schema.Json } /** * Optional response fields that can be requested with the `include` parameter. * - * @category response + * @category models * @since 4.0.0 */ export type IncludeEnum = @@ -379,7 +391,7 @@ type InputFileContent = { /** * Content blocks accepted in input messages. * - * @category request + * @category models * @since 4.0.0 */ export type InputContent = InputTextContent | InputImageContent | InputFileContent @@ -387,7 +399,7 @@ export type InputContent = InputTextContent | InputImageContent | InputFileConte /** * Text content block used for model-provided reasoning summaries. * - * @category response + * @category models * @since 4.0.0 */ export type SummaryTextContent = { @@ -449,7 +461,7 @@ type FilePathAnnotation = { /** * Citation and file-path annotations attached to output text content. * - * @category response + * @category models * @since 4.0.0 */ export type Annotation = @@ -488,7 +500,7 @@ type OutputMessage = { * Reasoning output item containing encrypted reasoning content, summaries, and * optional reasoning text. * - * @category response + * @category models * @since 4.0.0 */ export type ReasoningItem = { @@ -545,7 +557,7 @@ type ItemReference = { * Supports input messages, output messages, tool calls, tool outputs, reasoning * items, custom tool interactions, and item references. * - * @category request + * @category models * @since 4.0.0 */ export type InputItem = @@ -586,7 +598,7 @@ type CustomToolParam = { /** * Tool definitions that can be supplied to a Responses-style request. * - * @category request + * @category models * @since 4.0.0 */ export type Tool = @@ -637,7 +649,7 @@ export type TextResponseFormatConfiguration = * Request options for creating a Responses-style response with an * OpenAI-compatible provider. * - * @category request + * @category models * @since 4.0.0 */ export type CreateResponse = { @@ -677,7 +689,7 @@ export type CreateResponse = { /** * Token accounting reported on Responses-style response objects. * - * @category response + * @category models * @since 4.0.0 */ export type ResponseUsage = { @@ -698,7 +710,7 @@ type OutputItem = * Responses-style response object returned by compatible providers or embedded * in response stream lifecycle events. * - * @category response + * @category models * @since 4.0.0 */ export type Response = { @@ -855,7 +867,7 @@ export type ResponseStreamEvent = * string. The `index` field identifies the input item that produced this * embedding. * - * @category response + * @category models * @since 4.0.0 */ export type Embedding = { @@ -867,7 +879,7 @@ export type Embedding = { /** * Request payload for the embeddings endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type CreateEmbeddingRequest = { @@ -881,7 +893,7 @@ export type CreateEmbeddingRequest = { /** * Successful response payload returned by the embeddings endpoint. * - * @category response + * @category models * @since 4.0.0 */ export type CreateEmbeddingResponse = { @@ -897,21 +909,21 @@ export type CreateEmbeddingResponse = { /** * JSON request body accepted by the embeddings endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type CreateEmbeddingRequestJson = CreateEmbeddingRequest /** * Decoded successful embeddings response body. * - * @category response + * @category models * @since 4.0.0 */ export type CreateEmbedding200 = CreateEmbeddingResponse /** * Structured content parts accepted in chat completion messages. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionContentPart = @@ -929,7 +941,7 @@ export type ChatCompletionContentPart = /** * Tool call data attached to an assistant chat completion message. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionRequestToolCall = { @@ -943,7 +955,7 @@ export type ChatCompletionRequestToolCall = { /** * Message shapes accepted by the chat completions endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionRequestMessage = @@ -960,7 +972,7 @@ export type ChatCompletionRequestMessage = /** * Function tool definition accepted by the chat completions endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionTool = { @@ -1010,7 +1022,7 @@ export type ChatCompletionResponseFormat = /** * Request payload for the OpenAI-compatible chat completions endpoint. * - * @category request + * @category models * @since 4.0.0 */ export type ChatCompletionRequest = { @@ -1036,14 +1048,14 @@ export type ChatCompletionRequest = { /** * JSON request body used by this client when creating a chat completion response. * - * @category request + * @category models * @since 4.0.0 */ export type CreateResponseRequestJson = ChatCompletionRequest /** * Decoded successful chat completion response body returned by `createResponse`. * - * @category response + * @category models * @since 4.0.0 */ export type CreateResponse200 = ChatCompletionResponse @@ -1056,8 +1068,8 @@ export type CreateResponse200 = ChatCompletionResponse export type CreateResponse200Sse = ChatCompletionStreamEvent const EmbeddingSchema = Schema.Struct({ - embedding: Schema.Union([Schema.Array(Schema.Number), Schema.String]), - index: Schema.Number, + embedding: Schema.Union([Schema.Array(Schema.Finite), Schema.String]), + index: Schema.Int, object: Schema.optionalKey(Schema.String) }) @@ -1066,8 +1078,8 @@ const CreateEmbeddingResponseSchema = Schema.Struct({ model: Schema.String, object: Schema.optionalKey(Schema.Literal("list")), usage: Schema.optionalKey(Schema.Struct({ - prompt_tokens: Schema.Number, - total_tokens: Schema.Number + prompt_tokens: Schema.Int, + total_tokens: Schema.Int })) }) @@ -1086,14 +1098,14 @@ const ChatCompletionToolFunctionDelta = Schema.Struct({ const ChatCompletionToolCall = Schema.Struct({ id: Schema.optionalKey(Schema.String), - index: Schema.optionalKey(Schema.Number), + index: Schema.optionalKey(Schema.Int), type: Schema.optionalKey(Schema.String), function: Schema.optionalKey(ChatCompletionToolFunction) }) const ChatCompletionToolCallDelta = Schema.Struct({ id: Schema.optionalKey(Schema.String), - index: Schema.optionalKey(Schema.Number), + index: Schema.optionalKey(Schema.Int), type: Schema.optionalKey(Schema.String), function: Schema.optionalKey(ChatCompletionToolFunctionDelta) }) @@ -1111,20 +1123,23 @@ const ChatCompletionDelta = Schema.Struct({ content: Schema.optionalKey(Schema.NullOr(Schema.String)), reasoning: Schema.optionalKey(Schema.NullOr(Schema.String)), reasoning_content: Schema.optionalKey(Schema.NullOr(Schema.String)), - tool_calls: Schema.optionalKey(Schema.Array(ChatCompletionToolCallDelta)) + // Some OpenAI-compatible providers send `tool_calls: null` when a streamed + // chunk contains only text. Accepting null keeps the text-bearing chunk from + // being classified as an unknown event. + tool_calls: Schema.optionalKey(Schema.NullOr(Schema.Array(ChatCompletionToolCallDelta))) }) const ChatCompletionChoice = Schema.Struct({ - index: Schema.Number, + index: Schema.Int, finish_reason: Schema.optionalKey(Schema.NullOr(Schema.String)), message: Schema.optionalKey(ChatCompletionMessage), delta: Schema.optionalKey(ChatCompletionDelta) }) const ChatCompletionUsage = Schema.Struct({ - prompt_tokens: Schema.Number, - completion_tokens: Schema.Number, - total_tokens: Schema.Number, + prompt_tokens: Schema.Int, + completion_tokens: Schema.Int, + total_tokens: Schema.Int, prompt_tokens_details: Schema.optionalKey(Schema.Any), completion_tokens_details: Schema.optionalKey(Schema.Any) }) @@ -1132,7 +1147,7 @@ const ChatCompletionUsage = Schema.Struct({ const ChatCompletionResponse = Schema.Struct({ id: Schema.String, model: Schema.String, - created: Schema.Number, + created: Schema.Int, choices: Schema.Array(ChatCompletionChoice), usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), service_tier: Schema.optionalKey(Schema.String) @@ -1141,7 +1156,7 @@ const ChatCompletionResponse = Schema.Struct({ const ChatCompletionChunk = Schema.Struct({ id: Schema.String, model: Schema.String, - created: Schema.Number, + created: Schema.Int, choices: Schema.Array(ChatCompletionChoice), usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), service_tier: Schema.optionalKey(Schema.String) @@ -1150,35 +1165,35 @@ const ChatCompletionChunk = Schema.Struct({ /** * Decoded tool-call object from a chat completion response or streaming chunk. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionToolCall = typeof ChatCompletionToolCall.Type /** * Decoded message object from a non-streaming chat completion choice. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionMessage = typeof ChatCompletionMessage.Type /** * Decoded choice object returned by chat completion responses and chunks. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionChoice = typeof ChatCompletionChoice.Type /** * Decoded token usage summary returned by chat completions. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionUsage = typeof ChatCompletionUsage.Type /** * Decoded successful response from the chat completions endpoint. * - * @category response + * @category models * @since 4.0.0 */ export type ChatCompletionResponse = typeof ChatCompletionResponse.Type @@ -1190,13 +1205,23 @@ export type ChatCompletionResponse = typeof ChatCompletionResponse.Type */ export type ChatCompletionChunk = typeof ChatCompletionChunk.Type /** - * Streaming chat completion event, including decoded chunks and the `[DONE]` - * sentinel. + * A parsed chat completion event that does not match the expected chunk schema. * * @category streaming * @since 4.0.0 */ -export type ChatCompletionStreamEvent = ChatCompletionChunk | "[DONE]" +export interface UnknownChatCompletionEvent { + readonly _tag: "UnknownChatCompletionEvent" + readonly data: unknown +} +/** + * Streaming chat completion event, including decoded chunks, unknown parsed + * events, and the `[DONE]` sentinel. + * + * @category streaming + * @since 4.0.0 + */ +export type ChatCompletionStreamEvent = ChatCompletionChunk | UnknownChatCompletionEvent | "[DONE]" const parseJson = (value: string): unknown => { try { @@ -1215,7 +1240,11 @@ const decodeChatCompletionSseData = ( return data } const parsed = parseJson(data) - return isChatCompletionChunk(parsed) - ? parsed - : undefined + if (parsed === undefined) { + return undefined + } + return isChatCompletionChunk(parsed) ? parsed : { + _tag: "UnknownChatCompletionEvent", + data: parsed + } } diff --git a/packages/ai/openai-compat/src/OpenAiConfig.ts b/packages/ai/openai-compat/src/OpenAiConfig.ts index 452144aee..004b155da 100644 --- a/packages/ai/openai-compat/src/OpenAiConfig.ts +++ b/packages/ai/openai-compat/src/OpenAiConfig.ts @@ -36,7 +36,7 @@ export class OpenAiConfig extends Context.Service< */ static readonly getOrUndefined: Effect.Effect = Effect.map( Effect.context(), - (context) => context.mapUnsafe.get(OpenAiConfig.key) + Context.getOrUndefined(OpenAiConfig) ) } @@ -50,7 +50,7 @@ export declare namespace OpenAiConfig { * Configuration consumed by OpenAI-compatible clients when they build or * resolve the underlying HTTP client. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts b/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts index 1ae6c89f0..0cd5393aa 100644 --- a/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts +++ b/packages/ai/openai-compat/src/OpenAiEmbeddingModel.ts @@ -26,6 +26,9 @@ import { OpenAiClient } from "./OpenAiClient.ts" */ export type Model = string +type ConfigOptions = Simplify>> +type ModelConfig = Omit & { readonly [x: string]: unknown } + /** * Context service for OpenAI embedding model configuration. * @@ -43,22 +46,12 @@ export type Model = string * * @see {@link withConfigOverride} for scoping embedding request overrides * - * @category context + * @category services * @since 4.0.0 */ export class Config extends Context.Service< Config, - Simplify< - & Partial< - Omit< - CreateEmbeddingRequestJson, - "input" - > - > - & { - readonly [x: string]: unknown - } - > + ConfigOptions & { readonly [x: string]: unknown } >()("@effect/ai-openai-compat/OpenAiEmbeddingModel/Config") {} /** @@ -77,9 +70,9 @@ export class Config extends Context.Service< */ export const model = ( model: string, - options: { + options: Omit & { readonly dimensions: number - readonly config?: Omit + readonly [x: string]: unknown } ): AiModel.Model<"openai", EmbeddingModel.EmbeddingModel | EmbeddingModel.Dimensions, OpenAiClient> => AiModel.make( @@ -88,10 +81,7 @@ export const model = ( Layer.merge( layer({ model, - config: { - ...options.config, - dimensions: options.dimensions - } + config: options }), Layer.succeed(EmbeddingModel.Dimensions, options.dimensions) ) @@ -127,14 +117,13 @@ export const model = ( */ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Effect.fn.Return { const client = yield* OpenAiClient - const makeConfig = Effect.gen(function*() { - const services = yield* Effect.context() - return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) } - }) + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) return yield* EmbeddingModel.make({ embedMany: Effect.fnUntraced(function*({ inputs }) { @@ -162,7 +151,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig */ export const layer = (options: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Layer.Layer => Layer.effect(EmbeddingModel.EmbeddingModel, make(options)) diff --git a/packages/ai/openai-compat/src/OpenAiLanguageModel.ts b/packages/ai/openai-compat/src/OpenAiLanguageModel.ts index acce3af13..099acc58f 100644 --- a/packages/ai/openai-compat/src/OpenAiLanguageModel.ts +++ b/packages/ai/openai-compat/src/OpenAiLanguageModel.ts @@ -16,8 +16,9 @@ import { dual } from "effect/Function" import * as Layer from "effect/Layer" import * as Option from "effect/Option" import * as Predicate from "effect/Predicate" +import * as Rec from "effect/Record" import * as Redactable from "effect/Redactable" -import type * as Schema from "effect/Schema" +import * as Schema from "effect/Schema" import * as AST from "effect/SchemaAST" import * as Stream from "effect/Stream" import type { Span } from "effect/Tracer" @@ -35,6 +36,7 @@ import * as InternalUtilities from "./internal/utilities.ts" import { type Annotation, type ChatCompletionContentPart, + type ChatCompletionRequestToolCall, type CreateResponse, type CreateResponse200, type CreateResponse200Sse, @@ -47,7 +49,8 @@ import { type ReasoningItem, type SummaryTextContent, type TextResponseFormatConfiguration, - type Tool as OpenAiClientTool + type Tool as OpenAiClientTool, + type UnknownChatCompletionEvent } from "./OpenAiClient.ts" import { addGenAIAnnotations } from "./OpenAiTelemetry.ts" @@ -60,6 +63,43 @@ type ImageDetail = "auto" | "low" | "high" // Configuration // ============================================================================= +type ConfigOptions = Simplify< + & Partial< + Omit + > + & { + /** + * File ID prefixes used to identify file IDs in Responses API. + * When undefined, all file data is treated as base64 content. + * + * Examples: + * - OpenAI: ['file-'] for IDs like 'file-abc123' + * - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123' + */ + readonly fileIdPrefixes?: ReadonlyArray | undefined + /** + * Configuration options for a text response from the model. + */ + readonly text?: { + /** + * Constrains the verbosity of the model's response. Lower values will + * result in more concise responses, while higher values will result in + * more verbose responses. + * + * Defaults to `"medium"`. + */ + readonly verbosity?: "low" | "medium" | "high" | undefined + } | undefined + /** + * Whether to use strict JSON schema validation. + * + * Defaults to `true`. + */ + readonly strictJsonSchema?: boolean | undefined + } +> +type ModelConfig = Omit & { readonly [x: string]: unknown } + /** * Context service for OpenAI language model configuration. * @@ -71,50 +111,12 @@ type ImageDetail = "auto" | "low" | "high" * * @see {@link withConfigOverride} for scoping language model request overrides * - * @category context + * @category services * @since 4.0.0 */ export class Config extends Context.Service< Config, - Simplify< - & Partial< - Omit< - CreateResponse, - "input" | "tools" | "tool_choice" | "stream" | "text" - > - > - & { - /** - * File ID prefixes used to identify file IDs in Responses API. - * When undefined, all file data is treated as base64 content. - * - * Examples: - * - OpenAI: ['file-'] for IDs like 'file-abc123' - * - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123' - */ - readonly fileIdPrefixes?: ReadonlyArray | undefined - /** - * Configuration options for a text response from the model. - */ - readonly text?: { - /** - * Constrains the verbosity of the model's response. Lower values will - * result in more concise responses, while higher values will result in - * more verbose responses. - * - * Defaults to `"medium"`. - */ - readonly verbosity?: "low" | "medium" | "high" | undefined - } | undefined - /** - * Whether to use strict JSON schema validation. - * - * Defaults to `true`. - */ - readonly strictJsonSchema?: boolean | undefined - readonly [x: string]: unknown - } - > + ConfigOptions & { readonly [x: string]: unknown } >()("@effect/ai-openai-compat/OpenAiLanguageModel/Config") {} // ============================================================================= @@ -125,7 +127,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for file prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -143,7 +145,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for reasoning prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -167,7 +169,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for assistant tool-call prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -189,7 +191,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for tool-result prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { @@ -211,7 +213,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-compatible options for text prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -239,7 +241,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to a complete text response part. * - * @category response + * @category models * @since 4.0.0 */ export interface TextPartMetadata extends ProviderMetadata { @@ -271,7 +273,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed text part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface TextStartPartMetadata extends ProviderMetadata { @@ -289,7 +291,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed text part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface TextEndPartMetadata extends ProviderMetadata { @@ -311,7 +313,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to a complete reasoning response part. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -333,7 +335,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed reasoning part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -355,7 +357,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted for a streamed reasoning delta. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -373,7 +375,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata emitted when a streamed reasoning part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningEndPartMetadata extends ProviderMetadata { @@ -395,7 +397,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to tool-call response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -413,7 +415,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to document source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface DocumentSourcePartMetadata extends ProviderMetadata { @@ -469,7 +471,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to URL source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -495,7 +497,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI-compatible metadata attached to finish response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -531,7 +533,7 @@ declare module "effect/unstable/ai/Response" { */ export const model = ( model: string, - config?: Omit + config?: ModelConfig ): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> => AiModel.make("openai", model, layer({ model, config })) @@ -569,14 +571,13 @@ export const model = ( */ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Effect.fn.Return { const client = yield* OpenAiClient - const makeConfig = Effect.gen(function*() { - const services = yield* Effect.context() - return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) } - }) + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) const makeRequest = Effect.fnUntraced( function*>({ config, options, toolNameMapper }: { @@ -629,6 +630,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig const [rawResponse, response] = yield* client.createResponse(request) annotateResponse(options.span, rawResponse) return yield* makeResponse({ + options, rawResponse, response, toolNameMapper @@ -643,6 +645,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig annotateRequest(options.span, request) const [response, stream] = yield* client.createResponseStream(request) return yield* makeStreamResponse({ + options, stream, response, toolNameMapper @@ -677,7 +680,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig */ export const layer = (options: { readonly model: string - readonly config?: Omit | undefined + readonly config?: ModelConfig | undefined }): Layer.Layer => Layer.effect(LanguageModel.LanguageModel, make(options)) @@ -784,15 +787,14 @@ const prepareMessages = Effect.fnUntraced( if (typeof part.data === "string" && isFileId(part.data, config)) { content.push({ type: "input_image", file_id: part.data, detail }) - } - - if (part.data instanceof URL) { - content.push({ type: "input_image", image_url: part.data.toString(), detail }) - } - - if (part.data instanceof Uint8Array) { - const base64 = Encoding.encodeBase64(part.data) - const imageUrl = `data:${mediaType};base64,${base64}` + } else { + const imageUrl = part.data instanceof URL + ? part.data.toString() + : part.data instanceof Uint8Array + ? `data:${mediaType};base64,${Encoding.encodeBase64(part.data)}` + : /^(data:|https?:\/\/)/i.test(part.data) + ? part.data + : `data:${mediaType};base64,${part.data}` content.push({ type: "input_image", image_url: imageUrl, detail }) } } else if (part.mediaType === "application/pdf") { @@ -829,7 +831,7 @@ const prepareMessages = Effect.fnUntraced( } case "assistant": { - const reasoningMessages: Record> = {} + const reasoningMessages: Record> = Object.create(null) for (const part of message.content) { switch (part.type) { @@ -941,7 +943,6 @@ const prepareMessages = Effect.fnUntraced( type: "function_call", name: toolName, call_id: part.id, - // @effect-diagnostics-next-line preferSchemaOverJson:off arguments: JSON.stringify(part.params), ...(Predicate.isNotNull(id) ? { id } : {}), ...(Predicate.isNotNull(status) ? { status } : {}) @@ -987,7 +988,6 @@ const prepareMessages = Effect.fnUntraced( messages.push({ type: "function_call_output", call_id: part.id, - // @effect-diagnostics-next-line preferSchemaOverJson:off output: typeof part.result === "string" ? part.result : JSON.stringify(part.result), ...(Predicate.isNotNull(status) ? { status } : {}) }) @@ -1029,6 +1029,11 @@ const buildHttpResponseDetails = ( type ResponseStreamEvent = CreateResponse200Sse +const isUnknownChatCompletionEvent = ( + event: ResponseStreamEvent +): event is UnknownChatCompletionEvent => + typeof event !== "string" && "_tag" in event && event._tag === "UnknownChatCompletionEvent" + type ActiveToolCall = { readonly id: string name: string @@ -1037,10 +1042,12 @@ type ActiveToolCall = { const makeResponse = Effect.fnUntraced( function*>({ + options, rawResponse, response, toolNameMapper }: { + readonly options: LanguageModel.ProviderOptions readonly rawResponse: CreateResponse200 readonly response: HttpClientResponse.HttpClientResponse readonly toolNameMapper: Tool.NameMapper @@ -1079,20 +1086,20 @@ const makeResponse = Effect.fnUntraced( for (const [index, toolCall] of message.tool_calls.entries()) { const toolId = toolCall.id ?? `${rawResponse.id}_tool_${index}` const toolName = toolNameMapper.getCustomName(toolCall.function?.name ?? "unknown_tool") - const toolParams = toolCall.function?.arguments ?? "{}" - const params = yield* Effect.try({ - try: () => Tool.unsafeSecureJsonParse(toolParams), + const toolParamsJson = toolCall.function?.arguments ?? "{}" + const toolParams = yield* Effect.try({ + try: () => Tool.unsafeSecureJsonParse(toolParamsJson), catch: (cause) => AiError.make({ module: "OpenAiLanguageModel", method: "makeResponse", reason: new AiError.ToolParameterValidationError({ toolName, - toolParams: {}, description: `Failed to securely JSON parse tool parameters: ${cause}` }) }) }) + const params = yield* transformToolCallParams(options.tools, toolName, toolParams) hasToolCalls = true parts.push({ type: "tool-call", @@ -1125,10 +1132,12 @@ const makeResponse = Effect.fnUntraced( const makeStreamResponse = Effect.fnUntraced( function*>({ + options, stream, response, toolNameMapper }: { + readonly options: LanguageModel.ProviderOptions readonly stream: Stream.Stream readonly response: HttpClientResponse.HttpClientResponse readonly toolNameMapper: Tool.NameMapper @@ -1170,7 +1179,7 @@ const makeStreamResponse = Effect.fnUntraced( for (const toolCall of Object.values(activeToolCalls)) { const toolParams = toolCall.arguments.length > 0 ? toolCall.arguments : "{}" - const params = yield* Effect.try({ + const parsedParams = yield* Effect.try({ try: () => Tool.unsafeSecureJsonParse(toolParams), catch: (cause) => AiError.make({ @@ -1178,11 +1187,11 @@ const makeStreamResponse = Effect.fnUntraced( method: "makeStreamResponse", reason: new AiError.ToolParameterValidationError({ toolName: toolCall.name, - toolParams: {}, description: `Failed to securely JSON parse tool parameters: ${cause}` }) }) }) + const params = yield* transformToolCallParams(options.tools, toolCall.name, parsedParams) parts.push({ type: "tool-params-end", id: toolCall.id }) parts.push({ type: "tool-call", @@ -1207,6 +1216,12 @@ const makeStreamResponse = Effect.fnUntraced( return parts } + // Keep unknown events available to direct client consumers; this layer + // cannot translate provider-specific data into portable stream parts. + if (isUnknownChatCompletionEvent(event)) { + return parts + } + if (event.service_tier !== undefined) { serviceTier = event.service_tier } @@ -1266,7 +1281,7 @@ const makeStreamResponse = Effect.fnUntraced( parts.push({ type: "text-delta", id: textId, delta: choice.delta.content }) } - if (choice.delta?.tool_calls !== undefined) { + if (Predicate.isNotNullish(choice.delta?.tool_calls)) { hasToolCalls = hasToolCalls || choice.delta.tool_calls.length > 0 choice.delta.tool_calls.forEach((deltaTool, indexInChunk) => { const toolIndex = deltaTool.index ?? indexInChunk @@ -1394,6 +1409,12 @@ const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError }) }) +const tryCodecTransform = (schema: S, method: string) => + Effect.try({ + try: () => toCodecOpenAI(schema), + catch: (error) => unsupportedSchemaError(error, method) + }) + const tryJsonSchema = (schema: S, method: string) => Effect.try({ try: () => Tool.getJsonSchemaFromSchema(schema, { transformer: toCodecOpenAI }), @@ -1406,6 +1427,37 @@ const tryToolJsonSchema = (tool: T, method: string) => catch: (error) => unsupportedSchemaError(error, method) }) +const transformToolCallParams = Effect.fnUntraced(function*>( + tools: Tools, + toolName: string, + toolParams: unknown +): Effect.fn.Return { + const tool = tools.find((tool) => tool.name === toolName) + + if (Predicate.isUndefined(tool)) { + return yield* AiError.make({ + module: "OpenAiLanguageModel", + method: "makeResponse", + reason: new AiError.ToolNotFoundError({ + toolName, + availableTools: tools.map((tool) => tool.name) + }) + }) + } + + const { codec } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse") + + // Normalize valid parameters; leave invalid ones for Toolkit. + return yield* ( + Schema.decodeEffect(codec)(toolParams) as Effect.Effect + ).pipe( + Effect.flatMap((decoded) => + Schema.encodeUnknownEffect(tool.parametersSchema)(decoded) as Effect.Effect + ), + Effect.orElseSucceed(() => toolParams) + ) +}) + const prepareTools = Effect.fnUntraced(function*>({ config, options, @@ -1544,7 +1596,7 @@ const extractCustomRequestProperties = (payload: CreateResponse): Record = {} for (const [key, value] of Object.entries(payload)) { if (!createResponseKnownProperties.has(key)) { - customProperties[key] = value + Rec.assignProperty(customProperties, key, value) } } return customProperties @@ -1656,7 +1708,24 @@ const toChatMessages = ( const messages: Array = [] for (const item of input) { - messages.push(...toChatMessagesFromItem(item)) + if (Predicate.hasProperty(item, "type") && item.type === "function_call") { + const previous = messages.at(-1) + const toolCall = toChatToolCall(item) + if (previous?.role === "assistant" && previous.tool_calls !== undefined) { + messages[messages.length - 1] = { + ...previous, + tool_calls: [...previous.tool_calls, toolCall] + } + } else { + messages.push({ + role: "assistant", + content: null, + tool_calls: [toolCall] + }) + } + } else { + messages.push(...toChatMessagesFromItem(item)) + } } return messages @@ -1684,14 +1753,7 @@ const toChatMessagesFromItem = ( return [{ role: "assistant", content: null, - tool_calls: [{ - id: item.call_id, - type: "function", - function: { - name: item.name, - arguments: item.arguments - } - }] + tool_calls: [toChatToolCall(item)] }] } @@ -1709,12 +1771,23 @@ const toChatMessagesFromItem = ( } } +const toChatToolCall = ( + item: Extract +): ChatCompletionRequestToolCall => ({ + id: item.call_id, + type: "function", + function: { + name: item.name, + arguments: item.arguments + } +}) + const toAssistantChatMessageContent = ( content: ReadonlyArray<{ readonly type: string readonly [x: string]: unknown }> -): string | null => { +): string => { let text = "" for (const part of content) { if (part.type === "output_text" && typeof part.text === "string") { @@ -1724,7 +1797,7 @@ const toAssistantChatMessageContent = ( text += part.refusal } } - return text.length > 0 ? text : null + return text } const toChatMessageContent = ( diff --git a/packages/ai/openai-compat/src/OpenAiTelemetry.ts b/packages/ai/openai-compat/src/OpenAiTelemetry.ts index 349a139a0..ab4d10147 100644 --- a/packages/ai/openai-compat/src/OpenAiTelemetry.ts +++ b/packages/ai/openai-compat/src/OpenAiTelemetry.ts @@ -28,7 +28,7 @@ import * as Telemetry from "effect/unstable/ai/Telemetry" export type OpenAiTelemetryAttributes = Simplify< & Telemetry.GenAITelemetryAttributes & Telemetry.AttributesWithPrefix - & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix > /** diff --git a/packages/ai/openai-compat/src/internal/errors.ts b/packages/ai/openai-compat/src/internal/errors.ts index 2636d003a..500289c5c 100644 --- a/packages/ai/openai-compat/src/internal/errors.ts +++ b/packages/ai/openai-compat/src/internal/errors.ts @@ -10,6 +10,7 @@ import * as SchemaTransformation from "effect/SchemaTransformation" import * as String from "effect/String" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -22,7 +23,7 @@ export const OpenAiErrorBody = Schema.Struct({ type: Schema.optional(Schema.NullOr(Schema.String)), status: Schema.optional(Schema.NullOr(Schema.String)), param: Schema.optional(Schema.NullOr(Schema.String)), - code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Number]))) + code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite]))) }) }) const OpenAiErrorBodyJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Union([ @@ -49,6 +50,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapHttpClientError = dual< (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect, @@ -195,46 +207,6 @@ export const buildHttpContext = (params: { body: params.body }) -const buildInvalidRequestDescription = (params: { - readonly status: number - readonly message: string | undefined - readonly method: string - readonly url: string - readonly errorCode: string | null - readonly errorType: string | null - readonly requestId: string | null - readonly body: string | undefined -}): string => { - const parts: Array = [] - - if (params.message) { - parts.push(params.message) - } else { - parts.push(`HTTP ${params.status}`) - } - - parts.push(`(${params.method} ${params.url})`) - - if (params.errorCode) { - parts.push(`[code: ${params.errorCode}]`) - } else if (params.errorType) { - parts.push(`[type: ${params.errorType}]`) - } - - if (params.requestId) { - parts.push(`[requestId: ${params.requestId}]`) - } - - if (!params.message && params.body) { - const truncated = params.body.length > 200 - ? params.body.slice(0, 200) + "..." - : params.body - parts.push(`Response: ${truncated}`) - } - - return parts.join(" ") -} - /** @internal */ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { readonly status: number @@ -243,7 +215,7 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http readonly metadata: OpenAiErrorMetadata readonly http: typeof AiError.HttpContext.Type }): AiError.AiErrorReason => { - const invalidRequestDescription = buildInvalidRequestDescription({ + const errorDescription = AiError.buildErrorDescription({ status, message, method: http.request.method, @@ -257,32 +229,34 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http switch (status) { case 400: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openai: metadata }, http }) case 401: return new AiError.AuthenticationError({ kind: "InvalidKey", + description: errorDescription, metadata, http }) case 403: return new AiError.AuthenticationError({ kind: "InsufficientPermissions", + description: errorDescription, metadata, http }) case 404: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openai: metadata }, http }) case 409: case 422: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openai: metadata }, http }) diff --git a/packages/ai/openai-compat/src/internal/utilities.ts b/packages/ai/openai-compat/src/internal/utilities.ts index a67365d98..75daf6b7d 100644 --- a/packages/ai/openai-compat/src/internal/utilities.ts +++ b/packages/ai/openai-compat/src/internal/utilities.ts @@ -16,7 +16,7 @@ export const resolveFinishReason = ( if (finishReason == null) { return hasToolCalls ? "tool-calls" : "stop" } - const reason = finishReasonMap[finishReason] + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined if (reason == null) { return hasToolCalls ? "tool-calls" : "unknown" } diff --git a/packages/ai/openai-compat/test/OpenAiClient.test.ts b/packages/ai/openai-compat/test/OpenAiClient.test.ts index 2d89ffb26..7cae883a9 100644 --- a/packages/ai/openai-compat/test/OpenAiClient.test.ts +++ b/packages/ai/openai-compat/test/OpenAiClient.test.ts @@ -1,65 +1,43 @@ -import * as OpenAiClient from "@effect/ai-openai-compat/OpenAiClient" +import { OpenAiClient } from "@effect/ai-openai-compat" +import * as Errors from "@effect/ai-openai-compat/internal/errors" import { assert, describe, it } from "@effect/vitest" -import { Effect, Layer, Redacted, Stream } from "effect" -import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" +import { Context, Effect, Layer, Redacted, type Schema, Stream } from "effect" +import { + Headers, + HttpClient, + type HttpClientError, + type HttpClientRequest, + HttpClientResponse +} from "effect/unstable/http" describe("OpenAiClient", () => { describe("request behavior", () => { it.effect("sets auth and OpenAI headers on /chat/completions requests", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key"), - apiUrl: "https://compat.example.test/v1", - organizationId: Redacted.make("org_123"), - projectId: Redacted.make("proj_456") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hello" }] }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) - assert.isTrue(capturedRequest.url.endsWith("/chat/completions")) - assert.isTrue(capturedRequest.url.startsWith("https://compat.example.test/v1")) - assert.strictEqual(capturedRequest.headers["authorization"], "Bearer sk-test-key") - assert.strictEqual(capturedRequest.headers["openai-organization"], "org_123") - assert.strictEqual(capturedRequest.headers["openai-project"], "proj_456") + assert.isTrue(request.url.endsWith("/chat/completions")) + assert.isTrue(request.url.startsWith("https://compat.example.test/v1")) + assert.strictEqual(request.headers["authorization"], "Bearer sk-test-key") + assert.strictEqual(request.headers["openai-organization"], "org_123") + assert.strictEqual(request.headers["openai-project"], "proj_456") - const body = yield* getRequestBody(capturedRequest) assert.strictEqual(body.messages[0]?.role, "user") assert.strictEqual(body.messages[0]?.content, "hello") - })) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("passes custom chat-completions request properties through", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o-mini", @@ -69,161 +47,188 @@ describe("OpenAiClient", () => { } }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } - - const body = yield* getRequestBody(capturedRequest) + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) assert.deepStrictEqual(body.provider_feature, { enabled: true }) - })) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("uses /embeddings path and decodes permissive embedding payloads", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key"), - apiUrl: "https://compat.example.test/v1" - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, { - data: [{ - embedding: "YmFzZTY0LWRhdGE=", - index: 0, - object: "embedding", - vendor_payload: { future_field: true } - }], - model: "my-custom-embedding-model", - object: "list", - usage: { - prompt_tokens: 5, - total_tokens: 5 - }, - unknown_top_level: true - })) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient const embedding = yield* client.createEmbedding({ model: "my-custom-embedding-model", input: "embed this" }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] - assert.isTrue(capturedRequest.url.endsWith("/embeddings")) + assert.isTrue(request.url.endsWith("/embeddings")) assert.strictEqual(embedding.model, "my-custom-embedding-model") assert.strictEqual(embedding.data[0]?.index, 0) assert.strictEqual(typeof embedding.data[0]?.embedding, "string") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + body: { + data: [{ + embedding: "YmFzZTY0LWRhdGE=", + index: 0, + object: "embedding", + vendor_payload: { future_field: true } + }], + model: "my-custom-embedding-model", + object: "list", + usage: { + prompt_tokens: 5, + total_tokens: 5 + }, + unknown_top_level: true + } + })))) it.effect("sets stream=true for createResponseStream and returns chat chunks", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + const client = yield* OpenAiClient.OpenAiClient - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") + const events = yield* client.createResponseStream({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(sseResponse(request, [ - { - id: "chatcmpl_test_1", - object: "chat.completion.chunk", - model: "gpt-4o-mini", - created: 1, - future_provider_field: { accepted: true }, - choices: [{ - index: 0, - delta: { content: "Hello" }, - finish_reason: null - }] - }, - { - id: "chatcmpl_test_1", - object: "chat.completion.chunk", - model: "gpt-4o-mini", - created: 1, - usage: { - prompt_tokens: 4, - completion_tokens: 2, - total_tokens: 6, - prompt_tokens_details: { cached_tokens: 1 }, - completion_tokens_details: { reasoning_tokens: 1 } - }, - choices: [{ - index: 0, - delta: {}, - finish_reason: "stop" - }] - }, - "[DONE]" - ])) - }) - )) + Effect.flatMap(([_, stream]) => Stream.runCollect(stream)) ) - const eventsChunk = yield* client.createResponseStream({ + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) + assert.strictEqual(body.stream, true) + assert.strictEqual(body.stream_options.include_usage, true) + assert.isTrue(request.url.endsWith("/chat/completions")) + + assert.propertyVal(events[0], "id", "chatcmpl_test_1") + assert.propertyVal(events[1], "id", "chatcmpl_test_1") + assert.strictEqual(events[2], "[DONE]") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Sse", + events: [ + { + id: "chatcmpl_test_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + future_provider_field: { accepted: true }, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }, + { + id: "chatcmpl_test_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + usage: { + prompt_tokens: 4, + completion_tokens: 2, + total_tokens: 6, + prompt_tokens_details: { cached_tokens: 1 }, + completion_tokens_details: { reasoning_tokens: 1 } + }, + choices: [{ + index: 0, + delta: {}, + finish_reason: "stop" + }] + }, + "[DONE]" + ] + })))) + + it.effect("surfaces schema-mismatched chat chunks and continues streaming", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + + const events = yield* client.createResponseStream({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hello" }] }).pipe( Effect.flatMap(([_, stream]) => Stream.runCollect(stream)) ) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } - - const body = yield* getRequestBody(capturedRequest) - assert.strictEqual(body.stream, true) - assert.strictEqual(body.stream_options.include_usage, true) - assert.isTrue(capturedRequest.url.endsWith("/chat/completions")) - - const events = globalThis.Array.from(eventsChunk) - const firstEvent = events[0] - const secondEvent = events[1] - assert.isTrue(typeof firstEvent === "object") - assert.isTrue(typeof secondEvent === "object") - if ( - typeof firstEvent !== "object" || firstEvent === null || typeof secondEvent !== "object" || - secondEvent === null - ) { - return - } - assert.strictEqual(firstEvent.id, "chatcmpl_test_1") - assert.strictEqual(secondEvent.id, "chatcmpl_test_1") + assert.deepStrictEqual(events[0], { + _tag: "UnknownChatCompletionEvent", + data: { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + } + }) + assert.propertyVal(events[1], "id", "chatcmpl_test_2") assert.strictEqual(events[2], "[DONE]") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Sse", + events: [ + { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + }, + { + id: "chatcmpl_test_2", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }, + "[DONE]" + ] + })))) - it.effect("passes chat-completions tool_choice payload through unchanged", () => + it.effect("drops invalid JSON and continues streaming", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + const client = yield* OpenAiClient.OpenAiClient - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") + const events = yield* client.createResponseStream({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) + Effect.flatMap(([_, stream]) => Stream.runCollect(stream)) ) + assert.strictEqual(events.length, 2) + assert.propertyVal(events[0], "id", "chatcmpl_test_3") + assert.strictEqual(events[1], "[DONE]") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "RawSse", + body: [ + "data: {invalid-json\n\n", + `data: ${ + JSON.stringify({ + id: "chatcmpl_test_3", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { content: "Hello" }, + finish_reason: null + }] + }) + }\n\n`, + "data: [DONE]\n\n" + ].join("") + })))) + + it.effect("passes chat-completions tool_choice payload through unchanged", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o-mini", messages: [{ role: "user", content: "hello" }], @@ -249,30 +254,19 @@ describe("OpenAiClient", () => { }] }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) - const body = yield* getRequestBody(capturedRequest) - assert.deepStrictEqual(body.tool_choice, { type: "function", function: { name: "TestTool" } }) - })) + assert.deepStrictEqual(body.tool_choice, { + type: "function", + function: { name: "TestTool" } + }) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("accepts assistant tool-call and tool result chat history", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => { - capturedRequest = request - return Effect.succeed(jsonResponse(request, 200, makeChatCompletion())) - }) - )) - ) + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o-mini", @@ -303,12 +297,10 @@ describe("OpenAiClient", () => { ] }) - assert.isDefined(capturedRequest) - if (capturedRequest === undefined) { - return - } + const requests = yield* MockHttpClient.requests + const request = requests[0] + const body = yield* getRequestBody(request) - const body = yield* getRequestBody(capturedRequest) const assistantMessages = body.messages.filter((message: any) => message.role === "assistant") const patchMessage = assistantMessages.find((message: any) => message.tool_calls?.[0]?.function?.name === "apply_patch" @@ -329,28 +321,41 @@ describe("OpenAiClient", () => { const patchOutput = toolMessages.find((message: any) => message.tool_call_id === "patch_call_1") assert.isDefined(patchOutput) assert.strictEqual(patchOutput.content, "deleted") - })) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("redacts OpenAI-specific headers in AI error context", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + + const result = yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + const headers = result.reason._tag === "InvalidRequestError" + ? result.reason.http?.request.headers ?? Headers.empty + : Headers.empty + + assert.strictEqual(String(headers["authorization"]), "") + assert.strictEqual(String(headers["openai-organization"]), "") + assert.strictEqual(String(headers["openai-project"]), "") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 400, + body: { + error: { + message: "Bad request", + type: "invalid_request_error", + code: null + } + } + })))) }) describe("error mapping", () => { it.effect("maps 400 responses to InvalidRequestError", () => Effect.gen(function*() { - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => - Effect.succeed(jsonResponse(request, 400, { - error: { - message: "Bad request", - type: "invalid_request_error", - code: null - } - })) - ) - )) - ) + const client = yield* OpenAiClient.OpenAiClient const error = yield* client.createResponse({ model: "gpt-4o-mini", @@ -360,26 +365,101 @@ describe("OpenAiClient", () => { assert.strictEqual(error._tag, "AiError") assert.strictEqual(error.method, "createResponse") assert.strictEqual(error.reason._tag, "InvalidRequestError") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 400, + body: { + error: { + message: "Bad request", + type: "invalid_request_error", + code: null + } + } + })))) - it.effect("maps insufficient quota errors to QuotaExhaustedError", () => + it.effect("surfaces the provider message on 401 AuthenticationError", () => Effect.gen(function*() { - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-key") - }).pipe( - Effect.provide(Layer.succeed( - HttpClient.HttpClient, - makeHttpClient((request) => - Effect.succeed(jsonResponse(request, 429, { - error: { - message: "You exceeded your current quota", - type: "insufficient_quota", - code: "insufficient_quota" - } - })) - ) - )) + const client = yield* OpenAiClient.OpenAiClient + + const error = yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + assert.strictEqual(error.reason._tag, "AuthenticationError") + if (error.reason._tag !== "AuthenticationError") { + return yield* Effect.die(new Error("Expected AuthenticationError")) + } + assert.strictEqual(error.reason.kind, "InvalidKey") + assert.strictEqual( + error.reason.description, + "Incorrect API key provided (POST https://compat.example.test/v1/chat/completions) [code: invalid_api_key] [requestId: req_openai_compat]" ) + assert.include(error.reason.message, "Incorrect API key provided") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 401, + body: { + error: { + message: "Incorrect API key provided", + type: "invalid_request_error", + code: "invalid_api_key" + } + }, + headers: { "x-request-id": "req_openai_compat" } + })))) + + it("preserves and truncates a fallback HTTP response", () => { + const body = `${"a".repeat(200)}b` + const reason = Errors.mapStatusCodeToReason({ + status: 400, + headers: {}, + message: undefined, + metadata: { errorCode: null, errorType: null, requestId: null }, + http: makeHttpContext("https://compat.example.test/v1/chat/completions", body) + }) + + assert.strictEqual(reason._tag, "InvalidRequestError") + if (reason._tag !== "InvalidRequestError") { + throw new Error("Expected InvalidRequestError") + } + assert.strictEqual( + reason.description, + `HTTP 400 (POST https://compat.example.test/v1/chat/completions) Response: ${"a".repeat(200)}...` + ) + }) + + it.effect("surfaces the provider message on 403 AuthenticationError", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + + const error = yield* client.createResponse({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + assert.strictEqual(error.reason._tag, "AuthenticationError") + if (error.reason._tag !== "AuthenticationError") { + return yield* Effect.die(new Error("Expected AuthenticationError")) + } + assert.strictEqual(error.reason.kind, "InsufficientPermissions") + assert.include(error.reason.description ?? "", "Country, region, or territory not supported") + assert.include(error.reason.message, "Country, region, or territory not supported") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 403, + body: { + error: { + message: "Country, region, or territory not supported", + type: "permission_error", + code: null + } + } + })))) + + it.effect("maps insufficient quota errors to QuotaExhaustedError", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient const error = yield* client.createResponse({ model: "gpt-4o-mini", @@ -389,24 +469,93 @@ describe("OpenAiClient", () => { assert.strictEqual(error._tag, "AiError") assert.strictEqual(error.method, "createResponse") assert.strictEqual(error.reason._tag, "QuotaExhaustedError") - })) + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 429, + body: { + error: { + message: "You exceeded your current quota", + type: "insufficient_quota", + code: "insufficient_quota" + } + } + })))) }) }) -const makeHttpClient = ( - handler: ( - request: HttpClientRequest.HttpClientRequest - ) => Effect.Effect -) => - HttpClient.makeWith( +type MockResponse = + | { + readonly _tag: "Json" + readonly body: Schema.Json + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "Sse" + readonly events: ReadonlyArray + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "RawSse" + readonly body: string + readonly status?: number | undefined + readonly headers?: Record | undefined + } + +class MockOpenAiResponse extends Context.Service()("MockOpenAiResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = MockHttpClient.use((client) => client.requests) +} + +const makeHttpClientContext = Effect.gen(function*() { + const capturedRequests: Array = [] + const mock = yield* MockOpenAiResponse + + const httpClient = HttpClient.makeWith( Effect.fnUntraced(function*(requestEffect) { const request = yield* requestEffect - return yield* handler(request) + capturedRequests.push(request) + return makeResponse(request, mock.response) }), Effect.succeed as HttpClient.HttpClient.Preprocess ) -const makeChatCompletion = () => ({ + const mockHttpClient: MockHttpClient["Service"] = { + requests: Effect.sync(() => capturedRequests) + } + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, mockHttpClient) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClientContext) + +const makeTestLayer = (response: MockResponse = { + _tag: "Json", + body: makeCreateResponse() +}) => + OpenAiClient.layer({ + apiKey: Redacted.make("sk-test-key"), + apiUrl: "https://compat.example.test/v1", + organizationId: Redacted.make("org_123"), + projectId: Redacted.make("proj_456") + }).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenAiResponse, { + response + })) + ) + +const makeCreateResponse = ( + overrides: Partial = {} +) => ({ id: "chatcmpl_test_1", object: "chat.completion", model: "gpt-4o-mini", @@ -423,37 +572,34 @@ const makeChatCompletion = () => ({ prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 - } + }, + ...overrides }) -const jsonResponse = ( - request: HttpClientRequest.HttpClientRequest, - status: number, - body: unknown -): HttpClientResponse.HttpClientResponse => - HttpClientResponse.fromWeb( - request, - new Response(JSON.stringify(body), { - status, - headers: { - "content-type": "application/json" - } - }) - ) - -const sseResponse = ( +const makeResponse = ( request: HttpClientRequest.HttpClientRequest, - events: ReadonlyArray -): HttpClientResponse.HttpClientResponse => - HttpClientResponse.fromWeb( + response: MockResponse +): HttpClientResponse.HttpClientResponse => { + const contentType = response._tag === "Json" + ? "application/json" + : "text/event-stream" + const body = response._tag === "Json" + ? JSON.stringify(response.body) + : response._tag === "Sse" + ? toSseBody(response.events) + : response.body + + return HttpClientResponse.fromWeb( request, - new Response(toSseBody(events), { - status: 200, + new Response(body, { + status: response.status ?? 200, headers: { - "content-type": "text/event-stream" + "content-type": contentType, + ...response.headers } }) ) +} const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => Effect.gen(function*() { @@ -465,10 +611,19 @@ const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => return yield* Effect.die(new Error("Expected Uint8Array body")) }) -const toSseBody = (events: ReadonlyArray): string => +const toSseBody = (events: ReadonlyArray): string => events.map((event) => { - if (typeof event === "string") { - return `data: ${event}\n\n` - } - return `data: ${JSON.stringify(event)}\n\n` + const data = event === "[DONE]" ? event : JSON.stringify(event) + return `data: ${data}\n\n` }).join("") + +const makeHttpContext = (url: string, body: string) => ({ + request: { + method: "POST" as const, + url, + urlParams: [], + hash: undefined, + headers: {} + }, + body +}) diff --git a/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts b/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts index 276c3387a..dc90d1f4d 100644 --- a/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts +++ b/packages/ai/openai-compat/test/OpenAiLanguageModel.test.ts @@ -1,7 +1,7 @@ import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai-compat" import { assert, describe, it } from "@effect/vitest" import { Effect, Layer, Redacted, Ref, Schema, Stream } from "effect" -import { LanguageModel, Prompt, Tool, Toolkit } from "effect/unstable/ai" +import { type AiError, LanguageModel, Prompt, Tool, Toolkit } from "effect/unstable/ai" import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("OpenAiLanguageModel", () => { @@ -48,6 +48,67 @@ describe("OpenAiLanguageModel", () => { assert.strictEqual(requestBody.messages[0]?.content, "hello") })) + it.effect("routes invalid tool call params through failureMode: return without failing the effect", () => + Effect.gen(function*() { + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse( + request, + makeChatCompletion({ + choices: [{ + index: 0, + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [{ + id: "call_1", + type: "function", + function: { + name: "ReturnModeTool", + arguments: JSON.stringify({ input: 123 }) + } + }] + } + }] + }) + )) + ) + )) + ) + + const ReturnModeTool = Tool.make("ReturnModeTool", { + description: "A test tool", + failureMode: "return", + parameters: Schema.Struct({ input: Schema.String }), + success: Schema.Struct({ output: Schema.String }), + failure: Schema.Struct({ error: Schema.String }) + }) + + const toolkit = Toolkit.make(ReturnModeTool) + const toolkitLayer = toolkit.toLayer({ + ReturnModeTool: ({ input }) => Effect.succeed({ output: `processed: ${input}` }) + }) + + const result = yield* LanguageModel.generateText({ + prompt: "use the tool", + toolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(toolkitLayer), + Effect.provide(layer) + ) + + assert.strictEqual(result.toolResults.length, 1) + const toolResult = result.toolResults[0]! + assert.strictEqual(toolResult.isFailure, true) + const failure = toolResult.result as AiError.AiError + assert.strictEqual(failure._tag, "AiError") + assert.strictEqual(failure.reason._tag, "ToolParameterValidationError") + })) + it.effect("forwards reasoning config to chat completions request", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined @@ -142,8 +203,9 @@ describe("OpenAiLanguageModel", () => { }) })) - it.effect("preserves multimodal user content order in chat payload", () => + it.effect("preserves URL and base64 images in multimodal content order", () => Effect.gen(function*() { + const base64 = "iVBORw0KGgo=" let capturedRequest: HttpClientRequest.HttpClientRequest | undefined const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( @@ -177,6 +239,8 @@ describe("OpenAiLanguageModel", () => { mediaType: "image/png", data: new URL("https://example.com/image.png") }), + Prompt.filePart({ mediaType: "image/png", data: "https://example.com/string-image.png" }), + Prompt.filePart({ mediaType: "image/png", data: base64 }), Prompt.textPart({ text: "second text" }) ] }]) @@ -205,6 +269,20 @@ describe("OpenAiLanguageModel", () => { detail: "auto" } }, + { + type: "image_url", + image_url: { + url: "https://example.com/string-image.png", + detail: "auto" + } + }, + { + type: "image_url", + image_url: { + url: `data:image/png;base64,${base64}`, + detail: "auto" + } + }, { type: "text", text: "second text" @@ -212,6 +290,58 @@ describe("OpenAiLanguageModel", () => { ]) })) + it.effect("normalizes empty assistant message content to an empty string", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse( + request, + makeChatCompletion({ + choices: [{ + index: 0, + finish_reason: "stop", + message: { + role: "assistant", + content: "done" + } + }] + }) + )) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "hello" }, + { + role: "assistant", + content: [Prompt.textPart({ text: "" })] + }, + { role: "user", content: "continue" } + ]) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + const assistantMessage = requestBody.messages.find((message: any) => message.role === "assistant") + assert.isDefined(assistantMessage) + assert.strictEqual(assistantMessage.content, "") + assert.isUndefined(assistantMessage.tool_calls) + })) + it.effect("maps function_call output to tool-call part and sends function tool schema", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined @@ -277,6 +407,145 @@ describe("OpenAiLanguageModel", () => { assert.strictEqual(functionTool.function.strict, true) })) + it.effect("decodes tool call params with the OpenAI codec", () => + Effect.gen(function*() { + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(jsonResponse( + request, + makeChatCompletion({ + choices: [{ + index: 0, + finish_reason: "tool_calls", + message: { + role: "assistant", + content: null, + tool_calls: [{ + id: "call_record_1", + type: "function", + function: { + name: "RecordTool", + arguments: JSON.stringify({ env: [{ 0: "PATH", 1: "/usr/bin" }] }) + } + }] + } + }] + }) + )) + ) + )) + ) + + const result = yield* LanguageModel.generateText({ + prompt: "read the environment", + toolkit: RecordToolkit, + disableToolCallResolution: true + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(RecordToolkitLayer), + Effect.provide(layer) + ) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + assert.deepStrictEqual(toolCall.params, { env: { PATH: "/usr/bin" } }) + })) + + it.effect("groups parallel tool calls into one assistant message", () => + Effect.gen(function*() { + let capturedRequest: HttpClientRequest.HttpClientRequest | undefined + + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => { + capturedRequest = request + return Effect.succeed(jsonResponse(request, makeChatCompletion())) + }) + )) + ) + + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "use both tools" }, + { + role: "assistant", + content: [ + Prompt.toolCallPart({ + id: "call_1", + name: "TestTool", + params: { input: "first" }, + providerExecuted: false + }), + Prompt.toolCallPart({ + id: "call_2", + name: "TestTool", + params: { input: "second" }, + providerExecuted: false + }) + ] + }, + { + role: "tool", + content: [ + Prompt.toolResultPart({ + id: "call_1", + name: "TestTool", + isFailure: false, + result: { output: "first" }, + providerExecuted: false + }), + Prompt.toolResultPart({ + id: "call_2", + name: "TestTool", + isFailure: false, + result: { output: "second" }, + providerExecuted: false + }) + ] + } + ]), + toolkit: TestToolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(TestToolkitLayer), + Effect.provide(layer) + ) + + assert.isDefined(capturedRequest) + if (capturedRequest === undefined) { + return + } + + const requestBody = yield* getRequestBody(capturedRequest) + assert.deepStrictEqual(requestBody.messages, [ + { role: "user", content: "use both tools" }, + { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "TestTool", arguments: JSON.stringify({ input: "first" }) } + }, + { + id: "call_2", + type: "function", + function: { name: "TestTool", arguments: JSON.stringify({ input: "second" }) } + } + ] + }, + { role: "tool", tool_call_id: "call_1", content: JSON.stringify({ output: "first" }) }, + { role: "tool", tool_call_id: "call_2", content: JSON.stringify({ output: "second" }) } + ]) + })) + it.effect("converts dynamic tools to function type", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined @@ -710,6 +979,101 @@ describe("OpenAiLanguageModel", () => { } })) + it.effect("emits text when streamed tool_calls is null", () => + Effect.gen(function*() { + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + { + id: "chatcmpl_nullable_tool_calls", + object: "chat.completion.chunk", + model: "inception/mercury-2", + created: 1, + choices: [{ + index: 0, + delta: { + content: "Hello", + role: "assistant", + tool_calls: null + }, + finish_reason: null + }] + }, + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("inception/mercury-2")), + Effect.provide(layer) + ) + + const text = Array.from(partsChunk) + .filter((part) => part.type === "text-delta") + .map((part) => part.delta) + .join("") + + assert.strictEqual(text, "Hello") + })) + + it.effect("decodes streamed tool call params with the OpenAI codec", () => + Effect.gen(function*() { + const layer = OpenAiClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provide(Layer.succeed( + HttpClient.HttpClient, + makeHttpClient((request) => + Effect.succeed(sseResponse(request, [ + { + id: "chatcmpl_record_1", + object: "chat.completion.chunk", + model: "gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "call_record_1", + type: "function", + function: { + name: "RecordTool", + arguments: JSON.stringify({ env: [{ 0: "PATH", 1: "/usr/bin" }] }) + } + }] + }, + finish_reason: "tool_calls" + }] + }, + "[DONE]" + ])) + ) + )) + ) + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "read the environment", + toolkit: RecordToolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(RecordToolkitLayer), + Effect.provide(layer) + ) + + const toolCall = globalThis.Array.from(partsChunk).find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + if (toolCall?.type !== "tool-call") { + return + } + assert.deepStrictEqual(toolCall.params, { env: { PATH: "/usr/bin" } }) + })) + it.effect("maps local shell stream tool calls to local_shell call outputs", () => Effect.gen(function*() { const capturedRequests = yield* Ref.make>([]) @@ -782,7 +1146,8 @@ describe("OpenAiLanguageModel", () => { id: toolCall.id, name: toolCall.name, isFailure: false, - result: "done" + result: "done", + providerExecuted: false })] } ]), @@ -806,6 +1171,7 @@ describe("OpenAiLanguageModel", () => { item.role === "assistant" && item.tool_calls?.[0]?.function?.name === "local_shell" ) assert.isDefined(localShellCall) + assert.strictEqual(localShellCall.content, null) assert.strictEqual(localShellCall.tool_calls[0].id, toolCall.id) const localShellOutput = followUpBody.messages.find((item: any) => item.role === "tool") @@ -1011,11 +1377,12 @@ describe("OpenAiLanguageModel", () => { assert.deepStrictEqual(toolCall.params, expectedParams) })) - it.effect("streams known events and ignores unknown ones", () => + it.effect("continues after invalid JSON and schema-mismatched events", () => Effect.gen(function*() { let capturedRequest: HttpClientRequest.HttpClientRequest | undefined const events = [ + "{invalid-json", { id: "chatcmpl_stream_1", object: "chat.completion.chunk", @@ -1027,6 +1394,10 @@ describe("OpenAiLanguageModel", () => { finish_reason: null }] }, + { + type: "provider.chat.completion.delta", + provider_payload: { content: "provider-specific" } + }, { id: "chatcmpl_stream_1", object: "chat.completion.chunk", @@ -1358,6 +1729,19 @@ const TestToolkitLayer = TestToolkit.toLayer({ TestTool: ({ input }) => Effect.succeed({ output: input }) }) +const RecordTool = Tool.make("RecordTool", { + parameters: Schema.Struct({ + env: Schema.Record(Schema.String, Schema.String) + }), + success: Schema.String +}) + +const RecordToolkit = Toolkit.make(RecordTool) + +const RecordToolkitLayer = RecordToolkit.toLayer({ + RecordTool: () => Effect.succeed("done") +}) + const CompatApplyPatchTool = Tool.providerDefined({ id: "compat.apply_patch", customName: "CompatApplyPatch", diff --git a/packages/ai/openai-compat/tsconfig.json b/packages/ai/openai-compat/tsconfig.json index 19a2f5dbc..e2a8ca19a 100644 --- a/packages/ai/openai-compat/tsconfig.json +++ b/packages/ai/openai-compat/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/ai/openai-compat/typetest/OpenAiConfig.tst.ts b/packages/ai/openai-compat/typetest/OpenAiConfig.tst.ts new file mode 100644 index 000000000..b87ba69e8 --- /dev/null +++ b/packages/ai/openai-compat/typetest/OpenAiConfig.tst.ts @@ -0,0 +1,26 @@ +import { OpenAiEmbeddingModel, OpenAiLanguageModel } from "@effect/ai-openai-compat" +import { describe, expect, it } from "tstyche" + +describe("OpenAI-compatible config", () => { + it("accepts custom language model properties while preserving known property types", () => { + expect(OpenAiLanguageModel.model).type.toBeCallableWith("model", { + temperature: 0.5, + vendor_setting: true + }) + expect(OpenAiLanguageModel.model).type.not.toBeCallableWith("model", { + temperature: "high" + }) + }) + + it("accepts custom embedding properties while preserving known property types", () => { + expect(OpenAiEmbeddingModel.model).type.toBeCallableWith("model", { + dimensions: 3, + user: "test-user", + vendor_setting: true + }) + expect(OpenAiEmbeddingModel.model).type.not.toBeCallableWith("model", { + dimensions: 3, + user: 1 + }) + }) +}) diff --git a/packages/ai/openai-compat/vitest.config.ts b/packages/ai/openai-compat/vitest.config.ts deleted file mode 100644 index c8a52c182..000000000 --- a/packages/ai/openai-compat/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/packages/ai/openai/CHANGELOG.md b/packages/ai/openai/CHANGELOG.md index 63097db56..cb5fb0a47 100644 --- a/packages/ai/openai/CHANGELOG.md +++ b/packages/ai/openai/CHANGELOG.md @@ -1,5 +1,190 @@ # @effect/ai-openai +## 4.0.0-rc.112 + +### Patch Changes + +- [#7437](https://github.com/Effect-TS/effect/pull/7437) [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb) Thanks @wmaurer! - Add an optional `description` to `AiError.AuthenticationError`, rendered after the kind-based suggestion, and pass the provider's own error text through it on HTTP 401 and 403, so authentication failures report what actually went wrong instead of only a category. + +- [#7403](https://github.com/Effect-TS/effect/pull/7403) [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e) Thanks @hsyntax! - Add support for explicit cache breakpoints on the OpenAI responses API for GPT-5.6-or-later. +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- [#7262](https://github.com/Effect-TS/effect/pull/7262) [`b755950`](https://github.com/Effect-TS/effect/commit/b7559505c831c779eb2c3a974e88d35cb1f2fae5) Thanks @danieljvdm! - Decode nested OpenAI Responses API error events without changing their decoded type. +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- [#7258](https://github.com/Effect-TS/effect/pull/7258) [`2b6a6f5`](https://github.com/Effect-TS/effect/commit/2b6a6f5dbff2795a57cf3d7f04bfa635f9d5892b) Thanks @K-Mistele! - Support max reasoning effort and quota errors returned by OpenAI-compatible providers. +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7126](https://github.com/Effect-TS/effect/pull/7126) [`16b94c7`](https://github.com/Effect-TS/effect/commit/16b94c702419c318e0f3515c902c39cf3871ccce) Thanks @fubhy! - Fix OpenAI response telemetry attribute types to use the emitted response namespace. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- [#7043](https://github.com/Effect-TS/effect/pull/7043) [`dce8219`](https://github.com/Effect-TS/effect/commit/dce8219d4041e1b188a4710550caf20f0c452f09) Thanks @fubhy! - Preserve OpenAI provider errors from failed response stream events. +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6921](https://github.com/Effect-TS/effect/pull/6921) [`4686265`](https://github.com/Effect-TS/effect/commit/468626540686305d7ce34ecdd76e67b2bef2a60e) Thanks @fubhy! - Emit specialized OpenAI tool results only once. + +- [#6722](https://github.com/Effect-TS/effect/pull/6722) [`9344742`](https://github.com/Effect-TS/effect/commit/9344742c6b0ae4ff627b4492a6ddf7fbac5c3785) Thanks @mrtdurdenthe2! - Fix OpenAI stable web search response decoding by preserving the provider action in tool call parameters. + +- [#6675](https://github.com/Effect-TS/effect/pull/6675) [`4cc95ae`](https://github.com/Effect-TS/effect/commit/4cc95ae4a88bc9f5a2e7595de771caeee354cf6e) Thanks @danieljvdm! - Accept image generation-specific lifecycle statuses and nullable results in OpenAI response items. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- [#6920](https://github.com/Effect-TS/effect/pull/6920) [`da10211`](https://github.com/Effect-TS/effect/commit/da102116733d485e794c1d06f938a4e03daf418e) Thanks @fubhy! - Terminate OpenAI HTTP and WebSocket response streams when a `response.failed` event arrives. + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6650](https://github.com/Effect-TS/effect/pull/6650) [`acd385e`](https://github.com/Effect-TS/effect/commit/acd385ebb3f9edee37ab6715607119ee9762a615) Thanks @IMax153! - Redact OpenAI organization and project headers from client errors. + +- [#6549](https://github.com/Effect-TS/effect/pull/6549) [`64c6ab1`](https://github.com/Effect-TS/effect/commit/64c6ab1951ac6fb0bdd5e0398795946dc2872be7) Thanks @xianjianlf2! - Encode OpenAI Responses API system messages as typed input text content. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- [#6519](https://github.com/Effect-TS/effect/pull/6519) [`0e116a1`](https://github.com/Effect-TS/effect/commit/0e116a139c9d7af7c7e0ccbb7b5669926477370e) Thanks @tim-smart! - Preserve OpenAI Responses API cache write token usage in language model responses. + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/ai/openai/README.md b/packages/ai/openai/README.md new file mode 100644 index 000000000..2ef377e34 --- /dev/null +++ b/packages/ai/openai/README.md @@ -0,0 +1,14 @@ +# @effect/ai-openai + +An [OpenAI](https://openai.com) provider for the Effect AI modules. Includes a typed OpenAI API client, language model and embedding model layers, tools, and telemetry helpers. + +## Installation + +```sh +npm install effect@rc @effect/ai-openai@rc +``` + +## Documentation + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/ai-openai) diff --git a/packages/ai/openai/codegen.yaml b/packages/ai/openai/codegen.yaml index 19d06da16..05724087a 100644 --- a/packages/ai/openai/codegen.yaml +++ b/packages/ai/openai/codegen.yaml @@ -13,7 +13,6 @@ patches: - '[{"op":"add","path":"/components/schemas/ModelResponseProperties/properties/prompt_cache_key/nullable","value":true}]' - '[{"op":"add","path":"/components/schemas/Response/allOf/2/properties/usage/nullable","value":true}]' - '[{"op":"remove","path":"/components/schemas/ResponseFunctionCallArgumentsDoneEvent/required/2"}]' - - '[{"op":"remove","path":"/components/schemas/WebSearchActionSearch/required/1"}]' - '[{"op":"add","path":"/components/schemas/ModelResponseProperties/properties/prompt_cache_retention/anyOf/0/enum/1","value":"in_memory"}]' - '[{"op":"add","path":"/components/schemas/PromptCacheRetentionEnum/enum/1","value":"in-memory"}]' - '[{"op":"replace","path":"/components/schemas/OpenAIFile/properties/expires_at","value":{"anyOf":[{"type":"integer","format":"unixtime","description":"The Unix timestamp (in seconds) for when the file will expire."},{"type":"null"}]}}]' diff --git a/packages/ai/openai/docgen.json b/packages/ai/openai/docgen.json deleted file mode 100644 index 22eff2cf7..000000000 --- a/packages/ai/openai/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/openai/src/", - "exclude": ["src/Generated.ts", "src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/ai/openai/package.json b/packages/ai/openai/package.json index db95857af..52e041041 100644 --- a/packages/ai/openai/package.json +++ b/packages/ai/openai/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-openai", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "An OpenAI provider integration for Effect AI SDK", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/openai" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/packages/ai/openai/src/Generated.ts b/packages/ai/openai/src/Generated.ts index d7189bf5d..d51b31244 100644 --- a/packages/ai/openai/src/Generated.ts +++ b/packages/ai/openai/src/Generated.ts @@ -30441,7 +30441,7 @@ export const make = ( request: HttpClientRequest.HttpClientRequest ): Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, DecodingServices > => HttpClient.filterStatusOk(httpClient).execute(request).pipe( @@ -32832,7 +32832,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateSpeechRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateSpeech200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateSpeech200Sse.DecodingServices > /** @@ -32868,7 +32868,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateTranscriptionRequestFormData.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateTranscription200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateTranscription200Sse.DecodingServices > /** @@ -33058,7 +33058,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateChatCompletionRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateChatCompletion200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateChatCompletion200Sse.DecodingServices > /** @@ -33659,7 +33659,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateImageEditRequestFormData.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateImageEdit200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateImageEdit200Sse.DecodingServices > /** @@ -33678,7 +33678,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateImageRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateImage200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateImage200Sse.DecodingServices > /** @@ -34879,7 +34879,7 @@ export interface OpenAiClient { options: { readonly payload: typeof CreateResponseRequestJson.Encoded } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateResponse200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateResponse200Sse.DecodingServices > /** diff --git a/packages/ai/openai/src/OpenAiClient.ts b/packages/ai/openai/src/OpenAiClient.ts index a1774b6d3..21b4f38e6 100644 --- a/packages/ai/openai/src/OpenAiClient.ts +++ b/packages/ai/openai/src/OpenAiClient.ts @@ -47,7 +47,7 @@ import * as OpenAiSchema from "./OpenAiSchema.ts" * * Provides the configured HTTP client plus helpers for Responses API calls, streaming Responses events, and embeddings. Transport and schema decoding failures are mapped to `AiError`. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { @@ -158,6 +158,11 @@ const RedactedOpenAiHeaders = { OpenAiProject: "OpenAI-Project" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + /** * Creates an OpenAI client service with the given options. * @@ -232,25 +237,27 @@ export const make = Effect.fnUntraced( [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse], AiError.AiError > => - Effect.flatMap(resolveHttpClient, (client) => - client.execute( - HttpClientRequest.post("/responses", { + resolveHttpClient.pipe( + Effect.flatMap((client) => + client.execute(HttpClientRequest.post("/responses", { body: HttpBody.jsonUnsafe(payload) - }) - ).pipe( - Effect.flatMap((response) => - decodeResponse(response).pipe( - Effect.map((body): [typeof OpenAiSchema.Response.Type, HttpClientResponse.HttpClientResponse] => [ - body, - response - ]) - ) - ), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) - }) - )) + })).pipe( + Effect.flatMap((response) => + decodeResponse(response).pipe( + Effect.map((body): [typeof OpenAiSchema.Response.Type, HttpClientResponse.HttpClientResponse] => [ + body, + response + ]) + ) + ), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) + }) + ) + ), + withRedactedHeaders + ) const buildResponseStream = ( response: HttpClientResponse.HttpClientResponse @@ -263,12 +270,14 @@ export const make = Effect.fnUntraced( Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)), Stream.takeUntil((event) => event.data.type === "response.completed" || - event.data.type === "response.incomplete" + event.data.type === "response.incomplete" || + event.data.type === "response.failed" ), Stream.map((event) => event.data), Stream.catchTags({ // TODO: handle SSE retries Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createResponseStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")), SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createResponseStream")) }) @@ -280,18 +289,20 @@ export const make = Effect.fnUntraced( Effect.contextWith((services) => { const socket = Context.getOrUndefined(services, OpenAiSocket) if (socket) return socket.createResponseStream(payload) - return Effect.flatMap(resolveHttpClient, (client) => - client.execute( - HttpClientRequest.post("/responses", { + return resolveHttpClient.pipe( + Effect.flatMap((client) => + client.execute(HttpClientRequest.post("/responses", { body: HttpBody.jsonUnsafe({ ...payload, stream: true }) - }) - ).pipe( - Effect.map(buildResponseStream), - Effect.catchTag( - "HttpClientError", - (error) => Errors.mapHttpClientError(error, "createResponseStream") + })).pipe( + Effect.map(buildResponseStream), + Effect.catchTag( + "HttpClientError", + (error) => Errors.mapHttpClientError(error, "createResponseStream") + ) ) - )) + ), + withRedactedHeaders + ) }) const decodeEmbedding = HttpClientResponse.schemaBodyJson(OpenAiSchema.CreateEmbeddingResponse) @@ -299,18 +310,20 @@ export const make = Effect.fnUntraced( const createEmbedding = ( payload: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded ): Effect.Effect => - Effect.flatMap(resolveHttpClient, (client) => - client.execute( - HttpClientRequest.post("/embeddings", { + resolveHttpClient.pipe( + Effect.flatMap((client) => + client.execute(HttpClientRequest.post("/embeddings", { body: HttpBody.jsonUnsafe(payload) - }) - ).pipe( - Effect.flatMap(decodeEmbedding), - Effect.catchTags({ - HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), - SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) - }) - )) + })).pipe( + Effect.flatMap(decodeEmbedding), + Effect.catchTags({ + HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), + SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) + }) + ) + ), + withRedactedHeaders + ) return OpenAiClient.of({ client: httpClient, @@ -319,10 +332,7 @@ export const make = Effect.fnUntraced( createEmbedding }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedOpenAiHeaders)) - ) + withRedactedHeaders ) // ============================================================================= @@ -424,7 +434,7 @@ export const layerConfig = (options?: { /** * Response stream event emitted by the OpenAI Responses API. * - * @category Events + * @category models * @since 4.0.0 */ export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type @@ -451,7 +461,7 @@ export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type * @see {@link withWebSocketMode} for enabling WebSocket mode for one effect * @see {@link layerWebSocketMode} for providing WebSocket mode through a layer * - * @category Websocket mode + * @category services * @since 4.0.0 */ export class OpenAiSocket extends Context.Service makeWebSocket(url, { headers: request.headers - } as any)) + })) ) - const write = yield* socket.writer + const writer = yield* socket.writer yield* Scope.addFinalizerExit(scope, () => { tracker.clearUnsafe() @@ -511,7 +521,7 @@ const makeSocket = Effect.gen(function*() { const incoming = yield* Queue.unbounded() const send = (message: typeof OpenAiSchema.CreateResponse.Encoded) => - write(JSON.stringify({ + writer.write(JSON.stringify({ type: "response.create", ...message })).pipe( @@ -534,7 +544,7 @@ const makeSocket = Effect.gen(function*() { ) ) - yield* socket.runRaw((msg) => { + const handleMessage = (msg: Uint8Array | string): Effect.Effect | undefined => { const text = typeof msg === "string" ? msg : decoder.decode(msg) try { const event = decodeEvent(text) @@ -548,7 +558,11 @@ const makeSocket = Effect.gen(function*() { method: "createResponseStream", reason: AiError.reasonFromHttpStatus({ description: json, - status: isNaN(status) ? errorTypeToStatus[error.type] ?? 500 : status, + status: isNaN(status) ? + Object.hasOwn(errorTypeToStatus, error.type) + ? errorTypeToStatus[error.type] + : 500 : + status, metadata: error as any, http: { body: json, @@ -566,7 +580,22 @@ const makeSocket = Effect.gen(function*() { } Queue.offerUnsafe(incoming, event) } catch {} + return undefined + } + + yield* Effect.gen(function*() { + const { pull } = yield* socket.reader + while (true) { + const messages = yield* pull + for (let i = 0; i < messages.length; i++) { + const result = handleMessage(messages[i]) + if (result !== undefined) { + yield* result + } + } + } }).pipe( + Effect.scoped, Effect.catchTag("SocketError", (error) => AiError.make({ module: "OpenAiClient", @@ -624,7 +653,7 @@ const makeSocket = Effect.gen(function*() { return Stream.fromQueue(incoming).pipe( Stream.takeUntil((e) => { - done = e.type === "response.completed" || e.type === "response.incomplete" + done = e.type === "response.completed" || e.type === "response.incomplete" || e.type === "response.failed" return done }) ) @@ -642,7 +671,7 @@ const makeSocket = Effect.gen(function*() { const ErrorEvent = Schema.Struct({ type: Schema.Literal("error"), - status: Schema.Number.pipe( + status: Schema.Int.pipe( Schema.withDecodingDefault(Effect.succeed(500)) ), error: Schema.Struct({ @@ -683,7 +712,7 @@ const decodeEvent = Schema.decodeUnknownSync(Schema.fromJsonString(AllEvents)) * @see {@link layerWebSocketMode} for providing WebSocket mode through a layer * @see {@link OpenAiSocket} for direct access to the WebSocket-backed streaming service * - * @category Websocket mode + * @category providing services * @since 4.0.0 */ export const withWebSocketMode = ( @@ -720,7 +749,7 @@ export const withWebSocketMode = ( * * @see {@link withWebSocketMode} for enabling WebSocket mode around a single effect * - * @category Websocket mode + * @category layers * @since 4.0.0 */ export const layerWebSocketMode: Layer.Layer< diff --git a/packages/ai/openai/src/OpenAiClientGenerated.ts b/packages/ai/openai/src/OpenAiClientGenerated.ts index 1b2c9b01f..6df83c43a 100644 --- a/packages/ai/openai/src/OpenAiClientGenerated.ts +++ b/packages/ai/openai/src/OpenAiClientGenerated.ts @@ -74,6 +74,11 @@ const RedactedOpenAiHeaders = { OpenAiProject: "OpenAI-Project" } +const withRedactedHeaders = Effect.updateService( + Headers.CurrentRedactedNames, + Array.appendAll(Object.values(RedactedOpenAiHeaders)) +) + // ============================================================================= // Constructor // ============================================================================= @@ -124,10 +129,7 @@ export const make = Effect.fnUntraced( }) }) }, - Effect.updateService( - Headers.CurrentRedactedNames, - Array.appendAll(Object.values(RedactedOpenAiHeaders)) - ) + withRedactedHeaders ) // ============================================================================= diff --git a/packages/ai/openai/src/OpenAiConfig.ts b/packages/ai/openai/src/OpenAiConfig.ts index ea0b05cde..7d723974e 100644 --- a/packages/ai/openai/src/OpenAiConfig.ts +++ b/packages/ai/openai/src/OpenAiConfig.ts @@ -35,7 +35,7 @@ export class OpenAiConfig extends Context.Service< */ static readonly getOrUndefined: Effect.Effect = Effect.map( Effect.context(), - (context) => context.mapUnsafe.get(OpenAiConfig.key) + Context.getOrUndefined(OpenAiConfig) ) } @@ -49,7 +49,7 @@ export declare namespace OpenAiConfig { * Configuration values read by OpenAI provider operations when executing * requests. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/packages/ai/openai/src/OpenAiEmbeddingModel.ts b/packages/ai/openai/src/OpenAiEmbeddingModel.ts index 76443785a..fa466ca79 100644 --- a/packages/ai/openai/src/OpenAiEmbeddingModel.ts +++ b/packages/ai/openai/src/OpenAiEmbeddingModel.ts @@ -130,10 +130,9 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }): Effect.fn.Return { const client = yield* OpenAiClient - const makeConfig = Effect.gen(function*() { - const services = yield* Effect.context() - return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) } - }) + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) return yield* EmbeddingModel.make({ embedMany: Effect.fnUntraced(function*({ inputs }) { diff --git a/packages/ai/openai/src/OpenAiError.ts b/packages/ai/openai/src/OpenAiError.ts index cb0ccc2dd..5e2a96d60 100644 --- a/packages/ai/openai/src/OpenAiError.ts +++ b/packages/ai/openai/src/OpenAiError.ts @@ -68,7 +68,7 @@ declare module "effect/unstable/ai/AiError" { * from responses where the provider rejected the request because a limit was * reached. * - * @category configuration + * @category models * @since 4.0.0 */ export interface RateLimitErrorMetadata { @@ -86,7 +86,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failures caused by exhausted account, * billing, or usage quota. * - * @category configuration + * @category models * @since 4.0.0 */ export interface QuotaExhaustedErrorMetadata { @@ -104,7 +104,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failed API key, authorization, or * permission checks. * - * @category configuration + * @category models * @since 4.0.0 */ export interface AuthenticationErrorMetadata { @@ -122,7 +122,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenAI rejects input or output because * it violates a content policy. * - * @category configuration + * @category models * @since 4.0.0 */ export interface ContentPolicyErrorMetadata { @@ -140,7 +140,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for malformed requests, unsupported * parameters, or other request validation failures reported by OpenAI. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidRequestErrorMetadata { @@ -158,7 +158,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenAI-side failures such as transient * server errors. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InternalProviderErrorMetadata { @@ -176,7 +176,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an OpenAI response cannot be parsed or * validated as the expected output. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidOutputErrorMetadata { @@ -194,7 +194,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenAI returns content that does not * satisfy the requested structured output schema. * - * @category configuration + * @category models * @since 4.0.0 */ export interface StructuredOutputErrorMetadata { @@ -212,7 +212,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an unsupported schema failure is * associated with an OpenAI response. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnsupportedSchemaErrorMetadata { @@ -230,7 +230,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenAI failures that do not map cleanly * to a more specific AI error category. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnknownErrorMetadata { diff --git a/packages/ai/openai/src/OpenAiLanguageModel.ts b/packages/ai/openai/src/OpenAiLanguageModel.ts index 7930d3d59..5635f3836 100644 --- a/packages/ai/openai/src/OpenAiLanguageModel.ts +++ b/packages/ai/openai/src/OpenAiLanguageModel.ts @@ -55,6 +55,8 @@ export type Model = typeof ResponseModelIds.Encoded | typeof SharedModelIds.Enco */ type ImageDetail = "auto" | "low" | "high" +type PromptCacheBreakpoint = { readonly mode: "explicit" } + // ============================================================================= // Configuration // ============================================================================= @@ -124,10 +126,31 @@ export class Config extends Context.Service< // ============================================================================= declare module "effect/unstable/ai/Prompt" { + /** + * OpenAI-specific options for system messages. + * + * @category models + * @since 4.0.0 + */ + export interface SystemMessageOptions extends ProviderOptions { + /** + * Provider-specific system message options for the OpenAI Responses API. + */ + readonly openai?: { + /** + * Marks the system input text as the end of a reusable prompt prefix. + * + * Requires GPT-5.6 or later. OpenAI may reject requests that use this + * option with earlier models. + */ + readonly promptCacheBreakpoint?: PromptCacheBreakpoint | null + } | null + } + /** * OpenAI-specific options for file prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -145,7 +168,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for reasoning prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -169,7 +192,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for assistant tool-call prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -195,7 +218,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for tool-result prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { @@ -221,7 +244,7 @@ declare module "effect/unstable/ai/Prompt" { /** * OpenAI-specific options for text prompt parts. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -241,6 +264,13 @@ declare module "effect/unstable/ai/Prompt" { * A list of annotations that apply to the output text. */ readonly annotations?: ReadonlyArray | null + /** + * Marks the input text as the end of a reusable prompt prefix. + * + * Requires GPT-5.6 or later. OpenAI may reject requests that use this + * option with earlier models. + */ + readonly promptCacheBreakpoint?: PromptCacheBreakpoint | null } | null } } @@ -249,7 +279,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to a complete text response part. * - * @category response + * @category models * @since 4.0.0 */ export interface TextPartMetadata extends ProviderMetadata { @@ -281,7 +311,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed text part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface TextStartPartMetadata extends ProviderMetadata { @@ -299,7 +329,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed text part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface TextEndPartMetadata extends ProviderMetadata { @@ -321,7 +351,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to a complete reasoning response part. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -343,7 +373,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed reasoning part starts. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -365,7 +395,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted for a streamed reasoning delta. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -383,7 +413,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata emitted when a streamed reasoning part ends. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningEndPartMetadata extends ProviderMetadata { @@ -405,7 +435,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to tool-call response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -423,7 +453,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to document source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface DocumentSourcePartMetadata extends ProviderMetadata { @@ -479,7 +509,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to URL source citations. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -505,7 +535,7 @@ declare module "effect/unstable/ai/Response" { /** * OpenAI metadata attached to finish response parts. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -587,7 +617,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig const makeConfig = Effect.gen(function*() { const services = yield* Effect.context() - return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) } + return { model, ...providerConfig, ...Context.getOrUndefined(services, Config) } }) const makeRequest = Effect.fnUntraced( @@ -782,7 +812,7 @@ const prepareMessages = Effect.fnUntraced( Tool.isProviderDefined(tool) && tool.name === "OpenAiCodeInterpreter" ) const shellTool = options.tools.find((tool): tool is ReturnType => - Tool.isProviderDefined(tool) && tool.name === "OpenAiFunctionShell" + Tool.isProviderDefined(tool) && tool.name === "OpenAiShell" ) const localShellTool = options.tools.find((tool): tool is ReturnType => Tool.isProviderDefined(tool) && tool.name === "OpenAiLocalShell" @@ -816,7 +846,11 @@ const prepareMessages = Effect.fnUntraced( case "system": { messages.push({ role: getSystemMessageMode(config.model as string), - content: message.content + content: [{ + type: "input_text", + text: message.content, + ...getPromptCacheBreakpoint(message) + }] }) break } @@ -829,7 +863,11 @@ const prepareMessages = Effect.fnUntraced( switch (part.type) { case "text": { - content.push({ type: "input_text", text: part.text }) + content.push({ + type: "input_text", + text: part.text, + ...getPromptCacheBreakpoint(part) + }) break } @@ -840,15 +878,14 @@ const prepareMessages = Effect.fnUntraced( if (typeof part.data === "string" && isFileId(part.data, config)) { content.push({ type: "input_image", file_id: part.data, detail }) - } - - if (part.data instanceof URL) { - content.push({ type: "input_image", image_url: part.data.toString(), detail }) - } - - if (part.data instanceof Uint8Array) { - const base64 = Encoding.encodeBase64(part.data) - const imageUrl = `data:${mediaType};base64,${base64}` + } else { + const imageUrl = part.data instanceof URL + ? part.data.toString() + : part.data instanceof Uint8Array + ? `data:${mediaType};base64,${Encoding.encodeBase64(part.data)}` + : /^(data:|https?:\/\/)/i.test(part.data) + ? part.data + : `data:${mediaType};base64,${part.data}` content.push({ type: "input_image", image_url: imageUrl, detail }) } } else if (part.mediaType === "application/pdf") { @@ -885,7 +922,8 @@ const prepareMessages = Effect.fnUntraced( } case "assistant": { - const reasoningMessages: Record> = {} + const reasoningMessages: Record> = Object + .create(null) for (const part of message.content) { switch (part.type) { @@ -1003,7 +1041,6 @@ const prepareMessages = Effect.fnUntraced( method: "prepareMessages", reason: new AiError.ToolParameterValidationError({ toolName: "local_shell", - toolParams: part.params as Schema.Json, description: error.message }) }) @@ -1029,7 +1066,6 @@ const prepareMessages = Effect.fnUntraced( method: "prepareMessages", reason: new AiError.ToolParameterValidationError({ toolName: "shell", - toolParams: part.params as Schema.Json, description: error.message }) }) @@ -1129,6 +1165,7 @@ const prepareMessages = Effect.fnUntraced( call_id: part.id, ...(part.result as any) }) + continue } if (Predicate.isNotUndefined(shellTool) && toolName === "shell") { @@ -1139,6 +1176,7 @@ const prepareMessages = Effect.fnUntraced( output: part.result as any, ...(Predicate.isNotNull(status) ? { status } : {}) }) + continue } if (Predicate.isNotUndefined(localShellTool) && toolName === "local_shell") { @@ -1149,12 +1187,13 @@ const prepareMessages = Effect.fnUntraced( output: part.result as any, ...(Predicate.isNotNull(status) ? { status } : {}) }) + continue } messages.push({ type: "function_call_output", call_id: part.id, - output: JSON.stringify(part.result), + output: typeof part.result === "string" ? part.result : JSON.stringify(part.result), ...(Predicate.isNotNull(status) ? { status } : {}) }) } @@ -1353,7 +1392,6 @@ const makeResponse = Effect.fnUntraced( method: "makeResponse", reason: new AiError.ToolParameterValidationError({ toolName, - toolParams: {}, description: `Faled to securely JSON parse tool parameters: ${cause}` }) }) @@ -1614,7 +1652,9 @@ const makeResponse = Effect.fnUntraced( type: "tool-call", id: part.id, name: toolName, - params: {}, + params: webSearchTool?.name === "OpenAiWebSearchPreview" + ? {} + : { action: part.action }, providerExecuted: true }) parts.push({ @@ -1682,7 +1722,7 @@ const makeStreamResponse = Effect.fnUntraced( } // Track active reasoning items with state machine for proper concluding logic - const activeReasoning: Record = {} + const activeReasoning: Record = Object.create(null) const getOrCreateReasoningPart = ( itemId: string, @@ -1753,8 +1793,7 @@ const makeStreamResponse = Effect.fnUntraced( } case "response.completed": - case "response.incomplete": - case "response.failed": { + case "response.incomplete": { parts.push({ type: "finish", reason: InternalUtilities.resolveFinishReason( @@ -1768,6 +1807,20 @@ const makeStreamResponse = Effect.fnUntraced( break } + case "response.failed": { + if (event.response.error) { + parts.push({ type: "error", error: event.response.error }) + } + parts.push({ + type: "finish", + reason: "error", + usage: getUsage(event.response.usage), + response: buildHttpResponseDetails(response), + ...toServiceTier(event.response.service_tier) + }) + break + } + case "response.output_item.added": { switch (event.item.type) { case "apply_patch_call": { @@ -1942,6 +1995,9 @@ const makeStreamResponse = Effect.fnUntraced( id: event.item.id, name: toolName } + if (webSearchTool?.name === "OpenAiWebSearch") { + break + } parts.push({ type: "tool-params-start", id: event.item.id, @@ -2084,7 +2140,6 @@ const makeStreamResponse = Effect.fnUntraced( method: "makeStreamResponse", reason: new AiError.ToolParameterValidationError({ toolName, - toolParams: {}, description: `Failed securely JSON parse tool parameters: ${cause}` }) }) @@ -2255,6 +2310,15 @@ const makeStreamResponse = Effect.fnUntraced( const toolName = toolNameMapper.getCustomName( webSearchTool?.name ?? "web_search" ) + if (webSearchTool?.name === "OpenAiWebSearch") { + parts.push({ + type: "tool-call", + id: event.item.id, + name: toolName, + params: { action: event.item.action }, + providerExecuted: true + }) + } parts.push({ type: "tool-result", id: event.item.id, @@ -2378,7 +2442,6 @@ const makeStreamResponse = Effect.fnUntraced( method: "makeStreamResponse", reason: new AiError.ToolParameterValidationError({ toolName: toolCall.name, - toolParams: {}, description: `Failed securely JSON parse tool parameters: ${cause}` }) }) @@ -2876,6 +2939,13 @@ const getEncryptedContent = ( const getImageDetail = (part: Prompt.FilePart): ImageDetail => part.options.openai?.imageDetail ?? "auto" +const getPromptCacheBreakpoint = ( + input: Prompt.SystemMessage | Prompt.TextPart +) => { + const promptCacheBreakpoint = input.options.openai?.promptCacheBreakpoint + return Predicate.isNotNullish(promptCacheBreakpoint) ? { prompt_cache_breakpoint: promptCacheBreakpoint } : undefined +} + const makeItemIdMetadata = (itemId: string | undefined) => Predicate.isNotUndefined(itemId) ? { itemId } : {} const makeEncryptedContentMetadata = (encryptedContent: string | null | undefined) => @@ -3025,7 +3095,6 @@ const normalizeMcpToolCall = Effect.fnUntraced(function* - Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : 0 +const getUsageTokenDetail = (details: unknown, key: string): number | undefined => + Predicate.hasProperty(details, key) && typeof details[key] === "number" ? details[key] : undefined const transformToolCallParams = Effect.fnUntraced(function*>( tools: Tools, @@ -3115,19 +3185,13 @@ const transformToolCallParams = Effect.fnUntraced(function* - ).pipe(Effect.mapError((error) => - AiError.make({ - module: "OpenAiLanguageModel", - method: "makeResponse", - reason: new AiError.ToolParameterValidationError({ - toolName, - toolParams, - description: error.issue.toString() - }) - }) - )) + Schema.decodeEffect(codec)(toolParams) as Effect.Effect + ).pipe( + Effect.flatMap((decoded) => + Schema.encodeUnknownEffect(tool.parametersSchema)(decoded) as Effect.Effect + ), + Effect.orElseSucceed(() => toolParams) + ) }) diff --git a/packages/ai/openai/src/OpenAiSchema.ts b/packages/ai/openai/src/OpenAiSchema.ts index b93188db6..89015b512 100644 --- a/packages/ai/openai/src/OpenAiSchema.ts +++ b/packages/ai/openai/src/OpenAiSchema.ts @@ -9,6 +9,7 @@ import * as Effect from "effect/Effect" import * as Predicate from "effect/Predicate" import * as Schema from "effect/Schema" +import * as SchemaTransformation from "effect/SchemaTransformation" const UnknownRecord = Schema.Record(Schema.String, Schema.Unknown) @@ -18,6 +19,10 @@ const MessageRole = Schema.Literals(["system", "developer", "user", "assistant"] const ImageDetail = Schema.Literals(["low", "high", "auto"]) +const PromptCacheBreakpoint = Schema.Struct({ + mode: Schema.Literal("explicit") +}) + /** * Schema for optional `include` values supported by the local handwritten * Responses client schema. @@ -75,7 +80,8 @@ export type MessageStatus = typeof MessageStatus.Type const InputTextContent = Schema.Struct({ type: Schema.Literal("input_text"), - text: Schema.String + text: Schema.String, + prompt_cache_breakpoint: Schema.optional(PromptCacheBreakpoint) }) const InputImageContent = Schema.Struct({ @@ -173,15 +179,15 @@ const ComputerScreenshotContent = Schema.Struct({ const FileCitationAnnotation = Schema.Struct({ type: Schema.Literal("file_citation"), file_id: Schema.String, - index: Schema.Number, + index: Schema.Int, filename: Schema.String }) const UrlCitationAnnotation = Schema.Struct({ type: Schema.Literal("url_citation"), url: Schema.String, - start_index: Schema.Number, - end_index: Schema.Number, + start_index: Schema.Int, + end_index: Schema.Int, title: Schema.String }) @@ -189,15 +195,15 @@ const ContainerFileCitationAnnotation = Schema.Struct({ type: Schema.Literal("container_file_citation"), container_id: Schema.String, file_id: Schema.String, - start_index: Schema.Number, - end_index: Schema.Number, + start_index: Schema.Int, + end_index: Schema.Int, filename: Schema.String }) const FilePathAnnotation = Schema.Struct({ type: Schema.Literal("file_path"), file_id: Schema.String, - index: Schema.Number + index: Schema.Int }) /** @@ -639,7 +645,7 @@ export type TextResponseFormatConfiguration = typeof TextResponseFormatConfigura * Validates the Responses API request payload, including input content, model * selection, instructions, reasoning options, text output format, tools, * `tool_choice`, streaming, storage, response continuation, sampling options, - * and optional response fields requested through `include`. + * prompt caching, and optional response fields requested through `include`. * * **Gotchas** * @@ -654,22 +660,27 @@ export type TextResponseFormatConfiguration = typeof TextResponseFormatConfigura */ export const CreateResponse = Schema.Struct({ metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), - top_logprobs: Schema.optional(Schema.Number), - temperature: Schema.optional(Schema.Number), - top_p: Schema.optional(Schema.Number), + top_logprobs: Schema.optional(Schema.Int), + temperature: Schema.optional(Schema.Finite), + top_p: Schema.optional(Schema.Finite), user: Schema.optional(Schema.String), + prompt_cache_key: Schema.optional(Schema.String), + prompt_cache_options: Schema.optional(Schema.Struct({ + mode: Schema.optional(Schema.Literals(["implicit", "explicit"])), + ttl: Schema.optional(Schema.Literal("30m")) + })), service_tier: Schema.optional(Schema.String), previous_response_id: Schema.optional(Schema.String), model: Schema.optional(Schema.String), reasoning: Schema.optional(Schema.Struct({ - effort: Schema.optional(Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh"])), + effort: Schema.optional(Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh", "max"])), summary: Schema.optional(Schema.Literals(["auto", "concise", "detailed"])), generate_summary: Schema.optional(Schema.Literals(["auto", "concise", "detailed"])) })), background: Schema.optional(Schema.Boolean), - max_output_tokens: Schema.optional(Schema.Number), - max_tool_calls: Schema.optional(Schema.Number), + max_output_tokens: Schema.optional(Schema.Int), + max_tool_calls: Schema.optional(Schema.Int), text: Schema.optional( Schema.Struct({ format: Schema.optional(TextResponseFormatConfiguration), @@ -691,7 +702,7 @@ export const CreateResponse = Schema.Struct({ stream: Schema.optional(Schema.Boolean), conversation: Schema.optional(Schema.String), modalities: Schema.optional(Schema.Array(Schema.Literals(["text", "audio"]))), - seed: Schema.optional(Schema.Number) + seed: Schema.optional(Schema.Int) }) /** @@ -716,9 +727,9 @@ export type CreateResponse = typeof CreateResponse.Type */ export const ResponseUsage = Schema.StructWithRest( Schema.Struct({ - input_tokens: Schema.Number, - output_tokens: Schema.Number, - total_tokens: Schema.Number, + input_tokens: Schema.Int, + output_tokens: Schema.Int, + total_tokens: Schema.Int, input_tokens_details: Schema.optionalKey(Schema.Unknown), output_tokens_details: Schema.optionalKey(Schema.Unknown) }), @@ -778,8 +789,8 @@ const FileSearchCall = Schema.Struct({ const ImageGenerationCall = Schema.Struct({ id: Schema.String, type: Schema.Literal("image_generation_call"), - result: Schema.optionalKey(Schema.String), - status: Schema.optionalKey(MessageStatus) + result: Schema.optionalKey(Schema.NullOr(Schema.String)), + status: Schema.optionalKey(Schema.Literals(["in_progress", "completed", "generating", "failed"])) }) const McpCall = Schema.Struct({ @@ -830,6 +841,11 @@ const OutputItem = Schema.Union([ WebSearchCall ]) +const ResponseError = Schema.Struct({ + code: Schema.String, + message: Schema.String +}) + /** * Schema for an OpenAI Responses API response object. * @@ -853,11 +869,12 @@ export const Response = Schema.Struct({ id: Schema.String, object: Schema.optionalKey(Schema.Literal("response")), model: Schema.String, - created_at: Schema.Number, + created_at: Schema.Int, output: Schema.Array(OutputItem).pipe( Schema.withDecodingDefault(Effect.succeed([])) ), usage: Schema.optionalKey(Schema.NullOr(ResponseUsage)), + error: Schema.optionalKey(Schema.NullOr(ResponseError)), incomplete_details: Schema.optionalKey( Schema.NullOr( Schema.Struct({ @@ -888,141 +905,141 @@ export type Response = typeof Response.Type const ResponseCreatedEvent = Schema.Struct({ type: Schema.Literal("response.created"), response: Response, - sequence_number: Schema.Number + sequence_number: Schema.optionalKey(Schema.Int) }) const ResponseCompletedEvent = Schema.Struct({ type: Schema.Literal("response.completed"), response: Response, - sequence_number: Schema.Number + sequence_number: Schema.optionalKey(Schema.Int) }) const ResponseIncompleteEvent = Schema.Struct({ type: Schema.Literal("response.incomplete"), response: Response, - sequence_number: Schema.Number + sequence_number: Schema.optionalKey(Schema.Int) }) const ResponseFailedEvent = Schema.Struct({ type: Schema.Literal("response.failed"), response: Response, - sequence_number: Schema.Number + sequence_number: Schema.optionalKey(Schema.Int) }) const ResponseOutputItemAddedEvent = Schema.Struct({ type: Schema.Literal("response.output_item.added"), - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), item: OutputItem }) const ResponseOutputItemDoneEvent = Schema.Struct({ type: Schema.Literal("response.output_item.done"), - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), item: OutputItem }) const ResponseOutputTextDeltaEvent = Schema.Struct({ type: Schema.Literal("response.output_text.delta"), item_id: Schema.String, - output_index: Schema.Number, - content_index: Schema.Number, + output_index: Schema.Int, + content_index: Schema.Int, delta: Schema.String, - sequence_number: Schema.Number, + sequence_number: Schema.optionalKey(Schema.Int), logprobs: Schema.optionalKey(Schema.Array(Schema.Unknown)) }) const ResponseOutputTextAnnotationAddedEvent = Schema.Struct({ type: Schema.Literal("response.output_text.annotation.added"), item_id: Schema.String, - output_index: Schema.Number, - content_index: Schema.Number, - annotation_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + content_index: Schema.Int, + annotation_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), annotation: Annotation }) const ResponseReasoningSummaryPartAddedEvent = Schema.Struct({ type: Schema.Literal("response.reasoning_summary_part.added"), item_id: Schema.String, - output_index: Schema.Number, - summary_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + summary_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), part: SummaryTextContent }) const ResponseReasoningSummaryPartDoneEvent = Schema.Struct({ type: Schema.Literal("response.reasoning_summary_part.done"), item_id: Schema.String, - output_index: Schema.Number, - summary_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + summary_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), part: SummaryTextContent }) const ResponseReasoningSummaryTextDeltaEvent = Schema.Struct({ type: Schema.Literal("response.reasoning_summary_text.delta"), item_id: Schema.String, - output_index: Schema.Number, - summary_index: Schema.Number, + output_index: Schema.Int, + summary_index: Schema.Int, delta: Schema.String, - sequence_number: Schema.Number + sequence_number: Schema.optionalKey(Schema.Int) }) const ResponseFunctionCallArgumentsDeltaEvent = Schema.Struct({ type: Schema.Literal("response.function_call_arguments.delta"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), delta: Schema.String }) const ResponseFunctionCallArgumentsDoneEvent = Schema.Struct({ type: Schema.Literal("response.function_call_arguments.done"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), arguments: Schema.String }) const ResponseCodeInterpreterCallCodeDeltaEvent = Schema.Struct({ type: Schema.Literal("response.code_interpreter_call_code.delta"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), delta: Schema.String }) const ResponseCodeInterpreterCallCodeDoneEvent = Schema.Struct({ type: Schema.Literal("response.code_interpreter_call_code.done"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), code: Schema.String }) const ResponseApplyPatchCallOperationDiffDeltaEvent = Schema.Struct({ type: Schema.Literal("response.apply_patch_call_operation_diff.delta"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), delta: Schema.String }) const ResponseApplyPatchCallOperationDiffDoneEvent = Schema.Struct({ type: Schema.Literal("response.apply_patch_call_operation_diff.done"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), delta: Schema.optionalKey(Schema.String) }) const ResponseImageGenerationCallPartialImageEvent = Schema.Struct({ type: Schema.Literal("response.image_generation_call.partial_image"), item_id: Schema.String, - output_index: Schema.Number, - sequence_number: Schema.Number, + output_index: Schema.Int, + sequence_number: Schema.optionalKey(Schema.Int), partial_image_b64: Schema.String }) @@ -1031,10 +1048,30 @@ const ResponseErrorEvent = Schema.Struct({ code: Schema.NullOr(Schema.String), message: Schema.String, param: Schema.NullOr(Schema.String), - sequence_number: Schema.Number, - status: Schema.optionalKey(Schema.Number) + sequence_number: Schema.optionalKey(Schema.Int), + status: Schema.optionalKey(Schema.Int) }) +// OpenAI can nest stream error details under `error`. +const NestedResponseErrorEvent = Schema.Struct({ + type: Schema.Literal("error"), + error: Schema.Struct({ + code: Schema.NullOr(Schema.String), + message: Schema.String, + param: Schema.NullOr(Schema.String) + }), + sequence_number: Schema.optionalKey(Schema.Int), + status: Schema.optionalKey(Schema.Int) +}).pipe( + Schema.decodeTo( + ResponseErrorEvent, + SchemaTransformation.transform({ + decode: ({ error, ...rest }) => ({ ...rest, ...error }), + encode: ({ code, message, param, ...rest }) => ({ ...rest, error: { code, message, param } }) + }) + ) +) + const knownResponseStreamEventTypes = new Set([ "response.created", "response.completed", @@ -1123,6 +1160,7 @@ export const ResponseStreamEvent = Schema.Union([ ResponseApplyPatchCallOperationDiffDoneEvent, ResponseImageGenerationCallPartialImageEvent, ResponseErrorEvent, + NestedResponseErrorEvent, UnknownResponseStreamEvent ]) @@ -1166,10 +1204,10 @@ export type ResponseStreamEvent = typeof ResponseStreamEvent.Type */ export const Embedding = Schema.Struct({ embedding: Schema.Union([ - Schema.Array(Schema.Number), + Schema.Array(Schema.Finite), Schema.String ]), - index: Schema.Number, + index: Schema.Int, object: Schema.optionalKey(Schema.String) }) @@ -1213,12 +1251,12 @@ export const CreateEmbeddingRequest = Schema.Struct({ input: Schema.Union([ Schema.String, Schema.Array(Schema.String), - Schema.Array(Schema.Number), - Schema.Array(Schema.Array(Schema.Number)) + Schema.Array(Schema.Int), + Schema.Array(Schema.Array(Schema.Int)) ]), model: Schema.String, encoding_format: Schema.optionalKey(Schema.Literals(["float", "base64"])), - dimensions: Schema.optionalKey(Schema.Number), + dimensions: Schema.optionalKey(Schema.Int), user: Schema.optionalKey(Schema.String) }) @@ -1262,8 +1300,8 @@ export const CreateEmbeddingResponse = Schema.Struct({ object: Schema.optionalKey(Schema.Literal("list")), usage: Schema.optionalKey( Schema.Struct({ - prompt_tokens: Schema.Number, - total_tokens: Schema.Number + prompt_tokens: Schema.Int, + total_tokens: Schema.Int }) ) }) diff --git a/packages/ai/openai/src/OpenAiTelemetry.ts b/packages/ai/openai/src/OpenAiTelemetry.ts index 84b75332d..6af5c9b4f 100644 --- a/packages/ai/openai/src/OpenAiTelemetry.ts +++ b/packages/ai/openai/src/OpenAiTelemetry.ts @@ -29,7 +29,7 @@ import * as Telemetry from "effect/unstable/ai/Telemetry" export type OpenAiTelemetryAttributes = Simplify< & Telemetry.GenAITelemetryAttributes & Telemetry.AttributesWithPrefix - & Telemetry.AttributesWithPrefix + & Telemetry.AttributesWithPrefix > /** diff --git a/packages/ai/openai/src/internal/errors.ts b/packages/ai/openai/src/internal/errors.ts index a1be0c963..fc93181b2 100644 --- a/packages/ai/openai/src/internal/errors.ts +++ b/packages/ai/openai/src/internal/errors.ts @@ -8,6 +8,7 @@ import * as Redactable from "effect/Redactable" import * as Schema from "effect/Schema" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -27,6 +28,11 @@ export const OpenAiErrorBody = Schema.Struct({ }) }) +const OpenAiCompatibleErrorBody = Schema.Struct({ + error: Schema.String, + code: Schema.optional(Schema.String) +}) + // ============================================================================= // Error Mappers // ============================================================================= @@ -42,6 +48,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenAiClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapHttpClientError = dual< (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect, @@ -135,14 +152,25 @@ const mapStatusCodeError = Effect.fnUntraced(function*( json = undefined } const decoded = Schema.decodeUnknownOption(OpenAiErrorBody)(json) + const compatibleDecoded = Schema.decodeUnknownOption(OpenAiCompatibleErrorBody)(json) + const message = Option.isSome(decoded) + ? decoded.value.error.message + : Option.isSome(compatibleDecoded) + ? compatibleDecoded.value.error + : undefined + const errorCode = Option.isSome(decoded) + ? decoded.value.error.code ?? null + : Option.isSome(compatibleDecoded) + ? compatibleDecoded.value.code ?? null + : null const reason = mapStatusCodeToReason({ status, headers, - message: Option.isSome(decoded) ? decoded.value.error.message : undefined, + message, http: buildHttpContext({ request, response, body }), metadata: { - errorCode: Option.isSome(decoded) ? decoded.value.error.code ?? null : null, + errorCode, errorType: Option.isSome(decoded) ? decoded.value.error.type ?? null : null, requestId: requestId ?? null } @@ -213,51 +241,6 @@ export const buildHttpContext = (params: { // HTTP Status Code // ============================================================================= -const buildInvalidRequestDescription = (params: { - readonly status: number - readonly message: string | undefined - readonly method: string - readonly url: string - readonly errorCode: string | null - readonly errorType: string | null - readonly requestId: string | null - readonly body: string | undefined -}): string => { - const parts: Array = [] - - // Primary message or status description - if (params.message) { - parts.push(params.message) - } else { - parts.push(`HTTP ${params.status}`) - } - - // Request context - parts.push(`(${params.method} ${params.url})`) - - // Error code/type if available - if (params.errorCode) { - parts.push(`[code: ${params.errorCode}]`) - } else if (params.errorType) { - parts.push(`[type: ${params.errorType}]`) - } - - // Request ID for debugging - if (params.requestId) { - parts.push(`[requestId: ${params.requestId}]`) - } - - // If no message and we have body, show truncated body - if (!params.message && params.body) { - const truncated = params.body.length > 200 - ? params.body.slice(0, 200) + "..." - : params.body - parts.push(`Response: ${truncated}`) - } - - return parts.join(" ") -} - /** @internal */ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { readonly status: number @@ -266,7 +249,7 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http readonly metadata: OpenAiErrorMetadata readonly http: typeof AiError.HttpContext.Type }): AiError.AiErrorReason => { - const invalidRequestDescription = buildInvalidRequestDescription({ + const errorDescription = AiError.buildErrorDescription({ status, message, method: http.request.method, @@ -280,32 +263,39 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http switch (status) { case 400: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openai: metadata }, http }) case 401: return new AiError.AuthenticationError({ kind: "InvalidKey", + description: errorDescription, metadata, http }) case 403: return new AiError.AuthenticationError({ kind: "InsufficientPermissions", + description: errorDescription, metadata, http }) case 404: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openai: metadata }, http }) case 409: case 422: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, + metadata: { openai: metadata }, + http + }) + case 402: + return new AiError.QuotaExhaustedError({ metadata: { openai: metadata }, http }) @@ -313,7 +303,9 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http // Best-effort detection: OpenAI returns insufficient_quota for billing/quota issues if ( metadata.errorCode === "insufficient_quota" || - metadata.errorType === "insufficient_quota" + metadata.errorType === "insufficient_quota" || + metadata.errorCode === "billing_insufficient_balance" || + metadata.errorType === "billing_insufficient_balance" ) { return new AiError.QuotaExhaustedError({ metadata: { openai: metadata }, diff --git a/packages/ai/openai/src/internal/utilities.ts b/packages/ai/openai/src/internal/utilities.ts index 1aca7dae4..26cf670c3 100644 --- a/packages/ai/openai/src/internal/utilities.ts +++ b/packages/ai/openai/src/internal/utilities.ts @@ -19,7 +19,7 @@ export const resolveFinishReason = ( if (finishReason == null) { return hasToolCalls ? "tool-calls" : "stop" } - const reason = finishReasonMap[finishReason] + const reason = Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : undefined if (reason == null) { return hasToolCalls ? "tool-calls" : "unknown" } diff --git a/packages/ai/openai/test/OpenAiClient.test.ts b/packages/ai/openai/test/OpenAiClient.test.ts index d9069fd5a..c20107c5c 100644 --- a/packages/ai/openai/test/OpenAiClient.test.ts +++ b/packages/ai/openai/test/OpenAiClient.test.ts @@ -1,251 +1,113 @@ +import type { OpenAiSchema } from "@effect/ai-openai" import type * as Generated from "@effect/ai-openai/Generated" import * as Errors from "@effect/ai-openai/internal/errors" import * as OpenAiClient from "@effect/ai-openai/OpenAiClient" import * as OpenAiClientGenerated from "@effect/ai-openai/OpenAiClientGenerated" import * as OpenAiConfig from "@effect/ai-openai/OpenAiConfig" import { assert, describe, it } from "@effect/vitest" -import { Config, ConfigProvider, Effect, Layer, Redacted, Schema, Stream } from "effect" -import type * as AiError from "effect/unstable/ai/AiError" +import { Config, ConfigProvider, Context, Effect, Layer, Redacted, Schema, Stream } from "effect" import * as HttpClient from "effect/unstable/http/HttpClient" import * as HttpClientError from "effect/unstable/http/HttpClientError" import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" - -// ============================================================================= -// Mock Helpers -// ============================================================================= - -const makeMockResponse = (options: { - readonly status: number - readonly body: unknown - readonly request?: HttpClientRequest.HttpClientRequest -}): HttpClientResponse.HttpClientResponse => { - // Always use a plain request for the response to avoid Redacted headers in error contexts - const request = HttpClientRequest.get(options.request?.url ?? "/") - const json = JSON.stringify(options.body) - return HttpClientResponse.fromWeb( - request, - new Response(json, { - status: options.status, - headers: { "content-type": "application/json" } - }) - ) -} - -const makeMockStreamResponse = (options: { - readonly events: ReadonlyArray - readonly request?: HttpClientRequest.HttpClientRequest -}): HttpClientResponse.HttpClientResponse => { - const request = HttpClientRequest.get(options.request?.url ?? "/") - const body = options.events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") - return HttpClientResponse.fromWeb( - request, - new Response(body, { - status: 200, - headers: { "content-type": "text/event-stream" } - }) - ) -} - -const makeMockHttpClient = ( - handler: ( - request: HttpClientRequest.HttpClientRequest - ) => Effect.Effect -): HttpClient.HttpClient => - HttpClient.makeWith( - (effect) => - Effect.flatMap(effect, handler) as Effect.Effect< - HttpClientResponse.HttpClientResponse, - HttpClientError.HttpClientError, - never - >, - Effect.succeed - ) - -const makeResponseBody = ( - overrides: Partial = {} -): typeof Generated.Response.Encoded => ({ - id: "resp_test123", - object: "response", - created_at: 1, - model: "gpt-4o-mini", - status: "completed", - output: [], - metadata: null, - temperature: null, - top_p: null, - tools: [], - tool_choice: "auto", - error: null, - incomplete_details: null, - instructions: null, - parallel_tool_calls: false, - ...overrides -}) - -// ============================================================================= -// Tests -// ============================================================================= +import * as Socket from "effect/unstable/socket/Socket" +import { WS } from "vitest-websocket-mock" describe("OpenAiClient", () => { describe("make", () => { it.effect("sets Bearer token from apiKey", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("sk-test-12345") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - // Call method and ignore response parsing errors - we only care about the request - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - const authHeader = capturedRequest!.headers["authorization"] - assert.strictEqual(authHeader, "Bearer sk-test-12345") - })) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["authorization"], "Bearer sk-test-12345") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("sk-test-12345") + })))) it.effect("prepends default URL", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://api.openai.com/v1")) - })) + const requests = yield* MockHttpClient.requests + assert.isTrue(requests[0]?.url.startsWith("https://api.openai.com/v1")) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("uses custom apiUrl when provided", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - apiUrl: "https://custom.api.com/v2" - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://custom.api.com/v2")) - })) + const requests = yield* MockHttpClient.requests + assert.isTrue(requests[0]?.url.startsWith("https://custom.api.com/v2")) + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + apiUrl: "https://custom.api.com/v2" + })))) it.effect("sets OpenAI-Organization header when organizationId provided", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - organizationId: Redacted.make("org-12345") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) - - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["openai-organization"], "org-12345") - })) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["openai-organization"], "org-12345") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + organizationId: Redacted.make("org-12345") + })))) it.effect("sets OpenAI-Project header when projectId provided", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - projectId: Redacted.make("proj-67890") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) - - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["openai-project"], "proj-67890") - })) - - it.effect("applies transformClient option", () => - Effect.gen(function*() { - let transformApplied = false - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - ) + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => { - transformApplied = true - return client - } - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["openai-project"], "proj-67890") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + projectId: Redacted.make("proj-67890") + })))) - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + it.effect("applies transformClient option", () => { + let transformApplied = false + return Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + yield* client.createResponse({ model: "gpt-4o", input: "test" }) assert.isTrue(transformApplied) - })) + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => { + transformApplied = true + return client + } + }))) + }) it.effect("exposes transformed HttpClient via client field", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => - client.pipe(HttpClient.mapRequest(HttpClientRequest.setHeader("x-client-field", "enabled"))) - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - yield* client.client.execute(HttpClientRequest.get("/responses")).pipe(Effect.ignore) - - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://api.openai.com/v1")) - assert.strictEqual(capturedRequest!.headers["authorization"], "Bearer test-key") - assert.strictEqual(capturedRequest!.headers["x-client-field"], "enabled") - })) - - it.effect("applies OpenAiConfig transformClient after options transformClient", () => - Effect.gen(function*() { - let optionsTransformApplied = false - let configTransformApplied = false - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => { - optionsTransformApplied = true - return client.pipe( - HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) - ) - } - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + const client = yield* OpenAiClient.OpenAiClient + yield* client.client.execute(HttpClientRequest.get("/responses")) + + const requests = yield* MockHttpClient.requests + const request = requests[0] + assert.isTrue(request?.url.startsWith("https://api.openai.com/v1")) + assert.strictEqual(request?.headers["authorization"], "Bearer test-key") + assert.strictEqual(request?.headers["x-client-field"], "enabled") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => + client.pipe(HttpClient.mapRequest(HttpClientRequest.setHeader("x-client-field", "enabled"))) + })))) + + it.effect("applies OpenAiConfig transformClient after options transformClient", () => { + let optionsTransformApplied = false + let configTransformApplied = false + return Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient yield* client.createResponse({ model: "gpt-4o", input: "test" @@ -258,87 +120,58 @@ describe("OpenAiClient", () => { }) ) + const requests = yield* MockHttpClient.requests assert.isTrue(optionsTransformApplied) assert.isTrue(configTransformApplied) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["x-openai-transform"], "config") - })) + assert.strictEqual(requests[0]?.headers["x-openai-transform"], "config") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => { + optionsTransformApplied = true + return client.pipe( + HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) + ) + } + }))) + }) }) describe("OpenAiClientGenerated", () => { it.effect("sets Bearer token from apiKey", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClientGenerated.make({ - apiKey: Redacted.make("sk-generated-test") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated yield* client.createResponse({ - payload: { - model: "gpt-4o", - input: "test" - } + payload: { model: "gpt-4o", input: "test" } }) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["authorization"], "Bearer sk-generated-test") - })) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["authorization"], "Bearer sk-generated-test") + }).pipe(Effect.provide(makeGeneratedTestLayer({ + apiKey: Redacted.make("sk-generated-test") + })))) it.effect("prepends custom apiUrl", () => Effect.gen(function*() { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) - - const client = yield* OpenAiClientGenerated.make({ - apiKey: Redacted.make("test-key"), - apiUrl: "https://generated.example.test/v2" - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated yield* client.createResponse({ - payload: { - model: "gpt-4o", - input: "test" - } + payload: { model: "gpt-4o", input: "test" } }) - assert.isDefined(capturedRequest) - assert.isTrue(capturedRequest!.url.startsWith("https://generated.example.test/v2")) - })) - - it.effect("applies OpenAiConfig transformClient after options transformClient", () => - Effect.gen(function*() { - let optionsTransformApplied = false - let configTransformApplied = false - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - - const mockClient = makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: makeResponseBody(), request })) - }) + const requests = yield* MockHttpClient.requests + assert.isTrue(requests[0]?.url.startsWith("https://generated.example.test/v2")) + }).pipe(Effect.provide(makeGeneratedTestLayer({ + apiKey: Redacted.make("test-key"), + apiUrl: "https://generated.example.test/v2" + })))) - const client = yield* OpenAiClientGenerated.make({ - apiKey: Redacted.make("test-key"), - transformClient: (client) => { - optionsTransformApplied = true - return client.pipe( - HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) - ) - } - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + it.effect("applies OpenAiConfig transformClient after options transformClient", () => { + let optionsTransformApplied = false + let configTransformApplied = false + return Effect.gen(function*() { + const client = yield* OpenAiClientGenerated.OpenAiClientGenerated yield* client.createResponse({ - payload: { - model: "gpt-4o", - input: "test" - } + payload: { model: "gpt-4o", input: "test" } }).pipe( OpenAiConfig.withClientTransform((client) => { configTransformApplied = true @@ -348,40 +181,30 @@ describe("OpenAiClient", () => { }) ) + const requests = yield* MockHttpClient.requests assert.isTrue(optionsTransformApplied) assert.isTrue(configTransformApplied) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["x-openai-transform"], "config") - })) + assert.strictEqual(requests[0]?.headers["x-openai-transform"], "config") + }).pipe(Effect.provide(makeGeneratedTestLayer({ + apiKey: Redacted.make("test-key"), + transformClient: (client) => { + optionsTransformApplied = true + return client.pipe( + HttpClient.mapRequest(HttpClientRequest.setHeader("x-openai-transform", "options")) + ) + } + }))) + }) }) describe("layer", () => { - it.effect("creates working service", () => { - const HttpClientLayer = Layer.succeed( - HttpClient.HttpClient, - makeMockHttpClient(() => Effect.succeed(makeMockResponse({ status: 200, body: {} }))) - ) - - const MainLayer = OpenAiClient.layer({ - apiKey: Redacted.make("test-key") - }).pipe(Layer.provide(HttpClientLayer)) - - return Effect.gen(function*() { + it.effect("creates working service", () => + Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient assert.isNotNull(client.client) - }).pipe(Effect.provide(MainLayer)) - }) + }).pipe(Effect.provide(makeTestLayer()))) it.effect("layerConfig loads from Config", () => { - let capturedRequest: HttpClientRequest.HttpClientRequest | undefined - const HttpClientLayer = Layer.succeed( - HttpClient.HttpClient, - makeMockHttpClient((request) => { - capturedRequest = request - return Effect.succeed(makeMockResponse({ status: 200, body: {}, request })) - }) - ) - const configProvider = ConfigProvider.fromEnv({ env: { MY_API_KEY: "sk-config-key", @@ -389,45 +212,51 @@ describe("OpenAiClient", () => { } }) - // Use explicit config values to test the layerConfig mechanism - // Provide explicit configs that won't fail for optional fields - const MainLayer = OpenAiClient.layerConfig({ - apiKey: Config.redacted("MY_API_KEY"), - apiUrl: Config.string("MY_API_URL") - }).pipe( - Layer.provide(HttpClientLayer), - Layer.provide(ConfigProvider.layer(configProvider)) - ) - return Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient - yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.ignore) + yield* client.createResponse({ model: "gpt-4o", input: "test" }) - assert.isDefined(capturedRequest) - assert.strictEqual(capturedRequest!.headers["authorization"], "Bearer sk-config-key") - assert.isTrue(capturedRequest!.url.startsWith("https://config.api.com/v1")) - }).pipe(Effect.provide(MainLayer)) + const requests = yield* MockHttpClient.requests + assert.strictEqual(requests[0]?.headers["authorization"], "Bearer sk-config-key") + assert.isTrue(requests[0]?.url.startsWith("https://config.api.com/v1")) + }).pipe(Effect.provide(makeConfigTestLayer(configProvider))) }) }) + describe("request behavior", () => { + it.effect("redacts OpenAI-specific headers in AI error context", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ + model: "gpt-4o", + input: "test" + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "InvalidRequestError") + if (result.reason._tag !== "InvalidRequestError" || result.reason.http === undefined) { + return yield* Effect.die(new Error("Expected InvalidRequestError with HTTP context")) + } + const headers = result.reason.http.request.headers + assert.strictEqual(String(headers["authorization"]), "") + assert.strictEqual(String(headers["openai-organization"]), "") + assert.strictEqual(String(headers["openai-project"]), "") + }).pipe(Effect.provide(makeTestLayer({ + apiKey: Redacted.make("test-key"), + organizationId: Redacted.make("org-secret"), + projectId: Redacted.make("proj-secret") + }, { + _tag: "Json", + status: 400, + body: { error: { message: "Bad request" } } + })))) + }) + describe("error mapping", () => { it.effect("maps TransportError to NetworkError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient(() => - Effect.fail( - new HttpClientError.HttpClientError({ - reason: new HttpClientError.TransportError({ - request: HttpClientRequest.get("/"), - cause: new Error("Connection refused") - }) - }) - ) - ) - const client = yield* OpenAiClient.make({ apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + }) const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( Effect.flip ) @@ -436,129 +265,144 @@ describe("OpenAiClient", () => { assert.strictEqual(result.module, "OpenAiClient") assert.strictEqual(result.method, "createResponse") assert.strictEqual(result.reason._tag, "NetworkError") - })) + }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, makeTransportErrorHttpClient())))) it.effect("maps 400 status to InvalidRequestError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 400, - body: { error: { message: "Bad request" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.module, "OpenAiClient") assert.strictEqual(result.method, "createResponse") assert.strictEqual(result.reason._tag, "InvalidRequestError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 400, + body: { error: { message: "Bad request" } } + })))) it.effect("maps 401 status to AuthenticationError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 401, - body: { error: { message: "Invalid API key" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "AuthenticationError") - assert.strictEqual((result.reason as AiError.AuthenticationError).kind, "InvalidKey") - })) + if (result.reason._tag === "AuthenticationError") { + assert.strictEqual(result.reason.kind, "InvalidKey") + assert.strictEqual( + result.reason.description, + "Invalid API key (POST https://api.openai.com/v1/responses) [code: invalid_api_key] [requestId: req_openai]" + ) + assert.include(result.reason.message, "Invalid API key") + } + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 401, + body: { error: { message: "Invalid API key", type: "invalid_request_error", code: "invalid_api_key" } }, + headers: { "x-request-id": "req_openai" } + })))) + + it("preserves and truncates a fallback HTTP response", () => { + const body = `${"a".repeat(200)}b` + const reason = Errors.mapStatusCodeToReason({ + status: 400, + headers: {}, + message: undefined, + metadata: { errorCode: null, errorType: null, requestId: null }, + http: makeHttpContext("https://api.openai.com/v1/responses", body) + }) + + assert.strictEqual(reason._tag, "InvalidRequestError") + if (reason._tag !== "InvalidRequestError") { + throw new Error("Expected InvalidRequestError") + } + assert.strictEqual( + reason.description, + `HTTP 400 (POST https://api.openai.com/v1/responses) Response: ${"a".repeat(200)}...` + ) + }) it.effect("maps 403 status to AuthenticationError with InsufficientPermissions", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 403, - body: { error: { message: "Access denied" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "AuthenticationError") - assert.strictEqual((result.reason as AiError.AuthenticationError).kind, "InsufficientPermissions") - })) + if (result.reason._tag === "AuthenticationError") { + assert.strictEqual(result.reason.kind, "InsufficientPermissions") + assert.include(result.reason.description ?? "", "Access denied") + assert.include(result.reason.message, "Access denied") + } + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 403, + body: { error: { message: "Access denied" } } + })))) it.effect("maps 429 status to RateLimitError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 429, - body: { error: { message: "Rate limit exceeded", type: "rate_limit_error", code: null } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "RateLimitError") assert.isTrue(result.isRetryable) - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 429, + body: { error: { message: "Rate limit exceeded", type: "rate_limit_error", code: null } } + })))) it.effect("maps 429 with insufficient_quota code to QuotaExhaustedError", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 429, - body: { - error: { - message: "You exceeded your current quota", - type: "insufficient_quota", - code: "insufficient_quota" - } - }, - request - })) - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) + assert.strictEqual(result._tag, "AiError") + assert.strictEqual(result.reason._tag, "QuotaExhaustedError") + assert.isFalse(result.isRetryable) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 429, + body: { + error: { + message: "You exceeded your current quota", + type: "insufficient_quota", + code: "insufficient_quota" + } + } + })))) - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + it.effect("maps OpenAI-compatible 402 errors to QuotaExhaustedError", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "grok-4", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "QuotaExhaustedError") assert.isFalse(result.isRetryable) - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 402, + body: { error: "Your balance is too low", code: "billing_insufficient_balance" } + })))) + + it.effect("maps OpenAI-compatible insufficient balance errors to QuotaExhaustedError", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "grok-4", input: "test" }).pipe(Effect.flip) + + assert.strictEqual(result._tag, "AiError") + assert.strictEqual(result.reason._tag, "QuotaExhaustedError") + assert.isFalse(result.isRetryable) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 429, + body: { error: "Your balance is too low", code: "billing_insufficient_balance" } + })))) it("mapStatusCodeToReason detects insufficient_quota as QuotaExhaustedError", () => { const http = { @@ -600,66 +444,36 @@ describe("OpenAiClient", () => { it.effect("maps 5xx status to InternalProviderError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 500, - body: { error: { message: "Internal server error" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "InternalProviderError") assert.isTrue(result.isRetryable) - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 500, + body: { error: { message: "Internal server error" } } + })))) it.effect("maps schema error to InvalidOutputError reason", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 200, - body: { invalid: "response" }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - - const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe( - Effect.flip - ) + const client = yield* OpenAiClient.OpenAiClient + const result = yield* client.createResponse({ model: "gpt-4o", input: "test" }).pipe(Effect.flip) assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.method, "createResponse") assert.strictEqual(result.reason._tag, "InvalidOutputError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + body: { invalid: "response" } as any + })))) }) describe("createEmbedding", () => { it.effect("maps 400 error to AiError", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 400, - body: { error: { message: "Invalid model" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClient.OpenAiClient const result = yield* client.createEmbedding({ model: "invalid-model", input: "test" @@ -668,22 +482,15 @@ describe("OpenAiClient", () => { assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.method, "createEmbedding") assert.strictEqual(result.reason._tag, "InvalidRequestError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 400, + body: { error: { message: "Invalid model" } } + })))) it.effect("maps 429 error to RateLimitError", () => Effect.gen(function*() { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 429, - body: { error: { message: "Rate limit exceeded" } }, - request - })) - ) - - const client = yield* OpenAiClient.make({ - apiKey: Redacted.make("test-key") - }).pipe(Effect.provide(Layer.succeed(HttpClient.HttpClient, mockClient))) - + const client = yield* OpenAiClient.OpenAiClient const result = yield* client.createEmbedding({ model: "text-embedding-ada-002", input: "test" @@ -691,47 +498,70 @@ describe("OpenAiClient", () => { assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "RateLimitError") - })) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 429, + body: { error: { message: "Rate limit exceeded" } } + })))) }) describe("createResponseStream", () => { - it.effect("accepts keepalive stream events", () => { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockStreamResponse({ - request, - events: [ - { - type: "response.created", - sequence_number: 1, - response: makeResponseBody({ - id: "resp_stream", - status: "in_progress" - }) - }, - { - type: "keepalive", - sequence_number: 2 - }, - { - type: "response.completed", - sequence_number: 3, - response: makeResponseBody({ - id: "resp_stream" - }) - } - ] - })) - ) - - const HttpClientLayer = Layer.succeed(HttpClient.HttpClient, mockClient) + it.live("terminates an SSE stream at response.failed", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const [, stream] = yield* client.createResponseStream({ model: "gpt-4o", input: "test" }) + const result = yield* Stream.runCollect(stream).pipe(Effect.timeoutOption("100 millis")) + + assert.strictEqual(result._tag, "Some") + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Sse", + events: [{ + type: "response.failed", + sequence_number: 1, + response: makeResponseBody({ status: "failed" }) + }], + keepOpen: true + })))) + + it.live("terminates a WebSocket stream at response.failed", () => + Effect.gen(function*() { + const server = yield* Effect.acquireRelease( + Effect.sync(() => new WS("wss://api.openai.com/v1/responses", { jsonProtocol: true })), + (server) => + Effect.sync(() => { + server.close() + WS.clean() + }) + ) + const event = { + type: "response.failed", + sequence_number: 1, + response: makeResponseBody({ status: "failed" }) + } + const result = yield* OpenAiClient.withWebSocketMode( + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const [, stream] = yield* client.createResponseStream({ model: "gpt-4o", input: "test" }) + const [events] = yield* Effect.all([ + Stream.runCollect(stream), + Effect.promise(() => server.nextMessage).pipe( + Effect.tap(() => Effect.sync(() => server.send(event))) + ) + ], { concurrency: "unbounded" }) + return events + }) + ).pipe( + Effect.provide(makeTestLayer()), + Effect.provideService(Socket.WebSocketConstructor, (url) => new globalThis.WebSocket(url)), + Effect.timeoutOption("1 second") + ) - const MainLayer = OpenAiClient.layer({ - apiKey: Redacted.make("test-key") - }).pipe(Layer.provide(HttpClientLayer)) + assert.strictEqual(result._tag, "Some") + })) - return Effect.gen(function*() { + it.effect("accepts keepalive stream events", () => + Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient - const [_, stream] = yield* client.createResponseStream({ model: "gpt-4o", input: "test" @@ -761,27 +591,161 @@ describe("OpenAiClient", () => { assert.strictEqual(completed.response.id, "resp_stream") } } - }).pipe(Effect.provide(MainLayer)) - }) - - it.effect("maps HTTP error before stream starts", () => { - const mockClient = makeMockHttpClient((request) => - Effect.succeed(makeMockResponse({ - status: 500, - body: { error: { message: "Server error" } }, - request - })) - ) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Sse", + events: [ + { + type: "response.created", + sequence_number: 1, + response: makeResponseBody({ id: "resp_stream", status: "in_progress" }) + }, + { + type: "keepalive", + sequence_number: 2 + }, + { + type: "response.completed", + sequence_number: 3, + response: makeResponseBody({ id: "resp_stream" }) + } + ] + })))) - const HttpClientLayer = Layer.succeed(HttpClient.HttpClient, mockClient) + it.effect("accepts response stream events without sequence numbers", () => + Effect.gen(function*() { + const client = yield* OpenAiClient.OpenAiClient + const [, stream] = yield* client.createResponseStream({ + model: "gpt-4o", + input: "test" + }) - const MainLayer = OpenAiClient.layer({ - apiKey: Redacted.make("test-key") - }).pipe(Layer.provide(HttpClientLayer)) + const events = yield* Stream.runCollect(stream) + const decoded = globalThis.Array.from(events) + + assert.deepStrictEqual(decoded.map((event) => event.type), [ + "response.created", + "response.output_item.added", + "response.content_part.added", + "response.output_text.delta", + "response.reasoning_text.delta", + "response.reasoning_text.done", + "response.content_part.done", + "response.completed" + ]) + assert.deepStrictEqual(decoded[0], { + type: "response.created", + response: { + id: "resp_test123", + object: "response", + model: "gpt-4o-mini", + created_at: 1, + output: [], + error: null, + incomplete_details: null + } + }) + assert.deepStrictEqual(decoded[3], { + type: "response.output_text.delta", + output_index: 0, + item_id: "msg_123", + content_index: 0, + delta: "hello", + sequence_number: 4 + }) + assert.deepStrictEqual([decoded[2], decoded[4], decoded[5], decoded[6]], [ + { + type: "response.content_part.added", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + part: { type: "reasoning_text", text: "" } + }, + { + type: "response.reasoning_text.delta", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + delta: "thinking..." + }, + { + type: "response.reasoning_text.done", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + text: "thinking..." + }, + { + type: "response.content_part.done", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + part: { type: "reasoning_text", text: "thinking..." } + } + ]) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Sse", + events: [ + { + type: "response.created", + response: makeResponseBody({ status: "in_progress" }) + }, + { + type: "response.output_item.added", + output_index: 0, + item: { + id: "msg_123", + type: "message", + role: "assistant", + status: "in_progress", + content: [] + } + }, + { + type: "response.content_part.added", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + part: { type: "reasoning_text", text: "" } + }, + { + type: "response.output_text.delta", + output_index: 0, + item_id: "msg_123", + content_index: 0, + delta: "hello", + sequence_number: 4 + }, + { + type: "response.reasoning_text.delta", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + delta: "thinking..." + }, + { + type: "response.reasoning_text.done", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + text: "thinking..." + }, + { + type: "response.content_part.done", + output_index: 0, + item_id: "rs_tmp_123", + content_index: 0, + part: { type: "reasoning_text", text: "thinking..." } + }, + { + type: "response.completed", + response: makeResponseBody() + } + ] + })))) - return Effect.gen(function*() { + it.effect("maps HTTP error before stream starts", () => + Effect.gen(function*() { const client = yield* OpenAiClient.OpenAiClient - const result = yield* client.createResponseStream({ model: "gpt-4o", input: "test" @@ -792,7 +756,187 @@ describe("OpenAiClient", () => { assert.strictEqual(result._tag, "AiError") assert.strictEqual(result.reason._tag, "InternalProviderError") - }).pipe(Effect.provide(MainLayer)) + }).pipe(Effect.provide(makeTestLayer(undefined, { + _tag: "Json", + status: 500, + body: { error: { message: "Server error" } } + })))) + }) +}) + +type MockResponse = + | { + readonly _tag: "Json" + readonly body: typeof Generated.Response.Encoded | { + readonly error: { + readonly message: string + readonly type?: string + readonly code?: string | null + } + } | { + readonly error: string + readonly code?: string + } + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "Sse" + readonly events: ReadonlyArray + readonly keepOpen?: boolean | undefined + readonly status?: number | undefined + readonly headers?: Record | undefined + } + +class MockOpenAiResponse extends Context.Service()("MockOpenAiResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = MockHttpClient.use((client) => client.requests) +} + +const makeHttpClientContext = Effect.gen(function*() { + const capturedRequests: Array = [] + const mock = yield* MockOpenAiResponse + + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + capturedRequests.push(request) + return makeResponse(request, mock.response) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + const mockHttpClient: MockHttpClient["Service"] = { + requests: Effect.sync(() => capturedRequests) + } + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, mockHttpClient) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClientContext) + +const defaultResponse: MockResponse = { + _tag: "Json", + body: makeResponseBody() +} + +const makeTestLayer = ( + options: OpenAiClient.Options = { apiKey: Redacted.make("test-key") }, + response: MockResponse = defaultResponse +) => + OpenAiClient.layer(options).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenAiResponse, { response })) + ) + +const makeGeneratedTestLayer = ( + options: OpenAiClientGenerated.Options, + response: MockResponse = defaultResponse +) => + OpenAiClientGenerated.layer(options).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenAiResponse, { response })) + ) + +const makeConfigTestLayer = (configProvider: ConfigProvider.ConfigProvider) => + OpenAiClient.layerConfig({ + apiKey: Config.Redacted("MY_API_KEY"), + apiUrl: Config.String("MY_API_URL") + }).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenAiResponse, { response: defaultResponse })), + Layer.provide(ConfigProvider.layer(configProvider)) + ) + +const makeTransportErrorHttpClient = (): HttpClient.HttpClient => + HttpClient.makeWith( + (requestEffect) => + Effect.flatMap(requestEffect, (request) => + Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.TransportError({ + request, + cause: new Error("Connection refused") + }) + }) + )), + Effect.succeed + ) + +function makeResponseBody( + overrides: Partial = {} +): typeof Generated.Response.Encoded { + return { + id: "resp_test123", + object: "response", + created_at: 1, + model: "gpt-4o-mini", + status: "completed", + output: [], + metadata: null, + temperature: null, + top_p: null, + tools: [], + tool_choice: "auto", + error: null, + incomplete_details: null, + instructions: null, + parallel_tool_calls: false, + ...overrides + } +} + +const makeResponse = ( + request: HttpClientRequest.HttpClientRequest, + response: MockResponse +): HttpClientResponse.HttpClientResponse => { + const contentType = response._tag === "Json" + ? "application/json" + : "text/event-stream" + const body = response._tag === "Json" + ? JSON.stringify(response.body) + : response.events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + + const httpResponse = HttpClientResponse.fromWeb( + request, + new Response(body, { + status: response.status ?? 200, + headers: { + "content-type": contentType, + ...response.headers + } }) + ) + if (response._tag !== "Sse" || response.keepOpen !== true) return httpResponse + + const stream = Stream.concat( + Stream.succeed(new TextEncoder().encode(body)), + Stream.never + ) + // `fromWeb` stores the ReadableStream internally, so a proxy is needed to replace it with a non-terminating test stream. + return new Proxy(httpResponse, { + get(target, property) { + if (property === "stream") return stream + const value = Reflect.get(target, property, target) + return typeof value === "function" ? value.bind(target) : value + } }) +} + +const makeHttpContext = (url: string, body: string) => ({ + request: { + method: "POST" as const, + url, + urlParams: [], + hash: undefined, + headers: {} + }, + body }) diff --git a/packages/ai/openai/test/OpenAiLanguageModel.test.ts b/packages/ai/openai/test/OpenAiLanguageModel.test.ts index d85cb8644..75b0f15ff 100644 --- a/packages/ai/openai/test/OpenAiLanguageModel.test.ts +++ b/packages/ai/openai/test/OpenAiLanguageModel.test.ts @@ -1,8 +1,8 @@ -import { Generated, OpenAiClient, OpenAiLanguageModel, OpenAiTool } from "@effect/ai-openai" +import { type Generated, OpenAiClient, OpenAiLanguageModel, OpenAiSchema, OpenAiTool } from "@effect/ai-openai" import { assert, describe, it } from "@effect/vitest" -import { deepStrictEqual, strictEqual } from "@effect/vitest/utils" -import { Array, Context, Effect, Layer, Redacted, Ref, Schema, Stream } from "effect" -import { LanguageModel, Prompt, Tool, Toolkit } from "effect/unstable/ai" +import { assertTrue, deepStrictEqual, strictEqual } from "@effect/vitest/utils" +import { Array, Context, Effect, Layer, Redacted, Ref, Schema, SchemaGetter, Stream } from "effect" +import { type AiError, LanguageModel, Prompt, Response as AiResponse, Tool, Toolkit } from "effect/unstable/ai" import { HttpClient, type HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("OpenAiLanguageModel", () => { @@ -31,6 +31,69 @@ describe("OpenAiLanguageModel", () => { describe("generateText", () => { describe("message preparation", () => { + it.effect("forwards prompt cache configuration and text breakpoints", () => + Effect.gen(function*() { + const breakpoint = { mode: "explicit" } as const + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + Prompt.systemMessage({ + content: "Stable instructions", + options: { openai: { promptCacheBreakpoint: breakpoint } } + }), + Prompt.userMessage({ + content: [Prompt.textPart({ + text: "Stable context", + options: { openai: { promptCacheBreakpoint: breakpoint } } + })] + }) + ]) + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-5.6", { + prompt_cache_key: "assistant:v1", + prompt_cache_options: { mode: "explicit", ttl: "30m" } + })) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + strictEqual(body.prompt_cache_key, "assistant:v1") + deepStrictEqual(body.prompt_cache_options, { mode: "explicit", ttl: "30m" }) + deepStrictEqual(body.input, [{ + role: "developer", + content: [{ + type: "input_text", + text: "Stable instructions", + prompt_cache_breakpoint: breakpoint + }] + }, { + role: "user", + content: [{ + type: "input_text", + text: "Stable context", + prompt_cache_breakpoint: breakpoint + }] + }]) + }).pipe(Effect.provide(makeTestLayer({ body: { model: "gpt-5.6" as any } })))) + + it.effect("forwards implicit prompt cache mode without text breakpoints", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ prompt: "Stable context" }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-5.6", { + prompt_cache_options: { mode: "implicit" } + })) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + deepStrictEqual(body.prompt_cache_options, { mode: "implicit" }) + deepStrictEqual(body.input, [{ + role: "user", + content: [{ type: "input_text", text: "Stable context" }] + }]) + }).pipe(Effect.provide(makeTestLayer({ body: { model: "gpt-5.6" as any } })))) + describe("system messages", () => { it.effect("uses system role for standard models", () => Effect.gen(function*() { @@ -46,7 +109,10 @@ describe("OpenAiLanguageModel", () => { const systemMessage = body.input.find((m: any) => m.role === "system") assert.isDefined(systemMessage) - strictEqual(systemMessage.content, "You are a helpful assistant") + deepStrictEqual(systemMessage.content, [{ + type: "input_text", + text: "You are a helpful assistant" + }]) }).pipe(Effect.provide(makeTestLayer()))) it.effect("uses developer role for reasoning models", () => @@ -63,7 +129,10 @@ describe("OpenAiLanguageModel", () => { const devMessage = body.input.find((m: any) => m.role === "developer") assert.isDefined(devMessage) - strictEqual(devMessage.content, "You are a helpful assistant") + deepStrictEqual(devMessage.content, [{ + type: "input_text", + text: "You are a helpful assistant" + }]) }).pipe(Effect.provide(makeTestLayer({ body: { model: "o1" } })))) it.effect("uses developer role for gpt-5 models", () => @@ -139,6 +208,35 @@ describe("OpenAiLanguageModel", () => { }]) }).pipe(Effect.provide(makeTestLayer()))) + it.effect("handles image strings", () => + Effect.gen(function*() { + const base64 = "iVBORw0KGgo=" + const dataUrl = `data:image/png;base64,${base64}` + const upperCaseDataUrl = `DATA:image/png;base64,${base64}` + const url = "https://example.com/image.png" + + yield* LanguageModel.generateText({ + prompt: Prompt.make([Prompt.userMessage({ + content: [base64, dataUrl, upperCaseDataUrl, url].map((data) => + Prompt.filePart({ mediaType: "image/png", data }) + ) + })]) + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + assert.deepStrictEqual(body.input, [{ + role: "user", + content: [ + { type: "input_image", image_url: `data:image/png;base64,${base64}`, detail: "auto" }, + { type: "input_image", image_url: dataUrl, detail: "auto" }, + { type: "input_image", image_url: upperCaseDataUrl, detail: "auto" }, + { type: "input_image", image_url: url, detail: "auto" } + ] + }]) + }).pipe(Effect.provide(makeTestLayer()))) + it.effect("handles image with custom detail level", () => Effect.gen(function*() { yield* LanguageModel.generateText({ @@ -313,6 +411,45 @@ describe("OpenAiLanguageModel", () => { strictEqual(reasoningItem.id, "reasoning_123") }).pipe(Effect.provide(makeTestLayer({ body: { model: "o1" } })))) + it.effect("replays encrypted reasoning from response parts", () => + Effect.gen(function*() { + const history = Prompt.fromResponseParts([ + AiResponse.makePart("reasoning-start", { + id: "reasoning_123:0", + metadata: { openai: { itemId: "reasoning_123" } } + }), + AiResponse.makePart("reasoning-delta", { + id: "reasoning_123:0", + delta: "Let me think..." + }), + AiResponse.makePart("reasoning-end", { + id: "reasoning_123:0", + metadata: { + openai: { + itemId: "reasoning_123", + encryptedContent: "encrypted-reasoning" + } + } + }) + ]) + + yield* LanguageModel.generateText({ + prompt: Prompt.concat(history, Prompt.make("Continue")) + }).pipe(Effect.provide(OpenAiLanguageModel.model("o1"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const reasoningItem = body.input.find((item: any) => item.type === "reasoning") + + assert.isDefined(reasoningItem) + deepStrictEqual(reasoningItem, { + type: "reasoning", + id: "reasoning_123", + summary: [{ type: "summary_text", text: "Let me think..." }], + encrypted_content: "encrypted-reasoning" + }) + }).pipe(Effect.provide(makeTestLayer({ body: { model: "o1" } })))) + it.effect("converts tool call parts to function_call", () => Effect.gen(function*() { yield* LanguageModel.generateText({ @@ -336,7 +473,8 @@ describe("OpenAiLanguageModel", () => { id: "call_abc", name: "TestTool", isFailure: false, - result: { output: "result" } + result: { output: "result" }, + providerExecuted: false }) ] } @@ -378,7 +516,8 @@ describe("OpenAiLanguageModel", () => { id: "call_abc", name: "TestTool", isFailure: false, - result: { output: "result" } + result: { output: "result" }, + providerExecuted: false }) ] } @@ -394,6 +533,184 @@ describe("OpenAiLanguageModel", () => { strictEqual(toolOutput.call_id, "call_abc") strictEqual(toolOutput.output, JSON.stringify({ output: "result" })) }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("preserves string tool results", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Use the tool" }, + { + role: "assistant", + content: [ + Prompt.toolCallPart({ + id: "call_text", + name: "TestTool", + params: { input: "test" }, + providerExecuted: false + }) + ] + }, + { + role: "tool", + content: [ + Prompt.toolResultPart({ + id: "call_text", + name: "TestTool", + isFailure: false, + result: "PLAIN_TEXT_SENTINEL\n", + providerExecuted: false + }) + ] + } + ]), + toolkit: TestToolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const toolOutput = body.input.find((item: any) => item.type === "function_call_output") + + assert.isDefined(toolOutput) + strictEqual(toolOutput.output, "PLAIN_TEXT_SENTINEL\n") + }).pipe(Effect.provide([makeTestLayer(), TestToolkitLayer]))) + + it.effect("emits only the specialized output for apply_patch results", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.ApplyPatch({})) + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Apply a patch" }, + { + role: "assistant", + content: [Prompt.toolCallPart({ + id: "call_apply_patch", + name: "OpenAiApplyPatch", + params: { + call_id: "call_apply_patch", + operation: { type: "delete_file", path: "old.ts" } + }, + providerExecuted: false + })] + }, + { + role: "tool", + content: [Prompt.toolResultPart({ + id: "call_apply_patch", + name: "OpenAiApplyPatch", + isFailure: false, + result: { status: "completed", output: "deleted" }, + providerExecuted: false + })] + } + ]), + toolkit, + disableToolCallResolution: true + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const outputs = body.input.filter((item: any) => + item.call_id === "call_apply_patch" && item.type.endsWith("_output") + ) + + deepStrictEqual(outputs.map((item: any) => item.type), ["apply_patch_call_output"]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("emits only the specialized output for shell results", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.Shell({})) + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Run a shell command" }, + { + role: "assistant", + content: [Prompt.toolCallPart({ + id: "call_shell", + name: "OpenAiShell", + params: { + action: { + commands: ["echo hello"], + timeout_ms: null, + max_output_length: null + } + }, + providerExecuted: false + })] + }, + { + role: "tool", + content: [Prompt.toolResultPart({ + id: "call_shell", + name: "OpenAiShell", + isFailure: false, + result: { + output: [{ + stdout: "hello\n", + stderr: "", + outcome: { type: "exit", exit_code: 0 } + }] + }, + providerExecuted: false + })] + } + ]), + toolkit, + disableToolCallResolution: true + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const outputs = body.input.filter((item: any) => + item.call_id === "call_shell" && item.type.endsWith("_output") + ) + + deepStrictEqual(outputs.map((item: any) => item.type), ["shell_call_output"]) + }).pipe(Effect.provide(makeTestLayer()))) + + it.effect("emits only the specialized output for local_shell results", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.LocalShell({})) + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Run a local shell command" }, + { + role: "assistant", + content: [Prompt.toolCallPart({ + id: "call_local_shell", + name: "OpenAiLocalShell", + params: { + action: { + type: "exec", + command: ["echo", "hello"], + env: {} + } + }, + providerExecuted: false + })] + }, + { + role: "tool", + content: [Prompt.toolResultPart({ + id: "call_local_shell", + name: "OpenAiLocalShell", + isFailure: false, + result: { output: "hello\n" }, + providerExecuted: false + })] + } + ]), + toolkit, + disableToolCallResolution: true + }).pipe(Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const outputs = body.input.filter((item: any) => + item.call_id === "call_local_shell" && item.type.endsWith("_output") + ) + + deepStrictEqual(outputs.map((item: any) => item.type), ["local_shell_call_output"]) + }).pipe(Effect.provide(makeTestLayer()))) }) }) @@ -720,6 +1037,112 @@ describe("OpenAiLanguageModel", () => { ]) )) + it.effect("routes invalid tool call params through failureMode: return without failing the effect", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(ReturnModeTool) + const handlers = toolkit.toLayer({ + ReturnModeTool: ({ input }) => Effect.succeed({ output: `processed: ${input}` }) + }) + + const result = yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(handlers) + ) + + strictEqual(result.toolResults.length, 1) + const toolResult = result.toolResults[0]! + strictEqual(toolResult.isFailure, true) + const failure = toolResult.result as AiError.AiError + strictEqual(failure._tag, "AiError") + strictEqual(failure.reason._tag, "ToolParameterValidationError") + }).pipe( + Effect.provide(makeTestLayer({ + body: { output: [makeFunctionCall("ReturnModeTool", { input: 123 })] } + })) + )) + + it.effect("converts transformed tool call params to the tool's standard encoded form", () => + Effect.gen(function*() { + const received = yield* Ref.make(undefined) + const toolkit = Toolkit.make(TransformParamsTool) + const handlers = toolkit.toLayer({ + TransformParamsTool: ({ input }) => + Ref.set(received, input).pipe( + Effect.as({ output: input * 2 }) + ) + }) + + const result = yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(handlers) + ) + + const toolCall = result.toolCalls[0]! + deepStrictEqual(toolCall.params, { input: "21" }) + strictEqual(yield* Ref.get(received), 21) + strictEqual(result.toolResults[0]!.isFailure, false) + deepStrictEqual(result.toolResults[0]!.result, { output: 42 }) + }).pipe( + Effect.provide(makeTestLayer({ + body: { output: [makeFunctionCall("TransformParamsTool", { input: "21" })] } + })) + )) + + it.effect("provides parameter encoding services to provider normalization", () => + Effect.gen(function*() { + const used = yield* Ref.make(false) + const toolkit = Toolkit.make(AsymmetricParamsTool) + const handlers = toolkit.toLayer({ + AsymmetricParamsTool: ({ input }) => Effect.succeed({ output: `processed: ${input}` }) + }) + + const result = yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(handlers), + Effect.provideService(ParamEncodeService, { use: Ref.set(used, true) }) + ) + + strictEqual(yield* Ref.get(used), true) + strictEqual(result.toolResults[0]!.isFailure, false) + deepStrictEqual(result.toolResults[0]!.result, { output: "processed: hello" }) + }).pipe( + Effect.provide(makeTestLayer({ + body: { output: [makeFunctionCall("AsymmetricParamsTool", { input: "hello" })] } + })) + )) + + it.effect("provides parameter encoding services to provider normalization when tool call resolution is disabled", () => + Effect.gen(function*() { + const used = yield* Ref.make(false) + const toolkit = Toolkit.make(AsymmetricParamsTool) + + const result = yield* LanguageModel.generateText({ + prompt: "Use the tool", + toolkit, + disableToolCallResolution: true + }).pipe( + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provideService(ParamEncodeService, { use: Ref.set(used, true) }) + ) + + strictEqual(yield* Ref.get(used), true) + const toolCall = result.toolCalls[0]! + deepStrictEqual(toolCall.params, { input: "hello" }) + }).pipe( + Effect.provide(makeTestLayer({ + body: { output: [makeFunctionCall("AsymmetricParamsTool", { input: "hello" })] } + })) + )) + it.effect("uses canonical OpenAiMcp name for mcp_call", () => Effect.gen(function*() { const result = yield* LanguageModel.generateText({ @@ -738,6 +1161,7 @@ describe("OpenAiLanguageModel", () => { assert.isDefined(toolResult) if (toolResult?.type === "tool-result") { strictEqual(toolResult.name, "OpenAiMcp") + assertTrue(!toolResult.isFailure, "expected a successful MCP result") strictEqual(toolResult.result.name, "CheckPackage") } }).pipe(Effect.provide(makeTestLayer({ @@ -746,6 +1170,38 @@ describe("OpenAiLanguageModel", () => { } })))) + it.each(["gpt-4.1", "gpt-5.6"] as const)( + "maps stable web search action to tool call parameters with %s", + (model) => + Effect.runPromise( + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.WebSearch({})) + const result = yield* LanguageModel.generateText({ + prompt: "Search the web", + toolkit + }).pipe(Effect.provide(OpenAiLanguageModel.model(model))) + + const toolCall = result.content.find((part) => part.type === "tool-call") + assert.isDefined(toolCall) + assert.deepStrictEqual(toolCall.params, { + action: { type: "search", query: "Effect TypeScript" } + }) + + const toolResult = result.content.find((part) => part.type === "tool-result") + assert.isDefined(toolResult) + assert.deepStrictEqual(toolResult.result, { + action: { type: "search", query: "Effect TypeScript" }, + status: "completed" + }) + }).pipe(Effect.provide(makeTestLayer({ + body: { + model, + output: [makeWebSearchCall()] + } + }))) + ) + ) + it.effect("uses canonical OpenAiMcp name for mcp_approval_request", () => Effect.gen(function*() { const result = yield* LanguageModel.generateText({ @@ -799,17 +1255,22 @@ describe("OpenAiLanguageModel", () => { assert.isDefined(finishPart) if (finishPart?.type === "finish") { deepStrictEqual(finishPart.usage.inputTokens, { - uncached: 10, + uncached: 7, total: 10, - cacheRead: 0, - cacheWrite: undefined + cacheRead: 3, + cacheWrite: 4 }) deepStrictEqual(finishPart.usage.outputTokens, { total: 20, text: 20, reasoning: 0 }) } }).pipe(Effect.provide(makeTestLayer({ body: { output: [makeTextOutput("Hello")], - usage: makeUsage() + usage: makeUsage({ + input_tokens_details: { + cached_tokens: 3, + cache_write_tokens: 4 + } + }) } })))) @@ -902,6 +1363,48 @@ describe("OpenAiLanguageModel", () => { }) describe("streamText", () => { + it.effect("extracts usage information", () => + Effect.gen(function*() { + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ status: "in_progress" }) + }, + { + type: "response.completed", + sequence_number: 2, + response: makeDefaultResponse({ + usage: makeUsage({ + input_tokens_details: { + cached_tokens: 3, + cache_write_tokens: 4 + } + }) + }) + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Hello" + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const finishPart = globalThis.Array.from(partsChunk).find((part) => part.type === "finish") + assert.isDefined(finishPart) + if (finishPart?.type === "finish") { + deepStrictEqual(finishPart.usage.inputTokens, { + uncached: 7, + total: 10, + cacheRead: 3, + cacheWrite: 4 + }) + } + })) + it.effect("emits valid apply_patch tool params JSON for update_file diffs", () => Effect.gen(function*() { const diff = "@@ -1 +1 @@\n-old\n+new\n" @@ -1046,6 +1549,131 @@ describe("OpenAiLanguageModel", () => { assert.isDefined(toolParamsEnd) })) + it.effect("routes invalid tool call params through failureMode: return without failing the stream", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(ReturnModeTool) + const handlers = toolkit.toLayer({ + ReturnModeTool: ({ input }) => Effect.succeed({ output: `processed: ${input}` }) + }) + + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ + id: "resp_invalid_params", + status: "in_progress", + output: [] + }) + }, + { + type: "response.output_item.added", + sequence_number: 2, + output_index: 0, + item: { + type: "function_call", + id: "fc_1", + call_id: "call_1", + name: "ReturnModeTool", + arguments: "", + status: "in_progress" + } + }, + { + type: "response.function_call_arguments.done", + sequence_number: 3, + output_index: 0, + item_id: "fc_1", + name: "ReturnModeTool", + arguments: "{\"input\":123}" + }, + { + type: "response.completed", + sequence_number: 4, + response: makeDefaultResponse({ + id: "resp_invalid_params", + status: "completed", + output: [] + }) + } + ] as unknown as ReadonlyArray + + const partsChunk = yield* LanguageModel.streamText({ + prompt: "Use the test tool", + toolkit + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)), + Effect.provide(handlers) + ) + + const parts = globalThis.Array.from(partsChunk) + const toolResults = parts.filter((part) => part.type === "tool-result") + strictEqual(toolResults.length, 1) + const toolResult = toolResults[0] + if (toolResult?.type === "tool-result") { + strictEqual(toolResult.isFailure, true) + const result = toolResult.result as AiError.AiError + strictEqual(result._tag, "AiError") + strictEqual(result.reason._tag, "ToolParameterValidationError") + } + assert.isDefined(parts.find((part) => part.type === "finish")) + })) + + it.effect("waits for the stable streamed web search action before emitting the tool call", () => + Effect.gen(function*() { + const toolkit = Toolkit.make(OpenAiTool.WebSearch({})) + const streamEvents = [ + { + type: "response.created", + sequence_number: 1, + response: makeDefaultResponse({ status: "in_progress" }) + }, + { + type: "response.output_item.added", + sequence_number: 2, + output_index: 0, + item: { + type: "web_search_call", + id: "ws_123", + status: "in_progress" + } + }, + { + type: "response.output_item.done", + sequence_number: 3, + output_index: 0, + item: makeWebSearchCall() + } + ] as unknown as ReadonlyArray + + const parts = yield* LanguageModel.streamText({ + prompt: "Search the web", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(makeStreamTestLayer(streamEvents)) + ) + + const toolCalls = parts.filter((part) => part.type === "tool-call") + strictEqual(toolCalls.length, 1) + const toolCall = toolCalls[0] + assert.isDefined(toolCall) + assert.deepStrictEqual(toolCall.params, { + action: { type: "search", query: "Effect TypeScript" } + }) + + const toolResult = parts.find((part) => part.type === "tool-result") + assert.isDefined(toolResult) + assert.deepStrictEqual(toolResult.result, { + action: { type: "search", query: "Effect TypeScript" }, + status: "completed" + }) + })) + it.effect("handles reasoning summary events when reasoning state is missing", () => Effect.gen(function*() { const streamEvents = [ @@ -1160,6 +1788,7 @@ describe("OpenAiLanguageModel", () => { assert.isDefined(toolResult) if (toolResult?.type === "tool-result") { strictEqual(toolResult.name, "OpenAiMcp") + assertTrue(!toolResult.isFailure, "expected a successful MCP result") strictEqual(toolResult.result.name, "CheckPackage") } })) @@ -1322,7 +1951,7 @@ class MockHttpClient extends Context.Service>([]) @@ -1459,6 +2088,16 @@ const makeFunctionCall = ( ...overrides }) +const makeWebSearchCall = ( + overrides: Partial = {} +): Generated.WebSearchToolCall => ({ + type: "web_search_call", + id: "ws_123", + status: "completed", + action: { type: "search", query: "Effect TypeScript" }, + ...overrides +}) + const makeMcpCall = ( name: string, args: Record, @@ -1498,9 +2137,16 @@ const makeReasoningOutput = ( ...overrides }) +type TestResponseUsage = Omit & { + readonly input_tokens_details: { + readonly cached_tokens: number + readonly cache_write_tokens?: number + } +} + const makeUsage = ( - overrides: Partial = {} -): Generated.ResponseUsage => ({ + overrides: Partial = {} +): TestResponseUsage => ({ input_tokens: 10, output_tokens: 20, total_tokens: 30, @@ -1515,6 +2161,42 @@ const TestTool = Tool.make("TestTool", { success: Schema.Struct({ output: Schema.String }) }) +const ReturnModeTool = Tool.make("ReturnModeTool", { + description: "A test tool", + failureMode: "return", + parameters: Schema.Struct({ input: Schema.String }), + success: Schema.Struct({ output: Schema.String }), + failure: Schema.Struct({ error: Schema.String }) +}) + +const TransformParamsTool = Tool.make("TransformParamsTool", { + description: "A test tool", + parameters: Schema.Struct({ input: Schema.FiniteFromString }), + success: Schema.Struct({ output: Schema.Finite }) +}) + +class ParamEncodeService extends Context.Service +}>()("ParamEncodeService") {} + +const AsymmetricParam = Schema.String.pipe( + Schema.decodeTo(Schema.String, { + decode: SchemaGetter.passthrough(), + encode: SchemaGetter.transformOrFail((value) => + Effect.service(ParamEncodeService).pipe( + Effect.flatMap((service) => service.use), + Effect.as(value) + ) + ) + }) +) + +const AsymmetricParamsTool = Tool.make("AsymmetricParamsTool", { + description: "A test tool", + parameters: Schema.Struct({ input: AsymmetricParam }), + success: Schema.Struct({ output: Schema.String }) +}) + const TestToolkit = Toolkit.make(TestTool) const McpToolkit = Toolkit.make(OpenAiTool.Mcp({ diff --git a/packages/ai/openai/test/OpenAiSchema.test.ts b/packages/ai/openai/test/OpenAiSchema.test.ts index 70a30f22c..8cdd06a1b 100644 --- a/packages/ai/openai/test/OpenAiSchema.test.ts +++ b/packages/ai/openai/test/OpenAiSchema.test.ts @@ -1,7 +1,10 @@ +import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai" import * as OpenAiSchema from "@effect/ai-openai/OpenAiSchema" import { assert, describe, it } from "@effect/vitest" -import { Effect, Schema, Stream } from "effect" +import { Effect, Layer, Schema, Stream } from "effect" +import { LanguageModel } from "effect/unstable/ai" import * as Sse from "effect/unstable/encoding/Sse" +import { HttpClientRequest, HttpClientResponse } from "effect/unstable/http" const makeResponse = (overrides: Record = {}) => ({ id: "resp_123", @@ -14,6 +17,14 @@ const makeResponse = (overrides: Record = {}) => ({ }) describe("OpenAiSchema", () => { + it("accepts max reasoning effort", () => { + const decoded = Schema.decodeUnknownSync(OpenAiSchema.CreateResponse)({ + reasoning: { effort: "max" } + }) + + assert.strictEqual(decoded.reasoning?.effort, "max") + }) + it("decodes a representative response payload", () => { const decoded = Schema.decodeUnknownSync(OpenAiSchema.Response)({ ...makeResponse(), @@ -77,6 +88,25 @@ describe("OpenAiSchema", () => { } }) + it("decodes image generation lifecycle statuses", () => { + for (const status of ["generating", "failed"]) { + const decoded = Schema.decodeUnknownSync(OpenAiSchema.Response)({ + ...makeResponse(), + output: [{ + id: "image_1", + type: "image_generation_call", + status, + result: null + }] + }) + + assert.strictEqual(decoded.output[0].type, "image_generation_call") + if (decoded.output[0].type === "image_generation_call") { + assert.strictEqual(decoded.output[0].status, status) + } + } + }) + it("decodes required stream events", () => { const response = makeResponse({ status: "in_progress" }) const applyPatchItem = { @@ -253,6 +283,110 @@ describe("OpenAiSchema", () => { assert.isDefined(malformed) })) + it("decodes the error event whether the payload is flat or nested under `error`", () => { + const flat = Schema.decodeUnknownSync(OpenAiSchema.ResponseStreamEvent)({ + type: "error", + code: "ERR", + message: "boom", + param: null, + sequence_number: 1 + }) + assert.deepStrictEqual(flat, { + type: "error", + code: "ERR", + message: "boom", + param: null, + sequence_number: 1 + }) + + const nested = Schema.decodeUnknownSync(OpenAiSchema.ResponseStreamEvent)({ + type: "error", + error: { + type: "insufficient_quota", + code: "credit_balance_exhausted", + message: "You have no credits remaining.", + param: null + }, + sequence_number: 2 + }) + assert.deepStrictEqual(nested, { + type: "error", + code: "credit_balance_exhausted", + message: "You have no credits remaining.", + param: null, + sequence_number: 2 + }) + + const nestedWithStatus = Schema.decodeUnknownSync(OpenAiSchema.ResponseStreamEvent)({ + type: "error", + error: { + code: "rate_limited", + message: "Too many requests.", + param: null + }, + sequence_number: 3, + status: 429 + }) + assert.deepStrictEqual(nestedWithStatus, { + type: "error", + code: "rate_limited", + message: "Too many requests.", + param: null, + sequence_number: 3, + status: 429 + }) + }) + + it.effect("rejects error events missing spec-required fields", () => + Effect.gen(function*() { + const malformed = [ + { type: "error" }, + { type: "error", error: {}, sequence_number: 3 }, + { type: "error", error: { code: "x" }, sequence_number: 3 } + ] + for (const event of malformed) { + const failure = yield* Schema.decodeUnknownEffect(OpenAiSchema.ResponseStreamEvent)(event).pipe(Effect.flip) + assert.isDefined(failure) + } + })) + + it.effect("surfaces nested error events in SSE decoding instead of aborting the stream", () => + Effect.gen(function*() { + const sseBody = [ + { + type: "response.created", + sequence_number: 1, + response: makeResponse({ status: "in_progress" }) + }, + { + type: "error", + error: { + type: "insufficient_quota", + code: "credit_balance_exhausted", + message: "You have no credits remaining.", + param: null + }, + sequence_number: 2 + } + ].map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + + const events = yield* Stream.fromIterable([sseBody]).pipe( + Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)), + Stream.map((event) => event.data), + Stream.runCollect + ) + + const decoded = globalThis.Array.from(events) + assert.strictEqual(decoded.length, 2) + assert.deepStrictEqual(decoded[1], { + type: "error", + code: "credit_balance_exhausted", + message: "You have no credits remaining.", + param: null, + sequence_number: 2 + }) + })) + it("decodes embedding response variants (numeric + string/base64)", () => { const numeric = Schema.decodeUnknownSync(OpenAiSchema.CreateEmbeddingResponse)({ object: "list", @@ -270,4 +404,53 @@ describe("OpenAiSchema", () => { assert.strictEqual(numeric.data[0].embedding[0], 0.1) assert.strictEqual(base64.data[0].embedding, "AQID") }) + + it.effect("exposes the response.failed error payload", () => { + const response = HttpClientResponse.fromWeb( + HttpClientRequest.get("https://api.openai.com/v1/responses"), + new Response() + ) + const failed = Schema.decodeUnknownSync(OpenAiSchema.ResponseStreamEvent)({ + type: "response.failed", + sequence_number: 1, + response: { + id: "resp_1", + object: "response", + created_at: 1, + model: "gpt-4o-mini", + status: "failed", + output: [], + error: { code: "server_error", message: "provider exploded" } + } + }) + const client = Layer.succeed( + OpenAiClient.OpenAiClient, + OpenAiClient.OpenAiClient.of({ + client: undefined as any, + createResponse: () => Effect.die("unexpected"), + createResponseStream: () => + Effect.succeed([ + response, + Stream.make(failed) + ]), + createEmbedding: () => Effect.die("unexpected") + }) + ) + + return LanguageModel.streamText({ prompt: "test" }).pipe( + Stream.runCollect, + Effect.tap((parts) => + Effect.sync(() => { + const error = Array.from(parts).find((part) => part.type === "error") + const finish = Array.from(parts).find((part) => part.type === "finish") + assert.isDefined(error) + assert.deepStrictEqual(error.error, { code: "server_error", message: "provider exploded" }) + assert.isDefined(finish) + assert.strictEqual(finish.reason, "error") + }) + ), + Effect.provide(OpenAiLanguageModel.model("gpt-4o-mini")), + Effect.provide(client) + ) + }) }) diff --git a/packages/ai/openai/tsconfig.json b/packages/ai/openai/tsconfig.json index 19a2f5dbc..e2a8ca19a 100644 --- a/packages/ai/openai/tsconfig.json +++ b/packages/ai/openai/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/ai/openai/vitest.config.ts b/packages/ai/openai/vitest.config.ts deleted file mode 100644 index c8a52c182..000000000 --- a/packages/ai/openai/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/packages/ai/openrouter/CHANGELOG.md b/packages/ai/openrouter/CHANGELOG.md index 7c07b2ac7..6c9f47b0a 100644 --- a/packages/ai/openrouter/CHANGELOG.md +++ b/packages/ai/openrouter/CHANGELOG.md @@ -1,5 +1,185 @@ # @effect/ai-openrouter +## 4.0.0-rc.112 + +### Patch Changes + +- [#7437](https://github.com/Effect-TS/effect/pull/7437) [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb) Thanks @wmaurer! - Add an optional `description` to `AiError.AuthenticationError`, rendered after the kind-based suggestion, and pass the provider's own error text through it on HTTP 401 and 403, so authentication failures report what actually went wrong instead of only a category. +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7128](https://github.com/Effect-TS/effect/pull/7128) [`69756a2`](https://github.com/Effect-TS/effect/commit/69756a2290c11bb144b31240de29f39b8cb42c05) Thanks @fubhy! - Preserve start and end offsets for streamed OpenRouter citations. + +- [#7133](https://github.com/Effect-TS/effect/pull/7133) [`d6a4a9c`](https://github.com/Effect-TS/effect/commit/d6a4a9cef496c38663d3977a6a5796a33a9b19b7) Thanks @fubhy! - Fix the casing of OpenRouter reasoning-end metadata. + +- [#7132](https://github.com/Effect-TS/effect/pull/7132) [`3f01731`](https://github.com/Effect-TS/effect/commit/3f01731a09ebf5c53e9bede851ae8f8f28f1a5e8) Thanks @fubhy! - Emit incremental tool parameter fragments from OpenRouter streaming responses. +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- [#6572](https://github.com/Effect-TS/effect/pull/6572) [`c0f9fc9`](https://github.com/Effect-TS/effect/commit/c0f9fc9fda8af8ac138b23293876e78bd052ab00) Thanks @tim-smart! - Regenerate the `Generated` module against OpenRouter's current published specification. This preserves nullable + generation statistics and streamed usage cost metadata while incorporating the broader upstream schema changes. + + Notable generated schema renames include `ChatGenerationParams` to `ChatRequest`, `ChatGenerationTokenUsage` to + `ChatUsage`, `AssistantMessage` to `ChatAssistantMessage`, `ChatStreamingResponseChunk` to `ChatStreamingResponse`, + and `ChatMessageContentItemCacheControl` to `ChatContentCacheControl`. Handwritten public aliases such as + `ChatStreamingResponseChunkData`, `ReasoningDetails`, and `FileAnnotation` retain their existing names. + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- [#6518](https://github.com/Effect-TS/effect/pull/6518) [`017f384`](https://github.com/Effect-TS/effect/commit/017f384b1e40eeac220cff6781feb02e89fda349) Thanks @leodenham! - Fix dynamic tools defined with a raw JSON schema sending empty parameter schema to OpenRouter + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/ai/openrouter/README.md b/packages/ai/openrouter/README.md new file mode 100644 index 000000000..cc120b6a4 --- /dev/null +++ b/packages/ai/openrouter/README.md @@ -0,0 +1,14 @@ +# @effect/ai-openrouter + +An [OpenRouter](https://openrouter.ai) provider for the Effect AI modules. Includes a typed OpenRouter API client and language model layers. + +## Installation + +```sh +npm install effect@rc @effect/ai-openrouter@rc +``` + +## Documentation + +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/ai-openrouter) diff --git a/packages/ai/openrouter/codegen.yml b/packages/ai/openrouter/codegen.yml index b556693a3..7fea7aae9 100644 --- a/packages/ai/openrouter/codegen.yml +++ b/packages/ai/openrouter/codegen.yml @@ -10,26 +10,25 @@ excludeAnnotations: - default disableAdditionalProperties: true patches: - # Replace OpenResponsesStreamEvent with a flat oneOf of $refs. - # The original uses allOf wrappers that inline OpenResponsesNonStreamingResponse - # into each variant, producing lines over 1M chars that crash dprint. - - '[{"op":"replace","path":"/components/schemas/OpenResponsesStreamEvent","value":{"oneOf":[{"$ref":"#/components/schemas/OpenResponsesCreatedEvent"},{"$ref":"#/components/schemas/OpenResponsesInProgressEvent"},{"$ref":"#/components/schemas/OpenResponsesCompletedEvent"},{"$ref":"#/components/schemas/OpenResponsesIncompleteEvent"},{"$ref":"#/components/schemas/OpenResponsesFailedEvent"},{"$ref":"#/components/schemas/OpenResponsesErrorEvent"},{"$ref":"#/components/schemas/OpenResponsesOutputItemAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesOutputItemDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesContentPartAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesContentPartDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesTextDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesTextDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesRefusalDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesRefusalDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesOutputTextAnnotationAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesFunctionCallArgumentsDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesFunctionCallArgumentsDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryPartAddedEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryPartDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryTextDeltaEvent"},{"$ref":"#/components/schemas/OpenResponsesReasoningSummaryTextDoneEvent"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallInProgress"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallGenerating"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallPartialImage"},{"$ref":"#/components/schemas/OpenResponsesImageGenCallCompleted"}]}}]' - # Fix AssistantMessage images to include type:"image_url" discriminator and nullable - - '[{"op":"replace","path":"/components/schemas/AssistantMessage/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' - # Add images to ChatStreamingMessageChunk (streaming delta) - - '[{"op":"add","path":"/components/schemas/ChatStreamingMessageChunk/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' - # Add annotations to AssistantMessage (non-streaming) - - '[{"op":"add","path":"/components/schemas/AssistantMessage/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' - # Add annotations to ChatStreamingMessageChunk (streaming delta) - - '[{"op":"add","path":"/components/schemas/ChatStreamingMessageChunk/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' + # Flatten Responses streaming event allOf wrappers to keep generated schemas referential + - '[{"op":"replace","path":"/components/schemas/OpenResponsesCreatedEvent","value":{"type":"object","description":"Event emitted when a response is created","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.created"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/OpenResponsesInProgressEvent","value":{"type":"object","description":"Event emitted when a response is in progress","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.in_progress"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseCompleted","value":{"type":"object","description":"Event emitted when a response has completed successfully","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.completed"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseIncomplete","value":{"type":"object","description":"Event emitted when a response is incomplete","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.incomplete"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseFailed","value":{"type":"object","description":"Event emitted when a response has failed","properties":{"response":{"$ref":"#/components/schemas/OpenResponsesResult"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.failed"]}},"required":["type","response","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseOutputItemAdded","value":{"type":"object","description":"Event emitted when a new output item is added to the response","properties":{"item":{"$ref":"#/components/schemas/OutputItems"},"output_index":{"type":"integer"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.output_item.added"]}},"required":["type","output_index","item","sequence_number"]}},{"op":"replace","path":"/components/schemas/StreamEventsResponseOutputItemDone","value":{"type":"object","description":"Event emitted when an output item is complete","properties":{"item":{"$ref":"#/components/schemas/OutputItems"},"output_index":{"type":"integer"},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.output_item.done"]}},"required":["type","output_index","item","sequence_number"]}},{"op":"replace","path":"/components/schemas/ContentPartAddedEvent","value":{"type":"object","description":"Event emitted when a new content part is added to an output item","properties":{"content_index":{"type":"integer"},"item_id":{"type":"string"},"output_index":{"type":"integer"},"part":{"anyOf":[{"$ref":"#/components/schemas/ResponseOutputText"},{"$ref":"#/components/schemas/ReasoningTextContent"},{"$ref":"#/components/schemas/OpenAIResponsesRefusalContent"}]},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.content_part.added"]}},"required":["type","output_index","item_id","content_index","part","sequence_number"]}},{"op":"replace","path":"/components/schemas/ContentPartDoneEvent","value":{"type":"object","description":"Event emitted when a content part is complete","properties":{"content_index":{"type":"integer"},"item_id":{"type":"string"},"output_index":{"type":"integer"},"part":{"anyOf":[{"$ref":"#/components/schemas/ResponseOutputText"},{"$ref":"#/components/schemas/ReasoningTextContent"},{"$ref":"#/components/schemas/OpenAIResponsesRefusalContent"}]},"sequence_number":{"type":"integer"},"type":{"type":"string","enum":["response.content_part.done"]}},"required":["type","output_index","item_id","content_index","part","sequence_number"]}}]' + # Fix ChatAssistantMessage images to include type:"image_url" discriminator and nullable + - '[{"op":"replace","path":"/components/schemas/ChatAssistantMessage/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' + # Add images to ChatStreamDelta (streaming delta) + - '[{"op":"add","path":"/components/schemas/ChatStreamDelta/properties/images","value":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}},"required":["type","image_url"]}},{"type":"null"}]}}]' + # Add annotations to ChatAssistantMessage (non-streaming) + - '[{"op":"add","path":"/components/schemas/ChatAssistantMessage/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' + # Add annotations to ChatStreamDelta (streaming delta) + - '[{"op":"add","path":"/components/schemas/ChatStreamDelta/properties/annotations","value":{"anyOf":[{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"url_citation"},"url_citation":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":"string"},"start_index":{"type":"number"},"end_index":{"type":"number"},"content":{"type":"string"}},"required":["url"]}},"required":["type","url_citation"]},{"type":"object","properties":{"type":{"type":"string","const":"file_annotation"},"file_annotation":{"type":"object","properties":{"file_id":{"type":"string"},"quote":{"type":"string"}},"required":["file_id"]}},"required":["type","file_annotation"]},{"type":"object","properties":{"type":{"type":"string","const":"file"},"file":{"type":"object","properties":{"hash":{"type":"string"},"name":{"type":"string"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}},"required":["type"]}}},"required":["hash","name"]}},"required":["type","file"]}]}},{"type":"null"}]}}]' # Make tool call delta fields nullable (models like kimi-k2.5, minimax-m2.5 send null) - - '[{"op":"replace","path":"/components/schemas/ChatStreamingMessageToolCall/properties/id","value":{"anyOf":[{"type":"string"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamingMessageToolCall/properties/type","value":{"anyOf":[{"type":"string","const":"function"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamingMessageToolCall/properties/function/properties/name","value":{"anyOf":[{"type":"string"},{"type":"null"}]}}]' + - '[{"op":"replace","path":"/components/schemas/ChatStreamToolCall/properties/id","value":{"anyOf":[{"type":"string"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamToolCall/properties/type","value":{"anyOf":[{"type":"string","const":"function"},{"type":"null"}]}},{"op":"replace","path":"/components/schemas/ChatStreamToolCall/properties/function/properties/name","value":{"anyOf":[{"type":"string"},{"type":"null"}]}}]' # Make finish_reason optional (only present on final streaming chunk) - - '[{"op":"remove","path":"/components/schemas/ChatStreamingChoice/required/1"}]' + - '[{"op":"remove","path":"/components/schemas/ChatStreamChoice/required/1"}]' replacements: - # Schema.Unknown doesn't work with Schema.toCodecJson (used by HttpClientResponse.schemaBodyJson) - # Replace with Schema.Json which properly handles arbitrary JSON values - - from: "Schema.Record(Schema.String, Schema.Unknown)" - to: "Schema.Record(Schema.String, Schema.Json)" - - from: "{ readonly [x: string]: unknown }" - to: "{ readonly [x: string]: Schema.Json }" + # Help TypeScript serialize oversized generated schema values + - from: "export const StreamEvents = Schema.Union([" + to: "export const StreamEvents: Schema.Schema = Schema.Union([" + - from: "export const ResponsesStreamingResponse = Schema.Struct({ \"data\": StreamEvents })" + to: "export const ResponsesStreamingResponse: Schema.Schema = Schema.Struct({ \"data\": StreamEvents })" + - from: "export const CreateResponses200Sse = ResponsesStreamingResponse" + to: "export const CreateResponses200Sse: Schema.Schema = ResponsesStreamingResponse" diff --git a/packages/ai/openrouter/docgen.json b/packages/ai/openrouter/docgen.json deleted file mode 100644 index 00f806cd6..000000000 --- a/packages/ai/openrouter/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/ai/openrouter/src/", - "exclude": ["src/Generated.ts", "src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/ai/openrouter/package.json b/packages/ai/openrouter/package.json index ee8d89e45..deda898f1 100644 --- a/packages/ai/openrouter/package.json +++ b/packages/ai/openrouter/package.json @@ -1,17 +1,17 @@ { "name": "@effect/ai-openrouter", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "An OpenRouter provider integration for Effect AI SDK", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/ai/openrouter" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,17 +50,15 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^" diff --git a/packages/ai/openrouter/src/Generated.ts b/packages/ai/openrouter/src/Generated.ts index c015b4429..4c3859a37 100644 --- a/packages/ai/openrouter/src/Generated.ts +++ b/packages/ai/openrouter/src/Generated.ts @@ -1,5 +1,5 @@ /** - * @since 4.0.0 + * @since 1.0.0 */ import * as Data from "effect/Data" @@ -13,2382 +13,3371 @@ import * as HttpClientError from "effect/unstable/http/HttpClientError" import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" // non-recursive definitions -export type OpenAIResponsesResponseStatus = - | "completed" - | "incomplete" - | "in_progress" - | "failed" - | "cancelled" - | "queued" -export const OpenAIResponsesResponseStatus = Schema.Literals([ - "completed", - "incomplete", - "in_progress", - "failed", - "cancelled", - "queued" -]) -export type FileCitation = { - readonly "type": "file_citation" +export type AABenchmarkEntry = { + readonly "agentic_index": number | null + readonly "coding_index": number | null + readonly "intelligence_index": number | null +} +export const AABenchmarkEntry = Schema.Struct({ + "agentic_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Artificial Analysis Agentic Index score", "format": "double" }), + "coding_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Artificial Analysis Coding Index score", "format": "double" }), + "intelligence_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Artificial Analysis Intelligence Index score", "format": "double" }) +}).annotate({ "description": "Artificial Analysis benchmark index scores.", "identifier": "AABenchmarkEntry" }) +export type ActivityItem = { + readonly "byok_usage_inference": number + readonly "completion_tokens": number + readonly "date": string + readonly "endpoint_id": string + readonly "model": string + readonly "model_permaslug": string + readonly "prompt_tokens": number + readonly "provider_name": string + readonly "reasoning_tokens": number + readonly "requests": number + readonly "usage": number +} +export const ActivityItem = Schema.Struct({ + "byok_usage_inference": Schema.Number.annotate({ + "description": "BYOK inference cost in USD (external credits spent)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "completion_tokens": Schema.Number.annotate({ "description": "Total completion tokens generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "date": Schema.String.annotate({ "description": "Date of the activity (YYYY-MM-DD format)" }), + "endpoint_id": Schema.String.annotate({ "description": "Unique identifier for the endpoint" }), + "model": Schema.String.annotate({ "description": "Model slug (e.g., \"openai/gpt-4.1\")" }), + "model_permaslug": Schema.String.annotate({ "description": "Model permaslug (e.g., \"openai/gpt-4.1-2025-04-14\")" }), + "prompt_tokens": Schema.Number.annotate({ "description": "Total prompt tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "provider_name": Schema.String.annotate({ "description": "Name of the provider serving this endpoint" }), + "reasoning_tokens": Schema.Number.annotate({ "description": "Total reasoning tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "requests": Schema.Number.annotate({ "description": "Number of requests made" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "usage": Schema.Number.annotate({ "description": "Total cost in USD (OpenRouter credits spent)", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "ActivityItem" }) +export type AdvisorNestedTool = { readonly "parameters"?: {}; readonly "type": string } +export const AdvisorNestedTool = Schema.Struct({ + "parameters": Schema.optionalKey(Schema.Struct({})), + "type": Schema.String +}).annotate({ + "description": + "A tool made available to the advisor sub-agent. Only OpenRouter server tools (e.g. openrouter:web_search) are supported; function tools are rejected because the advisor has no way to execute them. The advisor tool may not list itself.", + "identifier": "AdvisorNestedTool" +}) +export type AdvisorReasoning = { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" + readonly "max_tokens"?: number +} +export const AdvisorReasoning = Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Literals(["max", "xhigh", "high", "medium", "low", "minimal", "none"]).annotate({ + "description": "Reasoning effort level for the advisor call." + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of reasoning tokens the advisor may use." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) +}).annotate({ + "description": + "Reasoning configuration forwarded to the advisor call. Use this to control reasoning effort and token budget for models that support extended thinking.", + "identifier": "AdvisorReasoning" +}) +export type AnthropicAdvisorToolResult = { + readonly "content": {} + readonly "tool_use_id": string + readonly "type": "advisor_tool_result" +} +export const AnthropicAdvisorToolResult = Schema.Struct({ + "content": Schema.Struct({}), + "tool_use_id": Schema.String, + "type": Schema.Literal("advisor_tool_result") +}).annotate({ "identifier": "AnthropicAdvisorToolResult" }) +export type AnthropicAllowedCallers = ReadonlyArray<"direct" | "code_execution_20250825" | "code_execution_20260120"> +export const AnthropicAllowedCallers = Schema.Array( + Schema.Literals(["direct", "code_execution_20250825", "code_execution_20260120"]) +).annotate({ "identifier": "AnthropicAllowedCallers" }) +export type AnthropicBase64PdfSource = { + readonly "data": string + readonly "media_type": "application/pdf" + readonly "type": "base64" +} +export const AnthropicBase64PdfSource = Schema.Struct({ + "data": Schema.String, + "media_type": Schema.Literal("application/pdf"), + "type": Schema.Literal("base64") +}).annotate({ "identifier": "AnthropicBase64PdfSource" }) +export type AnthropicBashCodeExecutionOutput = { readonly "file_id": string - readonly "filename": string - readonly "index": number + readonly "type": "bash_code_execution_output" } -export const FileCitation = Schema.Struct({ - "type": Schema.Literal("file_citation"), +export const AnthropicBashCodeExecutionOutput = Schema.Struct({ "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) + "type": Schema.Literal("bash_code_execution_output") +}).annotate({ "identifier": "AnthropicBashCodeExecutionOutput" }) +export type AnthropicBashCodeExecutionToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "output_file_too_large" + readonly "type": "bash_code_execution_tool_result_error" +} +export const AnthropicBashCodeExecutionToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "output_file_too_large" + ]), + "type": Schema.Literal("bash_code_execution_tool_result_error") +}).annotate({ "identifier": "AnthropicBashCodeExecutionToolResultError" }) +export type AnthropicCacheControlTtl = "5m" | "1h" +export const AnthropicCacheControlTtl = Schema.Literals(["5m", "1h"]).annotate({ + "identifier": "AnthropicCacheControlTtl" }) -export type URLCitation = { - readonly "type": "url_citation" +export type Objects_ = { + readonly "ephemeral_1h_input_tokens": number + readonly "ephemeral_5m_input_tokens": number + readonly [x: string]: Schema.Json +} +export const Objects_ = Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type AnthropicCitationCharLocation = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_char_index": number + readonly "file_id": string | null + readonly "start_char_index": number + readonly "type": "char_location" +} +export const AnthropicCitationCharLocation = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "file_id": Schema.Union([Schema.String, Schema.Null]), + "start_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("char_location") +}).annotate({ "identifier": "AnthropicCitationCharLocation" }) +export type AnthropicCitationCharLocationParam = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_char_index": number + readonly "start_char_index": number + readonly "type": "char_location" +} +export const AnthropicCitationCharLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_char_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("char_location") +}).annotate({ "identifier": "AnthropicCitationCharLocationParam" }) +export type AnthropicCitationContentBlockLocation = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_block_index": number + readonly "file_id": string | null + readonly "start_block_index": number + readonly "type": "content_block_location" +} +export const AnthropicCitationContentBlockLocation = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "file_id": Schema.Union([Schema.String, Schema.Null]), + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_location") +}).annotate({ "identifier": "AnthropicCitationContentBlockLocation" }) +export type AnthropicCitationContentBlockLocationParam = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_block_index": number + readonly "start_block_index": number + readonly "type": "content_block_location" +} +export const AnthropicCitationContentBlockLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_location") +}).annotate({ "identifier": "AnthropicCitationContentBlockLocationParam" }) +export type AnthropicCitationPageLocation = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_page_number": number + readonly "file_id": string | null + readonly "start_page_number": number + readonly "type": "page_location" +} +export const AnthropicCitationPageLocation = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "file_id": Schema.Union([Schema.String, Schema.Null]), + "start_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("page_location") +}).annotate({ "identifier": "AnthropicCitationPageLocation" }) +export type AnthropicCitationPageLocationParam = { + readonly "cited_text": string + readonly "document_index": number + readonly "document_title": string | null + readonly "end_page_number": number + readonly "start_page_number": number + readonly "type": "page_location" +} +export const AnthropicCitationPageLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "document_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "document_title": Schema.Union([Schema.String, Schema.Null]), + "end_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_page_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("page_location") +}).annotate({ "identifier": "AnthropicCitationPageLocationParam" }) +export type Objects_1 = { readonly "enabled": boolean; readonly [x: string]: Schema.Json } +export const Objects_1 = Schema.StructWithRest(Schema.Struct({ "enabled": Schema.Boolean }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) +]) +export type AnthropicCitationSearchResultLocation = { + readonly "cited_text": string + readonly "end_block_index": number + readonly "search_result_index": number + readonly "source": string + readonly "start_block_index": number + readonly "title": string | null + readonly "type": "search_result_location" +} +export const AnthropicCitationSearchResultLocation = Schema.Struct({ + "cited_text": Schema.String, + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "search_result_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "source": Schema.String, + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("search_result_location") +}).annotate({ "identifier": "AnthropicCitationSearchResultLocation" }) +export type AnthropicCitationSearchResultLocationParam = { + readonly "cited_text": string + readonly "end_block_index": number + readonly "search_result_index": number + readonly "source": string + readonly "start_block_index": number + readonly "title": string | null + readonly "type": "search_result_location" +} +export const AnthropicCitationSearchResultLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "end_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "search_result_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "source": Schema.String, + "start_block_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("search_result_location") +}).annotate({ "identifier": "AnthropicCitationSearchResultLocationParam" }) +export type AnthropicCitationWebSearchResultLocation = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number } -export const URLCitation = Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) -}) -export type FilePath = { readonly "type": "file_path"; readonly "file_id": string; readonly "index": number } -export const FilePath = Schema.Struct({ - "type": Schema.Literal("file_path"), +export const AnthropicCitationWebSearchResultLocation = Schema.Struct({ + "cited_text": Schema.String, + "encrypted_index": Schema.String, + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("web_search_result_location"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicCitationWebSearchResultLocation" }) +export type AnthropicCitationWebSearchResultLocationParam = { + readonly "cited_text": string + readonly "encrypted_index": string + readonly "title": string | null + readonly "type": "web_search_result_location" + readonly "url": string +} +export const AnthropicCitationWebSearchResultLocationParam = Schema.Struct({ + "cited_text": Schema.String, + "encrypted_index": Schema.String, + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("web_search_result_location"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicCitationWebSearchResultLocationParam" }) +export type AnthropicCodeExecution20250825Caller = { + readonly "tool_id": string + readonly "type": "code_execution_20250825" +} +export const AnthropicCodeExecution20250825Caller = Schema.Struct({ + "tool_id": Schema.String, + "type": Schema.Literal("code_execution_20250825") +}).annotate({ "identifier": "AnthropicCodeExecution20250825Caller" }) +export type AnthropicCodeExecution20260120Caller = { + readonly "tool_id": string + readonly "type": "code_execution_20260120" +} +export const AnthropicCodeExecution20260120Caller = Schema.Struct({ + "tool_id": Schema.String, + "type": Schema.Literal("code_execution_20260120") +}).annotate({ "identifier": "AnthropicCodeExecution20260120Caller" }) +export type AnthropicCodeExecutionOutput = { readonly "file_id": string; readonly "type": "code_execution_output" } +export const AnthropicCodeExecutionOutput = Schema.Struct({ "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) + "type": Schema.Literal("code_execution_output") +}).annotate({ "identifier": "AnthropicCodeExecutionOutput" }) +export type AnthropicCompactionBlock = { readonly "content": string | null; readonly "type": "compaction" } +export const AnthropicCompactionBlock = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("compaction") +}).annotate({ "identifier": "AnthropicCompactionBlock" }) +export type AnthropicContainer = { + readonly "expires_at": string + readonly "id": string + readonly [x: string]: Schema.Json +} | null +export const AnthropicContainer = Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "expires_at": Schema.String, "id": Schema.String }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null +]).annotate({ "identifier": "AnthropicContainer" }) +export type AnthropicContainerUpload = { readonly "file_id": string; readonly "type": "container_upload" } +export const AnthropicContainerUpload = Schema.Struct({ + "file_id": Schema.String, + "type": Schema.Literal("container_upload") +}).annotate({ "identifier": "AnthropicContainerUpload" }) +export type AnthropicDirectCaller = { readonly "type": "direct" } +export const AnthropicDirectCaller = Schema.Struct({ "type": Schema.Literal("direct") }).annotate({ + "identifier": "AnthropicDirectCaller" }) -export type OpenAIResponsesRefusalContent = { readonly "type": "refusal"; readonly "refusal": string } -export const OpenAIResponsesRefusalContent = Schema.Struct({ - "type": Schema.Literal("refusal"), - "refusal": Schema.String +export type AnthropicFileDocumentSource = { readonly "file_id": string; readonly "type": "file" } +export const AnthropicFileDocumentSource = Schema.Struct({ "file_id": Schema.String, "type": Schema.Literal("file") }) + .annotate({ "identifier": "AnthropicFileDocumentSource" }) +export type AnthropicImageMimeType = "image/jpeg" | "image/png" | "image/gif" | "image/webp" +export const AnthropicImageMimeType = Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]).annotate({ + "identifier": "AnthropicImageMimeType" }) -export type ReasoningTextContent = { readonly "type": "reasoning_text"; readonly "text": string } -export const ReasoningTextContent = Schema.Struct({ "type": Schema.Literal("reasoning_text"), "text": Schema.String }) -export type ReasoningSummaryText = { readonly "type": "summary_text"; readonly "text": string } -export const ReasoningSummaryText = Schema.Struct({ "type": Schema.Literal("summary_text"), "text": Schema.String }) -export type OutputItemFunctionCall = { - readonly "type": "function_call" - readonly "id"?: string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" +export type Objects_2 = { readonly "type": "input_tokens"; readonly "value": number; readonly [x: string]: Schema.Json } +export const Objects_2 = Schema.StructWithRest( + Schema.Struct({ + "type": Schema.Literal("input_tokens"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type AnthropicInputTokensTrigger = { readonly "type": "input_tokens"; readonly "value": number } +export const AnthropicInputTokensTrigger = Schema.Struct({ + "type": Schema.Literal("input_tokens"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicInputTokensTrigger" }) +export type Objects_3 = { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json } -export const OutputItemFunctionCall = Schema.Struct({ - "type": Schema.Literal("function_call"), - "id": Schema.optionalKey(Schema.String), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) -}) -export type ResponsesOutputItemFunctionCall = { - readonly "type": "function_call" - readonly "id"?: string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" +export const Objects_3 = Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type AnthropicOutputTokensDetails = + | { readonly "thinking_tokens": number; readonly [x: string]: Schema.Json } + | null +export const AnthropicOutputTokensDetails = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ "thinking_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "identifier": "AnthropicOutputTokensDetails" }) +export type AnthropicPlainTextSource = { + readonly "data": string + readonly "media_type": "text/plain" + readonly "type": "text" } -export const ResponsesOutputItemFunctionCall = Schema.Struct({ - "type": Schema.Literal("function_call"), - "id": Schema.optionalKey(Schema.String), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) +export const AnthropicPlainTextSource = Schema.Struct({ + "data": Schema.String, + "media_type": Schema.Literal("text/plain"), + "type": Schema.Literal("text") +}).annotate({ "identifier": "AnthropicPlainTextSource" }) +export type AnthropicRedactedThinkingBlock = { readonly "data": string; readonly "type": "redacted_thinking" } +export const AnthropicRedactedThinkingBlock = Schema.Struct({ + "data": Schema.String, + "type": Schema.Literal("redacted_thinking") +}).annotate({ "identifier": "AnthropicRedactedThinkingBlock" }) +export type AnthropicRefusalStopDetails = { + readonly "category": "cyber" | "bio" | null + readonly "explanation": string | null + readonly "type": "refusal" + readonly [x: string]: Schema.Json +} | null +export const AnthropicRefusalStopDetails = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "category": Schema.Union([Schema.Literal("cyber"), Schema.Literal("bio"), Schema.Null]), + "explanation": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("refusal") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Structured information about a refusal", "identifier": "AnthropicRefusalStopDetails" }) +export type AnthropicServerToolErrorCode = + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" +export const AnthropicServerToolErrorCode = Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded" +]).annotate({ "identifier": "AnthropicServerToolErrorCode" }) +export type AnthropicServerToolUsage = { + readonly "web_fetch_requests": number + readonly "web_search_requests": number + readonly [x: string]: Schema.Json +} | null +export const AnthropicServerToolUsage = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "web_fetch_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "web_search_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "identifier": "AnthropicServerToolUsage" }) +export type AnthropicServiceTier = "standard" | "priority" | "batch" | null +export const AnthropicServiceTier = Schema.Union([ + Schema.Literal("standard"), + Schema.Literal("priority"), + Schema.Literal("batch"), + Schema.Null +]).annotate({ "identifier": "AnthropicServiceTier" }) +export type AnthropicSpeed = "fast" | "standard" | null +export const AnthropicSpeed = Schema.Union([Schema.Literal("fast"), Schema.Literal("standard"), Schema.Null]).annotate({ + "identifier": "AnthropicSpeed" }) -export type WebSearchStatus = "completed" | "searching" | "in_progress" | "failed" -export const WebSearchStatus = Schema.Literals(["completed", "searching", "in_progress", "failed"]) -export type ImageGenerationStatus = "in_progress" | "completed" | "generating" | "failed" -export const ImageGenerationStatus = Schema.Literals(["in_progress", "completed", "generating", "failed"]) -export type ResponsesErrorField = { - readonly "code": - | "server_error" - | "rate_limit_exceeded" - | "invalid_prompt" - | "vector_store_timeout" - | "invalid_image" - | "invalid_image_format" - | "invalid_base64_image" - | "invalid_image_url" - | "image_too_large" - | "image_too_small" - | "image_parse_error" - | "image_content_policy_violation" - | "invalid_image_mode" - | "image_file_too_large" - | "unsupported_image_media_type" - | "empty_image_file" - | "failed_to_download_image" - | "image_file_not_found" - readonly "message": string +export type AnthropicTextEditorCodeExecutionCreateResult = { + readonly "is_file_update": boolean + readonly "type": "text_editor_code_execution_create_result" } -export const ResponsesErrorField = Schema.Struct({ - "code": Schema.Literals([ - "server_error", - "rate_limit_exceeded", - "invalid_prompt", - "vector_store_timeout", - "invalid_image", - "invalid_image_format", - "invalid_base64_image", - "invalid_image_url", - "image_too_large", - "image_too_small", - "image_parse_error", - "image_content_policy_violation", - "invalid_image_mode", - "image_file_too_large", - "unsupported_image_media_type", - "empty_image_file", - "failed_to_download_image", - "image_file_not_found" - ]), - "message": Schema.String -}).annotate({ "description": "Error information returned from the API" }) -export type OpenAIResponsesIncompleteDetails = { readonly "reason"?: "max_output_tokens" | "content_filter" } -export const OpenAIResponsesIncompleteDetails = Schema.Struct({ - "reason": Schema.optionalKey(Schema.Literals(["max_output_tokens", "content_filter"])) -}) -export type OpenAIResponsesUsage = { - readonly "input_tokens": number - readonly "input_tokens_details": { readonly "cached_tokens": number } - readonly "output_tokens": number - readonly "output_tokens_details": { readonly "reasoning_tokens": number } - readonly "total_tokens": number +export const AnthropicTextEditorCodeExecutionCreateResult = Schema.Struct({ + "is_file_update": Schema.Boolean, + "type": Schema.Literal("text_editor_code_execution_create_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionCreateResult" }) +export type AnthropicTextEditorCodeExecutionStrReplaceResult = { + readonly "lines": ReadonlyArray | null + readonly "new_lines": number | null + readonly "new_start": number | null + readonly "old_lines": number | null + readonly "old_start": number | null + readonly "type": "text_editor_code_execution_str_replace_result" } -export const OpenAIResponsesUsage = Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "input_tokens_details": Schema.Struct({ "cached_tokens": Schema.Number.check(Schema.isFinite()) }), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens_details": Schema.Struct({ "reasoning_tokens": Schema.Number.check(Schema.isFinite()) }), - "total_tokens": Schema.Number.check(Schema.isFinite()) -}) -export type ResponseInputText = { readonly "type": "input_text"; readonly "text": string } -export const ResponseInputText = Schema.Struct({ "type": Schema.Literal("input_text"), "text": Schema.String }) - .annotate({ "description": "Text input content item" }) -export type ResponseInputImage = { - readonly "type": "input_image" - readonly "detail": "auto" | "high" | "low" - readonly "image_url"?: string -} -export const ResponseInputImage = Schema.Struct({ - "type": Schema.Literal("input_image"), - "detail": Schema.Literals(["auto", "high", "low"]), - "image_url": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Image input content item" }) -export type ResponseInputFile = { - readonly "type": "input_file" - readonly "file_id"?: string - readonly "file_data"?: string - readonly "filename"?: string - readonly "file_url"?: string +export const AnthropicTextEditorCodeExecutionStrReplaceResult = Schema.Struct({ + "lines": Schema.Union([Schema.Array(Schema.String), Schema.Null]), + "new_lines": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "new_start": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "old_lines": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "old_start": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "type": Schema.Literal("text_editor_code_execution_str_replace_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionStrReplaceResult" }) +export type AnthropicTextEditorCodeExecutionToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "too_many_requests" + | "execution_time_exceeded" + | "file_not_found" + readonly "error_message": string | null + readonly "type": "text_editor_code_execution_tool_result_error" } -export const ResponseInputFile = Schema.Struct({ - "type": Schema.Literal("input_file"), - "file_id": Schema.optionalKey(Schema.String), - "file_data": Schema.optionalKey(Schema.String), - "filename": Schema.optionalKey(Schema.String), - "file_url": Schema.optionalKey(Schema.String) -}).annotate({ "description": "File input content item" }) -export type ResponseInputAudio = { - readonly "type": "input_audio" - readonly "input_audio": { readonly "data": string; readonly "format": "mp3" | "wav" } +export const AnthropicTextEditorCodeExecutionToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "too_many_requests", + "execution_time_exceeded", + "file_not_found" + ]), + "error_message": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("text_editor_code_execution_tool_result_error") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionToolResultError" }) +export type AnthropicTextEditorCodeExecutionViewResult = { + readonly "content": string + readonly "file_type": "text" | "image" | "pdf" + readonly "num_lines": number | null + readonly "start_line": number | null + readonly "total_lines": number | null + readonly "type": "text_editor_code_execution_view_result" } -export const ResponseInputAudio = Schema.Struct({ - "type": Schema.Literal("input_audio"), - "input_audio": Schema.Struct({ "data": Schema.String, "format": Schema.Literals(["mp3", "wav"]) }) -}).annotate({ "description": "Audio input content item" }) -export type ToolCallStatus = "in_progress" | "completed" | "incomplete" -export const ToolCallStatus = Schema.Literals(["in_progress", "completed", "incomplete"]) -export type OpenResponsesRequestMetadata = {} -export const OpenResponsesRequestMetadata = Schema.Struct({}).annotate({ - "description": - "Metadata key-value pairs for the request. Keys must be ≤64 characters and cannot contain brackets. Values must be ≤512 characters. Maximum 16 pairs allowed." +export const AnthropicTextEditorCodeExecutionViewResult = Schema.Struct({ + "content": Schema.String, + "file_type": Schema.Literals(["text", "image", "pdf"]), + "num_lines": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "start_line": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]), + "total_lines": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "type": Schema.Literal("text_editor_code_execution_view_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionViewResult" }) +export type AnthropicThinkingBlock = { + readonly "signature": string + readonly "thinking": string + readonly "type": "thinking" +} +export const AnthropicThinkingBlock = Schema.Struct({ + "signature": Schema.String, + "thinking": Schema.String, + "type": Schema.Literal("thinking") +}).annotate({ "identifier": "AnthropicThinkingBlock" }) +export type AnthropicThinkingDisplay = "summarized" | "omitted" | null +export const AnthropicThinkingDisplay = Schema.Union([ + Schema.Literal("summarized"), + Schema.Literal("omitted"), + Schema.Null +]).annotate({ "identifier": "AnthropicThinkingDisplay" }) +export type AnthropicThinkingTurns = { readonly "type": "thinking_turns"; readonly "value": number } +export const AnthropicThinkingTurns = Schema.Struct({ + "type": Schema.Literal("thinking_turns"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicThinkingTurns" }) +export type AnthropicToolReference = { readonly "tool_name": string; readonly "type": "tool_reference" } +export const AnthropicToolReference = Schema.Struct({ + "tool_name": Schema.String, + "type": Schema.Literal("tool_reference") +}).annotate({ "identifier": "AnthropicToolReference" }) +export type AnthropicToolUsesKeep = { readonly "type": "tool_uses"; readonly "value": number } +export const AnthropicToolUsesKeep = Schema.Struct({ + "type": Schema.Literal("tool_uses"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicToolUsesKeep" }) +export type AnthropicToolUsesTrigger = { readonly "type": "tool_uses"; readonly "value": number } +export const AnthropicToolUsesTrigger = Schema.Struct({ + "type": Schema.Literal("tool_uses"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "AnthropicToolUsesTrigger" }) +export type AnthropicUrlImageSource = { readonly "type": "url"; readonly "url": string } +export const AnthropicUrlImageSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "AnthropicUrlImageSource" }) -export type ResponsesSearchContextSize = "low" | "medium" | "high" -export const ResponsesSearchContextSize = Schema.Literals(["low", "medium", "high"]).annotate({ - "description": "Size of the search context for web search tools" +export type AnthropicUrlPdfSource = { readonly "type": "url"; readonly "url": string } +export const AnthropicUrlPdfSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "AnthropicUrlPdfSource" }) -export type WebSearchPreviewToolUserLocation = { - readonly "type": "approximate" - readonly "city"?: string - readonly "country"?: string - readonly "region"?: string - readonly "timezone"?: string -} -export const WebSearchPreviewToolUserLocation = Schema.Struct({ - "type": Schema.Literal("approximate"), - "city": Schema.optionalKey(Schema.String), - "country": Schema.optionalKey(Schema.String), - "region": Schema.optionalKey(Schema.String), - "timezone": Schema.optionalKey(Schema.String) -}) -export type ResponsesWebSearchUserLocation = { - readonly "type"?: "approximate" - readonly "city"?: string - readonly "country"?: string - readonly "region"?: string - readonly "timezone"?: string -} -export const ResponsesWebSearchUserLocation = Schema.Struct({ - "type": Schema.optionalKey(Schema.Literal("approximate")), - "city": Schema.optionalKey(Schema.String), - "country": Schema.optionalKey(Schema.String), - "region": Schema.optionalKey(Schema.String), - "timezone": Schema.optionalKey(Schema.String) -}).annotate({ "description": "User location information for web search" }) -export type OpenAIResponsesToolChoice = "auto" | "none" | "required" | { - readonly "type": "function" - readonly "name": string -} | { readonly "type": "web_search_preview_2025_03_11" | "web_search_preview" } -export const OpenAIResponsesToolChoice = Schema.Union([ - Schema.Literal("auto"), - Schema.Literal("none"), - Schema.Literal("required"), - Schema.Struct({ "type": Schema.Literal("function"), "name": Schema.String }), - Schema.Struct({ "type": Schema.Literals(["web_search_preview_2025_03_11", "web_search_preview"]) }) -]) -export type OpenAIResponsesPrompt = { readonly "id": string; readonly "variables"?: {} } -export const OpenAIResponsesPrompt = Schema.Struct({ - "id": Schema.String, - "variables": Schema.optionalKey(Schema.Struct({})) -}) -export type OpenAIResponsesReasoningEffort = "xhigh" | "high" | "medium" | "low" | "minimal" | "none" -export const OpenAIResponsesReasoningEffort = Schema.Literals(["xhigh", "high", "medium", "low", "minimal", "none"]) -export type ReasoningSummaryVerbosity = "auto" | "concise" | "detailed" -export const ReasoningSummaryVerbosity = Schema.Literals(["auto", "concise", "detailed"]) -export type OpenAIResponsesServiceTier = "auto" | "default" | "flex" | "priority" | "scale" -export const OpenAIResponsesServiceTier = Schema.Literals(["auto", "default", "flex", "priority", "scale"]) -export type OpenAIResponsesTruncation = "auto" | "disabled" -export const OpenAIResponsesTruncation = Schema.Literals(["auto", "disabled"]) -export type ResponsesFormatText = { readonly "type": "text" } -export const ResponsesFormatText = Schema.Struct({ "type": Schema.Literal("text") }).annotate({ - "description": "Plain text response format" -}) -export type ResponsesFormatJSONObject = { readonly "type": "json_object" } -export const ResponsesFormatJSONObject = Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ - "description": "JSON object response format" -}) -export type ResponsesFormatTextJSONSchemaConfig = { - readonly "type": "json_schema" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "schema": {} +export type AnthropicWebFetchToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "url_too_long" + | "url_not_allowed" + | "url_not_accessible" + | "unsupported_content_type" + | "too_many_requests" + | "max_uses_exceeded" + | "unavailable" + readonly "type": "web_fetch_tool_result_error" } -export const ResponsesFormatTextJSONSchemaConfig = Schema.Struct({ - "type": Schema.Literal("json_schema"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "schema": Schema.Struct({}) -}).annotate({ "description": "JSON schema constrained response format" }) -export type OpenResponsesErrorEvent = { - readonly "type": "error" - readonly "code": string - readonly "message": string - readonly "param": string - readonly "sequence_number": number +export const AnthropicWebFetchToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "url_too_long", + "url_not_allowed", + "url_not_accessible", + "unsupported_content_type", + "too_many_requests", + "max_uses_exceeded", + "unavailable" + ]), + "type": Schema.Literal("web_fetch_tool_result_error") +}).annotate({ "identifier": "AnthropicWebFetchToolResultError" }) +export type AnthropicWebSearchResult = { + readonly "encrypted_content": string + readonly "page_age": string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string } -export const OpenResponsesErrorEvent = Schema.Struct({ - "type": Schema.Literal("error"), - "code": Schema.String, - "message": Schema.String, - "param": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when an error occurs during streaming" }) -export type OpenResponsesTopLogprobs = { readonly "token"?: string; readonly "logprob"?: number } -export const OpenResponsesTopLogprobs = Schema.Struct({ - "token": Schema.optionalKey(Schema.String), - "logprob": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) -}).annotate({ "description": "Alternative token with its log probability" }) -export type OpenResponsesRefusalDeltaEvent = { - readonly "type": "response.refusal.delta" - readonly "output_index": number - readonly "item_id": string - readonly "content_index": number - readonly "delta": string - readonly "sequence_number": number +export const AnthropicWebSearchResult = Schema.Struct({ + "encrypted_content": Schema.String, + "page_age": Schema.Union([Schema.String, Schema.Null]), + "title": Schema.String, + "type": Schema.Literal("web_search_result"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicWebSearchResult" }) +export type AnthropicWebSearchResultBlockParam = { + readonly "encrypted_content": string + readonly "page_age"?: string | null + readonly "title": string + readonly "type": "web_search_result" + readonly "url": string } -export const OpenResponsesRefusalDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.refusal.delta"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a refusal delta is streamed" }) -export type OpenResponsesRefusalDoneEvent = { - readonly "type": "response.refusal.done" - readonly "output_index": number +export const AnthropicWebSearchResultBlockParam = Schema.Struct({ + "encrypted_content": Schema.String, + "page_age": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "title": Schema.String, + "type": Schema.Literal("web_search_result"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicWebSearchResultBlockParam" }) +export type AnthropicWebSearchToolResultError = { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "max_uses_exceeded" + | "too_many_requests" + | "query_too_long" + | "request_too_large" + readonly "type": "web_search_tool_result_error" +} +export const AnthropicWebSearchToolResultError = Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "max_uses_exceeded", + "too_many_requests", + "query_too_long", + "request_too_large" + ]), + "type": Schema.Literal("web_search_tool_result_error") +}).annotate({ "identifier": "AnthropicWebSearchToolResultError" }) +export type Objects_4 = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" + readonly [x: string]: Schema.Json +} +export const Objects_4 = Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("approximate") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type ApiErrorType = + | "context_length_exceeded" + | "max_tokens_exceeded" + | "token_limit_exceeded" + | "string_too_long" + | "authentication" + | "permission_denied" + | "payment_required" + | "rate_limit_exceeded" + | "provider_overloaded" + | "provider_unavailable" + | "invalid_request" + | "invalid_prompt" + | "not_found" + | "precondition_failed" + | "payload_too_large" + | "unprocessable" + | "content_policy_violation" + | "refusal" + | "invalid_image" + | "image_too_large" + | "image_too_small" + | "unsupported_image_format" + | "image_not_found" + | "image_download_failed" + | "server" + | "timeout" + | "unmapped" +export const ApiErrorType = Schema.Literals([ + "context_length_exceeded", + "max_tokens_exceeded", + "token_limit_exceeded", + "string_too_long", + "authentication", + "permission_denied", + "payment_required", + "rate_limit_exceeded", + "provider_overloaded", + "provider_unavailable", + "invalid_request", + "invalid_prompt", + "not_found", + "precondition_failed", + "payload_too_large", + "unprocessable", + "content_policy_violation", + "refusal", + "invalid_image", + "image_too_large", + "image_too_small", + "unsupported_image_format", + "image_not_found", + "image_download_failed", + "server", + "timeout", + "unmapped" +]).annotate({ + "description": "Canonical OpenRouter error type, stable across all API formats", + "identifier": "ApiErrorType" +}) +export type ApplyPatchCallOperationDiffDeltaEvent = { + readonly "delta": string readonly "item_id": string - readonly "content_index": number - readonly "refusal": string + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.apply_patch_call_operation_diff.delta" } -export const OpenResponsesRefusalDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.refusal.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const ApplyPatchCallOperationDiffDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "refusal": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when refusal streaming is complete" }) -export type OpenResponsesFunctionCallArgumentsDeltaEvent = { - readonly "type": "response.function_call_arguments.delta" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.apply_patch_call_operation_diff.delta") +}).annotate({ + "description": "Incremental chunk of `operation.diff` for an `apply_patch_call`. Matches OpenAI's streaming shape.", + "identifier": "ApplyPatchCallOperationDiffDeltaEvent" +}) +export type ApplyPatchCallOperationDiffDoneEvent = { + readonly "diff": string readonly "item_id": string readonly "output_index": number - readonly "delta": string readonly "sequence_number": number + readonly "type": "response.apply_patch_call_operation_diff.done" } -export const OpenResponsesFunctionCallArgumentsDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.function_call_arguments.delta"), +export const ApplyPatchCallOperationDiffDoneEvent = Schema.Struct({ + "diff": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when function call arguments are being streamed" }) -export type OpenResponsesFunctionCallArgumentsDoneEvent = { - readonly "type": "response.function_call_arguments.done" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.apply_patch_call_operation_diff.done") +}).annotate({ + "description": "Emitted when `operation.diff` streaming completes for an `apply_patch_call`.", + "identifier": "ApplyPatchCallOperationDiffDoneEvent" +}) +export type ApplyPatchCallOutputItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "output"?: string | null + readonly "status": "completed" | "failed" + readonly "type": "apply_patch_call_output" +} +export const ApplyPatchCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "output": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Literals(["completed", "failed"]), + "type": Schema.Literal("apply_patch_call_output") +}).annotate({ + "description": + "The client's echo of an `apply_patch_call` after applying the patch. `output` is an optional human-readable log; `status` is `completed` when the patch was applied successfully, `failed` otherwise.", + "identifier": "ApplyPatchCallOutputItem" +}) +export type ApplyPatchCallStatus = "in_progress" | "completed" +export const ApplyPatchCallStatus = Schema.Literals(["in_progress", "completed"]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item.", + "identifier": "ApplyPatchCallStatus" +}) +export type ApplyPatchCreateFileOperation = { + readonly "diff": string + readonly "path": string + readonly "type": "create_file" +} +export const ApplyPatchCreateFileOperation = Schema.Struct({ + "diff": Schema.String, + "path": Schema.String, + "type": Schema.Literal("create_file") +}).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents.", + "identifier": "ApplyPatchCreateFileOperation" +}) +export type ApplyPatchDeleteFileOperation = { readonly "path": string; readonly "type": "delete_file" } +export const ApplyPatchDeleteFileOperation = Schema.Struct({ + "path": Schema.String, + "type": Schema.Literal("delete_file") +}).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required.", + "identifier": "ApplyPatchDeleteFileOperation" +}) +export type ApplyPatchEngineEnum = "auto" | "native" | "openrouter" +export const ApplyPatchEngineEnum = Schema.Literals(["auto", "native", "openrouter"]).annotate({ + "description": + "Which apply_patch engine to use. \"auto\" (default) uses native passthrough when the endpoint advertises native apply_patch support, otherwise falls back to OpenRouter's HITL validator. \"native\" forces native passthrough — when the endpoint does not support native, the request falls back to HITL. \"openrouter\" always runs the HITL validator. Native passthrough streams the diff incrementally via `apply_patch_call_operation_diff.delta` events; HITL buffers the diff for atomic delivery as a single delta.", + "identifier": "ApplyPatchEngineEnum" +}) +export type ApplyPatchServerTool = { readonly "type": "apply_patch" } +export const ApplyPatchServerTool = Schema.Struct({ "type": Schema.Literal("apply_patch") }).annotate({ + "description": "Apply patch tool configuration", + "identifier": "ApplyPatchServerTool" +}) +export type ApplyPatchUpdateFileOperation = { + readonly "diff": string + readonly "path": string + readonly "type": "update_file" +} +export const ApplyPatchUpdateFileOperation = Schema.Struct({ + "diff": Schema.String, + "path": Schema.String, + "type": Schema.Literal("update_file") +}).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file.", + "identifier": "ApplyPatchUpdateFileOperation" +}) +export type AppRankingsItem = { + readonly "app_id": number + readonly "app_name": string + readonly "rank": number + readonly "total_requests": number + readonly "total_tokens": string +} +export const AppRankingsItem = Schema.Struct({ + "app_id": Schema.Number.annotate({ "description": "Stable numeric identifier of the app on OpenRouter." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "app_name": Schema.String.annotate({ "description": "Public display name of the app." }), + "rank": Schema.Number.annotate({ + "description": "1-based position of the app within this response, per the requested `sort`." + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "total_requests": Schema.Number.annotate({ + "description": "Number of requests attributed to the app inside the date window." + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "total_tokens": Schema.String.annotate({ + "description": + "Sum of `prompt_tokens + completion_tokens` attributed to the app inside the date window, returned as a decimal string so 64-bit values are not truncated." + }) +}).annotate({ "identifier": "AppRankingsItem" }) +export type AutoBetaRouterPlugin = { + readonly "allowed_models"?: ReadonlyArray + readonly "cost_quality_tradeoff"?: number + readonly "enabled"?: boolean + readonly "id": "auto-beta-router" +} +export const AutoBetaRouterPlugin = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "List of model patterns to filter which models the auto-beta-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." + }) + ), + "cost_quality_tradeoff": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Balances routing between cost and quality on a 0-10 scale. The auto-beta-router ranks models for the classified task type by community spend share, then filters candidates by their average cost per generation for that task. Higher values favor cheaper models: 10 keeps only models around the cheapest 10th percentile, while 0 permits models up to the 90th percentile for cost. Defaults to 9." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(10).annotate({ "expected": "a value less than or equal to 10" })) + ), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the auto-beta-router plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("auto-beta-router") +}).annotate({ "identifier": "AutoBetaRouterPlugin" }) +export type AutoRouterPlugin = { + readonly "allowed_models"?: ReadonlyArray + readonly "cost_quality_tradeoff"?: number + readonly "enabled"?: boolean + readonly "id": "auto-router" + readonly "pin_model"?: boolean +} +export const AutoRouterPlugin = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "List of model patterns to filter which models the auto-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." + }) + ), + "cost_quality_tradeoff": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Controls cost vs. quality routing tradeoff (0–10). 0 = pure quality (best model regardless of cost), 10 = maximize for cost (cheapest model wins). Intermediate values blend quality and cost signals continuously. Defaults to 7." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(10).annotate({ "expected": "a value less than or equal to 10" })) + ), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the auto-router plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("auto-router"), + "pin_model": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "When true, reuses the model from the most recent assistant message's `model` attribute for subsequent turns. Defaults to false." + }) + ) +}).annotate({ "identifier": "AutoRouterPlugin" }) +export type BadGatewayResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const BadGatewayResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for BadGatewayResponse", "identifier": "BadGatewayResponseErrorData" }) +export type BadRequestResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const BadRequestResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for BadRequestResponse", "identifier": "BadRequestResponseErrorData" }) +export type BaseCustomToolCallInputDeltaEvent = { + readonly "delta": string readonly "item_id": string readonly "output_index": number - readonly "name": string - readonly "arguments": string readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.delta" } -export const OpenResponsesFunctionCallArgumentsDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.function_call_arguments.done"), +export const BaseCustomToolCallInputDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "name": Schema.String, - "arguments": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when function call arguments streaming is complete" }) -export type OpenResponsesReasoningDeltaEvent = { - readonly "type": "response.reasoning_text.delta" - readonly "output_index": number + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.delta") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input is being streamed. Mirrors `response.function_call_arguments.delta` but for `custom` tools whose input is opaque text rather than JSON arguments.", + "identifier": "BaseCustomToolCallInputDeltaEvent" +}) +export type BaseCustomToolCallInputDoneEvent = { + readonly "input": string readonly "item_id": string - readonly "content_index": number - readonly "delta": string + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.done" } -export const OpenResponsesReasoningDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_text.delta"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const BaseCustomToolCallInputDoneEvent = Schema.Struct({ + "input": Schema.String, "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning text delta is streamed" }) -export type OpenResponsesReasoningDoneEvent = { - readonly "type": "response.reasoning_text.done" - readonly "output_index": number + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.done") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input streaming is complete. Mirrors `response.function_call_arguments.done` but for `custom` tools.", + "identifier": "BaseCustomToolCallInputDoneEvent" +}) +export type BaseErrorEvent = { + readonly "code": string | null + readonly "message": string + readonly "param": string | null + readonly "sequence_number": number + readonly "type": "error" +} +export const BaseErrorEvent = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "message": Schema.String, + "param": Schema.Union([Schema.String, Schema.Null]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("error") +}).annotate({ "description": "Event emitted when an error occurs during streaming", "identifier": "BaseErrorEvent" }) +export type BaseFunctionCallArgsDeltaEvent = { + readonly "delta": string readonly "item_id": string - readonly "content_index": number - readonly "text": string + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.function_call_arguments.delta" } -export const OpenResponsesReasoningDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_text.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const BaseFunctionCallArgsDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "text": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning text streaming is complete" }) -export type OpenResponsesReasoningSummaryTextDeltaEvent = { - readonly "type": "response.reasoning_summary_text.delta" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.delta") +}).annotate({ + "description": "Event emitted when function call arguments are being streamed", + "identifier": "BaseFunctionCallArgsDeltaEvent" +}) +export type BaseFunctionCallArgsDoneEvent = { + readonly "arguments": string readonly "item_id": string + readonly "name": string readonly "output_index": number - readonly "summary_index": number - readonly "delta": string readonly "sequence_number": number + readonly "type": "response.function_call_arguments.done" } -export const OpenResponsesReasoningSummaryTextDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_text.delta"), +export const BaseFunctionCallArgsDoneEvent = Schema.Struct({ + "arguments": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "summary_index": Schema.Number.check(Schema.isFinite()), + "name": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.done") +}).annotate({ + "description": "Event emitted when function call arguments streaming is complete", + "identifier": "BaseFunctionCallArgsDoneEvent" +}) +export type BaseReasoningDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.reasoning_text.delta" +} +export const BaseReasoningDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning summary text delta is streamed" }) -export type OpenResponsesReasoningSummaryTextDoneEvent = { - readonly "type": "response.reasoning_summary_text.done" + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_text.delta") +}).annotate({ + "description": "Event emitted when reasoning text delta is streamed", + "identifier": "BaseReasoningDeltaEvent" +}) +export type BaseReasoningDoneEvent = { + readonly "content_index": number readonly "item_id": string readonly "output_index": number - readonly "summary_index": number - readonly "text": string readonly "sequence_number": number + readonly "text": string + readonly "type": "response.reasoning_text.done" } -export const OpenResponsesReasoningSummaryTextDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_text.done"), +export const BaseReasoningDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "summary_index": Schema.Number.check(Schema.isFinite()), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "text": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when reasoning summary text streaming is complete" }) -export type OpenResponsesImageGenCallInProgress = { - readonly "type": "response.image_generation_call.in_progress" + "type": Schema.Literal("response.reasoning_text.done") +}).annotate({ + "description": "Event emitted when reasoning text streaming is complete", + "identifier": "BaseReasoningDoneEvent" +}) +export type BaseReasoningSummaryTextDeltaEvent = { + readonly "delta": string readonly "item_id": string readonly "output_index": number readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_text.delta" } -export const OpenResponsesImageGenCallInProgress = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.in_progress"), +export const BaseReasoningSummaryTextDeltaEvent = Schema.Struct({ + "delta": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call in progress" }) -export type OpenResponsesImageGenCallGenerating = { - readonly "type": "response.image_generation_call.generating" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_text.delta") +}).annotate({ + "description": "Event emitted when reasoning summary text delta is streamed", + "identifier": "BaseReasoningSummaryTextDeltaEvent" +}) +export type BaseReasoningSummaryTextDoneEvent = { readonly "item_id": string readonly "output_index": number readonly "sequence_number": number + readonly "summary_index": number + readonly "text": string + readonly "type": "response.reasoning_summary_text.done" } -export const OpenResponsesImageGenCallGenerating = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.generating"), +export const BaseReasoningSummaryTextDoneEvent = Schema.Struct({ "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call is generating" }) -export type OpenResponsesImageGenCallPartialImage = { - readonly "type": "response.image_generation_call.partial_image" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.reasoning_summary_text.done") +}).annotate({ + "description": "Event emitted when reasoning summary text streaming is complete", + "identifier": "BaseReasoningSummaryTextDoneEvent" +}) +export type BaseRefusalDeltaEvent = { + readonly "content_index": number + readonly "delta": string readonly "item_id": string readonly "output_index": number readonly "sequence_number": number - readonly "partial_image_b64": string - readonly "partial_image_index": number + readonly "type": "response.refusal.delta" } -export const OpenResponsesImageGenCallPartialImage = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.partial_image"), +export const BaseRefusalDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()), - "partial_image_b64": Schema.String, - "partial_image_index": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call with partial image" }) -export type OpenResponsesImageGenCallCompleted = { - readonly "type": "response.image_generation_call.completed" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.delta") +}).annotate({ "description": "Event emitted when a refusal delta is streamed", "identifier": "BaseRefusalDeltaEvent" }) +export type BaseRefusalDoneEvent = { + readonly "content_index": number readonly "item_id": string readonly "output_index": number + readonly "refusal": string readonly "sequence_number": number + readonly "type": "response.refusal.done" } -export const OpenResponsesImageGenCallCompleted = Schema.Struct({ - "type": Schema.Literal("response.image_generation_call.completed"), +export const BaseRefusalDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "output_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Image generation call completed" }) -export type BadRequestResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "refusal": Schema.String, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.done") +}).annotate({ "description": "Event emitted when refusal streaming is complete", "identifier": "BaseRefusalDoneEvent" }) +export type BashServerToolEngine = "auto" | "native" | "openrouter" +export const BashServerToolEngine = Schema.Literals(["auto", "native", "openrouter"]).annotate({ + "description": + "Which bash engine to use. \"openrouter\" runs commands server-side in the OpenRouter sandbox. \"auto\" (default) and \"native\" use native passthrough, returning the tool call to your application to run client-side; OpenRouter does not execute the commands.", + "identifier": "BashServerToolEngine" +}) +export type BooleanCapability = { readonly "type": "boolean" } +export const BooleanCapability = Schema.Struct({ "type": Schema.Literal("boolean") }).annotate({ + "description": "A supported-or-not flag. Present means the parameter is accepted.", + "identifier": "BooleanCapability" +}) +export type BulkAddWorkspaceMembersRequest = { readonly "user_ids": ReadonlyArray } +export const BulkAddWorkspaceMembersRequest = Schema.Struct({ + "user_ids": Schema.Array(Schema.String).annotate({ + "description": + "List of user IDs to add to the workspace. Members are assigned the same role they hold in the organization." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ) +}).annotate({ "identifier": "BulkAddWorkspaceMembersRequest" }) +export type BulkAssignKeysRequest = { readonly "key_hashes": ReadonlyArray } +export const BulkAssignKeysRequest = Schema.Struct({ + "key_hashes": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of API key hashes to assign to the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkAssignKeysRequest" }) +export type BulkAssignKeysResponse = { readonly "assigned_count": number } +export const BulkAssignKeysResponse = Schema.Struct({ + "assigned_count": Schema.Number.annotate({ "description": "Number of keys successfully assigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkAssignKeysResponse" }) +export type BulkAssignMembersRequest = { readonly "member_user_ids": ReadonlyArray } +export const BulkAssignMembersRequest = Schema.Struct({ + "member_user_ids": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of member user IDs to assign to the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkAssignMembersRequest" }) +export type BulkAssignMembersResponse = { readonly "assigned_count": number } +export const BulkAssignMembersResponse = Schema.Struct({ + "assigned_count": Schema.Number.annotate({ "description": "Number of members successfully assigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkAssignMembersResponse" }) +export type BulkRemoveWorkspaceMembersRequest = { readonly "user_ids": ReadonlyArray } +export const BulkRemoveWorkspaceMembersRequest = Schema.Struct({ + "user_ids": Schema.Array(Schema.String).annotate({ "description": "List of user IDs to remove from the workspace" }) + .check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ) +}).annotate({ "identifier": "BulkRemoveWorkspaceMembersRequest" }) +export type BulkRemoveWorkspaceMembersResponse = { readonly "removed_count": number } +export const BulkRemoveWorkspaceMembersResponse = Schema.Struct({ + "removed_count": Schema.Number.annotate({ "description": "Number of members removed" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkRemoveWorkspaceMembersResponse" }) +export type BulkUnassignKeysRequest = { readonly "key_hashes": ReadonlyArray } +export const BulkUnassignKeysRequest = Schema.Struct({ + "key_hashes": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of API key hashes to unassign from the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkUnassignKeysRequest" }) +export type BulkUnassignKeysResponse = { readonly "unassigned_count": number } +export const BulkUnassignKeysResponse = Schema.Struct({ + "unassigned_count": Schema.Number.annotate({ "description": "Number of keys successfully unassigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkUnassignKeysResponse" }) +export type BulkUnassignMembersRequest = { readonly "member_user_ids": ReadonlyArray } +export const BulkUnassignMembersRequest = Schema.Struct({ + "member_user_ids": Schema.Array( + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ).annotate({ "description": "Array of member user IDs to unassign from the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ "identifier": "BulkUnassignMembersRequest" }) +export type BulkUnassignMembersResponse = { readonly "unassigned_count": number } +export const BulkUnassignMembersResponse = Schema.Struct({ + "unassigned_count": Schema.Number.annotate({ "description": "Number of members successfully unassigned" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "BulkUnassignMembersResponse" }) +export type BYOKProviderSlug = + | "ai21" + | "aion-labs" + | "akashml" + | "alibaba" + | "amazon-bedrock" + | "amazon-nova" + | "ambient" + | "anthropic" + | "arcee-ai" + | "atlas-cloud" + | "avian" + | "azure" + | "baidu" + | "baseten" + | "black-forest-labs" + | "byteplus" + | "cerebras" + | "chutes" + | "cirrascale" + | "clarifai" + | "cloudflare" + | "cohere" + | "coreweave" + | "crusoe" + | "darkbloom" + | "decart" + | "deepgram" + | "deepinfra" + | "deepseek" + | "dekallm" + | "digitalocean" + | "featherless" + | "fireworks" + | "fish-audio" + | "friendli" + | "gmicloud" + | "google-ai-studio" + | "google-vertex" + | "groq" + | "heygen" + | "inception" + | "inceptron" + | "inferact-vllm" + | "inference-net" + | "infermatic" + | "inflection" + | "io-net" + | "ionstream" + | "krea" + | "liquid" + | "mancer" + | "mara" + | "meta" + | "minimax" + | "mistral" + | "modelrun" + | "modular" + | "moonshotai" + | "morph" + | "ncompass" + | "nebius" + | "nex-agi" + | "nextbit" + | "novita" + | "nvidia" + | "open-inference" + | "openai" + | "parasail" + | "perceptron" + | "perplexity" + | "phala" + | "poolside" + | "quiver" + | "recraft" + | "reka" + | "relace" + | "runway" + | "sail-research" + | "sakana" + | "sambanova" + | "seed" + | "siliconflow" + | "sourceful" + | "stepfun" + | "streamlake" + | "switchpoint" + | "tencent" + | "tenstorrent" + | "together" + | "upstage" + | "venice" + | "wafer" + | "wandb" + | "xai" + | "xiaomi" + | "z-ai" +export const BYOKProviderSlug = Schema.Literals([ + "ai21", + "aion-labs", + "akashml", + "alibaba", + "amazon-bedrock", + "amazon-nova", + "ambient", + "anthropic", + "arcee-ai", + "atlas-cloud", + "avian", + "azure", + "baidu", + "baseten", + "black-forest-labs", + "byteplus", + "cerebras", + "chutes", + "cirrascale", + "clarifai", + "cloudflare", + "cohere", + "coreweave", + "crusoe", + "darkbloom", + "decart", + "deepgram", + "deepinfra", + "deepseek", + "dekallm", + "digitalocean", + "featherless", + "fireworks", + "fish-audio", + "friendli", + "gmicloud", + "google-ai-studio", + "google-vertex", + "groq", + "heygen", + "inception", + "inceptron", + "inferact-vllm", + "inference-net", + "infermatic", + "inflection", + "io-net", + "ionstream", + "krea", + "liquid", + "mancer", + "mara", + "meta", + "minimax", + "mistral", + "modelrun", + "modular", + "moonshotai", + "morph", + "ncompass", + "nebius", + "nex-agi", + "nextbit", + "novita", + "nvidia", + "open-inference", + "openai", + "parasail", + "perceptron", + "perplexity", + "phala", + "poolside", + "quiver", + "recraft", + "reka", + "relace", + "runway", + "sail-research", + "sakana", + "sambanova", + "seed", + "siliconflow", + "sourceful", + "stepfun", + "streamlake", + "switchpoint", + "tencent", + "tenstorrent", + "together", + "upstage", + "venice", + "wafer", + "wandb", + "xai", + "xiaomi", + "z-ai" +]).annotate({ + "description": + "The upstream provider this credential authenticates against, as a lowercase slug (e.g. `openai`, `anthropic`, `amazon-bedrock`).", + "identifier": "BYOKProviderSlug" +}) +export type ChatAssistantImages = ReadonlyArray<{ readonly "image_url": { readonly "url": string } }> +export const ChatAssistantImages = Schema.Array( + Schema.Struct({ + "image_url": Schema.Struct({ + "url": Schema.String.annotate({ "description": "URL or base64-encoded data of the generated image" }) + }) + }) +).annotate({ "description": "Generated images from image generation models", "identifier": "ChatAssistantImages" }) +export type ChatAudioOutput = { + readonly "data"?: string + readonly "expires_at"?: number + readonly "id"?: string + readonly "transcript"?: string } -export const BadRequestResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for BadRequestResponse" }) -export type UnauthorizedResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatAudioOutput = Schema.Struct({ + "data": Schema.optionalKey(Schema.String.annotate({ "description": "Base64 encoded audio data" })), + "expires_at": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio expiration timestamp" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "Audio output identifier" })), + "transcript": Schema.optionalKey(Schema.String.annotate({ "description": "Audio transcript" })) +}).annotate({ "description": "Audio output data or reference", "identifier": "ChatAudioOutput" }) +export type ChatContentAudio = { + readonly "input_audio": { readonly "data": string; readonly "format": string } + readonly "type": "input_audio" } -export const UnauthorizedResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for UnauthorizedResponse" }) -export type PaymentRequiredResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatContentAudio = Schema.Struct({ + "input_audio": Schema.Struct({ + "data": Schema.String.annotate({ "description": "Base64 encoded audio data" }), + "format": Schema.String.annotate({ + "description": + "Audio format (e.g., wav, mp3, flac, m4a, ogg, aiff, aac, pcm16, pcm24). Supported formats vary by provider." + }) + }), + "type": Schema.Literal("input_audio") +}).annotate({ + "description": "Audio input content part. Supported audio formats vary by provider.", + "identifier": "ChatContentAudio" +}) +export type ChatContentFile = { + readonly "file": { readonly "file_data"?: string; readonly "file_id"?: string; readonly "filename"?: string } + readonly "type": "file" } -export const PaymentRequiredResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for PaymentRequiredResponse" }) -export type NotFoundResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatContentFile = Schema.Struct({ + "file": Schema.Struct({ + "file_data": Schema.optionalKey( + Schema.String.annotate({ "description": "File content as base64 data URL or URL" }) + ), + "file_id": Schema.optionalKey(Schema.String.annotate({ "description": "File ID for previously uploaded files" })), + "filename": Schema.optionalKey(Schema.String.annotate({ "description": "Original filename" })) + }), + "type": Schema.Literal("file") +}).annotate({ "description": "File content part for document processing", "identifier": "ChatContentFile" }) +export type ChatContentImage = { + readonly "image_url": { readonly "detail"?: "auto" | "low" | "high" | "original"; readonly "url": string } + readonly "type": "image_url" } -export const NotFoundResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for NotFoundResponse" }) -export type RequestTimeoutResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatContentImage = Schema.Struct({ + "image_url": Schema.Struct({ + "detail": Schema.optionalKey( + Schema.Literals(["auto", "low", "high", "original"]).annotate({ + "description": + "Image detail level for vision models. `original` is an OpenRouter extension (not in the OpenAI Chat Completions spec) requesting true original-resolution media; it is downgraded to `high` for providers that lack an original-resolution tier." + }) + ), + "url": Schema.String.annotate({ "description": "URL of the image (data: URLs supported)" }) + }), + "type": Schema.Literal("image_url") +}).annotate({ "description": "Image content part for vision models", "identifier": "ChatContentImage" }) +export type ChatContentVideoInput = { readonly "url": string } +export const ChatContentVideoInput = Schema.Struct({ + "url": Schema.String.annotate({ "description": "URL of the video (data: URLs supported)" }) +}).annotate({ "description": "Video input object", "identifier": "ChatContentVideoInput" }) +export type ChatDebugOptions = { readonly "echo_upstream_body"?: boolean } +export const ChatDebugOptions = Schema.Struct({ + "echo_upstream_body": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "If true, includes the transformed upstream request body in a debug chunk at the start of the stream. Only works with streaming mode." + }) + ) +}).annotate({ + "description": "Debug options for inspecting request transformations (streaming only)", + "identifier": "ChatDebugOptions" +}) +export type ChatFinishReasonEnum = "tool_calls" | "stop" | "length" | "content_filter" | "error" | null +export const ChatFinishReasonEnum = Schema.Union([ + Schema.Literal("tool_calls"), + Schema.Literal("stop"), + Schema.Literal("length"), + Schema.Literal("content_filter"), + Schema.Literal("error"), + Schema.Null +]).annotate({ "identifier": "ChatFinishReasonEnum" }) +export type ChatFormatGrammarConfig = { readonly "grammar": string; readonly "type": "grammar" } +export const ChatFormatGrammarConfig = Schema.Struct({ + "grammar": Schema.String.annotate({ "description": "Custom grammar for text generation" }), + "type": Schema.Literal("grammar") +}).annotate({ "description": "Custom grammar response format", "identifier": "ChatFormatGrammarConfig" }) +export type ChatFormatJsonObjectConfig = { readonly "type": "json_object" } +export const ChatFormatJsonObjectConfig = Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ + "description": "JSON object response format", + "identifier": "ChatFormatJsonObjectConfig" +}) +export type ChatFormatPythonConfig = { readonly "type": "python" } +export const ChatFormatPythonConfig = Schema.Struct({ "type": Schema.Literal("python") }).annotate({ + "description": "Python code response format", + "identifier": "ChatFormatPythonConfig" +}) +export type ChatFormatTextConfig = { readonly "type": "text" } +export const ChatFormatTextConfig = Schema.Struct({ "type": Schema.Literal("text") }).annotate({ + "description": "Default text response format", + "identifier": "ChatFormatTextConfig" +}) +export type ChatJsonSchemaConfig = { + readonly "description"?: string + readonly "name": string + readonly "schema"?: {} + readonly "strict"?: boolean | null } -export const RequestTimeoutResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for RequestTimeoutResponse" }) -export type PayloadTooLargeResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatJsonSchemaConfig = Schema.Struct({ + "description": Schema.optionalKey(Schema.String.annotate({ "description": "Schema description for the model" })), + "name": Schema.String.annotate({ "description": "Schema name (a-z, A-Z, 0-9, underscores, dashes, max 64 chars)" }) + .check(Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" })), + "schema": Schema.optionalKey(Schema.Struct({}).annotate({ "description": "JSON Schema object" })), + "strict": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Enable strict schema adherence" }) + ) +}).annotate({ "description": "JSON Schema configuration object", "identifier": "ChatJsonSchemaConfig" }) +export type ChatNamedToolChoice = { readonly "function": { readonly "name": string }; readonly "type": "function" } +export const ChatNamedToolChoice = Schema.Struct({ + "function": Schema.Struct({ "name": Schema.String.annotate({ "description": "Function name to call" }) }), + "type": Schema.Literal("function") +}).annotate({ "description": "Named tool choice for specific function", "identifier": "ChatNamedToolChoice" }) +export type ChatReasoningSummaryVerbosityEnum = "auto" | "concise" | "detailed" | null +export const ChatReasoningSummaryVerbosityEnum = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("concise"), + Schema.Literal("detailed"), + Schema.Null +]).annotate({ "identifier": "ChatReasoningSummaryVerbosityEnum" }) +export type ChatServerToolChoice = { readonly "type": string } +export const ChatServerToolChoice = Schema.Struct({ + "type": Schema.String.annotate({ + "description": + "OpenRouter server-tool type to force (e.g. `openrouter:web_search`, `web_search`, `web_search_preview`)." + }) +}).annotate({ + "description": + "OpenRouter extension: force a specific server tool by naming it directly in `tool_choice.type` instead of wrapping it in `{ type: \"function\", function: { name } }`.", + "identifier": "ChatServerToolChoice" +}) +export type Objects_6 = { readonly "include_usage"?: boolean; readonly [x: string]: Schema.Json } +export const Objects_6 = Schema.StructWithRest( + Schema.Struct({ + "include_usage": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Deprecated: This field has no effect. Full usage details are always included." + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type ChatStreamToolCall = { + readonly "function"?: { readonly "arguments"?: string; readonly "name"?: string | null } + readonly "id"?: string | null + readonly "index": number + readonly "type"?: "function" | null } -export const PayloadTooLargeResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for PayloadTooLargeResponse" }) -export type UnprocessableEntityResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatStreamToolCall = Schema.Struct({ + "function": Schema.optionalKey( + Schema.Struct({ + "arguments": Schema.optionalKey(Schema.String.annotate({ "description": "Function arguments as JSON string" })), + "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Function call details" }) + ), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.Number.annotate({ "description": "Tool call index in the array" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "type": Schema.optionalKey(Schema.Union([Schema.Literal("function"), Schema.Null])) +}).annotate({ "description": "Tool call delta for streaming responses", "identifier": "ChatStreamToolCall" }) +export type ChatTokenLogprob = { + readonly "bytes": ReadonlyArray | null + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray | null; readonly "logprob": number; readonly "token": string } + > } -export const UnprocessableEntityResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for UnprocessableEntityResponse" }) -export type TooManyRequestsResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatTokenLogprob = Schema.Struct({ + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "UTF-8 bytes of the token" }), + "logprob": Schema.Number.annotate({ "description": "Log probability of the token", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String.annotate({ "description": "The token" }), + "top_logprobs": Schema.Array(Schema.Struct({ + "bytes": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + })).annotate({ "description": "Top alternative tokens with probabilities" }) +}).annotate({ "description": "Token log probability information", "identifier": "ChatTokenLogprob" }) +export type ChatToolCall = { + readonly "function": { readonly "arguments": string; readonly "name": string } + readonly "id": string + readonly "type": "function" } -export const TooManyRequestsResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for TooManyRequestsResponse" }) -export type InternalServerResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const ChatToolCall = Schema.Struct({ + "function": Schema.Struct({ + "arguments": Schema.String.annotate({ "description": "Function arguments as JSON string" }), + "name": Schema.String.annotate({ "description": "Function name to call" }) + }), + "id": Schema.String.annotate({ "description": "Tool call identifier" }), + "type": Schema.Literal("function") +}).annotate({ "description": "Tool call made by the assistant", "identifier": "ChatToolCall" }) +export type Union_ = { + readonly "accepted_prediction_tokens"?: number | null + readonly "audio_tokens"?: number | null + readonly "reasoning_tokens"?: number | null + readonly "rejected_prediction_tokens"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_ = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "accepted_prediction_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Accepted prediction tokens" }) + ), + "audio_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for audio output" }) + ), + "reasoning_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for reasoning" }) + ), + "rejected_prediction_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Rejected prediction tokens" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Detailed completion token usage" }) +export type Union_1 = { + readonly "audio_tokens"?: number + readonly "cache_write_tokens"?: number + readonly "cached_tokens"?: number + readonly "video_tokens"?: number + readonly [x: string]: Schema.Json +} | null +export const Union_1 = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Audio input tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "cache_write_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Tokens written to cache. Only returned for models with explicit caching and cache write pricing." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cached_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cached prompt tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "video_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Video input tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Detailed prompt token usage" }) +export type CodeInterpreterServerTool = { + readonly "container": string | { + readonly "file_ids"?: ReadonlyArray + readonly "memory_limit"?: "1g" | "4g" | "16g" | "64g" | null + readonly "type": "auto" + } + readonly "type": "code_interpreter" } -export const InternalServerResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for InternalServerResponse" }) -export type BadGatewayResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} +export const CodeInterpreterServerTool = Schema.Struct({ + "container": Schema.Union([ + Schema.String, + Schema.Struct({ + "file_ids": Schema.optionalKey(Schema.Array(Schema.String)), + "memory_limit": Schema.optionalKey( + Schema.Union([ + Schema.Literal("1g"), + Schema.Literal("4g"), + Schema.Literal("16g"), + Schema.Literal("64g"), + Schema.Null + ]) + ), + "type": Schema.Literal("auto") + }) + ]), + "type": Schema.Literal("code_interpreter") +}).annotate({ "description": "Code interpreter tool configuration", "identifier": "CodeInterpreterServerTool" }) +export type CodexLocalShellTool = { readonly "type": "local_shell" } +export const CodexLocalShellTool = Schema.Struct({ "type": Schema.Literal("local_shell") }).annotate({ + "description": "Local shell tool configuration", + "identifier": "CodexLocalShellTool" +}) +export type CompactionItem = { + readonly "encrypted_content": string + readonly "id"?: string | null + readonly "type": "compaction" } -export const BadGatewayResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for BadGatewayResponse" }) -export type ServiceUnavailableResponseErrorData = { +export const CompactionItem = Schema.Struct({ + "encrypted_content": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("compaction") +}).annotate({ "description": "A context compaction marker with encrypted summary", "identifier": "CompactionItem" }) +export type CompoundFilter = { readonly "filters": ReadonlyArray<{}>; readonly "type": "and" | "or" } +export const CompoundFilter = Schema.Struct({ + "filters": Schema.Array(Schema.Struct({})), + "type": Schema.Literals(["and", "or"]) +}).annotate({ + "description": "A compound filter that combines multiple comparison or compound filters", + "identifier": "CompoundFilter" +}) +export type ComputerUseServerTool = { + readonly "display_height": number + readonly "display_width": number + readonly "environment": "windows" | "mac" | "linux" | "ubuntu" | "browser" + readonly "type": "computer_use_preview" +} +export const ComputerUseServerTool = Schema.Struct({ + "display_height": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "display_width": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "environment": Schema.Literals(["windows", "mac", "linux", "ubuntu", "browser"]), + "type": Schema.Literal("computer_use_preview") +}).annotate({ "description": "Computer use preview tool configuration", "identifier": "ComputerUseServerTool" }) +export type ConflictResponseErrorData = { readonly "code": number readonly "message": string - readonly "metadata"?: {} + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } -export const ServiceUnavailableResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), +export const ConflictResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for ServiceUnavailableResponse" }) + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for ConflictResponse", "identifier": "ConflictResponseErrorData" }) +export type ContainerAutoEnvironment = { readonly "type": "container_auto" } +export const ContainerAutoEnvironment = Schema.Struct({ "type": Schema.Literal("container_auto") }).annotate({ + "description": "An OpenRouter-managed, auto-provisioned ephemeral container.", + "identifier": "ContainerAutoEnvironment" +}) +export type ContainerReferenceEnvironment = { readonly "container_id": string; readonly "type": "container_reference" } +export const ContainerReferenceEnvironment = Schema.Struct({ + "container_id": Schema.String.annotate({ + "description": "Identifier of an existing container to reuse (max 20 characters)." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" }) + ).check(Schema.isPattern(new RegExp("^[\\w-]+$")).annotate({ "expected": "a string matching the RegExp ^[\\w-]+$" })), + "type": Schema.Literal("container_reference") +}).annotate({ + "description": "Reference to a previously created container to reuse.", + "identifier": "ContainerReferenceEnvironment" +}) +export type ContentFilterAction = "redact" | "block" | "flag" +export const ContentFilterAction = Schema.Literals(["redact", "block", "flag"]).annotate({ + "description": "Action taken when the pattern matches", + "identifier": "ContentFilterAction" +}) +export type ContentFilterBuiltinAction = "redact" | "block" | "flag" +export const ContentFilterBuiltinAction = Schema.Literals(["redact", "block", "flag"]).annotate({ + "description": "Action taken when the builtin filter triggers", + "identifier": "ContentFilterBuiltinAction" +}) +export type ContentFilterBuiltinSlug = + | "email" + | "phone" + | "ssn" + | "credit-card" + | "ip-address" + | "person-name" + | "address" + | "regex-prompt-injection" +export const ContentFilterBuiltinSlug = Schema.Literals([ + "email", + "phone", + "ssn", + "credit-card", + "ip-address", + "person-name", + "address", + "regex-prompt-injection" +]).annotate({ "description": "The builtin filter identifier", "identifier": "ContentFilterBuiltinSlug" }) +export type ContentPartAudio = { readonly "audio_url": { readonly "url": string }; readonly "type": "audio_url" } +export const ContentPartAudio = Schema.Struct({ + "audio_url": Schema.Struct({ "url": Schema.String }), + "type": Schema.Literal("audio_url") +}).annotate({ "identifier": "ContentPartAudio" }) +export type ContentPartImage = { readonly "image_url": { readonly "url": string }; readonly "type": "image_url" } +export const ContentPartImage = Schema.Struct({ + "image_url": Schema.Struct({ "url": Schema.String }), + "type": Schema.Literal("image_url") +}).annotate({ "identifier": "ContentPartImage" }) +export type ContentPartVideo = { readonly "type": "video_url"; readonly "video_url": { readonly "url": string } } +export const ContentPartVideo = Schema.Struct({ + "type": Schema.Literal("video_url"), + "video_url": Schema.Struct({ "url": Schema.String }) +}).annotate({ "identifier": "ContentPartVideo" }) +export type ContextCompactionItem = { + readonly "encrypted_content"?: string | null + readonly "id"?: string | null + readonly "type": "context_compaction" +} +export const ContextCompactionItem = Schema.Struct({ + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("context_compaction") +}).annotate({ + "description": "A context compaction marker with an optional encrypted summary", + "identifier": "ContextCompactionItem" +}) +export type ContextCompressionEngine = "middle-out" +export const ContextCompressionEngine = Schema.Literal("middle-out").annotate({ + "description": "The compression engine to use. Defaults to \"middle-out\".", + "identifier": "ContextCompressionEngine" +}) +export type Objects_8 = { + readonly "upstream_inference_completions_cost": number + readonly "upstream_inference_cost"?: number | null + readonly "upstream_inference_prompt_cost": number + readonly [x: string]: Schema.Json +} +export const Objects_8 = Schema.StructWithRest( + Schema.Struct({ + "upstream_inference_completions_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "upstream_inference_cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "upstream_inference_prompt_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type CreateWorkspaceRequest = { + readonly "default_image_model"?: string | null + readonly "default_provider_sort"?: string | null + readonly "default_text_model"?: string | null + readonly "description"?: string | null + readonly "io_logging_api_key_ids"?: ReadonlyArray | null + readonly "io_logging_sampling_rate"?: number + readonly "is_data_discount_logging_enabled"?: boolean + readonly "is_observability_broadcast_enabled"?: boolean + readonly "is_observability_io_logging_enabled"?: boolean + readonly "name": string + readonly "slug": string +} +export const CreateWorkspaceRequest = Schema.Struct({ + "default_image_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default image model for this workspace" }) + ), + "default_provider_sort": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default provider sort preference (price, throughput, latency, exacto)" + }) + ), + "default_text_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default text model for this workspace" }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(500).annotate({ "expected": "a value with a length of at most 500" })), + Schema.Null + ]).annotate({ "description": "Description of the workspace" }) + ), + "io_logging_api_key_ids": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "Optional array of API key IDs to filter I/O logging" }) + ), + "io_logging_sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate for I/O logging (0.0001-1)", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "is_data_discount_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether data discount logging is enabled" }) + ), + "is_observability_broadcast_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether broadcast is enabled" }) + ), + "is_observability_io_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether private logging is enabled" }) + ), + "name": Schema.String.annotate({ "description": "Name for the new workspace" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })), + "slug": Schema.String.annotate({ + "description": + "URL-friendly slug (lowercase alphanumeric segments separated by single hyphens, no leading/trailing hyphens)" + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(50).annotate({ "expected": "a value with a length of at most 50" }) + ).check( + Schema.isPattern(new RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ + "expected": "a string matching the RegExp ^[a-z0-9]+(?:-[a-z0-9]+)*$" + }) + ) +}).annotate({ "identifier": "CreateWorkspaceRequest" }) +export type CustomTool = { + readonly "description"?: string + readonly "format"?: { readonly "type": "text" } | { + readonly "definition": string + readonly "syntax": "lark" | "regex" + readonly "type": "grammar" + } + readonly "name": string + readonly "type": "custom" +} +export const CustomTool = Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "format": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("text") }), + Schema.Struct({ + "definition": Schema.String, + "syntax": Schema.Literals(["lark", "regex"]), + "type": Schema.Literal("grammar") + }) + ]) + ), + "name": Schema.String, + "type": Schema.Literal("custom") +}).annotate({ "description": "Custom tool configuration", "identifier": "CustomTool" }) +export type CustomToolCallInputDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.delta" +} +export const CustomToolCallInputDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.delta") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input is being streamed. Mirrors `response.function_call_arguments.delta` but for `custom` tools whose input is opaque text rather than JSON arguments.", + "identifier": "CustomToolCallInputDeltaEvent" +}) +export type CustomToolCallInputDoneEvent = { + readonly "input": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.custom_tool_call_input.done" +} +export const CustomToolCallInputDoneEvent = Schema.Struct({ + "input": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.custom_tool_call_input.done") +}).annotate({ + "description": + "Event emitted when a custom tool call's freeform input streaming is complete. Mirrors `response.function_call_arguments.done` but for `custom` tools.", + "identifier": "CustomToolCallInputDoneEvent" +}) +export type CustomToolCallItem = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const CustomToolCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments.", + "identifier": "CustomToolCallItem" +}) +export type DABenchmarkEntry = { + readonly "arena": string + readonly "category": string + readonly "elo": number + readonly "rank": number + readonly "win_rate": number +} +export const DABenchmarkEntry = Schema.Struct({ + "arena": Schema.String.annotate({ "description": "Arena type (e.g. models, builders, agents)" }), + "category": Schema.String.annotate({ + "description": "Category within the arena (e.g. website, gamedev, uicomponent)" + }), + "elo": Schema.Number.annotate({ "description": "ELO rating from head-to-head arena battles", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "rank": Schema.Number.annotate({ + "description": "Rank position within this arena+category among models available on OpenRouter (1 = highest ELO)" + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "win_rate": Schema.Number.annotate({ "description": "Win rate percentage in arena battles", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ + "description": "A single Design Arena benchmark entry for a specific arena+category", + "identifier": "DABenchmarkEntry" +}) +export type DatetimeServerToolConfig = { readonly "timezone"?: string } +export const DatetimeServerToolConfig = Schema.Struct({ + "timezone": Schema.optionalKey( + Schema.String.annotate({ "description": "IANA timezone name (e.g. \"America/New_York\"). Defaults to UTC." }) + ) +}).annotate({ + "description": "Configuration for the openrouter:datetime server tool", + "identifier": "DatetimeServerToolConfig" +}) +export type DebugEvent = { + readonly "debug": { + readonly "echo_upstream_body"?: {} + readonly "timings"?: { + readonly "epoch_ms": number + readonly "event": "adapter_request" | "upstream_headers_received" | "first_token_received" | "upstream_body_ended" + readonly "start_ms": number + } + } + readonly "sequence_number": number + readonly "type": "response.debug" +} +export const DebugEvent = Schema.Struct({ + "debug": Schema.Struct({ + "echo_upstream_body": Schema.optionalKey(Schema.Struct({})), + "timings": Schema.optionalKey(Schema.Struct({ + "epoch_ms": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "event": Schema.Literals([ + "adapter_request", + "upstream_headers_received", + "first_token_received", + "upstream_body_ended" + ]), + "start_ms": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + })) + }), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.debug") +}).annotate({ + "description": + "Debug event emitted when debug.echo_upstream_body is true. Contains the transformed upstream request body or timing milestones.", + "identifier": "DebugEvent" +}) +export type DefaultParameters = { + readonly "frequency_penalty"?: number | null + readonly "presence_penalty"?: number | null + readonly "repetition_penalty"?: number | null + readonly "temperature"?: number | null + readonly "top_k"?: number | null + readonly "top_p"?: number | null +} | null +export const DefaultParameters = Schema.Union([ + Schema.Struct({ + "frequency_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "presence_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "repetition_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "top_k": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ) + }), + Schema.Null +]).annotate({ "description": "Default parameters for this model", "identifier": "DefaultParameters" }) +export type DeleteBYOKKeyResponse = { readonly "deleted": true } +export const DeleteBYOKKeyResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the BYOK credential was deleted." }) +}).annotate({ "identifier": "DeleteBYOKKeyResponse" }) +export type DeleteGuardrailResponse = { readonly "deleted": true } +export const DeleteGuardrailResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the guardrail was deleted" }) +}).annotate({ "identifier": "DeleteGuardrailResponse" }) +export type DeleteObservabilityDestinationResponse = { readonly "deleted": true } +export const DeleteObservabilityDestinationResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Always `true` on success." }) +}).annotate({ "identifier": "DeleteObservabilityDestinationResponse" }) +export type DeleteWorkspaceBudgetResponse = { readonly "deleted": true } +export const DeleteWorkspaceBudgetResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ + "description": "Confirmation that the budget was deleted (or did not exist)" + }) +}).annotate({ "identifier": "DeleteWorkspaceBudgetResponse" }) +export type DeleteWorkspaceResponse = { readonly "deleted": true } +export const DeleteWorkspaceResponse = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the workspace was deleted" }) +}).annotate({ "identifier": "DeleteWorkspaceResponse" }) +export type DeprecatedRoute = "fallback" | "sort" | null +export const DeprecatedRoute = Schema.Union([Schema.Literal("fallback"), Schema.Literal("sort"), Schema.Null]).annotate( + { + "description": + "**DEPRECATED** Use providers.sort.partition instead. Backwards-compatible alias for providers.sort.partition. Accepts legacy values: \"fallback\" (maps to \"model\"), \"sort\" (maps to \"none\").", + "identifier": "DeprecatedRoute" + } +) export type EdgeNetworkTimeoutResponseErrorData = { readonly "code": number readonly "message": string - readonly "metadata"?: {} + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } export const EdgeNetworkTimeoutResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for EdgeNetworkTimeoutResponse" }) -export type ProviderOverloadedResponseErrorData = { + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for EdgeNetworkTimeoutResponse", + "identifier": "EdgeNetworkTimeoutResponseErrorData" +}) +export type EndpointInfo = { readonly "model": string; readonly "provider": string; readonly "selected": boolean } +export const EndpointInfo = Schema.Struct({ + "model": Schema.String, + "provider": Schema.String, + "selected": Schema.Boolean +}).annotate({ "identifier": "EndpointInfo" }) +export type EndpointStatus = 0 | -1 | -2 | -3 | -5 | -10 +export const EndpointStatus = Schema.Literals([0, -1, -2, -3, -5, -10]).annotate({ "identifier": "EndpointStatus" }) +export type EnumCapability = { readonly "type": "enum"; readonly "values": ReadonlyArray } +export const EnumCapability = Schema.Struct({ "type": Schema.Literal("enum"), "values": Schema.Array(Schema.String) }) + .annotate({ + "description": "A parameter that accepts one of a discrete set of string values.", + "identifier": "EnumCapability" + }) +export type ErrorEvent = { + readonly "code": string | null + readonly "message": string + readonly "param": string | null + readonly "sequence_number": number + readonly "type": "error" +} +export const ErrorEvent = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "message": Schema.String, + "param": Schema.Union([Schema.String, Schema.Null]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("error") +}).annotate({ "description": "Event emitted when an error occurs during streaming", "identifier": "ErrorEvent" }) +export type FileCitation = { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": "file_citation" +} +export const FileCitation = Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_citation") +}).annotate({ "identifier": "FileCitation" }) +export type FileDeleteResponse = { readonly "id": string; readonly "type": "file_deleted" } +export const FileDeleteResponse = Schema.Struct({ "id": Schema.String, "type": Schema.Literal("file_deleted") }) + .annotate({ "description": "Confirmation that a file was deleted.", "identifier": "FileDeleteResponse" }) +export type FileMetadata = { + readonly "created_at": string + readonly "downloadable": boolean + readonly "filename": string + readonly "id": string + readonly "mime_type": string + readonly "size_bytes": number + readonly "type": "file" +} +export const FileMetadata = Schema.Struct({ + "created_at": Schema.String, + "downloadable": Schema.Boolean, + "filename": Schema.String, + "id": Schema.String, + "mime_type": Schema.String, + "size_bytes": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file") +}).annotate({ "description": "Metadata describing a stored file.", "identifier": "FileMetadata" }) +export type FilePath = { readonly "file_id": string; readonly "index": number; readonly "type": "file_path" } +export const FilePath = Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_path") +}).annotate({ "identifier": "FilePath" }) +export type FilesServerToolConfig = {} +export const FilesServerToolConfig = Schema.Struct({}).annotate({ + "description": "Configuration for the openrouter:files server tool", + "identifier": "FilesServerToolConfig" +}) +export type ForbiddenResponseErrorData = { readonly "code": number readonly "message": string - readonly "metadata"?: {} + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } -export const ProviderOverloadedResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), +export const ForbiddenResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for ProviderOverloadedResponse" }) -export type ResponseInputVideo = { readonly "type": "input_video"; readonly "video_url": string } -export const ResponseInputVideo = Schema.Struct({ - "type": Schema.Literal("input_video"), - "video_url": Schema.String.annotate({ - "description": "A base64 data URL or remote URL that resolves to a video file" + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for ForbiddenResponse", "identifier": "ForbiddenResponseErrorData" }) +export type FormatJsonObjectConfig = { readonly "type": "json_object" } +export const FormatJsonObjectConfig = Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ + "description": "JSON object response format", + "identifier": "FormatJsonObjectConfig" +}) +export type FormatJsonSchemaConfig = { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": "json_schema" +} +export const FormatJsonSchemaConfig = Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("json_schema") +}).annotate({ "description": "JSON schema constrained response format", "identifier": "FormatJsonSchemaConfig" }) +export type FormatTextConfig = { readonly "type": "text" } +export const FormatTextConfig = Schema.Struct({ "type": Schema.Literal("text") }).annotate({ + "description": "Plain text response format", + "identifier": "FormatTextConfig" +}) +export type FrameImage = { + readonly "image_url": { readonly "url": string } + readonly "type": "image_url" + readonly "frame_type": "first_frame" | "last_frame" +} +export const FrameImage = Schema.Struct({ + "image_url": Schema.Struct({ "url": Schema.String }), + "type": Schema.Literal("image_url"), + "frame_type": Schema.Literals(["first_frame", "last_frame"]).annotate({ + "description": "Whether this image represents the first or last frame of the video" }) -}).annotate({ "description": "Video input content item" }) -export type ResponsesOutputModality = "text" | "image" -export const ResponsesOutputModality = Schema.Literals(["text", "image"]) -export type OpenAIResponsesIncludable = - | "file_search_call.results" - | "message.input_image.image_url" - | "computer_call_output.output.image_url" - | "reasoning.encrypted_content" - | "code_interpreter_call.outputs" -export const OpenAIResponsesIncludable = Schema.Literals([ - "file_search_call.results", - "message.input_image.image_url", - "computer_call_output.output.image_url", - "reasoning.encrypted_content", - "code_interpreter_call.outputs" -]) -export type DataCollection = "deny" | "allow" -export const DataCollection = Schema.Literals(["deny", "allow"]).annotate({ - "description": - "Data collection setting. If no available model provider meets the requirement, your request will return an error.\n- allow: (default) allow providers which store user data non-transiently and may train on it\n\n- deny: use only providers which do not collect user data." +}).annotate({ "identifier": "FrameImage" }) +export type FunctionCallArgsDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.function_call_arguments.delta" +} +export const FunctionCallArgsDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.delta") +}).annotate({ + "description": "Event emitted when function call arguments are being streamed", + "identifier": "FunctionCallArgsDeltaEvent" }) -export type ProviderName = - | "AI21" - | "AionLabs" - | "Alibaba" - | "Ambient" - | "Amazon Bedrock" - | "Amazon Nova" - | "Anthropic" - | "Arcee AI" - | "AtlasCloud" - | "Avian" - | "Azure" - | "BaseTen" - | "BytePlus" - | "Black Forest Labs" - | "Cerebras" - | "Chutes" - | "Cirrascale" - | "Clarifai" - | "Cloudflare" - | "Cohere" - | "Crusoe" - | "DeepInfra" - | "DeepSeek" - | "Featherless" - | "Fireworks" - | "Friendli" - | "GMICloud" - | "Google" - | "Google AI Studio" - | "Groq" - | "Hyperbolic" - | "Inception" - | "Inceptron" - | "InferenceNet" - | "Infermatic" - | "Io Net" - | "Inflection" - | "Liquid" - | "Mara" - | "Mancer 2" - | "Minimax" - | "ModelRun" - | "Mistral" - | "Modular" - | "Moonshot AI" - | "Morph" - | "NCompass" - | "Nebius" - | "NextBit" - | "Novita" - | "Nvidia" - | "OpenAI" - | "OpenInference" - | "Parasail" - | "Perplexity" - | "Phala" - | "Relace" - | "SambaNova" - | "Seed" - | "SiliconFlow" - | "Sourceful" - | "StepFun" - | "Stealth" - | "StreamLake" - | "Switchpoint" - | "Together" - | "Upstage" - | "Venice" - | "WandB" - | "Xiaomi" - | "xAI" - | "Z.AI" - | "FakeProvider" -export const ProviderName = Schema.Literals([ - "AI21", - "AionLabs", - "Alibaba", - "Ambient", - "Amazon Bedrock", - "Amazon Nova", - "Anthropic", - "Arcee AI", - "AtlasCloud", - "Avian", - "Azure", - "BaseTen", - "BytePlus", - "Black Forest Labs", - "Cerebras", - "Chutes", - "Cirrascale", - "Clarifai", - "Cloudflare", - "Cohere", - "Crusoe", - "DeepInfra", - "DeepSeek", - "Featherless", - "Fireworks", - "Friendli", - "GMICloud", - "Google", - "Google AI Studio", - "Groq", - "Hyperbolic", - "Inception", - "Inceptron", - "InferenceNet", - "Infermatic", - "Io Net", - "Inflection", - "Liquid", - "Mara", - "Mancer 2", - "Minimax", - "ModelRun", - "Mistral", - "Modular", - "Moonshot AI", - "Morph", - "NCompass", - "Nebius", - "NextBit", - "Novita", - "Nvidia", - "OpenAI", - "OpenInference", - "Parasail", - "Perplexity", - "Phala", - "Relace", - "SambaNova", - "Seed", - "SiliconFlow", - "Sourceful", - "StepFun", - "Stealth", - "StreamLake", - "Switchpoint", - "Together", - "Upstage", - "Venice", - "WandB", - "Xiaomi", - "xAI", - "Z.AI", - "FakeProvider" -]) -export type Quantization = "int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown" -export const Quantization = Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"]) -export type ProviderSort = "price" | "throughput" | "latency" -export const ProviderSort = Schema.Literals(["price", "throughput", "latency"]) -export type BigNumberUnion = string -export const BigNumberUnion = Schema.String.annotate({ "description": "Price per million prompt tokens" }) -export type PercentileThroughputCutoffs = { - readonly "p50"?: number - readonly "p75"?: number - readonly "p90"?: number - readonly "p99"?: number +export type FunctionCallArgsDoneEvent = { + readonly "arguments": string + readonly "item_id": string + readonly "name": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.function_call_arguments.done" } -export const PercentileThroughputCutoffs = Schema.Struct({ - "p50": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p50 throughput (tokens/sec)" }).check(Schema.isFinite()) - ), - "p75": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p75 throughput (tokens/sec)" }).check(Schema.isFinite()) - ), - "p90": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p90 throughput (tokens/sec)" }).check(Schema.isFinite()) - ), - "p99": Schema.optionalKey( - Schema.Number.annotate({ "description": "Minimum p99 throughput (tokens/sec)" }).check(Schema.isFinite()) - ) +export const FunctionCallArgsDoneEvent = Schema.Struct({ + "arguments": Schema.String, + "item_id": Schema.String, + "name": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.function_call_arguments.done") }).annotate({ - "description": - "Percentile-based throughput cutoffs. All specified cutoffs must be met for an endpoint to be preferred." + "description": "Event emitted when function call arguments streaming is complete", + "identifier": "FunctionCallArgsDoneEvent" }) -export type PercentileLatencyCutoffs = { - readonly "p50"?: number - readonly "p75"?: number - readonly "p90"?: number - readonly "p99"?: number +export type FunctionTool = { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" } -export const PercentileLatencyCutoffs = Schema.Struct({ - "p50": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p50 latency (seconds)" }).check(Schema.isFinite()) - ), - "p75": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p75 latency (seconds)" }).check(Schema.isFinite()) - ), - "p90": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p90 latency (seconds)" }).check(Schema.isFinite()) - ), - "p99": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum p99 latency (seconds)" }).check(Schema.isFinite()) - ) +export const FunctionTool = Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") +}).annotate({ "description": "Function tool definition", "identifier": "FunctionTool" }) +export type Arrays_1 = ReadonlyArray +export const Arrays_1 = Schema.Array(Schema.String) +export type Arrays_2 = ReadonlyArray +export const Arrays_2 = Schema.Array(Schema.String) +export type Arrays_3 = ReadonlyArray< + { + readonly "stances": ReadonlyArray<{ readonly "model": string; readonly "stance": string }> + readonly "topic": string + } +> +export const Arrays_3 = Schema.Array( + Schema.Struct({ + "stances": Schema.Array(Schema.Struct({ "model": Schema.String, "stance": Schema.String })), + "topic": Schema.String + }) +) +export type Arrays_4 = ReadonlyArray<{ readonly "models": ReadonlyArray; readonly "point": string }> +export const Arrays_4 = Schema.Array(Schema.Struct({ "models": Schema.Array(Schema.String), "point": Schema.String })) +export type Arrays_5 = ReadonlyArray<{ readonly "insight": string; readonly "model": string }> +export const Arrays_5 = Schema.Array(Schema.Struct({ "insight": Schema.String, "model": Schema.String })) +export type FusionCallAnalysisInProgressEvent = { + readonly "item_id": string + readonly "judge_model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.analysis.in_progress" +} +export const FusionCallAnalysisInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "judge_model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.analysis.in_progress") }).annotate({ - "description": "Percentile-based latency cutoffs. All specified cutoffs must be met for an endpoint to be preferred." + "description": "Emitted when the fusion judge starts producing the structured analysis.", + "identifier": "FusionCallAnalysisInProgressEvent" }) -export type WebSearchEngine = "native" | "exa" -export const WebSearchEngine = Schema.Literals(["native", "exa"]).annotate({ - "description": "The search engine to use for web search." +export type FusionCallCompletedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.completed" +} +export const FusionCallCompletedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.completed") +}).annotate({ + "description": "Emitted when the openrouter:fusion tool call finishes.", + "identifier": "FusionCallCompletedEvent" }) -export type PDFParserEngine = "mistral-ocr" | "pdf-text" | "native" -export const PDFParserEngine = Schema.Literals(["mistral-ocr", "pdf-text", "native"]).annotate({ - "description": "The engine to use for parsing PDF files." +export type FusionCallInProgressEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.in_progress" +} +export const FusionCallInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.in_progress") +}).annotate({ + "description": "Emitted when an openrouter:fusion tool call begins executing.", + "identifier": "FusionCallInProgressEvent" }) -export type AnthropicMessagesResponse = { - readonly "id": string - readonly "type": "message" - readonly "role": "assistant" - readonly "content": ReadonlyArray< - | { - readonly "type": "text" - readonly "text": string - readonly "citations": ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - } - | { readonly "type": "tool_use"; readonly "id": string; readonly "name": string; readonly "input"?: unknown } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "page_age": string - readonly "title": string - readonly "url": string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - } - > +export type FusionCallPanelAddedEvent = { + readonly "item_id": string readonly "model": string - readonly "stop_reason": "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" - readonly "stop_sequence": string - readonly "usage": { - readonly "input_tokens": number - readonly "output_tokens": number - readonly "cache_creation_input_tokens": number - readonly "cache_read_input_tokens": number - readonly "cache_creation": { - readonly "ephemeral_5m_input_tokens": number - readonly "ephemeral_1h_input_tokens": number - } - readonly "inference_geo": string - readonly "server_tool_use": { readonly "web_search_requests": number } - readonly "service_tier": "standard" | "priority" | "batch" - } + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.added" } -export const AnthropicMessagesResponse = Schema.Struct({ - "id": Schema.String, - "type": Schema.Literal("message"), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) +export const FusionCallPanelAddedEvent = Schema.Struct({ + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.added") +}).annotate({ + "description": "Emitted when a fusion analysis-panel model starts.", + "identifier": "FusionCallPanelAddedEvent" +}) +export type FusionCallPanelCompletedEvent = { + readonly "content": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.completed" +} +export const FusionCallPanelCompletedEvent = Schema.Struct({ + "content": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.completed") +}).annotate({ + "description": "Emitted when a fusion panel model finishes with its full content.", + "identifier": "FusionCallPanelCompletedEvent" +}) +export type FusionCallPanelDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.delta" +} +export const FusionCallPanelDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.delta") +}).annotate({ + "description": "Incremental content token from a fusion panel model.", + "identifier": "FusionCallPanelDeltaEvent" +}) +export type FusionCallPanelFailedEvent = { + readonly "error": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "status_code"?: number + readonly "type": "response.fusion_call.panel.failed" +} +export const FusionCallPanelFailedEvent = Schema.Struct({ + "error": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "status_code": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.Literal("response.fusion_call.panel.failed") +}).annotate({ "description": "Emitted when a fusion panel model fails.", "identifier": "FusionCallPanelFailedEvent" }) +export type FusionCallPanelReasoningDeltaEvent = { + readonly "delta": string + readonly "item_id": string + readonly "model": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.panel.reasoning.delta" +} +export const FusionCallPanelReasoningDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "model": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.panel.reasoning.delta") +}).annotate({ + "description": "Incremental reasoning token from a fusion panel model.", + "identifier": "FusionCallPanelReasoningDeltaEvent" +}) +export type FusionPlugin = { + readonly "analysis_models"?: ReadonlyArray + readonly "enabled"?: boolean + readonly "id": "fusion" + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "preset"?: "general-high" | "general-budget" | "general-fast" + readonly "tools"?: ReadonlyArray<{ readonly "parameters"?: {}; readonly "type": string }> +} +export const FusionPlugin = Schema.Struct({ + "analysis_models": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Slugs of models to run in parallel as the \"expert panel\" the judge analyzes. Each model receives the same user prompt with web_search + web_fetch enabled. Capped at 8 models to bound cost amplification. When omitted, defaults to the Quality preset from the /labs/fusion UI (~anthropic/claude-opus-latest, ~openai/gpt-latest, ~google/gemini-pro-latest)." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" }) + ) + ), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the fusion plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("fusion"), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps each panelist (analysis model) and the judge model may take during their agentic web-research loop. Models with web_search/web_fetch enabled iterate until they produce a text response or hit this ceiling. Defaults to 8. Capped at 16." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(16).annotate({ "expected": "a value less than or equal to 16" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the model that performs both the judge step (with web_search + web_fetch) and the final synthesis. When omitted, defaults to the first model in the Quality preset." + }) + ), + "preset": Schema.optionalKey( + Schema.Literals(["general-high", "general-budget", "general-fast"]).annotate({ + "description": + "A curated OpenRouter fusion preset (slugs follow `-`, e.g. `general-high`). Expands server-side into the preset's analysis_models panel and judge model, so callers never name individual models. Explicitly provided `analysis_models` / `model` take precedence." + }) + ), + "tools": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": "Optional configuration forwarded as the tool's `parameters` object." }) - ], { mode: "oneOf" })) - }), + ), + "type": Schema.String.annotate({ + "description": "Server tool type identifier (e.g. \"openrouter:web_search\", \"openrouter:web_fetch\")." + }) + })).annotate({ + "description": + "Server tools available to panelist and judge inner calls. Each entry uses the same `{ type, parameters? }` shorthand as the outer Chat Completions request. When omitted, defaults to `[{ type: \"openrouter:web_search\" }, { type: \"openrouter:web_fetch\" }]`. Pass an empty array to disable tools entirely (panelists answer from parametric knowledge only)." + }).check(Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" })) + ) +}).annotate({ "identifier": "FusionPlugin" }) +export type Arrays_6 = ReadonlyArray +export const Arrays_6 = Schema.Array(Schema.String).annotate({ + "description": + "Slugs of models to run in parallel as the analysis panel. Each model receives the user prompt with openrouter:web_search and openrouter:web_fetch enabled, then a judge model summarizes the collective output into structured analysis JSON. Capped at 8 models to bound cost amplification. Defaults to the Quality preset from /labs/fusion." +}).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" }) +) +export type Objects_9 = { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" + readonly "max_tokens"?: number +} +export const Objects_9 = Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Literals(["max", "xhigh", "high", "medium", "low", "minimal", "none"]).annotate({ + "description": "Reasoning effort level for panelist and judge inner calls." + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of reasoning tokens each panelist and judge model may use. Helps bound cost when models allocate too much budget to chain-of-thought." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) +}).annotate({ + "description": + "Reasoning configuration forwarded to panelist and judge inner calls. Use this to control reasoning effort and token budget for models that support extended thinking." +}) +export type Arrays_7 = ReadonlyArray<{ readonly "parameters"?: {}; readonly "type": string }> +export const Arrays_7 = Schema.Array(Schema.Struct({ + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Optional configuration forwarded as the tool's `parameters` object." }) + ), + "type": Schema.String.annotate({ + "description": "Server tool type identifier (e.g. \"openrouter:web_search\", \"openrouter:web_fetch\")." + }) +})).annotate({ + "description": + "Server tools available to panelist and judge inner calls. Each entry uses the same `{ type, parameters? }` shorthand as the outer Chat Completions request. When omitted, defaults to `[{ type: \"openrouter:web_search\" }, { type: \"openrouter:web_fetch\" }]`. Pass an empty array to disable tools entirely (panelists answer from parametric knowledge only)." +}).check(Schema.isMaxLength(8).annotate({ "expected": "a value with a length of at most 8" })) +export type FusionSource = { readonly "title": string; readonly "url": string } +export const FusionSource = Schema.Struct({ + "title": Schema.String.annotate({ "description": "Title of the retrieved web page." }), + "url": Schema.String.annotate({ + "description": "URL of the web page a panel or the judge retrieved during the run." + }) +}).annotate({ "description": "A web page retrieved via web search during a fusion run.", "identifier": "FusionSource" }) +export type GenerationContentData = { + readonly "input": { readonly "prompt": string } | { readonly "messages": ReadonlyArray } + readonly "output": { readonly "completion": string | null; readonly "reasoning": string | null } +} +export const GenerationContentData = Schema.Struct({ + "input": Schema.Union([ + Schema.Struct({ "prompt": Schema.String }), + Schema.Struct({ "messages": Schema.Array(Schema.Json.annotate({ "expected": "JSON value" })) }) + ]).annotate({ "description": "The input to the generation — either a prompt string or an array of messages" }), + "output": Schema.Struct({ + "completion": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "The completion output" }), + "reasoning": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Reasoning/thinking output, if any" + }) + }).annotate({ "description": "The output from the generation" }) +}).annotate({ "description": "Stored prompt and completion content", "identifier": "GenerationContentData" }) +export type GoneResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const GoneResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for GoneResponse", "identifier": "GoneResponseErrorData" }) +export type GuardrailInterval = "daily" | "weekly" | "monthly" | null +export const GuardrailInterval = Schema.Union([ + Schema.Literal("daily"), + Schema.Literal("weekly"), + Schema.Literal("monthly"), + Schema.Null +]).annotate({ + "description": "Interval at which the limit resets (daily, weekly, monthly)", + "identifier": "GuardrailInterval" +}) +export type ImageConfig = {} +export const ImageConfig = Schema.Struct({}).annotate({ + "description": + "Provider-specific image configuration options. Keys and values vary by model/provider. See https://openrouter.ai/docs/guides/overview/multimodal/image-generation for more details.", + "identifier": "ImageConfig" +}) +export type ImageGenCallCompletedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.completed" +} +export const ImageGenCallCompletedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.completed") +}).annotate({ "description": "Image generation call completed", "identifier": "ImageGenCallCompletedEvent" }) +export type ImageGenCallGeneratingEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.generating" +} +export const ImageGenCallGeneratingEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.generating") +}).annotate({ "description": "Image generation call is generating", "identifier": "ImageGenCallGeneratingEvent" }) +export type ImageGenCallInProgressEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.in_progress" +} +export const ImageGenCallInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.in_progress") +}).annotate({ "description": "Image generation call in progress", "identifier": "ImageGenCallInProgressEvent" }) +export type ImageGenCallPartialImageEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "partial_image_b64": string + readonly "partial_image_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.partial_image" +} +export const ImageGenCallPartialImageEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "partial_image_b64": Schema.String, + "partial_image_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.partial_image") +}).annotate({ + "description": "Image generation call with partial image", + "identifier": "ImageGenCallPartialImageEvent" +}) +export type ImageGenerationServerTool = { + readonly "background"?: "transparent" | "opaque" | "auto" + readonly "input_fidelity"?: "high" | "low" | null + readonly "input_image_mask"?: { readonly "file_id"?: string; readonly "image_url"?: string } + readonly "model"?: string + readonly "moderation"?: "auto" | "low" + readonly "output_compression"?: number + readonly "output_format"?: "png" | "webp" | "jpeg" + readonly "partial_images"?: number + readonly "quality"?: "low" | "medium" | "high" | "auto" + readonly "size"?: string + readonly "type": "image_generation" +} +export const ImageGenerationServerTool = Schema.Struct({ + "background": Schema.optionalKey(Schema.Literals(["transparent", "opaque", "auto"])), + "input_fidelity": Schema.optionalKey(Schema.Union([Schema.Literal("high"), Schema.Literal("low"), Schema.Null])), + "input_image_mask": Schema.optionalKey( + Schema.Struct({ "file_id": Schema.optionalKey(Schema.String), "image_url": Schema.optionalKey(Schema.String) }) + ), + "model": Schema.optionalKey(Schema.String), + "moderation": Schema.optionalKey(Schema.Literals(["auto", "low"])), + "output_compression": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_format": Schema.optionalKey(Schema.Literals(["png", "webp", "jpeg"])), + "partial_images": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "quality": Schema.optionalKey(Schema.Literals(["low", "medium", "high", "auto"])), + "size": Schema.optionalKey(Schema.String), + "type": Schema.Literal("image_generation") +}).annotate({ "description": "Image generation tool configuration", "identifier": "ImageGenerationServerTool" }) +export type ImageGenerationServerToolConfig = { readonly "model"?: string } +export const ImageGenerationServerToolConfig = Schema.Struct({ + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Which image generation model to use (e.g. \"openai/gpt-5-image\"). Defaults to \"openai/gpt-5-image\"." + }) + ) +}).annotate({ + "description": + "Configuration for the openrouter:image_generation server tool. Accepts all image_config params (aspect_ratio, quality, size, background, output_format, output_compression, moderation, etc.) plus a model field.", + "identifier": "ImageGenerationServerToolConfig" +}) +export type ImageGenerationStatus = "in_progress" | "completed" | "generating" | "failed" +export const ImageGenerationStatus = Schema.Literals(["in_progress", "completed", "generating", "failed"]).annotate({ + "identifier": "ImageGenerationStatus" +}) +export type Union_6 = { + readonly "audio_tokens"?: number | null + readonly "image_tokens"?: number | null + readonly "reasoning_tokens"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_6 = Schema.Union([ + Schema.StructWithRest( Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown) + "audio_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens generated by the model for audio output." }) + ), + "image_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens generated by the model for image output." }) + ), + "reasoning_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens generated by the model for reasoning." }) + ) }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]) +export type Union_8 = { + readonly "audio_tokens"?: number | null + readonly "cache_write_tokens"?: number | null + readonly "cached_tokens"?: number | null + readonly "file_tokens"?: number | null + readonly "video_tokens"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_8 = Schema.Union([ + Schema.StructWithRest( Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown) + "audio_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for input audio." }) + ), + "cache_write_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ + "description": + "Tokens written to cache. Only returned for models with explicit caching and cache write pricing." + }) + ), + "cached_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens cached by the endpoint." }) + ), + "file_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for input files/documents." }) + ), + "video_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Tokens used for input video." }) + ) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Breakdown of tokens used in the prompt." }) +export type Union_9 = { + readonly "tool_calls_executed"?: number | null + readonly "tool_calls_requested"?: number | null + readonly "web_search_requests"?: number | null + readonly [x: string]: Schema.Json +} | null +export const Union_9 = Schema.Union([ + Schema.StructWithRest( Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "page_age": Schema.String, - "title": Schema.String, - "url": Schema.String + "tool_calls_executed": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "Number of OpenRouter server tool calls that executed and produced a result." }) + ), + "tool_calls_requested": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ + "description": + "Total number of OpenRouter server-orchestrated tool calls the model requested, across all tool types. Provider-native tools (e.g. native web search) are not counted here." }) - ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) - ]) - }) - ], { mode: "oneOf" })), - "model": Schema.String, - "stop_reason": Schema.Literals(["end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal"]), - "stop_sequence": Schema.String, - "usage": Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_read_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation": Schema.Struct({ - "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isFinite()), - "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isFinite()) + ), + "web_search_requests": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ + "description": + "Number of web searches performed by server-side tools. For server-orchestrated tool calls a web search is also counted in tool_calls_requested; provider-native web search may report web_search_requests only. Do not sum the two." + }) + ) }), - "inference_geo": Schema.String, - "server_tool_use": Schema.Struct({ "web_search_requests": Schema.Number.check(Schema.isFinite()) }), - "service_tier": Schema.Literals(["standard", "priority", "batch"]) - }) -}).annotate({ "description": "Non-streaming response from the Anthropic Messages API with OpenRouter extensions" }) -export type AnthropicMessagesStreamEvent = - | { - readonly "type": "message_start" - readonly "message": { - readonly "id": string - readonly "type": "message" - readonly "role": "assistant" - readonly "content": ReadonlyArray< - | { - readonly "type": "text" - readonly "text": string - readonly "citations": ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - } - | { readonly "type": "tool_use"; readonly "id": string; readonly "name": string; readonly "input"?: unknown } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "page_age": string - readonly "title": string - readonly "url": string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - } - > - readonly "model": string - readonly "stop_reason": unknown - readonly "stop_sequence": unknown - readonly "usage": { - readonly "input_tokens": number - readonly "output_tokens": number - readonly "cache_creation_input_tokens": number - readonly "cache_read_input_tokens": number - readonly "cache_creation": { - readonly "ephemeral_5m_input_tokens": number - readonly "ephemeral_1h_input_tokens": number - } - readonly "inference_geo": string - readonly "server_tool_use": { readonly "web_search_requests": number } - readonly "service_tier": "standard" | "priority" | "batch" - } - } - } - | { - readonly "type": "message_delta" - readonly "delta": { - readonly "stop_reason": "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" - readonly "stop_sequence": string - } - readonly "usage": { - readonly "input_tokens": number - readonly "output_tokens": number - readonly "cache_creation_input_tokens": number - readonly "cache_read_input_tokens": number - readonly "server_tool_use": { readonly "web_search_requests": number } - } - } - | { readonly "type": "message_stop" } - | { - readonly "type": "content_block_start" - readonly "index": number - readonly "content_block": - | { - readonly "type": "text" - readonly "text": string - readonly "citations": ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - } - | { readonly "type": "tool_use"; readonly "id": string; readonly "name": string; readonly "input"?: unknown } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "page_age": string - readonly "title": string - readonly "url": string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - } - } - | { - readonly "type": "content_block_delta" - readonly "index": number - readonly "delta": - | { readonly "type": "text_delta"; readonly "text": string } - | { readonly "type": "input_json_delta"; readonly "partial_json": string } - | { readonly "type": "thinking_delta"; readonly "thinking": string } - | { readonly "type": "signature_delta"; readonly "signature": string } - | { - readonly "type": "citations_delta" - readonly "citation": { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - readonly "file_id": string - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - readonly "file_id": string - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - readonly "file_id": string - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - } + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Usage for server-side tool execution (e.g., web search)" }) +export type ImageGenPartialImageEvent = { + readonly "b64_json": string + readonly "partial_image_index": number + readonly "type": "image_generation.partial_image" +} +export const ImageGenPartialImageEvent = Schema.Struct({ + "b64_json": Schema.String.annotate({ "description": "Base64-encoded partial image data" }), + "partial_image_index": Schema.Number.annotate({ + "description": "0-based index indicating which partial image this is in the sequence" + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("image_generation.partial_image").annotate({ "description": "The event type" }) +}).annotate({ + "description": "Emitted when a partial image becomes available during streaming generation", + "identifier": "ImageGenPartialImageEvent" +}) +export type ImageGenStreamErrorEvent = { + readonly "error": { + readonly "code"?: string | null + readonly "message": string + readonly "param"?: string | null + readonly "type"?: string | null } - | { readonly "type": "content_block_stop"; readonly "index": number } - | { readonly "type": "ping" } - | { readonly "type": "error"; readonly "error": { readonly "type": string; readonly "message": string } } -export const AnthropicMessagesStreamEvent = Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("message_start"), - "message": Schema.Struct({ - "id": Schema.String, - "type": Schema.Literal("message"), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" })) - }), - Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown) - }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), - Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "page_age": Schema.String, - "title": Schema.String, - "url": Schema.String - }) - ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) - ]) - }) - ], { mode: "oneOf" })), - "model": Schema.String, - "stop_reason": Schema.Unknown, - "stop_sequence": Schema.Unknown, - "usage": Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_read_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation": Schema.Struct({ - "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isFinite()), - "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isFinite()) - }), - "inference_geo": Schema.String, - "server_tool_use": Schema.Struct({ "web_search_requests": Schema.Number.check(Schema.isFinite()) }), - "service_tier": Schema.Literals(["standard", "priority", "batch"]) + readonly "type": "error" +} +export const ImageGenStreamErrorEvent = Schema.Struct({ + "error": Schema.Struct({ + "code": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Provider error code, when supplied" }) + ), + "message": Schema.String.annotate({ "description": "Provider error message" }), + "param": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Request parameter associated with the error, when supplied" }) - }) + ), + "type": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Provider error type, when supplied" }) + ) + }).annotate({ "description": "Provider error details" }), + "type": Schema.Literal("error").annotate({ "description": "The event type" }) +}).annotate({ + "description": "Emitted when streaming generation fails after the SSE response starts", + "identifier": "ImageGenStreamErrorEvent" +}) +export type ImageGenTextChunkEvent = { + readonly "phase": "content" | "reasoning" | "draft" + readonly "text": string + readonly "type": "image_generation.text_chunk" +} +export const ImageGenTextChunkEvent = Schema.Struct({ + "phase": Schema.Literals(["content", "reasoning", "draft"]).annotate({ + "description": + "The generation phase this chunk belongs to. `content` is the renderable output; `reasoning` and `draft` are intermediate provider phases." }), - Schema.Struct({ - "type": Schema.Literal("message_delta"), - "delta": Schema.Struct({ - "stop_reason": Schema.Literals(["end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal"]), - "stop_sequence": Schema.String - }), - "usage": Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "cache_creation_input_tokens": Schema.Number.check(Schema.isFinite()), - "cache_read_input_tokens": Schema.Number.check(Schema.isFinite()), - "server_tool_use": Schema.Struct({ "web_search_requests": Schema.Number.check(Schema.isFinite()) }) - }) + "text": Schema.String.annotate({ + "description": "A text fragment of the image being generated (e.g. partial SVG markup)" }), - Schema.Struct({ "type": Schema.Literal("message_stop") }), - Schema.Struct({ - "type": Schema.Literal("content_block_start"), - "index": Schema.Number.check(Schema.isFinite()), - "content_block": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" })) - }), + "type": Schema.Literal("image_generation.text_chunk").annotate({ "description": "The event type" }) +}).annotate({ + "description": + "Emitted when a text chunk becomes available during streaming generation of text-based formats (e.g. SVG)", + "identifier": "ImageGenTextChunkEvent" +}) +export type ImageInputModality = "text" | "image" | "file" | "audio" | "video" +export const ImageInputModality = Schema.Literals(["text", "image", "file", "audio", "video"]).annotate({ + "identifier": "ImageInputModality" +}) +export type ImageOutputModality = + | "text" + | "image" + | "embeddings" + | "audio" + | "video" + | "rerank" + | "speech" + | "transcription" +export const ImageOutputModality = Schema.Literals([ + "text", + "image", + "embeddings", + "audio", + "video", + "rerank", + "speech", + "transcription" +]).annotate({ "identifier": "ImageOutputModality" }) +export type ImagePricingEntry = { + readonly "billable": "output_image" | "input_image" | "input_font" | "input_reference" | "input_text" + readonly "cost_usd": number + readonly "unit": "image" | "megapixel" | "token" + readonly "variant"?: string +} +export const ImagePricingEntry = Schema.Struct({ + "billable": Schema.Literals(["output_image", "input_image", "input_font", "input_reference", "input_text"]), + "cost_usd": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "unit": Schema.Literals(["image", "megapixel", "token"]), + "variant": Schema.optionalKey(Schema.String) +}).annotate({ "description": "One billable pricing line for an image provider.", "identifier": "ImagePricingEntry" }) +export type IncompleteDetails = { + readonly "reason"?: "max_output_tokens" | "content_filter" + readonly [x: string]: Schema.Json +} | null +export const IncompleteDetails = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ "reason": Schema.optionalKey(Schema.Literals(["max_output_tokens", "content_filter"])) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "identifier": "IncompleteDetails" }) +export type InputAudio = { + readonly "input_audio": { readonly "data": string; readonly "format": "mp3" | "wav" } + readonly "type": "input_audio" +} +export const InputAudio = Schema.Struct({ + "input_audio": Schema.Struct({ "data": Schema.String, "format": Schema.Literals(["mp3", "wav"]) }), + "type": Schema.Literal("input_audio") +}).annotate({ "description": "Audio input content item", "identifier": "InputAudio" }) +export type InputFile = { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": "input_file" +} +export const InputFile = Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Literal("input_file") +}).annotate({ "description": "File input content item", "identifier": "InputFile" }) +export type InputImage = { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" +} +export const InputImage = Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") +}).annotate({ "description": "Image input content item", "identifier": "InputImage" }) +export type InputModality = "text" | "image" | "file" | "audio" | "video" +export const InputModality = Schema.Literals(["text", "image", "file", "audio", "video"]).annotate({ + "identifier": "InputModality" +}) +export type InputVideo = { readonly "type": "input_video"; readonly "video_url": string } +export const InputVideo = Schema.Struct({ + "type": Schema.Literal("input_video"), + "video_url": Schema.String.annotate({ + "description": "A base64 data URL or remote URL that resolves to a video file" + }) +}).annotate({ "description": "Video input content item", "identifier": "InputVideo" }) +export type InstructType = + | "none" + | "airoboros" + | "alpaca" + | "alpaca-modif" + | "chatml" + | "claude" + | "code-llama" + | "gemma" + | "llama2" + | "llama3" + | "mistral" + | "nemotron" + | "neural" + | "openchat" + | "phi3" + | "rwkv" + | "vicuna" + | "zephyr" + | "deepseek-r1" + | "deepseek-v3.1" + | "qwq" + | "qwen3" + | null +export const InstructType = Schema.Union([ + Schema.Literal("none"), + Schema.Literal("airoboros"), + Schema.Literal("alpaca"), + Schema.Literal("alpaca-modif"), + Schema.Literal("chatml"), + Schema.Literal("claude"), + Schema.Literal("code-llama"), + Schema.Literal("gemma"), + Schema.Literal("llama2"), + Schema.Literal("llama3"), + Schema.Literal("mistral"), + Schema.Literal("nemotron"), + Schema.Literal("neural"), + Schema.Literal("openchat"), + Schema.Literal("phi3"), + Schema.Literal("rwkv"), + Schema.Literal("vicuna"), + Schema.Literal("zephyr"), + Schema.Literal("deepseek-r1"), + Schema.Literal("deepseek-v3.1"), + Schema.Literal("qwq"), + Schema.Literal("qwen3"), + Schema.Null +]).annotate({ "description": "Instruction format type", "identifier": "InstructType" }) +export type InternalServerResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const InternalServerResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for InternalServerResponse", "identifier": "InternalServerResponseErrorData" }) +export type ItemReferenceItem = { readonly "id": string; readonly "type": "item_reference" } +export const ItemReferenceItem = Schema.Struct({ "id": Schema.String, "type": Schema.Literal("item_reference") }) + .annotate({ "description": "A reference to a previous response item by ID", "identifier": "ItemReferenceItem" }) +export type KeyAssignment = { + readonly "assigned_by": string | null + readonly "created_at": string + readonly "guardrail_id": string + readonly "id": string + readonly "key_hash": string + readonly "key_label": string + readonly "key_name": string +} +export const KeyAssignment = Schema.Struct({ + "assigned_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID of who made the assignment" + }), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }), + "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), + "key_hash": Schema.String.annotate({ "description": "Hash of the assigned API key" }), + "key_label": Schema.String.annotate({ "description": "Label of the API key" }), + "key_name": Schema.String.annotate({ "description": "Name of the API key" }) +}).annotate({ "identifier": "KeyAssignment" }) +export type Legacy_ChatContentVideoInput = { readonly "url": string } +export const Legacy_ChatContentVideoInput = Schema.Struct({ + "url": Schema.String.annotate({ "description": "URL of the video (data: URLs supported)" }) +}).annotate({ "description": "Video input object", "identifier": "Legacy_ChatContentVideoInput" }) +export type McpApprovalRequestItem = { + readonly "arguments": string + readonly "id": string + readonly "name": string + readonly "server_label": string + readonly "type": "mcp_approval_request" +} +export const McpApprovalRequestItem = Schema.Struct({ + "arguments": Schema.String, + "id": Schema.String, + "name": Schema.String, + "server_label": Schema.String, + "type": Schema.Literal("mcp_approval_request") +}).annotate({ "description": "Request for approval to execute an MCP tool", "identifier": "McpApprovalRequestItem" }) +export type McpApprovalResponseItem = { + readonly "approval_request_id": string + readonly "approve": boolean + readonly "id"?: string | null + readonly "reason"?: string | null + readonly "type": "mcp_approval_response" +} +export const McpApprovalResponseItem = Schema.Struct({ + "approval_request_id": Schema.String, + "approve": Schema.Boolean, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "reason": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("mcp_approval_response") +}).annotate({ "description": "User response to an MCP tool approval request", "identifier": "McpApprovalResponseItem" }) +export type McpCallItem = { + readonly "arguments": string + readonly "error"?: string | null + readonly "id": string + readonly "name": string + readonly "output"?: string | null + readonly "server_label": string + readonly "type": "mcp_call" +} +export const McpCallItem = Schema.Struct({ + "arguments": Schema.String, + "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "name": Schema.String, + "output": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "server_label": Schema.String, + "type": Schema.Literal("mcp_call") +}).annotate({ "description": "An MCP tool call with its output or error", "identifier": "McpCallItem" }) +export type McpListToolsItem = { + readonly "error"?: string | null + readonly "id": string + readonly "server_label": string + readonly "tools": ReadonlyArray< + { + readonly "annotations"?: Schema.Json + readonly "description"?: string | null + readonly "input_schema": {} + readonly "name": string + } + > + readonly "type": "mcp_list_tools" +} +export const McpListToolsItem = Schema.Struct({ + "error": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "server_label": Schema.String, + "tools": Schema.Array( + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "input_schema": Schema.Struct({}), + "name": Schema.String + }) + ), + "type": Schema.Literal("mcp_list_tools") +}).annotate({ "description": "List of available MCP tools from a server", "identifier": "McpListToolsItem" }) +export type McpServerTool = { + readonly "allowed_tools"?: ReadonlyArray | { + readonly "read_only"?: boolean + readonly "tool_names"?: ReadonlyArray + } | null + readonly "authorization"?: string + readonly "connector_id"?: + | "connector_dropbox" + | "connector_gmail" + | "connector_googlecalendar" + | "connector_googledrive" + | "connector_microsoftteams" + | "connector_outlookcalendar" + | "connector_outlookemail" + | "connector_sharepoint" + readonly "headers"?: { readonly [x: string]: string } | null + readonly "require_approval"?: + | { + readonly "always"?: { readonly "tool_names"?: ReadonlyArray } + readonly "never"?: { readonly "tool_names"?: ReadonlyArray } + } + | "always" + | "never" + | null + readonly "server_description"?: string + readonly "server_label": string + readonly "server_url"?: string + readonly "type": "mcp" +} +export const McpServerTool = Schema.Struct({ + "allowed_tools": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String), Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown) + "read_only": Schema.optionalKey(Schema.Boolean), + "tool_names": Schema.optionalKey(Schema.Array(Schema.String)) }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), + Schema.Null + ]) + ), + "authorization": Schema.optionalKey(Schema.String), + "connector_id": Schema.optionalKey( + Schema.Literals([ + "connector_dropbox", + "connector_gmail", + "connector_googlecalendar", + "connector_googledrive", + "connector_microsoftteams", + "connector_outlookcalendar", + "connector_outlookemail", + "connector_sharepoint" + ]) + ), + "headers": Schema.optionalKey(Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null])), + "require_approval": Schema.optionalKey( + Schema.Union([ Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown) + "always": Schema.optionalKey(Schema.Struct({ "tool_names": Schema.optionalKey(Schema.Array(Schema.String)) })), + "never": Schema.optionalKey(Schema.Struct({ "tool_names": Schema.optionalKey(Schema.Array(Schema.String)) })) }), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "page_age": Schema.String, - "title": Schema.String, - "url": Schema.String - }) + Schema.Literal("always"), + Schema.Literal("never"), + Schema.Null + ]) + ), + "server_description": Schema.optionalKey(Schema.String), + "server_label": Schema.String, + "server_url": Schema.optionalKey(Schema.String), + "type": Schema.Literal("mcp") +}).annotate({ "description": "MCP (Model Context Protocol) tool configuration", "identifier": "McpServerTool" }) +export type MemberAssignment = { + readonly "assigned_by": string | null + readonly "created_at": string + readonly "guardrail_id": string + readonly "id": string + readonly "organization_id": string + readonly "user_id": string +} +export const MemberAssignment = Schema.Struct({ + "assigned_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID of who made the assignment" + }), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }), + "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), + "organization_id": Schema.String.annotate({ "description": "Organization ID" }), + "user_id": Schema.String.annotate({ "description": "Clerk user ID of the assigned member" }) +}).annotate({ "identifier": "MemberAssignment" }) +export type MessagesAdvisorToolResultBlock = { + readonly "content": {} + readonly "tool_use_id": string + readonly "type": "advisor_tool_result" +} +export const MessagesAdvisorToolResultBlock = Schema.Struct({ + "content": Schema.Struct({}), + "tool_use_id": Schema.String, + "type": Schema.Literal("advisor_tool_result") +}).annotate({ + "description": + "Advisor tool result from a prior assistant turn, replayed back to the model on the next turn. Mirrors the block Anthropic returns in assistant content when the `advisor_20260301` tool runs.", + "identifier": "MessagesAdvisorToolResultBlock" +}) +export type MessagesContentBlockStopEvent = { readonly "index": number; readonly "type": "content_block_stop" } +export const MessagesContentBlockStopEvent = Schema.Struct({ + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_stop") +}).annotate({ + "description": "Event sent when a content block is complete", + "identifier": "MessagesContentBlockStopEvent" +}) +export type MessagesFallbackParam = { readonly "model": string } +export const MessagesFallbackParam = Schema.Struct({ "model": Schema.String }).annotate({ + "description": + "Fallback model to try when the primary model fails or refuses. Only the `model` field is supported; per-attempt overrides are rejected.", + "identifier": "MessagesFallbackParam" +}) +export type Union_10 = + | { readonly "schema": {}; readonly "type": "json_schema"; readonly [x: string]: Schema.Json } + | null +export const Union_10 = Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "schema": Schema.Struct({}), "type": Schema.Literal("json_schema") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null +]).annotate({ + "description": + "A schema to specify Claude's output format in responses. See [structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)." +}) +export type Union_11 = { + readonly "remaining"?: number | null + readonly "total": number + readonly "type": "tokens" + readonly [x: string]: Schema.Json +} | null +export const Union_11 = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "remaining": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) + Schema.Null ]) - }) - ], { mode: "oneOf" }) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_delta"), - "index": Schema.Number.check(Schema.isFinite()), - "delta": Schema.Union([ - Schema.Struct({ "type": Schema.Literal("text_delta"), "text": Schema.String }), - Schema.Struct({ "type": Schema.Literal("input_json_delta"), "partial_json": Schema.String }), - Schema.Struct({ "type": Schema.Literal("thinking_delta"), "thinking": Schema.String }), - Schema.Struct({ "type": Schema.Literal("signature_delta"), "signature": Schema.String }), - Schema.Struct({ - "type": Schema.Literal("citations_delta"), - "citation": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }) - }) - ], { mode: "oneOf" }) - }), - Schema.Struct({ "type": Schema.Literal("content_block_stop"), "index": Schema.Number.check(Schema.isFinite()) }), - Schema.Struct({ "type": Schema.Literal("ping") }), - Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) - }) -], { mode: "oneOf" }).annotate({ "description": "Union of all possible streaming events" }) -export type OpenRouterAnthropicMessageParam = { - readonly "role": "user" | "assistant" - readonly "content": - | string - | ReadonlyArray< - | { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "image" - readonly "source": { - readonly "type": "base64" - readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" - readonly "data": string - } | { readonly "type": "url"; readonly "url": string } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "document" - readonly "source": - | { readonly "type": "base64"; readonly "media_type": "application/pdf"; readonly "data": string } - | { readonly "type": "text"; readonly "media_type": "text/plain"; readonly "data": string } - | { - readonly "type": "content" - readonly "content": - | string - | ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "image" - readonly "source": { - readonly "type": "base64" - readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" - readonly "data": string - } | { readonly "type": "url"; readonly "url": string } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - } - | { readonly "type": "url"; readonly "url": string } - readonly "citations"?: { readonly "enabled"?: boolean } - readonly "context"?: string - readonly "title"?: string - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "tool_use" - readonly "id": string - readonly "name": string - readonly "input"?: unknown - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "tool_result" - readonly "tool_use_id": string - readonly "content"?: - | string - | ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "image" - readonly "source": { - readonly "type": "base64" - readonly "media_type": "image/jpeg" | "image/png" | "image/gif" | "image/webp" - readonly "data": string - } | { readonly "type": "url"; readonly "url": string } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "is_error"?: boolean - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { readonly "type": "thinking"; readonly "thinking": string; readonly "signature": string } - | { readonly "type": "redacted_thinking"; readonly "data": string } - | { - readonly "type": "server_tool_use" - readonly "id": string - readonly "name": "web_search" - readonly "input"?: unknown - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "web_search_tool_result" - readonly "tool_use_id": string - readonly "content": - | ReadonlyArray< - { - readonly "type": "web_search_result" - readonly "encrypted_content": string - readonly "title": string - readonly "url": string - readonly "page_age"?: string - } - > - | { - readonly "type": "web_search_tool_result_error" - readonly "error_code": - | "invalid_tool_input" - | "unavailable" - | "max_uses_exceeded" - | "too_many_requests" - | "query_too_long" - } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - | { - readonly "type": "search_result" - readonly "source": string - readonly "title": string - readonly "content": ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "citations"?: { readonly "enabled"?: boolean } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > -} -export const OpenRouterAnthropicMessageParam = Schema.Struct({ - "role": Schema.Literals(["user", "assistant"]), - "content": Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("image"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]), - "data": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("document"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literal("application/pdf"), - "data": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("text"), - "media_type": Schema.Literal("text/plain"), - "data": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("content"), - "content": Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("image"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]), - "data": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }) - ], { mode: "oneOf" })) - ]) - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "citations": Schema.optionalKey(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) })), - "context": Schema.optionalKey(Schema.String), - "title": Schema.optionalKey(Schema.String), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("tool_use"), - "id": Schema.String, - "name": Schema.String, - "input": Schema.optionalKey(Schema.Unknown), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("tool_result"), - "tool_use_id": Schema.String, - "content": Schema.optionalKey(Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("image"), - "source": Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("base64"), - "media_type": Schema.Literals(["image/jpeg", "image/png", "image/gif", "image/webp"]), - "data": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }) - ], { mode: "oneOf" }), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }) - ])) - ])), - "is_error": Schema.optionalKey(Schema.Boolean), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ "type": Schema.Literal("thinking"), "thinking": Schema.String, "signature": Schema.String }), - Schema.Struct({ "type": Schema.Literal("redacted_thinking"), "data": Schema.String }), - Schema.Struct({ - "type": Schema.Literal("server_tool_use"), - "id": Schema.String, - "name": Schema.Literal("web_search"), - "input": Schema.optionalKey(Schema.Unknown), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result"), - "tool_use_id": Schema.String, - "content": Schema.Union([ - Schema.Array( - Schema.Struct({ - "type": Schema.Literal("web_search_result"), - "encrypted_content": Schema.String, - "title": Schema.String, - "url": Schema.String, - "page_age": Schema.optionalKey(Schema.String) - }) - ), - Schema.Struct({ - "type": Schema.Literal("web_search_tool_result_error"), - "error_code": Schema.Literals([ - "invalid_tool_input", - "unavailable", - "max_uses_exceeded", - "too_many_requests", - "query_too_long" - ]) - }) - ]), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("search_result"), - "source": Schema.String, - "title": Schema.String, - "content": Schema.Array(Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - })), - "citations": Schema.optionalKey(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) })), - "cache_control": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) - }) - ) - }) - ], { mode: "oneOf" })) - ]) -}).annotate({ "description": "Anthropic message with OpenRouter extensions" }) -export type AnthropicOutputConfig = { readonly "effort"?: "low" | "medium" | "high" | "max" } -export const AnthropicOutputConfig = Schema.Struct({ - "effort": Schema.optionalKey( - Schema.Literals(["low", "medium", "high", "max"]).annotate({ - "description": - "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer. Valid values are `low`, `medium`, `high`, or `max`." - }) - ) -}).annotate({ + ), + "total": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(20000).annotate({ "expected": "a value greater than or equal to 20000" }) + ), + "type": Schema.Literal("tokens") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": - "Configuration for controlling output behavior. Currently supports the effort parameter for Claude Opus 4.5." + "Task budget for an agentic turn. The model sees a countdown of remaining tokens and uses it to prioritize work and wind down gracefully. Advisory — does not enforce a hard cap." }) -export type ActivityItem = { - readonly "date": string - readonly "model": string - readonly "model_permaslug": string - readonly "endpoint_id": string - readonly "provider_name": string - readonly "usage": number - readonly "byok_usage_inference": number - readonly "requests": number - readonly "prompt_tokens": number - readonly "completion_tokens": number - readonly "reasoning_tokens": number -} -export const ActivityItem = Schema.Struct({ - "date": Schema.String.annotate({ "description": "Date of the activity (YYYY-MM-DD format)" }), - "model": Schema.String.annotate({ "description": "Model slug (e.g., \"openai/gpt-4.1\")" }), - "model_permaslug": Schema.String.annotate({ "description": "Model permaslug (e.g., \"openai/gpt-4.1-2025-04-14\")" }), - "endpoint_id": Schema.String.annotate({ "description": "Unique identifier for the endpoint" }), - "provider_name": Schema.String.annotate({ "description": "Name of the provider serving this endpoint" }), - "usage": Schema.Number.annotate({ "description": "Total cost in USD (OpenRouter credits spent)" }).check( - Schema.isFinite() - ), - "byok_usage_inference": Schema.Number.annotate({ - "description": "BYOK inference cost in USD (external credits spent)" - }).check(Schema.isFinite()), - "requests": Schema.Number.annotate({ "description": "Number of requests made" }).check(Schema.isFinite()), - "prompt_tokens": Schema.Number.annotate({ "description": "Total prompt tokens used" }).check(Schema.isFinite()), - "completion_tokens": Schema.Number.annotate({ "description": "Total completion tokens generated" }).check( - Schema.isFinite() - ), - "reasoning_tokens": Schema.Number.annotate({ "description": "Total reasoning tokens used" }).check(Schema.isFinite()) +export type MessagesPingEvent = { readonly "type": "ping" } +export const MessagesPingEvent = Schema.Struct({ "type": Schema.Literal("ping") }).annotate({ + "description": "Keep-alive ping event", + "identifier": "MessagesPingEvent" +}) +export type MetadataLevel = "disabled" | "enabled" +export const MetadataLevel = Schema.Literals(["disabled", "enabled"]).annotate({ + "description": "Opt-in level for surfacing routing metadata on the response under `openrouter_metadata`.", + "identifier": "MetadataLevel" }) -export type ForbiddenResponseErrorData = { - readonly "code": number - readonly "message": string - readonly "metadata"?: {} -} -export const ForbiddenResponseErrorData = Schema.Struct({ - "code": Schema.Number.check(Schema.isInt()), - "message": Schema.String, - "metadata": Schema.optionalKey(Schema.Struct({})) -}).annotate({ "description": "Error data for ForbiddenResponse" }) -export type CreateChargeRequest = { - readonly "amount": number - readonly "sender": string - readonly "chain_id": 1 | 137 | 8453 -} -export const CreateChargeRequest = Schema.Struct({ - "amount": Schema.Number.check(Schema.isFinite()), - "sender": Schema.String, - "chain_id": Schema.Literals([1, 137, 8453]) -}).annotate({ "description": "Create a Coinbase charge for crypto payment" }) -export type PublicPricing = { - readonly "prompt": string - readonly "completion": string - readonly "request"?: string - readonly "image"?: string - readonly "image_token"?: string - readonly "image_output"?: string - readonly "audio"?: string - readonly "audio_output"?: string - readonly "input_audio_cache"?: string - readonly "web_search"?: string - readonly "internal_reasoning"?: string - readonly "input_cache_read"?: string - readonly "input_cache_write"?: string - readonly "discount"?: number -} -export const PublicPricing = Schema.Struct({ - "prompt": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "completion": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "request": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_token": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_audio_cache": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "web_search": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "internal_reasoning": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_read": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_write": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "discount": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) -}).annotate({ "description": "Pricing information for the model" }) export type ModelGroup = | "Router" | "Media" @@ -2396,6 +3385,7 @@ export type ModelGroup = | "GPT" | "Claude" | "Gemini" + | "Gemma" | "Grok" | "Cohere" | "Nova" @@ -2416,6 +3406,7 @@ export const ModelGroup = Schema.Literals([ "GPT", "Claude", "Gemini", + "Gemma", "Grok", "Cohere", "Nova", @@ -2429,36 +3420,445 @@ export const ModelGroup = Schema.Literals([ "PaLM", "RWKV", "Qwen3" -]).annotate({ "description": "Tokenizer type used by the model" }) -export type InputModality = "text" | "image" | "file" | "audio" | "video" -export const InputModality = Schema.Literals(["text", "image", "file", "audio", "video"]) -export type OutputModality = "text" | "image" | "embeddings" | "audio" -export const OutputModality = Schema.Literals(["text", "image", "embeddings", "audio"]) -export type TopProviderInfo = { - readonly "context_length"?: number - readonly "max_completion_tokens"?: number - readonly "is_moderated": boolean +]).annotate({ "description": "Tokenizer type used by the model", "identifier": "ModelGroup" }) +export type ModelLinks = { readonly "details": string } +export const ModelLinks = Schema.Struct({ + "details": Schema.String.annotate({ "description": "URL for the model details/endpoints API" }) +}).annotate({ "description": "Related API endpoints and resources for this model.", "identifier": "ModelLinks" }) +export type ModelName = string +export const ModelName = Schema.String.annotate({ + "description": "Model to use for completion", + "identifier": "ModelName" +}) +export type ModelsCountResponse = { readonly "data": { readonly "count": number } } +export const ModelsCountResponse = Schema.Struct({ + "data": Schema.Struct({ + "count": Schema.Number.annotate({ "description": "Total number of available models" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }).annotate({ "description": "Model count data" }) +}).annotate({ "description": "Model count data", "identifier": "ModelsCountResponse" }) +export type ModerationPlugin = { readonly "id": "moderation" } +export const ModerationPlugin = Schema.Struct({ "id": Schema.Literal("moderation") }).annotate({ + "identifier": "ModerationPlugin" +}) +export type MultimodalMedia = { readonly "data": string; readonly "format"?: string } +export const MultimodalMedia = Schema.Struct({ "data": Schema.String, "format": Schema.optionalKey(Schema.String) }) + .annotate({ "identifier": "MultimodalMedia" }) +export type NamespaceFunctionTool = { + readonly "allowed_callers"?: ReadonlyArray<"direct" | "programmatic"> | null + readonly "defer_loading"?: boolean + readonly "description"?: string | null + readonly "name": string + readonly "output_schema"?: { readonly [x: string]: Schema.Json } | null + readonly "parameters"?: { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" } -export const TopProviderInfo = Schema.Struct({ - "context_length": Schema.optionalKey( - Schema.Number.annotate({ "description": "Context length from the top provider" }).check(Schema.isFinite()) +export const NamespaceFunctionTool = Schema.Struct({ + "allowed_callers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Literals(["direct", "programmatic"])), Schema.Null]) ), - "max_completion_tokens": Schema.optionalKey( - Schema.Number.annotate({ "description": "Maximum completion tokens from the top provider" }).check( - Schema.isFinite() - ) + "defer_loading": Schema.optionalKey(Schema.Boolean), + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "output_schema": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "is_moderated": Schema.Boolean.annotate({ "description": "Whether the top provider moderates content" }) -}).annotate({ "description": "Information about the top provider for this model" }) -export type PerRequestLimits = { readonly "prompt_tokens": number; readonly "completion_tokens": number } -export const PerRequestLimits = Schema.Struct({ - "prompt_tokens": Schema.Number.annotate({ "description": "Maximum prompt tokens per request" }).check( - Schema.isFinite() + "parameters": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "completion_tokens": Schema.Number.annotate({ "description": "Maximum completion tokens per request" }).check( - Schema.isFinite() + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") +}).annotate({ "description": "A function tool grouped inside a namespace tool", "identifier": "NamespaceFunctionTool" }) +export type NotFoundResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const NotFoundResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ) -}).annotate({ "description": "Per-request token limits" }) +}).annotate({ "description": "Error data for NotFoundResponse", "identifier": "NotFoundResponseErrorData" }) +export type ObservabilityFilterRuleGroup = { + readonly "logic"?: "and" | "or" + readonly "rules": ReadonlyArray< + { + readonly "field": + | "model" + | "provider" + | "session_id" + | "user_id" + | "api_key_name" + | "finish_reason" + | "input" + | "output" + | "total_cost" + | "total_tokens" + | "prompt_tokens" + | "completion_tokens" + readonly "operator": + | "equals" + | "not_equals" + | "contains" + | "not_contains" + | "regex" + | "starts_with" + | "ends_with" + | "gt" + | "lt" + | "gte" + | "lte" + | "exists" + | "not_exists" + readonly "value"?: string | number + } + > +} +export const ObservabilityFilterRuleGroup = Schema.Struct({ + "logic": Schema.optionalKey(Schema.Literals(["and", "or"])), + "rules": Schema.Array(Schema.Struct({ + "field": Schema.Literals([ + "model", + "provider", + "session_id", + "user_id", + "api_key_name", + "finish_reason", + "input", + "output", + "total_cost", + "total_tokens", + "prompt_tokens", + "completion_tokens" + ]), + "operator": Schema.Literals([ + "equals", + "not_equals", + "contains", + "not_contains", + "regex", + "starts_with", + "ends_with", + "gt", + "lt", + "gte", + "lte", + "exists", + "not_exists" + ]), + "value": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))]) + ) + })) +}).annotate({ "identifier": "ObservabilityFilterRuleGroup" }) +export type OpenAIResponseCustomToolCall = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const OpenAIResponseCustomToolCall = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ "identifier": "OpenAIResponseCustomToolCall" }) +export type OpenAIResponsesImageGenCallCompleted = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.completed" +} +export const OpenAIResponsesImageGenCallCompleted = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.completed") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallCompleted" }) +export type OpenAIResponsesImageGenCallGenerating = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.generating" +} +export const OpenAIResponsesImageGenCallGenerating = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.generating") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallGenerating" }) +export type OpenAIResponsesImageGenCallInProgress = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.in_progress" +} +export const OpenAIResponsesImageGenCallInProgress = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.in_progress") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallInProgress" }) +export type OpenAIResponsesImageGenCallPartialImage = { + readonly "item_id": string + readonly "output_index": number + readonly "partial_image_b64": string + readonly "partial_image_index": number + readonly "sequence_number": number + readonly "type": "response.image_generation_call.partial_image" +} +export const OpenAIResponsesImageGenCallPartialImage = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "partial_image_b64": Schema.String, + "partial_image_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.image_generation_call.partial_image") +}).annotate({ "identifier": "OpenAIResponsesImageGenCallPartialImage" }) +export type OpenAIResponsesRefusalContent = { readonly "refusal": string; readonly "type": "refusal" } +export const OpenAIResponsesRefusalContent = Schema.Struct({ + "refusal": Schema.String, + "type": Schema.Literal("refusal") +}).annotate({ "identifier": "OpenAIResponsesRefusalContent" }) +export type OpenAIResponsesResponseStatus = + | "completed" + | "incomplete" + | "in_progress" + | "failed" + | "cancelled" + | "queued" +export const OpenAIResponsesResponseStatus = Schema.Literals([ + "completed", + "incomplete", + "in_progress", + "failed", + "cancelled", + "queued" +]).annotate({ "identifier": "OpenAIResponsesResponseStatus" }) +export type OpenAIResponsesSearchCompleted = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.completed" +} +export const OpenAIResponsesSearchCompleted = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.completed") +}).annotate({ "identifier": "OpenAIResponsesSearchCompleted" }) +export type Objects_11 = { readonly "name": string; readonly "type": "function" } +export const Objects_11 = Schema.Struct({ "name": Schema.String, "type": Schema.Literal("function") }) +export type Objects_12 = { readonly "type": "web_search_preview_2025_03_11" | "web_search_preview" } +export const Objects_12 = Schema.Struct({ + "type": Schema.Literals(["web_search_preview_2025_03_11", "web_search_preview"]) +}) +export type Objects_13 = { readonly "type": "apply_patch" } +export const Objects_13 = Schema.Struct({ "type": Schema.Literal("apply_patch") }) +export type Objects_14 = { readonly "type": "shell" } +export const Objects_14 = Schema.Struct({ "type": Schema.Literal("shell") }) +export type OpenAIResponsesTruncation = "auto" | "disabled" | null +export const OpenAIResponsesTruncation = Schema.Union([Schema.Literal("auto"), Schema.Literal("disabled"), Schema.Null]) + .annotate({ "identifier": "OpenAIResponsesTruncation" }) +export type Objects_15 = { readonly "cache_write_tokens"?: number | null; readonly "cached_tokens": number } +export const Objects_15 = Schema.Struct({ + "cache_write_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "cached_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}) +export type Objects_16 = { readonly "reasoning_tokens": number } +export const Objects_16 = Schema.Struct({ + "reasoning_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}) +export type OpenAIResponsesWebSearchCallInProgress = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.in_progress" +} +export const OpenAIResponsesWebSearchCallInProgress = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.in_progress") +}).annotate({ "identifier": "OpenAIResponsesWebSearchCallInProgress" }) +export type OpenAIResponsesWebSearchCallSearching = { + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.searching" +} +export const OpenAIResponsesWebSearchCallSearching = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.searching") +}).annotate({ "identifier": "OpenAIResponsesWebSearchCallSearching" }) +export type OpenResponsesTopLogprobs = { + readonly "bytes"?: ReadonlyArray + readonly "logprob"?: number + readonly "token"?: string +} +export const OpenResponsesTopLogprobs = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "token": Schema.optionalKey(Schema.String) +}).annotate({ "description": "Alternative token with its log probability", "identifier": "OpenResponsesTopLogprobs" }) +export type ORAnthropicStopReason = + | "end_turn" + | "max_tokens" + | "stop_sequence" + | "tool_use" + | "pause_turn" + | "refusal" + | "compaction" + | null +export const ORAnthropicStopReason = Schema.Union([ + Schema.Literal("end_turn"), + Schema.Literal("max_tokens"), + Schema.Literal("stop_sequence"), + Schema.Literal("tool_use"), + Schema.Literal("pause_turn"), + Schema.Literal("refusal"), + Schema.Literal("compaction"), + Schema.Null +]).annotate({ "identifier": "ORAnthropicStopReason" }) +export type OutputComputerCallItem = { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id"?: string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": "computer_call" +} +export const OutputComputerCallItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Literals(["completed", "incomplete", "in_progress"]), + "type": Schema.Literal("computer_call") +}).annotate({ "identifier": "OutputComputerCallItem" }) +export type OutputCustomToolCallItem = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const OutputCustomToolCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments.", + "identifier": "OutputCustomToolCallItem" +}) +export type OutputFunctionCallItem = { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "function_call" +} +export const OutputFunctionCallItem = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("function_call") +}).annotate({ "identifier": "OutputFunctionCallItem" }) +export type OutputItemCustomToolCall = { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" +} +export const OutputItemCustomToolCall = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") +}).annotate({ "identifier": "OutputItemCustomToolCall" }) +export type OutputItemFunctionCall = { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "function_call" +} +export const OutputItemFunctionCall = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("function_call") +}).annotate({ "identifier": "OutputItemFunctionCall" }) +export type OutputModality = "text" | "image" | "embeddings" | "audio" | "video" | "rerank" | "speech" | "transcription" +export const OutputModality = Schema.Literals([ + "text", + "image", + "embeddings", + "audio", + "video", + "rerank", + "speech", + "transcription" +]).annotate({ "identifier": "OutputModality" }) +export type OutputModalityEnum = "text" | "image" +export const OutputModalityEnum = Schema.Literals(["text", "image"]).annotate({ "identifier": "OutputModalityEnum" }) export type Parameter = | "temperature" | "top_p" @@ -2469,9 +3869,11 @@ export type Parameter = | "presence_penalty" | "repetition_penalty" | "max_tokens" + | "max_completion_tokens" | "logit_bias" | "logprobs" | "top_logprobs" + | "prediction" | "seed" | "response_format" | "structured_outputs" @@ -2494,9 +3896,11 @@ export const Parameter = Schema.Literals([ "presence_penalty", "repetition_penalty", "max_tokens", + "max_completion_tokens", "logit_bias", "logprobs", "top_logprobs", + "prediction", "seed", "response_format", "structured_outputs", @@ -2509,53 +3913,350 @@ export const Parameter = Schema.Literals([ "reasoning_effort", "web_search_options", "verbosity" -]) -export type DefaultParameters = { - readonly "temperature"?: number - readonly "top_p"?: number - readonly "frequency_penalty"?: number +]).annotate({ "identifier": "Parameter" }) +export type ParetoRouterPlugin = { + readonly "enabled"?: boolean + readonly "id": "pareto-router" + readonly "max_price"?: number + readonly "min_coding_score"?: number + readonly "price_source"?: "prompt" | "weighted_avg" } -export const DefaultParameters = Schema.Struct({ - "temperature": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)) +export const ParetoRouterPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the pareto-router plugin for this request. Defaults to true." + }) ), - "top_p": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(1)) + "id": Schema.Literal("pareto-router"), + "max_price": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum input price in USD per million tokens. When set, quality-tier selection (min_coding_score) is bypassed: the router computes the Pareto frontier over the top coding models and routes to the best-scoring frontier model priced at or below this cap, falling back through cheaper frontier models, then non-frontier models. Enforced against the price source given by price_source. Returns 404 when no candidate satisfies the cap.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ) ), - "frequency_penalty": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check(Schema.isLessThanOrEqualTo(2)) + "min_coding_score": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Minimum coding quality score between 0 and 1. Maps to internal quality tiers: >= 0.66 → high (top coding models), >= 0.33 → medium (strong modern flagships), < 0.33 → low (capable coders above the median). Omit to default to the highest tier (equivalent to >= 0.66). Not used when max_price is set (price-based selection takes over).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(1).annotate({ "expected": "a value less than or equal to 1" })) + ), + "price_source": Schema.optionalKey( + Schema.Literals(["prompt", "weighted_avg"]).annotate({ + "description": + "Price source for the Pareto frontier cost axis and for enforcing max_price. \"prompt\" uses catalog list price (endpoint.pricing.prompt). \"weighted_avg\" uses traffic-weighted effective input price from ClickHouse, falling back to prompt price for models without traffic data. Defaults to \"prompt\"." + }) ) -}).annotate({ "description": "Default parameters for this model" }) -export type ModelsCountResponse = { readonly "data": { readonly "count": number } } -export const ModelsCountResponse = Schema.Struct({ - "data": Schema.Struct({ - "count": Schema.Number.annotate({ "description": "Total number of available models" }).check(Schema.isFinite()) - }).annotate({ "description": "Model count data" }) -}).annotate({ "description": "Model count data" }) -export type EndpointStatus = 0 | -1 | -2 | -3 | -5 | -10 -export const EndpointStatus = Schema.Literals([0, -1, -2, -3, -5, -10]) -export type PercentileStats = { - readonly "p50": number - readonly "p75": number - readonly "p90": number - readonly "p99": number +}).annotate({ "identifier": "ParetoRouterPlugin" }) +export type PayloadTooLargeResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const PayloadTooLargeResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for PayloadTooLargeResponse", + "identifier": "PayloadTooLargeResponseErrorData" +}) +export type PaymentRequiredResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null } -export const PercentileStats = Schema.Struct({ - "p50": Schema.Number.annotate({ "description": "Median (50th percentile)" }).check(Schema.isFinite()), - "p75": Schema.Number.annotate({ "description": "75th percentile" }).check(Schema.isFinite()), - "p90": Schema.Number.annotate({ "description": "90th percentile" }).check(Schema.isFinite()), - "p99": Schema.Number.annotate({ "description": "99th percentile" }).check(Schema.isFinite()) +export const PaymentRequiredResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) }).annotate({ + "description": "Error data for PaymentRequiredResponse", + "identifier": "PaymentRequiredResponseErrorData" +}) +export type PDFParserEngine = "mistral-ocr" | "native" | "cloudflare-ai" | "pdf-text" +export const PDFParserEngine = Schema.Union([ + Schema.Literals(["mistral-ocr", "native", "cloudflare-ai"]), + Schema.Literal("pdf-text") +]).annotate({ "description": - "Latency percentiles in milliseconds over the last 30 minutes. Latency measures time to first token. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests." + "The engine to use for parsing PDF files. \"pdf-text\" is deprecated and automatically redirected to \"cloudflare-ai\".", + "identifier": "PDFParserEngine" }) -export type __schema5 = ReadonlyArray< - | "AI21" - | "AionLabs" - | "Alibaba" - | "Ambient" - | "Amazon Bedrock" - | "Amazon Nova" +export type PercentileLatencyCutoffs = { + readonly "p50"?: number | null + readonly "p75"?: number | null + readonly "p90"?: number | null + readonly "p99"?: number | null +} +export const PercentileLatencyCutoffs = Schema.Struct({ + "p50": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p50 latency (seconds)", "format": "double" }) + ), + "p75": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p75 latency (seconds)", "format": "double" }) + ), + "p90": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p90 latency (seconds)", "format": "double" }) + ), + "p99": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Maximum p99 latency (seconds)", "format": "double" }) + ) +}).annotate({ + "description": "Percentile-based latency cutoffs. All specified cutoffs must be met for an endpoint to be preferred.", + "identifier": "PercentileLatencyCutoffs" +}) +export type PercentileStats = { + readonly "p50": number + readonly "p75": number + readonly "p90": number + readonly "p99": number + readonly [x: string]: Schema.Json +} | null +export const PercentileStats = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "p50": Schema.Number.annotate({ "description": "Median (50th percentile)", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "p75": Schema.Number.annotate({ "description": "75th percentile", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "p90": Schema.Number.annotate({ "description": "90th percentile", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "p99": Schema.Number.annotate({ "description": "99th percentile", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ + "description": + "Latency percentiles in milliseconds over the last 30 minutes. Latency measures time to first token. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests.", + "identifier": "PercentileStats" +}) +export type PercentileThroughputCutoffs = { + readonly "p50"?: number | null + readonly "p75"?: number | null + readonly "p90"?: number | null + readonly "p99"?: number | null +} +export const PercentileThroughputCutoffs = Schema.Struct({ + "p50": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p50 throughput (tokens/sec)", "format": "double" }) + ), + "p75": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p75 throughput (tokens/sec)", "format": "double" }) + ), + "p90": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p90 throughput (tokens/sec)", "format": "double" }) + ), + "p99": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Minimum p99 throughput (tokens/sec)", "format": "double" }) + ) +}).annotate({ + "description": + "Percentile-based throughput cutoffs. All specified cutoffs must be met for an endpoint to be preferred.", + "identifier": "PercentileThroughputCutoffs" +}) +export type PerRequestLimits = { + readonly "completion_tokens": number + readonly "prompt_tokens": number + readonly [x: string]: Schema.Json +} | null +export const PerRequestLimits = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ "description": "Maximum completion tokens per request" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Maximum prompt tokens per request" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Per-request token limits", "identifier": "PerRequestLimits" }) +export type PipelineStageType = "guardrail" | "plugin" | "server_tools" | "response_healing" | "context_compression" +export const PipelineStageType = Schema.Literals([ + "guardrail", + "plugin", + "server_tools", + "response_healing", + "context_compression" +]).annotate({ + "description": + "Categorical kind of a pipeline stage. Multiple plugins can share a type (e.g. all guardrail-level plugins emit `guardrail`); the `name` field disambiguates which plugin emitted it.", + "identifier": "PipelineStageType" +}) +export type PredictionContentText = { readonly "text": string; readonly "type": "text" } +export const PredictionContentText = Schema.Struct({ "text": Schema.String, "type": Schema.Literal("text") }).annotate({ + "description": "Text content part for a predicted output.", + "identifier": "PredictionContentText" +}) +export type PresetDesignatedVersion = { + readonly "config": {} + readonly "created_at": string + readonly "creator_id": string + readonly "id": string + readonly "preset_id": string + readonly "system_prompt": string | null + readonly "updated_at": string + readonly "version": number + readonly [x: string]: Schema.Json +} | null +export const PresetDesignatedVersion = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "config": Schema.Struct({}), + "created_at": Schema.String, + "creator_id": Schema.String, + "id": Schema.String, + "preset_id": Schema.String, + "system_prompt": Schema.Union([Schema.String, Schema.Null]), + "updated_at": Schema.String, + "version": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ + "description": "A specific version of a preset, containing config and optional system prompt.", + "identifier": "PresetDesignatedVersion" +}) +export type PresetStatus = "active" | "disabled" | "archived" +export const PresetStatus = Schema.Literals(["active", "disabled", "archived"]).annotate({ + "description": "The status of a preset.", + "identifier": "PresetStatus" +}) +export type Objects_18 = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" + readonly [x: string]: Schema.Json +} +export const Objects_18 = Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("approximate") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type PricingOverride = { + readonly "audio"?: string + readonly "completion"?: string + readonly "input_audio_cache"?: string + readonly "input_cache_read"?: string + readonly "input_cache_write"?: string + readonly "input_cache_write_1h"?: string + readonly "min_prompt_tokens"?: number + readonly "prompt"?: string + readonly "utc_end"?: number + readonly "utc_start"?: number +} +export const PricingOverride = Schema.Struct({ + "audio": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per audio input token" }) + ), + "completion": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per token for completion (output) generation" }) + ), + "input_audio_cache": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per cached audio input token" }) + ), + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per cached input token (read)" }) + ), + "input_cache_write": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per cache-write token" }) + ), + "input_cache_write_1h": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per 1-hour cache-write token" }) + ), + "min_prompt_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Condition: the entry applies when the total prompt tokens of a request are strictly greater than this threshold", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "Overridden price in USD per token for prompt (input) processing" }) + ), + "utc_end": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Condition: exclusive end of a daily UTC time window as an HHMM clock number (e.g. 400 = 04:00)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "utc_start": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Condition: inclusive start of a daily UTC time window as an HHMM clock number (e.g. 100 = 01:00, 1030 = 10:30). The entry applies while the current UTC time is inside the half-open window [utc_start, utc_end), which may wrap past midnight (utc_start > utc_end).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ) +}).annotate({ + "description": + "A conditional override of the base pricing. An entry applies only when all of its condition fields (e.g. min_prompt_tokens, or the utc_start/utc_end time window) match the request; among applicable entries, later entries win per price key; price keys absent from an entry inherit the base price.", + "identifier": "PricingOverride" +}) +export type Objects_19 = { readonly "mode": "explicit"; readonly [x: string]: Schema.Json } +export const Objects_19 = Schema.StructWithRest(Schema.Struct({ "mode": Schema.Literal("explicit") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) +]) +export type Objects_20 = { + readonly "mode": "explicit" + readonly "ttl"?: string | null + readonly [x: string]: Schema.Json +} +export const Objects_20 = Schema.StructWithRest( + Schema.Struct({ + "mode": Schema.Literal("explicit"), + "ttl": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type PromptInjectionScanScope = "user_only" | "all_messages" +export const PromptInjectionScanScope = Schema.Literals(["user_only", "all_messages"]).annotate({ + "description": + "Which message roles to scan for prompt injection. Only applies to the regex-prompt-injection builtin. Defaults to all_messages.", + "identifier": "PromptInjectionScanScope" +}) +export type ProviderName = + | "AkashML" + | "AI21" + | "AionLabs" + | "Alibaba" + | "Ambient" + | "Baidu" + | "Amazon Bedrock" + | "Amazon Nova" | "Anthropic" | "Arcee AI" | "AtlasCloud" @@ -2570,26 +4271,37 @@ export type __schema5 = ReadonlyArray< | "Clarifai" | "Cloudflare" | "Cohere" + | "CoreWeave" + | "Crucible" | "Crusoe" + | "Darkbloom" + | "Decart" + | "Deepgram" | "DeepInfra" | "DeepSeek" + | "DekaLLM" + | "DigitalOcean" | "Featherless" | "Fireworks" + | "Fish Audio" | "Friendli" | "GMICloud" | "Google" | "Google AI Studio" | "Groq" - | "Hyperbolic" + | "HeyGen" | "Inception" | "Inceptron" | "InferenceNet" + | "Ionstream" | "Infermatic" | "Io Net" + | "Inferact vLLM" | "Inflection" | "Liquid" | "Mara" | "Mancer 2" + | "Meta" | "Minimax" | "ModelRun" | "Mistral" @@ -2598,15 +4310,22 @@ export type __schema5 = ReadonlyArray< | "Morph" | "NCompass" | "Nebius" + | "Nex AGI" | "NextBit" | "Novita" | "Nvidia" | "OpenAI" | "OpenInference" | "Parasail" + | "Poolside" + | "Perceptron" | "Perplexity" | "Phala" + | "Recraft" + | "Reka" | "Relace" + | "Sail Research" + | "Sakana AI" | "SambaNova" | "Seed" | "SiliconFlow" @@ -2615,23 +4334,575 @@ export type __schema5 = ReadonlyArray< | "Stealth" | "StreamLake" | "Switchpoint" + | "Tencent" + | "Tenstorrent" | "Together" | "Upstage" | "Venice" + | "Wafer" | "WandB" + | "Quiver" + | "Krea" + | "Runway" | "Xiaomi" | "xAI" | "Z.AI" | "FakeProvider" - | string -> -export const __schema5 = Schema.Array( - Schema.Union([ +export const ProviderName = Schema.Literals([ + "AkashML", + "AI21", + "AionLabs", + "Alibaba", + "Ambient", + "Baidu", + "Amazon Bedrock", + "Amazon Nova", + "Anthropic", + "Arcee AI", + "AtlasCloud", + "Avian", + "Azure", + "BaseTen", + "BytePlus", + "Black Forest Labs", + "Cerebras", + "Chutes", + "Cirrascale", + "Clarifai", + "Cloudflare", + "Cohere", + "CoreWeave", + "Crucible", + "Crusoe", + "Darkbloom", + "Decart", + "Deepgram", + "DeepInfra", + "DeepSeek", + "DekaLLM", + "DigitalOcean", + "Featherless", + "Fireworks", + "Fish Audio", + "Friendli", + "GMICloud", + "Google", + "Google AI Studio", + "Groq", + "HeyGen", + "Inception", + "Inceptron", + "InferenceNet", + "Ionstream", + "Infermatic", + "Io Net", + "Inferact vLLM", + "Inflection", + "Liquid", + "Mara", + "Mancer 2", + "Meta", + "Minimax", + "ModelRun", + "Mistral", + "Modular", + "Moonshot AI", + "Morph", + "NCompass", + "Nebius", + "Nex AGI", + "NextBit", + "Novita", + "Nvidia", + "OpenAI", + "OpenInference", + "Parasail", + "Poolside", + "Perceptron", + "Perplexity", + "Phala", + "Recraft", + "Reka", + "Relace", + "Sail Research", + "Sakana AI", + "SambaNova", + "Seed", + "SiliconFlow", + "Sourceful", + "StepFun", + "Stealth", + "StreamLake", + "Switchpoint", + "Tencent", + "Tenstorrent", + "Together", + "Upstage", + "Venice", + "Wafer", + "WandB", + "Quiver", + "Krea", + "Runway", + "Xiaomi", + "xAI", + "Z.AI", + "FakeProvider" +]).annotate({ "identifier": "ProviderName" }) +export type Objects_21 = {} +export const Objects_21 = Schema.Struct({}) +export type Objects_22 = {} +export const Objects_22 = Schema.Struct({}) +export type Objects_23 = {} +export const Objects_23 = Schema.Struct({}) +export type Objects_24 = {} +export const Objects_24 = Schema.Struct({}) +export type Objects_25 = {} +export const Objects_25 = Schema.Struct({}) +export type Objects_26 = {} +export const Objects_26 = Schema.Struct({}) +export type Objects_27 = {} +export const Objects_27 = Schema.Struct({}) +export type Objects_28 = {} +export const Objects_28 = Schema.Struct({}) +export type Objects_29 = {} +export const Objects_29 = Schema.Struct({}) +export type Objects_30 = {} +export const Objects_30 = Schema.Struct({}) +export type Objects_31 = {} +export const Objects_31 = Schema.Struct({}) +export type Objects_32 = {} +export const Objects_32 = Schema.Struct({}) +export type Objects_33 = {} +export const Objects_33 = Schema.Struct({}) +export type Objects_34 = {} +export const Objects_34 = Schema.Struct({}) +export type Objects_35 = {} +export const Objects_35 = Schema.Struct({}) +export type Objects_36 = {} +export const Objects_36 = Schema.Struct({}) +export type Objects_37 = {} +export const Objects_37 = Schema.Struct({}) +export type Objects_38 = {} +export const Objects_38 = Schema.Struct({}) +export type Objects_39 = {} +export const Objects_39 = Schema.Struct({}) +export type Objects_40 = {} +export const Objects_40 = Schema.Struct({}) +export type Objects_41 = {} +export const Objects_41 = Schema.Struct({}) +export type Objects_42 = {} +export const Objects_42 = Schema.Struct({}) +export type Objects_43 = {} +export const Objects_43 = Schema.Struct({}) +export type Objects_44 = {} +export const Objects_44 = Schema.Struct({}) +export type Objects_45 = {} +export const Objects_45 = Schema.Struct({}) +export type Objects_46 = {} +export const Objects_46 = Schema.Struct({}) +export type Objects_47 = {} +export const Objects_47 = Schema.Struct({}) +export type Objects_48 = {} +export const Objects_48 = Schema.Struct({}) +export type Objects_49 = {} +export const Objects_49 = Schema.Struct({}) +export type Objects_50 = {} +export const Objects_50 = Schema.Struct({}) +export type Objects_51 = {} +export const Objects_51 = Schema.Struct({}) +export type Objects_52 = {} +export const Objects_52 = Schema.Struct({}) +export type Objects_53 = {} +export const Objects_53 = Schema.Struct({}) +export type Objects_54 = {} +export const Objects_54 = Schema.Struct({}) +export type Objects_55 = {} +export const Objects_55 = Schema.Struct({}) +export type Objects_56 = {} +export const Objects_56 = Schema.Struct({}) +export type Objects_57 = {} +export const Objects_57 = Schema.Struct({}) +export type Objects_58 = {} +export const Objects_58 = Schema.Struct({}) +export type Objects_59 = {} +export const Objects_59 = Schema.Struct({}) +export type Objects_60 = {} +export const Objects_60 = Schema.Struct({}) +export type Objects_61 = {} +export const Objects_61 = Schema.Struct({}) +export type Objects_62 = {} +export const Objects_62 = Schema.Struct({}) +export type Objects_63 = {} +export const Objects_63 = Schema.Struct({}) +export type Objects_64 = {} +export const Objects_64 = Schema.Struct({}) +export type Objects_65 = {} +export const Objects_65 = Schema.Struct({}) +export type Objects_66 = {} +export const Objects_66 = Schema.Struct({}) +export type Objects_67 = {} +export const Objects_67 = Schema.Struct({}) +export type Objects_68 = {} +export const Objects_68 = Schema.Struct({}) +export type Objects_69 = {} +export const Objects_69 = Schema.Struct({}) +export type Objects_70 = {} +export const Objects_70 = Schema.Struct({}) +export type Objects_71 = {} +export const Objects_71 = Schema.Struct({}) +export type Objects_72 = {} +export const Objects_72 = Schema.Struct({}) +export type Objects_73 = {} +export const Objects_73 = Schema.Struct({}) +export type Objects_74 = {} +export const Objects_74 = Schema.Struct({}) +export type Objects_75 = {} +export const Objects_75 = Schema.Struct({}) +export type Objects_76 = {} +export const Objects_76 = Schema.Struct({}) +export type Objects_77 = {} +export const Objects_77 = Schema.Struct({}) +export type Objects_78 = {} +export const Objects_78 = Schema.Struct({}) +export type Objects_79 = {} +export const Objects_79 = Schema.Struct({}) +export type Objects_80 = {} +export const Objects_80 = Schema.Struct({}) +export type Objects_81 = {} +export const Objects_81 = Schema.Struct({}) +export type Objects_82 = {} +export const Objects_82 = Schema.Struct({}) +export type Objects_83 = {} +export const Objects_83 = Schema.Struct({}) +export type Objects_84 = {} +export const Objects_84 = Schema.Struct({}) +export type Objects_85 = {} +export const Objects_85 = Schema.Struct({}) +export type Objects_86 = {} +export const Objects_86 = Schema.Struct({}) +export type Objects_87 = {} +export const Objects_87 = Schema.Struct({}) +export type Objects_88 = {} +export const Objects_88 = Schema.Struct({}) +export type Objects_89 = {} +export const Objects_89 = Schema.Struct({}) +export type Objects_90 = {} +export const Objects_90 = Schema.Struct({}) +export type Objects_91 = {} +export const Objects_91 = Schema.Struct({}) +export type Objects_92 = {} +export const Objects_92 = Schema.Struct({}) +export type Objects_93 = {} +export const Objects_93 = Schema.Struct({}) +export type Objects_94 = {} +export const Objects_94 = Schema.Struct({}) +export type Objects_95 = {} +export const Objects_95 = Schema.Struct({}) +export type Objects_96 = {} +export const Objects_96 = Schema.Struct({}) +export type Objects_97 = {} +export const Objects_97 = Schema.Struct({}) +export type Objects_98 = {} +export const Objects_98 = Schema.Struct({}) +export type Objects_99 = {} +export const Objects_99 = Schema.Struct({}) +export type Objects_100 = {} +export const Objects_100 = Schema.Struct({}) +export type Objects_101 = {} +export const Objects_101 = Schema.Struct({}) +export type Objects_102 = {} +export const Objects_102 = Schema.Struct({}) +export type Objects_103 = {} +export const Objects_103 = Schema.Struct({}) +export type Objects_104 = {} +export const Objects_104 = Schema.Struct({}) +export type Objects_105 = {} +export const Objects_105 = Schema.Struct({}) +export type Objects_106 = {} +export const Objects_106 = Schema.Struct({}) +export type Objects_107 = {} +export const Objects_107 = Schema.Struct({}) +export type Objects_108 = {} +export const Objects_108 = Schema.Struct({}) +export type Objects_109 = {} +export const Objects_109 = Schema.Struct({}) +export type Objects_110 = {} +export const Objects_110 = Schema.Struct({}) +export type Objects_111 = {} +export const Objects_111 = Schema.Struct({}) +export type Objects_112 = {} +export const Objects_112 = Schema.Struct({}) +export type Objects_113 = {} +export const Objects_113 = Schema.Struct({}) +export type Objects_114 = {} +export const Objects_114 = Schema.Struct({}) +export type Objects_115 = {} +export const Objects_115 = Schema.Struct({}) +export type Objects_116 = {} +export const Objects_116 = Schema.Struct({}) +export type Objects_117 = {} +export const Objects_117 = Schema.Struct({}) +export type Objects_118 = {} +export const Objects_118 = Schema.Struct({}) +export type Objects_119 = {} +export const Objects_119 = Schema.Struct({}) +export type Objects_120 = {} +export const Objects_120 = Schema.Struct({}) +export type Objects_121 = {} +export const Objects_121 = Schema.Struct({}) +export type Objects_122 = {} +export const Objects_122 = Schema.Struct({}) +export type Objects_123 = {} +export const Objects_123 = Schema.Struct({}) +export type Objects_124 = {} +export const Objects_124 = Schema.Struct({}) +export type Objects_125 = {} +export const Objects_125 = Schema.Struct({}) +export type Objects_126 = {} +export const Objects_126 = Schema.Struct({}) +export type Objects_127 = {} +export const Objects_127 = Schema.Struct({}) +export type Objects_128 = {} +export const Objects_128 = Schema.Struct({}) +export type Objects_129 = {} +export const Objects_129 = Schema.Struct({}) +export type Objects_130 = {} +export const Objects_130 = Schema.Struct({}) +export type Objects_131 = {} +export const Objects_131 = Schema.Struct({}) +export type Objects_132 = {} +export const Objects_132 = Schema.Struct({}) +export type Objects_133 = {} +export const Objects_133 = Schema.Struct({}) +export type Objects_134 = {} +export const Objects_134 = Schema.Struct({}) +export type Objects_135 = {} +export const Objects_135 = Schema.Struct({}) +export type Objects_136 = {} +export const Objects_136 = Schema.Struct({}) +export type Objects_137 = {} +export const Objects_137 = Schema.Struct({}) +export type Objects_138 = {} +export const Objects_138 = Schema.Struct({}) +export type Objects_139 = {} +export const Objects_139 = Schema.Struct({}) +export type Objects_140 = {} +export const Objects_140 = Schema.Struct({}) +export type Objects_141 = {} +export const Objects_141 = Schema.Struct({}) +export type Objects_142 = {} +export const Objects_142 = Schema.Struct({}) +export type Objects_143 = {} +export const Objects_143 = Schema.Struct({}) +export type Objects_144 = {} +export const Objects_144 = Schema.Struct({}) +export type Objects_145 = {} +export const Objects_145 = Schema.Struct({}) +export type Objects_146 = {} +export const Objects_146 = Schema.Struct({}) +export type Objects_147 = {} +export const Objects_147 = Schema.Struct({}) +export type ProviderOverloadedResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const ProviderOverloadedResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for ProviderOverloadedResponse", + "identifier": "ProviderOverloadedResponseErrorData" +}) +export type ProviderResponse = { + readonly "endpoint_id"?: string + readonly "id"?: string + readonly "is_byok"?: boolean + readonly "latency"?: number + readonly "model_permaslug"?: string + readonly "provider_name"?: + | "AnyScale" + | "Atoma" + | "Cent-ML" + | "CrofAI" + | "Enfer" + | "GoPomelo" + | "HuggingFace" + | "Hyperbolic" + | "Hyperbolic 2" + | "InoCloud" + | "Kluster" + | "Lambda" + | "Lepton" + | "Lynn 2" + | "Lynn" + | "Mancer" + | "Modal" + | "Nineteen" + | "OctoAI" + | "Recursal" + | "Reflection" + | "Replicate" + | "SambaNova 2" + | "SF Compute" + | "Targon" + | "Together 2" + | "Ubicloud" + | "01.AI" + | "AkashML" + | "AI21" + | "AionLabs" + | "Alibaba" + | "Ambient" + | "Baidu" + | "Amazon Bedrock" + | "Amazon Nova" + | "Anthropic" + | "Arcee AI" + | "AtlasCloud" + | "Avian" + | "Azure" + | "BaseTen" + | "BytePlus" + | "Black Forest Labs" + | "Cerebras" + | "Chutes" + | "Cirrascale" + | "Clarifai" + | "Cloudflare" + | "Cohere" + | "CoreWeave" + | "Crucible" + | "Crusoe" + | "Darkbloom" + | "Decart" + | "Deepgram" + | "DeepInfra" + | "DeepSeek" + | "DekaLLM" + | "DigitalOcean" + | "Featherless" + | "Fireworks" + | "Fish Audio" + | "Friendli" + | "GMICloud" + | "Google" + | "Google AI Studio" + | "Groq" + | "HeyGen" + | "Inception" + | "Inceptron" + | "InferenceNet" + | "Ionstream" + | "Infermatic" + | "Io Net" + | "Inferact vLLM" + | "Inflection" + | "Liquid" + | "Mara" + | "Mancer 2" + | "Meta" + | "Minimax" + | "ModelRun" + | "Mistral" + | "Modular" + | "Moonshot AI" + | "Morph" + | "NCompass" + | "Nebius" + | "Nex AGI" + | "NextBit" + | "Novita" + | "Nvidia" + | "OpenAI" + | "OpenInference" + | "Parasail" + | "Poolside" + | "Perceptron" + | "Perplexity" + | "Phala" + | "Recraft" + | "Reka" + | "Relace" + | "Sail Research" + | "Sakana AI" + | "SambaNova" + | "Seed" + | "SiliconFlow" + | "Sourceful" + | "StepFun" + | "Stealth" + | "StreamLake" + | "Switchpoint" + | "Tencent" + | "Tenstorrent" + | "Together" + | "Upstage" + | "Venice" + | "Wafer" + | "WandB" + | "Quiver" + | "Krea" + | "Runway" + | "Xiaomi" + | "xAI" + | "Z.AI" + | "FakeProvider" + readonly "routed_service_tier"?: "flex" | "priority" + readonly "status": number | null +} +export const ProviderResponse = Schema.Struct({ + "endpoint_id": Schema.optionalKey(Schema.String.annotate({ "description": "Internal endpoint identifier" })), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "Upstream provider response identifier" })), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether the request used a bring-your-own-key" }) + ), + "latency": Schema.optionalKey( + Schema.Number.annotate({ "description": "Response latency in milliseconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "model_permaslug": Schema.optionalKey(Schema.String.annotate({ "description": "Canonical model slug" })), + "provider_name": Schema.optionalKey( Schema.Literals([ + "AnyScale", + "Atoma", + "Cent-ML", + "CrofAI", + "Enfer", + "GoPomelo", + "HuggingFace", + "Hyperbolic", + "Hyperbolic 2", + "InoCloud", + "Kluster", + "Lambda", + "Lepton", + "Lynn 2", + "Lynn", + "Mancer", + "Modal", + "Nineteen", + "OctoAI", + "Recursal", + "Reflection", + "Replicate", + "SambaNova 2", + "SF Compute", + "Targon", + "Together 2", + "Ubicloud", + "01.AI", + "AkashML", "AI21", "AionLabs", "Alibaba", "Ambient", + "Baidu", "Amazon Bedrock", "Amazon Nova", "Anthropic", @@ -2648,26 +4919,37 @@ export const __schema5 = Schema.Array( "Clarifai", "Cloudflare", "Cohere", + "CoreWeave", + "Crucible", "Crusoe", + "Darkbloom", + "Decart", + "Deepgram", "DeepInfra", "DeepSeek", + "DekaLLM", + "DigitalOcean", "Featherless", "Fireworks", + "Fish Audio", "Friendli", "GMICloud", "Google", "Google AI Studio", "Groq", - "Hyperbolic", + "HeyGen", "Inception", "Inceptron", "InferenceNet", + "Ionstream", "Infermatic", "Io Net", + "Inferact vLLM", "Inflection", "Liquid", "Mara", "Mancer 2", + "Meta", "Minimax", "ModelRun", "Mistral", @@ -2676,15 +4958,22 @@ export const __schema5 = Schema.Array( "Morph", "NCompass", "Nebius", + "Nex AGI", "NextBit", "Novita", "Nvidia", "OpenAI", "OpenInference", "Parasail", + "Poolside", + "Perceptron", "Perplexity", "Phala", + "Recraft", + "Reka", "Relace", + "Sail Research", + "Sakana AI", "SambaNova", "Seed", "SiliconFlow", @@ -2693,6758 +4982,30620 @@ export const __schema5 = Schema.Array( "Stealth", "StreamLake", "Switchpoint", + "Tencent", + "Tenstorrent", "Together", "Upstage", "Venice", + "Wafer", "WandB", + "Quiver", + "Krea", + "Runway", "Xiaomi", "xAI", "Z.AI", "FakeProvider" - ]), - Schema.String - ]) -) -export type __schema11 = number -export const __schema11 = Schema.Number.check(Schema.isFinite()) -export type __schema13 = unknown -export const __schema13 = Schema.Unknown -export type __schema21 = string | null -export const __schema21 = Schema.Union([Schema.String, Schema.Null]) -export type __schema22 = - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - | null -export const __schema22 = Schema.Union([ - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]), - Schema.Null -]) -export type ModelName = string -export const ModelName = Schema.String -export type ChatMessageContentItemImage = { - readonly "type": "image_url" - readonly "image_url": { readonly "url": string; readonly "detail"?: "auto" | "low" | "high" } -} -export const ChatMessageContentItemImage = Schema.Struct({ - "type": Schema.Literal("image_url"), - "image_url": Schema.Struct({ - "url": Schema.String, - "detail": Schema.optionalKey(Schema.Literals(["auto", "low", "high"])) - }) -}) -export type ChatMessageContentItemAudio = { - readonly "type": "input_audio" - readonly "input_audio": { readonly "data": string; readonly "format": string } -} -export const ChatMessageContentItemAudio = Schema.Struct({ - "type": Schema.Literal("input_audio"), - "input_audio": Schema.Struct({ "data": Schema.String, "format": Schema.String }) + ]).annotate({ "description": "Name of the provider" }) + ), + "routed_service_tier": Schema.optionalKey( + Schema.Literals(["flex", "priority"]).annotate({ + "description": + "The service tier this request was routed to (e.g. flex, priority). The tier actually applied and billed is determined by the provider response and may differ." + }) + ), + "status": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "HTTP status code from the provider" }) +}).annotate({ + "description": "Details of a provider response for a generation attempt", + "identifier": "ProviderResponse" }) -export type ChatMessageContentItemVideo = { - readonly "type": "input_video" - readonly "video_url": { readonly "url": string } -} | { readonly "type": "video_url"; readonly "video_url": { readonly "url": string } } -export const ChatMessageContentItemVideo = Schema.Union([ - Schema.Struct({ "type": Schema.Literal("input_video"), "video_url": Schema.Struct({ "url": Schema.String }) }), - Schema.Struct({ "type": Schema.Literal("video_url"), "video_url": Schema.Struct({ "url": Schema.String }) }) -], { mode: "oneOf" }) -export type ChatMessageToolCall = { - readonly "id": string - readonly "type": "function" - readonly "function": { readonly "name": string; readonly "arguments": string } -} -export const ChatMessageToolCall = Schema.Struct({ - "id": Schema.String, - "type": Schema.Literal("function"), - "function": Schema.Struct({ "name": Schema.String, "arguments": Schema.String }) +export type ProviderSort = "price" | "throughput" | "latency" | "exacto" +export const ProviderSort = Schema.Literals(["price", "throughput", "latency", "exacto"]).annotate({ + "description": "The provider sorting strategy (price, throughput, latency)", + "identifier": "ProviderSort" }) -export type ChatMessageTokenLogprob = { - readonly "token": string - readonly "logprob": number - readonly "bytes": ReadonlyArray | null - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "logprob": number; readonly "bytes": ReadonlyArray | null } - > +export type ProviderSortConfig = { + readonly "by"?: "price" | "throughput" | "latency" | "exacto" | null + readonly "partition"?: "model" | "none" | null } -export const ChatMessageTokenLogprob = Schema.Struct({ - "token": Schema.String, - "logprob": Schema.Number.check(Schema.isFinite()), - "bytes": Schema.Union([Schema.Array(Schema.Number.check(Schema.isFinite())), Schema.Null]), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "logprob": Schema.Number.check(Schema.isFinite()), - "bytes": Schema.Union([Schema.Array(Schema.Number.check(Schema.isFinite())), Schema.Null]) +export const ProviderSortConfig = Schema.Struct({ + "by": Schema.optionalKey( + Schema.Union([ + Schema.Literal("price"), + Schema.Literal("throughput"), + Schema.Literal("latency"), + Schema.Literal("exacto"), + Schema.Null + ]).annotate({ "description": "The provider sorting strategy (price, throughput, latency)" }) + ), + "partition": Schema.optionalKey( + Schema.Union([Schema.Literal("model"), Schema.Literal("none"), Schema.Null]).annotate({ + "description": + "Partitioning strategy for sorting: \"model\" (default) groups endpoints by model before sorting (fallback models remain fallbacks), \"none\" sorts all endpoints together regardless of model." }) ) +}).annotate({ + "description": "The provider sorting strategy (price, throughput, latency)", + "identifier": "ProviderSortConfig" }) -export type ChatGenerationTokenUsage = { - readonly "completion_tokens": number - readonly "prompt_tokens": number - readonly "total_tokens": number - readonly "completion_tokens_details"?: { - readonly "reasoning_tokens"?: number | null - readonly "audio_tokens"?: number | null - readonly "accepted_prediction_tokens"?: number | null - readonly "rejected_prediction_tokens"?: number | null - } | null - readonly "prompt_tokens_details"?: { - readonly "cached_tokens"?: number - readonly "cache_write_tokens"?: number - readonly "audio_tokens"?: number - readonly "video_tokens"?: number - } | null -} -export const ChatGenerationTokenUsage = Schema.Struct({ - "completion_tokens": Schema.Number.check(Schema.isFinite()), - "prompt_tokens": Schema.Number.check(Schema.isFinite()), - "total_tokens": Schema.Number.check(Schema.isFinite()), - "completion_tokens_details": Schema.optionalKey(Schema.Union([ - Schema.Struct({ - "reasoning_tokens": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "audio_tokens": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "accepted_prediction_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null]) - ), - "rejected_prediction_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null]) - ) - }), - Schema.Null - ])), - "prompt_tokens_details": Schema.optionalKey(Schema.Union([ - Schema.Struct({ - "cached_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "cache_write_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "audio_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "video_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }), - Schema.Null - ])) +export type Quantization = "int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown" +export const Quantization = Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"]) + .annotate({ "identifier": "Quantization" }) +export type RangeCapability = { readonly "max": number; readonly "min": number; readonly "type": "range" } +export const RangeCapability = Schema.Struct({ + "max": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "min": Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("range") +}).annotate({ + "description": "A parameter that accepts any value within an inclusive numeric range.", + "identifier": "RangeCapability" }) -export type ChatCompletionFinishReason = "tool_calls" | "stop" | "length" | "content_filter" | "error" -export const ChatCompletionFinishReason = Schema.Literals(["tool_calls", "stop", "length", "content_filter", "error"]) -export type JSONSchemaConfig = { - readonly "name": string - readonly "description"?: string - readonly "schema"?: {} - readonly "strict"?: boolean | null -} -export const JSONSchemaConfig = Schema.Struct({ - "name": Schema.String.check(Schema.isMaxLength(64)), - "description": Schema.optionalKey(Schema.String), - "schema": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) -}) -export type ResponseFormatTextGrammar = { readonly "type": "grammar"; readonly "grammar": string } -export const ResponseFormatTextGrammar = Schema.Struct({ "type": Schema.Literal("grammar"), "grammar": Schema.String }) -export type ChatMessageContentItemCacheControl = { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } -export const ChatMessageContentItemCacheControl = Schema.Struct({ - "type": Schema.Literal("ephemeral"), - "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) -}) -export type NamedToolChoice = { readonly "type": "function"; readonly "function": { readonly "name": string } } -export const NamedToolChoice = Schema.Struct({ - "type": Schema.Literal("function"), - "function": Schema.Struct({ "name": Schema.String }) -}) -export type ChatStreamOptions = { readonly "include_usage"?: boolean } -export const ChatStreamOptions = Schema.Struct({ "include_usage": Schema.optionalKey(Schema.Boolean) }) -export type ChatStreamingMessageToolCall = { - readonly "index": number - readonly "id"?: string | null - readonly "type"?: "function" | null - readonly "function"?: { readonly "name"?: string | null; readonly "arguments"?: string } +export type RankingsDailyItem = { + readonly "date": string + readonly "model_permaslug": string + readonly "total_tokens": string } -export const ChatStreamingMessageToolCall = Schema.Struct({ - "index": Schema.Number.check(Schema.isFinite()), - "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "type": Schema.optionalKey(Schema.Union([Schema.Literal("function"), Schema.Null])), - "function": Schema.optionalKey( - Schema.Struct({ - "name": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "arguments": Schema.optionalKey(Schema.String) - }) - ) -}) -export type ChatError = { - readonly "error": { - readonly "code": string | number | null - readonly "message": string - readonly "param"?: string | null - readonly "type"?: string | null - } +export const RankingsDailyItem = Schema.Struct({ + "date": Schema.String.annotate({ "description": "UTC calendar date the row is aggregated over (YYYY-MM-DD)." }), + "model_permaslug": Schema.String.annotate({ + "description": + "Model variant permaslug (e.g. `openai/gpt-4o-2024-05-13`, `openai/gpt-4o-2024-05-13:free`). Non-default variants include a `:variant` suffix and are ranked as their own entry. The reserved value `other` denotes the aggregated row covering every model outside the daily top 50 for that date — always sorted last within its date." + }), + "total_tokens": Schema.String.annotate({ + "description": + "Sum of `prompt_tokens + completion_tokens` for the day, returned as a decimal string so 64-bit values are not truncated." + }) +}).annotate({ "identifier": "RankingsDailyItem" }) +export type RankingsDailyMeta = { + readonly "as_of": string + readonly "end_date": string + readonly "start_date": string + readonly "version": "v1" } -export const ChatError = Schema.Struct({ - "error": Schema.Struct({ - "code": Schema.Union([Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite())]), Schema.Null]), - "message": Schema.String, - "param": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "type": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +export const RankingsDailyMeta = Schema.Struct({ + "as_of": Schema.String.annotate({ + "description": + "ISO-8601 timestamp of when the response was generated. Reflects data-freshness because the underlying materialized view continuously ingests upstream events." + }), + "end_date": Schema.String.annotate({ "description": "Resolved end of the date window (UTC, inclusive)." }), + "start_date": Schema.String.annotate({ "description": "Resolved start of the date window (UTC, inclusive)." }), + "version": Schema.Literal("v1").annotate({ + "description": "Dataset version. Field names and grain are stable for the life of `v1`." }) +}).annotate({ "identifier": "RankingsDailyMeta" }) +export type ReasoningContext = "auto" | "all_turns" | "current_turn" | null +export const ReasoningContext = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("all_turns"), + Schema.Literal("current_turn"), + Schema.Null +]).annotate({ + "description": + "Controls which reasoning is available to the model. `auto` uses the model default (same as omitting); `all_turns` includes reasoning from earlier turns passed in input; `current_turn` limits to the current turn only. Only supported by OpenAI GPT-5.6 and newer.", + "identifier": "ReasoningContext" }) -export type OpenAIResponsesAnnotation = FileCitation | URLCitation | FilePath -export const OpenAIResponsesAnnotation = Schema.Union([FileCitation, URLCitation, FilePath]) -export type OutputItemReasoning = { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" +export type ReasoningDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.reasoning_text.delta" } -export const OutputItemReasoning = Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) +export const ReasoningDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_text.delta") +}).annotate({ + "description": "Event emitted when reasoning text delta is streamed", + "identifier": "ReasoningDeltaEvent" }) -export type ResponsesOutputItemReasoning = { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" -} -export const ResponsesOutputItemReasoning = Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ) -}).annotate({ "description": "An output item containing reasoning" }) -export type OpenResponsesReasoningSummaryPartAddedEvent = { - readonly "type": "response.reasoning_summary_part.added" +export type ReasoningDoneEvent = { + readonly "content_index": number + readonly "item_id": string readonly "output_index": number + readonly "sequence_number": number + readonly "text": string + readonly "type": "response.reasoning_text.done" +} +export const ReasoningDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.reasoning_text.done") +}).annotate({ + "description": "Event emitted when reasoning text streaming is complete", + "identifier": "ReasoningDoneEvent" +}) +export type ReasoningEffort = "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null +export const ReasoningEffort = Schema.Union([ + Schema.Literal("max"), + Schema.Literal("xhigh"), + Schema.Literal("high"), + Schema.Literal("medium"), + Schema.Literal("low"), + Schema.Literal("minimal"), + Schema.Literal("none"), + Schema.Null +]).annotate({ "identifier": "ReasoningEffort" }) +export type ReasoningFormat = + | "unknown" + | "openai-responses-v1" + | "azure-openai-responses-v1" + | "xai-responses-v1" + | "meta-responses-v1" + | "anthropic-claude-v1" + | "google-gemini-v1" + | null +export const ReasoningFormat = Schema.Union([ + Schema.Literal("unknown"), + Schema.Literal("openai-responses-v1"), + Schema.Literal("azure-openai-responses-v1"), + Schema.Literal("xai-responses-v1"), + Schema.Literal("meta-responses-v1"), + Schema.Literal("anthropic-claude-v1"), + Schema.Literal("google-gemini-v1"), + Schema.Null +]).annotate({ "identifier": "ReasoningFormat" }) +export type ReasoningMode = "standard" | "pro" | null +export const ReasoningMode = Schema.Union([Schema.Literal("standard"), Schema.Literal("pro"), Schema.Null]).annotate({ + "description": + "Selects the reasoning mode. `standard` is the default; `pro` engages deeper reasoning on models that support it, billed at standard token rates. Only supported by OpenAI GPT-5.6 and newer.", + "identifier": "ReasoningMode" +}) +export type ReasoningSummaryText = { readonly "text": string; readonly "type": "summary_text" } +export const ReasoningSummaryText = Schema.Struct({ "text": Schema.String, "type": Schema.Literal("summary_text") }) + .annotate({ "identifier": "ReasoningSummaryText" }) +export type ReasoningSummaryTextDeltaEvent = { + readonly "delta": string readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number readonly "summary_index": number - readonly "part": ReasoningSummaryText + readonly "type": "response.reasoning_summary_text.delta" +} +export const ReasoningSummaryTextDeltaEvent = Schema.Struct({ + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_text.delta") +}).annotate({ + "description": "Event emitted when reasoning summary text delta is streamed", + "identifier": "ReasoningSummaryTextDeltaEvent" +}) +export type ReasoningSummaryTextDoneEvent = { + readonly "item_id": string + readonly "output_index": number readonly "sequence_number": number + readonly "summary_index": number + readonly "text": string + readonly "type": "response.reasoning_summary_text.done" } -export const OpenResponsesReasoningSummaryPartAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_part.added"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const ReasoningSummaryTextDoneEvent = Schema.Struct({ "item_id": Schema.String, - "summary_index": Schema.Number.check(Schema.isFinite()), - "part": ReasoningSummaryText, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a reasoning summary part is added" }) -export type OpenResponsesReasoningSummaryPartDoneEvent = { - readonly "type": "response.reasoning_summary_part.done" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.reasoning_summary_text.done") +}).annotate({ + "description": "Event emitted when reasoning summary text streaming is complete", + "identifier": "ReasoningSummaryTextDoneEvent" +}) +export type ReasoningSummaryVerbosity = "auto" | "concise" | "detailed" | null +export const ReasoningSummaryVerbosity = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("concise"), + Schema.Literal("detailed"), + Schema.Null +]).annotate({ "identifier": "ReasoningSummaryVerbosity" }) +export type ReasoningTextContent = { readonly "text": string; readonly "type": "reasoning_text" } +export const ReasoningTextContent = Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }) + .annotate({ "identifier": "ReasoningTextContent" }) +export type RefusalDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.refusal.delta" +} +export const RefusalDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.delta") +}).annotate({ "description": "Event emitted when a refusal delta is streamed", "identifier": "RefusalDeltaEvent" }) +export type RefusalDoneEvent = { + readonly "content_index": number readonly "item_id": string - readonly "summary_index": number - readonly "part": ReasoningSummaryText + readonly "output_index": number + readonly "refusal": string readonly "sequence_number": number + readonly "type": "response.refusal.done" } -export const OpenResponsesReasoningSummaryPartDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.reasoning_summary_part.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const RefusalDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "summary_index": Schema.Number.check(Schema.isFinite()), - "part": ReasoningSummaryText, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a reasoning summary part is complete" }) -export type OpenResponsesReasoning = { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" -} -export const OpenResponsesReasoning = Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "signature": Schema.optionalKey(Schema.String), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]) + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "refusal": Schema.String, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.refusal.done") +}).annotate({ "description": "Event emitted when refusal streaming is complete", "identifier": "RefusalDoneEvent" }) +export type Objects_149 = { readonly [x: string]: string } +export const Objects_149 = Schema.Record( + Schema.String, + Schema.String.check(Schema.isMaxLength(512).annotate({ "expected": "a value with a length of at most 512" })) +) +export type RequestTimeoutResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const RequestTimeoutResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ) -}).annotate({ "description": "Reasoning output item with signature and format extensions" }) -export type OutputItemWebSearchCall = { - readonly "type": "web_search_call" - readonly "id": string - readonly "status": WebSearchStatus +}).annotate({ "description": "Error data for RequestTimeoutResponse", "identifier": "RequestTimeoutResponseErrorData" }) +export type ResponseHealingPlugin = { readonly "enabled"?: boolean; readonly "id": "response-healing" } +export const ResponseHealingPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the response-healing plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("response-healing") +}).annotate({ "identifier": "ResponseHealingPlugin" }) +export type ResponseIncludesEnum = + | "file_search_call.results" + | "message.input_image.image_url" + | "computer_call_output.output.image_url" + | "reasoning.encrypted_content" + | "code_interpreter_call.outputs" +export const ResponseIncludesEnum = Schema.Literals([ + "file_search_call.results", + "message.input_image.image_url", + "computer_call_output.output.image_url", + "reasoning.encrypted_content", + "code_interpreter_call.outputs" +]).annotate({ "identifier": "ResponseIncludesEnum" }) +export type ResponsesErrorField = { + readonly "code": + | "server_error" + | "rate_limit_exceeded" + | "invalid_prompt" + | "vector_store_timeout" + | "invalid_image" + | "invalid_image_format" + | "invalid_base64_image" + | "invalid_image_url" + | "image_too_large" + | "image_too_small" + | "image_parse_error" + | "image_content_policy_violation" + | "invalid_image_mode" + | "image_file_too_large" + | "unsupported_image_media_type" + | "empty_image_file" + | "failed_to_download_image" + | "image_file_not_found" + | "bio_policy" + readonly "message": string + readonly [x: string]: Schema.Json +} | null +export const ResponsesErrorField = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "code": Schema.Literals([ + "server_error", + "rate_limit_exceeded", + "invalid_prompt", + "vector_store_timeout", + "invalid_image", + "invalid_image_format", + "invalid_base64_image", + "invalid_image_url", + "image_too_large", + "image_too_small", + "image_parse_error", + "image_content_policy_violation", + "invalid_image_mode", + "image_file_too_large", + "unsupported_image_media_type", + "empty_image_file", + "failed_to_download_image", + "image_file_not_found", + "bio_policy" + ]), + "message": Schema.String + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null +]).annotate({ "description": "Error information returned from the API", "identifier": "ResponsesErrorField" }) +export type RouterAttempt = { readonly "model": string; readonly "provider": string; readonly "status": number } +export const RouterAttempt = Schema.Struct({ + "model": Schema.String, + "provider": Schema.String, + "status": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "RouterAttempt" }) +export type RouterParams = { + readonly "quality_floor"?: number + readonly "throughput_floor"?: number + readonly "version_group"?: string } -export const OutputItemWebSearchCall = Schema.Struct({ - "type": Schema.Literal("web_search_call"), - "id": Schema.String, - "status": WebSearchStatus +export const RouterParams = Schema.Struct({ + "quality_floor": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "throughput_floor": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "version_group": Schema.optionalKey(Schema.String) +}).annotate({ "identifier": "RouterParams" }) +export type RoutingStrategy = + | "direct" + | "auto" + | "free" + | "latest" + | "alias" + | "fallback" + | "pareto" + | "bodybuilder" + | "fusion" +export const RoutingStrategy = Schema.Literals([ + "direct", + "auto", + "free", + "latest", + "alias", + "fallback", + "pareto", + "bodybuilder", + "fusion" +]).annotate({ "identifier": "RoutingStrategy" }) +export type SandboxSleepAfterSeconds = number +export const SandboxSleepAfterSeconds = Schema.Number.annotate({ + "description": + "How long (in seconds) the container stays warm after its last command before sleeping, freeing its capacity slot. Idle-based: each command renews the timer. Defaults to 900 (15 minutes); capped at 2592000 (30 days)." +}).check(Schema.isInt().annotate({ "expected": "an integer", "identifier": "SandboxSleepAfterSeconds" })) +export type SearchContextSizeEnum = "low" | "medium" | "high" +export const SearchContextSizeEnum = Schema.Literals(["low", "medium", "high"]).annotate({ + "description": "Size of the search context for web search tools", + "identifier": "SearchContextSizeEnum" }) -export type ResponsesWebSearchCallOutput = { - readonly "type": "web_search_call" - readonly "id": string - readonly "status": WebSearchStatus -} -export const ResponsesWebSearchCallOutput = Schema.Struct({ - "type": Schema.Literal("web_search_call"), - "id": Schema.String, - "status": WebSearchStatus +export type SearchModelsServerToolConfig = { readonly "max_results"?: number } +export const SearchModelsServerToolConfig = Schema.Struct({ + "max_results": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of models to return. Defaults to 5, max 20." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) +}).annotate({ + "description": "Configuration for the openrouter:experimental__search_models server tool", + "identifier": "SearchModelsServerToolConfig" }) -export type OutputItemFileSearchCall = { - readonly "type": "file_search_call" - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": WebSearchStatus -} -export const OutputItemFileSearchCall = Schema.Struct({ - "type": Schema.Literal("file_search_call"), - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": WebSearchStatus +export type SearchQualityLevel = "low" | "medium" | "high" +export const SearchQualityLevel = Schema.Literals(["low", "medium", "high"]).annotate({ + "description": + "How much context to retrieve per result. Applies to Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, pins a fixed per-result character cap (low=5,000, medium=15,000, high=30,000); when omitted, Exa picks an adaptive size per query and document (typically ~2,000–4,000 characters per result). For Parallel, controls the total characters across all results; when omitted, Parallel uses its own default size. For Perplexity, maps directly to the Search API's native search_context_size parameter. Overridden by `max_characters` when both are set.", + "identifier": "SearchQualityLevel" }) -export type ResponsesOutputItemFileSearchCall = { - readonly "type": "file_search_call" - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": WebSearchStatus +export type Objects_150 = { + readonly "tool_calls_executed"?: number | null + readonly "tool_calls_requested"?: number | null + readonly "web_search_requests"?: number | null + readonly [x: string]: Schema.Json } -export const ResponsesOutputItemFileSearchCall = Schema.Struct({ - "type": Schema.Literal("file_search_call"), - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": WebSearchStatus +export const Objects_150 = Schema.StructWithRest( + Schema.Struct({ + "tool_calls_executed": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Number of OpenRouter server tool calls that executed and produced a result." + }) + ), + "tool_calls_requested": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Total number of OpenRouter server-orchestrated tool calls the model requested, across all tool types. Provider-native tools (e.g. native web search) are not counted here." + }) + ), + "web_search_requests": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Number of web searches performed by server-side tools. For server-orchestrated tool calls a web search is also counted in tool_calls_requested; provider-native web search may report web_search_requests only. Do not sum the two." + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type ServiceTier = "auto" | "default" | "flex" | "priority" | "scale" | null +export const ServiceTier = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Null +]).annotate({ "identifier": "ServiceTier" }) +export type ServiceUnavailableResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const ServiceUnavailableResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for ServiceUnavailableResponse", + "identifier": "ServiceUnavailableResponseErrorData" }) -export type OutputItemImageGenerationCall = { - readonly "type": "image_generation_call" - readonly "id": string - readonly "result"?: string - readonly "status": ImageGenerationStatus -} -export const OutputItemImageGenerationCall = Schema.Struct({ - "type": Schema.Literal("image_generation_call"), - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": ImageGenerationStatus +export type ShellCallStatus = "in_progress" | "completed" | "incomplete" +export const ShellCallStatus = Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "description": "Status of a shell call or its output.", + "identifier": "ShellCallStatus" }) -export type ResponsesImageGenerationCall = { - readonly "type": "image_generation_call" - readonly "id": string - readonly "result"?: string - readonly "status": ImageGenerationStatus -} -export const ResponsesImageGenerationCall = Schema.Struct({ - "type": Schema.Literal("image_generation_call"), - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": ImageGenerationStatus +export type ShellServerTool = { readonly "type": "shell" } +export const ShellServerTool = Schema.Struct({ "type": Schema.Literal("shell") }).annotate({ + "description": "Shell tool configuration", + "identifier": "ShellServerTool" }) -export type OpenResponsesFunctionToolCall = { - readonly "type": "function_call" - readonly "call_id": string - readonly "name": string - readonly "arguments": string - readonly "id": string - readonly "status"?: ToolCallStatus -} -export const OpenResponsesFunctionToolCall = Schema.Struct({ - "type": Schema.Literal("function_call"), - "call_id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "id": Schema.String, - "status": Schema.optionalKey(ToolCallStatus) -}).annotate({ "description": "A function call initiated by the model" }) -export type OpenResponsesFunctionCallOutput = { - readonly "type": "function_call_output" - readonly "id"?: string - readonly "call_id": string - readonly "output": string - readonly "status"?: ToolCallStatus -} -export const OpenResponsesFunctionCallOutput = Schema.Struct({ - "type": Schema.Literal("function_call_output"), - "id": Schema.optionalKey(Schema.String), - "call_id": Schema.String, - "output": Schema.String, - "status": Schema.optionalKey(ToolCallStatus) -}).annotate({ "description": "The output from a function call execution" }) -export type OpenResponsesWebSearchPreviewTool = { - readonly "type": "web_search_preview" - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: WebSearchPreviewToolUserLocation -} -export const OpenResponsesWebSearchPreviewTool = Schema.Struct({ - "type": Schema.Literal("web_search_preview"), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(WebSearchPreviewToolUserLocation) -}).annotate({ "description": "Web search preview tool configuration" }) -export type OpenResponsesWebSearchPreview20250311Tool = { - readonly "type": "web_search_preview_2025_03_11" - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: WebSearchPreviewToolUserLocation -} -export const OpenResponsesWebSearchPreview20250311Tool = Schema.Struct({ - "type": Schema.Literal("web_search_preview_2025_03_11"), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(WebSearchPreviewToolUserLocation) -}).annotate({ "description": "Web search preview tool configuration (2025-03-11 version)" }) -export type OpenResponsesWebSearchTool = { - readonly "type": "web_search" - readonly "filters"?: { readonly "allowed_domains"?: ReadonlyArray } - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: ResponsesWebSearchUserLocation -} -export const OpenResponsesWebSearchTool = Schema.Struct({ - "type": Schema.Literal("web_search"), - "filters": Schema.optionalKey(Schema.Struct({ "allowed_domains": Schema.optionalKey(Schema.Array(Schema.String)) })), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(ResponsesWebSearchUserLocation) -}).annotate({ "description": "Web search tool configuration" }) -export type OpenResponsesWebSearch20250826Tool = { - readonly "type": "web_search_2025_08_26" - readonly "filters"?: { readonly "allowed_domains"?: ReadonlyArray } - readonly "search_context_size"?: ResponsesSearchContextSize - readonly "user_location"?: ResponsesWebSearchUserLocation -} -export const OpenResponsesWebSearch20250826Tool = Schema.Struct({ - "type": Schema.Literal("web_search_2025_08_26"), - "filters": Schema.optionalKey(Schema.Struct({ "allowed_domains": Schema.optionalKey(Schema.Array(Schema.String)) })), - "search_context_size": Schema.optionalKey(ResponsesSearchContextSize), - "user_location": Schema.optionalKey(ResponsesWebSearchUserLocation) -}).annotate({ "description": "Web search tool configuration (2025-08-26 version)" }) -export type OpenAIResponsesReasoningConfig = { - readonly "effort"?: OpenAIResponsesReasoningEffort - readonly "summary"?: ReasoningSummaryVerbosity -} -export const OpenAIResponsesReasoningConfig = Schema.Struct({ - "effort": Schema.optionalKey(OpenAIResponsesReasoningEffort), - "summary": Schema.optionalKey(ReasoningSummaryVerbosity) +export type ShellServerToolEngine = "auto" | "openrouter" +export const ShellServerToolEngine = Schema.Literals(["auto", "openrouter"]).annotate({ + "description": + "Which shell engine to use. \"openrouter\" runs commands server-side in the OpenRouter sandbox. \"auto\" (default) keeps the provider's native hosted shell when available (OpenAI); on other providers the call is routed to the OpenRouter sandbox.", + "identifier": "ShellServerToolEngine" }) -export type OpenResponsesReasoningConfig = { - readonly "effort"?: OpenAIResponsesReasoningEffort - readonly "summary"?: ReasoningSummaryVerbosity - readonly "max_tokens"?: number - readonly "enabled"?: boolean -} -export const OpenResponsesReasoningConfig = Schema.Struct({ - "effort": Schema.optionalKey(OpenAIResponsesReasoningEffort), - "summary": Schema.optionalKey(ReasoningSummaryVerbosity), - "max_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "enabled": Schema.optionalKey(Schema.Boolean) -}).annotate({ "description": "Configuration for reasoning mode in the response" }) -export type ResponseFormatTextConfig = - | ResponsesFormatText - | ResponsesFormatJSONObject - | ResponsesFormatTextJSONSchemaConfig -export const ResponseFormatTextConfig = Schema.Union([ - ResponsesFormatText, - ResponsesFormatJSONObject, - ResponsesFormatTextJSONSchemaConfig -]).annotate({ "description": "Text response format configuration" }) -export type OpenResponsesLogProbs = { +export type StopServerToolsWhenFinishReasonIs = { readonly "reason": string; readonly "type": "finish_reason_is" } +export const StopServerToolsWhenFinishReasonIs = Schema.Struct({ + "reason": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "type": Schema.Literal("finish_reason_is") +}).annotate({ + "description": "Stop when the upstream model emits this finish reason (e.g. `length`).", + "identifier": "StopServerToolsWhenFinishReasonIs" +}) +export type StopServerToolsWhenHasToolCall = { readonly "tool_name": string; readonly "type": "has_tool_call" } +export const StopServerToolsWhenHasToolCall = Schema.Struct({ + "tool_name": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "type": Schema.Literal("has_tool_call") +}).annotate({ + "description": "Stop after a tool with this name has been called.", + "identifier": "StopServerToolsWhenHasToolCall" +}) +export type StopServerToolsWhenMaxCost = { readonly "max_cost_in_dollars": number; readonly "type": "max_cost" } +export const StopServerToolsWhenMaxCost = Schema.Struct({ + "max_cost_in_dollars": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "type": Schema.Literal("max_cost") +}).annotate({ + "description": "Stop once cumulative cost across the loop exceeds this dollar threshold.", + "identifier": "StopServerToolsWhenMaxCost" +}) +export type StopServerToolsWhenMaxTokensUsed = { readonly "max_tokens": number; readonly "type": "max_tokens_used" } +export const StopServerToolsWhenMaxTokensUsed = Schema.Struct({ + "max_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("max_tokens_used") +}).annotate({ + "description": "Stop once cumulative token usage across the loop exceeds this threshold.", + "identifier": "StopServerToolsWhenMaxTokensUsed" +}) +export type StopServerToolsWhenStepCountIs = { readonly "step_count": number; readonly "type": "step_count_is" } +export const StopServerToolsWhenStepCountIs = Schema.Struct({ + "step_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("step_count_is") +}).annotate({ + "description": "Stop after the agent loop has executed this many steps.", + "identifier": "StopServerToolsWhenStepCountIs" +}) +export type StreamLogprob = { + readonly "bytes"?: ReadonlyArray readonly "logprob": number readonly "token": string - readonly "top_logprobs"?: ReadonlyArray + readonly "top_logprobs"?: ReadonlyArray< + { readonly "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } + > } -export const OpenResponsesLogProbs = Schema.Struct({ - "logprob": Schema.Number.check(Schema.isFinite()), +export const StreamLogprob = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), "token": Schema.String, - "top_logprobs": Schema.optionalKey(Schema.Array(OpenResponsesTopLogprobs)) -}).annotate({ "description": "Log probability information for a token" }) -export type BadRequestResponse = { readonly "error": BadRequestResponseErrorData; readonly "user_id"?: string } -export const BadRequestResponse = Schema.Struct({ - "error": BadRequestResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Bad Request - Invalid request parameters or malformed input" }) -export type UnauthorizedResponse = { readonly "error": UnauthorizedResponseErrorData; readonly "user_id"?: string } -export const UnauthorizedResponse = Schema.Struct({ - "error": UnauthorizedResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Unauthorized - Authentication required or invalid credentials" }) -export type PaymentRequiredResponse = { - readonly "error": PaymentRequiredResponseErrorData - readonly "user_id"?: string -} -export const PaymentRequiredResponse = Schema.Struct({ - "error": PaymentRequiredResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Payment Required - Insufficient credits or quota to complete request" }) -export type NotFoundResponse = { readonly "error": NotFoundResponseErrorData; readonly "user_id"?: string } -export const NotFoundResponse = Schema.Struct({ - "error": NotFoundResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Not Found - Resource does not exist" }) -export type RequestTimeoutResponse = { readonly "error": RequestTimeoutResponseErrorData; readonly "user_id"?: string } -export const RequestTimeoutResponse = Schema.Struct({ - "error": RequestTimeoutResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Request Timeout - Operation exceeded time limit" }) -export type PayloadTooLargeResponse = { - readonly "error": PayloadTooLargeResponseErrorData - readonly "user_id"?: string -} -export const PayloadTooLargeResponse = Schema.Struct({ - "error": PayloadTooLargeResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Payload Too Large - Request payload exceeds size limits" }) -export type UnprocessableEntityResponse = { - readonly "error": UnprocessableEntityResponseErrorData - readonly "user_id"?: string -} -export const UnprocessableEntityResponse = Schema.Struct({ - "error": UnprocessableEntityResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Unprocessable Entity - Semantic validation failure" }) -export type TooManyRequestsResponse = { - readonly "error": TooManyRequestsResponseErrorData - readonly "user_id"?: string -} -export const TooManyRequestsResponse = Schema.Struct({ - "error": TooManyRequestsResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Too Many Requests - Rate limit exceeded" }) -export type InternalServerResponse = { readonly "error": InternalServerResponseErrorData; readonly "user_id"?: string } -export const InternalServerResponse = Schema.Struct({ - "error": InternalServerResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Internal Server Error - Unexpected server error" }) -export type BadGatewayResponse = { readonly "error": BadGatewayResponseErrorData; readonly "user_id"?: string } -export const BadGatewayResponse = Schema.Struct({ - "error": BadGatewayResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Bad Gateway - Provider/upstream API failure" }) -export type ServiceUnavailableResponse = { - readonly "error": ServiceUnavailableResponseErrorData - readonly "user_id"?: string + "top_logprobs": Schema.optionalKey(Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "token": Schema.optionalKey(Schema.String) + }).annotate({ "description": "Alternative token with its log probability" }) + )) +}).annotate({ "description": "Log probability information for a token", "identifier": "StreamLogprob" }) +export type StreamLogprobTopLogprob = { + readonly "bytes"?: ReadonlyArray + readonly "logprob"?: number + readonly "token"?: string } -export const ServiceUnavailableResponse = Schema.Struct({ - "error": ServiceUnavailableResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Service Unavailable - Service temporarily unavailable" }) -export type EdgeNetworkTimeoutResponse = { - readonly "error": EdgeNetworkTimeoutResponseErrorData - readonly "user_id"?: string +export const StreamLogprobTopLogprob = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "token": Schema.optionalKey(Schema.String) +}).annotate({ "description": "Alternative token with its log probability", "identifier": "StreamLogprobTopLogprob" }) +export type STTInputAudio = { readonly "data": string; readonly "format": string } +export const STTInputAudio = Schema.Struct({ + "data": Schema.String.annotate({ "description": "Base64-encoded audio data (raw bytes, not a data URI)" }), + "format": Schema.String.annotate({ + "description": "Audio format (e.g., wav, mp3, flac, m4a, ogg, webm, aac). Supported formats vary by provider." + }) +}).annotate({ "description": "Base64-encoded audio to transcribe", "identifier": "STTInputAudio" }) +export type STTSegment = { + readonly "avg_logprob"?: number + readonly "compression_ratio"?: number + readonly "end": number + readonly "id": number + readonly "no_speech_prob"?: number + readonly "seek"?: number + readonly "start": number + readonly "temperature"?: number + readonly "text": string + readonly "tokens"?: ReadonlyArray } -export const EdgeNetworkTimeoutResponse = Schema.Struct({ - "error": EdgeNetworkTimeoutResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Infrastructure Timeout - Provider request timed out at edge network" }) -export type ProviderOverloadedResponse = { - readonly "error": ProviderOverloadedResponseErrorData - readonly "user_id"?: string +export const STTSegment = Schema.Struct({ + "avg_logprob": Schema.optionalKey( + Schema.Number.annotate({ "description": "Average log probability of the segment", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "compression_ratio": Schema.optionalKey( + Schema.Number.annotate({ "description": "Compression ratio of the segment", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "end": Schema.Number.annotate({ "description": "Segment end time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "id": Schema.Number.annotate({ "description": "Segment index within the transcript" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "no_speech_prob": Schema.optionalKey( + Schema.Number.annotate({ "description": "Probability the segment contains no speech", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "seek": Schema.optionalKey( + Schema.Number.annotate({ "description": "Seek offset of the segment" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "start": Schema.Number.annotate({ "description": "Segment start time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "Temperature used for the segment", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "text": Schema.String.annotate({ "description": "Transcribed text of the segment" }), + "tokens": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))).annotate({ + "description": "Token IDs of the segment" + }) + ) +}).annotate({ + "description": "A timestamped transcript segment, returned when response_format is verbose_json", + "identifier": "STTSegment" +}) +export type STTTimestampGranularity = "word" | "segment" +export const STTTimestampGranularity = Schema.Literals(["word", "segment"]).annotate({ + "description": "A timestamp detail level for verbose_json transcription responses.", + "identifier": "STTTimestampGranularity" +}) +export type STTUsage = { + readonly "cost"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "seconds"?: number + readonly "total_tokens"?: number } -export const ProviderOverloadedResponse = Schema.Struct({ - "error": ProviderOverloadedResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Provider Overloaded - Provider is temporarily overloaded" }) -export type OpenResponsesEasyInputMessage = { - readonly "type"?: "message" - readonly "role": "user" | "system" | "assistant" | "developer" - readonly "content": - | ReadonlyArray< - | ResponseInputText - | { readonly "type": "input_image"; readonly "detail": "auto" | "high" | "low"; readonly "image_url"?: string } - | ResponseInputFile - | ResponseInputAudio - | ResponseInputVideo - > - | string +export const STTUsage = Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Number.annotate({ "description": "Total cost of the request in USD", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of input tokens billed for this request" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of output tokens generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "seconds": Schema.optionalKey( + Schema.Number.annotate({ "description": "Duration of the input audio in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "total_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Total number of tokens used (input + output)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) +}).annotate({ "description": "Aggregated usage statistics for the request", "identifier": "STTUsage" }) +export type STTWord = { + readonly "end": number + readonly "speaker"?: number + readonly "start": number + readonly "word": string } -export const OpenResponsesEasyInputMessage = Schema.Struct({ - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "assistant", "developer"]), - "content": Schema.Union([ - Schema.Array( - Schema.Union([ - ResponseInputText, - Schema.Struct({ - "type": Schema.Literal("input_image"), - "detail": Schema.Literals(["auto", "high", "low"]), - "image_url": Schema.optionalKey(Schema.String) - }).annotate({ "description": "Image input content item" }), - ResponseInputFile, - ResponseInputAudio, - ResponseInputVideo - ], { mode: "oneOf" }) - ), - Schema.String - ]) +export const STTWord = Schema.Struct({ + "end": Schema.Number.annotate({ "description": "Word end time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "speaker": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Zero-based speaker index for the word, present when the provider returns speaker diarization" + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "start": Schema.Number.annotate({ "description": "Word start time in seconds", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "word": Schema.String.annotate({ "description": "The transcribed word" }) +}).annotate({ + "description": "A timestamped word, returned when the provider includes word-level timestamps", + "identifier": "STTWord" }) -export type OpenResponsesInputMessageItem = { - readonly "id"?: string - readonly "type"?: "message" - readonly "role": "user" | "system" | "developer" - readonly "content": ReadonlyArray< - | ResponseInputText - | { readonly "type": "input_image"; readonly "detail": "auto" | "high" | "low"; readonly "image_url"?: string } - | ResponseInputFile - | ResponseInputAudio - | ResponseInputVideo - > +export type SubagentNestedTool = { readonly "parameters"?: {}; readonly "type": string } +export const SubagentNestedTool = Schema.Struct({ + "parameters": Schema.optionalKey(Schema.Struct({})), + "type": Schema.String +}).annotate({ + "description": + "A tool made available to the subagent. Only OpenRouter server tools (e.g. openrouter:web_search) are supported; function tools are rejected because the worker has no way to execute them. The subagent tool may not list itself.", + "identifier": "SubagentNestedTool" +}) +export type SubagentReasoning = { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" + readonly "max_tokens"?: number } -export const OpenResponsesInputMessageItem = Schema.Struct({ - "id": Schema.optionalKey(Schema.String), - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "developer"]), - "content": Schema.Array( - Schema.Union([ - ResponseInputText, - Schema.Struct({ - "type": Schema.Literal("input_image"), - "detail": Schema.Literals(["auto", "high", "low"]), - "image_url": Schema.optionalKey(Schema.String) - }).annotate({ "description": "Image input content item" }), - ResponseInputFile, - ResponseInputAudio, - ResponseInputVideo - ], { mode: "oneOf" }) +export const SubagentReasoning = Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Literals(["max", "xhigh", "high", "medium", "low", "minimal", "none"]).annotate({ + "description": "Reasoning effort level for the subagent call." + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of reasoning tokens the subagent may use. Accepted and validated but not yet forwarded to the subagent call." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) ) +}).annotate({ + "description": + "Reasoning configuration forwarded to the subagent call. Use this to control reasoning effort and token budget for models that support extended thinking.", + "identifier": "SubagentReasoning" }) -export type ProviderSortConfig = { readonly "by"?: ProviderSort | null; readonly "partition"?: "model" | "none" | null } -export const ProviderSortConfig = Schema.Struct({ - "by": Schema.optionalKey(Schema.Union([ProviderSort, Schema.Null])), - "partition": Schema.optionalKey(Schema.Union([Schema.Literals(["model", "none"]), Schema.Null])) +export type SubmitGenerationFeedbackRequest = { + readonly "category": + | "latency" + | "incoherence" + | "incorrect_response" + | "formatting" + | "billing" + | "api_error" + | "other" + readonly "comment"?: string + readonly "generation_id": string +} +export const SubmitGenerationFeedbackRequest = Schema.Struct({ + "category": Schema.Literals([ + "latency", + "incoherence", + "incorrect_response", + "formatting", + "billing", + "api_error", + "other" + ]).annotate({ "description": "The category of feedback being reported" }), + "comment": Schema.optionalKey( + Schema.String.annotate({ "description": "An optional free-text comment describing the feedback" }).check( + Schema.isMaxLength(1000).annotate({ "expected": "a value with a length of at most 1000" }) + ) + ), + "generation_id": Schema.String.annotate({ "description": "The generation to submit feedback on" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ + "description": "Structured feedback about a specific generation", + "identifier": "SubmitGenerationFeedbackRequest" }) -export type PreferredMinThroughput = number | PercentileThroughputCutoffs | unknown -export const PreferredMinThroughput = Schema.Union([ - Schema.Number.check(Schema.isFinite()), - PercentileThroughputCutoffs, - Schema.Unknown -]).annotate({ - "description": - "Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." +export type SubmitGenerationFeedbackResponse = { readonly "data": { readonly "success": true } } +export const SubmitGenerationFeedbackResponse = Schema.Struct({ + "data": Schema.Struct({ + "success": Schema.Literal(true).annotate({ "description": "Whether the feedback was recorded" }) + }) +}).annotate({ + "description": "Confirmation that the feedback was recorded", + "identifier": "SubmitGenerationFeedbackResponse" }) -export type PreferredMaxLatency = number | PercentileLatencyCutoffs | unknown -export const PreferredMaxLatency = Schema.Union([ - Schema.Number.check(Schema.isFinite()), - PercentileLatencyCutoffs, - Schema.Unknown -]).annotate({ +export type SupportedParameters = {} +export const SupportedParameters = Schema.Struct({}).annotate({ "description": - "Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." + "Union of supported parameters across every endpoint of this model. Coarse discovery aid; the definitive per-endpoint set is behind the endpoints URL.", + "identifier": "SupportedParameters" }) -export type PDFParserOptions = { readonly "engine"?: PDFParserEngine } -export const PDFParserOptions = Schema.Struct({ "engine": Schema.optionalKey(PDFParserEngine) }).annotate({ - "description": "Options for PDF parsing." -}) -export type ForbiddenResponse = { readonly "error": ForbiddenResponseErrorData; readonly "user_id"?: string } -export const ForbiddenResponse = Schema.Struct({ - "error": ForbiddenResponseErrorData, - "user_id": Schema.optionalKey(Schema.String) -}).annotate({ "description": "Forbidden - Authentication successful but insufficient permissions" }) -export type ModelArchitecture = { - readonly "tokenizer"?: ModelGroup - readonly "instruct_type"?: - | "none" - | "airoboros" - | "alpaca" - | "alpaca-modif" - | "chatml" - | "claude" - | "code-llama" - | "gemma" - | "llama2" - | "llama3" - | "mistral" - | "nemotron" - | "neural" - | "openchat" - | "phi3" - | "rwkv" - | "vicuna" - | "zephyr" - | "deepseek-r1" - | "deepseek-v3.1" - | "qwq" - | "qwen3" - readonly "modality": string - readonly "input_modalities": ReadonlyArray - readonly "output_modalities": ReadonlyArray +export type TaskClassificationMacroCategory = { + readonly "key": string + readonly "label": string + readonly "token_share": number + readonly "usage_share": number } -export const ModelArchitecture = Schema.Struct({ - "tokenizer": Schema.optionalKey(ModelGroup), - "instruct_type": Schema.optionalKey( - Schema.Literals([ - "none", - "airoboros", - "alpaca", - "alpaca-modif", - "chatml", - "claude", - "code-llama", - "gemma", - "llama2", - "llama3", - "mistral", - "nemotron", - "neural", - "openchat", - "phi3", - "rwkv", - "vicuna", - "zephyr", - "deepseek-r1", - "deepseek-v3.1", - "qwq", - "qwen3" - ]).annotate({ "description": "Instruction format type" }) - ), - "modality": Schema.String.annotate({ "description": "Primary modality of the model" }), - "input_modalities": Schema.Array(InputModality).annotate({ "description": "Supported input modalities" }), - "output_modalities": Schema.Array(OutputModality).annotate({ "description": "Supported output modalities" }) -}).annotate({ "description": "Model architecture information" }) -export type PublicEndpoint = { - readonly "name": string - readonly "model_id": string - readonly "model_name": string - readonly "context_length": number - readonly "pricing": { - readonly "prompt": string - readonly "completion": string - readonly "request"?: string - readonly "image"?: string - readonly "image_token"?: string - readonly "image_output"?: string - readonly "audio"?: string - readonly "audio_output"?: string - readonly "input_audio_cache"?: string - readonly "web_search"?: string - readonly "internal_reasoning"?: string - readonly "input_cache_read"?: string - readonly "input_cache_write"?: string - readonly "discount"?: number - } - readonly "provider_name": ProviderName - readonly "tag": string - readonly "quantization": "int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown" - readonly "max_completion_tokens": number - readonly "max_prompt_tokens": number - readonly "supported_parameters": ReadonlyArray - readonly "status"?: EndpointStatus - readonly "uptime_last_30m": number - readonly "supports_implicit_caching": boolean - readonly "latency_last_30m": PercentileStats - readonly "throughput_last_30m": { - readonly "p50": number - readonly "p75": number - readonly "p90": number - readonly "p99": number - } +export const TaskClassificationMacroCategory = Schema.Struct({ + "key": Schema.String.annotate({ "description": "Macro-category identifier." }), + "label": Schema.String.annotate({ "description": "Human-readable label for the macro-category." }), + "token_share": Schema.Number.annotate({ + "description": "Combined token share of all classifications in this macro-category (0–1).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_share": Schema.Number.annotate({ + "description": "Combined usage share of all classifications in this macro-category (0–1).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "TaskClassificationMacroCategory" }) +export type TaskClassificationModel = { + readonly "id": string + readonly "tag_token_share": number + readonly "tag_usage_share": number } -export const PublicEndpoint = Schema.Struct({ - "name": Schema.String, - "model_id": Schema.String.annotate({ "description": "The unique identifier for the model (permaslug)" }), - "model_name": Schema.String, - "context_length": Schema.Number.check(Schema.isFinite()), - "pricing": Schema.Struct({ - "prompt": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "completion": Schema.String.annotate({ "description": "A number or string value representing a large number" }), - "request": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_token": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "image_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "audio_output": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_audio_cache": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "web_search": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "internal_reasoning": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_read": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "input_cache_write": Schema.optionalKey( - Schema.String.annotate({ "description": "A number or string value representing a large number" }) - ), - "discount": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }), - "provider_name": ProviderName, - "tag": Schema.String, - "quantization": Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"]), - "max_completion_tokens": Schema.Number.check(Schema.isFinite()), - "max_prompt_tokens": Schema.Number.check(Schema.isFinite()), - "supported_parameters": Schema.Array(Parameter), - "status": Schema.optionalKey(EndpointStatus), - "uptime_last_30m": Schema.Number.check(Schema.isFinite()), - "supports_implicit_caching": Schema.Boolean, - "latency_last_30m": PercentileStats, - "throughput_last_30m": Schema.Struct({ - "p50": Schema.Number.annotate({ "description": "Median (50th percentile)" }).check(Schema.isFinite()), - "p75": Schema.Number.annotate({ "description": "75th percentile" }).check(Schema.isFinite()), - "p90": Schema.Number.annotate({ "description": "90th percentile" }).check(Schema.isFinite()), - "p99": Schema.Number.annotate({ "description": "99th percentile" }).check(Schema.isFinite()) - }).annotate({ +export const TaskClassificationModel = Schema.Struct({ + "id": Schema.String.annotate({ "description": "Model identifier (permaslug)." }), + "tag_token_share": Schema.Number.annotate({ "description": - "Throughput percentiles in tokens per second over the last 30 minutes. Throughput measures output token generation speed. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests." - }) -}).annotate({ "description": "Information about a specific model endpoint" }) -export type __schema20 = { - readonly "type": "reasoning.summary" - readonly "summary": string - readonly "id"?: __schema21 - readonly "format"?: __schema22 - readonly "index"?: __schema11 -} | { - readonly "type": "reasoning.encrypted" - readonly "data": string - readonly "id"?: __schema21 - readonly "format"?: __schema22 - readonly "index"?: __schema11 -} | { - readonly "type": "reasoning.text" - readonly "text"?: string | null - readonly "signature"?: string | null - readonly "id"?: __schema21 - readonly "format"?: __schema22 - readonly "index"?: __schema11 -} -export const __schema20 = Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("reasoning.summary"), - "summary": Schema.String, - "id": Schema.optionalKey(__schema21), - "format": Schema.optionalKey(__schema22), - "index": Schema.optionalKey(__schema11) - }), - Schema.Struct({ - "type": Schema.Literal("reasoning.encrypted"), - "data": Schema.String, - "id": Schema.optionalKey(__schema21), - "format": Schema.optionalKey(__schema22), - "index": Schema.optionalKey(__schema11) - }), - Schema.Struct({ - "type": Schema.Literal("reasoning.text"), - "text": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "signature": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "id": Schema.optionalKey(__schema21), - "format": Schema.optionalKey(__schema22), - "index": Schema.optionalKey(__schema11) - }) -], { mode: "oneOf" }) -export type __schema14 = __schema11 | ModelName | __schema13 -export const __schema14 = Schema.Union([__schema11, ModelName, __schema13]) -export type ChatMessageTokenLogprobs = { - readonly "content": ReadonlyArray | null - readonly "refusal": ReadonlyArray | null -} -export const ChatMessageTokenLogprobs = Schema.Struct({ - "content": Schema.Union([Schema.Array(ChatMessageTokenLogprob), Schema.Null]), - "refusal": Schema.Union([Schema.Array(ChatMessageTokenLogprob), Schema.Null]) -}) -export type __schema26 = ChatCompletionFinishReason | null -export const __schema26 = Schema.Union([ChatCompletionFinishReason, Schema.Null]) -export type ResponseFormatJSONSchema = { readonly "type": "json_schema"; readonly "json_schema": JSONSchemaConfig } -export const ResponseFormatJSONSchema = Schema.Struct({ - "type": Schema.Literal("json_schema"), - "json_schema": JSONSchemaConfig -}) -export type ChatMessageContentItemText = { - readonly "type": "text" - readonly "text": string - readonly "cache_control"?: ChatMessageContentItemCacheControl -} -export const ChatMessageContentItemText = Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "cache_control": Schema.optionalKey(ChatMessageContentItemCacheControl) -}) -export type ToolDefinitionJson = { - readonly "type": "function" - readonly "function": { - readonly "name": string - readonly "description"?: string - readonly "parameters"?: {} - readonly "strict"?: boolean | null - } - readonly "cache_control"?: ChatMessageContentItemCacheControl -} -export const ToolDefinitionJson = Schema.Struct({ - "type": Schema.Literal("function"), - "function": Schema.Struct({ - "name": Schema.String.check(Schema.isMaxLength(64)), - "description": Schema.optionalKey(Schema.String), - "parameters": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) - }), - "cache_control": Schema.optionalKey(ChatMessageContentItemCacheControl) -}) -export type ToolChoiceOption = "none" | "auto" | "required" | NamedToolChoice -export const ToolChoiceOption = Schema.Union([ - Schema.Literal("none"), - Schema.Literal("auto"), - Schema.Literal("required"), - NamedToolChoice -]) -export type ResponseOutputText = { - readonly "type": "output_text" - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< + "Fraction of this classification's sampled token volume attributed to this model (0–1). Sums to ≤1 across the returned models (only top-N are included and unattributed requests are excluded).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "tag_usage_share": Schema.Number.annotate({ + "description": + "Fraction of this classification's sampled requests attributed to this model (0–1). Sums to ≤1 across the returned models (only top-N are included and unattributed requests are excluded).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "TaskClassificationModel" }) +export type TextDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "logprobs": ReadonlyArray< { - readonly "token": string - readonly "bytes": ReadonlyArray + readonly "bytes"?: ReadonlyArray readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } + readonly "token": string + readonly "top_logprobs"?: ReadonlyArray< + { readonly "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } > } > -} -export const ResponseOutputText = Schema.Struct({ - "type": Schema.Literal("output_text"), - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) -}) -export type OpenResponsesOutputTextAnnotationAddedEvent = { - readonly "type": "response.output_text.annotation.added" readonly "output_index": number - readonly "item_id": string - readonly "content_index": number readonly "sequence_number": number - readonly "annotation_index": number - readonly "annotation": OpenAIResponsesAnnotation -} -export const OpenResponsesOutputTextAnnotationAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.output_text.annotation.added"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "sequence_number": Schema.Number.check(Schema.isFinite()), - "annotation_index": Schema.Number.check(Schema.isFinite()), - "annotation": OpenAIResponsesAnnotation -}).annotate({ "description": "Event emitted when a text annotation is added to output" }) -export type ResponseTextConfig = { - readonly "format"?: ResponseFormatTextConfig - readonly "verbosity"?: "high" | "low" | "medium" -} -export const ResponseTextConfig = Schema.Struct({ - "format": Schema.optionalKey(ResponseFormatTextConfig), - "verbosity": Schema.optionalKey(Schema.Literals(["high", "low", "medium"])) -}).annotate({ "description": "Text output configuration including format and verbosity" }) -export type OpenResponsesResponseText = { - readonly "format"?: ResponseFormatTextConfig - readonly "verbosity"?: "high" | "low" | "medium" -} -export const OpenResponsesResponseText = Schema.Struct({ - "format": Schema.optionalKey(ResponseFormatTextConfig), - "verbosity": Schema.optionalKey(Schema.Literals(["high", "low", "medium"])) -}).annotate({ "description": "Text output configuration including format and verbosity" }) -export type OpenResponsesTextDeltaEvent = { readonly "type": "response.output_text.delta" - readonly "logprobs": ReadonlyArray - readonly "output_index": number - readonly "item_id": string - readonly "content_index": number - readonly "delta": string - readonly "sequence_number": number } -export const OpenResponsesTextDeltaEvent = Schema.Struct({ - "type": Schema.Literal("response.output_text.delta"), - "logprobs": Schema.Array(OpenResponsesLogProbs), - "output_index": Schema.Number.check(Schema.isFinite()), - "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), +export const TextDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "delta": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a text delta is streamed" }) -export type OpenResponsesTextDoneEvent = { - readonly "type": "response.output_text.done" - readonly "output_index": number - readonly "item_id": string + "item_id": Schema.String, + "logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.optionalKey(Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "token": Schema.optionalKey(Schema.String) + }).annotate({ "description": "Alternative token with its log probability" }) + )) + }).annotate({ "description": "Log probability information for a token" }) + ), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.delta") +}).annotate({ "description": "Event emitted when a text delta is streamed", "identifier": "TextDeltaEvent" }) +export type TextDoneEvent = { readonly "content_index": number - readonly "text": string + readonly "item_id": string + readonly "logprobs": ReadonlyArray< + { + readonly "bytes"?: ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs"?: ReadonlyArray< + { readonly "bytes"?: ReadonlyArray; readonly "logprob"?: number; readonly "token"?: string } + > + } + > + readonly "output_index": number readonly "sequence_number": number - readonly "logprobs": ReadonlyArray + readonly "text": string + readonly "type": "response.output_text.done" } -export const OpenResponsesTextDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.output_text.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const TextDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), + "logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.optionalKey(Schema.Array( + Schema.Struct({ + "bytes": Schema.optionalKey( + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) + ), + "logprob": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "token": Schema.optionalKey(Schema.String) + }).annotate({ "description": "Alternative token with its log probability" }) + )) + }).annotate({ "description": "Log probability information for a token" }) + ), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), "text": Schema.String, - "sequence_number": Schema.Number.check(Schema.isFinite()), - "logprobs": Schema.Array(OpenResponsesLogProbs) -}).annotate({ "description": "Event emitted when text streaming is complete" }) -export type ProviderSortUnion = ProviderSort | ProviderSortConfig -export const ProviderSortUnion = Schema.Union([ProviderSort, ProviderSortConfig]) -export type ProviderPreferences = { - readonly "allow_fallbacks"?: boolean - readonly "require_parameters"?: boolean - readonly "data_collection"?: DataCollection - readonly "zdr"?: boolean - readonly "enforce_distillable_text"?: boolean - readonly "order"?: ReadonlyArray - readonly "only"?: ReadonlyArray - readonly "ignore"?: ReadonlyArray - readonly "quantizations"?: ReadonlyArray - readonly "sort"?: "price" | "price" | "throughput" | "throughput" | "latency" | "latency" - readonly "max_price"?: { - readonly "prompt"?: BigNumberUnion - readonly "completion"?: string - readonly "image"?: string - readonly "audio"?: string - readonly "request"?: string - } - readonly "preferred_min_throughput"?: PreferredMinThroughput - readonly "preferred_max_latency"?: PreferredMaxLatency + "type": Schema.Literal("response.output_text.done") +}).annotate({ "description": "Event emitted when text streaming is complete", "identifier": "TextDoneEvent" }) +export type ToolCallStatus = "in_progress" | "completed" | "incomplete" +export const ToolCallStatus = Schema.Literals(["in_progress", "completed", "incomplete"]).annotate({ + "identifier": "ToolCallStatus" +}) +export type ToolChoiceAllowed = { + readonly "mode": "auto" | "required" + readonly "tools": ReadonlyArray<{}> + readonly "type": "allowed_tools" } -export const ProviderPreferences = Schema.Struct({ - "allow_fallbacks": Schema.optionalKey(Schema.Boolean.annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - })), - "require_parameters": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." +export const ToolChoiceAllowed = Schema.Struct({ + "mode": Schema.Literals(["auto", "required"]), + "tools": Schema.Array(Schema.Struct({})), + "type": Schema.Literal("allowed_tools") +}).annotate({ + "description": "Constrains the model to a pre-defined set of allowed tools", + "identifier": "ToolChoiceAllowed" +}) +export type TooManyRequestsResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const TooManyRequestsResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for TooManyRequestsResponse", + "identifier": "TooManyRequestsResponseErrorData" +}) +export type TopProviderInfo = { + readonly "context_length"?: number | null + readonly "is_moderated": boolean + readonly "max_completion_tokens"?: number | null +} +export const TopProviderInfo = Schema.Struct({ + "context_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Context length from the top provider" }) ), - "data_collection": Schema.optionalKey(DataCollection), - "zdr": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." + "is_moderated": Schema.Boolean.annotate({ "description": "Whether the top provider moderates content" }), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Maximum completion tokens from the top provider" }) + ) +}).annotate({ "description": "Information about the top provider for this model", "identifier": "TopProviderInfo" }) +export type TraceConfig = { + readonly "generation_name"?: string + readonly "parent_span_id"?: string + readonly "span_name"?: string + readonly "trace_id"?: string + readonly "trace_name"?: string +} +export const TraceConfig = Schema.Struct({ + "generation_name": Schema.optionalKey(Schema.String), + "parent_span_id": Schema.optionalKey(Schema.String), + "span_name": Schema.optionalKey(Schema.String), + "trace_id": Schema.optionalKey(Schema.String), + "trace_name": Schema.optionalKey(Schema.String) +}).annotate({ + "description": + "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations.", + "identifier": "TraceConfig" +}) +export type Truncation = "auto" | "disabled" | null +export const Truncation = Schema.Union([Schema.Literal("auto"), Schema.Literal("disabled"), Schema.Null]).annotate({ + "identifier": "Truncation" +}) +export type UnauthorizedResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const UnauthorizedResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ "description": "Error data for UnauthorizedResponse", "identifier": "UnauthorizedResponseErrorData" }) +export type UnifiedBenchmarkPricing = { + readonly "completion": string + readonly "prompt": string + readonly [x: string]: Schema.Json +} | null +export const UnifiedBenchmarkPricing = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "completion": Schema.String.annotate({ "description": "Cost per output token (USD, decimal string)." }), + "prompt": Schema.String.annotate({ "description": "Cost per input token (USD, decimal string)." }) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] ), - "enforce_distillable_text": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." + Schema.Null +]).annotate({ + "description": "OpenRouter pricing per token for this model. Null if pricing is unavailable.", + "identifier": "UnifiedBenchmarkPricing" +}) +export type UnifiedBenchmarksMeta = { + readonly "as_of": string + readonly "citation": string | null + readonly "model_count": number + readonly "source": "artificial-analysis" | "design-arena" | null + readonly "source_url": string | null + readonly "task_type": string | null + readonly "version": "v1" +} +export const UnifiedBenchmarksMeta = Schema.Struct({ + "as_of": Schema.String.annotate({ "description": "ISO-8601 timestamp of when this data was last updated." }), + "citation": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Required attribution when republishing this data, or null when results span multiple sources (attribute each item individually by its `source` discriminator)." + }), + "model_count": Schema.Number.annotate({ "description": "Number of unique models in the response." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "source": Schema.Union([Schema.Literal("artificial-analysis"), Schema.Literal("design-arena"), Schema.Null]).annotate( + { "description": "The source filter applied, or null when all sources are returned." } + ), + "source_url": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "URL of the upstream data source, or null when results span multiple sources." + }), + "task_type": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The task_type filter applied, or null if showing all." + }), + "version": Schema.Literal("v1").annotate({ "description": "Dataset version." }) +}).annotate({ "identifier": "UnifiedBenchmarksMeta" }) +export type UnprocessableEntityResponseErrorData = { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly [x: string]: Schema.Json } | null +} +export const UnprocessableEntityResponseErrorData = Schema.Struct({ + "code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "message": Schema.String, + "metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ) +}).annotate({ + "description": "Error data for UnprocessableEntityResponse", + "identifier": "UnprocessableEntityResponseErrorData" +}) +export type UpdateBYOKKeyRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_user_ids"?: ReadonlyArray | null + readonly "disabled"?: boolean + readonly "is_fallback"?: boolean + readonly "key"?: string + readonly "name"?: string | null +} +export const UpdateBYOKKeyRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of model slugs this credential may be used for. `null` means no restriction." }) ), - "order": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." + "allowed_user_ids": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of user IDs that may use this credential. `null` means no restriction." }) ), - "only": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ + "disabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether this credential is disabled." })), + "is_fallback": Schema.optionalKey( + Schema.Boolean.annotate({ "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." + "Whether this credential is treated as a fallback — used only after non-fallback keys for the same provider have been tried." }) ), - "ignore": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ + "key": Schema.optionalKey( + Schema.String.annotate({ "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." + "A new raw provider API key to rotate the credential in-place. The previous key material is overwritten and the masked label is regenerated. Encrypted at rest and never returned in API responses." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(255).annotate({ "expected": "a value with a length of at most 255" })), + Schema.Null + ]).annotate({ "description": "Optional human-readable name for the credential." }) + ) +}).annotate({ "identifier": "UpdateBYOKKeyRequest" }) +export type UpdateWorkspaceRequest = { + readonly "default_image_model"?: string | null + readonly "default_provider_sort"?: string | null + readonly "default_text_model"?: string | null + readonly "description"?: string | null + readonly "io_logging_api_key_ids"?: ReadonlyArray | null + readonly "io_logging_sampling_rate"?: number + readonly "is_data_discount_logging_enabled"?: boolean + readonly "is_observability_broadcast_enabled"?: boolean + readonly "is_observability_io_logging_enabled"?: boolean + readonly "name"?: string + readonly "slug"?: string +} +export const UpdateWorkspaceRequest = Schema.Struct({ + "default_image_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default image model for this workspace" }) + ), + "default_provider_sort": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default provider sort preference (price, throughput, latency, exacto)" }) ), - "quantizations": Schema.optionalKey( - Schema.Array(Quantization).annotate({ "description": "A list of quantization levels to filter the provider by." }) + "default_text_model": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Default text model for this workspace" }) ), - "sort": Schema.optionalKey( + "description": Schema.optionalKey( Schema.Union([ - Schema.Union([Schema.Literal("price"), Schema.Literal("price")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("throughput"), Schema.Literal("throughput")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("latency"), Schema.Literal("latency")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ]) + Schema.String.check(Schema.isMaxLength(500).annotate({ "expected": "a value with a length of at most 500" })), + Schema.Null + ]).annotate({ "description": "New description for the workspace" }) ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(BigNumberUnion), - "completion": Schema.optionalKey( - Schema.String.annotate({ "description": "Price per million completion tokens" }) - ), - "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price per image" })), - "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price per audio unit" })), - "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price per request" })) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), - "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency) -}).annotate({ "description": "Provider routing preferences for the request." }) -export type AnthropicMessagesRequest = { - readonly "model": string - readonly "max_tokens": number - readonly "messages": ReadonlyArray - readonly "system"?: - | string - | ReadonlyArray< - { - readonly "type": "text" - readonly "text": string - readonly "citations"?: ReadonlyArray< - { - readonly "type": "char_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_char_index": number - readonly "end_char_index": number - } | { - readonly "type": "page_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_page_number": number - readonly "end_page_number": number - } | { - readonly "type": "content_block_location" - readonly "cited_text": string - readonly "document_index": number - readonly "document_title": string - readonly "start_block_index": number - readonly "end_block_index": number - } | { - readonly "type": "web_search_result_location" - readonly "cited_text": string - readonly "encrypted_index": string - readonly "title": string - readonly "url": string - } | { - readonly "type": "search_result_location" - readonly "cited_text": string - readonly "search_result_index": number - readonly "source": string - readonly "title": string - readonly "start_block_index": number - readonly "end_block_index": number - } - > - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "metadata"?: { readonly "user_id"?: string } - readonly "stop_sequences"?: ReadonlyArray - readonly "stream"?: boolean - readonly "temperature"?: number - readonly "top_p"?: number - readonly "top_k"?: number - readonly "tools"?: ReadonlyArray< - { - readonly "name": string - readonly "description"?: string - readonly "input_schema": { - readonly "type": "object" - readonly "properties"?: unknown - readonly "required"?: ReadonlyArray - } - readonly "type"?: "custom" - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "bash_20250124" - readonly "name": "bash" - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "text_editor_20250124" - readonly "name": "str_replace_editor" - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } | { - readonly "type": "web_search_20250305" - readonly "name": "web_search" - readonly "allowed_domains"?: ReadonlyArray - readonly "blocked_domains"?: ReadonlyArray - readonly "max_uses"?: number - readonly "user_location"?: { - readonly "type": "approximate" - readonly "city"?: string - readonly "country"?: string - readonly "region"?: string - readonly "timezone"?: string - } - readonly "cache_control"?: { readonly "type": "ephemeral"; readonly "ttl"?: "5m" | "1h" } - } - > - readonly "tool_choice"?: - | { readonly "type": "auto"; readonly "disable_parallel_tool_use"?: boolean } - | { readonly "type": "any"; readonly "disable_parallel_tool_use"?: boolean } - | { readonly "type": "none" } - | { readonly "type": "tool"; readonly "name": string; readonly "disable_parallel_tool_use"?: boolean } - readonly "thinking"?: { readonly "type": "enabled"; readonly "budget_tokens": number } | { - readonly "type": "disabled" - } | { readonly "type": "adaptive" } - readonly "service_tier"?: "auto" | "standard_only" - readonly "provider"?: { - readonly "allow_fallbacks"?: boolean - readonly "require_parameters"?: boolean - readonly "data_collection"?: DataCollection - readonly "zdr"?: boolean - readonly "enforce_distillable_text"?: boolean - readonly "order"?: ReadonlyArray - readonly "only"?: ReadonlyArray - readonly "ignore"?: ReadonlyArray - readonly "quantizations"?: ReadonlyArray - readonly "sort"?: "price" | "price" | "throughput" | "throughput" | "latency" | "latency" - readonly "max_price"?: { - readonly "prompt"?: BigNumberUnion - readonly "completion"?: string - readonly "image"?: string - readonly "audio"?: string - readonly "request"?: string - } - readonly "preferred_min_throughput"?: PreferredMinThroughput - readonly "preferred_max_latency"?: PreferredMaxLatency - } - readonly "plugins"?: ReadonlyArray< - | { readonly "id": "auto-router"; readonly "enabled"?: boolean; readonly "allowed_models"?: ReadonlyArray } - | { readonly "id": "moderation" } - | { - readonly "id": "web" - readonly "enabled"?: boolean - readonly "max_results"?: number - readonly "search_prompt"?: string - readonly "engine"?: WebSearchEngine - } - | { readonly "id": "file-parser"; readonly "enabled"?: boolean; readonly "pdf"?: PDFParserOptions } - | { readonly "id": "response-healing"; readonly "enabled"?: boolean } - > - readonly "route"?: "fallback" | "sort" - readonly "user"?: string - readonly "session_id"?: string - readonly "trace"?: { - readonly "trace_id"?: string - readonly "trace_name"?: string - readonly "span_name"?: string - readonly "generation_name"?: string - readonly "parent_span_id"?: string - } - readonly "models"?: ReadonlyArray - readonly "output_config"?: AnthropicOutputConfig -} -export const AnthropicMessagesRequest = Schema.Struct({ - "model": Schema.String, - "max_tokens": Schema.Number.check(Schema.isFinite()), - "messages": Schema.Array(OpenRouterAnthropicMessageParam), - "system": Schema.optionalKey(Schema.Union([ - Schema.String, - Schema.Array(Schema.Struct({ - "type": Schema.Literal("text"), - "text": Schema.String, - "citations": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("char_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_char_index": Schema.Number.check(Schema.isFinite()), - "end_char_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("page_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_page_number": Schema.Number.check(Schema.isFinite()), - "end_page_number": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("content_block_location"), - "cited_text": Schema.String, - "document_index": Schema.Number.check(Schema.isFinite()), - "document_title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_result_location"), - "cited_text": Schema.String, - "encrypted_index": Schema.String, - "title": Schema.String, - "url": Schema.String - }), - Schema.Struct({ - "type": Schema.Literal("search_result_location"), - "cited_text": Schema.String, - "search_result_index": Schema.Number.check(Schema.isFinite()), - "source": Schema.String, - "title": Schema.String, - "start_block_index": Schema.Number.check(Schema.isFinite()), - "end_block_index": Schema.Number.check(Schema.isFinite()) - }) - ], { mode: "oneOf" }))), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - })) - ])), - "metadata": Schema.optionalKey(Schema.Struct({ "user_id": Schema.optionalKey(Schema.String) })), - "stop_sequences": Schema.optionalKey(Schema.Array(Schema.String)), - "stream": Schema.optionalKey(Schema.Boolean), - "temperature": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_p": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_k": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "tools": Schema.optionalKey(Schema.Array(Schema.Union([ - Schema.Struct({ - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "input_schema": Schema.Struct({ - "type": Schema.Literal("object"), - "properties": Schema.optionalKey(Schema.Unknown), - "required": Schema.optionalKey(Schema.Array(Schema.String)) - }), - "type": Schema.optionalKey(Schema.Literal("custom")), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("bash_20250124"), - "name": Schema.Literal("bash"), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("text_editor_20250124"), - "name": Schema.Literal("str_replace_editor"), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_20250305"), - "name": Schema.Literal("web_search"), - "allowed_domains": Schema.optionalKey(Schema.Array(Schema.String)), - "blocked_domains": Schema.optionalKey(Schema.Array(Schema.String)), - "max_uses": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "user_location": Schema.optionalKey( - Schema.Struct({ - "type": Schema.Literal("approximate"), - "city": Schema.optionalKey(Schema.String), - "country": Schema.optionalKey(Schema.String), - "region": Schema.optionalKey(Schema.String), - "timezone": Schema.optionalKey(Schema.String) - }) - ), - "cache_control": Schema.optionalKey( - Schema.Struct({ "type": Schema.Literal("ephemeral"), "ttl": Schema.optionalKey(Schema.Literals(["5m", "1h"])) }) - ) - }) - ], { mode: "oneOf" }))), - "tool_choice": Schema.optionalKey( + "io_logging_api_key_ids": Schema.optionalKey( Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("auto"), - "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean) - }), - Schema.Struct({ "type": Schema.Literal("any"), "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean) }), - Schema.Struct({ "type": Schema.Literal("none") }), - Schema.Struct({ - "type": Schema.Literal("tool"), - "name": Schema.String, - "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean) - }) - ], { mode: "oneOf" }) + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "Optional array of API key IDs to filter I/O logging" }) ), - "thinking": Schema.optionalKey( - Schema.Union([ - Schema.Struct({ "type": Schema.Literal("enabled"), "budget_tokens": Schema.Number.check(Schema.isFinite()) }), - Schema.Struct({ "type": Schema.Literal("disabled") }), - Schema.Struct({ "type": Schema.Literal("adaptive") }) - ], { mode: "oneOf" }) + "io_logging_sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate for I/O logging (0.0001-1)", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) ), - "service_tier": Schema.optionalKey(Schema.Literals(["auto", "standard_only"])), - "provider": Schema.optionalKey( - Schema.Struct({ - "allow_fallbacks": Schema.optionalKey(Schema.Boolean.annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - })), - "require_parameters": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." - }) - ), - "data_collection": Schema.optionalKey(DataCollection), - "zdr": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." - }) - ), - "enforce_distillable_text": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." - }) - ), - "order": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." - }) - ), - "only": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." - }) - ), - "ignore": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." - }) - ), - "quantizations": Schema.optionalKey( - Schema.Array(Quantization).annotate({ - "description": "A list of quantization levels to filter the provider by." - }) - ), - "sort": Schema.optionalKey( - Schema.Union([ - Schema.Union([Schema.Literal("price"), Schema.Literal("price")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("throughput"), Schema.Literal("throughput")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }), - Schema.Union([Schema.Literal("latency"), Schema.Literal("latency")]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ]) - ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(BigNumberUnion), - "completion": Schema.optionalKey( - Schema.String.annotate({ "description": "Price per million completion tokens" }) - ), - "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price per image" })), - "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price per audio unit" })), - "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price per request" })) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), - "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency) - }).annotate({ - "description": "When multiple model providers are available, optionally indicate your routing preference." - }) + "is_data_discount_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether data discount logging is enabled" }) ), - "plugins": Schema.optionalKey( - Schema.Array(Schema.Union([ - Schema.Struct({ - "id": Schema.Literal("auto-router"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the auto-router plugin for this request. Defaults to true." - }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": - "List of model patterns to filter which models the auto-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." - }) - ) - }), - Schema.Struct({ "id": Schema.Literal("moderation") }), - Schema.Struct({ - "id": Schema.Literal("web"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the web-search plugin for this request. Defaults to true." - }) - ), - "max_results": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "search_prompt": Schema.optionalKey(Schema.String), - "engine": Schema.optionalKey(WebSearchEngine) - }), - Schema.Struct({ - "id": Schema.Literal("file-parser"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the file-parser plugin for this request. Defaults to true." - }) - ), - "pdf": Schema.optionalKey(PDFParserOptions) - }), - Schema.Struct({ - "id": Schema.Literal("response-healing"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the response-healing plugin for this request. Defaults to true." - }) - ) - }) - ], { mode: "oneOf" })).annotate({ - "description": "Plugins you want to enable for this request, including their settings." - }) + "is_observability_broadcast_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether broadcast is enabled" }) ), - "route": Schema.optionalKey( - Schema.Literals(["fallback", "sort"]).annotate({ - "description": - "**DEPRECATED** Use providers.sort.partition instead. Backwards-compatible alias for providers.sort.partition. Accepts legacy values: \"fallback\" (maps to \"model\"), \"sort\" (maps to \"none\")." - }) + "is_observability_io_logging_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether private logging is enabled" }) ), - "user": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "New name for the workspace" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) ), - "session_id": Schema.optionalKey( + "slug": Schema.optionalKey( Schema.String.annotate({ "description": - "A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) - ), - "trace": Schema.optionalKey( - Schema.Struct({ - "trace_id": Schema.optionalKey(Schema.String), - "trace_name": Schema.optionalKey(Schema.String), - "span_name": Schema.optionalKey(Schema.String), - "generation_name": Schema.optionalKey(Schema.String), - "parent_span_id": Schema.optionalKey(Schema.String) - }).annotate({ - "description": - "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations." - }) + "New URL-friendly slug (lowercase alphanumeric segments separated by single hyphens, no leading/trailing hyphens)" + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(50).annotate({ "expected": "a value with a length of at most 50" }) + ).check( + Schema.isPattern(new RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*$")).annotate({ + "expected": "a string matching the RegExp ^[a-z0-9]+(?:-[a-z0-9]+)*$" + }) + ) + ) +}).annotate({ "identifier": "UpdateWorkspaceRequest" }) +export type UpsertWorkspaceBudgetRequest = { readonly "limit_usd": number } +export const UpsertWorkspaceBudgetRequest = Schema.Struct({ + "limit_usd": Schema.Number.annotate({ + "description": "Spending limit in USD. Must be greater than 0.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "UpsertWorkspaceBudgetRequest" }) +export type URLCitation = { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": "url_citation" + readonly "url": string +} +export const URLCitation = Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Literal("url_citation"), + "url": Schema.String +}).annotate({ "identifier": "URLCitation" }) +export type VideoGenerationUsage = { readonly "cost"?: number | null; readonly "is_byok"?: boolean } +export const VideoGenerationUsage = Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "The cost of the video generation in USD.", "format": "double" }) ), - "models": Schema.optionalKey(Schema.Array(Schema.String)), - "output_config": Schema.optionalKey(AnthropicOutputConfig) -}).annotate({ "description": "Request schema for Anthropic Messages API endpoint" }) -export type Model = { - readonly "id": string + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether the request was made using a Bring Your Own Key configuration." }) + ) +}).annotate({ + "description": "Usage and cost information for the video generation. Available once the job has completed.", + "identifier": "VideoGenerationUsage" +}) +export type VideoModel = { + readonly "allowed_passthrough_parameters": ReadonlyArray readonly "canonical_slug": string - readonly "hugging_face_id"?: string - readonly "name": string readonly "created": number readonly "description"?: string - readonly "pricing": PublicPricing - readonly "context_length": number - readonly "architecture": ModelArchitecture - readonly "top_provider": TopProviderInfo - readonly "per_request_limits": PerRequestLimits - readonly "supported_parameters": ReadonlyArray - readonly "default_parameters": DefaultParameters - readonly "expiration_date"?: string + readonly "generate_audio": boolean | null + readonly "hugging_face_id"?: string | null + readonly "id": string + readonly "name": string + readonly "pricing_skus"?: { readonly [x: string]: string } | null + readonly "seed": boolean | null + readonly "supported_aspect_ratios": + | ReadonlyArray<"16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "3:2" | "2:3" | "21:9" | "9:21"> + | null + readonly "supported_durations": ReadonlyArray | null + readonly "supported_frame_images": ReadonlyArray<"first_frame" | "last_frame"> | null + readonly "supported_resolutions": ReadonlyArray<"480p" | "720p" | "1080p" | "1K" | "2K" | "4K"> | null + readonly "supported_sizes": + | ReadonlyArray< + | "480x480" + | "480x640" + | "480x720" + | "480x854" + | "480x1120" + | "640x480" + | "720x480" + | "720x720" + | "720x960" + | "720x1080" + | "720x1280" + | "720x1680" + | "854x480" + | "960x720" + | "1080x720" + | "1080x1080" + | "1080x1440" + | "1080x1620" + | "1080x1920" + | "1080x2520" + | "1120x480" + | "1280x720" + | "1440x1080" + | "1620x1080" + | "1680x720" + | "1920x1080" + | "2160x2160" + | "2160x2880" + | "2160x3240" + | "2160x3840" + | "2160x5040" + | "2520x1080" + | "2880x2160" + | "3240x2160" + | "3840x2160" + | "5040x2160" + > + | null } -export const Model = Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), +export const VideoModel = Schema.Struct({ + "allowed_passthrough_parameters": Schema.Array(Schema.String).annotate({ + "description": "List of parameters that are allowed to be passed through to the provider" + }), "canonical_slug": Schema.String.annotate({ "description": "Canonical slug for the model" }), - "hugging_face_id": Schema.optionalKey( - Schema.String.annotate({ "description": "Hugging Face model identifier, if applicable" }) - ), - "name": Schema.String.annotate({ "description": "Display name of the model" }), "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( - Schema.isFinite() + Schema.isInt().annotate({ "expected": "an integer" }) ), "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the model" })), - "pricing": PublicPricing, - "context_length": Schema.Number.annotate({ "description": "Maximum context length in tokens" }).check( - Schema.isFinite() - ), - "architecture": ModelArchitecture, - "top_provider": TopProviderInfo, - "per_request_limits": PerRequestLimits, - "supported_parameters": Schema.Array(Parameter).annotate({ - "description": "List of supported parameters for this model" + "generate_audio": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the model supports generating audio alongside video" }), - "default_parameters": DefaultParameters, - "expiration_date": Schema.optionalKey( - Schema.String.annotate({ - "description": - "The date after which the model may be removed. ISO 8601 date string (YYYY-MM-DD) or null if no expiration." + "hugging_face_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Hugging Face model identifier, if applicable" }) - ) -}).annotate({ "description": "Information about an AI model available on OpenRouter" }) -export type ListEndpointsResponse = { - readonly "id": string - readonly "name": string - readonly "created": number - readonly "description": string - readonly "architecture": { - readonly "tokenizer": - | "Router" - | "Media" - | "Other" - | "GPT" - | "Claude" - | "Gemini" - | "Grok" - | "Cohere" - | "Nova" - | "Qwen" - | "Yi" - | "DeepSeek" - | "Mistral" - | "Llama2" - | "Llama3" - | "Llama4" - | "PaLM" - | "RWKV" - | "Qwen3" - readonly "instruct_type": - | "none" - | "airoboros" - | "alpaca" - | "alpaca-modif" - | "chatml" - | "claude" - | "code-llama" - | "gemma" - | "llama2" - | "llama3" - | "mistral" - | "nemotron" - | "neural" - | "openchat" - | "phi3" - | "rwkv" - | "vicuna" - | "zephyr" - | "deepseek-r1" - | "deepseek-v3.1" - | "qwq" - | "qwen3" - readonly "modality": string - readonly "input_modalities": ReadonlyArray<"text" | "image" | "file" | "audio" | "video"> - readonly "output_modalities": ReadonlyArray<"text" | "image" | "embeddings" | "audio"> - } - readonly "endpoints": ReadonlyArray -} -export const ListEndpointsResponse = Schema.Struct({ + ), "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), "name": Schema.String.annotate({ "description": "Display name of the model" }), - "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( - Schema.isFinite() + "pricing_skus": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]).annotate({ + "description": "Pricing SKUs with provider prefix stripped, values as strings" + }) ), - "description": Schema.String.annotate({ "description": "Description of the model" }), - "architecture": Schema.Struct({ - "tokenizer": Schema.Union([ - Schema.Literal("Router").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Media").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Other").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("GPT").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Claude").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Gemini").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Grok").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Cohere").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Nova").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Qwen").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Yi").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("DeepSeek").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Mistral").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Llama2").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Llama3").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Llama4").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("PaLM").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("RWKV").annotate({ "description": "Tokenizer type used by the model" }), - Schema.Literal("Qwen3").annotate({ "description": "Tokenizer type used by the model" }) - ]).annotate({ "description": "Tokenizer type used by the model" }), - "instruct_type": Schema.Union([ - Schema.Literal("none").annotate({ "description": "Instruction format type" }), - Schema.Literal("airoboros").annotate({ "description": "Instruction format type" }), - Schema.Literal("alpaca").annotate({ "description": "Instruction format type" }), - Schema.Literal("alpaca-modif").annotate({ "description": "Instruction format type" }), - Schema.Literal("chatml").annotate({ "description": "Instruction format type" }), - Schema.Literal("claude").annotate({ "description": "Instruction format type" }), - Schema.Literal("code-llama").annotate({ "description": "Instruction format type" }), - Schema.Literal("gemma").annotate({ "description": "Instruction format type" }), - Schema.Literal("llama2").annotate({ "description": "Instruction format type" }), - Schema.Literal("llama3").annotate({ "description": "Instruction format type" }), - Schema.Literal("mistral").annotate({ "description": "Instruction format type" }), - Schema.Literal("nemotron").annotate({ "description": "Instruction format type" }), - Schema.Literal("neural").annotate({ "description": "Instruction format type" }), - Schema.Literal("openchat").annotate({ "description": "Instruction format type" }), - Schema.Literal("phi3").annotate({ "description": "Instruction format type" }), - Schema.Literal("rwkv").annotate({ "description": "Instruction format type" }), - Schema.Literal("vicuna").annotate({ "description": "Instruction format type" }), - Schema.Literal("zephyr").annotate({ "description": "Instruction format type" }), - Schema.Literal("deepseek-r1").annotate({ "description": "Instruction format type" }), - Schema.Literal("deepseek-v3.1").annotate({ "description": "Instruction format type" }), - Schema.Literal("qwq").annotate({ "description": "Instruction format type" }), - Schema.Literal("qwen3").annotate({ "description": "Instruction format type" }) - ]).annotate({ "description": "Instruction format type" }), - "modality": Schema.String.annotate({ "description": "Primary modality of the model" }), - "input_modalities": Schema.Array( - Schema.Union([ - Schema.Literal("text"), - Schema.Literal("image"), - Schema.Literal("file"), - Schema.Literal("audio"), - Schema.Literal("video") - ]) - ).annotate({ "description": "Supported input modalities" }), - "output_modalities": Schema.Array( - Schema.Union([ - Schema.Literal("text"), - Schema.Literal("image"), - Schema.Literal("embeddings"), - Schema.Literal("audio") + "seed": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the model supports deterministic generation via seed parameter" + }), + "supported_aspect_ratios": Schema.Union([ + Schema.Array(Schema.Literals(["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9", "9:21"])), + Schema.Null + ]).annotate({ "description": "Supported output aspect ratios" }), + "supported_durations": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + Schema.Null + ]).annotate({ "description": "Supported video durations in seconds" }), + "supported_frame_images": Schema.Union([Schema.Array(Schema.Literals(["first_frame", "last_frame"])), Schema.Null]) + .annotate({ "description": "Supported frame image types (e.g. first_frame, last_frame)" }), + "supported_resolutions": Schema.Union([ + Schema.Array(Schema.Literals(["480p", "720p", "1080p", "1K", "2K", "4K"])), + Schema.Null + ]).annotate({ "description": "Supported output resolutions" }), + "supported_sizes": Schema.Union([ + Schema.Array( + Schema.Literals([ + "480x480", + "480x640", + "480x720", + "480x854", + "480x1120", + "640x480", + "720x480", + "720x720", + "720x960", + "720x1080", + "720x1280", + "720x1680", + "854x480", + "960x720", + "1080x720", + "1080x1080", + "1080x1440", + "1080x1620", + "1080x1920", + "1080x2520", + "1120x480", + "1280x720", + "1440x1080", + "1620x1080", + "1680x720", + "1920x1080", + "2160x2160", + "2160x2880", + "2160x3240", + "2160x3840", + "2160x5040", + "2520x1080", + "2880x2160", + "3240x2160", + "3840x2160", + "5040x2160" ]) - ).annotate({ "description": "Supported output modalities" }) - }).annotate({ "description": "Model architecture information" }), - "endpoints": Schema.Array(PublicEndpoint).annotate({ "description": "List of available endpoints for this model" }) -}).annotate({ "description": "List of available endpoints for a model" }) -export type ChatStreamingMessageChunk = { - readonly "role"?: "assistant" - readonly "content"?: string | null - readonly "reasoning"?: string | null - readonly "refusal"?: string | null - readonly "tool_calls"?: ReadonlyArray - readonly "reasoning_details"?: ReadonlyArray<__schema20> - readonly "images"?: - | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> - | null - readonly "annotations"?: - | ReadonlyArray< - { - readonly "type": "url_citation" - readonly "url_citation": { - readonly "url": string - readonly "title"?: string - readonly "start_index"?: number - readonly "end_index"?: number - readonly "content"?: string - } - } | { - readonly "type": "file_annotation" - readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } - } | { - readonly "type": "file" - readonly "file": { - readonly "hash": string - readonly "name": string - readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> - } - } - > - | null -} -export const ChatStreamingMessageChunk = Schema.Struct({ - "role": Schema.optionalKey(Schema.Literal("assistant")), - "content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "reasoning": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "refusal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "tool_calls": Schema.optionalKey(Schema.Array(ChatStreamingMessageToolCall)), - "reasoning_details": Schema.optionalKey(Schema.Array(__schema20)), - "images": Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) - ), - Schema.Null - ]) - ), - "annotations": Schema.optionalKey(Schema.Union([ - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url_citation": Schema.Struct({ - "url": Schema.String, - "title": Schema.optionalKey(Schema.String), - "start_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "end_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "content": Schema.optionalKey(Schema.String) - }) - }), - Schema.Struct({ - "type": Schema.Literal("file_annotation"), - "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) - }), - Schema.Struct({ - "type": Schema.Literal("file"), - "file": Schema.Struct({ - "hash": Schema.String, - "name": Schema.String, - "content": Schema.optionalKey( - Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) - ) - }) - }) - ], { mode: "oneOf" })), + ), Schema.Null - ])) -}) -export type ChatMessageContentItem = - | ChatMessageContentItemText - | ChatMessageContentItemImage - | ChatMessageContentItemAudio - | ChatMessageContentItemVideo -export const ChatMessageContentItem = Schema.Union([ - ChatMessageContentItemText, - ChatMessageContentItemImage, - ChatMessageContentItemAudio, - ChatMessageContentItemVideo -], { mode: "oneOf" }) -export type SystemMessage = { - readonly "role": "system" - readonly "content": string | ReadonlyArray - readonly "name"?: string -} -export const SystemMessage = Schema.Struct({ - "role": Schema.Literal("system"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItemText)]), - "name": Schema.optionalKey(Schema.String) -}) -export type DeveloperMessage = { - readonly "role": "developer" - readonly "content": string | ReadonlyArray - readonly "name"?: string -} -export const DeveloperMessage = Schema.Struct({ - "role": Schema.Literal("developer"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItemText)]), - "name": Schema.optionalKey(Schema.String) -}) -export type OutputMessage = { - readonly "id": string - readonly "role": "assistant" - readonly "type": "message" - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray -} -export const OutputMessage = Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Literal("message"), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) -}) -export type ResponsesOutputMessage = { - readonly "id": string - readonly "role": "assistant" - readonly "type": "message" - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray + ]).annotate({ "description": "Supported output sizes (width x height)" }) +}).annotate({ "identifier": "VideoModel" }) +export type WebFetchEngineEnum = "auto" | "native" | "openrouter" | "exa" | "parallel" | "firecrawl" +export const WebFetchEngineEnum = Schema.Literals(["auto", "native", "openrouter", "exa", "parallel", "firecrawl"]) + .annotate({ + "description": + "Which fetch engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in fetch. \"exa\" uses Exa Contents API. \"openrouter\" uses direct HTTP fetch. \"firecrawl\" uses Firecrawl scrape (requires BYOK). \"parallel\" uses the Parallel extract API.", + "identifier": "WebFetchEngineEnum" + }) +export type WebFetchPlugin = { + readonly "allowed_domains"?: ReadonlyArray + readonly "blocked_domains"?: ReadonlyArray + readonly "id": "web-fetch" + readonly "max_content_tokens"?: number + readonly "max_uses"?: number } -export const ResponsesOutputMessage = Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Literal("message"), - "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) -}).annotate({ "description": "An output message item" }) -export type OpenResponsesContentPartAddedEvent = { - readonly "type": "response.content_part.added" - readonly "output_index": number +export const WebFetchPlugin = Schema.Struct({ + "allowed_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "Only fetch from these domains." }) + ), + "blocked_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ "description": "Never fetch from these domains." }) + ), + "id": Schema.Literal("web-fetch"), + "max_content_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum content length in approximate tokens. Content exceeding this limit is truncated." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_uses": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of web fetches per request. Once exceeded, the tool returns an error." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) +}).annotate({ "identifier": "WebFetchPlugin" }) +export type Arrays_13 = ReadonlyArray +export const Arrays_13 = Schema.Array(Schema.String).annotate({ "description": "Only fetch from these domains." }) +export type Arrays_14 = ReadonlyArray +export const Arrays_14 = Schema.Array(Schema.String).annotate({ "description": "Never fetch from these domains." }) +export type WebSearchCallCompletedEvent = { readonly "item_id": string - readonly "content_index": number - readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.web_search_call.completed" } -export const OpenResponsesContentPartAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.content_part.added"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const WebSearchCallCompletedEvent = Schema.Struct({ "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a new content part is added to an output item" }) -export type OpenResponsesContentPartDoneEvent = { - readonly "type": "response.content_part.done" + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.completed") +}).annotate({ "description": "Web search call completed", "identifier": "WebSearchCallCompletedEvent" }) +export type WebSearchCallInProgressEvent = { + readonly "item_id": string readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.web_search_call.in_progress" +} +export const WebSearchCallInProgressEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.in_progress") +}).annotate({ "description": "Web search call in progress", "identifier": "WebSearchCallInProgressEvent" }) +export type WebSearchCallSearchingEvent = { readonly "item_id": string - readonly "content_index": number - readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "output_index": number readonly "sequence_number": number + readonly "type": "response.web_search_call.searching" } -export const OpenResponsesContentPartDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.content_part.done"), - "output_index": Schema.Number.check(Schema.isFinite()), +export const WebSearchCallSearchingEvent = Schema.Struct({ "item_id": Schema.String, - "content_index": Schema.Number.check(Schema.isFinite()), - "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a content part is complete" }) -export type ModelsListResponseData = ReadonlyArray -export const ModelsListResponseData = Schema.Array(Model).annotate({ "description": "List of available models" }) -export type ChatStreamingChoice = { - readonly "delta": ChatStreamingMessageChunk - readonly "finish_reason"?: __schema26 - readonly "index": number - readonly "logprobs"?: ChatMessageTokenLogprobs | null + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.web_search_call.searching") +}).annotate({ "description": "Web search call is searching", "identifier": "WebSearchCallSearchingEvent" }) +export type Arrays_15 = ReadonlyArray +export const Arrays_15 = Schema.Array(Schema.String).annotate({ + "description": + "Limit search results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (Anthropic, OpenAI, xAI). Cannot be used with excluded_domains." +}) +export type Arrays_16 = ReadonlyArray +export const Arrays_16 = Schema.Array(Schema.String).annotate({ + "description": + "Exclude search results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, Anthropic, and xAI. Not supported with OpenAI (silently ignored). Cannot be used with allowed_domains." +}) +export type Objects_152 = { + readonly "allowed_domains"?: ReadonlyArray | null + readonly "excluded_domains"?: ReadonlyArray | null + readonly [x: string]: Schema.Json } -export const ChatStreamingChoice = Schema.Struct({ - "delta": ChatStreamingMessageChunk, - "finish_reason": Schema.optionalKey(__schema26), - "index": Schema.Number.check(Schema.isFinite()), - "logprobs": Schema.optionalKey(Schema.Union([ChatMessageTokenLogprobs, Schema.Null])) +export const Objects_152 = Schema.StructWithRest( + Schema.Struct({ + "allowed_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "excluded_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type WebSearchEngine = "native" | "exa" | "firecrawl" | "parallel" | "perplexity" +export const WebSearchEngine = Schema.Literals(["native", "exa", "firecrawl", "parallel", "perplexity"]).annotate({ + "description": "The search engine to use for web search.", + "identifier": "WebSearchEngine" }) -export type UserMessage = { - readonly "role": "user" - readonly "content": string | ReadonlyArray - readonly "name"?: string +export type WebSearchEngineEnum = "native" | "exa" | "parallel" | "firecrawl" | "perplexity" | "auto" +export const WebSearchEngineEnum = Schema.Literals(["native", "exa", "parallel", "firecrawl", "perplexity", "auto"]) + .annotate({ + "description": + "Which search engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in search. \"exa\" forces the Exa search API. \"firecrawl\" uses Firecrawl (requires BYOK). \"parallel\" uses the Parallel search API. \"perplexity\" uses the Perplexity Search API (raw ranked results).", + "identifier": "WebSearchEngineEnum" + }) +export type Arrays_17 = ReadonlyArray +export const Arrays_17 = Schema.Array(Schema.String).annotate({ + "description": + "Limit search results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (Anthropic, OpenAI, xAI). Cannot be used with excluded_domains." +}) +export type Arrays_18 = ReadonlyArray +export const Arrays_18 = Schema.Array(Schema.String).annotate({ + "description": + "Exclude search results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, Anthropic, and xAI. Not supported with OpenAI (silently ignored). Cannot be used with allowed_domains." +}) +export type WebSearchSource = { readonly "type": "url"; readonly "url": string } +export const WebSearchSource = Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String }).annotate({ + "identifier": "WebSearchSource" +}) +export type WebSearchStatus = "completed" | "searching" | "in_progress" | "failed" +export const WebSearchStatus = Schema.Literals(["completed", "searching", "in_progress", "failed"]).annotate({ + "identifier": "WebSearchStatus" +}) +export type Objects_153 = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type"?: "approximate" + readonly [x: string]: Schema.Json +} +export const Objects_153 = Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.optionalKey(Schema.Literal("approximate")) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type WebSearchUserLocationServerTool = { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type"?: "approximate" } -export const UserMessage = Schema.Struct({ - "role": Schema.Literal("user"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItem)]), - "name": Schema.optionalKey(Schema.String) +export const WebSearchUserLocationServerTool = Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.optionalKey(Schema.Literal("approximate")) +}).annotate({ + "description": "Approximate user location for location-biased results.", + "identifier": "WebSearchUserLocationServerTool" }) -export type AssistantMessage = { - readonly "role": "assistant" - readonly "content"?: string | ReadonlyArray | null - readonly "name"?: string - readonly "tool_calls"?: ReadonlyArray - readonly "refusal"?: string | null - readonly "reasoning"?: string | null - readonly "reasoning_details"?: ReadonlyArray<__schema20> - readonly "images"?: - | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> - | null - readonly "annotations"?: - | ReadonlyArray< - { - readonly "type": "url_citation" - readonly "url_citation": { - readonly "url": string - readonly "title"?: string - readonly "start_index"?: number - readonly "end_index"?: number - readonly "content"?: string - } - } | { - readonly "type": "file_annotation" - readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } - } | { - readonly "type": "file" - readonly "file": { - readonly "hash": string - readonly "name": string - readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> - } - } - > - | null +export type Workspace = { + readonly "created_at": string + readonly "created_by": string | null + readonly "default_guardrail_id": string + readonly "default_image_model": string | null + readonly "default_provider_sort": string | null + readonly "default_text_model": string | null + readonly "description": string | null + readonly "id": string + readonly "io_logging_api_key_ids": ReadonlyArray | null + readonly "io_logging_sampling_rate": number + readonly "is_data_discount_logging_enabled": boolean + readonly "is_observability_broadcast_enabled": boolean + readonly "is_observability_io_logging_enabled": boolean + readonly "name": string + readonly "slug": string + readonly "updated_at": string | null } -export const AssistantMessage = Schema.Struct({ - "role": Schema.Literal("assistant"), - "content": Schema.optionalKey( - Schema.Union([Schema.Union([Schema.String, Schema.Array(ChatMessageContentItem)]), Schema.Null]) - ), - "name": Schema.optionalKey(Schema.String), - "tool_calls": Schema.optionalKey(Schema.Array(ChatMessageToolCall)), - "refusal": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "reasoning": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "reasoning_details": Schema.optionalKey(Schema.Array(__schema20)), - "images": Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) - ), - Schema.Null - ]) - ), - "annotations": Schema.optionalKey(Schema.Union([ - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url_citation": Schema.Struct({ - "url": Schema.String, - "title": Schema.optionalKey(Schema.String), - "start_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "end_index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "content": Schema.optionalKey(Schema.String) - }) - }), - Schema.Struct({ - "type": Schema.Literal("file_annotation"), - "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) - }), - Schema.Struct({ - "type": Schema.Literal("file"), - "file": Schema.Struct({ - "hash": Schema.String, - "name": Schema.String, - "content": Schema.optionalKey( - Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) - ) - }) - }) - ], { mode: "oneOf" })), +export const Workspace = Schema.Struct({ + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the workspace was created" }), + "created_by": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID of the workspace creator" + }), + "default_guardrail_id": Schema.String.annotate({ + "description": "Deterministic ID of the workspace's implicitly-created default guardrail", + "format": "uuid" + }), + "default_image_model": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default image model for this workspace" + }), + "default_provider_sort": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default provider sort preference (price, throughput, latency, exacto)" + }), + "default_text_model": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Default text model for this workspace" + }), + "description": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Description of the workspace" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the workspace", "format": "uuid" }), + "io_logging_api_key_ids": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), Schema.Null - ])) + ]).annotate({ + "description": "Optional array of API key IDs to filter I/O logging. Null means all keys are logged." + }), + "io_logging_sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for I/O logging (0.0001-1). 1 means 100% of requests are logged.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "is_data_discount_logging_enabled": Schema.Boolean.annotate({ + "description": "Whether data discount logging is enabled for this workspace" + }), + "is_observability_broadcast_enabled": Schema.Boolean.annotate({ + "description": "Whether broadcast is enabled for this workspace" + }), + "is_observability_io_logging_enabled": Schema.Boolean.annotate({ + "description": "Whether private logging is enabled for this workspace" + }), + "name": Schema.String.annotate({ "description": "Name of the workspace" }), + "slug": Schema.String.annotate({ "description": "URL-friendly slug for the workspace" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the workspace was last updated" + }) +}).annotate({ "identifier": "Workspace" }) +export type WorkspaceBudget = { + readonly "created_at": string + readonly "id": string + readonly "limit_usd": number + readonly "reset_interval": "daily" | "weekly" | "monthly" | null + readonly "updated_at": string + readonly "workspace_id": string +} +export const WorkspaceBudget = Schema.Struct({ + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the budget was created" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the budget", "format": "uuid" }), + "limit_usd": Schema.Number.annotate({ "description": "Spending limit in USD for this interval", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "reset_interval": Schema.Union([ + Schema.Literal("daily"), + Schema.Literal("weekly"), + Schema.Literal("monthly"), + Schema.Null + ]).annotate({ "description": "Interval at which spend resets. Null means a lifetime (one-time) budget." }), + "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the budget was last updated" }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace the budget belongs to", + "format": "uuid" + }) +}).annotate({ "identifier": "WorkspaceBudget" }) +export type WorkspaceBudgetInterval = "daily" | "weekly" | "monthly" | "lifetime" +export const WorkspaceBudgetInterval = Schema.Literals(["daily", "weekly", "monthly", "lifetime"]).annotate({ + "description": "Budget reset interval. Use \"lifetime\" for a one-time budget that never resets.", + "identifier": "WorkspaceBudgetInterval" }) -export type ToolResponseMessage = { - readonly "role": "tool" - readonly "content": string | ReadonlyArray - readonly "tool_call_id": string +export type WorkspaceMember = { + readonly "created_at": string + readonly "id": string + readonly "role": "admin" | "member" + readonly "user_id": string + readonly "workspace_id": string } -export const ToolResponseMessage = Schema.Struct({ - "role": Schema.Literal("tool"), - "content": Schema.Union([Schema.String, Schema.Array(ChatMessageContentItem)]), - "tool_call_id": Schema.String +export const WorkspaceMember = Schema.Struct({ + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the membership was created" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the workspace membership", "format": "uuid" }), + "role": Schema.Literals(["admin", "member"]).annotate({ "description": "Role of the member in the workspace" }), + "user_id": Schema.String.annotate({ "description": "Clerk user ID of the member" }), + "workspace_id": Schema.String.annotate({ "description": "ID of the workspace", "format": "uuid" }) +}).annotate({ "identifier": "WorkspaceMember" }) +export type ActivityResponse = { readonly "data": ReadonlyArray } +export const ActivityResponse = Schema.Struct({ + "data": Schema.Array(ActivityItem).annotate({ "description": "List of activity items" }) +}).annotate({ "identifier": "ActivityResponse" }) +export type Arrays_ = ReadonlyArray +export const Arrays_ = Schema.Array(AdvisorNestedTool).annotate({ + "description": + "Tools the advisor sub-agent may use while forming its advice. The advisor runs as an agentic sub-agent over these tools, then returns its text. Only OpenRouter server tools are supported — function tools are rejected — and the list must not include the advisor tool itself." }) -export type OpenAIResponsesInput = - | string - | ReadonlyArray< - | { - readonly "type"?: "message" - readonly "role": "user" | "system" | "assistant" | "developer" - readonly "content": - | ReadonlyArray - | string - } - | { - readonly "id": string - readonly "type"?: "message" - readonly "role": "user" | "system" | "developer" - readonly "content": ReadonlyArray - } - | { - readonly "type": "function_call_output" - readonly "id"?: string - readonly "call_id": string - readonly "output": string - readonly "status"?: ToolCallStatus - } +export type AnthropicBashCodeExecutionResult = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "bash_code_execution_result" +} +export const AnthropicBashCodeExecutionResult = Schema.Struct({ + "content": Schema.Array(AnthropicBashCodeExecutionOutput), + "return_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "stderr": Schema.String, + "stdout": Schema.String, + "type": Schema.Literal("bash_code_execution_result") +}).annotate({ "identifier": "AnthropicBashCodeExecutionResult" }) +export type AnthropicCacheControlDirective = { readonly "ttl"?: AnthropicCacheControlTtl; readonly "type": "ephemeral" } +export const AnthropicCacheControlDirective = Schema.Struct({ + "ttl": Schema.optionalKey(AnthropicCacheControlTtl), + "type": Schema.Literal("ephemeral") +}).annotate({ + "description": + "Enable automatic prompt caching. When set at the top level, the system automatically applies cache breakpoints to the last cacheable block in the request. When set on an individual content block, it marks an explicit cache breakpoint; block-level markers also work on OpenAI models that support explicit prompt caching — OpenRouter converts them to the provider's native format.", + "identifier": "AnthropicCacheControlDirective" +}) +export type ChatContentCacheControl = { readonly "ttl"?: AnthropicCacheControlTtl; readonly "type": "ephemeral" } +export const ChatContentCacheControl = Schema.Struct({ + "ttl": Schema.optionalKey(AnthropicCacheControlTtl), + "type": Schema.Literal("ephemeral") +}).annotate({ + "description": + "Anthropic-style cache breakpoint for the content part. Interchangeable with the OpenAI-style `prompt_cache_breakpoint` marker: OpenRouter converts between the two based on the provider serving the request.", + "identifier": "ChatContentCacheControl" +}) +export type AnthropicCacheCreation = Objects_ | null +export const AnthropicCacheCreation = Schema.Union([Objects_, Schema.Null]).annotate({ + "identifier": "AnthropicCacheCreation" +}) +export type AnthropicCitationsConfig = Objects_1 | null +export const AnthropicCitationsConfig = Schema.Union([Objects_1, Schema.Null]).annotate({ + "identifier": "AnthropicCitationsConfig" +}) +export type AnthropicTextCitation = + | AnthropicCitationCharLocation + | AnthropicCitationPageLocation + | AnthropicCitationContentBlockLocation + | AnthropicCitationWebSearchResultLocation + | AnthropicCitationSearchResultLocation +export const AnthropicTextCitation = Schema.Union([ + AnthropicCitationCharLocation, + AnthropicCitationPageLocation, + AnthropicCitationContentBlockLocation, + AnthropicCitationWebSearchResultLocation, + AnthropicCitationSearchResultLocation +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicTextCitation" }) +export type MessagesContentBlockDeltaEvent = { + readonly "delta": + | { readonly "text": string; readonly "type": "text_delta" } + | { readonly "partial_json": string; readonly "type": "input_json_delta" } + | { readonly "thinking": string; readonly "type": "thinking_delta" } + | { readonly "signature": string; readonly "type": "signature_delta" } | { - readonly "type": "function_call" - readonly "call_id": string - readonly "name": string - readonly "arguments": string - readonly "id"?: string - readonly "status"?: ToolCallStatus + readonly "citation": + | AnthropicCitationCharLocation + | AnthropicCitationPageLocation + | AnthropicCitationContentBlockLocation + | AnthropicCitationWebSearchResultLocation + | AnthropicCitationSearchResultLocation + readonly "type": "citations_delta" } - | OutputItemImageGenerationCall - | OutputMessage - > - | unknown -export const OpenAIResponsesInput = Schema.Union([ - Schema.String, - Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "assistant", "developer"]), - "content": Schema.Union([ - Schema.Array( - Schema.Union([ResponseInputText, ResponseInputImage, ResponseInputFile, ResponseInputAudio], { - mode: "oneOf" - }) - ), - Schema.String - ]) - }), - Schema.Struct({ - "id": Schema.String, - "type": Schema.optionalKey(Schema.Literal("message")), - "role": Schema.Literals(["user", "system", "developer"]), - "content": Schema.Array( - Schema.Union([ResponseInputText, ResponseInputImage, ResponseInputFile, ResponseInputAudio], { mode: "oneOf" }) - ) - }), - Schema.Struct({ - "type": Schema.Literal("function_call_output"), - "id": Schema.optionalKey(Schema.String), - "call_id": Schema.String, - "output": Schema.String, - "status": Schema.optionalKey(ToolCallStatus) - }), + | { readonly "content": string | null; readonly "type": "compaction_delta" } + readonly "index": number + readonly "type": "content_block_delta" +} +export const MessagesContentBlockDeltaEvent = Schema.Struct({ + "delta": Schema.Union([ + Schema.Struct({ "text": Schema.String, "type": Schema.Literal("text_delta") }), + Schema.Struct({ "partial_json": Schema.String, "type": Schema.Literal("input_json_delta") }), + Schema.Struct({ "thinking": Schema.String, "type": Schema.Literal("thinking_delta") }), + Schema.Struct({ "signature": Schema.String, "type": Schema.Literal("signature_delta") }), Schema.Struct({ - "type": Schema.Literal("function_call"), - "call_id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "id": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey(ToolCallStatus) + "citation": Schema.Union([ + AnthropicCitationCharLocation, + AnthropicCitationPageLocation, + AnthropicCitationContentBlockLocation, + AnthropicCitationWebSearchResultLocation, + AnthropicCitationSearchResultLocation + ], { mode: "oneOf" }), + "type": Schema.Literal("citations_delta") }), - OutputItemImageGenerationCall, - OutputMessage - ])), - Schema.Unknown -]) -export type OpenResponsesOutputItemAddedEvent = { - readonly "type": "response.output_item.added" - readonly "output_index": number - readonly "item": - | OutputMessage - | OutputItemReasoning - | OutputItemFunctionCall - | OutputItemWebSearchCall - | OutputItemFileSearchCall - | OutputItemImageGenerationCall - readonly "sequence_number": number -} -export const OpenResponsesOutputItemAddedEvent = Schema.Struct({ - "type": Schema.Literal("response.output_item.added"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item": Schema.Union([ - OutputMessage, - OutputItemReasoning, - OutputItemFunctionCall, - OutputItemWebSearchCall, - OutputItemFileSearchCall, - OutputItemImageGenerationCall + Schema.Struct({ "content": Schema.Union([Schema.String, Schema.Null]), "type": Schema.Literal("compaction_delta") }) ], { mode: "oneOf" }), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a new output item is added to the response" }) -export type OpenResponsesOutputItemDoneEvent = { - readonly "type": "response.output_item.done" - readonly "output_index": number - readonly "item": - | OutputMessage - | OutputItemReasoning - | OutputItemFunctionCall - | OutputItemWebSearchCall - | OutputItemFileSearchCall - | OutputItemImageGenerationCall - readonly "sequence_number": number + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_delta") +}).annotate({ + "description": "Event sent when content is added to a content block", + "identifier": "MessagesContentBlockDeltaEvent" +}) +export type AnthropicCodeExecutionResult = { + readonly "content": ReadonlyArray + readonly "return_code": number + readonly "stderr": string + readonly "stdout": string + readonly "type": "code_execution_result" } -export const OpenResponsesOutputItemDoneEvent = Schema.Struct({ - "type": Schema.Literal("response.output_item.done"), - "output_index": Schema.Number.check(Schema.isFinite()), - "item": Schema.Union([ - OutputMessage, - OutputItemReasoning, - OutputItemFunctionCall, - OutputItemWebSearchCall, - OutputItemFileSearchCall, - OutputItemImageGenerationCall - ], { mode: "oneOf" }), - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when an output item is complete" }) -export type OpenResponsesInput = - | string - | ReadonlyArray< - | OpenResponsesReasoning - | OpenResponsesEasyInputMessage - | OpenResponsesInputMessageItem - | OpenResponsesFunctionToolCall - | OpenResponsesFunctionCallOutput - | ResponsesOutputMessage - | ResponsesOutputItemReasoning - | ResponsesOutputItemFunctionCall - | ResponsesWebSearchCallOutput - | ResponsesOutputItemFileSearchCall - | ResponsesImageGenerationCall - > -export const OpenResponsesInput = Schema.Union([ - Schema.String, - Schema.Array( - Schema.Union([ - OpenResponsesReasoning, - OpenResponsesEasyInputMessage, - OpenResponsesInputMessageItem, - OpenResponsesFunctionToolCall, - OpenResponsesFunctionCallOutput, - ResponsesOutputMessage, - ResponsesOutputItemReasoning, - ResponsesOutputItemFunctionCall, - ResponsesWebSearchCallOutput, - ResponsesOutputItemFileSearchCall, - ResponsesImageGenerationCall - ]) - ) -]).annotate({ "description": "Input for a response request - can be a string or array of items" }) -export type ModelsListResponse = { readonly "data": ModelsListResponseData } -export const ModelsListResponse = Schema.Struct({ "data": ModelsListResponseData }).annotate({ - "description": "List of available models" +export const AnthropicCodeExecutionResult = Schema.Struct({ + "content": Schema.Array(AnthropicCodeExecutionOutput), + "return_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "stderr": Schema.String, + "stdout": Schema.String, + "type": Schema.Literal("code_execution_result") +}).annotate({ "identifier": "AnthropicCodeExecutionResult" }) +export type AnthropicEncryptedCodeExecutionResult = { + readonly "content": ReadonlyArray + readonly "encrypted_stdout": string + readonly "return_code": number + readonly "stderr": string + readonly "type": "encrypted_code_execution_result" +} +export const AnthropicEncryptedCodeExecutionResult = Schema.Struct({ + "content": Schema.Array(AnthropicCodeExecutionOutput), + "encrypted_stdout": Schema.String, + "return_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "stderr": Schema.String, + "type": Schema.Literal("encrypted_code_execution_result") +}).annotate({ "identifier": "AnthropicEncryptedCodeExecutionResult" }) +export type AnthropicCaller = + | AnthropicDirectCaller + | AnthropicCodeExecution20250825Caller + | AnthropicCodeExecution20260120Caller +export const AnthropicCaller = Schema.Union([ + AnthropicDirectCaller, + AnthropicCodeExecution20250825Caller, + AnthropicCodeExecution20260120Caller +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicCaller" }) +export type ORAnthropicNullableCaller = + | AnthropicDirectCaller + | AnthropicCodeExecution20250825Caller + | AnthropicCodeExecution20260120Caller + | null +export const ORAnthropicNullableCaller = Schema.Union([ + AnthropicDirectCaller, + AnthropicCodeExecution20250825Caller, + AnthropicCodeExecution20260120Caller, + Schema.Null +], { mode: "oneOf" }).annotate({ "identifier": "ORAnthropicNullableCaller" }) +export type AnthropicBase64ImageSource = { + readonly "data": string + readonly "media_type": AnthropicImageMimeType + readonly "type": "base64" +} +export const AnthropicBase64ImageSource = Schema.Struct({ + "data": Schema.String, + "media_type": AnthropicImageMimeType, + "type": Schema.Literal("base64") +}).annotate({ "identifier": "AnthropicBase64ImageSource" }) +export type AnthropicInputTokensClearAtLeast = Objects_2 | null +export const AnthropicInputTokensClearAtLeast = Schema.Union([Objects_2, Schema.Null]).annotate({ + "identifier": "AnthropicInputTokensClearAtLeast" }) -export type ChatStreamingResponseChunk = { - readonly "data": { - readonly "id": string - readonly "choices": ReadonlyArray - readonly "created": number - readonly "model": string - readonly "object": "chat.completion.chunk" - readonly "system_fingerprint"?: string | null - readonly "error"?: { readonly "message": string; readonly "code": number } - readonly "usage"?: ChatGenerationTokenUsage - } +export type AnthropicIterationCacheCreation = Objects_3 | null +export const AnthropicIterationCacheCreation = Schema.Union([Objects_3, Schema.Null]).annotate({ + "identifier": "AnthropicIterationCacheCreation" +}) +export type AnthropicCodeExecutionToolResultError = { + readonly "error_code": AnthropicServerToolErrorCode + readonly "type": "code_execution_tool_result_error" } -export const ChatStreamingResponseChunk = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String, - "choices": Schema.Array(ChatStreamingChoice), - "created": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "object": Schema.Literal("chat.completion.chunk"), - "system_fingerprint": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "error": Schema.optionalKey( - Schema.Struct({ "message": Schema.String, "code": Schema.Number.check(Schema.isFinite()) }) - ), - "usage": Schema.optionalKey(ChatGenerationTokenUsage) +export const AnthropicCodeExecutionToolResultError = Schema.Struct({ + "error_code": AnthropicServerToolErrorCode, + "type": Schema.Literal("code_execution_tool_result_error") +}).annotate({ "identifier": "AnthropicCodeExecutionToolResultError" }) +export type AnthropicToolSearchResultError = { + readonly "error_code": AnthropicServerToolErrorCode + readonly "error_message": string | null + readonly "type": "tool_search_tool_result_error" +} +export const AnthropicToolSearchResultError = Schema.Struct({ + "error_code": AnthropicServerToolErrorCode, + "error_message": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("tool_search_tool_result_error") +}).annotate({ "identifier": "AnthropicToolSearchResultError" }) +export type AnthropicTextEditorCodeExecutionContent = + | AnthropicTextEditorCodeExecutionToolResultError + | AnthropicTextEditorCodeExecutionViewResult + | AnthropicTextEditorCodeExecutionCreateResult + | AnthropicTextEditorCodeExecutionStrReplaceResult +export const AnthropicTextEditorCodeExecutionContent = Schema.Union([ + AnthropicTextEditorCodeExecutionToolResultError, + AnthropicTextEditorCodeExecutionViewResult, + AnthropicTextEditorCodeExecutionCreateResult, + AnthropicTextEditorCodeExecutionStrReplaceResult +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicTextEditorCodeExecutionContent" }) +export type AnthropicToolSearchResult = { + readonly "tool_references": ReadonlyArray + readonly "type": "tool_search_tool_search_result" +} +export const AnthropicToolSearchResult = Schema.Struct({ + "tool_references": Schema.Array(AnthropicToolReference), + "type": Schema.Literal("tool_search_tool_search_result") +}).annotate({ "identifier": "AnthropicToolSearchResult" }) +export type AnthropicWebSearchToolUserLocation = Objects_4 | null +export const AnthropicWebSearchToolUserLocation = Schema.Union([Objects_4, Schema.Null]).annotate({ + "identifier": "AnthropicWebSearchToolUserLocation" +}) +export type MessagesErrorDetail = { + readonly "error_type"?: ApiErrorType + readonly "message": string + readonly "type": string +} +export const MessagesErrorDetail = Schema.Struct({ + "error_type": Schema.optionalKey(ApiErrorType), + "message": Schema.String, + "type": Schema.String +}).annotate({ "identifier": "MessagesErrorDetail" }) +export type ApplyPatchServerToolConfig = { readonly "engine"?: ApplyPatchEngineEnum } +export const ApplyPatchServerToolConfig = Schema.Struct({ "engine": Schema.optionalKey(ApplyPatchEngineEnum) }) + .annotate({ + "description": "Configuration for the openrouter:apply_patch server tool", + "identifier": "ApplyPatchServerToolConfig" }) +export type ApplyPatchCallOperation = + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation +export const ApplyPatchCallOperation = Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation +], { mode: "oneOf" }).annotate({ + "description": + "The patch operation requested by an `apply_patch_call`. `create_file` and `update_file` carry a V4A diff; `delete_file` omits it.", + "identifier": "ApplyPatchCallOperation" }) -export type ChatResponseChoice = { - readonly "finish_reason": __schema26 - readonly "index": number - readonly "message": AssistantMessage - readonly "logprobs"?: ChatMessageTokenLogprobs | null -} -export const ChatResponseChoice = Schema.Struct({ - "finish_reason": __schema26, - "index": Schema.Number.check(Schema.isFinite()), - "message": AssistantMessage, - "logprobs": Schema.optionalKey(Schema.Union([ChatMessageTokenLogprobs, Schema.Null])) -}) -export type Message = SystemMessage | UserMessage | DeveloperMessage | AssistantMessage | ToolResponseMessage -export const Message = Schema.Union([ - SystemMessage, - UserMessage, - DeveloperMessage, - AssistantMessage, - ToolResponseMessage -], { mode: "oneOf" }) -export type OpenAIResponsesNonStreamingResponse = { +export type OutputItemApplyPatchCall = { + readonly "call_id": string + readonly "created_by"?: string readonly "id": string - readonly "object": "response" - readonly "created_at": number - readonly "model": string - readonly "status": OpenAIResponsesResponseStatus - readonly "completed_at": number - readonly "output": ReadonlyArray< - | OutputMessage - | OutputItemReasoning - | OutputItemFunctionCall - | OutputItemWebSearchCall - | OutputItemFileSearchCall - | OutputItemImageGenerationCall - > - readonly "user"?: string - readonly "output_text"?: string - readonly "prompt_cache_key"?: string - readonly "safety_identifier"?: string - readonly "error": ResponsesErrorField - readonly "incomplete_details": OpenAIResponsesIncompleteDetails - readonly "usage"?: OpenAIResponsesUsage - readonly "max_tool_calls"?: number - readonly "top_logprobs"?: number - readonly "max_output_tokens"?: number - readonly "temperature": number - readonly "top_p": number - readonly "presence_penalty": number - readonly "frequency_penalty": number - readonly "instructions": OpenAIResponsesInput - readonly "metadata": OpenResponsesRequestMetadata - readonly "tools": ReadonlyArray< - | { - readonly "type": "function" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "parameters": {} - } - | OpenResponsesWebSearchPreviewTool - | OpenResponsesWebSearchPreview20250311Tool - | OpenResponsesWebSearchTool - | OpenResponsesWebSearch20250826Tool - > - readonly "tool_choice": OpenAIResponsesToolChoice - readonly "parallel_tool_calls": boolean - readonly "prompt"?: OpenAIResponsesPrompt - readonly "background"?: boolean - readonly "previous_response_id"?: string - readonly "reasoning"?: OpenAIResponsesReasoningConfig - readonly "service_tier"?: OpenAIResponsesServiceTier - readonly "store"?: boolean - readonly "truncation"?: OpenAIResponsesTruncation - readonly "text"?: ResponseTextConfig + readonly "operation": ApplyPatchCreateFileOperation | ApplyPatchUpdateFileOperation | ApplyPatchDeleteFileOperation + readonly "status": "in_progress" | "completed" + readonly "type": "apply_patch_call" } -export const OpenAIResponsesNonStreamingResponse = Schema.Struct({ +export const OutputItemApplyPatchCall = Schema.Struct({ + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), "id": Schema.String, - "object": Schema.Literal("response"), - "created_at": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "status": OpenAIResponsesResponseStatus, - "completed_at": Schema.Number.check(Schema.isFinite()), - "output": Schema.Array( - Schema.Union([ - OutputMessage, - OutputItemReasoning, - OutputItemFunctionCall, - OutputItemWebSearchCall, - OutputItemFileSearchCall, - OutputItemImageGenerationCall - ], { mode: "oneOf" }) + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }), + "status": Schema.Literals(["in_progress", "completed"]), + "type": Schema.Literal("apply_patch_call") +}).annotate({ "identifier": "OutputItemApplyPatchCall" }) +export type BadGatewayResponse = { + readonly "error": BadGatewayResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const BadGatewayResponse = Schema.Struct({ + "error": BadGatewayResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "user": Schema.optionalKey(Schema.String), - "output_text": Schema.optionalKey(Schema.String), - "prompt_cache_key": Schema.optionalKey(Schema.String), - "safety_identifier": Schema.optionalKey(Schema.String), - "error": ResponsesErrorField, - "incomplete_details": OpenAIResponsesIncompleteDetails, - "usage": Schema.optionalKey(OpenAIResponsesUsage), - "max_tool_calls": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "max_output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "temperature": Schema.Number.check(Schema.isFinite()), - "top_p": Schema.Number.check(Schema.isFinite()), - "presence_penalty": Schema.Number.check(Schema.isFinite()), - "frequency_penalty": Schema.Number.check(Schema.isFinite()), - "instructions": OpenAIResponsesInput, - "metadata": OpenResponsesRequestMetadata, - "tools": Schema.Array( - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("function"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "parameters": Schema.Struct({}) - }).annotate({ "description": "Function tool definition" }), - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool - ], { mode: "oneOf" }) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "description": "Bad Gateway - Provider/upstream API failure", "identifier": "BadGatewayResponse" }) +export type BadRequestResponse = { + readonly "error": BadRequestResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const BadRequestResponse = Schema.Struct({ + "error": BadRequestResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "tool_choice": OpenAIResponsesToolChoice, - "parallel_tool_calls": Schema.Boolean, - "prompt": Schema.optionalKey(OpenAIResponsesPrompt), - "background": Schema.optionalKey(Schema.Boolean), - "previous_response_id": Schema.optionalKey(Schema.String), - "reasoning": Schema.optionalKey(OpenAIResponsesReasoningConfig), - "service_tier": Schema.optionalKey(OpenAIResponsesServiceTier), - "store": Schema.optionalKey(Schema.Boolean), - "truncation": Schema.optionalKey(OpenAIResponsesTruncation), - "text": Schema.optionalKey(ResponseTextConfig) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Bad Request - Invalid request parameters or malformed input", + "identifier": "BadRequestResponse" }) -export type OpenResponsesNonStreamingResponse = { +export type BYOKKey = { + readonly "allowed_api_key_hashes": ReadonlyArray | null + readonly "allowed_models": ReadonlyArray | null + readonly "allowed_user_ids": ReadonlyArray | null + readonly "created_at": string + readonly "disabled": boolean readonly "id": string - readonly "object": "response" - readonly "created_at": number - readonly "model": string - readonly "status": OpenAIResponsesResponseStatus - readonly "completed_at": number - readonly "output": ReadonlyArray< - { - readonly "id": string - readonly "role": "assistant" - readonly "type": "message" - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray< - { - readonly "type": "output_text" - readonly "text": string - readonly "annotations"?: ReadonlyArray< - { - readonly "type": "file_citation" - readonly "file_id": string - readonly "filename": string - readonly "index": number - } | { - readonly "type": never - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - readonly "file_id": string - readonly "filename": string - readonly "index": number - } | { - readonly "type": never - readonly "file_id": string - readonly "index": number - readonly "filename": string - } | { - readonly "type": never - readonly "file_id": string - readonly "filename": string - readonly "index": number - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - } | { - readonly "type": "url_citation" - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - } | { - readonly "type": never - readonly "file_id": string - readonly "index": number - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - } | { - readonly "type": never - readonly "file_id": string - readonly "filename": string - readonly "index": number - } | { - readonly "type": never - readonly "url": string - readonly "title": string - readonly "start_index": number - readonly "end_index": number - readonly "file_id": string - readonly "index": number - } | { readonly "type": "file_path"; readonly "file_id": string; readonly "index": number } - > - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { - readonly "type": never - readonly "refusal": string - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { - readonly "type": never - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - readonly "refusal": string - } | { readonly "type": "refusal"; readonly "refusal": string } - > - } | { - readonly "type": never - readonly "id": string - readonly "content": ReadonlyArray< - { - readonly "type": never - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { readonly "type": never; readonly "refusal": string; readonly "text": string } - > - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "role": "assistant" - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" - readonly "role": "assistant" - readonly "content": ReadonlyArray - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray< - { - readonly "type": never - readonly "text": string - readonly "annotations"?: ReadonlyArray - readonly "logprobs"?: ReadonlyArray< - { - readonly "token": string - readonly "bytes": ReadonlyArray - readonly "logprob": number - readonly "top_logprobs": ReadonlyArray< - { readonly "token": string; readonly "bytes": ReadonlyArray; readonly "logprob": number } - > - } - > - } | { readonly "type": never; readonly "refusal": string; readonly "text": string } - > - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": "reasoning" - readonly "id": string - readonly "content"?: ReadonlyArray<{ readonly "type": "reasoning_text"; readonly "text": string }> - readonly "summary": ReadonlyArray<{ readonly "type": "summary_text"; readonly "text": string }> - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "content": ReadonlyArray - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status"?: "completed" | "incomplete" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": "function_call" - readonly "id"?: string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status"?: "completed" | "incomplete" | "in_progress" - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" - readonly "name": string - readonly "arguments": string - readonly "call_id": string - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status": "completed" | "in_progress" - readonly "content": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status": "completed" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status": "completed" | "in_progress" - } | { - readonly "type": "web_search_call" - readonly "id": string - readonly "status": "completed" | "searching" | "in_progress" | "failed" - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "searching" | "in_progress" | "failed" - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" | "failed" - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status": "completed" | "in_progress" - readonly "content": ReadonlyArray - readonly "queries": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status": "completed" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "queries": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status": "completed" | "in_progress" - readonly "queries": ReadonlyArray - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "searching" | "in_progress" | "failed" - readonly "queries": ReadonlyArray - } | { - readonly "type": "file_search_call" - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "searching" | "in_progress" | "failed" - } | { - readonly "type": never - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" | "failed" - readonly "queries": ReadonlyArray - } | { - readonly "id": string - readonly "role": "assistant" - readonly "type": never - readonly "status": "completed" | "in_progress" - readonly "content": ReadonlyArray - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "content"?: ReadonlyArray - readonly "summary": ReadonlyArray - readonly "encrypted_content"?: string - readonly "status": "completed" | "in_progress" - readonly "signature"?: string - readonly "format"?: - | "unknown" - | "openai-responses-v1" - | "azure-openai-responses-v1" - | "xai-responses-v1" - | "anthropic-claude-v1" - | "google-gemini-v1" - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "name": string - readonly "arguments": string - readonly "call_id": string - readonly "status": "completed" | "in_progress" - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "status": "completed" | "in_progress" | "failed" - readonly "result"?: string - } | { - readonly "type": never - readonly "id": string - readonly "queries": ReadonlyArray - readonly "status": "completed" | "in_progress" | "failed" - readonly "result"?: string - } | { - readonly "type": "image_generation_call" - readonly "id": string - readonly "result"?: string - readonly "status": "in_progress" | "completed" | "generating" | "failed" - } - > - readonly "user"?: string - readonly "output_text"?: string - readonly "prompt_cache_key"?: string - readonly "safety_identifier"?: string - readonly "error": ResponsesErrorField - readonly "incomplete_details": OpenAIResponsesIncompleteDetails - readonly "usage"?: { - readonly "input_tokens": number - readonly "input_tokens_details": { readonly "cached_tokens": number } - readonly "output_tokens": number - readonly "output_tokens_details": { readonly "reasoning_tokens": number } - readonly "total_tokens": number - readonly "cost"?: number - readonly "is_byok"?: boolean - readonly "cost_details"?: { - readonly "upstream_inference_cost"?: number - readonly "upstream_inference_input_cost": number - readonly "upstream_inference_output_cost": number - } - } - readonly "max_tool_calls"?: number - readonly "top_logprobs"?: number - readonly "max_output_tokens"?: number - readonly "temperature": number - readonly "top_p": number - readonly "presence_penalty": number - readonly "frequency_penalty": number - readonly "instructions": OpenAIResponsesInput - readonly "metadata": OpenResponsesRequestMetadata - readonly "tools": ReadonlyArray< + readonly "is_fallback": boolean + readonly "label": string + readonly "name"?: string | null + readonly "provider": BYOKProviderSlug + readonly "sort_order": number + readonly "workspace_id": string +} +export const BYOKKey = Schema.Struct({ + "allowed_api_key_hashes": Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) that may use this credential. `null` means no restriction." + }), + "allowed_models": Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of model slugs this credential may be used for. `null` means no restriction." + }), + "allowed_user_ids": Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of user IDs that may use this credential. `null` means no restriction." + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the credential was created." }), + "disabled": Schema.Boolean.annotate({ "description": "Whether this credential is currently disabled." }), + "id": Schema.String.annotate({ + "description": "Stable public identifier for this BYOK credential.", + "format": "uuid" + }), + "is_fallback": Schema.Boolean.annotate({ + "description": + "Whether this credential is treated as a fallback — used only after non-fallback keys for the same provider have been tried." + }), + "label": Schema.String.annotate({ + "description": "Short masked snippet of the key (e.g. the first/last few characters) used to identify it in the UI." + }), + "name": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Optional human-readable name for the credential." + }) + ), + "provider": BYOKProviderSlug, + "sort_order": Schema.Number.annotate({ + "description": "Position within the provider — credentials are tried in ascending sort order." + }).check(Schema.isInt().annotate({ "expected": "an integer" })), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this credential belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "BYOKKey" }) +export type CreateBYOKKeyRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_user_ids"?: ReadonlyArray | null + readonly "disabled"?: boolean + readonly "is_fallback"?: boolean + readonly "key": string + readonly "name"?: string | null + readonly "provider": BYOKProviderSlug + readonly "workspace_id"?: string +} +export const CreateBYOKKeyRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of model slugs this credential may be used for. `null` means no restriction." + }) + ), + "allowed_user_ids": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" }) + ), + Schema.Null + ]).annotate({ + "description": "Optional allowlist of user IDs that may use this credential. `null` means no restriction." + }) + ), + "disabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this credential should be created in a disabled state." }) + ), + "is_fallback": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether this credential is treated as a fallback — used only after non-fallback keys for the same provider have been tried." + }) + ), + "key": Schema.String.annotate({ + "description": + "The raw provider API key or credential. This value is encrypted at rest and never returned in API responses." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "name": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(255).annotate({ "expected": "a value with a length of at most 255" })), + Schema.Null + ]).annotate({ "description": "Optional human-readable name for the credential." }) + ), + "provider": BYOKProviderSlug, + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ) +}).annotate({ "identifier": "CreateBYOKKeyRequest" }) +export type ChatContentVideo = { readonly "type": "video_url"; readonly "video_url": ChatContentVideoInput } +export const ChatContentVideo = Schema.Struct({ + "type": Schema.Literal("video_url"), + "video_url": ChatContentVideoInput +}).annotate({ "description": "Video input content part", "identifier": "ChatContentVideo" }) +export type ChatFormatJsonSchemaConfig = { + readonly "json_schema": ChatJsonSchemaConfig + readonly "type": "json_schema" +} +export const ChatFormatJsonSchemaConfig = Schema.Struct({ + "json_schema": ChatJsonSchemaConfig, + "type": Schema.Literal("json_schema") +}).annotate({ + "description": "JSON Schema response format for structured outputs", + "identifier": "ChatFormatJsonSchemaConfig" +}) +export type ChatToolChoice = "none" | "auto" | "required" | ChatNamedToolChoice | ChatServerToolChoice +export const ChatToolChoice = Schema.Union([ + Schema.Literal("none"), + Schema.Literal("auto"), + Schema.Literal("required"), + ChatNamedToolChoice, + ChatServerToolChoice +]).annotate({ "description": "Tool choice configuration", "identifier": "ChatToolChoice" }) +export type ChatStreamOptions = Objects_6 | null +export const ChatStreamOptions = Schema.Union([Objects_6, Schema.Null]).annotate({ + "description": "Streaming configuration options", + "identifier": "ChatStreamOptions" +}) +export type Objects_7 = { + readonly "content": ReadonlyArray | null + readonly "refusal"?: ReadonlyArray | null + readonly [x: string]: Schema.Json +} +export const Objects_7 = Schema.StructWithRest( + Schema.Struct({ + "content": Schema.Union([Schema.Array(ChatTokenLogprob), Schema.Null]).annotate({ + "description": "Log probabilities for content tokens" + }), + "refusal": Schema.optionalKey( + Schema.Union([Schema.Array(ChatTokenLogprob), Schema.Null]).annotate({ + "description": "Log probabilities for refusal tokens" + }) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type FileSearchServerTool = { + readonly "filters"?: | { - readonly "type": "function" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "parameters": {} + readonly "key": string + readonly "type": "eq" | "ne" | "gt" | "gte" | "lt" | "lte" + readonly "value": string | number | boolean | ReadonlyArray } - | OpenResponsesWebSearchPreviewTool - | OpenResponsesWebSearchPreview20250311Tool - | OpenResponsesWebSearchTool - | OpenResponsesWebSearch20250826Tool - > - readonly "tool_choice": OpenAIResponsesToolChoice - readonly "parallel_tool_calls": boolean - readonly "prompt"?: OpenAIResponsesPrompt - readonly "background"?: boolean - readonly "previous_response_id"?: string - readonly "reasoning"?: OpenAIResponsesReasoningConfig - readonly "service_tier"?: OpenAIResponsesServiceTier - readonly "store"?: boolean - readonly "truncation"?: OpenAIResponsesTruncation - readonly "text"?: ResponseTextConfig + | CompoundFilter + | null + readonly "max_num_results"?: number + readonly "ranking_options"?: { + readonly "ranker"?: "auto" | "default-2024-11-15" + readonly "score_threshold"?: number + } + readonly "type": "file_search" + readonly "vector_store_ids": ReadonlyArray } -export const OpenResponsesNonStreamingResponse = Schema.Struct({ - "id": Schema.String, - "object": Schema.Literal("response"), - "created_at": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "status": OpenAIResponsesResponseStatus, - "completed_at": Schema.Number.check(Schema.isFinite()), - "output": Schema.Array(Schema.Union([ - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Literal("message"), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) +export const FileSearchServerTool = Schema.Struct({ + "filters": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "key": Schema.String, + "type": Schema.Literals(["eq", "ne", "gt", "gte", "lt", "lte"]), + "value": Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ), - "content": Schema.Array(Schema.Union([ + Schema.Boolean, + Schema.Array( Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("output_text"), - "text": Schema.String, - "annotations": Schema.optionalKey( - Schema.Array( - Schema.Union([ - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("file_citation"), - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()), - "filename": Schema.String - }) - ]), - Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("url_citation"), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()), - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()) - }) - ]), - Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "file_id": Schema.String, - "filename": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Never, - "url": Schema.String, - "title": Schema.String, - "start_index": Schema.Number.check(Schema.isFinite()), - "end_index": Schema.Number.check(Schema.isFinite()), - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }), - Schema.Struct({ - "type": Schema.Literal("file_path"), - "file_id": Schema.String, - "index": Schema.Number.check(Schema.isFinite()) - }) - ]) - ]) - ) - ), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }), - Schema.Struct({ - "type": Schema.Never, - "refusal": Schema.String, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }) - ]), - Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))), - "refusal": Schema.String - }), - Schema.Struct({ "type": Schema.Literal("refusal"), "refusal": Schema.String }) + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) ]) - ])) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }), - Schema.Struct({ "type": Schema.Never, "refusal": Schema.String, "text": Schema.String }) - ])), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "role": Schema.Literal("assistant") - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), - "role": Schema.Literal("assistant"), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "content": Schema.Array(Schema.Union([ - Schema.Struct({ - "type": Schema.Never, - "text": Schema.String, - "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), - "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()), - "top_logprobs": Schema.Array( - Schema.Struct({ - "token": Schema.String, - "bytes": Schema.Array(Schema.Number.check(Schema.isFinite())), - "logprob": Schema.Number.check(Schema.isFinite()) - }) - ) - }))) - }), - Schema.Struct({ "type": Schema.Never, "refusal": Schema.String, "text": Schema.String }) - ])), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Literal("reasoning"), - "id": Schema.String, - "content": Schema.optionalKey( - Schema.Array(Schema.Struct({ "type": Schema.Literal("reasoning_text"), "text": Schema.String })) - ), - "summary": Schema.Array(Schema.Struct({ "type": Schema.Literal("summary_text"), "text": Schema.String })), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) ) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) - ), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Literal("function_call"), - "id": Schema.optionalKey(Schema.String), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.optionalKey( - Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ]) + }), + CompoundFilter, + Schema.Null + ])), + "max_num_results": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "ranking_options": Schema.optionalKey( + Schema.Struct({ + "ranker": Schema.optionalKey(Schema.Literals(["auto", "default-2024-11-15"])), + "score_threshold": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]) - }), - Schema.Struct({ - "type": Schema.Literal("web_search_call"), - "id": Schema.String, - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), - "queries": Schema.Array(Schema.String) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "queries": Schema.Array(Schema.String) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "queries": Schema.Array(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]), - "queries": Schema.Array(Schema.String) - }), - Schema.Struct({ - "type": Schema.Literal("file_search_call"), - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([ - Schema.Literal("completed"), - Schema.Literal("searching"), - Schema.Literal("in_progress"), - Schema.Literal("failed") - ]) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]), - "queries": Schema.Array(Schema.String) - }) - ]).annotate({ "description": "An output item from the response" }), - Schema.Union([ - Schema.Struct({ - "id": Schema.String, - "role": Schema.Literal("assistant"), - "type": Schema.Never, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), - "result": Schema.optionalKey(Schema.String) - }).annotate({ "description": "An output message item" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), - "summary": Schema.Array(ReasoningSummaryText), - "encrypted_content": Schema.optionalKey(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "signature": Schema.optionalKey( - Schema.String.annotate({ "description": "A signature for the reasoning content, used for verification" }) - ), - "format": Schema.optionalKey( - Schema.Literals([ - "unknown", - "openai-responses-v1", - "azure-openai-responses-v1", - "xai-responses-v1", - "anthropic-claude-v1", - "google-gemini-v1" - ]).annotate({ "description": "The format of the reasoning content" }) - ), - "result": Schema.optionalKey(Schema.String) - }).annotate({ "description": "An output item containing reasoning" }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "name": Schema.String, - "arguments": Schema.String, - "call_id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), - "result": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), - "result": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Never, - "id": Schema.String, - "queries": Schema.Array(Schema.String), - "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), - "result": Schema.optionalKey(Schema.String) - }), - Schema.Struct({ - "type": Schema.Literal("image_generation_call"), - "id": Schema.String, - "result": Schema.optionalKey(Schema.String), - "status": Schema.Union([ - Schema.Literal("in_progress"), - Schema.Literal("completed"), - Schema.Literal("generating"), - Schema.Literal("failed") - ]) - }) - ]).annotate({ "description": "An output item from the response" }) - ])), - "user": Schema.optionalKey(Schema.String), - "output_text": Schema.optionalKey(Schema.String), - "prompt_cache_key": Schema.optionalKey(Schema.String), - "safety_identifier": Schema.optionalKey(Schema.String), - "error": ResponsesErrorField, - "incomplete_details": OpenAIResponsesIncompleteDetails, - "usage": Schema.optionalKey( - Schema.Struct({ - "input_tokens": Schema.Number.check(Schema.isFinite()), - "input_tokens_details": Schema.Struct({ "cached_tokens": Schema.Number.check(Schema.isFinite()) }), - "output_tokens": Schema.Number.check(Schema.isFinite()), - "output_tokens_details": Schema.Struct({ "reasoning_tokens": Schema.Number.check(Schema.isFinite()) }), - "total_tokens": Schema.Number.check(Schema.isFinite()), - "cost": Schema.optionalKey( - Schema.Number.annotate({ "description": "Cost of the completion" }).check(Schema.isFinite()) - ), - "is_byok": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Whether a request was made using a Bring Your Own Key configuration" - }) - ), - "cost_details": Schema.optionalKey( - Schema.Struct({ - "upstream_inference_cost": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "upstream_inference_input_cost": Schema.Number.check(Schema.isFinite()), - "upstream_inference_output_cost": Schema.Number.check(Schema.isFinite()) - }) ) - }).annotate({ "description": "Token usage information for the response" }) - ), - "max_tool_calls": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "max_output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "temperature": Schema.Number.check(Schema.isFinite()), - "top_p": Schema.Number.check(Schema.isFinite()), - "presence_penalty": Schema.Number.check(Schema.isFinite()), - "frequency_penalty": Schema.Number.check(Schema.isFinite()), - "instructions": OpenAIResponsesInput, - "metadata": OpenResponsesRequestMetadata, - "tools": Schema.Array( - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("function"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "parameters": Schema.Struct({}) - }).annotate({ "description": "Function tool definition" }), - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool - ], { mode: "oneOf" }) + }) ), - "tool_choice": OpenAIResponsesToolChoice, - "parallel_tool_calls": Schema.Boolean, - "prompt": Schema.optionalKey(OpenAIResponsesPrompt), - "background": Schema.optionalKey(Schema.Boolean), - "previous_response_id": Schema.optionalKey(Schema.String), - "reasoning": Schema.optionalKey(OpenAIResponsesReasoningConfig), - "service_tier": Schema.optionalKey(OpenAIResponsesServiceTier), - "store": Schema.optionalKey(Schema.Boolean), - "truncation": Schema.optionalKey(OpenAIResponsesTruncation), - "text": Schema.optionalKey(ResponseTextConfig) -}).annotate({ "description": "Complete non-streaming response from the Responses API" }) -export type OpenResponsesRequest = { - readonly "input"?: OpenResponsesInput - readonly "instructions"?: string - readonly "metadata"?: OpenResponsesRequestMetadata - readonly "tools"?: ReadonlyArray< - | { - readonly "type": "function" - readonly "name": string - readonly "description"?: string - readonly "strict"?: boolean - readonly "parameters": {} - } - | OpenResponsesWebSearchPreviewTool - | OpenResponsesWebSearchPreview20250311Tool - | OpenResponsesWebSearchTool - | OpenResponsesWebSearch20250826Tool - > - readonly "tool_choice"?: OpenAIResponsesToolChoice - readonly "parallel_tool_calls"?: boolean - readonly "model"?: string - readonly "models"?: ReadonlyArray - readonly "text"?: OpenResponsesResponseText - readonly "reasoning"?: OpenResponsesReasoningConfig - readonly "max_output_tokens"?: number - readonly "temperature"?: number - readonly "top_p"?: number - readonly "top_logprobs"?: number - readonly "max_tool_calls"?: number - readonly "presence_penalty"?: number - readonly "frequency_penalty"?: number - readonly "top_k"?: number - readonly "image_config"?: {} - readonly "modalities"?: ReadonlyArray - readonly "prompt_cache_key"?: string - readonly "previous_response_id"?: string - readonly "prompt"?: OpenAIResponsesPrompt - readonly "include"?: ReadonlyArray - readonly "background"?: boolean - readonly "safety_identifier"?: string - readonly "store"?: false - readonly "service_tier"?: "auto" - readonly "truncation"?: "auto" | "disabled" - readonly "stream"?: boolean - readonly "provider"?: { - readonly "allow_fallbacks"?: boolean - readonly "require_parameters"?: boolean - readonly "data_collection"?: DataCollection - readonly "zdr"?: boolean - readonly "enforce_distillable_text"?: boolean - readonly "order"?: ReadonlyArray - readonly "only"?: ReadonlyArray - readonly "ignore"?: ReadonlyArray - readonly "quantizations"?: ReadonlyArray - readonly "sort"?: ProviderSort | ProviderSortConfig | unknown - readonly "max_price"?: { - readonly "prompt"?: BigNumberUnion - readonly "completion"?: string - readonly "image"?: string - readonly "audio"?: string - readonly "request"?: string - } - readonly "preferred_min_throughput"?: PreferredMinThroughput - readonly "preferred_max_latency"?: PreferredMaxLatency - } - readonly "plugins"?: ReadonlyArray< - | { readonly "id": "auto-router"; readonly "enabled"?: boolean; readonly "allowed_models"?: ReadonlyArray } - | { readonly "id": "moderation" } - | { - readonly "id": "web" - readonly "enabled"?: boolean - readonly "max_results"?: number - readonly "search_prompt"?: string - readonly "engine"?: WebSearchEngine - } - | { readonly "id": "file-parser"; readonly "enabled"?: boolean; readonly "pdf"?: PDFParserOptions } - | { readonly "id": "response-healing"; readonly "enabled"?: boolean } - > - readonly "route"?: "fallback" | "sort" - readonly "user"?: string - readonly "session_id"?: string - readonly "trace"?: { - readonly "trace_id"?: string - readonly "trace_name"?: string - readonly "span_name"?: string - readonly "generation_name"?: string - readonly "parent_span_id"?: string - } + "type": Schema.Literal("file_search"), + "vector_store_ids": Schema.Array(Schema.String) +}).annotate({ "description": "File search tool configuration", "identifier": "FileSearchServerTool" }) +export type ConflictResponse = { + readonly "error": ConflictResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const OpenResponsesRequest = Schema.Struct({ - "input": Schema.optionalKey(OpenResponsesInput), - "instructions": Schema.optionalKey(Schema.String), - "metadata": Schema.optionalKey(OpenResponsesRequestMetadata), - "tools": Schema.optionalKey( - Schema.Array( - Schema.Union([ - Schema.Struct({ - "type": Schema.Literal("function"), - "name": Schema.String, - "description": Schema.optionalKey(Schema.String), - "strict": Schema.optionalKey(Schema.Boolean), - "parameters": Schema.Struct({}) - }).annotate({ "description": "Function tool definition" }), - OpenResponsesWebSearchPreviewTool, - OpenResponsesWebSearchPreview20250311Tool, - OpenResponsesWebSearchTool, - OpenResponsesWebSearch20250826Tool - ], { mode: "oneOf" }) - ) +export const ConflictResponse = Schema.Struct({ + "error": ConflictResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "tool_choice": Schema.optionalKey(OpenAIResponsesToolChoice), - "parallel_tool_calls": Schema.optionalKey(Schema.Boolean), - "model": Schema.optionalKey(Schema.String), - "models": Schema.optionalKey(Schema.Array(Schema.String)), - "text": Schema.optionalKey(OpenResponsesResponseText), - "reasoning": Schema.optionalKey(OpenResponsesReasoningConfig), - "max_output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "temperature": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(2)) - ), - "top_p": Schema.optionalKey(Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0))), - "top_logprobs": Schema.optionalKey( - Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)).check(Schema.isLessThanOrEqualTo(20)) - ), - "max_tool_calls": Schema.optionalKey(Schema.Number.check(Schema.isInt())), - "presence_penalty": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check(Schema.isLessThanOrEqualTo(2)) - ), - "frequency_penalty": Schema.optionalKey( - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check(Schema.isLessThanOrEqualTo(2)) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Conflict - Resource conflict or concurrent modification", + "identifier": "ConflictResponse" +}) +export type BashServerToolEnvironment = ContainerAutoEnvironment | ContainerReferenceEnvironment +export const BashServerToolEnvironment = Schema.Union([ContainerAutoEnvironment, ContainerReferenceEnvironment], { + mode: "oneOf" +}).annotate({ + "description": "Execution environment for the bash server tool.", + "identifier": "BashServerToolEnvironment" +}) +export type ShellServerToolEnvironment = ContainerAutoEnvironment | ContainerReferenceEnvironment +export const ShellServerToolEnvironment = Schema.Union([ContainerAutoEnvironment, ContainerReferenceEnvironment], { + mode: "oneOf" +}).annotate({ + "description": + "Server-side execution environment for the shell tool. Only container-backed environments are supported; \"local\" shells are not.", + "identifier": "ShellServerToolEnvironment" +}) +export type ContentFilterEntry = { + readonly "action": ContentFilterAction + readonly "label"?: string | null + readonly "pattern": string +} +export const ContentFilterEntry = Schema.Struct({ + "action": ContentFilterAction, + "label": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })), + Schema.Null + ]).annotate({ "description": "Optional label used in redaction placeholders or error messages" }) ), - "top_k": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "image_config": Schema.optionalKey( - Schema.Struct({}).annotate({ - "description": - "Provider-specific image configuration options. Keys and values vary by model/provider. See https://openrouter.ai/docs/features/multimodal/image-generation for more details." + "pattern": Schema.String.annotate({ "description": "A regex pattern to match against request content" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}).annotate({ + "description": "A custom regex content filter that scans request messages for matching patterns.", + "identifier": "ContentFilterEntry" +}) +export type InputReference = ContentPartImage | ContentPartAudio | ContentPartVideo +export const InputReference = Schema.Union([ContentPartImage, ContentPartAudio, ContentPartVideo], { mode: "oneOf" }) + .annotate({ + "description": + "A reference asset used to guide video generation. Image references are supported by all providers; audio and video references are only honored by providers that support them (currently BytePlus Seedance 2.0).", + "identifier": "InputReference" + }) +export type ContextCompressionPlugin = { + readonly "enabled"?: boolean + readonly "engine"?: ContextCompressionEngine + readonly "id": "context-compression" +} +export const ContextCompressionPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the context-compression plugin for this request. Defaults to true." }) ), - "modalities": Schema.optionalKey( - Schema.Array(ResponsesOutputModality).annotate({ - "description": "Output modalities for the response. Supported values are \"text\" and \"image\"." - }) + "engine": Schema.optionalKey(ContextCompressionEngine), + "id": Schema.Literal("context-compression") +}).annotate({ "identifier": "ContextCompressionPlugin" }) +export type CostDetails = Objects_8 | null +export const CostDetails = Schema.Union([Objects_8, Schema.Null]).annotate({ + "description": "Breakdown of upstream inference costs", + "identifier": "CostDetails" +}) +export type Arrays_9 = ReadonlyArray +export const Arrays_9 = Schema.Array(DABenchmarkEntry).annotate({ + "description": "Design Arena ELO rankings across arena+category pairs." +}) +export type DatetimeServerTool = { + readonly "parameters"?: DatetimeServerToolConfig + readonly "type": "openrouter:datetime" +} +export const DatetimeServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(DatetimeServerToolConfig), + "type": Schema.Literal("openrouter:datetime") +}).annotate({ + "description": "OpenRouter built-in server tool: returns the current date and time", + "identifier": "DatetimeServerTool" +}) +export type EdgeNetworkTimeoutResponse = { + readonly "error": EdgeNetworkTimeoutResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const EdgeNetworkTimeoutResponse = Schema.Struct({ + "error": EdgeNetworkTimeoutResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "prompt_cache_key": Schema.optionalKey(Schema.String), - "previous_response_id": Schema.optionalKey(Schema.String), - "prompt": Schema.optionalKey(OpenAIResponsesPrompt), - "include": Schema.optionalKey(Schema.Array(OpenAIResponsesIncludable)), - "background": Schema.optionalKey(Schema.Boolean), - "safety_identifier": Schema.optionalKey(Schema.String), - "store": Schema.optionalKey(Schema.Literal(false)), - "service_tier": Schema.optionalKey(Schema.Literal("auto")), - "truncation": Schema.optionalKey(Schema.Literals(["auto", "disabled"])), - "stream": Schema.optionalKey(Schema.Boolean), - "provider": Schema.optionalKey( - Schema.Struct({ - "allow_fallbacks": Schema.optionalKey(Schema.Boolean.annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - })), - "require_parameters": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." - }) - ), - "data_collection": Schema.optionalKey(DataCollection), - "zdr": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." - }) - ), - "enforce_distillable_text": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": - "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." - }) - ), - "order": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." - }) - ), - "only": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." - }) - ), - "ignore": Schema.optionalKey( - Schema.Array(Schema.Union([ProviderName, Schema.String])).annotate({ - "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." - }) - ), - "quantizations": Schema.optionalKey( - Schema.Array(Quantization).annotate({ - "description": "A list of quantization levels to filter the provider by." - }) - ), - "sort": Schema.optionalKey( - Schema.Union([ProviderSort, ProviderSortConfig, Schema.Unknown]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(BigNumberUnion), - "completion": Schema.optionalKey( - Schema.String.annotate({ "description": "Price per million completion tokens" }) - ), - "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price per image" })), - "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price per audio unit" })), - "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price per request" })) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), - "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency) - }).annotate({ - "description": "When multiple model providers are available, optionally indicate your routing preference." - }) - ), - "plugins": Schema.optionalKey( - Schema.Array(Schema.Union([ - Schema.Struct({ - "id": Schema.Literal("auto-router"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the auto-router plugin for this request. Defaults to true." - }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": - "List of model patterns to filter which models the auto-router can route between. Supports wildcards (e.g., \"anthropic/*\" matches all Anthropic models). When not specified, uses the default supported models list." - }) - ) - }), - Schema.Struct({ "id": Schema.Literal("moderation") }), - Schema.Struct({ - "id": Schema.Literal("web"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the web-search plugin for this request. Defaults to true." - }) - ), - "max_results": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "search_prompt": Schema.optionalKey(Schema.String), - "engine": Schema.optionalKey(WebSearchEngine) - }), - Schema.Struct({ - "id": Schema.Literal("file-parser"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the file-parser plugin for this request. Defaults to true." - }) - ), - "pdf": Schema.optionalKey(PDFParserOptions) - }), - Schema.Struct({ - "id": Schema.Literal("response-healing"), - "enabled": Schema.optionalKey( - Schema.Boolean.annotate({ - "description": "Set to false to disable the response-healing plugin for this request. Defaults to true." - }) - ) - }) - ], { mode: "oneOf" })).annotate({ - "description": "Plugins you want to enable for this request, including their settings." - }) - ), - "route": Schema.optionalKey( - Schema.Literals(["fallback", "sort"]).annotate({ - "description": - "**DEPRECATED** Use providers.sort.partition instead. Backwards-compatible alias for providers.sort.partition. Accepts legacy values: \"fallback\" (maps to \"model\"), \"sort\" (maps to \"none\")." - }) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Infrastructure Timeout - Provider request timed out at edge network", + "identifier": "EdgeNetworkTimeoutResponse" +}) +export type EndpointsMetadata = { readonly "available": ReadonlyArray; readonly "total": number } +export const EndpointsMetadata = Schema.Struct({ + "available": Schema.Array(EndpointInfo), + "total": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "EndpointsMetadata" }) +export type FileListResponse = { + readonly "cursor": string | null + readonly "data": ReadonlyArray + readonly "first_id": string | null + readonly "has_more": boolean + readonly "last_id": string | null +} +export const FileListResponse = Schema.Struct({ + "cursor": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Opaque cursor for the next page; null when there are no more results." + }), + "data": Schema.Array(FileMetadata), + "first_id": Schema.Union([Schema.String, Schema.Null]), + "has_more": Schema.Boolean, + "last_id": Schema.Union([Schema.String, Schema.Null]) +}).annotate({ + "description": "A page of files belonging to the requesting workspace.", + "identifier": "FileListResponse" +}) +export type FilesServerTool = { readonly "parameters"?: FilesServerToolConfig; readonly "type": "openrouter:files" } +export const FilesServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(FilesServerToolConfig), + "type": Schema.Literal("openrouter:files") +}).annotate({ + "description": + "OpenRouter built-in server tool: read, write, edit, and list workspace files via the Files API. Requires the `x-openrouter-file-ids: openrouter` request header.", + "identifier": "FilesServerTool" +}) +export type ForbiddenResponse = { + readonly "error": ForbiddenResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const ForbiddenResponse = Schema.Struct({ + "error": ForbiddenResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "user": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Forbidden - Authentication successful but insufficient permissions", + "identifier": "ForbiddenResponse" +}) +export type Formats = FormatTextConfig | FormatJsonObjectConfig | FormatJsonSchemaConfig +export const Formats = Schema.Union([FormatTextConfig, FormatJsonObjectConfig, FormatJsonSchemaConfig]).annotate({ + "description": "Text response format configuration", + "identifier": "Formats" +}) +export type FusionAnalysisResult = { + readonly "blind_spots": Arrays_1 + readonly "consensus": Arrays_2 + readonly "contradictions": Arrays_3 + readonly "partial_coverage": Arrays_4 + readonly "unique_insights": Arrays_5 +} +export const FusionAnalysisResult = Schema.Struct({ + "blind_spots": Arrays_1, + "consensus": Arrays_2, + "contradictions": Arrays_3, + "partial_coverage": Arrays_4, + "unique_insights": Arrays_5 +}).annotate({ + "description": "Structured analysis produced by the fusion judge model.", + "identifier": "FusionAnalysisResult" +}) +export type GenerationContentResponse = { readonly "data": GenerationContentData } +export const GenerationContentResponse = Schema.Struct({ "data": GenerationContentData }).annotate({ + "description": "Stored prompt and completion content for a generation", + "identifier": "GenerationContentResponse" +}) +export type GoneResponse = { + readonly "error": GoneResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const GoneResponse = Schema.Struct({ + "error": GoneResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "session_id": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Gone - Endpoint has been permanently removed or deprecated", + "identifier": "GoneResponse" +}) +export type ImageGenerationServerTool_OpenRouter = { + readonly "parameters"?: ImageGenerationServerToolConfig + readonly "type": "openrouter:image_generation" +} +export const ImageGenerationServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(ImageGenerationServerToolConfig), + "type": Schema.Literal("openrouter:image_generation") +}).annotate({ + "description": "OpenRouter built-in server tool: generates images from text prompts using an image generation model", + "identifier": "ImageGenerationServerTool_OpenRouter" +}) +export type OutputImageGenerationCallItem = { + readonly "id": string + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + readonly "type": "image_generation_call" + readonly "prompt"?: string +} +export const OutputImageGenerationCallItem = Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus, + "type": Schema.Literal("image_generation_call"), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt (possibly rewritten) that the image was generated from." }) + ) +}).annotate({ "identifier": "OutputImageGenerationCallItem" }) +export type OutputItemImageGenerationCall = { + readonly "id": string + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + readonly "type": "image_generation_call" +} +export const OutputItemImageGenerationCall = Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus, + "type": Schema.Literal("image_generation_call") +}).annotate({ "identifier": "OutputItemImageGenerationCall" }) +export type ImageModelArchitecture = { + readonly "input_modalities": ReadonlyArray + readonly "output_modalities": ReadonlyArray +} +export const ImageModelArchitecture = Schema.Struct({ + "input_modalities": Schema.Array(ImageInputModality).annotate({ "description": "Supported input modalities" }), + "output_modalities": Schema.Array(ImageOutputModality).annotate({ "description": "Supported output modalities" }) +}).annotate({ "identifier": "ImageModelArchitecture" }) +export type InternalServerResponse = { + readonly "error": InternalServerResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const InternalServerResponse = Schema.Struct({ + "error": InternalServerResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "trace": Schema.optionalKey( - Schema.Struct({ - "trace_id": Schema.optionalKey(Schema.String), - "trace_name": Schema.optionalKey(Schema.String), - "span_name": Schema.optionalKey(Schema.String), - "generation_name": Schema.optionalKey(Schema.String), - "parent_span_id": Schema.optionalKey(Schema.String) - }).annotate({ - "description": - "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations." - }) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Internal Server Error - Unexpected server error", + "identifier": "InternalServerResponse" +}) +export type ListKeyAssignmentsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListKeyAssignmentsResponse = Schema.Struct({ + "data": Schema.Array(KeyAssignment).annotate({ "description": "List of key assignments" }), + "total_count": Schema.Number.annotate({ "description": "Total number of key assignments for this guardrail" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) ) -}).annotate({ "description": "Request schema for Responses endpoint" }) -export type ChatGenerationParams = { - readonly "provider"?: { - readonly "allow_fallbacks"?: boolean | null - readonly "require_parameters"?: boolean | null - readonly "data_collection"?: "deny" | "allow" | null - readonly "zdr"?: boolean | null - readonly "enforce_distillable_text"?: boolean | null - readonly "order"?: __schema5 | null - readonly "only"?: __schema5 | null - readonly "ignore"?: __schema5 | null - readonly "quantizations"?: - | ReadonlyArray<"int4" | "int8" | "fp4" | "fp6" | "fp8" | "fp16" | "bf16" | "fp32" | "unknown"> - | null - readonly "sort"?: ProviderSortUnion | null - readonly "max_price"?: { - readonly "prompt"?: __schema11 | ModelName | __schema13 - readonly "completion"?: __schema11 | ModelName | __schema13 - readonly "image"?: __schema14 - readonly "audio"?: __schema14 - readonly "request"?: __schema14 - } - readonly "preferred_min_throughput"?: number | { - readonly "p50"?: number | null - readonly "p75"?: number | null - readonly "p90"?: number | null - readonly "p99"?: number | null - } | null - readonly "preferred_max_latency"?: number | { - readonly "p50"?: number | null - readonly "p75"?: number | null - readonly "p90"?: number | null - readonly "p99"?: number | null - } | null - } | null - readonly "plugins"?: ReadonlyArray< - | { readonly "id": "auto-router"; readonly "enabled"?: boolean; readonly "allowed_models"?: ReadonlyArray } - | { readonly "id": "moderation" } - | { - readonly "id": "web" - readonly "enabled"?: boolean - readonly "max_results"?: number - readonly "search_prompt"?: string - readonly "engine"?: "native" | "exa" - } - | { - readonly "id": "file-parser" - readonly "enabled"?: boolean - readonly "pdf"?: { readonly "engine"?: "mistral-ocr" | "pdf-text" | "native" } - } - | { readonly "id": "response-healing"; readonly "enabled"?: boolean } - > - readonly "route"?: "fallback" | "sort" | null - readonly "user"?: string - readonly "session_id"?: string - readonly "trace"?: { - readonly "trace_id"?: string - readonly "trace_name"?: string - readonly "span_name"?: string - readonly "generation_name"?: string - readonly "parent_span_id"?: string - } - readonly "messages": ReadonlyArray - readonly "model"?: ModelName - readonly "models"?: ReadonlyArray - readonly "frequency_penalty"?: number | null - readonly "logit_bias"?: {} | null - readonly "logprobs"?: boolean | null - readonly "top_logprobs"?: number | null - readonly "max_completion_tokens"?: number | null - readonly "max_tokens"?: number | null - readonly "metadata"?: {} - readonly "presence_penalty"?: number | null - readonly "reasoning"?: { - readonly "effort"?: "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null - readonly "summary"?: ReasoningSummaryVerbosity | null - } - readonly "response_format"?: - | { readonly "type": "text" } - | { readonly "type": "json_object" } - | ResponseFormatJSONSchema - | ResponseFormatTextGrammar - | { readonly "type": "python" } - readonly "seed"?: number | null - readonly "stop"?: string | ReadonlyArray | null - readonly "stream"?: boolean - readonly "stream_options"?: ChatStreamOptions | null - readonly "temperature"?: number | null - readonly "parallel_tool_calls"?: boolean | null - readonly "tool_choice"?: ToolChoiceOption - readonly "tools"?: ReadonlyArray - readonly "top_p"?: number | null - readonly "debug"?: { readonly "echo_upstream_body"?: boolean } - readonly "image_config"?: {} - readonly "modalities"?: ReadonlyArray<"text" | "image"> +}).annotate({ "identifier": "ListKeyAssignmentsResponse" }) +export type Legacy_ChatContentVideo = { + readonly "type": "input_video" + readonly "video_url": Legacy_ChatContentVideoInput } -export const ChatGenerationParams = Schema.Struct({ - "provider": Schema.optionalKey( +export const Legacy_ChatContentVideo = Schema.Struct({ + "type": Schema.Literal("input_video"), + "video_url": Legacy_ChatContentVideoInput +}).annotate({ + "description": "Video input content part (legacy format - deprecated)", + "identifier": "Legacy_ChatContentVideo" +}) +export type ListMemberAssignmentsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListMemberAssignmentsResponse = Schema.Struct({ + "data": Schema.Array(MemberAssignment).annotate({ "description": "List of member assignments" }), + "total_count": Schema.Number.annotate({ "description": "Total number of member assignments" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListMemberAssignmentsResponse" }) +export type MessagesOutputConfig = { + readonly "effort"?: "low" | "medium" | "high" | "xhigh" | "max" | null + readonly "format"?: Union_10 + readonly "task_budget"?: Union_11 +} +export const MessagesOutputConfig = Schema.Struct({ + "effort": Schema.optionalKey( Schema.Union([ - Schema.Struct({ - "allow_fallbacks": Schema.optionalKey( - Schema.Union([Schema.Boolean, Schema.Null]).annotate({ - "description": - "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" - }) - ), - "require_parameters": Schema.optionalKey( - Schema.Union([Schema.Boolean, Schema.Null]).annotate({ - "description": - "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." - }) - ), - "data_collection": Schema.optionalKey( - Schema.Union([Schema.Literals(["deny", "allow"]), Schema.Null]).annotate({ - "description": - "Data collection setting. If no available model provider meets the requirement, your request will return an error.\n- allow: (default) allow providers which store user data non-transiently and may train on it\n\n- deny: use only providers which do not collect user data." - }) - ), - "zdr": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "enforce_distillable_text": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "order": Schema.optionalKey( - Schema.Union([__schema5, Schema.Null]).annotate({ - "description": - "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." - }) - ), - "only": Schema.optionalKey( - Schema.Union([__schema5, Schema.Null]).annotate({ - "description": - "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." - }) - ), - "ignore": Schema.optionalKey( - Schema.Union([__schema5, Schema.Null]).annotate({ - "description": - "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." - }) - ), - "quantizations": Schema.optionalKey( - Schema.Union([ - Schema.Array(Schema.Literals(["int4", "int8", "fp4", "fp6", "fp8", "fp16", "bf16", "fp32", "unknown"])), - Schema.Null - ]).annotate({ "description": "A list of quantization levels to filter the provider by." }) - ), - "sort": Schema.optionalKey( - Schema.Union([ProviderSortUnion, Schema.Null]).annotate({ - "description": - "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." - }) - ), - "max_price": Schema.optionalKey( - Schema.Struct({ - "prompt": Schema.optionalKey(Schema.Union([__schema11, ModelName, __schema13])), - "completion": Schema.optionalKey(Schema.Union([__schema11, ModelName, __schema13])), - "image": Schema.optionalKey(__schema14), - "audio": Schema.optionalKey(__schema14), - "request": Schema.optionalKey(__schema14) - }).annotate({ - "description": - "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." - }) - ), - "preferred_min_throughput": Schema.optionalKey( - Schema.Union([ - Schema.Union([ - Schema.Number.check(Schema.isFinite()), - Schema.Struct({ - "p50": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p75": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p90": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p99": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])) - }) - ]), - Schema.Null - ]).annotate({ - "description": - "Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." - }) - ), - "preferred_max_latency": Schema.optionalKey( - Schema.Union([ - Schema.Union([ - Schema.Number.check(Schema.isFinite()), - Schema.Struct({ - "p50": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p75": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p90": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])), - "p99": Schema.optionalKey(Schema.Union([Schema.Number.check(Schema.isFinite()), Schema.Null])) - }) - ]), - Schema.Null - ]).annotate({ - "description": - "Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold." - }) - ) - }), + Schema.Literal("low"), + Schema.Literal("medium"), + Schema.Literal("high"), + Schema.Literal("xhigh"), + Schema.Literal("max"), Schema.Null ]).annotate({ - "description": "When multiple model providers are available, optionally indicate your routing preference." - }) - ), - "plugins": Schema.optionalKey( - Schema.Array( - Schema.Union([ - Schema.Struct({ - "id": Schema.Literal("auto-router"), - "enabled": Schema.optionalKey(Schema.Boolean), - "allowed_models": Schema.optionalKey(Schema.Array(Schema.String)) - }), - Schema.Struct({ "id": Schema.Literal("moderation") }), - Schema.Struct({ - "id": Schema.Literal("web"), - "enabled": Schema.optionalKey(Schema.Boolean), - "max_results": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "search_prompt": Schema.optionalKey(Schema.String), - "engine": Schema.optionalKey(Schema.Literals(["native", "exa"])) - }), - Schema.Struct({ - "id": Schema.Literal("file-parser"), - "enabled": Schema.optionalKey(Schema.Boolean), - "pdf": Schema.optionalKey( - Schema.Struct({ "engine": Schema.optionalKey(Schema.Literals(["mistral-ocr", "pdf-text", "native"])) }) - ) - }), - Schema.Struct({ "id": Schema.Literal("response-healing"), "enabled": Schema.optionalKey(Schema.Boolean) }) - ], { mode: "oneOf" }) - ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) - ), - "route": Schema.optionalKey(Schema.Union([Schema.Literals(["fallback", "sort"]), Schema.Null])), - "user": Schema.optionalKey(Schema.String), - "session_id": Schema.optionalKey( - Schema.String.annotate({ - "description": - "A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 128 characters." - }).check(Schema.isMaxLength(128)) - ), - "trace": Schema.optionalKey( - Schema.Struct({ - "trace_id": Schema.optionalKey(Schema.String), - "trace_name": Schema.optionalKey(Schema.String), - "span_name": Schema.optionalKey(Schema.String), - "generation_name": Schema.optionalKey(Schema.String), - "parent_span_id": Schema.optionalKey(Schema.String) - }).annotate({ "description": - "Metadata for observability and tracing. Known keys (trace_id, trace_name, span_name, generation_name, parent_span_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations." + "How much effort the model should put into its response. Higher effort levels may result in more thorough analysis but take longer. Valid values are `low`, `medium`, `high`, `xhigh`, or `max`." }) ), - "messages": Schema.Array(Message).check(Schema.isMinLength(1)), - "model": Schema.optionalKey(ModelName), - "models": Schema.optionalKey(Schema.Array(ModelName)), - "frequency_penalty": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check( - Schema.isLessThanOrEqualTo(2) - ), - Schema.Null - ]) + "format": Schema.optionalKey(Union_10), + "task_budget": Schema.optionalKey(Union_11) +}).annotate({ + "description": + "Configuration for controlling output behavior. Supports the effort parameter and structured output format.", + "identifier": "MessagesOutputConfig" +}) +export type ChatModelNames = ReadonlyArray +export const ChatModelNames = Schema.Array( + Schema.suspend((): Schema.Codec => ModelName).annotate({ + "description": "Available OpenRouter chat completion models" + }) +).annotate({ "description": "Models to use for completion", "identifier": "ChatModelNames" }) +export type ContentPartInputAudio = { readonly "input_audio": MultimodalMedia; readonly "type": "input_audio" } +export const ContentPartInputAudio = Schema.Struct({ + "input_audio": MultimodalMedia, + "type": Schema.Literal("input_audio") +}).annotate({ "identifier": "ContentPartInputAudio" }) +export type ContentPartInputFile = { readonly "input_file": MultimodalMedia; readonly "type": "input_file" } +export const ContentPartInputFile = Schema.Struct({ + "input_file": MultimodalMedia, + "type": Schema.Literal("input_file") +}).annotate({ "identifier": "ContentPartInputFile" }) +export type ContentPartInputVideo = { readonly "input_video": MultimodalMedia; readonly "type": "input_video" } +export const ContentPartInputVideo = Schema.Struct({ + "input_video": MultimodalMedia, + "type": Schema.Literal("input_video") +}).annotate({ "identifier": "ContentPartInputVideo" }) +export type NamespaceTool = { + readonly "description": string + readonly "name": string + readonly "tools": ReadonlyArray + readonly "type": "namespace" +} +export const NamespaceTool = Schema.Struct({ + "description": Schema.String, + "name": Schema.String, + "tools": Schema.Array(Schema.Union([NamespaceFunctionTool, CustomTool])), + "type": Schema.Literal("namespace") +}).annotate({ "description": "Groups function/custom tools under a shared namespace", "identifier": "NamespaceTool" }) +export type NotFoundResponse = { + readonly "error": NotFoundResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const NotFoundResponse = Schema.Struct({ + "error": NotFoundResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "logit_bias": Schema.optionalKey( - Schema.Union([Schema.Struct({}).check(Schema.isPropertyNames(Schema.String)), Schema.Null]) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "description": "Not Found - Resource does not exist", "identifier": "NotFoundResponse" }) +export type ObservabilityFilterRulesConfig = { + readonly "enabled"?: boolean + readonly "groups": ReadonlyArray + readonly [x: string]: Schema.Json +} | null +export const ObservabilityFilterRulesConfig = Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "enabled": Schema.optionalKey(Schema.Boolean), + "groups": Schema.Array(ObservabilityFilterRuleGroup) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] ), - "logprobs": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "top_logprobs": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( - Schema.isLessThanOrEqualTo(20) - ), - Schema.Null - ]) + Schema.Null +]).annotate({ + "description": "Optional structured filter rules controlling which events are forwarded.", + "identifier": "ObservabilityFilterRulesConfig" +}) +export type Objects_10 = { + readonly "enabled"?: boolean + readonly "groups": ReadonlyArray + readonly [x: string]: Schema.Json +} +export const Objects_10 = Schema.StructWithRest( + Schema.Struct({ + "enabled": Schema.optionalKey(Schema.Boolean), + "groups": Schema.Array(ObservabilityFilterRuleGroup) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type OpenAIResponsesUsage = { + readonly "input_tokens": number + readonly "input_tokens_details": Objects_15 + readonly "output_tokens": number + readonly "output_tokens_details": Objects_16 + readonly "total_tokens": number +} +export const OpenAIResponsesUsage = Schema.Struct({ + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "input_tokens_details": Objects_15, + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Objects_16, + "total_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "OpenAIResponsesUsage" }) +export type OpenResponsesLogProbs = { + readonly "bytes"?: ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs"?: ReadonlyArray +} +export const OpenResponsesLogProbs = Schema.Struct({ + "bytes": Schema.optionalKey(Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ), - "max_completion_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(1)), Schema.Null]) + "token": Schema.String, + "top_logprobs": Schema.optionalKey(Schema.Array(OpenResponsesTopLogprobs)) +}).annotate({ "description": "Log probability information for a token", "identifier": "OpenResponsesLogProbs" }) +export type ModelArchitecture = { + readonly "input_modalities": ReadonlyArray + readonly "instruct_type"?: InstructType + readonly "modality": string | null + readonly "output_modalities": ReadonlyArray + readonly "tokenizer"?: ModelGroup +} +export const ModelArchitecture = Schema.Struct({ + "input_modalities": Schema.Array(InputModality).annotate({ "description": "Supported input modalities" }), + "instruct_type": Schema.optionalKey(InstructType), + "modality": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Primary modality of the model" }), + "output_modalities": Schema.Array(OutputModality).annotate({ "description": "Supported output modalities" }), + "tokenizer": Schema.optionalKey(ModelGroup) +}).annotate({ "description": "Model architecture information", "identifier": "ModelArchitecture" }) +export type PayloadTooLargeResponse = { + readonly "error": PayloadTooLargeResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const PayloadTooLargeResponse = Schema.Struct({ + "error": PayloadTooLargeResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "max_tokens": Schema.optionalKey( - Schema.Union([Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(1)), Schema.Null]) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Payload Too Large - Request payload exceeds size limits", + "identifier": "PayloadTooLargeResponse" +}) +export type PaymentRequiredResponse = { + readonly "error": PaymentRequiredResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const PaymentRequiredResponse = Schema.Struct({ + "error": PaymentRequiredResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "metadata": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "presence_penalty": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(-2)).check( - Schema.isLessThanOrEqualTo(2) - ), - Schema.Null - ]) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Payment Required - Insufficient credits or quota to complete request", + "identifier": "PaymentRequiredResponse" +}) +export type PDFParserOptions = { readonly "engine"?: PDFParserEngine } +export const PDFParserOptions = Schema.Struct({ "engine": Schema.optionalKey(PDFParserEngine) }).annotate({ + "description": "Options for PDF parsing.", + "identifier": "PDFParserOptions" +}) +export type PreferredMaxLatency = number | PercentileLatencyCutoffs | null +export const PreferredMaxLatency = Schema.Union([ + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + PercentileLatencyCutoffs, + Schema.Null +]).annotate({ + "description": + "Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold.", + "identifier": "PreferredMaxLatency" +}) +export type PreferredMinThroughput = number | PercentileThroughputCutoffs | null +export const PreferredMinThroughput = Schema.Union([ + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + PercentileThroughputCutoffs, + Schema.Null +]).annotate({ + "description": + "Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold.", + "identifier": "PreferredMinThroughput" +}) +export type PipelineStage = { + readonly "cost_usd"?: number | null + readonly "data"?: {} + readonly "guardrail_id"?: string + readonly "guardrail_scope"?: string + readonly "name": string + readonly "summary"?: string + readonly "type": PipelineStageType +} +export const PipelineStage = Schema.Struct({ + "cost_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) ), - "reasoning": Schema.optionalKey( - Schema.Struct({ - "effort": Schema.optionalKey( - Schema.Union([Schema.Literals(["xhigh", "high", "medium", "low", "minimal", "none"]), Schema.Null]) - ), - "summary": Schema.optionalKey(Schema.Union([ReasoningSummaryVerbosity, Schema.Null])) - }) + "data": Schema.optionalKey(Schema.Struct({})), + "guardrail_id": Schema.optionalKey(Schema.String), + "guardrail_scope": Schema.optionalKey(Schema.String), + "name": Schema.String, + "summary": Schema.optionalKey(Schema.String), + "type": PipelineStageType +}).annotate({ "identifier": "PipelineStage" }) +export type Objects_17 = { + readonly "content": string | ReadonlyArray + readonly "type": "content" + readonly [x: string]: Schema.Json +} +export const Objects_17 = Schema.StructWithRest( + Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(PredictionContentText)]), + "type": Schema.Literal("content") + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type GetPresetVersionResponse = { readonly "data": PresetDesignatedVersion } +export const GetPresetVersionResponse = Schema.Struct({ "data": PresetDesignatedVersion }).annotate({ + "description": "A single version of a preset.", + "identifier": "GetPresetVersionResponse" +}) +export type ListPresetVersionsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListPresetVersionsResponse = Schema.Struct({ + "data": Schema.Array(PresetDesignatedVersion), + "total_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "description": "A paginated list of preset versions.", "identifier": "ListPresetVersionsResponse" }) +export type Preset = { + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "description": string | null + readonly "designated_version_id": string | null + readonly "id": string + readonly "name": string + readonly "slug": string + readonly "status": PresetStatus + readonly "status_updated_at": string | null + readonly "updated_at": string + readonly "workspace_id": string | null +} +export const Preset = Schema.Struct({ + "created_at": Schema.String, + "creator_user_id": Schema.Union([Schema.String, Schema.Null]), + "description": Schema.Union([Schema.String, Schema.Null]), + "designated_version_id": Schema.Union([Schema.String, Schema.Null]), + "id": Schema.String, + "name": Schema.String, + "slug": Schema.String, + "status": PresetStatus, + "status_updated_at": Schema.Union([Schema.String, Schema.Null]), + "updated_at": Schema.String, + "workspace_id": Schema.Union([Schema.String, Schema.Null]) +}).annotate({ "description": "A preset without version details.", "identifier": "Preset" }) +export type PresetWithDesignatedVersion = { + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "description": string | null + readonly "designated_version_id": string | null + readonly "id": string + readonly "name": string + readonly "slug": string + readonly "status": PresetStatus + readonly "status_updated_at": string | null + readonly "updated_at": string + readonly "workspace_id": string | null + readonly "designated_version": PresetDesignatedVersion +} +export const PresetWithDesignatedVersion = Schema.Struct({ + "created_at": Schema.String, + "creator_user_id": Schema.Union([Schema.String, Schema.Null]), + "description": Schema.Union([Schema.String, Schema.Null]), + "designated_version_id": Schema.Union([Schema.String, Schema.Null]), + "id": Schema.String, + "name": Schema.String, + "slug": Schema.String, + "status": PresetStatus, + "status_updated_at": Schema.Union([Schema.String, Schema.Null]), + "updated_at": Schema.String, + "workspace_id": Schema.Union([Schema.String, Schema.Null]), + "designated_version": PresetDesignatedVersion +}).annotate({ + "description": "A preset with its currently designated version.", + "identifier": "PresetWithDesignatedVersion" +}) +export type Preview_WebSearchUserLocation = Objects_18 | null +export const Preview_WebSearchUserLocation = Schema.Union([Objects_18, Schema.Null]).annotate({ + "identifier": "Preview_WebSearchUserLocation" +}) +export type PublicPricing = { + readonly "audio"?: string + readonly "audio_output"?: string + readonly "completion": string + readonly "discount"?: number + readonly "image"?: string + readonly "image_output"?: string + readonly "image_token"?: string + readonly "input_audio_cache"?: string + readonly "input_cache_read"?: string + readonly "input_cache_write"?: string + readonly "input_cache_write_1h"?: string + readonly "internal_reasoning"?: string + readonly "overrides"?: ReadonlyArray + readonly "prompt": string + readonly "request"?: string + readonly "web_search"?: string +} +export const PublicPricing = Schema.Struct({ + "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per audio input token" })), + "audio_output": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per audio output token" })), + "completion": Schema.String.annotate({ "description": "Price in USD per token for completion (output) generation" }), + "discount": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Fractional discount applied to this endpoint's pricing; the price is multiplied by (1 - discount) (0 = no discount, 1 = free)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) ), - "response_format": Schema.optionalKey( - Schema.Union([ - Schema.Struct({ "type": Schema.Literal("text") }), - Schema.Struct({ "type": Schema.Literal("json_object") }), - ResponseFormatJSONSchema, - ResponseFormatTextGrammar, - Schema.Struct({ "type": Schema.Literal("python") }) - ], { mode: "oneOf" }) + "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per input image" })), + "image_output": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per output image" })), + "image_token": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per image token" })), + "input_audio_cache": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached audio input token" }) ), - "seed": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(-9007199254740991)).check( - Schema.isLessThanOrEqualTo(9007199254740991) - ), - Schema.Null - ]) + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached input token (read)" }) ), - "stop": Schema.optionalKey( - Schema.Union([Schema.Union([Schema.String, Schema.Array(ModelName).check(Schema.isMaxLength(4))]), Schema.Null]) + "input_cache_write": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per cache-write token, in USD per token. For providers with multiple cache TTLs (e.g. Anthropic), this is the default (5-minute) cache-write rate." + }) ), - "stream": Schema.optionalKey(Schema.Boolean), - "stream_options": Schema.optionalKey(Schema.Union([ChatStreamOptions, Schema.Null])), - "temperature": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( - Schema.isLessThanOrEqualTo(2) - ), - Schema.Null - ]) + "input_cache_write_1h": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per 1-hour cache-write token, in USD per token. Only present for providers that price an extended (1-hour) cache TTL separately, such as Anthropic." + }) ), - "parallel_tool_calls": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - "tool_choice": Schema.optionalKey(ToolChoiceOption), - "tools": Schema.optionalKey(Schema.Array(ToolDefinitionJson)), - "top_p": Schema.optionalKey( - Schema.Union([ - Schema.Number.check(Schema.isFinite()).check(Schema.isGreaterThanOrEqualTo(0)).check( - Schema.isLessThanOrEqualTo(1) - ), - Schema.Null - ]) + "internal_reasoning": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per internal reasoning token" }) + ), + "overrides": Schema.optionalKey( + Schema.Array(PricingOverride).annotate({ + "description": + "Conditional overrides of the base pricing (e.g. long-context or time-based pricing). An entry applies when all of its condition fields (e.g. min_prompt_tokens, or the utc_start/utc_end time window) match the request; among applicable entries, later entries win per key; price keys absent from an entry inherit the base price. The top-level pricing keys always reflect the price that applies under default conditions." + }) ), - "debug": Schema.optionalKey(Schema.Struct({ "echo_upstream_body": Schema.optionalKey(Schema.Boolean) })), - "image_config": Schema.optionalKey(Schema.Struct({}).check(Schema.isPropertyNames(Schema.String))), - "modalities": Schema.optionalKey(Schema.Array(Schema.Literals(["text", "image"]))) + "prompt": Schema.String.annotate({ "description": "Price in USD per token for prompt (input) processing" }), + "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per request" })), + "web_search": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per web search" })) +}).annotate({ "description": "Pricing information for the model", "identifier": "PublicPricing" }) +export type PromptCacheBreakpoint = Objects_19 | null +export const PromptCacheBreakpoint = Schema.Union([Objects_19, Schema.Null]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically.", + "identifier": "PromptCacheBreakpoint" }) -export type OpenResponsesCreatedEvent = { - readonly "type": "response.created" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesCreatedEvent = Schema.Struct({ - "type": Schema.Literal("response.created"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response is created" }) -export type OpenResponsesInProgressEvent = { - readonly "type": "response.in_progress" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesInProgressEvent = Schema.Struct({ - "type": Schema.Literal("response.in_progress"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response is in progress" }) -export type OpenResponsesCompletedEvent = { - readonly "type": "response.completed" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesCompletedEvent = Schema.Struct({ - "type": Schema.Literal("response.completed"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response has completed successfully" }) -export type OpenResponsesIncompleteEvent = { - readonly "type": "response.incomplete" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesIncompleteEvent = Schema.Struct({ - "type": Schema.Literal("response.incomplete"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response is incomplete" }) -export type OpenResponsesFailedEvent = { - readonly "type": "response.failed" - readonly "response": OpenAIResponsesNonStreamingResponse - readonly "sequence_number": number -} -export const OpenResponsesFailedEvent = Schema.Struct({ - "type": Schema.Literal("response.failed"), - "response": OpenAIResponsesNonStreamingResponse, - "sequence_number": Schema.Number.check(Schema.isFinite()) -}).annotate({ "description": "Event emitted when a response has failed" }) -export type OpenResponsesStreamEvent = - | OpenResponsesCreatedEvent - | OpenResponsesInProgressEvent - | OpenResponsesCompletedEvent - | OpenResponsesIncompleteEvent - | OpenResponsesFailedEvent - | OpenResponsesErrorEvent - | OpenResponsesOutputItemAddedEvent - | OpenResponsesOutputItemDoneEvent - | OpenResponsesContentPartAddedEvent - | OpenResponsesContentPartDoneEvent - | OpenResponsesTextDeltaEvent - | OpenResponsesTextDoneEvent - | OpenResponsesRefusalDeltaEvent - | OpenResponsesRefusalDoneEvent - | OpenResponsesOutputTextAnnotationAddedEvent - | OpenResponsesFunctionCallArgumentsDeltaEvent - | OpenResponsesFunctionCallArgumentsDoneEvent - | OpenResponsesReasoningDeltaEvent - | OpenResponsesReasoningDoneEvent - | OpenResponsesReasoningSummaryPartAddedEvent - | OpenResponsesReasoningSummaryPartDoneEvent - | OpenResponsesReasoningSummaryTextDeltaEvent - | OpenResponsesReasoningSummaryTextDoneEvent - | OpenResponsesImageGenCallInProgress - | OpenResponsesImageGenCallGenerating - | OpenResponsesImageGenCallPartialImage - | OpenResponsesImageGenCallCompleted -export const OpenResponsesStreamEvent = Schema.Union([ - OpenResponsesCreatedEvent, - OpenResponsesInProgressEvent, - OpenResponsesCompletedEvent, - OpenResponsesIncompleteEvent, - OpenResponsesFailedEvent, - OpenResponsesErrorEvent, - OpenResponsesOutputItemAddedEvent, - OpenResponsesOutputItemDoneEvent, - OpenResponsesContentPartAddedEvent, - OpenResponsesContentPartDoneEvent, - OpenResponsesTextDeltaEvent, - OpenResponsesTextDoneEvent, - OpenResponsesRefusalDeltaEvent, - OpenResponsesRefusalDoneEvent, - OpenResponsesOutputTextAnnotationAddedEvent, - OpenResponsesFunctionCallArgumentsDeltaEvent, - OpenResponsesFunctionCallArgumentsDoneEvent, - OpenResponsesReasoningDeltaEvent, - OpenResponsesReasoningDoneEvent, - OpenResponsesReasoningSummaryPartAddedEvent, - OpenResponsesReasoningSummaryPartDoneEvent, - OpenResponsesReasoningSummaryTextDeltaEvent, - OpenResponsesReasoningSummaryTextDoneEvent, - OpenResponsesImageGenCallInProgress, - OpenResponsesImageGenCallGenerating, - OpenResponsesImageGenCallPartialImage, - OpenResponsesImageGenCallCompleted -], { mode: "oneOf" }) -// schemas -export type CreateResponsesRequestJson = OpenResponsesRequest -export const CreateResponsesRequestJson = OpenResponsesRequest -export type CreateResponses200 = OpenResponsesNonStreamingResponse -export const CreateResponses200 = OpenResponsesNonStreamingResponse -export type CreateResponses200Sse = { readonly "data": OpenResponsesStreamEvent } -export const CreateResponses200Sse = Schema.Struct({ "data": OpenResponsesStreamEvent }) -export type CreateResponses400 = BadRequestResponse -export const CreateResponses400 = BadRequestResponse -export type CreateResponses401 = UnauthorizedResponse -export const CreateResponses401 = UnauthorizedResponse -export type CreateResponses402 = PaymentRequiredResponse -export const CreateResponses402 = PaymentRequiredResponse -export type CreateResponses404 = NotFoundResponse -export const CreateResponses404 = NotFoundResponse -export type CreateResponses408 = RequestTimeoutResponse -export const CreateResponses408 = RequestTimeoutResponse -export type CreateResponses413 = PayloadTooLargeResponse -export const CreateResponses413 = PayloadTooLargeResponse -export type CreateResponses422 = UnprocessableEntityResponse -export const CreateResponses422 = UnprocessableEntityResponse -export type CreateResponses429 = TooManyRequestsResponse -export const CreateResponses429 = TooManyRequestsResponse -export type CreateResponses500 = InternalServerResponse -export const CreateResponses500 = InternalServerResponse -export type CreateResponses502 = BadGatewayResponse -export const CreateResponses502 = BadGatewayResponse -export type CreateResponses503 = ServiceUnavailableResponse -export const CreateResponses503 = ServiceUnavailableResponse -export type CreateResponses524 = EdgeNetworkTimeoutResponse -export const CreateResponses524 = EdgeNetworkTimeoutResponse -export type CreateResponses529 = ProviderOverloadedResponse -export const CreateResponses529 = ProviderOverloadedResponse -export type CreateMessagesRequestJson = AnthropicMessagesRequest -export const CreateMessagesRequestJson = AnthropicMessagesRequest -export type CreateMessages200 = AnthropicMessagesResponse -export const CreateMessages200 = AnthropicMessagesResponse -export type CreateMessages200Sse = { readonly "event": string; readonly "data": AnthropicMessagesStreamEvent } -export const CreateMessages200Sse = Schema.Struct({ "event": Schema.String, "data": AnthropicMessagesStreamEvent }) -export type CreateMessages400 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages400 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type PromptCacheOptions = Objects_20 | null +export const PromptCacheOptions = Schema.Union([Objects_20, Schema.Null]).annotate({ + "description": + "Request-level prompt-cache controls. `mode: \"explicit\"` disables OpenAI-managed breakpoints so only blocks marked with `prompt_cache_breakpoint` are cached. Only supported by OpenAI GPT-5.6 and newer.", + "identifier": "PromptCacheOptions" }) -export type CreateMessages401 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ContentFilterBuiltinEntry = { + readonly "action": ContentFilterBuiltinAction + readonly "label"?: string + readonly "scan_scope"?: PromptInjectionScanScope + readonly "slug": ContentFilterBuiltinSlug } -export const CreateMessages401 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export const ContentFilterBuiltinEntry = Schema.Struct({ + "action": ContentFilterBuiltinAction, + "label": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Read-only, system-assigned redaction placeholder derived from the slug (e.g. \"[EMAIL]\", \"[PHONE]\"). Not settable by the caller." + }).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) + ), + "scan_scope": Schema.optionalKey(PromptInjectionScanScope), + "slug": ContentFilterBuiltinSlug +}).annotate({ + "description": + "A builtin content filter entry. Builtin filters include PII detectors and the regex-based prompt injection detector.", + "identifier": "ContentFilterBuiltinEntry" }) -export type CreateMessages403 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ContentFilterBuiltinEntryInput = { + readonly "action": ContentFilterBuiltinAction + readonly "label"?: string + readonly "scan_scope"?: PromptInjectionScanScope + readonly "slug": ContentFilterBuiltinSlug } -export const CreateMessages403 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export const ContentFilterBuiltinEntryInput = Schema.Struct({ + "action": ContentFilterBuiltinAction, + "label": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Deprecated: labels are system-assigned and cannot be set by the caller. Accepted for backward compatibility but silently ignored." + }).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) + ), + "scan_scope": Schema.optionalKey(PromptInjectionScanScope), + "slug": ContentFilterBuiltinSlug +}).annotate({ + "description": + "A builtin content filter entry for create/update requests. Labels are system-assigned and cannot be set by the caller.", + "identifier": "ContentFilterBuiltinEntryInput" }) -export type CreateMessages404 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages404 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type Union_2 = ReadonlyArray | null +export const Union_2 = Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." }) -export type CreateMessages429 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages429 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type Union_3 = ReadonlyArray | null +export const Union_3 = Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." }) -export type CreateMessages500 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } -} -export const CreateMessages500 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export type Union_4 = ReadonlyArray | null +export const Union_4 = Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." }) -export type CreateMessages503 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ProviderOptions = { + readonly "01ai"?: Objects_21 + readonly "ai21"?: Objects_22 + readonly "aion-labs"?: Objects_23 + readonly "akashml"?: Objects_24 + readonly "alibaba"?: Objects_25 + readonly "amazon-bedrock"?: Objects_26 + readonly "amazon-nova"?: Objects_27 + readonly "ambient"?: Objects_28 + readonly "anthropic"?: Objects_29 + readonly "anyscale"?: Objects_30 + readonly "arcee-ai"?: Objects_31 + readonly "atlas-cloud"?: Objects_32 + readonly "atoma"?: Objects_33 + readonly "avian"?: Objects_34 + readonly "azure"?: Objects_35 + readonly "baidu"?: Objects_36 + readonly "baseten"?: Objects_37 + readonly "black-forest-labs"?: Objects_38 + readonly "byteplus"?: Objects_39 + readonly "centml"?: Objects_40 + readonly "cerebras"?: Objects_41 + readonly "chutes"?: Objects_42 + readonly "cirrascale"?: Objects_43 + readonly "clarifai"?: Objects_44 + readonly "cloudflare"?: Objects_45 + readonly "cohere"?: Objects_46 + readonly "coreweave"?: Objects_47 + readonly "crofai"?: Objects_48 + readonly "crucible"?: Objects_49 + readonly "crusoe"?: Objects_50 + readonly "darkbloom"?: Objects_51 + readonly "decart"?: Objects_52 + readonly "deepgram"?: Objects_53 + readonly "deepinfra"?: Objects_54 + readonly "deepseek"?: Objects_55 + readonly "dekallm"?: Objects_56 + readonly "digitalocean"?: Objects_57 + readonly "enfer"?: Objects_58 + readonly "fake-provider"?: Objects_59 + readonly "featherless"?: Objects_60 + readonly "fireworks"?: Objects_61 + readonly "fish-audio"?: Objects_62 + readonly "friendli"?: Objects_63 + readonly "gmicloud"?: Objects_64 + readonly "google-ai-studio"?: Objects_65 + readonly "google-vertex"?: Objects_66 + readonly "gopomelo"?: Objects_67 + readonly "groq"?: Objects_68 + readonly "heygen"?: Objects_69 + readonly "huggingface"?: Objects_70 + readonly "hyperbolic"?: Objects_71 + readonly "hyperbolic-quantized"?: Objects_72 + readonly "inception"?: Objects_73 + readonly "inceptron"?: Objects_74 + readonly "inferact-vllm"?: Objects_75 + readonly "inference-net"?: Objects_76 + readonly "infermatic"?: Objects_77 + readonly "inflection"?: Objects_78 + readonly "inocloud"?: Objects_79 + readonly "io-net"?: Objects_80 + readonly "ionstream"?: Objects_81 + readonly "klusterai"?: Objects_82 + readonly "krea"?: Objects_83 + readonly "lambda"?: Objects_84 + readonly "lepton"?: Objects_85 + readonly "liquid"?: Objects_86 + readonly "lynn"?: Objects_87 + readonly "lynn-private"?: Objects_88 + readonly "mancer"?: Objects_89 + readonly "mancer-old"?: Objects_90 + readonly "mara"?: Objects_91 + readonly "meta"?: Objects_92 + readonly "minimax"?: Objects_93 + readonly "mistral"?: Objects_94 + readonly "modal"?: Objects_95 + readonly "modelrun"?: Objects_96 + readonly "modular"?: Objects_97 + readonly "moonshotai"?: Objects_98 + readonly "morph"?: Objects_99 + readonly "ncompass"?: Objects_100 + readonly "nebius"?: Objects_101 + readonly "nex-agi"?: Objects_102 + readonly "nextbit"?: Objects_103 + readonly "nineteen"?: Objects_104 + readonly "novita"?: Objects_105 + readonly "nvidia"?: Objects_106 + readonly "octoai"?: Objects_107 + readonly "open-inference"?: Objects_108 + readonly "openai"?: Objects_109 + readonly "parasail"?: Objects_110 + readonly "perceptron"?: Objects_111 + readonly "perplexity"?: Objects_112 + readonly "phala"?: Objects_113 + readonly "poolside"?: Objects_114 + readonly "quiver"?: Objects_115 + readonly "recraft"?: Objects_116 + readonly "recursal"?: Objects_117 + readonly "reflection"?: Objects_118 + readonly "reka"?: Objects_119 + readonly "relace"?: Objects_120 + readonly "replicate"?: Objects_121 + readonly "runway"?: Objects_122 + readonly "sail-research"?: Objects_123 + readonly "sakana"?: Objects_124 + readonly "sambanova"?: Objects_125 + readonly "sambanova-cloaked"?: Objects_126 + readonly "seed"?: Objects_127 + readonly "sf-compute"?: Objects_128 + readonly "siliconflow"?: Objects_129 + readonly "sourceful"?: Objects_130 + readonly "stealth"?: Objects_131 + readonly "stepfun"?: Objects_132 + readonly "streamlake"?: Objects_133 + readonly "switchpoint"?: Objects_134 + readonly "targon"?: Objects_135 + readonly "tencent"?: Objects_136 + readonly "tenstorrent"?: Objects_137 + readonly "together"?: Objects_138 + readonly "together-lite"?: Objects_139 + readonly "ubicloud"?: Objects_140 + readonly "upstage"?: Objects_141 + readonly "venice"?: Objects_142 + readonly "wafer"?: Objects_143 + readonly "wandb"?: Objects_144 + readonly "xai"?: Objects_145 + readonly "xiaomi"?: Objects_146 + readonly "z-ai"?: Objects_147 } -export const CreateMessages503 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) +export const ProviderOptions = Schema.Struct({ + "01ai": Schema.optionalKey(Objects_21), + "ai21": Schema.optionalKey(Objects_22), + "aion-labs": Schema.optionalKey(Objects_23), + "akashml": Schema.optionalKey(Objects_24), + "alibaba": Schema.optionalKey(Objects_25), + "amazon-bedrock": Schema.optionalKey(Objects_26), + "amazon-nova": Schema.optionalKey(Objects_27), + "ambient": Schema.optionalKey(Objects_28), + "anthropic": Schema.optionalKey(Objects_29), + "anyscale": Schema.optionalKey(Objects_30), + "arcee-ai": Schema.optionalKey(Objects_31), + "atlas-cloud": Schema.optionalKey(Objects_32), + "atoma": Schema.optionalKey(Objects_33), + "avian": Schema.optionalKey(Objects_34), + "azure": Schema.optionalKey(Objects_35), + "baidu": Schema.optionalKey(Objects_36), + "baseten": Schema.optionalKey(Objects_37), + "black-forest-labs": Schema.optionalKey(Objects_38), + "byteplus": Schema.optionalKey(Objects_39), + "centml": Schema.optionalKey(Objects_40), + "cerebras": Schema.optionalKey(Objects_41), + "chutes": Schema.optionalKey(Objects_42), + "cirrascale": Schema.optionalKey(Objects_43), + "clarifai": Schema.optionalKey(Objects_44), + "cloudflare": Schema.optionalKey(Objects_45), + "cohere": Schema.optionalKey(Objects_46), + "coreweave": Schema.optionalKey(Objects_47), + "crofai": Schema.optionalKey(Objects_48), + "crucible": Schema.optionalKey(Objects_49), + "crusoe": Schema.optionalKey(Objects_50), + "darkbloom": Schema.optionalKey(Objects_51), + "decart": Schema.optionalKey(Objects_52), + "deepgram": Schema.optionalKey(Objects_53), + "deepinfra": Schema.optionalKey(Objects_54), + "deepseek": Schema.optionalKey(Objects_55), + "dekallm": Schema.optionalKey(Objects_56), + "digitalocean": Schema.optionalKey(Objects_57), + "enfer": Schema.optionalKey(Objects_58), + "fake-provider": Schema.optionalKey(Objects_59), + "featherless": Schema.optionalKey(Objects_60), + "fireworks": Schema.optionalKey(Objects_61), + "fish-audio": Schema.optionalKey(Objects_62), + "friendli": Schema.optionalKey(Objects_63), + "gmicloud": Schema.optionalKey(Objects_64), + "google-ai-studio": Schema.optionalKey(Objects_65), + "google-vertex": Schema.optionalKey(Objects_66), + "gopomelo": Schema.optionalKey(Objects_67), + "groq": Schema.optionalKey(Objects_68), + "heygen": Schema.optionalKey(Objects_69), + "huggingface": Schema.optionalKey(Objects_70), + "hyperbolic": Schema.optionalKey(Objects_71), + "hyperbolic-quantized": Schema.optionalKey(Objects_72), + "inception": Schema.optionalKey(Objects_73), + "inceptron": Schema.optionalKey(Objects_74), + "inferact-vllm": Schema.optionalKey(Objects_75), + "inference-net": Schema.optionalKey(Objects_76), + "infermatic": Schema.optionalKey(Objects_77), + "inflection": Schema.optionalKey(Objects_78), + "inocloud": Schema.optionalKey(Objects_79), + "io-net": Schema.optionalKey(Objects_80), + "ionstream": Schema.optionalKey(Objects_81), + "klusterai": Schema.optionalKey(Objects_82), + "krea": Schema.optionalKey(Objects_83), + "lambda": Schema.optionalKey(Objects_84), + "lepton": Schema.optionalKey(Objects_85), + "liquid": Schema.optionalKey(Objects_86), + "lynn": Schema.optionalKey(Objects_87), + "lynn-private": Schema.optionalKey(Objects_88), + "mancer": Schema.optionalKey(Objects_89), + "mancer-old": Schema.optionalKey(Objects_90), + "mara": Schema.optionalKey(Objects_91), + "meta": Schema.optionalKey(Objects_92), + "minimax": Schema.optionalKey(Objects_93), + "mistral": Schema.optionalKey(Objects_94), + "modal": Schema.optionalKey(Objects_95), + "modelrun": Schema.optionalKey(Objects_96), + "modular": Schema.optionalKey(Objects_97), + "moonshotai": Schema.optionalKey(Objects_98), + "morph": Schema.optionalKey(Objects_99), + "ncompass": Schema.optionalKey(Objects_100), + "nebius": Schema.optionalKey(Objects_101), + "nex-agi": Schema.optionalKey(Objects_102), + "nextbit": Schema.optionalKey(Objects_103), + "nineteen": Schema.optionalKey(Objects_104), + "novita": Schema.optionalKey(Objects_105), + "nvidia": Schema.optionalKey(Objects_106), + "octoai": Schema.optionalKey(Objects_107), + "open-inference": Schema.optionalKey(Objects_108), + "openai": Schema.optionalKey(Objects_109), + "parasail": Schema.optionalKey(Objects_110), + "perceptron": Schema.optionalKey(Objects_111), + "perplexity": Schema.optionalKey(Objects_112), + "phala": Schema.optionalKey(Objects_113), + "poolside": Schema.optionalKey(Objects_114), + "quiver": Schema.optionalKey(Objects_115), + "recraft": Schema.optionalKey(Objects_116), + "recursal": Schema.optionalKey(Objects_117), + "reflection": Schema.optionalKey(Objects_118), + "reka": Schema.optionalKey(Objects_119), + "relace": Schema.optionalKey(Objects_120), + "replicate": Schema.optionalKey(Objects_121), + "runway": Schema.optionalKey(Objects_122), + "sail-research": Schema.optionalKey(Objects_123), + "sakana": Schema.optionalKey(Objects_124), + "sambanova": Schema.optionalKey(Objects_125), + "sambanova-cloaked": Schema.optionalKey(Objects_126), + "seed": Schema.optionalKey(Objects_127), + "sf-compute": Schema.optionalKey(Objects_128), + "siliconflow": Schema.optionalKey(Objects_129), + "sourceful": Schema.optionalKey(Objects_130), + "stealth": Schema.optionalKey(Objects_131), + "stepfun": Schema.optionalKey(Objects_132), + "streamlake": Schema.optionalKey(Objects_133), + "switchpoint": Schema.optionalKey(Objects_134), + "targon": Schema.optionalKey(Objects_135), + "tencent": Schema.optionalKey(Objects_136), + "tenstorrent": Schema.optionalKey(Objects_137), + "together": Schema.optionalKey(Objects_138), + "together-lite": Schema.optionalKey(Objects_139), + "ubicloud": Schema.optionalKey(Objects_140), + "upstage": Schema.optionalKey(Objects_141), + "venice": Schema.optionalKey(Objects_142), + "wafer": Schema.optionalKey(Objects_143), + "wandb": Schema.optionalKey(Objects_144), + "xai": Schema.optionalKey(Objects_145), + "xiaomi": Schema.optionalKey(Objects_146), + "z-ai": Schema.optionalKey(Objects_147) +}).annotate({ + "description": + "Provider-specific options keyed by provider slug. Only options for the matched provider are forwarded; the rest are ignored. Unrecognized keys are silently dropped.", + "identifier": "ProviderOptions" }) -export type CreateMessages529 = { - readonly "type": "error" - readonly "error": { readonly "type": string; readonly "message": string } +export type ProviderOverloadedResponse = { + readonly "error": ProviderOverloadedResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const CreateMessages529 = Schema.Struct({ - "type": Schema.Literal("error"), - "error": Schema.Struct({ "type": Schema.String, "message": Schema.String }) -}) -export type GetUserActivityParams = { readonly "date"?: string } -export const GetUserActivityParams = Schema.Struct({ - "date": Schema.optionalKey( - Schema.String.annotate({ "description": "Filter by a single UTC date in the last 30 days (YYYY-MM-DD format)." }) - ) -}) -export type GetUserActivity200 = { readonly "data": ReadonlyArray } -export const GetUserActivity200 = Schema.Struct({ - "data": Schema.Array(ActivityItem).annotate({ "description": "List of activity items" }) +export const ProviderOverloadedResponse = Schema.Struct({ + "error": ProviderOverloadedResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Provider Overloaded - Provider is temporarily overloaded", + "identifier": "ProviderOverloadedResponse" }) -export type GetUserActivity400 = BadRequestResponse -export const GetUserActivity400 = BadRequestResponse -export type GetUserActivity401 = UnauthorizedResponse -export const GetUserActivity401 = UnauthorizedResponse -export type GetUserActivity403 = ForbiddenResponse -export const GetUserActivity403 = ForbiddenResponse -export type GetUserActivity500 = InternalServerResponse -export const GetUserActivity500 = InternalServerResponse -export type GetCredits200 = { readonly "data": { readonly "total_credits": number; readonly "total_usage": number } } -export const GetCredits200 = Schema.Struct({ - "data": Schema.Struct({ - "total_credits": Schema.Number.annotate({ "description": "Total credits purchased" }).check(Schema.isFinite()), - "total_usage": Schema.Number.annotate({ "description": "Total credits used" }).check(Schema.isFinite()) - }) -}).annotate({ "description": "Total credits purchased and used" }) -export type GetCredits401 = UnauthorizedResponse -export const GetCredits401 = UnauthorizedResponse -export type GetCredits403 = ForbiddenResponse -export const GetCredits403 = ForbiddenResponse -export type GetCredits500 = InternalServerResponse -export const GetCredits500 = InternalServerResponse -export type CreateCoinbaseChargeRequestJson = CreateChargeRequest -export const CreateCoinbaseChargeRequestJson = CreateChargeRequest -export type CreateCoinbaseCharge200 = { +export type GenerationResponse = { readonly "data": { - readonly "id": string + readonly "api_type": "completions" | "embeddings" | "rerank" | "tts" | "stt" | "video" | "image" | null + readonly "app_id": number | null + readonly "cache_discount": number | null + readonly "cancelled": boolean | null readonly "created_at": string - readonly "expires_at": string - readonly "web3_data": { - readonly "transfer_intent": { - readonly "call_data": { - readonly "deadline": string - readonly "fee_amount": string - readonly "id": string - readonly "operator": string - readonly "prefix": string - readonly "recipient": string - readonly "recipient_amount": string - readonly "recipient_currency": string - readonly "refund_destination": string - readonly "signature": string - } - readonly "metadata": { - readonly "chain_id": number - readonly "contract_address": string - readonly "sender": string - } - } - } - } -} -export const CreateCoinbaseCharge200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String, - "created_at": Schema.String, - "expires_at": Schema.String, - "web3_data": Schema.Struct({ - "transfer_intent": Schema.Struct({ - "call_data": Schema.Struct({ - "deadline": Schema.String, - "fee_amount": Schema.String, - "id": Schema.String, - "operator": Schema.String, - "prefix": Schema.String, - "recipient": Schema.String, - "recipient_amount": Schema.String, - "recipient_currency": Schema.String, - "refund_destination": Schema.String, - "signature": Schema.String - }), - "metadata": Schema.Struct({ - "chain_id": Schema.Number.check(Schema.isFinite()), - "contract_address": Schema.String, - "sender": Schema.String - }) - }) - }) - }) -}) -export type CreateCoinbaseCharge400 = BadRequestResponse -export const CreateCoinbaseCharge400 = BadRequestResponse -export type CreateCoinbaseCharge401 = UnauthorizedResponse -export const CreateCoinbaseCharge401 = UnauthorizedResponse -export type CreateCoinbaseCharge429 = TooManyRequestsResponse -export const CreateCoinbaseCharge429 = TooManyRequestsResponse -export type CreateCoinbaseCharge500 = InternalServerResponse -export const CreateCoinbaseCharge500 = InternalServerResponse -export type CreateEmbeddingsRequestJson = { - readonly "input": - | string - | ReadonlyArray - | ReadonlyArray - | ReadonlyArray> - | ReadonlyArray< - { - readonly "content": ReadonlyArray< - { readonly "type": "text"; readonly "text": string } | { - readonly "type": "image_url" - readonly "image_url": { readonly "url": string } - } - > - } - > - readonly "model": string - readonly "encoding_format"?: "float" | "base64" - readonly "dimensions"?: number - readonly "user"?: string - readonly "provider"?: ProviderPreferences - readonly "input_type"?: string -} -export const CreateEmbeddingsRequestJson = Schema.Struct({ - "input": Schema.Union([ - Schema.String, - Schema.Array(Schema.String), - Schema.Array(Schema.Number.check(Schema.isFinite())), - Schema.Array(Schema.Array(Schema.Number.check(Schema.isFinite()))), - Schema.Array( - Schema.Struct({ - "content": Schema.Array( - Schema.Union([ - Schema.Struct({ "type": Schema.Literal("text"), "text": Schema.String }), - Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) - ], { mode: "oneOf" }) - ) - }) - ) - ]), - "model": Schema.String, - "encoding_format": Schema.optionalKey(Schema.Literals(["float", "base64"])), - "dimensions": Schema.optionalKey(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))), - "user": Schema.optionalKey(Schema.String), - "provider": Schema.optionalKey(ProviderPreferences), - "input_type": Schema.optionalKey(Schema.String) -}) -export type CreateEmbeddings200 = { - readonly "id"?: string - readonly "object": "list" - readonly "data": ReadonlyArray< - { readonly "object": "embedding"; readonly "embedding": ReadonlyArray | string; readonly "index"?: number } - > - readonly "model": string - readonly "usage"?: { readonly "prompt_tokens": number; readonly "total_tokens": number; readonly "cost"?: number } -} -export const CreateEmbeddings200 = Schema.Struct({ - "id": Schema.optionalKey(Schema.String), - "object": Schema.Literal("list"), - "data": Schema.Array( - Schema.Struct({ - "object": Schema.Literal("embedding"), - "embedding": Schema.Union([Schema.Array(Schema.Number.check(Schema.isFinite())), Schema.String]), - "index": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }) - ), - "model": Schema.String, - "usage": Schema.optionalKey( - Schema.Struct({ - "prompt_tokens": Schema.Number.check(Schema.isFinite()), - "total_tokens": Schema.Number.check(Schema.isFinite()), - "cost": Schema.optionalKey(Schema.Number.check(Schema.isFinite())) - }) - ) -}) -export type CreateEmbeddings200Sse = string -export const CreateEmbeddings200Sse = Schema.String.annotate({ - "description": "Not used for embeddings - embeddings do not support streaming" -}) -export type CreateEmbeddings400 = BadRequestResponse -export const CreateEmbeddings400 = BadRequestResponse -export type CreateEmbeddings401 = UnauthorizedResponse -export const CreateEmbeddings401 = UnauthorizedResponse -export type CreateEmbeddings402 = PaymentRequiredResponse -export const CreateEmbeddings402 = PaymentRequiredResponse -export type CreateEmbeddings404 = NotFoundResponse -export const CreateEmbeddings404 = NotFoundResponse -export type CreateEmbeddings429 = TooManyRequestsResponse -export const CreateEmbeddings429 = TooManyRequestsResponse -export type CreateEmbeddings500 = InternalServerResponse -export const CreateEmbeddings500 = InternalServerResponse -export type CreateEmbeddings502 = BadGatewayResponse -export const CreateEmbeddings502 = BadGatewayResponse -export type CreateEmbeddings503 = ServiceUnavailableResponse -export const CreateEmbeddings503 = ServiceUnavailableResponse -export type CreateEmbeddings524 = EdgeNetworkTimeoutResponse -export const CreateEmbeddings524 = EdgeNetworkTimeoutResponse -export type CreateEmbeddings529 = ProviderOverloadedResponse -export const CreateEmbeddings529 = ProviderOverloadedResponse -export type ListEmbeddingsModels200 = ModelsListResponse -export const ListEmbeddingsModels200 = ModelsListResponse -export type ListEmbeddingsModels400 = BadRequestResponse -export const ListEmbeddingsModels400 = BadRequestResponse -export type ListEmbeddingsModels500 = InternalServerResponse -export const ListEmbeddingsModels500 = InternalServerResponse -export type GetGenerationParams = { readonly "id": string } -export const GetGenerationParams = Schema.Struct({ "id": Schema.String.check(Schema.isMinLength(1)) }) -export type GetGeneration200 = { - readonly "data": { + readonly "data_region": "global" | "europe" + readonly "external_user": string | null + readonly "finish_reason": string | null + readonly "generation_time": number | null + readonly "http_referer": string | null readonly "id": string - readonly "upstream_id": string - readonly "total_cost": number - readonly "cache_discount": number - readonly "upstream_inference_cost": number - readonly "created_at": string + readonly "is_byok": boolean + readonly "latency": number | null readonly "model": string - readonly "app_id": number - readonly "streamed": boolean - readonly "cancelled": boolean - readonly "provider_name": string - readonly "latency": number - readonly "moderation_latency": number - readonly "generation_time": number - readonly "finish_reason": string - readonly "tokens_prompt": number - readonly "tokens_completion": number - readonly "native_tokens_prompt": number - readonly "native_tokens_completion": number - readonly "native_tokens_completion_images": number - readonly "native_tokens_reasoning": number - readonly "native_tokens_cached": number - readonly "num_media_prompt": number - readonly "num_input_audio_prompt": number - readonly "num_media_completion": number - readonly "num_search_results": number + readonly "moderation_latency": number | null + readonly "native_finish_reason": string | null + readonly "native_tokens_cached": number | null + readonly "native_tokens_completion": number | null + readonly "native_tokens_completion_images": number | null + readonly "native_tokens_prompt": number | null + readonly "native_tokens_reasoning": number | null + readonly "num_fetches": number | null + readonly "num_input_audio_prompt": number | null + readonly "num_media_completion": number | null + readonly "num_media_prompt": number | null + readonly "num_search_results": number | null readonly "origin": string + readonly "preset_id": string | null + readonly "provider_name": string | null + readonly "provider_responses": ReadonlyArray | null + readonly "request_id"?: string | null + readonly "response_cache_source_id"?: string | null + readonly "router": string | null + readonly "service_tier": string | null + readonly "session_id"?: string | null + readonly "streamed": boolean | null + readonly "tokens_completion": number | null + readonly "tokens_prompt": number | null + readonly "total_cost": number + readonly "upstream_id": string | null + readonly "upstream_inference_cost": number | null readonly "usage": number - readonly "is_byok": boolean - readonly "native_finish_reason": string - readonly "external_user": string - readonly "api_type": "completions" | "embeddings" - readonly "router": string - readonly "provider_responses": ReadonlyArray< - { - readonly "id"?: string - readonly "endpoint_id"?: string - readonly "model_permaslug"?: string - readonly "provider_name"?: - | "AnyScale" - | "Atoma" - | "Cent-ML" - | "CrofAI" - | "Enfer" - | "GoPomelo" - | "HuggingFace" - | "Hyperbolic 2" - | "InoCloud" - | "Kluster" - | "Lambda" - | "Lepton" - | "Lynn 2" - | "Lynn" - | "Mancer" - | "Meta" - | "Modal" - | "Nineteen" - | "OctoAI" - | "Recursal" - | "Reflection" - | "Replicate" - | "SambaNova 2" - | "SF Compute" - | "Targon" - | "Together 2" - | "Ubicloud" - | "01.AI" - | "AI21" - | "AionLabs" - | "Alibaba" - | "Ambient" - | "Amazon Bedrock" - | "Amazon Nova" - | "Anthropic" - | "Arcee AI" - | "AtlasCloud" - | "Avian" - | "Azure" - | "BaseTen" - | "BytePlus" - | "Black Forest Labs" - | "Cerebras" - | "Chutes" - | "Cirrascale" - | "Clarifai" - | "Cloudflare" - | "Cohere" - | "Crusoe" - | "DeepInfra" - | "DeepSeek" - | "Featherless" - | "Fireworks" - | "Friendli" - | "GMICloud" - | "Google" - | "Google AI Studio" - | "Groq" - | "Hyperbolic" - | "Inception" - | "Inceptron" - | "InferenceNet" - | "Infermatic" - | "Io Net" - | "Inflection" - | "Liquid" - | "Mara" - | "Mancer 2" - | "Minimax" - | "ModelRun" - | "Mistral" - | "Modular" - | "Moonshot AI" - | "Morph" - | "NCompass" - | "Nebius" - | "NextBit" - | "Novita" - | "Nvidia" - | "OpenAI" - | "OpenInference" - | "Parasail" - | "Perplexity" - | "Phala" - | "Relace" - | "SambaNova" - | "Seed" - | "SiliconFlow" - | "Sourceful" - | "StepFun" - | "Stealth" - | "StreamLake" - | "Switchpoint" - | "Together" - | "Upstage" - | "Venice" - | "WandB" - | "Xiaomi" - | "xAI" - | "Z.AI" - | "FakeProvider" - readonly "status": number - readonly "latency"?: number - readonly "is_byok"?: boolean - } - > + readonly "user_agent": string | null + readonly "web_search_engine": string | null } } -export const GetGeneration200 = Schema.Struct({ +export const GenerationResponse = Schema.Struct({ "data": Schema.Struct({ + "api_type": Schema.Union([ + Schema.Literal("completions"), + Schema.Literal("embeddings"), + Schema.Literal("rerank"), + Schema.Literal("tts"), + Schema.Literal("stt"), + Schema.Literal("video"), + Schema.Literal("image"), + Schema.Null + ]).annotate({ "description": "Type of API used for the generation" }), + "app_id": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + .annotate({ "description": "ID of the app that made the request" }), + "cache_discount": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Discount applied due to caching", "format": "double" }), + "cancelled": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the generation was cancelled" + }), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the generation was created" }), + "data_region": Schema.Literals(["global", "europe"]).annotate({ + "description": + "The data region this generation was routed through. 'europe' for EU-routed requests, 'global' otherwise." + }), + "external_user": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "External user identifier" }), + "finish_reason": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Reason the generation finished" + }), + "generation_time": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Time taken for generation in milliseconds", "format": "double" }), + "http_referer": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Referer header from the request" + }), "id": Schema.String.annotate({ "description": "Unique identifier for the generation" }), - "upstream_id": Schema.String.annotate({ "description": "Upstream provider's identifier for this generation" }), - "total_cost": Schema.Number.annotate({ "description": "Total cost of the generation in USD" }).check( - Schema.isFinite() + "is_byok": Schema.Boolean.annotate({ "description": "Whether this used bring-your-own-key" }), + "latency": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Total latency in milliseconds", "format": "double" }), + "model": Schema.String.annotate({ "description": "Model used for the generation" }), + "moderation_latency": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Moderation latency in milliseconds", "format": "double" }), + "native_finish_reason": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Native finish reason as reported by provider" + }), + "native_tokens_cached": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native cached tokens as reported by provider" }), + "native_tokens_completion": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native completion tokens as reported by provider" }), + "native_tokens_completion_images": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native completion image tokens as reported by provider" }), + "native_tokens_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native prompt tokens as reported by provider" }), + "native_tokens_reasoning": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Native reasoning tokens as reported by provider" }), + "num_fetches": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of web fetches performed" }), + "num_input_audio_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of audio inputs in the prompt" }), + "num_media_completion": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of media items in the completion" }), + "num_media_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of media items in the prompt" }), + "num_search_results": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of search results included" }), + "origin": Schema.String.annotate({ "description": "Origin URL of the request" }), + "preset_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ID of the preset used for this generation, null if no preset was used" + }), + "provider_name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Name of the provider that served the request" + }), + "provider_responses": Schema.Union([Schema.Array(ProviderResponse), Schema.Null]).annotate({ + "description": "List of provider responses for this generation, including fallback attempts" + }), + "request_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Unique identifier grouping all generations from a single API request" + }) ), - "cache_discount": Schema.Number.annotate({ "description": "Discount applied due to caching" }).check( - Schema.isFinite() + "response_cache_source_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "If this generation was served from response cache, contains the original generation ID. Null otherwise." + }) ), - "upstream_inference_cost": Schema.Number.annotate({ "description": "Cost charged by the upstream provider" }).check( - Schema.isFinite() + "router": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Router used for the request (e.g., openrouter/auto)" + }), + "service_tier": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Service tier the upstream provider reported running this request on, or null if it did not report one." + }), + "session_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Session identifier grouping multiple generations in the same session" + }) ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the generation was created" }), - "model": Schema.String.annotate({ "description": "Model used for the generation" }), - "app_id": Schema.Number.annotate({ "description": "ID of the app that made the request" }).check(Schema.isFinite()), - "streamed": Schema.Boolean.annotate({ "description": "Whether the response was streamed" }), - "cancelled": Schema.Boolean.annotate({ "description": "Whether the generation was cancelled" }), - "provider_name": Schema.String.annotate({ "description": "Name of the provider that served the request" }), - "latency": Schema.Number.annotate({ "description": "Total latency in milliseconds" }).check(Schema.isFinite()), - "moderation_latency": Schema.Number.annotate({ "description": "Moderation latency in milliseconds" }).check( - Schema.isFinite() + "streamed": Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": "Whether the response was streamed" + }), + "tokens_completion": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of tokens in the completion" }), + "tokens_prompt": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Number of tokens in the prompt" }), + "total_cost": Schema.Number.annotate({ "description": "Total cost of the generation in USD", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "upstream_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Upstream provider's identifier for this generation" + }), + "upstream_inference_cost": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Cost charged by the upstream provider", "format": "double" }), + "usage": Schema.Number.annotate({ "description": "Usage amount in USD", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) ), - "generation_time": Schema.Number.annotate({ "description": "Time taken for generation in milliseconds" }).check( - Schema.isFinite() + "user_agent": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User-Agent header from the request" + }), + "web_search_engine": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The resolved web search engine used for this generation (e.g. exa, firecrawl, parallel)" + }) + }).annotate({ "description": "Generation data" }) +}).annotate({ "description": "Generation response", "identifier": "GenerationResponse" }) +export type Union_5 = ProviderSort | ProviderSortConfig | null +export const Union_5 = Schema.Union([ProviderSort, ProviderSortConfig, Schema.Null]).annotate({ + "description": + "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." +}) +export type PublicEndpoint = { + readonly "context_length": number + readonly "latency_last_30m": PercentileStats + readonly "max_completion_tokens": number | null + readonly "max_prompt_tokens": number | null + readonly "model_id": string + readonly "model_name": string + readonly "name": string + readonly "pricing": { + readonly "audio"?: string + readonly "audio_output"?: string + readonly "completion": string + readonly "discount"?: number + readonly "image"?: string + readonly "image_output"?: string + readonly "image_token"?: string + readonly "input_audio_cache"?: string + readonly "input_cache_read"?: string + readonly "input_cache_write"?: string + readonly "input_cache_write_1h"?: string + readonly "internal_reasoning"?: string + readonly "overrides"?: ReadonlyArray + readonly "prompt": string + readonly "request"?: string + readonly "web_search"?: string + } + readonly "provider_name": ProviderName + readonly "quantization": Quantization | null + readonly "status"?: EndpointStatus + readonly "supported_parameters": ReadonlyArray + readonly "supports_implicit_caching": boolean + readonly "tag": string + readonly "throughput_last_30m": PercentileStats + readonly "uptime_last_1d": number | null + readonly "uptime_last_30m": number | null + readonly "uptime_last_5m": number | null +} +export const PublicEndpoint = Schema.Struct({ + "context_length": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "latency_last_30m": PercentileStats, + "max_completion_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "max_prompt_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "model_id": Schema.String.annotate({ "description": "The unique identifier for the model (permaslug)" }), + "model_name": Schema.String, + "name": Schema.String, + "pricing": Schema.Struct({ + "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per audio input token" })), + "audio_output": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per audio output token" }) ), - "finish_reason": Schema.String.annotate({ "description": "Reason the generation finished" }), - "tokens_prompt": Schema.Number.annotate({ "description": "Number of tokens in the prompt" }).check( - Schema.isFinite() + "completion": Schema.String.annotate({ + "description": "Price in USD per token for completion (output) generation" + }), + "discount": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Fractional discount applied to this endpoint's pricing; the price is multiplied by (1 - discount) (0 = no discount, 1 = free)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) ), - "tokens_completion": Schema.Number.annotate({ "description": "Number of tokens in the completion" }).check( - Schema.isFinite() + "image": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per input image" })), + "image_output": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per output image" })), + "image_token": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per image token" })), + "input_audio_cache": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached audio input token" }) ), - "native_tokens_prompt": Schema.Number.annotate({ "description": "Native prompt tokens as reported by provider" }) - .check(Schema.isFinite()), - "native_tokens_completion": Schema.Number.annotate({ - "description": "Native completion tokens as reported by provider" - }).check(Schema.isFinite()), - "native_tokens_completion_images": Schema.Number.annotate({ - "description": "Native completion image tokens as reported by provider" - }).check(Schema.isFinite()), - "native_tokens_reasoning": Schema.Number.annotate({ - "description": "Native reasoning tokens as reported by provider" - }).check(Schema.isFinite()), - "native_tokens_cached": Schema.Number.annotate({ "description": "Native cached tokens as reported by provider" }) - .check(Schema.isFinite()), - "num_media_prompt": Schema.Number.annotate({ "description": "Number of media items in the prompt" }).check( - Schema.isFinite() + "input_cache_read": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per cached input token (read)" }) ), - "num_input_audio_prompt": Schema.Number.annotate({ "description": "Number of audio inputs in the prompt" }).check( - Schema.isFinite() + "input_cache_write": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per cache-write token, in USD per token. For providers with multiple cache TTLs (e.g. Anthropic), this is the default (5-minute) cache-write rate." + }) ), - "num_media_completion": Schema.Number.annotate({ "description": "Number of media items in the completion" }).check( - Schema.isFinite() + "input_cache_write_1h": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Price per 1-hour cache-write token, in USD per token. Only present for providers that price an extended (1-hour) cache TTL separately, such as Anthropic." + }) ), - "num_search_results": Schema.Number.annotate({ "description": "Number of search results included" }).check( - Schema.isFinite() + "internal_reasoning": Schema.optionalKey( + Schema.String.annotate({ "description": "Price in USD per internal reasoning token" }) ), - "origin": Schema.String.annotate({ "description": "Origin URL of the request" }), - "usage": Schema.Number.annotate({ "description": "Usage amount in USD" }).check(Schema.isFinite()), - "is_byok": Schema.Boolean.annotate({ "description": "Whether this used bring-your-own-key" }), - "native_finish_reason": Schema.String.annotate({ "description": "Native finish reason as reported by provider" }), - "external_user": Schema.String.annotate({ "description": "External user identifier" }), - "api_type": Schema.Literals(["completions", "embeddings"]).annotate({ - "description": "Type of API used for the generation" - }), - "router": Schema.String.annotate({ "description": "Router used for the request (e.g., openrouter/auto)" }), - "provider_responses": Schema.Array(Schema.Struct({ - "id": Schema.optionalKey(Schema.String), - "endpoint_id": Schema.optionalKey(Schema.String), - "model_permaslug": Schema.optionalKey(Schema.String), - "provider_name": Schema.optionalKey( - Schema.Literals([ - "AnyScale", - "Atoma", - "Cent-ML", - "CrofAI", - "Enfer", - "GoPomelo", - "HuggingFace", - "Hyperbolic 2", - "InoCloud", - "Kluster", - "Lambda", - "Lepton", - "Lynn 2", - "Lynn", - "Mancer", - "Meta", - "Modal", - "Nineteen", - "OctoAI", - "Recursal", - "Reflection", - "Replicate", - "SambaNova 2", - "SF Compute", - "Targon", - "Together 2", - "Ubicloud", - "01.AI", - "AI21", - "AionLabs", - "Alibaba", - "Ambient", - "Amazon Bedrock", - "Amazon Nova", - "Anthropic", - "Arcee AI", - "AtlasCloud", - "Avian", - "Azure", - "BaseTen", - "BytePlus", - "Black Forest Labs", - "Cerebras", - "Chutes", - "Cirrascale", - "Clarifai", - "Cloudflare", - "Cohere", - "Crusoe", - "DeepInfra", - "DeepSeek", - "Featherless", - "Fireworks", - "Friendli", - "GMICloud", - "Google", - "Google AI Studio", - "Groq", - "Hyperbolic", - "Inception", - "Inceptron", - "InferenceNet", - "Infermatic", - "Io Net", - "Inflection", - "Liquid", - "Mara", - "Mancer 2", - "Minimax", - "ModelRun", - "Mistral", - "Modular", - "Moonshot AI", - "Morph", - "NCompass", - "Nebius", - "NextBit", - "Novita", - "Nvidia", - "OpenAI", - "OpenInference", - "Parasail", - "Perplexity", - "Phala", - "Relace", - "SambaNova", - "Seed", - "SiliconFlow", - "Sourceful", - "StepFun", - "Stealth", - "StreamLake", - "Switchpoint", - "Together", - "Upstage", - "Venice", - "WandB", - "Xiaomi", - "xAI", - "Z.AI", - "FakeProvider" - ]) - ), - "status": Schema.Number.check(Schema.isFinite()), - "latency": Schema.optionalKey(Schema.Number.check(Schema.isFinite())), - "is_byok": Schema.optionalKey(Schema.Boolean) - })).annotate({ "description": "List of provider responses for this generation, including fallback attempts" }) - }).annotate({ "description": "Generation data" }) -}).annotate({ "description": "Generation response" }) -export type GetGeneration401 = UnauthorizedResponse -export const GetGeneration401 = UnauthorizedResponse -export type GetGeneration402 = PaymentRequiredResponse -export const GetGeneration402 = PaymentRequiredResponse -export type GetGeneration404 = NotFoundResponse -export const GetGeneration404 = NotFoundResponse -export type GetGeneration429 = TooManyRequestsResponse -export const GetGeneration429 = TooManyRequestsResponse -export type GetGeneration500 = InternalServerResponse -export const GetGeneration500 = InternalServerResponse -export type GetGeneration502 = BadGatewayResponse -export const GetGeneration502 = BadGatewayResponse -export type GetGeneration524 = EdgeNetworkTimeoutResponse -export const GetGeneration524 = EdgeNetworkTimeoutResponse -export type GetGeneration529 = ProviderOverloadedResponse -export const GetGeneration529 = ProviderOverloadedResponse -export type ListModelsCount200 = ModelsCountResponse -export const ListModelsCount200 = ModelsCountResponse -export type ListModelsCount500 = InternalServerResponse -export const ListModelsCount500 = InternalServerResponse -export type GetModelsParams = { - readonly "category"?: - | "programming" - | "roleplay" - | "marketing" - | "marketing/seo" - | "technology" - | "science" - | "translation" - | "legal" - | "finance" - | "health" - | "trivia" - | "academia" - readonly "supported_parameters"?: string -} -export const GetModelsParams = Schema.Struct({ - "category": Schema.optionalKey( - Schema.Literals([ - "programming", - "roleplay", - "marketing", - "marketing/seo", - "technology", - "science", - "translation", - "legal", - "finance", - "health", - "trivia", - "academia" - ]).annotate({ "description": "Filter models by use case category" }) - ), - "supported_parameters": Schema.optionalKey(Schema.String) + "overrides": Schema.optionalKey( + Schema.Array(PricingOverride).annotate({ + "description": + "Conditional overrides of the base pricing (e.g. long-context or time-based pricing). An entry applies when all of its condition fields (e.g. min_prompt_tokens, or the utc_start/utc_end time window) match the request; among applicable entries, later entries win per key; price keys absent from an entry inherit the base price. The top-level pricing keys always reflect the price that applies under default conditions." + }) + ), + "prompt": Schema.String.annotate({ "description": "Price in USD per token for prompt (input) processing" }), + "request": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per request" })), + "web_search": Schema.optionalKey(Schema.String.annotate({ "description": "Price in USD per web search" })) + }), + "provider_name": ProviderName, + "quantization": Schema.Union([Quantization, Schema.Null]), + "status": Schema.optionalKey(EndpointStatus), + "supported_parameters": Schema.Array(Parameter), + "supports_implicit_caching": Schema.Boolean, + "tag": Schema.String, + "throughput_last_30m": Schema.suspend((): Schema.Codec => PercentileStats).annotate({ + "description": + "Throughput percentiles in tokens per second over the last 30 minutes. Throughput measures output token generation speed. Only visible when authenticated with an API key or cookie; returns null for unauthenticated requests." + }), + "uptime_last_1d": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": + "Uptime percentage over the last 1 day, calculated as successful requests / (successful + error requests) * 100. Rate-limited requests are excluded. Returns null if insufficient data.", + "format": "double" + }), + "uptime_last_30m": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "uptime_last_5m": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": + "Uptime percentage over the last 5 minutes, calculated as successful requests / (successful + error requests) * 100. Rate-limited requests are excluded. Returns null if insufficient data.", + "format": "double" + }) +}).annotate({ "description": "Information about a specific model endpoint", "identifier": "PublicEndpoint" }) +export type CapabilityDescriptor = EnumCapability | RangeCapability | BooleanCapability +export const CapabilityDescriptor = Schema.Union([EnumCapability, RangeCapability, BooleanCapability], { + mode: "oneOf" +}).annotate({ + "description": "A typed descriptor for one supported request parameter.", + "identifier": "CapabilityDescriptor" }) -export type GetModels200 = ModelsListResponse -export const GetModels200 = ModelsListResponse -export type GetModels400 = BadRequestResponse -export const GetModels400 = BadRequestResponse -export type GetModels500 = InternalServerResponse -export const GetModels500 = InternalServerResponse -export type ListModelsUser200 = ModelsListResponse -export const ListModelsUser200 = ModelsListResponse -export type ListModelsUser401 = UnauthorizedResponse -export const ListModelsUser401 = UnauthorizedResponse -export type ListModelsUser404 = NotFoundResponse -export const ListModelsUser404 = NotFoundResponse -export type ListModelsUser500 = InternalServerResponse -export const ListModelsUser500 = InternalServerResponse -export type ListEndpoints200 = { readonly "data": ListEndpointsResponse } -export const ListEndpoints200 = Schema.Struct({ "data": ListEndpointsResponse }) -export type ListEndpoints404 = NotFoundResponse -export const ListEndpoints404 = NotFoundResponse -export type ListEndpoints500 = InternalServerResponse -export const ListEndpoints500 = InternalServerResponse -export type ListEndpointsZdr200 = { readonly "data": ReadonlyArray } -export const ListEndpointsZdr200 = Schema.Struct({ "data": Schema.Array(PublicEndpoint) }) -export type ListEndpointsZdr500 = InternalServerResponse -export const ListEndpointsZdr500 = InternalServerResponse -export type ListProviders200 = { - readonly "data": ReadonlyArray< - { - readonly "name": string - readonly "slug": string - readonly "privacy_policy_url": string - readonly "terms_of_service_url"?: string - readonly "status_page_url"?: string - } - > +export type AppRankingsResponse = { + readonly "data": ReadonlyArray + readonly "meta": RankingsDailyMeta } -export const ListProviders200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "name": Schema.String.annotate({ "description": "Display name of the provider" }), - "slug": Schema.String.annotate({ "description": "URL-friendly identifier for the provider" }), - "privacy_policy_url": Schema.String.annotate({ "description": "URL to the provider's privacy policy" }), - "terms_of_service_url": Schema.optionalKey( - Schema.String.annotate({ "description": "URL to the provider's terms of service" }) - ), - "status_page_url": Schema.optionalKey( - Schema.String.annotate({ "description": "URL to the provider's status page" }) - ) - })) +export const AppRankingsResponse = Schema.Struct({ + "data": Schema.Array(AppRankingsItem).annotate({ + "description": + "Apps ranked per the requested `sort`, re-numbered 1..N after category filtering. `popular` sorts by `total_tokens` descending; `trending` sorts by absolute excess token growth descending and may return fewer than `limit` rows when few apps are growing." + }), + "meta": RankingsDailyMeta +}).annotate({ "identifier": "AppRankingsResponse" }) +export type RankingsDailyResponse = { + readonly "data": ReadonlyArray + readonly "meta": RankingsDailyMeta +} +export const RankingsDailyResponse = Schema.Struct({ + "data": Schema.Array(RankingsDailyItem).annotate({ + "description": + "Up to 51 rows per day — the top 50 public models by `total_tokens` for each UTC calendar date in the window, plus one aggregated `other` row summing every model outside that top 50 (omitted when the long tail is empty). Rows are sorted by `date` ascending, then by `total_tokens` descending, with `other` pinned last within its date. Ties between real models break alphabetically on `model_permaslug` so the order is stable across requests." + }), + "meta": RankingsDailyMeta +}).annotate({ "identifier": "RankingsDailyResponse" }) +export type Union_12 = ReadonlyArray | null +export const Union_12 = Schema.Union([Schema.Array(ReasoningEffort), Schema.Null]).annotate({ + "description": + "Allowed reasoning effort values for this model, in descending effort order (highest first). Null means no allowlist — all gateway effort values are accepted." }) -export type ListProviders500 = InternalServerResponse -export const ListProviders500 = InternalServerResponse -export type ListParams = { readonly "include_disabled"?: string; readonly "offset"?: string } -export const ListParams = Schema.Struct({ - "include_disabled": Schema.optionalKey( - Schema.String.annotate({ "description": "Whether to include disabled API keys in the response" }) - ), - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of API keys to skip for pagination" })) +export type ReasoningDetailEncrypted = { + readonly "data": string + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "type": "reasoning.encrypted" +} +export const ReasoningDetailEncrypted = Schema.Struct({ + "data": Schema.String, + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.Literal("reasoning.encrypted") +}).annotate({ "description": "Reasoning detail encrypted schema", "identifier": "ReasoningDetailEncrypted" }) +export type ReasoningDetailServerToolCall = { + readonly "arguments": string + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "result": string + readonly "tool_call_id"?: string | null + readonly "tool_name": string + readonly "type": "reasoning.server_tool_call" +} +export const ReasoningDetailServerToolCall = Schema.Struct({ + "arguments": Schema.String, + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "result": Schema.String, + "tool_call_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "tool_name": Schema.String, + "type": Schema.Literal("reasoning.server_tool_call") +}).annotate({ + "description": + "Record of an OpenRouter server-tool invocation (e.g. openrouter:fusion), carried in reasoning_details so a prior tool call can be rehydrated into a later turn of the same conversation.", + "identifier": "ReasoningDetailServerToolCall" }) -export type List200 = { - readonly "data": ReadonlyArray< - { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } - > +export type ReasoningDetailSummary = { + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "summary": string + readonly "type": "reasoning.summary" } -export const List200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - })).annotate({ "description": "List of API keys" }) +export const ReasoningDetailSummary = Schema.Struct({ + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "summary": Schema.String, + "type": Schema.Literal("reasoning.summary") +}).annotate({ "description": "Reasoning detail summary schema", "identifier": "ReasoningDetailSummary" }) +export type ReasoningDetailText = { + readonly "format"?: ReasoningFormat + readonly "id"?: string | null + readonly "index"?: number + readonly "signature"?: string | null + readonly "text"?: string | null + readonly "type": "reasoning.text" +} +export const ReasoningDetailText = Schema.Struct({ + "format": Schema.optionalKey(ReasoningFormat), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "index": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "signature": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "text": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("reasoning.text") +}).annotate({ "description": "Reasoning detail text schema", "identifier": "ReasoningDetailText" }) +export type BaseReasoningSummaryPartAddedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.added" +} +export const BaseReasoningSummaryPartAddedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.added") +}).annotate({ + "description": "Event emitted when a reasoning summary part is added", + "identifier": "BaseReasoningSummaryPartAddedEvent" }) -export type List401 = UnauthorizedResponse -export const List401 = UnauthorizedResponse -export type List429 = TooManyRequestsResponse -export const List429 = TooManyRequestsResponse -export type List500 = InternalServerResponse -export const List500 = InternalServerResponse -export type CreateKeysRequestJson = { - readonly "name": string - readonly "limit"?: number - readonly "limit_reset"?: "daily" | "weekly" | "monthly" - readonly "include_byok_in_limit"?: boolean - readonly "expires_at"?: string +export type BaseReasoningSummaryPartDoneEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.done" } -export const CreateKeysRequestJson = Schema.Struct({ - "name": Schema.String.annotate({ "description": "Name for the new API key" }).check(Schema.isMinLength(1)), - "limit": Schema.optionalKey( - Schema.Number.annotate({ "description": "Optional spending limit for the API key in USD" }).check(Schema.isFinite()) - ), - "limit_reset": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": - "Type of limit reset for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." - }) - ), - "include_byok_in_limit": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) +export const BaseReasoningSummaryPartDoneEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.done") +}).annotate({ + "description": "Event emitted when a reasoning summary part is complete", + "identifier": "BaseReasoningSummaryPartDoneEvent" +}) +export type OutputReasoningItem = { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null +} +export const OutputReasoningItem = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }))]) ), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": - "Optional ISO 8601 UTC timestamp when the API key should expire. Must be UTC, other timezones will be rejected", - "format": "date-time" + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" }) ) +}).annotate({ "description": "An output item containing reasoning", "identifier": "OutputReasoningItem" }) +export type ReasoningItem = { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null +} +export const ReasoningItem = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }))]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Reasoning output item with signature and format extensions", + "identifier": "ReasoningItem" }) -export type CreateKeys201 = { - readonly "data": { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } - readonly "key": string +export type ReasoningSummaryPartAddedEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.added" } -export const CreateKeys201 = Schema.Struct({ - "data": Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - }).annotate({ "description": "The created API key information" }), - "key": Schema.String.annotate({ "description": "The actual API key string (only shown once)" }) +export const ReasoningSummaryPartAddedEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.added") +}).annotate({ + "description": "Event emitted when a reasoning summary part is added", + "identifier": "ReasoningSummaryPartAddedEvent" }) -export type CreateKeys400 = BadRequestResponse -export const CreateKeys400 = BadRequestResponse -export type CreateKeys401 = UnauthorizedResponse -export const CreateKeys401 = UnauthorizedResponse -export type CreateKeys429 = TooManyRequestsResponse -export const CreateKeys429 = TooManyRequestsResponse -export type CreateKeys500 = InternalServerResponse -export const CreateKeys500 = InternalServerResponse -export type GetKey200 = { - readonly "data": { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } +export type ReasoningSummaryPartDoneEvent = { + readonly "item_id": string + readonly "output_index": number + readonly "part": ReasoningSummaryText + readonly "sequence_number": number + readonly "summary_index": number + readonly "type": "response.reasoning_summary_part.done" } -export const GetKey200 = Schema.Struct({ - "data": Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - }).annotate({ "description": "The API key information" }) +export const ReasoningSummaryPartDoneEvent = Schema.Struct({ + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": ReasoningSummaryText, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "summary_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.reasoning_summary_part.done") +}).annotate({ + "description": "Event emitted when a reasoning summary part is complete", + "identifier": "ReasoningSummaryPartDoneEvent" }) -export type GetKey401 = UnauthorizedResponse -export const GetKey401 = UnauthorizedResponse -export type GetKey404 = NotFoundResponse -export const GetKey404 = NotFoundResponse -export type GetKey429 = TooManyRequestsResponse -export const GetKey429 = TooManyRequestsResponse -export type GetKey500 = InternalServerResponse -export const GetKey500 = InternalServerResponse -export type DeleteKeys200 = { readonly "deleted": true } -export const DeleteKeys200 = Schema.Struct({ - "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the API key was deleted" }) +export type Objects_5 = { + readonly "context"?: ReasoningContext + readonly "effort"?: ReasoningEffort + readonly "mode"?: ReasoningMode + readonly "summary"?: ReasoningSummaryVerbosity + readonly [x: string]: Schema.Json +} +export const Objects_5 = Schema.StructWithRest( + Schema.Struct({ + "context": Schema.optionalKey(ReasoningContext), + "effort": Schema.optionalKey(ReasoningEffort), + "mode": Schema.optionalKey(ReasoningMode), + "summary": Schema.optionalKey(ReasoningSummaryVerbosity) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type Union_13 = { + readonly "context"?: ReasoningContext + readonly "effort"?: ReasoningEffort + readonly "mode"?: ReasoningMode + readonly "summary"?: ReasoningSummaryVerbosity + readonly "enabled"?: boolean | null + readonly "max_tokens"?: number | null +} +export const Union_13 = Schema.Union([Schema.Struct({ + "context": Schema.optionalKey(ReasoningContext), + "effort": Schema.optionalKey(ReasoningEffort), + "mode": Schema.optionalKey(ReasoningMode), + "summary": Schema.optionalKey(ReasoningSummaryVerbosity), + "enabled": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "max_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ) +})]) +export type OutputItemReasoning = { + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": "reasoning" +} +export const OutputItemReasoning = Schema.Struct({ + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Literal("reasoning") +}).annotate({ "identifier": "OutputItemReasoning" }) +export type RequestMetadata = Objects_149 | null +export const RequestMetadata = Schema.Union([Objects_149, Schema.Null]).annotate({ + "description": + "Metadata key-value pairs for the request. Keys must be ≤64 characters and cannot contain brackets. Values must be ≤512 characters. Maximum 16 pairs allowed.", + "identifier": "RequestMetadata" }) -export type DeleteKeys401 = UnauthorizedResponse -export const DeleteKeys401 = UnauthorizedResponse -export type DeleteKeys404 = NotFoundResponse -export const DeleteKeys404 = NotFoundResponse -export type DeleteKeys429 = TooManyRequestsResponse -export const DeleteKeys429 = TooManyRequestsResponse -export type DeleteKeys500 = InternalServerResponse -export const DeleteKeys500 = InternalServerResponse -export type UpdateKeysRequestJson = { - readonly "name"?: string - readonly "disabled"?: boolean - readonly "limit"?: number - readonly "limit_reset"?: "daily" | "weekly" | "monthly" - readonly "include_byok_in_limit"?: boolean +export type RequestTimeoutResponse = { + readonly "error": RequestTimeoutResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const UpdateKeysRequestJson = Schema.Struct({ - "name": Schema.optionalKey(Schema.String.annotate({ "description": "New name for the API key" })), - "disabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether to disable the API key" })), - "limit": Schema.optionalKey( - Schema.Number.annotate({ "description": "New spending limit for the API key in USD" }).check(Schema.isFinite()) - ), - "limit_reset": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": - "New limit reset type for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." - }) +export const RequestTimeoutResponse = Schema.Struct({ + "error": RequestTimeoutResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "include_byok_in_limit": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) - ) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Request Timeout - Operation exceeded time limit", + "identifier": "RequestTimeoutResponse" }) -export type UpdateKeys200 = { - readonly "data": { - readonly "hash": string - readonly "name": string - readonly "label": string - readonly "disabled": boolean - readonly "limit": number - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "created_at": string - readonly "updated_at": string - readonly "expires_at"?: string - } +export type Arrays_10 = ReadonlyArray +export const Arrays_10 = Schema.Array(RouterAttempt) +export type ChatSearchModelsServerTool = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" } -export const UpdateKeys200 = Schema.Struct({ - "data": Schema.Struct({ - "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), - "name": Schema.String.annotate({ "description": "Name of the API key" }), - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), - "updated_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was last updated" }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ) - }).annotate({ "description": "The updated API key information" }) +export const ChatSearchModelsServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(SearchModelsServerToolConfig), + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "OpenRouter built-in server tool: searches and filters AI models available on OpenRouter", + "identifier": "ChatSearchModelsServerTool" }) -export type UpdateKeys400 = BadRequestResponse -export const UpdateKeys400 = BadRequestResponse -export type UpdateKeys401 = UnauthorizedResponse -export const UpdateKeys401 = UnauthorizedResponse -export type UpdateKeys404 = NotFoundResponse -export const UpdateKeys404 = NotFoundResponse -export type UpdateKeys429 = TooManyRequestsResponse -export const UpdateKeys429 = TooManyRequestsResponse -export type UpdateKeys500 = InternalServerResponse -export const UpdateKeys500 = InternalServerResponse -export type ListGuardrailsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListGuardrailsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) +export type MessagesSearchModelsServerTool = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" +} +export const MessagesSearchModelsServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(SearchModelsServerToolConfig), + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "OpenRouter built-in server tool: searches and filters AI models available on OpenRouter", + "identifier": "MessagesSearchModelsServerTool" }) -export type ListGuardrails200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string - } - > - readonly "total_count": number +export type SearchModelsServerTool_OpenRouter = { + readonly "parameters"?: SearchModelsServerToolConfig + readonly "type": "openrouter:experimental__search_models" } -export const ListGuardrails200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) - ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - })).annotate({ "description": "List of guardrails" }), - "total_count": Schema.Number.annotate({ "description": "Total number of guardrails" }).check(Schema.isFinite()) +export const SearchModelsServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(SearchModelsServerToolConfig), + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "OpenRouter built-in server tool: searches and filters AI models available on OpenRouter", + "identifier": "SearchModelsServerTool_OpenRouter" }) -export type ListGuardrails401 = UnauthorizedResponse -export const ListGuardrails401 = UnauthorizedResponse -export type ListGuardrails500 = InternalServerResponse -export const ListGuardrails500 = InternalServerResponse -export type CreateGuardrailRequestJson = { - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean +export type ServerToolUseDetails = Objects_150 | null +export const ServerToolUseDetails = Schema.Union([Objects_150, Schema.Null]).annotate({ + "description": "Usage for server-side tool execution (e.g., web search)", + "identifier": "ServerToolUseDetails" +}) +export type ServiceUnavailableResponse = { + readonly "error": ServiceUnavailableResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null } -export const CreateGuardrailRequestJson = Schema.Struct({ - "name": Schema.String.annotate({ "description": "Name for the new guardrail" }).check(Schema.isMinLength(1)).check( - Schema.isMaxLength(200) - ), - "description": Schema.optionalKey( - Schema.String.annotate({ "description": "Description of the guardrail" }).check(Schema.isMaxLength(1000)) - ), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }).check(Schema.isMinLength(1)) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": "Array of model identifiers (slug or canonical_slug accepted)" - }).check(Schema.isMinLength(1)) +export const ServiceUnavailableResponse = Schema.Struct({ + "error": ServiceUnavailableResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ) + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Service Unavailable - Service temporarily unavailable", + "identifier": "ServiceUnavailableResponse" }) -export type CreateGuardrail201 = { - readonly "data": { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string +export type OutputShellCallItem = { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null } + readonly "call_id": string + readonly "id": string + readonly "status": ShellCallStatus + readonly "type": "shell_call" } -export const CreateGuardrail201 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) - ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - }).annotate({ "description": "The created guardrail" }) +export const OutputShellCallItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": ShellCallStatus, + "type": Schema.Literal("shell_call") +}).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool.", + "identifier": "OutputShellCallItem" }) -export type CreateGuardrail400 = BadRequestResponse -export const CreateGuardrail400 = BadRequestResponse -export type CreateGuardrail401 = UnauthorizedResponse -export const CreateGuardrail401 = UnauthorizedResponse -export type CreateGuardrail500 = InternalServerResponse -export const CreateGuardrail500 = InternalServerResponse -export type GetGuardrail200 = { - readonly "data": { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string - } +export type OutputShellCallOutputItem = { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": ShellCallStatus + readonly "type": "shell_call_output" } -export const GetGuardrail200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) - ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - }).annotate({ "description": "The guardrail" }) +export const OutputShellCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": ShellCallStatus, + "type": Schema.Literal("shell_call_output") +}).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome.", + "identifier": "OutputShellCallOutputItem" }) -export type GetGuardrail401 = UnauthorizedResponse -export const GetGuardrail401 = UnauthorizedResponse -export type GetGuardrail404 = NotFoundResponse -export const GetGuardrail404 = NotFoundResponse -export type GetGuardrail500 = InternalServerResponse -export const GetGuardrail500 = InternalServerResponse -export type DeleteGuardrail200 = { readonly "deleted": true } -export const DeleteGuardrail200 = Schema.Struct({ - "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the guardrail was deleted" }) +export type StopServerToolsWhenCondition = + | StopServerToolsWhenStepCountIs + | StopServerToolsWhenHasToolCall + | StopServerToolsWhenMaxTokensUsed + | StopServerToolsWhenMaxCost + | StopServerToolsWhenFinishReasonIs +export const StopServerToolsWhenCondition = Schema.Union([ + StopServerToolsWhenStepCountIs, + StopServerToolsWhenHasToolCall, + StopServerToolsWhenMaxTokensUsed, + StopServerToolsWhenMaxCost, + StopServerToolsWhenFinishReasonIs +], { mode: "oneOf" }).annotate({ + "description": "A single condition that, when met, halts the server-tool agent loop.", + "identifier": "StopServerToolsWhenCondition" }) -export type DeleteGuardrail401 = UnauthorizedResponse -export const DeleteGuardrail401 = UnauthorizedResponse -export type DeleteGuardrail404 = NotFoundResponse -export const DeleteGuardrail404 = NotFoundResponse -export type DeleteGuardrail500 = InternalServerResponse -export const DeleteGuardrail500 = InternalServerResponse -export type UpdateGuardrailRequestJson = { - readonly "name"?: string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean +export type STTResponse = { + readonly "duration"?: number + readonly "language"?: string + readonly "segments"?: ReadonlyArray + readonly "task"?: string + readonly "text": string + readonly "usage"?: STTUsage + readonly "words"?: ReadonlyArray } -export const UpdateGuardrailRequestJson = Schema.Struct({ - "name": Schema.optionalKey( - Schema.String.annotate({ "description": "New name for the guardrail" }).check(Schema.isMinLength(1)).check( - Schema.isMaxLength(200) - ) - ), - "description": Schema.optionalKey( - Schema.String.annotate({ "description": "New description for the guardrail" }).check(Schema.isMaxLength(1000)) +export const STTResponse = Schema.Struct({ + "duration": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Duration of the input audio in seconds, present when response_format is verbose_json", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) ), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "New spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "Detected or forced language, present when response_format is verbose_json" }) ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "New list of allowed provider IDs" }).check( - Schema.isMinLength(1) - ) + "segments": Schema.optionalKey( + Schema.Array(STTSegment).annotate({ + "description": "Timestamped transcript segments, present when response_format is verbose_json" + }) ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - "description": "Array of model identifiers (slug or canonical_slug accepted)" - }).check(Schema.isMinLength(1)) + "task": Schema.optionalKey( + Schema.String.annotate({ "description": "The task performed, present when response_format is verbose_json" }) ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) + "text": Schema.String.annotate({ "description": "The transcribed text" }), + "usage": Schema.optionalKey(STTUsage), + "words": Schema.optionalKey( + Schema.Array(STTWord).annotate({ + "description": "Timestamped words, present when the provider returns word-level timestamps" + }) ) +}).annotate({ + "description": "STT response containing transcribed text and optional usage statistics", + "identifier": "STTResponse" }) -export type UpdateGuardrail200 = { - readonly "data": { - readonly "id": string - readonly "name": string - readonly "description"?: string - readonly "limit_usd"?: number - readonly "reset_interval"?: "daily" | "weekly" | "monthly" - readonly "allowed_providers"?: ReadonlyArray - readonly "allowed_models"?: ReadonlyArray - readonly "enforce_zdr"?: boolean - readonly "created_at": string - readonly "updated_at"?: string - } +export type Arrays_12 = ReadonlyArray +export const Arrays_12 = Schema.Array(SubagentNestedTool).annotate({ + "description": + "Tools the subagent may use while executing a delegated task. The subagent runs as an agentic sub-agent over these tools, then returns its outcome. Only OpenRouter server tools are supported — function tools are rejected — and the list must not include the subagent tool itself." +}) +export type ImageEndpoint = { + readonly "allowed_passthrough_parameters": ReadonlyArray + readonly "pricing": ReadonlyArray + readonly "provider_name": string + readonly "provider_slug": string + readonly "provider_tag": string | null + readonly "supported_parameters": SupportedParameters + readonly "supports_streaming": boolean } -export const UpdateGuardrail200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), - "name": Schema.String.annotate({ "description": "Name of the guardrail" }), - "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the guardrail" })), - "limit_usd": Schema.optionalKey( - Schema.Number.annotate({ "description": "Spending limit in USD" }).check(Schema.isFinite()).check( - Schema.isGreaterThanOrEqualTo(0) - ) - ), - "reset_interval": Schema.optionalKey( - Schema.Literals(["daily", "weekly", "monthly"]).annotate({ - "description": "Interval at which the limit resets (daily, weekly, monthly)" - }) - ), - "allowed_providers": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "List of allowed provider IDs" }) - ), - "allowed_models": Schema.optionalKey( - Schema.Array(Schema.String).annotate({ "description": "Array of model canonical_slugs (immutable identifiers)" }) +export const ImageEndpoint = Schema.Struct({ + "allowed_passthrough_parameters": Schema.Array(Schema.String).annotate({ + "description": "Provider-specific options accepted under provider.options[provider_slug]." + }), + "pricing": Schema.Array(ImagePricingEntry).annotate({ "description": "Billable pricing lines for this endpoint." }), + "provider_name": Schema.String.annotate({ "description": "Provider display name" }), + "provider_slug": Schema.String.annotate({ "description": "Provider slug" }), + "provider_tag": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Provider tag for request-side selection" + }), + "supported_parameters": Schema.suspend((): Schema.Codec => SupportedParameters).annotate({ + "description": "The definitive set of parameters this endpoint accepts for this model." + }), + "supports_streaming": Schema.Boolean.annotate({ + "description": "Whether this endpoint supports native SSE streaming (`stream: true` in the request)." + }) +}).annotate({ "description": "An endpoint that serves a given image model.", "identifier": "ImageEndpoint" }) +export type TaskClassificationItem = { + readonly "category_token_share": number + readonly "category_usage_share": number + readonly "display_name": string + readonly "macro_category": string + readonly "models": ReadonlyArray + readonly "tag": string + readonly "token_share": number + readonly "usage_share": number +} +export const TaskClassificationItem = Schema.Struct({ + "category_token_share": Schema.Number.annotate({ + "description": + "Fraction of this classification's token volume within its macro-category (0–1). Sums to 1 across all classifications sharing the same `macro_category`.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "category_usage_share": Schema.Number.annotate({ + "description": + "Fraction of this classification's usage within its macro-category (0–1). Sums to 1 across all classifications sharing the same `macro_category`.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "display_name": Schema.String.annotate({ "description": "Human-readable label for the classification." }), + "macro_category": Schema.String.annotate({ + "description": "Coarse grouping derived from the tag prefix: `code`, `data`, `agent`, or `general`." + }), + "models": Schema.Array(TaskClassificationModel).annotate({ + "description": + "Top models for this classification by request volume, sorted descending. Each entry reports the model's share of this classification's requests and tokens." + }), + "tag": Schema.String.annotate({ + "description": "Classification tag identifier (e.g. `code:general_impl`, `agent:web_search`)." + }), + "token_share": Schema.Number.annotate({ + "description": + "Fraction of classified sampled token volume (prompt + completion) attributed to this classification (0–1). The unclassified `other` bucket is excluded from the denominator.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_share": Schema.Number.annotate({ + "description": + "Fraction of classified sampled requests attributed to this classification (0–1). The unclassified `other` bucket is excluded from the denominator.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) +}).annotate({ "identifier": "TaskClassificationItem" }) +export type CodeInterpreterCallItem = { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": ToolCallStatus + readonly "type": "code_interpreter_call" +} +export const CodeInterpreterCallItem = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) ), - "enforce_zdr": Schema.optionalKey( - Schema.Boolean.annotate({ "description": "Whether to enforce zero data retention" }) + Schema.Null + ]), + "status": ToolCallStatus, + "type": Schema.Literal("code_interpreter_call") +}).annotate({ + "description": "A code interpreter execution call with outputs", + "identifier": "CodeInterpreterCallItem" +}) +export type FunctionCallItem = { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: ToolCallStatus + readonly "type": "function_call" +} +export const FunctionCallItem = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(ToolCallStatus), + "type": Schema.Literal("function_call") +}).annotate({ "description": "A function call initiated by the model", "identifier": "FunctionCallItem" }) +export type LocalShellCallItem = { + readonly "action": { + readonly "command": ReadonlyArray + readonly "env": {} + readonly "timeout_ms"?: number | null + readonly "type": "exec" + readonly "user"?: string | null + readonly "working_directory"?: string | null + } + readonly "call_id": string + readonly "id": string + readonly "status": ToolCallStatus + readonly "type": "local_shell_call" +} +export const LocalShellCallItem = Schema.Struct({ + "action": Schema.Struct({ + "command": Schema.Array(Schema.String), + "env": Schema.Struct({}), + "timeout_ms": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), - "updated_at": Schema.optionalKey( - Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was last updated" }) - ) - }).annotate({ "description": "The updated guardrail" }) + "type": Schema.Literal("exec"), + "user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "working_directory": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + "call_id": Schema.String, + "id": Schema.String, + "status": ToolCallStatus, + "type": Schema.Literal("local_shell_call") +}).annotate({ "description": "A local shell command execution call", "identifier": "LocalShellCallItem" }) +export type LocalShellCallOutputItem = { + readonly "id": string + readonly "output": string + readonly "status"?: ToolCallStatus | null + readonly "type": "local_shell_call_output" +} +export const LocalShellCallOutputItem = Schema.Struct({ + "id": Schema.String, + "output": Schema.String, + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("local_shell_call_output") +}).annotate({ "description": "Output from a local shell command execution", "identifier": "LocalShellCallOutputItem" }) +export type OpenAIResponseFunctionToolCall = { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: ToolCallStatus + readonly "type": "function_call" +} +export const OpenAIResponseFunctionToolCall = Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(ToolCallStatus), + "type": Schema.Literal("function_call") +}).annotate({ "identifier": "OpenAIResponseFunctionToolCall" }) +export type OutputAdvisorServerToolItem = { + readonly "advice"?: string + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:advisor" +} +export const OutputAdvisorServerToolItem = Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:advisor") +}).annotate({ + "description": "An openrouter:advisor server tool output item", + "identifier": "OutputAdvisorServerToolItem" }) -export type UpdateGuardrail400 = BadRequestResponse -export const UpdateGuardrail400 = BadRequestResponse -export type UpdateGuardrail401 = UnauthorizedResponse -export const UpdateGuardrail401 = UnauthorizedResponse -export type UpdateGuardrail404 = NotFoundResponse -export const UpdateGuardrail404 = NotFoundResponse -export type UpdateGuardrail500 = InternalServerResponse -export const UpdateGuardrail500 = InternalServerResponse -export type ListKeyAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListKeyAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) +export type OutputBashServerToolItem = { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": "openrouter:bash" +} +export const OutputBashServerToolItem = Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:bash") +}).annotate({ "description": "An openrouter:bash server tool output item", "identifier": "OutputBashServerToolItem" }) +export type OutputBrowserUseServerToolItem = { + readonly "action"?: string + readonly "id"?: string + readonly "screenshotB64"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:browser_use" +} +export const OutputBrowserUseServerToolItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "screenshotB64": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:browser_use") +}).annotate({ + "description": "An openrouter:browser_use server tool output item", + "identifier": "OutputBrowserUseServerToolItem" }) -export type ListKeyAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "key_hash": string - readonly "guardrail_id": string - readonly "key_name": string - readonly "key_label": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export type OutputCodeInterpreterCallItem = { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": ToolCallStatus + readonly "type": "code_interpreter_call" } -export const ListKeyAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "key_hash": Schema.String.annotate({ "description": "Hash of the assigned API key" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "key_name": Schema.String.annotate({ "description": "Name of the API key" }), - "key_label": Schema.String.annotate({ "description": "Label of the API key" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of key assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of key assignments for this guardrail" }).check( - Schema.isFinite() - ) +export const OutputCodeInterpreterCallItem = Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": ToolCallStatus, + "type": Schema.Literal("code_interpreter_call") +}).annotate({ + "description": "A code interpreter execution call with outputs", + "identifier": "OutputCodeInterpreterCallItem" }) -export type ListKeyAssignments401 = UnauthorizedResponse -export const ListKeyAssignments401 = UnauthorizedResponse -export type ListKeyAssignments500 = InternalServerResponse -export const ListKeyAssignments500 = InternalServerResponse -export type ListMemberAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListMemberAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) +export type OutputCodeInterpreterServerToolItem = { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "language"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": "openrouter:code_interpreter" +} +export const OutputCodeInterpreterServerToolItem = Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "language": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:code_interpreter") +}).annotate({ + "description": "An openrouter:code_interpreter server tool output item", + "identifier": "OutputCodeInterpreterServerToolItem" }) -export type ListMemberAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "user_id": string - readonly "organization_id": string - readonly "guardrail_id": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export type OutputDatetimeItem = { + readonly "datetime": string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "timezone": string + readonly "type": "openrouter:datetime" } -export const ListMemberAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "user_id": Schema.String.annotate({ "description": "Clerk user ID of the assigned member" }), - "organization_id": Schema.String.annotate({ "description": "Organization ID" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of member assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of member assignments" }).check( - Schema.isFinite() - ) +export const OutputDatetimeItem = Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Literal("openrouter:datetime") +}).annotate({ "description": "An openrouter:datetime server tool output item", "identifier": "OutputDatetimeItem" }) +export type OutputFileSearchServerToolItem = { + readonly "id"?: string + readonly "queries"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": "openrouter:file_search" +} +export const OutputFileSearchServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:file_search") +}).annotate({ + "description": "An openrouter:file_search server tool output item", + "identifier": "OutputFileSearchServerToolItem" }) -export type ListMemberAssignments401 = UnauthorizedResponse -export const ListMemberAssignments401 = UnauthorizedResponse -export type ListMemberAssignments500 = InternalServerResponse -export const ListMemberAssignments500 = InternalServerResponse -export type ListGuardrailKeyAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListGuardrailKeyAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) -}) -export type ListGuardrailKeyAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "key_hash": string - readonly "guardrail_id": string - readonly "key_name": string - readonly "key_label": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export type OutputFilesServerToolItem = { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id"?: string + readonly "operation"?: string + readonly "result"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:files" } -export const ListGuardrailKeyAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "key_hash": Schema.String.annotate({ "description": "Hash of the assigned API key" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "key_name": Schema.String.annotate({ "description": "Name of the API key" }), - "key_label": Schema.String.annotate({ "description": "Label of the API key" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of key assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of key assignments for this guardrail" }).check( - Schema.isFinite() - ) -}) -export type ListGuardrailKeyAssignments401 = UnauthorizedResponse -export const ListGuardrailKeyAssignments401 = UnauthorizedResponse -export type ListGuardrailKeyAssignments404 = NotFoundResponse -export const ListGuardrailKeyAssignments404 = NotFoundResponse -export type ListGuardrailKeyAssignments500 = InternalServerResponse -export const ListGuardrailKeyAssignments500 = InternalServerResponse -export type BulkAssignKeysToGuardrailRequestJson = { readonly "key_hashes": ReadonlyArray } -export const BulkAssignKeysToGuardrailRequestJson = Schema.Struct({ - "key_hashes": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of API key hashes to assign to the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkAssignKeysToGuardrail200 = { readonly "assigned_count": number } -export const BulkAssignKeysToGuardrail200 = Schema.Struct({ - "assigned_count": Schema.Number.annotate({ "description": "Number of keys successfully assigned" }).check( - Schema.isFinite() - ) -}) -export type BulkAssignKeysToGuardrail400 = BadRequestResponse -export const BulkAssignKeysToGuardrail400 = BadRequestResponse -export type BulkAssignKeysToGuardrail401 = UnauthorizedResponse -export const BulkAssignKeysToGuardrail401 = UnauthorizedResponse -export type BulkAssignKeysToGuardrail404 = NotFoundResponse -export const BulkAssignKeysToGuardrail404 = NotFoundResponse -export type BulkAssignKeysToGuardrail500 = InternalServerResponse -export const BulkAssignKeysToGuardrail500 = InternalServerResponse -export type ListGuardrailMemberAssignmentsParams = { readonly "offset"?: string; readonly "limit"?: string } -export const ListGuardrailMemberAssignmentsParams = Schema.Struct({ - "offset": Schema.optionalKey(Schema.String.annotate({ "description": "Number of records to skip for pagination" })), - "limit": Schema.optionalKey( - Schema.String.annotate({ "description": "Maximum number of records to return (max 100)" }) - ) -}) -export type ListGuardrailMemberAssignments200 = { - readonly "data": ReadonlyArray< - { - readonly "id": string - readonly "user_id": string - readonly "organization_id": string - readonly "guardrail_id": string - readonly "assigned_by": string - readonly "created_at": string - } - > - readonly "total_count": number +export const OutputFilesServerToolItem = Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:files") +}).annotate({ "description": "An openrouter:files server tool output item", "identifier": "OutputFilesServerToolItem" }) +export type OutputImageGenerationServerToolItem = { + readonly "id"?: string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:image_generation" } -export const ListGuardrailMemberAssignments200 = Schema.Struct({ - "data": Schema.Array(Schema.Struct({ - "id": Schema.String.annotate({ "description": "Unique identifier for the assignment", "format": "uuid" }), - "user_id": Schema.String.annotate({ "description": "Clerk user ID of the assigned member" }), - "organization_id": Schema.String.annotate({ "description": "Organization ID" }), - "guardrail_id": Schema.String.annotate({ "description": "ID of the guardrail", "format": "uuid" }), - "assigned_by": Schema.String.annotate({ "description": "User ID of who made the assignment" }), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the assignment was created" }) - })).annotate({ "description": "List of member assignments" }), - "total_count": Schema.Number.annotate({ "description": "Total number of member assignments" }).check( - Schema.isFinite() - ) -}) -export type ListGuardrailMemberAssignments401 = UnauthorizedResponse -export const ListGuardrailMemberAssignments401 = UnauthorizedResponse -export type ListGuardrailMemberAssignments404 = NotFoundResponse -export const ListGuardrailMemberAssignments404 = NotFoundResponse -export type ListGuardrailMemberAssignments500 = InternalServerResponse -export const ListGuardrailMemberAssignments500 = InternalServerResponse -export type BulkAssignMembersToGuardrailRequestJson = { readonly "member_user_ids": ReadonlyArray } -export const BulkAssignMembersToGuardrailRequestJson = Schema.Struct({ - "member_user_ids": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of member user IDs to assign to the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkAssignMembersToGuardrail200 = { readonly "assigned_count": number } -export const BulkAssignMembersToGuardrail200 = Schema.Struct({ - "assigned_count": Schema.Number.annotate({ "description": "Number of members successfully assigned" }).check( - Schema.isFinite() - ) -}) -export type BulkAssignMembersToGuardrail400 = BadRequestResponse -export const BulkAssignMembersToGuardrail400 = BadRequestResponse -export type BulkAssignMembersToGuardrail401 = UnauthorizedResponse -export const BulkAssignMembersToGuardrail401 = UnauthorizedResponse -export type BulkAssignMembersToGuardrail404 = NotFoundResponse -export const BulkAssignMembersToGuardrail404 = NotFoundResponse -export type BulkAssignMembersToGuardrail500 = InternalServerResponse -export const BulkAssignMembersToGuardrail500 = InternalServerResponse -export type BulkUnassignKeysFromGuardrailRequestJson = { readonly "key_hashes": ReadonlyArray } -export const BulkUnassignKeysFromGuardrailRequestJson = Schema.Struct({ - "key_hashes": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of API key hashes to unassign from the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkUnassignKeysFromGuardrail200 = { readonly "unassigned_count": number } -export const BulkUnassignKeysFromGuardrail200 = Schema.Struct({ - "unassigned_count": Schema.Number.annotate({ "description": "Number of keys successfully unassigned" }).check( - Schema.isFinite() - ) -}) -export type BulkUnassignKeysFromGuardrail400 = BadRequestResponse -export const BulkUnassignKeysFromGuardrail400 = BadRequestResponse -export type BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse -export const BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse -export type BulkUnassignKeysFromGuardrail404 = NotFoundResponse -export const BulkUnassignKeysFromGuardrail404 = NotFoundResponse -export type BulkUnassignKeysFromGuardrail500 = InternalServerResponse -export const BulkUnassignKeysFromGuardrail500 = InternalServerResponse -export type BulkUnassignMembersFromGuardrailRequestJson = { readonly "member_user_ids": ReadonlyArray } -export const BulkUnassignMembersFromGuardrailRequestJson = Schema.Struct({ - "member_user_ids": Schema.Array(Schema.String.check(Schema.isMinLength(1))).annotate({ - "description": "Array of member user IDs to unassign from the guardrail" - }).check(Schema.isMinLength(1)) -}) -export type BulkUnassignMembersFromGuardrail200 = { readonly "unassigned_count": number } -export const BulkUnassignMembersFromGuardrail200 = Schema.Struct({ - "unassigned_count": Schema.Number.annotate({ "description": "Number of members successfully unassigned" }).check( - Schema.isFinite() - ) +export const OutputImageGenerationServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt (possibly rewritten) that the image was generated from." }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:image_generation") +}).annotate({ + "description": "An openrouter:image_generation server tool output item", + "identifier": "OutputImageGenerationServerToolItem" }) -export type BulkUnassignMembersFromGuardrail400 = BadRequestResponse -export const BulkUnassignMembersFromGuardrail400 = BadRequestResponse -export type BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse -export const BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse -export type BulkUnassignMembersFromGuardrail404 = NotFoundResponse -export const BulkUnassignMembersFromGuardrail404 = NotFoundResponse -export type BulkUnassignMembersFromGuardrail500 = InternalServerResponse -export const BulkUnassignMembersFromGuardrail500 = InternalServerResponse -export type GetCurrentKey200 = { - readonly "data": { - readonly "label": string - readonly "limit": number - readonly "usage": number - readonly "usage_daily": number - readonly "usage_weekly": number - readonly "usage_monthly": number - readonly "byok_usage": number - readonly "byok_usage_daily": number - readonly "byok_usage_weekly": number - readonly "byok_usage_monthly": number - readonly "is_free_tier": boolean - readonly "is_management_key": boolean - readonly "is_provisioning_key": boolean - readonly "limit_remaining": number - readonly "limit_reset": string - readonly "include_byok_in_limit": boolean - readonly "expires_at"?: string - readonly "rate_limit": { readonly "requests": number; readonly "interval": string; readonly "note": string } - } +export type OutputMcpServerToolItem = { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": ToolCallStatus + readonly "toolName"?: string + readonly "type": "openrouter:mcp" } -export const GetCurrentKey200 = Schema.Struct({ - "data": Schema.Struct({ - "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), - "limit": Schema.Number.annotate({ "description": "Spending limit for the API key in USD" }).check( - Schema.isFinite() - ), - "usage": Schema.Number.annotate({ "description": "Total OpenRouter credit usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "usage_daily": Schema.Number.annotate({ "description": "OpenRouter credit usage (in USD) for the current UTC day" }) - .check(Schema.isFinite()), - "usage_weekly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "usage_monthly": Schema.Number.annotate({ - "description": "OpenRouter credit usage (in USD) for the current UTC month" - }).check(Schema.isFinite()), - "byok_usage": Schema.Number.annotate({ "description": "Total external BYOK usage (in USD) for the API key" }).check( - Schema.isFinite() - ), - "byok_usage_daily": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC day" - }).check(Schema.isFinite()), - "byok_usage_weekly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)" - }).check(Schema.isFinite()), - "byok_usage_monthly": Schema.Number.annotate({ - "description": "External BYOK usage (in USD) for current UTC month" - }).check(Schema.isFinite()), - "is_free_tier": Schema.Boolean.annotate({ "description": "Whether this is a free tier API key" }), - "is_management_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), - "is_provisioning_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), - "limit_remaining": Schema.Number.annotate({ "description": "Remaining spending limit in USD" }).check( - Schema.isFinite() - ), - "limit_reset": Schema.String.annotate({ "description": "Type of limit reset for the API key" }), - "include_byok_in_limit": Schema.Boolean.annotate({ - "description": "Whether to include external BYOK usage in the credit limit" - }), - "expires_at": Schema.optionalKey( - Schema.String.annotate({ - "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", - "format": "date-time" - }) - ), - "rate_limit": Schema.Struct({ - "requests": Schema.Number.annotate({ "description": "Number of requests allowed per interval" }).check( - Schema.isFinite() - ), - "interval": Schema.String.annotate({ "description": "Rate limit interval" }), - "note": Schema.String.annotate({ "description": "Note about the rate limit" }) - }).annotate({ "description": "Legacy rate limit information about a key. Will always return -1." }) - }).annotate({ "description": "Current API key information" }) +export const OutputMcpServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "serverLabel": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:mcp") +}).annotate({ "description": "An openrouter:mcp server tool output item", "identifier": "OutputMcpServerToolItem" }) +export type OutputMemoryServerToolItem = { + readonly "action"?: "read" | "write" | "delete" + readonly "id"?: string + readonly "key"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:memory" + readonly "value"?: Schema.Json +} +export const OutputMemoryServerToolItem = Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.optionalKey(Schema.String), + "key": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:memory"), + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })) +}).annotate({ + "description": "An openrouter:memory server tool output item", + "identifier": "OutputMemoryServerToolItem" }) -export type GetCurrentKey401 = UnauthorizedResponse -export const GetCurrentKey401 = UnauthorizedResponse -export type GetCurrentKey500 = InternalServerResponse -export const GetCurrentKey500 = InternalServerResponse -export type ExchangeAuthCodeForAPIKeyRequestJson = { - readonly "code": string - readonly "code_verifier"?: string - readonly "code_challenge_method"?: "S256" | "plain" +export type OutputSearchModelsServerToolItem = { + readonly "arguments"?: string + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:experimental__search_models" } -export const ExchangeAuthCodeForAPIKeyRequestJson = Schema.Struct({ - "code": Schema.String.annotate({ "description": "The authorization code received from the OAuth redirect" }), - "code_verifier": Schema.optionalKey( +export const OutputSearchModelsServerToolItem = Schema.Struct({ + "arguments": Schema.optionalKey( Schema.String.annotate({ - "description": "The code verifier if code_challenge was used in the authorization request" + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" }) ), - "code_challenge_method": Schema.optionalKey( - Schema.Literals(["S256", "plain"]).annotate({ "description": "The method used to generate the code challenge" }) - ) -}) -export type ExchangeAuthCodeForAPIKey200 = { readonly "key": string; readonly "user_id": string } -export const ExchangeAuthCodeForAPIKey200 = Schema.Struct({ - "key": Schema.String.annotate({ "description": "The API key to use for OpenRouter requests" }), - "user_id": Schema.String.annotate({ "description": "User ID associated with the API key" }) + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:experimental__search_models") +}).annotate({ + "description": "An openrouter:experimental__search_models server tool output item", + "identifier": "OutputSearchModelsServerToolItem" }) -export type ExchangeAuthCodeForAPIKey400 = BadRequestResponse -export const ExchangeAuthCodeForAPIKey400 = BadRequestResponse -export type ExchangeAuthCodeForAPIKey403 = ForbiddenResponse -export const ExchangeAuthCodeForAPIKey403 = ForbiddenResponse -export type ExchangeAuthCodeForAPIKey500 = InternalServerResponse -export const ExchangeAuthCodeForAPIKey500 = InternalServerResponse -export type CreateAuthKeysCodeRequestJson = { - readonly "callback_url": string - readonly "code_challenge"?: string - readonly "code_challenge_method"?: "S256" | "plain" - readonly "limit"?: number - readonly "expires_at"?: string +export type OutputSubagentServerToolItem = { + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": ToolCallStatus + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": "openrouter:subagent" } -export const CreateAuthKeysCodeRequestJson = Schema.Struct({ - "callback_url": Schema.String.annotate({ - "description": - "The callback URL to redirect to after authorization. Note, only https URLs on ports 443 and 3000 are allowed.", - "format": "uri" - }), - "code_challenge": Schema.optionalKey( - Schema.String.annotate({ "description": "PKCE code challenge for enhanced security" }) +export const OutputSubagentServerToolItem = Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) ), - "code_challenge_method": Schema.optionalKey( - Schema.Literals(["S256", "plain"]).annotate({ "description": "The method used to generate the code challenge" }) + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) ), - "limit": Schema.optionalKey( - Schema.Number.annotate({ "description": "Credit limit for the API key to be created" }).check(Schema.isFinite()) + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) ), - "expires_at": Schema.optionalKey( + "outcome": Schema.optionalKey( Schema.String.annotate({ - "description": "Optional expiration time for the API key to be created", - "format": "date-time" + "description": "The worker model's result (the outcome text returned to the delegating model)." }) - ) + ), + "status": ToolCallStatus, + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Literal("openrouter:subagent") +}).annotate({ + "description": "An openrouter:subagent server tool output item", + "identifier": "OutputSubagentServerToolItem" }) -export type CreateAuthKeysCode200 = { - readonly "data": { readonly "id": string; readonly "app_id": number; readonly "created_at": string } +export type OutputTextEditorServerToolItem = { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:text_editor" } -export const CreateAuthKeysCode200 = Schema.Struct({ - "data": Schema.Struct({ - "id": Schema.String.annotate({ "description": "The authorization code ID to use in the exchange request" }), - "app_id": Schema.Number.annotate({ "description": "The application ID associated with this auth code" }).check( - Schema.isFinite() - ), - "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the auth code was created" }) - }).annotate({ "description": "Auth code data" }) +export const OutputTextEditorServerToolItem = Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:text_editor") +}).annotate({ + "description": "An openrouter:text_editor server tool output item", + "identifier": "OutputTextEditorServerToolItem" }) -export type CreateAuthKeysCode400 = BadRequestResponse -export const CreateAuthKeysCode400 = BadRequestResponse -export type CreateAuthKeysCode401 = UnauthorizedResponse -export const CreateAuthKeysCode401 = UnauthorizedResponse -export type CreateAuthKeysCode500 = InternalServerResponse -export const CreateAuthKeysCode500 = InternalServerResponse -export type SendChatCompletionRequestRequestJson = ChatGenerationParams -export const SendChatCompletionRequestRequestJson = ChatGenerationParams -export type SendChatCompletionRequest200 = { - readonly "id": string - readonly "choices": ReadonlyArray - readonly "created": number - readonly "model": string - readonly "object": "chat.completion" - readonly "system_fingerprint"?: string | null - readonly "usage"?: ChatGenerationTokenUsage +export type OutputToolSearchServerToolItem = { + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:tool_search" } -export const SendChatCompletionRequest200 = Schema.Struct({ - "id": Schema.String, - "choices": Schema.Array(ChatResponseChoice), - "created": Schema.Number.check(Schema.isFinite()), - "model": Schema.String, - "object": Schema.Literal("chat.completion"), - "system_fingerprint": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - "usage": Schema.optionalKey(ChatGenerationTokenUsage) -}).annotate({ "description": "Chat completion response" }) -export type SendChatCompletionRequest200Sse = ChatStreamingResponseChunk -export const SendChatCompletionRequest200Sse = ChatStreamingResponseChunk -export type SendChatCompletionRequest400 = ChatError -export const SendChatCompletionRequest400 = ChatError -export type SendChatCompletionRequest401 = ChatError -export const SendChatCompletionRequest401 = ChatError -export type SendChatCompletionRequest429 = ChatError -export const SendChatCompletionRequest429 = ChatError -export type SendChatCompletionRequest500 = ChatError -export const SendChatCompletionRequest500 = ChatError - -export interface OperationConfig { - /** - * Whether or not the response should be included in the value returned from - * an operation. - * - * If set to `true`, a tuple of `[A, HttpClientResponse]` will be returned, - * where `A` is the success type of the operation. - * - * If set to `false`, only the success type of the operation will be returned. - */ - readonly includeResponse?: boolean | undefined +export const OutputToolSearchServerToolItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:tool_search") +}).annotate({ + "description": "An openrouter:tool_search server tool output item", + "identifier": "OutputToolSearchServerToolItem" +}) +export type OutputWebFetchServerToolItem = { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "title"?: string + readonly "type": "openrouter:web_fetch" + readonly "url"?: string } - -/** - * A utility type which optionally includes the response in the return result - * of an operation based upon the value of the `includeResponse` configuration - * option. - */ -export type WithOptionalResponse = Config extends { - readonly includeResponse: true -} ? [A, HttpClientResponse.HttpClientResponse] : - A - -export const make = ( - httpClient: HttpClient.HttpClient, - options: { - readonly transformClient?: ((client: HttpClient.HttpClient) => Effect.Effect) | undefined - } = {} -): OpenRouterClient => { - const unexpectedStatus = (response: HttpClientResponse.HttpClientResponse) => - Effect.flatMap( - Effect.orElseSucceed(response.json, () => "Unexpected status code"), - (description) => - Effect.fail( - new HttpClientError.HttpClientError({ - reason: new HttpClientError.StatusCodeError({ - request: response.request, - response, - description: typeof description === "string" ? description : JSON.stringify(description) - }) - }) - ) +export const OutputWebFetchServerToolItem = Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey(Schema.String.annotate({ "description": "The error message if the fetch failed." })), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) ) - const withResponse = (config: Config | undefined) => - ( - f: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect - ): (request: HttpClientRequest.HttpClientRequest) => Effect.Effect => { - const withOptionalResponse = ( - config?.includeResponse - ? (response: HttpClientResponse.HttpClientResponse) => Effect.map(f(response), (a) => [a, response]) - : (response: HttpClientResponse.HttpClientResponse) => f(response) - ) as any - return options?.transformClient - ? (request) => - Effect.flatMap( - Effect.flatMap(options.transformClient!(httpClient), (client) => client.execute(request)), - withOptionalResponse - ) - : (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "title": Schema.optionalKey(Schema.String), + "type": Schema.Literal("openrouter:web_fetch"), + "url": Schema.optionalKey(Schema.String) +}).annotate({ + "description": "An openrouter:web_fetch server tool output item", + "identifier": "OutputWebFetchServerToolItem" +}) +export type OutputWebSearchServerToolItem = { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" } - const sseRequest = < - Type, - DecodingServices - >( - schema: Schema.ConstraintDecoder - ) => - ( - request: HttpClientRequest.HttpClientRequest - ): Stream.Stream< - { readonly event: string; readonly id: string | undefined; readonly data: Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - DecodingServices - > => - HttpClient.filterStatusOk(httpClient).execute(request).pipe( - Effect.map((response) => response.stream), - Stream.unwrap, - Stream.decodeText(), - Stream.pipeThroughChannel(Sse.decodeDataSchema(schema)) - ) - const decodeSuccess = - (schema: Schema) => (response: HttpClientResponse.HttpClientResponse) => - HttpClientResponse.schemaBodyJson(schema)(response) - const decodeError = - (tag: Tag, schema: Schema) => - (response: HttpClientResponse.HttpClientResponse) => - Effect.flatMap( - HttpClientResponse.schemaBodyJson(schema)(response), - (cause) => Effect.fail(OpenRouterClientError(tag, cause, response)) - ) - return { - httpClient, - "createResponses": (options) => - HttpClientRequest.post(`/responses`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateResponses200), - "400": decodeError("CreateResponses400", CreateResponses400), - "401": decodeError("CreateResponses401", CreateResponses401), - "402": decodeError("CreateResponses402", CreateResponses402), - "404": decodeError("CreateResponses404", CreateResponses404), - "408": decodeError("CreateResponses408", CreateResponses408), - "413": decodeError("CreateResponses413", CreateResponses413), - "422": decodeError("CreateResponses422", CreateResponses422), - "429": decodeError("CreateResponses429", CreateResponses429), - "500": decodeError("CreateResponses500", CreateResponses500), - "502": decodeError("CreateResponses502", CreateResponses502), - "503": decodeError("CreateResponses503", CreateResponses503), - "524": decodeError("CreateResponses524", CreateResponses524), - "529": decodeError("CreateResponses529", CreateResponses529), - orElse: unexpectedStatus - })) - ), - "createResponsesSse": (options) => - HttpClientRequest.post(`/responses`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(CreateResponses200Sse) - ), - "createMessages": (options) => - HttpClientRequest.post(`/messages`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateMessages200), - "400": decodeError("CreateMessages400", CreateMessages400), - "401": decodeError("CreateMessages401", CreateMessages401), - "403": decodeError("CreateMessages403", CreateMessages403), - "404": decodeError("CreateMessages404", CreateMessages404), - "429": decodeError("CreateMessages429", CreateMessages429), - "500": decodeError("CreateMessages500", CreateMessages500), - "503": decodeError("CreateMessages503", CreateMessages503), - "529": decodeError("CreateMessages529", CreateMessages529), - orElse: unexpectedStatus - })) - ), - "createMessagesSse": (options) => - HttpClientRequest.post(`/messages`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(CreateMessages200Sse) - ), - "getUserActivity": (options) => - HttpClientRequest.get(`/activity`).pipe( - HttpClientRequest.setUrlParams({ "date": options?.params?.["date"] as any }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetUserActivity200), - "400": decodeError("GetUserActivity400", GetUserActivity400), - "401": decodeError("GetUserActivity401", GetUserActivity401), - "403": decodeError("GetUserActivity403", GetUserActivity403), - "500": decodeError("GetUserActivity500", GetUserActivity500), - orElse: unexpectedStatus - })) - ), - "getCredits": (options) => - HttpClientRequest.get(`/credits`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetCredits200), - "401": decodeError("GetCredits401", GetCredits401), - "403": decodeError("GetCredits403", GetCredits403), - "500": decodeError("GetCredits500", GetCredits500), - orElse: unexpectedStatus - })) - ), - "createCoinbaseCharge": (options) => - HttpClientRequest.post(`/credits/coinbase`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateCoinbaseCharge200), - "400": decodeError("CreateCoinbaseCharge400", CreateCoinbaseCharge400), - "401": decodeError("CreateCoinbaseCharge401", CreateCoinbaseCharge401), - "429": decodeError("CreateCoinbaseCharge429", CreateCoinbaseCharge429), - "500": decodeError("CreateCoinbaseCharge500", CreateCoinbaseCharge500), - orElse: unexpectedStatus - })) - ), - "createEmbeddings": (options) => - HttpClientRequest.post(`/embeddings`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateEmbeddings200), - "400": decodeError("CreateEmbeddings400", CreateEmbeddings400), - "401": decodeError("CreateEmbeddings401", CreateEmbeddings401), - "402": decodeError("CreateEmbeddings402", CreateEmbeddings402), - "404": decodeError("CreateEmbeddings404", CreateEmbeddings404), - "429": decodeError("CreateEmbeddings429", CreateEmbeddings429), - "500": decodeError("CreateEmbeddings500", CreateEmbeddings500), - "502": decodeError("CreateEmbeddings502", CreateEmbeddings502), - "503": decodeError("CreateEmbeddings503", CreateEmbeddings503), - "524": decodeError("CreateEmbeddings524", CreateEmbeddings524), - "529": decodeError("CreateEmbeddings529", CreateEmbeddings529), - orElse: unexpectedStatus - })) - ), - "createEmbeddingsSse": (options) => - HttpClientRequest.post(`/embeddings`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(CreateEmbeddings200Sse) - ), - "listEmbeddingsModels": (options) => - HttpClientRequest.get(`/embeddings/models`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListEmbeddingsModels200), - "400": decodeError("ListEmbeddingsModels400", ListEmbeddingsModels400), - "500": decodeError("ListEmbeddingsModels500", ListEmbeddingsModels500), - orElse: unexpectedStatus - })) - ), - "getGeneration": (options) => - HttpClientRequest.get(`/generation`).pipe( - HttpClientRequest.setUrlParams({ "id": options.params["id"] as any }), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetGeneration200), - "401": decodeError("GetGeneration401", GetGeneration401), - "402": decodeError("GetGeneration402", GetGeneration402), - "404": decodeError("GetGeneration404", GetGeneration404), - "429": decodeError("GetGeneration429", GetGeneration429), - "500": decodeError("GetGeneration500", GetGeneration500), - "502": decodeError("GetGeneration502", GetGeneration502), - "524": decodeError("GetGeneration524", GetGeneration524), - "529": decodeError("GetGeneration529", GetGeneration529), - orElse: unexpectedStatus - })) - ), - "listModelsCount": (options) => - HttpClientRequest.get(`/models/count`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListModelsCount200), - "500": decodeError("ListModelsCount500", ListModelsCount500), - orElse: unexpectedStatus - })) - ), - "getModels": (options) => - HttpClientRequest.get(`/models`).pipe( - HttpClientRequest.setUrlParams({ - "category": options?.params?.["category"] as any, - "supported_parameters": options?.params?.["supported_parameters"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetModels200), - "400": decodeError("GetModels400", GetModels400), - "500": decodeError("GetModels500", GetModels500), - orElse: unexpectedStatus - })) + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": "openrouter:web_search" +} +export const OutputWebSearchServerToolItem = Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) ), - "listModelsUser": (options) => - HttpClientRequest.get(`/models/user`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListModelsUser200), - "401": decodeError("ListModelsUser401", ListModelsUser401), - "404": decodeError("ListModelsUser404", ListModelsUser404), - "500": decodeError("ListModelsUser500", ListModelsUser500), - orElse: unexpectedStatus - })) + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:web_search") +}).annotate({ + "description": "An openrouter:web_search server tool output item", + "identifier": "OutputWebSearchServerToolItem" +}) +export type ShellCallItem = { + readonly "action": { + readonly "commands": ReadonlyArray + readonly "max_output_length"?: number | null + readonly "timeout_ms"?: number | null + } + readonly "call_id": string + readonly "environment"?: Schema.Json + readonly "id"?: string | null + readonly "status"?: ToolCallStatus | null + readonly "type": "shell_call" +} +export const ShellCallItem = Schema.Struct({ + "action": Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "timeout_ms": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ) + }), + "call_id": Schema.String, + "environment": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("shell_call") +}).annotate({ "description": "A shell command execution call (newer variant)", "identifier": "ShellCallItem" }) +export type ShellCallOutputItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { readonly "content"?: string | null; readonly "exit_code"?: number | null; readonly "type": string } + > + readonly "status"?: ToolCallStatus | null + readonly "type": "shell_call_output" +} +export const ShellCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array( + Schema.Struct({ + "content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "exit_code": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) ), - "listEndpoints": (author, slug, options) => - HttpClientRequest.get(`/models/${author}/${slug}/endpoints`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListEndpoints200), - "404": decodeError("ListEndpoints404", ListEndpoints404), - "500": decodeError("ListEndpoints500", ListEndpoints500), - orElse: unexpectedStatus - })) - ), - "listEndpointsZdr": (options) => - HttpClientRequest.get(`/endpoints/zdr`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListEndpointsZdr200), - "500": decodeError("ListEndpointsZdr500", ListEndpointsZdr500), - orElse: unexpectedStatus - })) - ), - "listProviders": (options) => - HttpClientRequest.get(`/providers`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListProviders200), - "500": decodeError("ListProviders500", ListProviders500), - orElse: unexpectedStatus - })) - ), - "list": (options) => - HttpClientRequest.get(`/keys`).pipe( - HttpClientRequest.setUrlParams({ - "include_disabled": options?.params?.["include_disabled"] as any, - "offset": options?.params?.["offset"] as any + "type": Schema.String + }) + ), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("shell_call_output") +}).annotate({ + "description": "Output from a shell command execution (newer variant)", + "identifier": "ShellCallOutputItem" +}) +export type OpenAIResponsesToolChoice = + | "auto" + | "none" + | "required" + | Objects_11 + | Objects_12 + | ToolChoiceAllowed + | Objects_13 + | Objects_14 +export const OpenAIResponsesToolChoice = Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("none"), + Schema.Literal("required"), + Objects_11, + Objects_12, + ToolChoiceAllowed, + Objects_13, + Objects_14 +]).annotate({ "identifier": "OpenAIResponsesToolChoice" }) +export type TooManyRequestsResponse = { + readonly "error": TooManyRequestsResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const TooManyRequestsResponse = Schema.Struct({ + "error": TooManyRequestsResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "description": "Too Many Requests - Rate limit exceeded", "identifier": "TooManyRequestsResponse" }) +export type UnauthorizedResponse = { + readonly "error": UnauthorizedResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const UnauthorizedResponse = Schema.Struct({ + "error": UnauthorizedResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Unauthorized - Authentication required or invalid credentials", + "identifier": "UnauthorizedResponse" +}) +export type UnifiedBenchmarksAAItem = { + readonly "agentic_index": number | null + readonly "coding_index": number | null + readonly "display_name": string + readonly "intelligence_index": number | null + readonly "model_permaslug": string + readonly "pricing": UnifiedBenchmarkPricing + readonly "source": "artificial-analysis" +} +export const UnifiedBenchmarksAAItem = Schema.Struct({ + "agentic_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": "Artificial Analysis Agentic Index composite score. Higher is better.", + "format": "double" + }), + "coding_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": "Artificial Analysis Coding Index composite score. Higher is better.", + "format": "double" + }), + "display_name": Schema.String.annotate({ "description": "Model name as listed on Artificial Analysis." }), + "intelligence_index": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ + "description": "Artificial Analysis Intelligence Index composite score. Higher is better.", + "format": "double" + }), + "model_permaslug": Schema.String.annotate({ "description": "Stable OpenRouter model identifier." }), + "pricing": UnifiedBenchmarkPricing, + "source": Schema.Literal("artificial-analysis").annotate({ "description": "Benchmark source discriminator." }) +}).annotate({ "identifier": "UnifiedBenchmarksAAItem" }) +export type UnifiedBenchmarksDAItem = { + readonly "arena": string + readonly "avg_generation_time_ms": number | null + readonly "category": string + readonly "display_name": string + readonly "elo": number + readonly "model_permaslug": string + readonly "pricing": UnifiedBenchmarkPricing + readonly "source": "design-arena" + readonly "tournament_stats": { + readonly "first_place": number | null + readonly "fourth_place": number | null + readonly "second_place": number | null + readonly "third_place": number | null + readonly "total": number | null + } + readonly "win_rate": number +} +export const UnifiedBenchmarksDAItem = Schema.Struct({ + "arena": Schema.String.annotate({ "description": "Arena this ranking belongs to." }), + "avg_generation_time_ms": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Average generation time in milliseconds.", "format": "double" }), + "category": Schema.String.annotate({ "description": "Category within the arena." }), + "display_name": Schema.String.annotate({ "description": "Human-readable model name from Design Arena." }), + "elo": Schema.Number.annotate({ "description": "ELO rating from head-to-head arena battles.", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "model_permaslug": Schema.String.annotate({ + "description": "Stable OpenRouter model identifier when mapped; otherwise the upstream Design Arena model id." + }), + "pricing": UnifiedBenchmarkPricing, + "source": Schema.Literal("design-arena").annotate({ "description": "Benchmark source discriminator." }), + "tournament_stats": Schema.Struct({ + "first_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "fourth_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "second_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "third_place": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "total": Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + }).annotate({ "description": "Placement distribution from tournament matches." }), + "win_rate": Schema.Number.annotate({ "description": "Win rate as a percentage (0–100).", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) +}).annotate({ "identifier": "UnifiedBenchmarksDAItem" }) +export type UnprocessableEntityResponse = { + readonly "error": UnprocessableEntityResponseErrorData + readonly "openrouter_metadata"?: { readonly [x: string]: Schema.Json } | null + readonly "user_id"?: string | null +} +export const UnprocessableEntityResponse = Schema.Struct({ + "error": UnprocessableEntityResponseErrorData, + "openrouter_metadata": Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), Schema.Null]) + ), + "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ + "description": "Unprocessable Entity - Semantic validation failure", + "identifier": "UnprocessableEntityResponse" +}) +export type OpenAIResponsesAnnotation = FileCitation | URLCitation | FilePath +export const OpenAIResponsesAnnotation = Schema.Union([FileCitation, URLCitation, FilePath]).annotate({ + "identifier": "OpenAIResponsesAnnotation" +}) +export type VideoGenerationResponse = { + readonly "error"?: string + readonly "generation_id"?: string + readonly "id": string + readonly "polling_url": string + readonly "status": "pending" | "in_progress" | "completed" | "failed" | "cancelled" | "expired" + readonly "unsigned_urls"?: ReadonlyArray + readonly "usage"?: VideoGenerationUsage +} +export const VideoGenerationResponse = Schema.Struct({ + "error": Schema.optionalKey(Schema.String), + "generation_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "The generation ID associated with this video generation job. Available once the job has been processed." + }) + ), + "id": Schema.String, + "polling_url": Schema.String, + "status": Schema.Literals(["pending", "in_progress", "completed", "failed", "cancelled", "expired"]), + "unsigned_urls": Schema.optionalKey(Schema.Array(Schema.String)), + "usage": Schema.optionalKey(VideoGenerationUsage) +}).annotate({ "identifier": "VideoGenerationResponse" }) +export type VideoModelsListResponse = { readonly "data": ReadonlyArray } +export const VideoModelsListResponse = Schema.Struct({ "data": Schema.Array(VideoModel) }).annotate({ + "identifier": "VideoModelsListResponse" +}) +export type WebFetchServerToolConfig = { + readonly "allowed_domains"?: Arrays_13 + readonly "blocked_domains"?: Arrays_14 + readonly "engine"?: WebFetchEngineEnum + readonly "max_content_tokens"?: number + readonly "max_uses"?: number +} +export const WebFetchServerToolConfig = Schema.Struct({ + "allowed_domains": Schema.optionalKey(Arrays_13), + "blocked_domains": Schema.optionalKey(Arrays_14), + "engine": Schema.optionalKey(WebFetchEngineEnum), + "max_content_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum content length in approximate tokens. Content exceeding this limit is truncated." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_uses": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of web fetches per request. Once exceeded, the tool returns an error." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) +}).annotate({ + "description": "Configuration for the openrouter:web_fetch server tool", + "identifier": "WebFetchServerToolConfig" +}) +export type WebSearchDomainFilter = Objects_152 | null +export const WebSearchDomainFilter = Schema.Union([Objects_152, Schema.Null]).annotate({ + "identifier": "WebSearchDomainFilter" +}) +export type WebSearchPlugin = { + readonly "enabled"?: boolean + readonly "engine"?: WebSearchEngine + readonly "exclude_domains"?: ReadonlyArray + readonly "id": "web" + readonly "include_domains"?: ReadonlyArray + readonly "max_results"?: number + readonly "max_uses"?: number + readonly "search_prompt"?: string + readonly "user_location"?: { + readonly "city"?: string | null + readonly "country"?: string | null + readonly "region"?: string | null + readonly "timezone"?: string | null + readonly "type": "approximate" + readonly [x: string]: Schema.Json + } +} +export const WebSearchPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the web-search plugin for this request. Defaults to true." + }) + ), + "engine": Schema.optionalKey(WebSearchEngine), + "exclude_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of domains to exclude from web search results. Supports wildcards (e.g. \"*.substack.com\") and path filtering (e.g. \"openai.com/blog\")." + }) + ), + "id": Schema.Literal("web"), + "include_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "A list of domains to restrict web search results to. Supports wildcards (e.g. \"*.substack.com\") and path filtering (e.g. \"openai.com/blog\")." + }) + ), + "max_results": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "max_uses": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of times the model can invoke web search in a single turn. Passed through to native providers that support it (e.g. Anthropic)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_prompt": Schema.optionalKey(Schema.String), + "user_location": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "city": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "country": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "region": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "timezone": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("approximate") }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(List200), - "401": decodeError("List401", List401), - "429": decodeError("List429", List429), - "500": decodeError("List500", List500), - orElse: unexpectedStatus - })) - ), - "createKeys": (options) => - HttpClientRequest.post(`/keys`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateKeys201), - "400": decodeError("CreateKeys400", CreateKeys400), - "401": decodeError("CreateKeys401", CreateKeys401), - "429": decodeError("CreateKeys429", CreateKeys429), - "500": decodeError("CreateKeys500", CreateKeys500), - orElse: unexpectedStatus - })) + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ).annotate({ + "description": + "Approximate user location for location-biased search results. Passed through to native providers that support it (e.g. Anthropic)." + }) + ]).annotate({ "description": "User location information for web search" }) + ) +}).annotate({ "identifier": "WebSearchPlugin" }) +export type OutputFileSearchCallItem = { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + readonly "type": "file_search_call" +} +export const OutputFileSearchCallItem = Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus, + "type": Schema.Literal("file_search_call") +}).annotate({ "identifier": "OutputFileSearchCallItem" }) +export type OutputItemFileSearchCall = { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + readonly "type": "file_search_call" +} +export const OutputItemFileSearchCall = Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus, + "type": Schema.Literal("file_search_call") +}).annotate({ "identifier": "OutputItemFileSearchCall" }) +export type OutputItemWebSearchCall = { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": WebSearchStatus + readonly "type": "web_search_call" +} +export const OutputItemWebSearchCall = Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": WebSearchStatus, + "type": Schema.Literal("web_search_call") +}).annotate({ "identifier": "OutputItemWebSearchCall" }) +export type OutputWebSearchCallItem = { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": WebSearchStatus + readonly "type": "web_search_call" +} +export const OutputWebSearchCallItem = Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": WebSearchStatus, + "type": Schema.Literal("web_search_call") +}).annotate({ "identifier": "OutputWebSearchCallItem" }) +export type WebSearchUserLocation = Objects_153 | null +export const WebSearchUserLocation = Schema.Union([Objects_153, Schema.Null]).annotate({ + "description": "User location information for web search", + "identifier": "WebSearchUserLocation" +}) +export type WebSearchConfig = { + readonly "allowed_domains"?: Arrays_15 + readonly "engine"?: WebSearchEngineEnum + readonly "excluded_domains"?: Arrays_16 + readonly "max_characters"?: number + readonly "max_results"?: number + readonly "max_total_results"?: number + readonly "search_context_size"?: SearchQualityLevel + readonly "user_location"?: WebSearchUserLocationServerTool +} +export const WebSearchConfig = Schema.Struct({ + "allowed_domains": Schema.optionalKey(Arrays_15), + "engine": Schema.optionalKey(WebSearchEngineEnum), + "excluded_domains": Schema.optionalKey(Arrays_16), + "max_characters": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Exact maximum number of characters of content per search result. Applies to the Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, maps to the native `max_tokens_per_page` parameter (converted from characters to tokens) and trims the response to the exact character cap. When both `max_characters` and `search_context_size` are set, `max_characters` takes precedence. When omitted, falls back to `search_context_size` mapping (Exa) or engine defaults (Parallel, Perplexity)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_total_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total number of search results across all search calls in a single request. Once this limit is reached, the tool will stop returning new results. Useful for controlling cost and context size in agentic loops. Defaults to 50 when not specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchQualityLevel), + "user_location": Schema.optionalKey(WebSearchUserLocationServerTool) +}).annotate({ "identifier": "WebSearchConfig" }) +export type WebSearchServerToolConfig = { + readonly "allowed_domains"?: Arrays_17 + readonly "engine"?: WebSearchEngineEnum + readonly "excluded_domains"?: Arrays_18 + readonly "max_characters"?: number + readonly "max_results"?: number + readonly "max_total_results"?: number + readonly "search_context_size"?: SearchQualityLevel + readonly "user_location"?: WebSearchUserLocationServerTool +} +export const WebSearchServerToolConfig = Schema.Struct({ + "allowed_domains": Schema.optionalKey(Arrays_17), + "engine": Schema.optionalKey(WebSearchEngineEnum), + "excluded_domains": Schema.optionalKey(Arrays_18), + "max_characters": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Exact maximum number of characters of content per search result. Applies to the Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, maps to the native `max_tokens_per_page` parameter (converted from characters to tokens) and trims the response to the exact character cap. When both `max_characters` and `search_context_size` are set, `max_characters` takes precedence. When omitted, falls back to `search_context_size` mapping (Exa) or engine defaults (Parallel, Perplexity)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_total_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total number of search results across all search calls in a single request. Once this limit is reached, the tool will stop returning new results. Useful for controlling cost and context size in agentic loops. Defaults to 50 when not specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchQualityLevel), + "user_location": Schema.optionalKey(WebSearchUserLocationServerTool) +}).annotate({ + "description": "Configuration for the openrouter:web_search server tool", + "identifier": "WebSearchServerToolConfig" +}) +export type CreateWorkspaceResponse = { readonly "data": Workspace } +export const CreateWorkspaceResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Workspace).annotate({ "description": "The created workspace" }) +}).annotate({ "identifier": "CreateWorkspaceResponse" }) +export type GetWorkspaceResponse = { readonly "data": Workspace } +export const GetWorkspaceResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Workspace).annotate({ "description": "The workspace" }) +}).annotate({ "identifier": "GetWorkspaceResponse" }) +export type ListWorkspacesResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListWorkspacesResponse = Schema.Struct({ + "data": Schema.Array(Workspace).annotate({ "description": "List of workspaces" }), + "total_count": Schema.Number.annotate({ "description": "Total number of workspaces" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListWorkspacesResponse" }) +export type UpdateWorkspaceResponse = { readonly "data": Workspace } +export const UpdateWorkspaceResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Workspace).annotate({ "description": "The updated workspace" }) +}).annotate({ "identifier": "UpdateWorkspaceResponse" }) +export type ListWorkspaceBudgetsResponse = { readonly "data": ReadonlyArray } +export const ListWorkspaceBudgetsResponse = Schema.Struct({ + "data": Schema.Array(WorkspaceBudget).annotate({ "description": "List of budgets configured for the workspace" }) +}).annotate({ "identifier": "ListWorkspaceBudgetsResponse" }) +export type UpsertWorkspaceBudgetResponse = { readonly "data": WorkspaceBudget } +export const UpsertWorkspaceBudgetResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => WorkspaceBudget).annotate({ + "description": "The created or updated budget" + }) +}).annotate({ "identifier": "UpsertWorkspaceBudgetResponse" }) +export type BulkAddWorkspaceMembersResponse = { + readonly "added_count": number + readonly "data": ReadonlyArray +} +export const BulkAddWorkspaceMembersResponse = Schema.Struct({ + "added_count": Schema.Number.annotate({ "description": "Number of workspace memberships created or updated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "data": Schema.Array(WorkspaceMember).annotate({ "description": "List of added workspace memberships" }) +}).annotate({ "identifier": "BulkAddWorkspaceMembersResponse" }) +export type ListWorkspaceMembersResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListWorkspaceMembersResponse = Schema.Struct({ + "data": Schema.Array(WorkspaceMember).annotate({ "description": "List of workspace members" }), + "total_count": Schema.Number.annotate({ "description": "Total number of members in the workspace" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListWorkspaceMembersResponse" }) +export type AdvisorServerToolConfig = { + readonly "forward_transcript"?: boolean + readonly "instructions"?: string + readonly "max_completion_tokens"?: number + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "name"?: string + readonly "reasoning"?: AdvisorReasoning + readonly "stream"?: boolean + readonly "temperature"?: number + readonly "tools"?: Arrays_ +} +export const AdvisorServerToolConfig = Schema.Struct({ + "forward_transcript": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "When true, the full parent conversation is forwarded to the advisor so it sees the same context the executor does (and the tool-call `prompt`, if given, is appended as a final user turn). When false or omitted, the advisor receives only the `prompt` the executor passes in the tool call." + })), + "instructions": Schema.optionalKey( + Schema.String.annotate({ + "description": + "System instructions for the advisor sub-agent. When omitted, the advisor responds with no system prompt of its own." + }) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of output tokens (including reasoning) the advisor may produce. When omitted, the provider's default applies." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps the advisor sub-agent may take during its agentic loop. Capped at 25. Only relevant when the advisor is given tools." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(25).annotate({ "expected": "a value less than or equal to 25" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the advisor model to consult (any OpenRouter model). When omitted, the executor can choose it via the tool call's `model` argument; if neither is set, the model from the outer API request is used. The advisor tool itself cannot be the advisor model." + }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Optional name for this advisor. The model sees one tool per named advisor (and one default for an unnamed entry). Names must be unique across advisor entries. Letters, digits, spaces, underscores, and dashes; trimmed; 1–64 chars." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" }) + ).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9 _-]+$")).annotate({ + "expected": "a string matching the RegExp ^[a-zA-Z0-9 _-]+$" + }) + ) + ), + "reasoning": Schema.optionalKey(AdvisorReasoning), + "stream": Schema.optionalKey(Schema.Boolean.annotate({ + "description": + "When true, the advisor's advice streams incrementally as it is produced. In the Responses API this emits `response.output_text.delta` events targeting the advisor output item; the final `advice` field is still set on the completed item. Has no effect on the Chat Completions API (where the advice arrives only as the final tool result). When false or omitted, the advice arrives only as the final result." + })), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Sampling temperature forwarded to the advisor call. When omitted, the provider's default applies.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "tools": Schema.optionalKey(Arrays_) +}).annotate({ + "description": "Configuration for one openrouter:advisor server tool entry.", + "identifier": "AdvisorServerToolConfig" +}) +export type AnthropicBashCodeExecutionContent = + | AnthropicBashCodeExecutionToolResultError + | AnthropicBashCodeExecutionResult +export const AnthropicBashCodeExecutionContent = Schema.Union([ + AnthropicBashCodeExecutionToolResultError, + AnthropicBashCodeExecutionResult +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicBashCodeExecutionContent" }) +export type AnthropicTextBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "citations"?: + | ReadonlyArray< + | AnthropicCitationCharLocationParam + | AnthropicCitationPageLocationParam + | AnthropicCitationContentBlockLocationParam + | AnthropicCitationWebSearchResultLocationParam + | AnthropicCitationSearchResultLocationParam + > + | null + readonly "text": string + readonly "type": "text" +} +export const AnthropicTextBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + AnthropicCitationCharLocationParam, + AnthropicCitationPageLocationParam, + AnthropicCitationContentBlockLocationParam, + AnthropicCitationWebSearchResultLocationParam, + AnthropicCitationSearchResultLocationParam + ], { mode: "oneOf" }) ), - "getKey": (hash, options) => - HttpClientRequest.get(`/keys/${hash}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetKey200), - "401": decodeError("GetKey401", GetKey401), - "404": decodeError("GetKey404", GetKey404), - "429": decodeError("GetKey429", GetKey429), - "500": decodeError("GetKey500", GetKey500), - orElse: unexpectedStatus - })) - ), - "deleteKeys": (hash, options) => - HttpClientRequest.delete(`/keys/${hash}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(DeleteKeys200), - "401": decodeError("DeleteKeys401", DeleteKeys401), - "404": decodeError("DeleteKeys404", DeleteKeys404), - "429": decodeError("DeleteKeys429", DeleteKeys429), - "500": decodeError("DeleteKeys500", DeleteKeys500), - orElse: unexpectedStatus - })) - ), - "updateKeys": (hash, options) => - HttpClientRequest.patch(`/keys/${hash}`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(UpdateKeys200), - "400": decodeError("UpdateKeys400", UpdateKeys400), - "401": decodeError("UpdateKeys401", UpdateKeys401), - "404": decodeError("UpdateKeys404", UpdateKeys404), - "429": decodeError("UpdateKeys429", UpdateKeys429), - "500": decodeError("UpdateKeys500", UpdateKeys500), - orElse: unexpectedStatus - })) - ), - "listGuardrails": (options) => - HttpClientRequest.get(`/guardrails`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListGuardrails200), - "401": decodeError("ListGuardrails401", ListGuardrails401), - "500": decodeError("ListGuardrails500", ListGuardrails500), - orElse: unexpectedStatus - })) - ), - "createGuardrail": (options) => - HttpClientRequest.post(`/guardrails`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateGuardrail201), - "400": decodeError("CreateGuardrail400", CreateGuardrail400), - "401": decodeError("CreateGuardrail401", CreateGuardrail401), - "500": decodeError("CreateGuardrail500", CreateGuardrail500), - orElse: unexpectedStatus - })) - ), - "getGuardrail": (id, options) => - HttpClientRequest.get(`/guardrails/${id}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetGuardrail200), - "401": decodeError("GetGuardrail401", GetGuardrail401), - "404": decodeError("GetGuardrail404", GetGuardrail404), - "500": decodeError("GetGuardrail500", GetGuardrail500), - orElse: unexpectedStatus - })) - ), - "deleteGuardrail": (id, options) => - HttpClientRequest.delete(`/guardrails/${id}`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(DeleteGuardrail200), - "401": decodeError("DeleteGuardrail401", DeleteGuardrail401), - "404": decodeError("DeleteGuardrail404", DeleteGuardrail404), - "500": decodeError("DeleteGuardrail500", DeleteGuardrail500), - orElse: unexpectedStatus - })) - ), - "updateGuardrail": (id, options) => - HttpClientRequest.patch(`/guardrails/${id}`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(UpdateGuardrail200), - "400": decodeError("UpdateGuardrail400", UpdateGuardrail400), - "401": decodeError("UpdateGuardrail401", UpdateGuardrail401), - "404": decodeError("UpdateGuardrail404", UpdateGuardrail404), - "500": decodeError("UpdateGuardrail500", UpdateGuardrail500), - orElse: unexpectedStatus - })) - ), - "listKeyAssignments": (options) => - HttpClientRequest.get(`/guardrails/assignments/keys`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListKeyAssignments200), - "401": decodeError("ListKeyAssignments401", ListKeyAssignments401), - "500": decodeError("ListKeyAssignments500", ListKeyAssignments500), - orElse: unexpectedStatus - })) - ), - "listMemberAssignments": (options) => - HttpClientRequest.get(`/guardrails/assignments/members`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListMemberAssignments200), - "401": decodeError("ListMemberAssignments401", ListMemberAssignments401), - "500": decodeError("ListMemberAssignments500", ListMemberAssignments500), - orElse: unexpectedStatus - })) - ), - "listGuardrailKeyAssignments": (id, options) => - HttpClientRequest.get(`/guardrails/${id}/assignments/keys`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListGuardrailKeyAssignments200), - "401": decodeError("ListGuardrailKeyAssignments401", ListGuardrailKeyAssignments401), - "404": decodeError("ListGuardrailKeyAssignments404", ListGuardrailKeyAssignments404), - "500": decodeError("ListGuardrailKeyAssignments500", ListGuardrailKeyAssignments500), - orElse: unexpectedStatus - })) - ), - "bulkAssignKeysToGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/keys`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkAssignKeysToGuardrail200), - "400": decodeError("BulkAssignKeysToGuardrail400", BulkAssignKeysToGuardrail400), - "401": decodeError("BulkAssignKeysToGuardrail401", BulkAssignKeysToGuardrail401), - "404": decodeError("BulkAssignKeysToGuardrail404", BulkAssignKeysToGuardrail404), - "500": decodeError("BulkAssignKeysToGuardrail500", BulkAssignKeysToGuardrail500), - orElse: unexpectedStatus - })) - ), - "listGuardrailMemberAssignments": (id, options) => - HttpClientRequest.get(`/guardrails/${id}/assignments/members`).pipe( - HttpClientRequest.setUrlParams({ - "offset": options?.params?.["offset"] as any, - "limit": options?.params?.["limit"] as any - }), - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ListGuardrailMemberAssignments200), - "401": decodeError("ListGuardrailMemberAssignments401", ListGuardrailMemberAssignments401), - "404": decodeError("ListGuardrailMemberAssignments404", ListGuardrailMemberAssignments404), - "500": decodeError("ListGuardrailMemberAssignments500", ListGuardrailMemberAssignments500), - orElse: unexpectedStatus - })) - ), - "bulkAssignMembersToGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/members`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkAssignMembersToGuardrail200), - "400": decodeError("BulkAssignMembersToGuardrail400", BulkAssignMembersToGuardrail400), - "401": decodeError("BulkAssignMembersToGuardrail401", BulkAssignMembersToGuardrail401), - "404": decodeError("BulkAssignMembersToGuardrail404", BulkAssignMembersToGuardrail404), - "500": decodeError("BulkAssignMembersToGuardrail500", BulkAssignMembersToGuardrail500), - orElse: unexpectedStatus - })) - ), - "bulkUnassignKeysFromGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/keys/remove`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkUnassignKeysFromGuardrail200), - "400": decodeError("BulkUnassignKeysFromGuardrail400", BulkUnassignKeysFromGuardrail400), - "401": decodeError("BulkUnassignKeysFromGuardrail401", BulkUnassignKeysFromGuardrail401), - "404": decodeError("BulkUnassignKeysFromGuardrail404", BulkUnassignKeysFromGuardrail404), - "500": decodeError("BulkUnassignKeysFromGuardrail500", BulkUnassignKeysFromGuardrail500), - orElse: unexpectedStatus - })) - ), - "bulkUnassignMembersFromGuardrail": (id, options) => - HttpClientRequest.post(`/guardrails/${id}/assignments/members/remove`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(BulkUnassignMembersFromGuardrail200), - "400": decodeError("BulkUnassignMembersFromGuardrail400", BulkUnassignMembersFromGuardrail400), - "401": decodeError("BulkUnassignMembersFromGuardrail401", BulkUnassignMembersFromGuardrail401), - "404": decodeError("BulkUnassignMembersFromGuardrail404", BulkUnassignMembersFromGuardrail404), - "500": decodeError("BulkUnassignMembersFromGuardrail500", BulkUnassignMembersFromGuardrail500), - orElse: unexpectedStatus - })) - ), - "getCurrentKey": (options) => - HttpClientRequest.get(`/key`).pipe( - withResponse(options?.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(GetCurrentKey200), - "401": decodeError("GetCurrentKey401", GetCurrentKey401), - "500": decodeError("GetCurrentKey500", GetCurrentKey500), - orElse: unexpectedStatus - })) - ), - "exchangeAuthCodeForAPIKey": (options) => - HttpClientRequest.post(`/auth/keys`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(ExchangeAuthCodeForAPIKey200), - "400": decodeError("ExchangeAuthCodeForAPIKey400", ExchangeAuthCodeForAPIKey400), - "403": decodeError("ExchangeAuthCodeForAPIKey403", ExchangeAuthCodeForAPIKey403), - "500": decodeError("ExchangeAuthCodeForAPIKey500", ExchangeAuthCodeForAPIKey500), - orElse: unexpectedStatus - })) - ), - "createAuthKeysCode": (options) => - HttpClientRequest.post(`/auth/keys/code`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(CreateAuthKeysCode200), - "400": decodeError("CreateAuthKeysCode400", CreateAuthKeysCode400), - "401": decodeError("CreateAuthKeysCode401", CreateAuthKeysCode401), - "500": decodeError("CreateAuthKeysCode500", CreateAuthKeysCode500), - orElse: unexpectedStatus - })) - ), - "sendChatCompletionRequest": (options) => - HttpClientRequest.post(`/chat/completions`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - withResponse(options.config)(HttpClientResponse.matchStatus({ - "2xx": decodeSuccess(SendChatCompletionRequest200), - "400": decodeError("SendChatCompletionRequest400", SendChatCompletionRequest400), - "401": decodeError("SendChatCompletionRequest401", SendChatCompletionRequest401), - "429": decodeError("SendChatCompletionRequest429", SendChatCompletionRequest429), - "500": decodeError("SendChatCompletionRequest500", SendChatCompletionRequest500), - orElse: unexpectedStatus - })) - ), - "sendChatCompletionRequestSse": (options) => - HttpClientRequest.post(`/chat/completions`).pipe( - HttpClientRequest.bodyJsonUnsafe(options.payload), - sseRequest(SendChatCompletionRequest200Sse) - ) - } + Schema.Null + ]) + ), + "text": Schema.String, + "type": Schema.Literal("text") +}).annotate({ "identifier": "AnthropicTextBlockParam" }) +export type AnthropicToolSearchToolBm25 = { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "defer_loading"?: boolean + readonly "name": "tool_search_tool_bm25" + readonly "strict"?: boolean + readonly "type": "tool_search_tool_bm25_20251119" | "tool_search_tool_bm25" } - -export interface OpenRouterClient { - readonly httpClient: HttpClient.HttpClient +export const AnthropicToolSearchToolBm25 = Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "defer_loading": Schema.optionalKey(Schema.Boolean), + "name": Schema.Literal("tool_search_tool_bm25"), + "strict": Schema.optionalKey(Schema.Boolean), + "type": Schema.Literals(["tool_search_tool_bm25_20251119", "tool_search_tool_bm25"]) +}).annotate({ "identifier": "AnthropicToolSearchToolBm25" }) +export type AnthropicToolSearchToolRegex = { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "defer_loading"?: boolean + readonly "name": "tool_search_tool_regex" + readonly "strict"?: boolean + readonly "type": "tool_search_tool_regex_20251119" | "tool_search_tool_regex" +} +export const AnthropicToolSearchToolRegex = Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "defer_loading": Schema.optionalKey(Schema.Boolean), + "name": Schema.Literal("tool_search_tool_regex"), + "strict": Schema.optionalKey(Schema.Boolean), + "type": Schema.Literals(["tool_search_tool_regex_20251119", "tool_search_tool_regex"]) +}).annotate({ "identifier": "AnthropicToolSearchToolRegex" }) +export type FusionServerToolConfig = { + readonly "analysis_models"?: Arrays_6 + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "max_completion_tokens"?: number + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "reasoning"?: Objects_9 + readonly "temperature"?: number + readonly "tools"?: Arrays_7 +} +export const FusionServerToolConfig = Schema.Struct({ + "analysis_models": Schema.optionalKey(Arrays_6), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of output tokens (including reasoning tokens) each panelist and the judge model may produce per inner call. Controls the total output budget so reasoning-heavy models like GPT-5.5 do not exhaust their token allowance before producing visible text. When omitted, panelists default to 32000 and the judge to 20000." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps each panelist (analysis model) and the judge model may take during their agentic web-research loop. Models with web_search/web_fetch enabled iterate until they produce a text response or hit this ceiling. Defaults to 8. Capped at 16." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(16).annotate({ "expected": "a value less than or equal to 16" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the judge model that produces the structured analysis JSON. Defaults to the model used in the outer API request." + }) + ), + "reasoning": Schema.optionalKey(Objects_9), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Temperature forwarded to panelist inner calls. The judge always runs at temperature 0 regardless of this value. When omitted, the provider's default applies.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "tools": Schema.optionalKey(Arrays_7) +}).annotate({ + "description": "Configuration for the openrouter:fusion server tool.", + "identifier": "FusionServerToolConfig" +}) +export type AnthropicUsage = { + readonly "cache_creation": AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": AnthropicServerToolUsage + readonly "service_tier": AnthropicServiceTier +} +export const AnthropicUsage = Schema.Struct({ + "cache_creation": AnthropicCacheCreation, + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "inference_geo": Schema.Union([Schema.String, Schema.Null]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": AnthropicServerToolUsage, + "service_tier": AnthropicServiceTier +}).annotate({ "identifier": "AnthropicUsage" }) +export type AnthropicDocumentBlock = { + readonly "citations"?: AnthropicCitationsConfig + readonly "source": AnthropicBase64PdfSource | AnthropicPlainTextSource + readonly "title": string | null + readonly "type": "document" +} +export const AnthropicDocumentBlock = Schema.Struct({ + "citations": Schema.optionalKey(AnthropicCitationsConfig), + "source": Schema.Union([AnthropicBase64PdfSource, AnthropicPlainTextSource]), + "title": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("document") +}).annotate({ "identifier": "AnthropicDocumentBlock" }) +export type AnthropicTextBlock = { + readonly "citations": ReadonlyArray | null + readonly "text": string + readonly "type": "text" +} +export const AnthropicTextBlock = Schema.Struct({ + "citations": Schema.Union([Schema.Array(AnthropicTextCitation), Schema.Null]), + "text": Schema.String, + "type": Schema.Literal("text") +}).annotate({ "identifier": "AnthropicTextBlock" }) +export type AnthropicToolUseBlock = { + readonly "caller": AnthropicCaller + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "tool_use" +} +export const AnthropicToolUseBlock = Schema.Struct({ + "caller": AnthropicCaller, + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("tool_use") +}).annotate({ "identifier": "AnthropicToolUseBlock" }) +export type AnthropicWebSearchToolResult = { + readonly "caller": AnthropicCaller + readonly "content": ReadonlyArray | AnthropicWebSearchToolResultError + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" +} +export const AnthropicWebSearchToolResult = Schema.Struct({ + "caller": AnthropicCaller, + "content": Schema.Union([Schema.Array(AnthropicWebSearchResult), AnthropicWebSearchToolResultError]), + "tool_use_id": Schema.String, + "type": Schema.Literal("web_search_tool_result") +}).annotate({ "identifier": "AnthropicWebSearchToolResult" }) +export type ORAnthropicServerToolUseBlock = { + readonly "caller"?: ORAnthropicNullableCaller + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "server_tool_use" +} +export const ORAnthropicServerToolUseBlock = Schema.Struct({ + "caller": Schema.optionalKey(ORAnthropicNullableCaller), + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("server_tool_use") +}).annotate({ "identifier": "ORAnthropicServerToolUseBlock" }) +export type AnthropicImageBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "source": AnthropicBase64ImageSource | AnthropicUrlImageSource + readonly "type": "image" +} +export const AnthropicImageBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "source": Schema.Union([AnthropicBase64ImageSource, AnthropicUrlImageSource], { mode: "oneOf" }), + "type": Schema.Literal("image") +}).annotate({ "identifier": "AnthropicImageBlockParam" }) +export type AnthropicAdvisorMessageUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": "advisor_message" +} +export const AnthropicAdvisorMessageUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "model": Schema.String, + "type": Schema.Literal("advisor_message") +}).annotate({ "identifier": "AnthropicAdvisorMessageUsageIteration" }) +export type AnthropicBaseUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number +} +export const AnthropicBaseUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))) +}).annotate({ "identifier": "AnthropicBaseUsageIteration" }) +export type AnthropicCompactionUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" +} +export const AnthropicCompactionUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.Literal("compaction") +}).annotate({ "identifier": "AnthropicCompactionUsageIteration" }) +export type AnthropicMessageUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": "message" +} +export const AnthropicMessageUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Literal("message") +}).annotate({ "identifier": "AnthropicMessageUsageIteration" }) +export type AnthropicUnknownUsageIteration = { + readonly "cache_creation"?: AnthropicIterationCacheCreation + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": string +} +export const AnthropicUnknownUsageIteration = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicIterationCacheCreation), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "output_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "type": Schema.String +}).annotate({ "identifier": "AnthropicUnknownUsageIteration" }) +export type AnthropicCodeExecutionContent = + | AnthropicCodeExecutionToolResultError + | AnthropicCodeExecutionResult + | AnthropicEncryptedCodeExecutionResult +export const AnthropicCodeExecutionContent = Schema.Union([ + AnthropicCodeExecutionToolResultError, + AnthropicCodeExecutionResult, + AnthropicEncryptedCodeExecutionResult +], { mode: "oneOf" }).annotate({ "identifier": "AnthropicCodeExecutionContent" }) +export type AnthropicTextEditorCodeExecutionToolResult = { + readonly "content": AnthropicTextEditorCodeExecutionContent + readonly "tool_use_id": string + readonly "type": "text_editor_code_execution_tool_result" +} +export const AnthropicTextEditorCodeExecutionToolResult = Schema.Struct({ + "content": AnthropicTextEditorCodeExecutionContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("text_editor_code_execution_tool_result") +}).annotate({ "identifier": "AnthropicTextEditorCodeExecutionToolResult" }) +export type AnthropicToolSearchContent = AnthropicToolSearchResultError | AnthropicToolSearchResult +export const AnthropicToolSearchContent = Schema.Union([AnthropicToolSearchResultError, AnthropicToolSearchResult], { + mode: "oneOf" +}).annotate({ "identifier": "AnthropicToolSearchContent" }) +export type MessagesErrorResponse = { readonly "error": MessagesErrorDetail; readonly "type": "error" } +export const MessagesErrorResponse = Schema.Struct({ "error": MessagesErrorDetail, "type": Schema.Literal("error") }) + .annotate({ "identifier": "MessagesErrorResponse" }) +export type ApplyPatchServerTool_OpenRouter = { + readonly "parameters"?: ApplyPatchServerToolConfig + readonly "type": "openrouter:apply_patch" +} +export const ApplyPatchServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(ApplyPatchServerToolConfig), + "type": Schema.Literal("openrouter:apply_patch") +}).annotate({ + "description": + "OpenRouter built-in server tool: validates V4A diff patches for file operations (create, update, delete). Restricted to the Responses API.", + "identifier": "ApplyPatchServerTool_OpenRouter" +}) +export type ApplyPatchCallItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "operation": ApplyPatchCallOperation + readonly "status": ApplyPatchCallStatus + readonly "type": "apply_patch_call" +} +export const ApplyPatchCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "operation": ApplyPatchCallOperation, + "status": ApplyPatchCallStatus, + "type": Schema.Literal("apply_patch_call") +}).annotate({ + "description": + "A tool call emitted by the model requesting a V4A patch operation. The client applies the patch and echoes an `apply_patch_call_output` on the next turn.", + "identifier": "ApplyPatchCallItem" +}) +export type OutputApplyPatchCallItem = { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": ApplyPatchCallStatus + readonly "type": "apply_patch_call" +} +export const OutputApplyPatchCallItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": ApplyPatchCallStatus, + "type": Schema.Literal("apply_patch_call") +}).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand.", + "identifier": "OutputApplyPatchCallItem" +}) +export type OutputApplyPatchServerToolItem = { + readonly "call_id"?: string + readonly "id"?: string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": ToolCallStatus + readonly "type": "openrouter:apply_patch" +} +export const OutputApplyPatchServerToolItem = Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:apply_patch") +}).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn.", + "identifier": "OutputApplyPatchServerToolItem" +}) +export type CreateBYOKKeyResponse = { readonly "data": BYOKKey } +export const CreateBYOKKeyResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => BYOKKey).annotate({ + "description": "The created BYOK credential." + }) +}).annotate({ "identifier": "CreateBYOKKeyResponse" }) +export type GetBYOKKeyResponse = { readonly "data": BYOKKey } +export const GetBYOKKeyResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => BYOKKey).annotate({ "description": "The BYOK credential." }) +}).annotate({ "identifier": "GetBYOKKeyResponse" }) +export type ListBYOKKeysResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListBYOKKeysResponse = Schema.Struct({ + "data": Schema.Array(BYOKKey).annotate({ "description": "List of BYOK credentials." }), + "total_count": Schema.Number.annotate({ "description": "Total number of BYOK credentials matching the filters." }) + .check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "identifier": "ListBYOKKeysResponse" }) +export type UpdateBYOKKeyResponse = { readonly "data": BYOKKey } +export const UpdateBYOKKeyResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => BYOKKey).annotate({ + "description": "The updated BYOK credential." + }) +}).annotate({ "identifier": "UpdateBYOKKeyResponse" }) +export type ChatTokenLogprobs = Objects_7 | null +export const ChatTokenLogprobs = Schema.Union([Objects_7, Schema.Null]).annotate({ + "description": "Log probabilities for the completion", + "identifier": "ChatTokenLogprobs" +}) +export type BashServerToolConfig = { + readonly "engine"?: BashServerToolEngine + readonly "environment"?: BashServerToolEnvironment + readonly "sleep_after_seconds"?: SandboxSleepAfterSeconds +} +export const BashServerToolConfig = Schema.Struct({ + "engine": Schema.optionalKey(BashServerToolEngine), + "environment": Schema.optionalKey(BashServerToolEnvironment), + "sleep_after_seconds": Schema.optionalKey(SandboxSleepAfterSeconds) +}).annotate({ + "description": "Configuration for the openrouter:bash server tool", + "identifier": "BashServerToolConfig" +}) +export type ShellServerToolConfig = { + readonly "engine"?: ShellServerToolEngine + readonly "environment"?: ShellServerToolEnvironment + readonly "sleep_after_seconds"?: SandboxSleepAfterSeconds +} +export const ShellServerToolConfig = Schema.Struct({ + "engine": Schema.optionalKey(ShellServerToolEngine), + "environment": Schema.optionalKey(ShellServerToolEnvironment), + "sleep_after_seconds": Schema.optionalKey(SandboxSleepAfterSeconds) +}).annotate({ + "description": "Configuration for the openrouter:shell server tool", + "identifier": "ShellServerToolConfig" +}) +export type ModelBenchmarks = { readonly "artificial_analysis"?: AABenchmarkEntry; readonly "design_arena": Arrays_9 } +export const ModelBenchmarks = Schema.Struct({ + "artificial_analysis": Schema.optionalKey(AABenchmarkEntry), + "design_arena": Arrays_9 +}).annotate({ + "description": "Third-party benchmark rankings for this model. Omitted when no benchmark data is available.", + "identifier": "ModelBenchmarks" +}) +export type TextConfig = { readonly "format"?: Formats; readonly "verbosity"?: "high" | "low" | "medium" | null } +export const TextConfig = Schema.Struct({ + "format": Schema.optionalKey(Formats), + "verbosity": Schema.optionalKey( + Schema.Union([Schema.Literal("high"), Schema.Literal("low"), Schema.Literal("medium"), Schema.Null]) + ) +}).annotate({ "description": "Text output configuration including format and verbosity", "identifier": "TextConfig" }) +export type TextExtendedConfig = { + readonly "format"?: Formats + readonly "verbosity"?: "high" | "low" | "medium" | null +} +export const TextExtendedConfig = Schema.Struct({ + "format": Schema.optionalKey(Formats), + "verbosity": Schema.optionalKey( + Schema.Union([Schema.Literal("high"), Schema.Literal("low"), Schema.Literal("medium"), Schema.Union([Schema.Null])]) + ) +}).annotate({ + "description": "Text output configuration including format and verbosity", + "identifier": "TextExtendedConfig" +}) +export type FusionCallAnalysisCompletedEvent = { + readonly "analysis": FusionAnalysisResult + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.fusion_call.analysis.completed" +} +export const FusionCallAnalysisCompletedEvent = Schema.Struct({ + "analysis": FusionAnalysisResult, + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.fusion_call.analysis.completed") +}).annotate({ + "description": "Emitted when the fusion judge completes with the structured analysis.", + "identifier": "FusionCallAnalysisCompletedEvent" +}) +export type OutputFusionServerToolItem = { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id"?: string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": "openrouter:fusion" +} +export const OutputFusionServerToolItem = Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the fusion run did not produce an analysis result." }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.optionalKey(Schema.String), + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })).annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": ToolCallStatus, + "type": Schema.Literal("openrouter:fusion") +}).annotate({ + "description": "An openrouter:fusion server tool output item", + "identifier": "OutputFusionServerToolItem" +}) +export type ImageModelListItem = { + readonly "architecture": ImageModelArchitecture + readonly "created": number + readonly "description": string + readonly "endpoints": string + readonly "id": string + readonly "name": string + readonly "supported_parameters": SupportedParameters + readonly "supports_streaming": boolean +} +export const ImageModelListItem = Schema.Struct({ + "architecture": ImageModelArchitecture, + "created": Schema.Number.annotate({ "description": "Unix timestamp (seconds) of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "description": Schema.String, + "endpoints": Schema.String.annotate({ + "description": "Relative URL to the full per-endpoint records for this model" + }), + "id": Schema.String.annotate({ "description": "Model slug" }), + "name": Schema.String.annotate({ "description": "Display name" }), + "supported_parameters": SupportedParameters, + "supports_streaming": Schema.Boolean.annotate({ + "description": + "Whether any endpoint of this model supports native SSE streaming on the dedicated Image API (i.e. `stream: true` in the request). OR across endpoints." + }) +}).annotate({ "description": "A single image model in the discovery listing.", "identifier": "ImageModelListItem" }) +export type ObservabilityArizeDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "modelId": string + readonly "spaceKey": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "arize" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityArizeDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "modelId": Schema.String.annotate({ "description": "The name of the tracing project in Arize AX" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "spaceKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("arize"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityArizeDestination" }) +export type ObservabilityBraintrustDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "projectId": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "braintrust" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityBraintrustDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "projectId": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("braintrust"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityBraintrustDestination" }) +export type ObservabilityClickhouseDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "database": string + readonly "headers"?: {} + readonly "host": string + readonly "password": string + readonly "table"?: string + readonly "username": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "clickhouse" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityClickhouseDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "database": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "host": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "password": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "table": Schema.optionalKey(Schema.String), + "username": Schema.String.annotate({ + "description": "If you have not set a specific username in ClickHouse, simply type in 'default' below." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("clickhouse"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityClickhouseDestination" }) +export type ObservabilityDatadogDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "headers"?: {} + readonly "mlApp": string + readonly "url"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "datadog" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityDatadogDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.annotate({ + "description": "Datadog API key must have LLM Observability permissions. Create at: " + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "mlApp": Schema.String.annotate({ "description": "Name to identify your application in Datadog LLM Observability" }) + .check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "url": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Datadog API URL for your region (e.g., https://api.datadoghq.com, https://api.us3.datadoghq.com, https://api.datadoghq.eu)" + }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("datadog"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityDatadogDestination" }) +export type ObservabilityGrafanaDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "instanceId": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "grafana" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityGrafanaDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "instanceId": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("grafana"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityGrafanaDestination" }) +export type ObservabilityLangfuseDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "baseUrl"?: string + readonly "headers"?: {} + readonly "publicKey": string + readonly "secretKey": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "langfuse" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityLangfuseDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "publicKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "secretKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("langfuse"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityLangfuseDestination" }) +export type ObservabilityLangsmithDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "endpoint"?: string + readonly "headers"?: {} + readonly "project"?: string + readonly "workspaceId"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "langsmith" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityLangsmithDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "endpoint": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "project": Schema.optionalKey( + Schema.String.annotate({ + "description": "The name for this project, such as pr-openrouter-demo. Defaults to \"main\" if not set." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + ), + "workspaceId": Schema.optionalKey( + Schema.String.annotate({ + "description": "Required for org-scoped API keys. Find this in your LangSmith workspace settings." + }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("langsmith"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityLangsmithDestination" }) +export type ObservabilityNewrelicDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "headers"?: {}; readonly "licenseKey": string; readonly "region"?: "us" | "eu" } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "newrelic" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityNewrelicDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "licenseKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "region": Schema.optionalKey(Schema.Literals(["us", "eu"])) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("newrelic"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityNewrelicDestination" }) +export type ObservabilityOpikDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "headers"?: {} + readonly "projectName": string + readonly "workspace": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "opik" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityOpikDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "projectName": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "workspace": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("opik"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityOpikDestination" }) +export type ObservabilityOtelCollectorDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "endpoint": string; readonly "headers"?: {} } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "otel-collector" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityOtelCollectorDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "endpoint": Schema.String, + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "Custom HTTP headers as a JSON object. For Axiom, use {\"Authorization\": \"Bearer xaat-xxx\", \"X-Axiom-Dataset\": \"your-dataset\"}" + }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("otel-collector"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityOtelCollectorDestination" }) +export type ObservabilityPosthogDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "apiKey": string; readonly "endpoint"?: string; readonly "headers"?: {} } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "posthog" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityPosthogDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "endpoint": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("posthog"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityPosthogDestination" }) +export type ObservabilityRampDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "apiKey": string; readonly "baseUrl"?: string; readonly "headers"?: {} } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "ramp" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityRampDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.annotate({ "description": "Generate this in your Ramp integration settings." }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to Ramp." }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("ramp"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityRampDestination" }) +export type ObservabilityS3Destination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "accessKeyId": string + readonly "bucketName": string + readonly "endpoint"?: string + readonly "headers"?: {} + readonly "pathTemplate"?: string + readonly "prefix"?: string + readonly "region"?: string + readonly "secretAccessKey": string + readonly "sessionToken"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "s3" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityS3Destination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "accessKeyId": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "bucketName": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "endpoint": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Only for S3-compatible services like Cloudflare R2 (https://account-id.r2.cloudflarestorage.com) or MinIO. Leave blank for standard AWS S3.", + "format": "uri" + }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "pathTemplate": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Template for S3 object path. The filename ({traceId}-{timestamp}.json) is automatically appended. Available variables: {prefix}, {date}, {year}, {month}, {day}, {apiKeyName}" + }) + ), + "prefix": Schema.optionalKey(Schema.String), + "region": Schema.optionalKey(Schema.String), + "secretAccessKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "sessionToken": Schema.optionalKey(Schema.String) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("s3"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityS3Destination" }) +export type ObservabilitySentryDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "dsn": string; readonly "headers"?: {}; readonly "otlpEndpoint": string } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "sentry" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilitySentryDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "dsn": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })) + .check( + Schema.isPattern(new RegExp("^https:\\/\\/([^:@]+)(?::[^@]*)?@([^/]+)(?:\\/[^/]+)*\\/(\\d+)\\/?$")).annotate({ + "expected": "a string matching the RegExp ^https:\\/\\/([^:@]+)(?::[^@]*)?@([^/]+)(?:\\/[^/]+)*\\/(\\d+)\\/?$" + }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "otlpEndpoint": Schema.String + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("sentry"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilitySentryDestination" }) +export type ObservabilitySnowflakeDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "account": string + readonly "database"?: string + readonly "headers"?: {} + readonly "schema"?: string + readonly "table"?: string + readonly "token": string + readonly "warehouse"?: string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "snowflake" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilitySnowflakeDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "account": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "database": Schema.optionalKey(Schema.String), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "schema": Schema.optionalKey(Schema.String), + "table": Schema.optionalKey(Schema.String), + "token": Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "warehouse": Schema.optionalKey(Schema.String) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("snowflake"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilitySnowflakeDestination" }) +export type ObservabilityWeaveDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { + readonly "apiKey": string + readonly "baseUrl"?: string + readonly "entity": string + readonly "headers"?: {} + readonly "project": string + } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "weave" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityWeaveDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "apiKey": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "baseUrl": Schema.optionalKey(Schema.String), + "entity": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "headers": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Custom HTTP headers to include in requests to this destination." }) + ), + "project": Schema.String.check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("weave"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityWeaveDestination" }) +export type ObservabilityWebhookDestination = { + readonly "api_key_hashes": ReadonlyArray | null + readonly "config": { readonly "headers"?: {}; readonly "method"?: "POST" | "PUT"; readonly "url": string } + readonly "created_at": string + readonly "enabled": boolean + readonly "filter_rules": ObservabilityFilterRulesConfig + readonly "id": string + readonly "name": string | null + readonly "privacy_mode": boolean + readonly "sampling_rate": number + readonly "type": "webhook" + readonly "updated_at": string + readonly "workspace_id": string +} +export const ObservabilityWebhookDestination = Schema.Struct({ + "api_key_hashes": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes (`api_keys.hash`) whose traffic is forwarded to this destination. `null` means all keys." + }), + "config": Schema.Struct({ + "headers": Schema.optionalKey(Schema.Struct({})), + "method": Schema.optionalKey(Schema.Literals(["POST", "PUT"])), + "url": Schema.String + }), + "created_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was created." }), + "enabled": Schema.Boolean.annotate({ "description": "Whether this destination is currently enabled." }), + "filter_rules": ObservabilityFilterRulesConfig, + "id": Schema.String.annotate({ "description": "Stable public identifier for this destination.", "format": "uuid" }), + "name": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Human-readable name for the destination." + }), + "privacy_mode": Schema.Boolean.annotate({ + "description": "When true, request/response bodies are not forwarded to this destination — only metadata." + }), + "sampling_rate": Schema.Number.annotate({ + "description": "Sampling rate for events sent to this destination, between 0.0001 and 1 (1 = 100%).", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "type": Schema.Literal("webhook"), + "updated_at": Schema.String.annotate({ "description": "ISO timestamp of when the destination was last updated." }), + "workspace_id": Schema.String.annotate({ + "description": "ID of the workspace this destination belongs to.", + "format": "uuid" + }) +}).annotate({ "identifier": "ObservabilityWebhookDestination" }) +export type ObservabilityFilterRulesConfigNullable = Objects_10 | null +export const ObservabilityFilterRulesConfigNullable = Schema.Union([Objects_10, Schema.Null]).annotate({ + "description": "Optional structured filter rules controlling which events are forwarded.", + "identifier": "ObservabilityFilterRulesConfigNullable" +}) +export type BaseTextDeltaEvent = { + readonly "content_index": number + readonly "delta": string + readonly "item_id": string + readonly "logprobs": ReadonlyArray + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_text.delta" +} +export const BaseTextDeltaEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "delta": Schema.String, + "item_id": Schema.String, + "logprobs": Schema.Array(OpenResponsesLogProbs), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.delta") +}).annotate({ "description": "Event emitted when a text delta is streamed", "identifier": "BaseTextDeltaEvent" }) +export type BaseTextDoneEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "logprobs": ReadonlyArray + readonly "output_index": number + readonly "sequence_number": number + readonly "text": string + readonly "type": "response.output_text.done" +} +export const BaseTextDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "logprobs": Schema.Array(OpenResponsesLogProbs), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "text": Schema.String, + "type": Schema.Literal("response.output_text.done") +}).annotate({ "description": "Event emitted when text streaming is complete", "identifier": "BaseTextDoneEvent" }) +export type FileParserPlugin = { + readonly "enabled"?: boolean + readonly "id": "file-parser" + readonly "pdf"?: PDFParserOptions +} +export const FileParserPlugin = Schema.Struct({ + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Set to false to disable the file-parser plugin for this request. Defaults to true." + }) + ), + "id": Schema.Literal("file-parser"), + "pdf": Schema.optionalKey(PDFParserOptions) +}).annotate({ "identifier": "FileParserPlugin" }) +export type Objects_148 = { + readonly "allow_fallbacks"?: boolean | null + readonly "data_collection"?: "deny" | "allow" | null + readonly "enforce_distillable_text"?: boolean | null + readonly "ignore"?: ReadonlyArray | null + readonly "max_price"?: { + readonly "audio"?: string + readonly "completion"?: string + readonly "image"?: string + readonly "prompt"?: string + readonly "request"?: string + } + readonly "only"?: ReadonlyArray | null + readonly "order"?: ReadonlyArray | null + readonly "preferred_max_latency"?: PreferredMaxLatency + readonly "preferred_min_throughput"?: PreferredMinThroughput + readonly "quantizations"?: ReadonlyArray | null + readonly "require_parameters"?: boolean | null + readonly "sort"?: ProviderSort | ProviderSortConfig | null + readonly "zdr"?: boolean | null +} +export const Objects_148 = Schema.Struct({ + "allow_fallbacks": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" + }) + ), + "data_collection": Schema.optionalKey( + Schema.Union([Schema.Literal("deny"), Schema.Literal("allow"), Schema.Null]).annotate({ + "description": + "Data collection setting. If no available model provider meets the requirement, your request will return an error.\n- allow: (default) allow providers which store user data non-transiently and may train on it\n\n- deny: use only providers which do not collect user data." + }) + ), + "enforce_distillable_text": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used." + }) + ), + "ignore": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request." + }) + ), + "max_price": Schema.optionalKey( + Schema.Struct({ + "audio": Schema.optionalKey(Schema.String.annotate({ "description": "Maximum price in USD per audio unit" })), + "completion": Schema.optionalKey( + Schema.String.annotate({ "description": "Maximum price in USD per million completion tokens" }) + ), + "image": Schema.optionalKey(Schema.String.annotate({ "description": "Maximum price in USD per image" })), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "Maximum price in USD per million prompt tokens" }) + ), + "request": Schema.optionalKey(Schema.String.annotate({ "description": "Maximum price in USD per request" })) + }).annotate({ + "description": + "The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion." + }) + ), + "only": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request." + }) + ), + "order": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Union([ProviderName, Schema.String])), Schema.Null]).annotate({ + "description": + "An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message." + }) + ), + "preferred_max_latency": Schema.optionalKey(PreferredMaxLatency), + "preferred_min_throughput": Schema.optionalKey(PreferredMinThroughput), + "quantizations": Schema.optionalKey( + Schema.Union([Schema.Array(Quantization), Schema.Null]).annotate({ + "description": "A list of quantization levels to filter the provider by." + }) + ), + "require_parameters": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest." + }) + ), + "sort": Schema.optionalKey( + Schema.Union([ProviderSort, ProviderSortConfig, Schema.Null]).annotate({ + "description": + "The sorting strategy to use for this request, if \"order\" is not specified. When set, no load balancing is performed." + }) + ), + "zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used." + }) + ) +}) +export type Arrays_11 = ReadonlyArray +export const Arrays_11 = Schema.Array(PipelineStage) +export type Prediction = Objects_17 | null +export const Prediction = Schema.Union([Objects_17, Schema.Null]).annotate({ + "description": + "Static predicted output content. Supported models can use this to reduce latency when much of the response is known in advance.", + "identifier": "Prediction" +}) +export type ListPresetsResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListPresetsResponse = Schema.Struct({ + "data": Schema.Array(Preset), + "total_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) +}).annotate({ "description": "A paginated list of presets.", "identifier": "ListPresetsResponse" }) +export type CreatePresetFromInferenceResponse = { readonly "data": PresetWithDesignatedVersion } +export const CreatePresetFromInferenceResponse = Schema.Struct({ "data": PresetWithDesignatedVersion }).annotate({ + "description": "Response containing the created preset with its designated version.", + "identifier": "CreatePresetFromInferenceResponse" +}) +export type GetPresetResponse = { readonly "data": PresetWithDesignatedVersion } +export const GetPresetResponse = Schema.Struct({ "data": PresetWithDesignatedVersion }).annotate({ + "description": "A preset with its currently designated version.", + "identifier": "GetPresetResponse" +}) +export type ChatContentText = { + readonly "cache_control"?: ChatContentCacheControl + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": "text" +} +export const ChatContentText = Schema.Struct({ + "cache_control": Schema.optionalKey(ChatContentCacheControl), + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Literal("text") +}).annotate({ "description": "Text content part", "identifier": "ChatContentText" }) +export type CustomToolCallOutputItem = { + readonly "call_id": string + readonly "id"?: string + readonly "output": + | string + | ReadonlyArray< + { + readonly "prompt_cache_breakpoint"?: { readonly "mode": "explicit"; readonly [x: string]: Schema.Json } | null + readonly "text": string + readonly "type": "input_text" + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": "input_file" + } + > + readonly "type": "custom_tool_call_output" +} +export const CustomToolCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "output": Schema.Union([ + Schema.Union([Schema.String]), + Schema.Union([Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "mode": Schema.Literal("explicit") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ), + "text": Schema.String, + "type": Schema.Literal("input_text") + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "detail": Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("high"), + Schema.Literal("low"), + Schema.Literal("original") + ]), + "image_url": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Literal("input_file") + }).annotate({ "description": "File input content item" }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }))]) + ]), + "type": Schema.Literal("custom_tool_call_output") +}).annotate({ + "description": + "The output from a custom (freeform-grammar) tool call execution. Mirrors `function_call_output` but is matched to a `custom_tool_call` rather than a `function_call`.", + "identifier": "CustomToolCallOutputItem" +}) +export type FunctionCallOutputItem = { + readonly "call_id": string + readonly "id"?: string | null + readonly "output": + | string + | ReadonlyArray< + { + readonly "prompt_cache_breakpoint"?: { readonly "mode": "explicit"; readonly [x: string]: Schema.Json } | null + readonly "text": string + readonly "type": "input_text" + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": never + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + } | { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": never + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + } | { + readonly "file_data"?: string + readonly "file_id"?: string | null + readonly "file_url"?: string + readonly "filename"?: string + readonly "type": "input_file" + } + > + readonly "status"?: ToolCallStatus | null + readonly "type": "function_call_output" +} +export const FunctionCallOutputItem = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "output": Schema.Union([ + Schema.Union([Schema.String]), + Schema.Union([Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "mode": Schema.Literal("explicit") }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ]).annotate({ + "description": + "Marks an explicit prompt-cache boundary on this content block (OpenAI-style). Everything through the block carrying this marker is part of the candidate cached prefix. Supported natively by OpenAI GPT-5.6 and newer; on providers that use Anthropic-style `cache_control`, OpenRouter converts the marker to that format automatically." + }) + ), + "text": Schema.String, + "type": Schema.Literal("input_text") + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String + }).annotate({ "description": "Text input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "detail": Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("high"), + Schema.Literal("low"), + Schema.Literal("original") + ]), + "image_url": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "Image input content item" }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Never, + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String) + }).annotate({ "description": "File input content item" }), + Schema.Struct({ + "file_data": Schema.optionalKey(Schema.String), + "file_id": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "file_url": Schema.optionalKey(Schema.String), + "filename": Schema.optionalKey(Schema.String), + "type": Schema.Literal("input_file") + }).annotate({ "description": "File input content item" }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }))]) + ]), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("function_call_output") +}).annotate({ "description": "The output from a function call execution", "identifier": "FunctionCallOutputItem" }) +export type InputText = { + readonly "prompt_cache_breakpoint"?: PromptCacheBreakpoint + readonly "text": string + readonly "type": "input_text" +} +export const InputText = Schema.Struct({ + "prompt_cache_breakpoint": Schema.optionalKey(PromptCacheBreakpoint), + "text": Schema.String, + "type": Schema.Literal("input_text") +}).annotate({ "description": "Text input content item", "identifier": "InputText" }) +export type Guardrail = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_providers"?: ReadonlyArray | null + readonly "content_filter_builtins"?: ReadonlyArray | null + readonly "content_filters"?: ReadonlyArray | null + readonly "created_at": string + readonly "description"?: string | null + readonly "enforce_zdr"?: boolean | null + readonly "enforce_zdr_anthropic"?: boolean | null + readonly "enforce_zdr_google"?: boolean | null + readonly "enforce_zdr_openai"?: boolean | null + readonly "enforce_zdr_other"?: boolean | null + readonly "enforce_zdr_xai"?: boolean | null + readonly "id": string + readonly "ignored_models"?: ReadonlyArray | null + readonly "ignored_providers"?: ReadonlyArray | null + readonly "limit_usd"?: number | null + readonly "name": string + readonly "reset_interval"?: GuardrailInterval + readonly "updated_at"?: string | null + readonly "workspace_id": string +} +export const Guardrail = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "Array of model canonical_slugs (immutable identifiers)" + }) + ), + "allowed_providers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ "description": "List of allowed provider IDs" }) + ), + "content_filter_builtins": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterBuiltinEntry), Schema.Null]).annotate({ + "description": + "Builtin content filters applied to requests. Includes PII detectors and the regex-based prompt injection detector." + }) + ), + "content_filters": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterEntry), Schema.Null]).annotate({ + "description": "Custom regex content filters applied to request messages" + }) + ), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the guardrail was created" }), + "description": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Description of the guardrail" }) + ), + "enforce_zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Deprecated. Use enforce_zdr_anthropic, enforce_zdr_openai, enforce_zdr_google, enforce_zdr_xai, and enforce_zdr_other instead. When provided, its value is copied into any of those per-provider fields that are not explicitly specified on the request." + }) + ), + "enforce_zdr_anthropic": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Anthropic models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_google": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Google models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_openai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for OpenAI models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_other": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for models that are not from Anthropic, OpenAI, Google, or xAI. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_xai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for xAI models. Falls back to enforce_zdr when not provided." + }) + ), + "id": Schema.String.annotate({ "description": "Unique identifier for the guardrail", "format": "uuid" }), + "ignored_models": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "Array of model canonical_slugs to exclude from routing" + }) + ), + "ignored_providers": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "List of provider IDs to exclude from routing" + }) + ), + "limit_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Spending limit in USD", "format": "double" }) + ), + "name": Schema.String.annotate({ "description": "Name of the guardrail" }), + "reset_interval": Schema.optionalKey(GuardrailInterval), + "updated_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the guardrail was last updated" + }) + ), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this guardrail belongs to." }) +}).annotate({ "identifier": "Guardrail" }) +export type CreateGuardrailRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_providers"?: ReadonlyArray | null + readonly "content_filter_builtins"?: ReadonlyArray | null + readonly "content_filters"?: ReadonlyArray | null + readonly "description"?: string | null + readonly "enforce_zdr"?: boolean | null + readonly "enforce_zdr_anthropic"?: boolean | null + readonly "enforce_zdr_google"?: boolean | null + readonly "enforce_zdr_openai"?: boolean | null + readonly "enforce_zdr_other"?: boolean | null + readonly "enforce_zdr_xai"?: boolean | null + readonly "ignored_models"?: ReadonlyArray | null + readonly "ignored_providers"?: ReadonlyArray | null + readonly "limit_usd"?: number | null + readonly "name": string + readonly "reset_interval"?: GuardrailInterval + readonly "workspace_id"?: string +} +export const CreateGuardrailRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "Array of model identifiers (slug or canonical_slug accepted)" }) + ), + "allowed_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "List of allowed provider IDs" }) + ), + "content_filter_builtins": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterBuiltinEntryInput), Schema.Null]).annotate({ + "description": + "Builtin content filters to apply. Every builtin slug supports \"block\", \"redact\", and the detect-only \"flag\" action." + }) + ), + "content_filters": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterEntry), Schema.Null]).annotate({ + "description": "Custom regex content filters to apply to request messages" + }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(1000).annotate({ "expected": "a value with a length of at most 1000" })), + Schema.Null + ]).annotate({ "description": "Description of the guardrail" }) + ), + "enforce_zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Deprecated. Use enforce_zdr_anthropic, enforce_zdr_openai, enforce_zdr_google, enforce_zdr_xai, and enforce_zdr_other instead. When provided, its value is copied into any of those per-provider fields that are not explicitly specified on the request." + }) + ), + "enforce_zdr_anthropic": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Anthropic models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_google": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Google models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_openai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for OpenAI models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_other": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for models that are not from Anthropic, OpenAI, Google, or xAI. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_xai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for xAI models. Falls back to enforce_zdr when not provided." + }) + ), + "ignored_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": "Array of model identifiers to exclude from routing (slug or canonical_slug accepted)" + }) + ), + "ignored_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "List of provider IDs to exclude from routing" }) + ), + "limit_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Spending limit in USD", "format": "double" }) + ), + "name": Schema.String.annotate({ "description": "Name for the new guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(200).annotate({ "expected": "a value with a length of at most 200" })), + "reset_interval": Schema.optionalKey(GuardrailInterval), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The workspace to create the guardrail in. Defaults to the default workspace if not provided.", + "format": "uuid" + }) + ) +}).annotate({ "identifier": "CreateGuardrailRequest" }) +export type UpdateGuardrailRequest = { + readonly "allowed_models"?: ReadonlyArray | null + readonly "allowed_providers"?: ReadonlyArray | null + readonly "content_filter_builtins"?: ReadonlyArray | null + readonly "content_filters"?: ReadonlyArray | null + readonly "description"?: string | null + readonly "enforce_zdr"?: boolean | null + readonly "enforce_zdr_anthropic"?: boolean | null + readonly "enforce_zdr_google"?: boolean | null + readonly "enforce_zdr_openai"?: boolean | null + readonly "enforce_zdr_other"?: boolean | null + readonly "enforce_zdr_xai"?: boolean | null + readonly "ignored_models"?: ReadonlyArray | null + readonly "ignored_providers"?: ReadonlyArray | null + readonly "limit_usd"?: number | null + readonly "name"?: string + readonly "reset_interval"?: GuardrailInterval +} +export const UpdateGuardrailRequest = Schema.Struct({ + "allowed_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "Array of model identifiers (slug or canonical_slug accepted)" }) + ), + "allowed_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "New list of allowed provider IDs" }) + ), + "content_filter_builtins": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterBuiltinEntryInput), Schema.Null]).annotate({ + "description": + "Builtin content filters to apply. Set to null to remove. Every builtin slug supports \"block\", \"redact\", and the detect-only \"flag\" action." + }) + ), + "content_filters": Schema.optionalKey( + Schema.Union([Schema.Array(ContentFilterEntry), Schema.Null]).annotate({ + "description": "Custom regex content filters to apply. Set to null to remove." + }) + ), + "description": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMaxLength(1000).annotate({ "expected": "a value with a length of at most 1000" })), + Schema.Null + ]).annotate({ "description": "New description for the guardrail" }) + ), + "enforce_zdr": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Deprecated. Use enforce_zdr_anthropic, enforce_zdr_openai, enforce_zdr_google, enforce_zdr_xai, and enforce_zdr_other instead. When provided, its value is copied into any of those per-provider fields that are not explicitly specified on the request." + }) + ), + "enforce_zdr_anthropic": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Anthropic models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_google": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for Google models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_openai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for OpenAI models. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_other": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for models that are not from Anthropic, OpenAI, Google, or xAI. Falls back to enforce_zdr when not provided." + }) + ), + "enforce_zdr_xai": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enforce zero data retention for xAI models. Falls back to enforce_zdr when not provided." + }) + ), + "ignored_models": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": "Array of model identifiers to exclude from routing (slug or canonical_slug accepted)" + }) + ), + "ignored_providers": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ "description": "List of provider IDs to exclude from routing" }) + ), + "limit_usd": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "New spending limit in USD", "format": "double" }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ "description": "New name for the guardrail" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ).check(Schema.isMaxLength(200).annotate({ "expected": "a value with a length of at most 200" })) + ), + "reset_interval": Schema.optionalKey(GuardrailInterval) +}).annotate({ "identifier": "UpdateGuardrailRequest" }) +export type SpeechRequest = { + readonly "input": string + readonly "model": string + readonly "provider"?: { readonly "options"?: ProviderOptions } + readonly "response_format"?: "mp3" | "pcm" + readonly "speed"?: number + readonly "voice": string +} +export const SpeechRequest = Schema.Struct({ + "input": Schema.String.annotate({ "description": "Text to synthesize" }), + "model": Schema.String.annotate({ "description": "TTS model identifier" }), + "provider": Schema.optionalKey( + Schema.Struct({ "options": Schema.optionalKey(ProviderOptions) }).annotate({ + "description": "Provider-specific passthrough configuration" + }) + ), + "response_format": Schema.optionalKey( + Schema.Literals(["mp3", "pcm"]).annotate({ "description": "Audio output format" }) + ), + "speed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Playback speed multiplier. Only used by models that support it (e.g. OpenAI TTS). Ignored by other providers.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "voice": Schema.String.annotate({ "description": "Voice identifier (provider-specific)." }) +}).annotate({ "description": "Text-to-speech request input", "identifier": "SpeechRequest" }) +export type STTRequest = { + readonly "input_audio": STTInputAudio + readonly "language"?: string + readonly "model": string + readonly "provider"?: { readonly "options"?: ProviderOptions } + readonly "response_format"?: "json" | "verbose_json" + readonly "temperature"?: number + readonly "timestamp_granularities"?: ReadonlyArray +} +export const STTRequest = Schema.Struct({ + "input_audio": STTInputAudio, + "language": Schema.optionalKey( + Schema.String.annotate({ + "description": "ISO-639-1 language code (e.g., \"en\", \"ja\"). Auto-detected if omitted." + }) + ), + "model": Schema.String.annotate({ "description": "STT model identifier" }), + "provider": Schema.optionalKey( + Schema.Struct({ "options": Schema.optionalKey(ProviderOptions) }).annotate({ + "description": "Provider-specific passthrough configuration" + }) + ), + "response_format": Schema.optionalKey( + Schema.Literals(["json", "verbose_json"]).annotate({ + "description": + "Output format. \"json\" (default) returns { text, usage }. \"verbose_json\" additionally returns task, language, duration, and segment-level timestamps; only supported by OpenAI-compatible providers." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling temperature for transcription", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "timestamp_granularities": Schema.optionalKey( + Schema.Array(STTTimestampGranularity).annotate({ + "description": + "Timestamp detail levels to include when response_format is \"verbose_json\". \"segment\" returns segment-level timestamps; \"word\" additionally returns word-level timestamps in the words array. Ignored unless response_format is \"verbose_json\"." + }) + ) +}).annotate({ + "description": "Speech-to-text request input. Accepts a JSON body with input_audio containing base64-encoded audio.", + "identifier": "STTRequest" +}) +export type VideoGenerationRequest = { + readonly "aspect_ratio"?: "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "3:2" | "2:3" | "21:9" | "9:21" + readonly "callback_url"?: string + readonly "duration"?: number + readonly "frame_images"?: ReadonlyArray + readonly "generate_audio"?: boolean + readonly "input_references"?: ReadonlyArray + readonly "model": string + readonly "prompt"?: string + readonly "provider"?: { readonly "options"?: ProviderOptions } + readonly "resolution"?: "480p" | "720p" | "1080p" | "1K" | "2K" | "4K" + readonly "seed"?: number + readonly "size"?: string +} +export const VideoGenerationRequest = Schema.Struct({ + "aspect_ratio": Schema.optionalKey( + Schema.Literals(["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9", "9:21"]).annotate({ + "description": "Aspect ratio of the generated video" + }) + ), + "callback_url": Schema.optionalKey( + Schema.String.annotate({ + "description": + "URL to receive a webhook notification when the video generation job completes. Overrides the workspace-level default callback URL if set. Must be HTTPS.", + "format": "uri" + }) + ), + "duration": Schema.optionalKey( + Schema.Number.annotate({ "description": "Duration of the generated video in seconds" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })) + ), + "frame_images": Schema.optionalKey( + Schema.Array(FrameImage).annotate({ + "description": + "Images to use as the first and/or last frame of the generated video. Each image must specify a frame_type of first_frame or last_frame." + }) + ), + "generate_audio": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Whether to generate audio alongside the video. Defaults to the endpoint's generate_audio capability flag, false if not set." + }) + ), + "input_references": Schema.optionalKey( + Schema.Array(InputReference).annotate({ + "description": + "Reference assets to guide video generation. Accepts image, audio, and video references. Audio and video references are only honored by providers that support them (currently BytePlus Seedance 2.0); other providers use image references and ignore the rest." + }) + ), + "model": Schema.String, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Text prompt describing the video to generate. Optional for models that support generating a video from image input alone; required by all other models." + }) + ), + "provider": Schema.optionalKey( + Schema.Struct({ "options": Schema.optionalKey(ProviderOptions) }).annotate({ + "description": "Provider-specific passthrough configuration" + }) + ), + "resolution": Schema.optionalKey( + Schema.Literals(["480p", "720p", "1080p", "1K", "2K", "4K"]).annotate({ + "description": "Resolution of the generated video" + }) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "If specified, the generation will sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed for all providers." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "size": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Exact pixel dimensions of the generated video in \"WIDTHxHEIGHT\" format (e.g. \"1280x720\"). Interchangeable with resolution + aspect_ratio." + }) + ) +}).annotate({ "identifier": "VideoGenerationRequest" }) +export type ImageGenerationProviderPreferences = { + readonly "allow_fallbacks"?: boolean | null + readonly "ignore"?: Union_2 + readonly "only"?: Union_3 + readonly "options"?: ProviderOptions + readonly "order"?: Union_4 + readonly "sort"?: Union_5 +} +export const ImageGenerationProviderPreferences = Schema.Struct({ + "allow_fallbacks": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to allow backup providers to serve requests\n- true: (default) when the primary provider (or your custom providers in \"order\") is unavailable, use the next best provider.\n- false: use only the primary/custom provider, and return the upstream error if it's unavailable.\n" + }) + ), + "ignore": Schema.optionalKey(Union_2), + "only": Schema.optionalKey(Union_3), + "options": Schema.optionalKey(ProviderOptions), + "order": Schema.optionalKey(Union_4), + "sort": Schema.optionalKey(Union_5) +}).annotate({ + "description": "Provider routing preferences and provider-specific passthrough configuration.", + "identifier": "ImageGenerationProviderPreferences" +}) +export type ListEndpointsResponse = { + readonly "architecture": { + readonly "input_modalities": ReadonlyArray<"text" | "image" | "file" | "audio" | "video"> + readonly "instruct_type": + | "none" + | "airoboros" + | "alpaca" + | "alpaca-modif" + | "chatml" + | "claude" + | "code-llama" + | "gemma" + | "llama2" + | "llama3" + | "mistral" + | "nemotron" + | "neural" + | "openchat" + | "phi3" + | "rwkv" + | "vicuna" + | "zephyr" + | "deepseek-r1" + | "deepseek-v3.1" + | "qwq" + | "qwen3" + | null + readonly "modality": string | null + readonly "output_modalities": ReadonlyArray< + "text" | "image" | "embeddings" | "audio" | "video" | "rerank" | "speech" | "transcription" + > + readonly "tokenizer": + | "Router" + | "Media" + | "Other" + | "GPT" + | "Claude" + | "Gemini" + | "Gemma" + | "Grok" + | "Cohere" + | "Nova" + | "Qwen" + | "Yi" + | "DeepSeek" + | "Mistral" + | "Llama2" + | "Llama3" + | "Llama4" + | "PaLM" + | "RWKV" + | "Qwen3" + } + readonly "created": number + readonly "description": string + readonly "endpoints": ReadonlyArray + readonly "id": string + readonly "name": string +} +export const ListEndpointsResponse = Schema.Struct({ + "architecture": Schema.Struct({ + "input_modalities": Schema.Array( + Schema.Union([ + Schema.Literal("text"), + Schema.Literal("image"), + Schema.Literal("file"), + Schema.Literal("audio"), + Schema.Literal("video") + ]) + ).annotate({ "description": "Supported input modalities" }), + "instruct_type": Schema.Union([ + Schema.Literal("none").annotate({ "description": "Instruction format type" }), + Schema.Literal("airoboros").annotate({ "description": "Instruction format type" }), + Schema.Literal("alpaca").annotate({ "description": "Instruction format type" }), + Schema.Literal("alpaca-modif").annotate({ "description": "Instruction format type" }), + Schema.Literal("chatml").annotate({ "description": "Instruction format type" }), + Schema.Literal("claude").annotate({ "description": "Instruction format type" }), + Schema.Literal("code-llama").annotate({ "description": "Instruction format type" }), + Schema.Literal("gemma").annotate({ "description": "Instruction format type" }), + Schema.Literal("llama2").annotate({ "description": "Instruction format type" }), + Schema.Literal("llama3").annotate({ "description": "Instruction format type" }), + Schema.Literal("mistral").annotate({ "description": "Instruction format type" }), + Schema.Literal("nemotron").annotate({ "description": "Instruction format type" }), + Schema.Literal("neural").annotate({ "description": "Instruction format type" }), + Schema.Literal("openchat").annotate({ "description": "Instruction format type" }), + Schema.Literal("phi3").annotate({ "description": "Instruction format type" }), + Schema.Literal("rwkv").annotate({ "description": "Instruction format type" }), + Schema.Literal("vicuna").annotate({ "description": "Instruction format type" }), + Schema.Literal("zephyr").annotate({ "description": "Instruction format type" }), + Schema.Literal("deepseek-r1").annotate({ "description": "Instruction format type" }), + Schema.Literal("deepseek-v3.1").annotate({ "description": "Instruction format type" }), + Schema.Literal("qwq").annotate({ "description": "Instruction format type" }), + Schema.Literal("qwen3").annotate({ "description": "Instruction format type" }), + Schema.Union([Schema.Null]).annotate({ "description": "Instruction format type" }) + ]).annotate({ "description": "Instruction format type" }), + "modality": Schema.Union([ + Schema.Union([Schema.String]).annotate({ "description": "Primary modality of the model" }), + Schema.Union([Schema.Null]).annotate({ "description": "Primary modality of the model" }) + ]).annotate({ "description": "Primary modality of the model" }), + "output_modalities": Schema.Array( + Schema.Union([ + Schema.Literal("text"), + Schema.Literal("image"), + Schema.Literal("embeddings"), + Schema.Literal("audio"), + Schema.Literal("video"), + Schema.Literal("rerank"), + Schema.Literal("speech"), + Schema.Literal("transcription") + ]) + ).annotate({ "description": "Supported output modalities" }), + "tokenizer": Schema.Union([ + Schema.Union([Schema.Literal("Router").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Media").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Other").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("GPT").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Claude").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Gemini").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Gemma").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Grok").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Cohere").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Nova").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Qwen").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Yi").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("DeepSeek").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Mistral").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Llama2").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Llama3").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Llama4").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("PaLM").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("RWKV").annotate({ "description": "Tokenizer type used by the model" })]), + Schema.Union([Schema.Literal("Qwen3").annotate({ "description": "Tokenizer type used by the model" })]) + ]).annotate({ "description": "Tokenizer type used by the model" }) + }).annotate({ "description": "Model architecture information" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "description": Schema.String.annotate({ "description": "Description of the model" }), + "endpoints": Schema.Array(PublicEndpoint).annotate({ "description": "List of available endpoints for this model" }), + "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), + "name": Schema.String.annotate({ "description": "Display name of the model" }) +}).annotate({ "description": "List of available endpoints for a model", "identifier": "ListEndpointsResponse" }) +export type ModelReasoning = { + readonly "default_effort"?: ReasoningEffort + readonly "default_enabled"?: boolean + readonly "mandatory": boolean + readonly "supported_efforts"?: Union_12 + readonly "supports_max_tokens"?: boolean +} +export const ModelReasoning = Schema.Struct({ + "default_effort": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ReasoningEffort).annotate({ + "description": + "Default reasoning effort when the client enables reasoning without specifying effort. Maps to `reasoning.effort` in chat requests. When `\"none\"`, prefer omitting effort unless the user explicitly disables reasoning." + }) + ), + "default_enabled": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Default reasoning enabled state when the client does not set `reasoning.enabled`." + }) + ), + "mandatory": Schema.Boolean.annotate({ + "description": "When true, reasoning cannot be disabled and effort \"none\" is rejected." + }), + "supported_efforts": Schema.optionalKey(Union_12), + "supports_max_tokens": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "Present and `true` when the model accepts `reasoning.max_tokens` in requests (Anthropic-style) instead of or in addition to `reasoning.effort`. Omitted otherwise." + }) + ) +}).annotate({ + "description": "Reasoning effort configuration. Omitted for non-reasoning models and dynamic router models.", + "identifier": "ModelReasoning" +}) +export type ReasoningDetailUnion = + | ReasoningDetailSummary + | ReasoningDetailEncrypted + | ReasoningDetailText + | ReasoningDetailServerToolCall +export const ReasoningDetailUnion = Schema.Union([ + ReasoningDetailSummary, + ReasoningDetailEncrypted, + ReasoningDetailText, + ReasoningDetailServerToolCall +], { mode: "oneOf" }).annotate({ "description": "Reasoning detail union schema", "identifier": "ReasoningDetailUnion" }) +export type BaseReasoningConfig = Objects_5 | null +export const BaseReasoningConfig = Schema.Union([Objects_5, Schema.Null]).annotate({ + "identifier": "BaseReasoningConfig" +}) +export type ReasoningConfig = Union_13 | null +export const ReasoningConfig = Schema.Union([Union_13, Schema.Null]).annotate({ + "description": "Configuration for reasoning mode in the response", + "identifier": "ReasoningConfig" +}) +export type ChatUsage = { + readonly "completion_tokens": number + readonly "completion_tokens_details"?: Union_ + readonly "cost"?: number | null + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + readonly "prompt_tokens": number + readonly "prompt_tokens_details"?: Union_1 + readonly "server_tool_use_details"?: ServerToolUseDetails + readonly "total_tokens": number +} +export const ChatUsage = Schema.Struct({ + "completion_tokens": Schema.Number.annotate({ "description": "Number of tokens in the completion" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "completion_tokens_details": Schema.optionalKey(Union_), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether a request was made using a Bring Your Own Key configuration" }) + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Number of tokens in the prompt" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "prompt_tokens_details": Schema.optionalKey(Union_1), + "server_tool_use_details": Schema.optionalKey(ServerToolUseDetails), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "description": "Token usage statistics", "identifier": "ChatUsage" }) +export type Usage = { + readonly "input_tokens": number + readonly "input_tokens_details": { readonly "cache_write_tokens"?: number | null; readonly "cached_tokens": number } + readonly "output_tokens": number + readonly "output_tokens_details": { readonly "reasoning_tokens": number } + readonly "total_tokens": number + readonly "cost"?: number | null + readonly "cost_details"?: { + readonly "upstream_inference_cost"?: number | null + readonly "upstream_inference_input_cost": number + readonly "upstream_inference_output_cost": number + } + readonly "is_byok"?: boolean + readonly "server_tool_use_details"?: ServerToolUseDetails +} | null +export const Usage = Schema.Union([ + Schema.Struct({ + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "input_tokens_details": Schema.Struct({ + "cache_write_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "cached_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "total_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(Schema.Struct({ + "upstream_inference_cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "upstream_inference_input_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "upstream_inference_output_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + })), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether a request was made using a Bring Your Own Key configuration" }) + ), + "server_tool_use_details": Schema.optionalKey(ServerToolUseDetails) + }), + Schema.Null +]).annotate({ "description": "Token usage information for the response", "identifier": "Usage" }) +export type StopServerToolsWhen = ReadonlyArray +export const StopServerToolsWhen = Schema.Array(StopServerToolsWhenCondition).annotate({ + "description": + "Stop conditions for the server-tool agent loop. Any condition firing halts the loop (OR logic). When set, this overrides `max_tool_calls`. When a condition fires while the model is still emitting tool calls, the pending tool calls are executed and one final turn is made with tool calls disabled so the response ends with a natural-language answer instead of an unfinished tool call." +}).check( + Schema.isMinLength(1).annotate({ + "expected": "a value with a length of at least 1", + "identifier": "StopServerToolsWhen" + }) +) +export type SubagentServerToolConfig = { + readonly "instructions"?: string + readonly "max_completion_tokens"?: number + readonly "max_tool_calls"?: number + readonly "model"?: string + readonly "name"?: string + readonly "reasoning"?: SubagentReasoning + readonly "temperature"?: number + readonly "tools"?: Arrays_12 +} +export const SubagentServerToolConfig = Schema.Struct({ + "instructions": Schema.optionalKey( + Schema.String.annotate({ + "description": + "System instructions for the subagent. When omitted, the subagent responds with no system prompt of its own." + }) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of output tokens (including reasoning) the subagent may produce. When omitted, the provider's default applies." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of tool-calling steps the subagent may take during its agentic loop. Capped at 25. Only relevant when the subagent is given tools. Accepted and validated but not yet enforced on the subagent call." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(25).annotate({ "expected": "a value less than or equal to 25" })) + ), + "model": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Slug of the model that executes delegated tasks (any OpenRouter model). Typically a smaller, cheaper, faster model than the one delegating. When omitted, the model from the outer API request is used. The subagent tool itself cannot be the subagent model." + }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Optional name for this subagent. The model sees one tool per named subagent (and one default for an unnamed entry). Names must be unique across subagent entries. Letters, digits, spaces, underscores, and dashes; trimmed; 1–64 chars." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" }) + ).check( + Schema.isPattern(new RegExp("^[a-zA-Z0-9 _-]+$")).annotate({ + "expected": "a string matching the RegExp ^[a-zA-Z0-9 _-]+$" + }) + ) + ), + "reasoning": Schema.optionalKey(SubagentReasoning), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Sampling temperature forwarded to the subagent call. When omitted, the provider's default applies.", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "tools": Schema.optionalKey(Arrays_12) +}).annotate({ + "description": "Configuration for one openrouter:subagent server tool entry.", + "identifier": "SubagentServerToolConfig" +}) +export type ImageModelEndpointsResponse = { readonly "endpoints": ReadonlyArray; readonly "id": string } +export const ImageModelEndpointsResponse = Schema.Struct({ + "endpoints": Schema.Array(ImageEndpoint), + "id": Schema.String.annotate({ "description": "Model slug" }) +}).annotate({ + "description": "The full per-endpoint records for an image model.", + "identifier": "ImageModelEndpointsResponse" +}) +export type TaskClassificationResponse = { + readonly "data": { + readonly "as_of": string + readonly "classifications": ReadonlyArray + readonly "macro_categories": ReadonlyArray + readonly "window_days": number + } +} +export const TaskClassificationResponse = Schema.Struct({ + "data": Schema.Struct({ + "as_of": Schema.String.annotate({ + "description": + "UTC date (YYYY-MM-DD) of the window upper bound (yesterday). Data is exclusive of the current incomplete UTC day. This is the expected latest date in the snapshot; it does not confirm data presence for that date." + }), + "classifications": Schema.Array(TaskClassificationItem).annotate({ + "description": "Per-task classification market-share data, sorted by usage_share descending." + }), + "macro_categories": Schema.Array(TaskClassificationMacroCategory).annotate({ + "description": "Aggregate market-share data per macro-category (code, data, agent, general)." + }), + "window_days": Schema.Number.annotate({ "description": "Number of trailing days covered by this snapshot." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }) +}).annotate({ "identifier": "TaskClassificationResponse" }) +export type UnifiedBenchmarksResponse = { + readonly "data": ReadonlyArray + readonly "meta": UnifiedBenchmarksMeta +} +export const UnifiedBenchmarksResponse = Schema.Struct({ + "data": Schema.Array(Schema.Union([UnifiedBenchmarksAAItem, UnifiedBenchmarksDAItem], { mode: "oneOf" })), + "meta": UnifiedBenchmarksMeta +}).annotate({ "identifier": "UnifiedBenchmarksResponse" }) +export type AnnotationAddedEvent = { + readonly "annotation": OpenAIResponsesAnnotation + readonly "annotation_index": number + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_text.annotation.added" +} +export const AnnotationAddedEvent = Schema.Struct({ + "annotation": OpenAIResponsesAnnotation, + "annotation_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.annotation.added") +}).annotate({ + "description": "Event emitted when a text annotation is added to output", + "identifier": "AnnotationAddedEvent" +}) +export type BaseAnnotationAddedEvent = { + readonly "annotation": OpenAIResponsesAnnotation + readonly "annotation_index": number + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_text.annotation.added" +} +export const BaseAnnotationAddedEvent = Schema.Struct({ + "annotation": OpenAIResponsesAnnotation, + "annotation_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_text.annotation.added") +}).annotate({ + "description": "Event emitted when a text annotation is added to output", + "identifier": "BaseAnnotationAddedEvent" +}) +export type ResponseOutputText = { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": "output_text" +} +export const ResponseOutputText = Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Literal("output_text") +}).annotate({ "identifier": "ResponseOutputText" }) +export type WebFetchServerTool = { + readonly "parameters"?: WebFetchServerToolConfig + readonly "type": "openrouter:web_fetch" +} +export const WebFetchServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(WebFetchServerToolConfig), + "type": Schema.Literal("openrouter:web_fetch") +}).annotate({ + "description": "OpenRouter built-in server tool: fetches full content from a URL (web page or PDF)", + "identifier": "WebFetchServerTool" +}) +export type Preview_20250311_WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search_preview_2025_03_11" + readonly "user_location"?: Preview_WebSearchUserLocation +} +export const Preview_20250311_WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search_preview_2025_03_11"), + "user_location": Schema.optionalKey(Preview_WebSearchUserLocation) +}).annotate({ + "description": "Web search preview tool configuration (2025-03-11 version)", + "identifier": "Preview_20250311_WebSearchServerTool" +}) +export type Preview_WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search_preview" + readonly "user_location"?: Preview_WebSearchUserLocation +} +export const Preview_WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search_preview"), + "user_location": Schema.optionalKey(Preview_WebSearchUserLocation) +}).annotate({ "description": "Web search preview tool configuration", "identifier": "Preview_WebSearchServerTool" }) +export type Legacy_WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search" + readonly "user_location"?: WebSearchUserLocation +} +export const Legacy_WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search"), + "user_location": Schema.optionalKey(WebSearchUserLocation) +}).annotate({ "description": "Web search tool configuration", "identifier": "Legacy_WebSearchServerTool" }) +export type WebSearchServerTool = { + readonly "engine"?: WebSearchEngineEnum + readonly "filters"?: WebSearchDomainFilter + readonly "max_results"?: number + readonly "search_context_size"?: SearchContextSizeEnum + readonly "type": "web_search_2025_08_26" + readonly "user_location"?: WebSearchUserLocation +} +export const WebSearchServerTool = Schema.Struct({ + "engine": Schema.optionalKey(WebSearchEngineEnum), + "filters": Schema.optionalKey(WebSearchDomainFilter), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "search_context_size": Schema.optionalKey(SearchContextSizeEnum), + "type": Schema.Literal("web_search_2025_08_26"), + "user_location": Schema.optionalKey(WebSearchUserLocation) +}).annotate({ + "description": "Web search tool configuration (2025-08-26 version)", + "identifier": "WebSearchServerTool" +}) +export type ChatWebSearchShorthand = { + readonly "allowed_domains"?: ReadonlyArray + readonly "engine"?: WebSearchEngineEnum + readonly "excluded_domains"?: ReadonlyArray + readonly "max_characters"?: number + readonly "max_results"?: number + readonly "max_total_results"?: number + readonly "parameters"?: WebSearchConfig + readonly "search_context_size"?: SearchQualityLevel + readonly "type": "web_search" | "web_search_preview" | "web_search_preview_2025_03_11" | "web_search_2025_08_26" + readonly "user_location"?: WebSearchUserLocationServerTool +} +export const ChatWebSearchShorthand = Schema.Struct({ + "allowed_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Limit search results to these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, and most native providers (Anthropic, OpenAI, xAI). Cannot be used with excluded_domains." + }) + ), + "engine": Schema.optionalKey(WebSearchEngineEnum), + "excluded_domains": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Exclude search results from these domains. Supported by Exa, Firecrawl, Parallel, Perplexity, Anthropic, and xAI. Not supported with OpenAI (silently ignored). Cannot be used with allowed_domains." + }) + ), + "max_characters": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Exact maximum number of characters of content per search result. Applies to the Exa, Parallel, and Perplexity engines; ignored with native provider search and Firecrawl. For Exa, caps highlight content per result. For Parallel, caps excerpt content per result (default 1,500 when omitted). For Perplexity, maps to the native `max_tokens_per_page` parameter (converted from characters to tokens) and trims the response to the exact character cap. When both `max_characters` and `search_context_size` are set, `max_characters` takes precedence. When omitted, falls back to `search_context_size` mapping (Exa) or engine defaults (Parallel, Perplexity)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, Parallel, and Perplexity engines; ignored with native provider search. Perplexity supports a maximum of 20; values above 20 are clamped." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "max_total_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total number of search results across all search calls in a single request. Once this limit is reached, the tool will stop returning new results. Useful for controlling cost and context size in agentic loops. Defaults to 50 when not specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "parameters": Schema.optionalKey(WebSearchConfig), + "search_context_size": Schema.optionalKey(SearchQualityLevel), + "type": Schema.Literals([ + "web_search", + "web_search_preview", + "web_search_preview_2025_03_11", + "web_search_2025_08_26" + ]), + "user_location": Schema.optionalKey(WebSearchUserLocationServerTool) +}).annotate({ + "description": "Web search tool using OpenAI Responses API syntax. Automatically converted to openrouter:web_search.", + "identifier": "ChatWebSearchShorthand" +}) +export type OpenRouterWebSearchServerTool = { + readonly "parameters"?: WebSearchConfig + readonly "type": "openrouter:web_search" +} +export const OpenRouterWebSearchServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(WebSearchConfig), + "type": Schema.Literal("openrouter:web_search") +}).annotate({ + "description": "OpenRouter built-in server tool: searches the web for current information", + "identifier": "OpenRouterWebSearchServerTool" +}) +export type WebSearchServerTool_OpenRouter = { + readonly "parameters"?: WebSearchServerToolConfig + readonly "type": "openrouter:web_search" +} +export const WebSearchServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(WebSearchServerToolConfig), + "type": Schema.Literal("openrouter:web_search") +}).annotate({ + "description": "OpenRouter built-in server tool: searches the web for current information", + "identifier": "WebSearchServerTool_OpenRouter" +}) +export type AdvisorServerTool_OpenRouter = { + readonly "parameters"?: AdvisorServerToolConfig + readonly "type": "openrouter:advisor" +} +export const AdvisorServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(AdvisorServerToolConfig), + "type": Schema.Literal("openrouter:advisor") +}).annotate({ + "description": + "OpenRouter built-in server tool: consults a higher-intelligence advisor model (any OpenRouter model) for guidance mid-generation and returns its response. The advisor may run as a sub-agent with its own tools. Include multiple entries to offer several named advisors; at most one entry may omit `name` to act as the default advisor.", + "identifier": "AdvisorServerTool_OpenRouter" +}) +export type AnthropicBashCodeExecutionToolResult = { + readonly "content": AnthropicBashCodeExecutionContent + readonly "tool_use_id": string + readonly "type": "bash_code_execution_tool_result" +} +export const AnthropicBashCodeExecutionToolResult = Schema.Struct({ + "content": AnthropicBashCodeExecutionContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("bash_code_execution_tool_result") +}).annotate({ "identifier": "AnthropicBashCodeExecutionToolResult" }) +export type AnthropicSearchResultBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "citations"?: { readonly "enabled"?: boolean } + readonly "content": ReadonlyArray + readonly "source": string + readonly "title": string + readonly "type": "search_result" +} +export const AnthropicSearchResultBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "citations": Schema.optionalKey(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) })), + "content": Schema.Array(AnthropicTextBlockParam), + "source": Schema.String, + "title": Schema.String, + "type": Schema.Literal("search_result") +}).annotate({ "identifier": "AnthropicSearchResultBlockParam" }) +export type FusionServerTool_OpenRouter = { + readonly "parameters"?: FusionServerToolConfig + readonly "type": "openrouter:fusion" +} +export const FusionServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(FusionServerToolConfig), + "type": Schema.Literal("openrouter:fusion") +}).annotate({ + "description": + "OpenRouter built-in server tool: fans out the user prompt to a panel of analysis models, then asks a judge model to summarize their collective output as structured JSON the outer model can synthesize from.", + "identifier": "FusionServerTool_OpenRouter" +}) +export type AnthropicWebFetchBlock = { + readonly "content": AnthropicDocumentBlock + readonly "retrieved_at": string | null + readonly "type": "web_fetch_result" + readonly "url": string +} +export const AnthropicWebFetchBlock = Schema.Struct({ + "content": AnthropicDocumentBlock, + "retrieved_at": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("web_fetch_result"), + "url": Schema.String +}).annotate({ "identifier": "AnthropicWebFetchBlock" }) +export type AnthropicDocumentBlockParam = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "citations"?: { readonly "enabled"?: boolean; readonly [x: string]: Schema.Json } | null + readonly "context"?: string | null + readonly "source": + | AnthropicBase64PdfSource + | AnthropicPlainTextSource + | { + readonly "content": string | ReadonlyArray + readonly "type": "content" + } + | AnthropicUrlPdfSource + | AnthropicFileDocumentSource + readonly "title"?: string | null + readonly "type": "document" +} +export const AnthropicDocumentBlockParam = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "citations": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "enabled": Schema.optionalKey(Schema.Boolean) }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null + ]) + ), + "context": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "source": Schema.Union([ + AnthropicBase64PdfSource, + AnthropicPlainTextSource, + Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([AnthropicTextBlockParam, AnthropicImageBlockParam], { mode: "oneOf" })) + ]), + "type": Schema.Literal("content") + }), + AnthropicUrlPdfSource, + AnthropicFileDocumentSource + ], { mode: "oneOf" }), + "title": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("document") +}).annotate({ "identifier": "AnthropicDocumentBlockParam" }) +export type AnthropicUsageIteration = + | AnthropicCompactionUsageIteration + | AnthropicMessageUsageIteration + | AnthropicAdvisorMessageUsageIteration + | AnthropicUnknownUsageIteration +export const AnthropicUsageIteration = Schema.Union([ + AnthropicCompactionUsageIteration, + AnthropicMessageUsageIteration, + AnthropicAdvisorMessageUsageIteration, + AnthropicUnknownUsageIteration +]).annotate({ "identifier": "AnthropicUsageIteration" }) +export type AnthropicCodeExecutionToolResult = { + readonly "content": AnthropicCodeExecutionContent + readonly "tool_use_id": string + readonly "type": "code_execution_tool_result" +} +export const AnthropicCodeExecutionToolResult = Schema.Struct({ + "content": AnthropicCodeExecutionContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("code_execution_tool_result") +}).annotate({ "identifier": "AnthropicCodeExecutionToolResult" }) +export type AnthropicToolSearchToolResult = { + readonly "content": AnthropicToolSearchContent + readonly "tool_use_id": string + readonly "type": "tool_search_tool_result" +} +export const AnthropicToolSearchToolResult = Schema.Struct({ + "content": AnthropicToolSearchContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("tool_search_tool_result") +}).annotate({ "identifier": "AnthropicToolSearchToolResult" }) +export type BashServerTool = { readonly "parameters"?: BashServerToolConfig; readonly "type": "openrouter:bash" } +export const BashServerTool = Schema.Struct({ + "parameters": Schema.optionalKey(BashServerToolConfig), + "type": Schema.Literal("openrouter:bash") +}).annotate({ + "description": "OpenRouter built-in server tool: runs shell commands server-side in a sandboxed container", + "identifier": "BashServerTool" +}) +export type ShellServerTool_OpenRouter = { + readonly "parameters"?: ShellServerToolConfig + readonly "type": "openrouter:shell" +} +export const ShellServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(ShellServerToolConfig), + "type": Schema.Literal("openrouter:shell") +}).annotate({ + "description": + "OpenRouter built-in server tool: runs shell commands server-side in a sandboxed container (a sandbox-backed clone of OpenAI's hosted shell tool)", + "identifier": "ShellServerTool_OpenRouter" +}) +export type ImageModelsListResponse = { readonly "data": ReadonlyArray } +export const ImageModelsListResponse = Schema.Struct({ "data": Schema.Array(ImageModelListItem) }).annotate({ + "description": "List of image generation models.", + "identifier": "ImageModelsListResponse" +}) +export type ObservabilityDestination = + | ObservabilityArizeDestination + | ObservabilityBraintrustDestination + | ObservabilityClickhouseDestination + | ObservabilityDatadogDestination + | ObservabilityGrafanaDestination + | ObservabilityLangfuseDestination + | ObservabilityLangsmithDestination + | ObservabilityNewrelicDestination + | ObservabilityOpikDestination + | ObservabilityOtelCollectorDestination + | ObservabilityPosthogDestination + | ObservabilityRampDestination + | ObservabilityS3Destination + | ObservabilitySentryDestination + | ObservabilitySnowflakeDestination + | ObservabilityWeaveDestination + | ObservabilityWebhookDestination +export const ObservabilityDestination = Schema.Union([ + ObservabilityArizeDestination, + ObservabilityBraintrustDestination, + ObservabilityClickhouseDestination, + ObservabilityDatadogDestination, + ObservabilityGrafanaDestination, + ObservabilityLangfuseDestination, + ObservabilityLangsmithDestination, + ObservabilityNewrelicDestination, + ObservabilityOpikDestination, + ObservabilityOtelCollectorDestination, + ObservabilityPosthogDestination, + ObservabilityRampDestination, + ObservabilityS3Destination, + ObservabilitySentryDestination, + ObservabilitySnowflakeDestination, + ObservabilityWeaveDestination, + ObservabilityWebhookDestination +], { mode: "oneOf" }).annotate({ "identifier": "ObservabilityDestination" }) +export type CreateObservabilityDestinationRequest = { + readonly "api_key_hashes"?: ReadonlyArray | null + readonly "config": {} + readonly "enabled"?: boolean + readonly "filter_rules"?: ObservabilityFilterRulesConfigNullable + readonly "name": string + readonly "privacy_mode"?: boolean + readonly "sampling_rate"?: number + readonly "type": + | "arize" + | "braintrust" + | "clickhouse" + | "datadog" + | "grafana" + | "langfuse" + | "langsmith" + | "newrelic" + | "opik" + | "otel-collector" + | "posthog" + | "ramp" + | "s3" + | "sentry" + | "snowflake" + | "weave" + | "webhook" + readonly "workspace_id"?: string +} +export const CreateObservabilityDestinationRequest = Schema.Struct({ + "api_key_hashes": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes whose traffic is forwarded. `null` or omitted means all keys. Must contain at least one hash if provided." + }) + ), + "config": Schema.Struct({}).annotate({ + "description": "Provider-specific configuration. The shape depends on `type` and is validated server-side." + }), + "enabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether this destination should be enabled immediately." }) + ), + "filter_rules": Schema.optionalKey(ObservabilityFilterRulesConfigNullable), + "name": Schema.String.annotate({ "description": "Human-readable name for the destination." }), + "privacy_mode": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "When true, request/response bodies are not forwarded — only metadata." }) + ), + "sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate between 0.0001 and 1 (1 = 100%).", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "type": Schema.Literals([ + "arize", + "braintrust", + "clickhouse", + "datadog", + "grafana", + "langfuse", + "langsmith", + "newrelic", + "opik", + "otel-collector", + "posthog", + "ramp", + "s3", + "sentry", + "snowflake", + "weave", + "webhook" + ]).annotate({ "description": "The destination type. Only stable destination types are accepted." }), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ) +}).annotate({ "identifier": "CreateObservabilityDestinationRequest" }) +export type UpdateObservabilityDestinationRequest = { + readonly "api_key_hashes"?: ReadonlyArray | null + readonly "config"?: {} + readonly "enabled"?: boolean + readonly "filter_rules"?: ObservabilityFilterRulesConfigNullable + readonly "name"?: string + readonly "privacy_mode"?: boolean + readonly "sampling_rate"?: number +} +export const UpdateObservabilityDestinationRequest = Schema.Struct({ + "api_key_hashes": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + Schema.Null + ]).annotate({ + "description": + "Optional allowlist of OpenRouter API key hashes. `null` clears the filter (all keys). Omitting leaves the current value. Must contain at least one hash if provided." + }) + ), + "config": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": + "Provider-specific configuration fields to update. Masked values are ignored; unset fields keep their current value." + }) + ), + "enabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether the destination is enabled." })), + "filter_rules": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ObservabilityFilterRulesConfigNullable) + .annotate({ + "description": "Optional structured filter rules. `null` clears the rules. Omitting keeps the current value." + }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Human-readable name for the destination." })), + "privacy_mode": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "When true, request/response bodies are not forwarded — only metadata." }) + ), + "sampling_rate": Schema.optionalKey( + Schema.Number.annotate({ "description": "Sampling rate between 0.0001 and 1 (1 = 100%).", "format": "double" }) + .check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ) +}).annotate({ "identifier": "UpdateObservabilityDestinationRequest" }) +export type ProviderPreferences = Objects_148 | null +export const ProviderPreferences = Schema.Union([Objects_148, Schema.Null]).annotate({ + "description": "When multiple model providers are available, optionally indicate your routing preference.", + "identifier": "ProviderPreferences" +}) +export type OpenRouterMetadata = { + readonly "attempt": number + readonly "attempts"?: Arrays_10 + readonly "endpoints": EndpointsMetadata + readonly "is_byok": boolean + readonly "params"?: RouterParams + readonly "pipeline"?: Arrays_11 + readonly "region": string | null + readonly "requested": string + readonly "strategy": RoutingStrategy + readonly "summary": string +} +export const OpenRouterMetadata = Schema.Struct({ + "attempt": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "attempts": Schema.optionalKey(Arrays_10), + "endpoints": EndpointsMetadata, + "is_byok": Schema.Boolean, + "params": Schema.optionalKey(RouterParams), + "pipeline": Schema.optionalKey(Arrays_11), + "region": Schema.Union([Schema.String, Schema.Null]), + "requested": Schema.String, + "strategy": RoutingStrategy, + "summary": Schema.String +}).annotate({ "identifier": "OpenRouterMetadata" }) +export type ChatContentItems = + | ChatContentText + | ChatContentImage + | ChatContentAudio + | Legacy_ChatContentVideo + | ChatContentVideo + | ChatContentFile +export const ChatContentItems = Schema.Union([ + ChatContentText, + ChatContentImage, + ChatContentAudio, + Legacy_ChatContentVideo, + ChatContentVideo, + ChatContentFile +], { mode: "oneOf" }).annotate({ + "description": "Content part for chat completion messages", + "identifier": "ChatContentItems" +}) +export type ChatDeveloperMessage = { + readonly "content": string | ReadonlyArray + readonly "name"?: string + readonly "role": "developer" +} +export const ChatDeveloperMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentText)]).annotate({ + "description": "Developer message content" + }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the developer message" })), + "role": Schema.Literal("developer") +}).annotate({ "description": "Developer message", "identifier": "ChatDeveloperMessage" }) +export type ChatSystemMessage = { + readonly "content": string | ReadonlyArray + readonly "name"?: string + readonly "role": "system" +} +export const ChatSystemMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentText)]).annotate({ + "description": "System message content" + }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the system message" })), + "role": Schema.Literal("system") +}).annotate({ "description": "System message for setting behavior", "identifier": "ChatSystemMessage" }) +export type AgentMessageItem = { + readonly "agent"?: { readonly "agent_name": string; readonly [x: string]: Schema.Json } | null + readonly "author": string + readonly "content": ReadonlyArray< + InputText | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } | { readonly "encrypted_content": string; readonly "type": "encrypted_content" } + > + readonly "id"?: string | null + readonly "recipient": string + readonly "type": "agent_message" +} +export const AgentMessageItem = Schema.Struct({ + "agent": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest(Schema.Struct({ "agent_name": Schema.String }), [ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })) + ]), + Schema.Null + ]) + ), + "author": Schema.String, + "content": Schema.Array( + Schema.Union([ + InputText, + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + Schema.Struct({ "encrypted_content": Schema.String, "type": Schema.Literal("encrypted_content") }) + ], { mode: "oneOf" }) + ), + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "recipient": Schema.String, + "type": Schema.Literal("agent_message") +}).annotate({ + "description": "A message routed between agents in a multi-agent session", + "identifier": "AgentMessageItem" +}) +export type EasyInputMessage = { + readonly "content"?: + | ReadonlyArray< + | InputText + | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } + | InputFile + | InputAudio + | InputVideo + > + | string + | null + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "user" | "system" | "assistant" | "developer" + readonly "type"?: "message" +} +export const EasyInputMessage = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + InputText, + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + InputFile, + InputAudio, + InputVideo + ], { mode: "oneOf" }) + ), + Schema.String, + Schema.Null + ]) + ), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literals(["user", "system", "assistant", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) +}).annotate({ "identifier": "EasyInputMessage" }) +export type InputMessageItem = { + readonly "content"?: + | ReadonlyArray< + | InputText + | { + readonly "detail": "auto" | "high" | "low" | "original" + readonly "image_url"?: string | null + readonly "type": "input_image" + } + | InputFile + | InputAudio + | InputVideo + > + | null + readonly "id"?: string + readonly "role": "user" | "system" | "developer" + readonly "type"?: "message" +} +export const InputMessageItem = Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Union([ + InputText, + Schema.Struct({ + "detail": Schema.Literals(["auto", "high", "low", "original"]), + "image_url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "type": Schema.Literal("input_image") + }).annotate({ "description": "Image input content item" }), + InputFile, + InputAudio, + InputVideo + ], { mode: "oneOf" }) + ), + Schema.Null + ]) + ), + "id": Schema.optionalKey(Schema.String), + "role": Schema.Literals(["user", "system", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) +}).annotate({ "identifier": "InputMessageItem" }) +export type OpenAIResponseCustomToolCallOutput = { + readonly "call_id": string + readonly "id"?: string + readonly "output": string | ReadonlyArray + readonly "type": "custom_tool_call_output" +} +export const OpenAIResponseCustomToolCallOutput = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "output": Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([InputText, InputImage, InputFile], { mode: "oneOf" })) + ]), + "type": Schema.Literal("custom_tool_call_output") +}).annotate({ "identifier": "OpenAIResponseCustomToolCallOutput" }) +export type OpenAIResponseFunctionToolCallOutput = { + readonly "call_id": string + readonly "id"?: string | null + readonly "output": string | ReadonlyArray + readonly "status"?: ToolCallStatus | null + readonly "type": "function_call_output" +} +export const OpenAIResponseFunctionToolCallOutput = Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "output": Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([InputText, InputImage, InputFile], { mode: "oneOf" })) + ]), + "status": Schema.optionalKey(Schema.Union([ToolCallStatus, Schema.Null])), + "type": Schema.Literal("function_call_output") +}).annotate({ "identifier": "OpenAIResponseFunctionToolCallOutput" }) +export type OpenAIResponseInputMessageItem = { + readonly "content": ReadonlyArray + readonly "id": string + readonly "role": "user" | "system" | "developer" + readonly "type"?: "message" +} +export const OpenAIResponseInputMessageItem = Schema.Struct({ + "content": Schema.Array(Schema.Union([InputText, InputImage, InputFile, InputAudio], { mode: "oneOf" })), + "id": Schema.String, + "role": Schema.Literals(["user", "system", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) +}).annotate({ "identifier": "OpenAIResponseInputMessageItem" }) +export type Objects_151 = { + readonly "id": string + readonly "variables"?: { readonly [x: string]: string | InputText | InputImage | InputFile } | null + readonly [x: string]: Schema.Json +} +export const Objects_151 = Schema.StructWithRest( + Schema.Struct({ + "id": Schema.String, + "variables": Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, Schema.Union([Schema.String, InputText, InputImage, InputFile])), + Schema.Null + ]) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] +) +export type CreateGuardrailResponse = { readonly "data": Guardrail } +export const CreateGuardrailResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Guardrail).annotate({ "description": "The created guardrail" }) +}).annotate({ "identifier": "CreateGuardrailResponse" }) +export type GetGuardrailResponse = { readonly "data": Guardrail } +export const GetGuardrailResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Guardrail).annotate({ "description": "The guardrail" }) +}).annotate({ "identifier": "GetGuardrailResponse" }) +export type ListGuardrailsResponse = { readonly "data": ReadonlyArray; readonly "total_count": number } +export const ListGuardrailsResponse = Schema.Struct({ + "data": Schema.Array(Guardrail).annotate({ "description": "List of guardrails" }), + "total_count": Schema.Number.annotate({ "description": "Total number of guardrails" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListGuardrailsResponse" }) +export type UpdateGuardrailResponse = { readonly "data": Guardrail } +export const UpdateGuardrailResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => Guardrail).annotate({ "description": "The updated guardrail" }) +}).annotate({ "identifier": "UpdateGuardrailResponse" }) +export type ImageGenerationRequest = { + readonly "aspect_ratio"?: + | "1:1" + | "1:2" + | "1:4" + | "1:8" + | "2:1" + | "2:3" + | "3:2" + | "3:4" + | "4:1" + | "4:3" + | "4:5" + | "5:4" + | "8:1" + | "9:16" + | "16:9" + | "9:19.5" + | "19.5:9" + | "9:20" + | "20:9" + | "9:21" + | "21:9" + | "auto" + readonly "background"?: "auto" | "transparent" | "opaque" + readonly "input_references"?: ReadonlyArray + readonly "model": string + readonly "n"?: number + readonly "output_compression"?: number + readonly "output_format"?: "png" | "jpeg" | "webp" | "svg" + readonly "prompt": string + readonly "provider"?: ImageGenerationProviderPreferences + readonly "quality"?: "auto" | "low" | "medium" | "high" + readonly "resolution"?: "512" | "1K" | "2K" | "4K" + readonly "seed"?: number + readonly "size"?: string + readonly "stream"?: boolean +} +export const ImageGenerationRequest = Schema.Struct({ + "aspect_ratio": Schema.optionalKey( + Schema.Literals([ + "1:1", + "1:2", + "1:4", + "1:8", + "2:1", + "2:3", + "3:2", + "3:4", + "4:1", + "4:3", + "4:5", + "5:4", + "8:1", + "9:16", + "16:9", + "9:19.5", + "19.5:9", + "9:20", + "20:9", + "9:21", + "21:9", + "auto" + ]).annotate({ + "description": "Normalized aspect ratio of the generated image. Providers clamp to their supported subset." + }) + ), + "background": Schema.optionalKey( + Schema.Literals(["auto", "transparent", "opaque"]).annotate({ + "description": "Background treatment. `transparent` requires an output_format that supports alpha (png or webp)." + }) + ), + "input_references": Schema.optionalKey( + Schema.Array(ContentPartImage).annotate({ + "description": "Reference images to guide image-to-image generation, as base64 data URLs or HTTP(S) URLs." + }).check(Schema.isMaxLength(16).annotate({ "expected": "a value with a length of at most 16" })) + ), + "model": Schema.String.annotate({ "description": "The image generation model to use" }), + "n": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Number of images to generate (1-10). Providers that only support single-image generation reject n > 1." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_compression": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Compression level (0-100) for webp/jpeg output. Ignored for png and by providers without a compression knob." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_format": Schema.optionalKey( + Schema.Literals(["png", "jpeg", "webp", "svg"]).annotate({ + "description": + "Encoding of the returned image bytes. Most models produce raster formats (png, jpeg, webp). SVG is supported by vectorization models (e.g. Quiver) — the SVG markup is UTF-8 base64-encoded in `b64_json`." + }) + ), + "prompt": Schema.String.annotate({ "description": "Text description of the desired image" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "provider": Schema.optionalKey(ImageGenerationProviderPreferences), + "quality": Schema.optionalKey( + Schema.Literals(["auto", "low", "medium", "high"]).annotate({ + "description": "Rendering quality. Providers without a quality knob ignore this." + }) + ), + "resolution": Schema.optionalKey( + Schema.Literals(["512", "1K", "2K", "4K"]).annotate({ + "description": + "Normalized resolution tier of the generated image. Concrete pixel dimensions are derived per-provider." + }) + ), + "seed": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "If specified, the generation will sample deterministically, such that repeated requests with the same seed and parameters should return the same result. Determinism is not guaranteed for all providers." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "size": Schema.optionalKey(Schema.String.annotate({ + "description": + "Optional. A convenience shorthand for output dimensions — pass a tier (\"2K\", \"4K\") or explicit pixels (\"2048x2048\") and we normalize it to the right dimensions for the chosen provider. A tier size is equivalent to setting `resolution` and combines with `aspect_ratio`. An explicit pixel size is authoritative: a mismatched `resolution` or `aspect_ratio` alongside it is rejected with a 400." + })), + "stream": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "If true, partial images are streamed as SSE events as they become available. Only supported by providers with native streaming (currently OpenAI). Non-streaming providers ignore this flag and return a buffered response." + }) + ) +}).annotate({ "description": "Image generation request input", "identifier": "ImageGenerationRequest" }) +export type Model = { + readonly "architecture": ModelArchitecture + readonly "benchmarks"?: ModelBenchmarks + readonly "canonical_slug": string + readonly "context_length": number | null + readonly "created": number + readonly "default_parameters": DefaultParameters + readonly "description"?: string + readonly "expiration_date"?: string | null + readonly "hugging_face_id"?: string | null + readonly "id": string + readonly "knowledge_cutoff"?: string | null + readonly "links": ModelLinks + readonly "name": string + readonly "per_request_limits": PerRequestLimits + readonly "pricing": PublicPricing + readonly "reasoning"?: ModelReasoning + readonly "supported_parameters": ReadonlyArray + readonly "supported_voices": ReadonlyArray | null + readonly "top_provider": TopProviderInfo +} +export const Model = Schema.Struct({ + "architecture": ModelArchitecture, + "benchmarks": Schema.optionalKey(ModelBenchmarks), + "canonical_slug": Schema.String.annotate({ "description": "Canonical slug for the model" }), + "context_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]).annotate({ "description": "Maximum context length in tokens" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of when the model was created" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "default_parameters": DefaultParameters, + "description": Schema.optionalKey(Schema.String.annotate({ "description": "Description of the model" })), + "expiration_date": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The date after which the model may be removed. ISO 8601 date string (YYYY-MM-DD) or null if no expiration." + }) + ), + "hugging_face_id": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Hugging Face model identifier, if applicable" + }) + ), + "id": Schema.String.annotate({ "description": "Unique identifier for the model" }), + "knowledge_cutoff": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The date up to which the model was trained on data. ISO 8601 date string (YYYY-MM-DD) or null if unknown." + }) + ), + "links": ModelLinks, + "name": Schema.String.annotate({ "description": "Display name of the model" }), + "per_request_limits": PerRequestLimits, + "pricing": PublicPricing, + "reasoning": Schema.optionalKey(ModelReasoning), + "supported_parameters": Schema.Array(Parameter).annotate({ + "description": "List of supported parameters for this model" + }), + "supported_voices": Schema.Union([Schema.Array(Schema.String), Schema.Null]).annotate({ + "description": "List of supported voice identifiers for TTS models. Null for non-TTS models." + }), + "top_provider": TopProviderInfo +}).annotate({ "description": "Information about an AI model available on OpenRouter", "identifier": "Model" }) +export type ChatReasoningDetails = ReadonlyArray +export const ChatReasoningDetails = Schema.Array(ReasoningDetailUnion).annotate({ + "description": "Reasoning details for extended thinking models", + "identifier": "ChatReasoningDetails" +}) +export type ChatStreamReasoningDetails = ReadonlyArray +export const ChatStreamReasoningDetails = Schema.Array(ReasoningDetailUnion).annotate({ + "description": "Reasoning details for extended thinking models", + "identifier": "ChatStreamReasoningDetails" +}) +export type SubagentServerTool_OpenRouter = { + readonly "parameters"?: SubagentServerToolConfig + readonly "type": "openrouter:subagent" +} +export const SubagentServerTool_OpenRouter = Schema.Struct({ + "parameters": Schema.optionalKey(SubagentServerToolConfig), + "type": Schema.Literal("openrouter:subagent") +}).annotate({ + "description": + "OpenRouter built-in server tool: delegates self-contained tasks to a smaller, cheaper, faster worker model (any OpenRouter model) mid-generation and returns its outcome. The worker may run as a sub-agent with its own tools.", + "identifier": "SubagentServerTool_OpenRouter" +}) +export type BaseContentPartAddedEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.added" +} +export const BaseContentPartAddedEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.added") +}).annotate({ + "description": "Event emitted when a new content part is added to an output item", + "identifier": "BaseContentPartAddedEvent" +}) +export type BaseContentPartDoneEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.done" +} +export const BaseContentPartDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.done") +}).annotate({ + "description": "Event emitted when a content part is complete", + "identifier": "BaseContentPartDoneEvent" +}) +export type ContentPartAddedEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | ReasoningTextContent | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.added" +} +export const ContentPartAddedEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, ReasoningTextContent, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.added") +}).annotate({ + "description": "Event emitted when a new content part is added to an output item", + "identifier": "ContentPartAddedEvent" +}) +export type ContentPartDoneEvent = { + readonly "content_index": number + readonly "item_id": string + readonly "output_index": number + readonly "part": ResponseOutputText | ReasoningTextContent | OpenAIResponsesRefusalContent + readonly "sequence_number": number + readonly "type": "response.content_part.done" +} +export const ContentPartDoneEvent = Schema.Struct({ + "content_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "item_id": Schema.String, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "part": Schema.Union([ResponseOutputText, ReasoningTextContent, OpenAIResponsesRefusalContent]), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.content_part.done") +}).annotate({ "description": "Event emitted when a content part is complete", "identifier": "ContentPartDoneEvent" }) +export type OutputMessage = { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" +} +export const OutputMessage = Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("message") +}).annotate({ "identifier": "OutputMessage" }) +export type OutputMessageItem = { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" +} +export const OutputMessageItem = Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("message") +}).annotate({ "description": "An output message item", "identifier": "OutputMessageItem" }) +export type AnthropicWebFetchContent = AnthropicWebFetchToolResultError | AnthropicWebFetchBlock +export const AnthropicWebFetchContent = Schema.Union([AnthropicWebFetchToolResultError, AnthropicWebFetchBlock], { + mode: "oneOf" +}).annotate({ "identifier": "AnthropicWebFetchContent" }) +export type MessagesMessageParam = { + readonly "content": + | string + | ReadonlyArray< + | AnthropicTextBlockParam + | AnthropicImageBlockParam + | AnthropicDocumentBlockParam + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "tool_use" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "content"?: + | string + | ReadonlyArray< + | AnthropicTextBlockParam + | AnthropicImageBlockParam + | { readonly "tool_name": string; readonly "type": "tool_reference" } + | AnthropicSearchResultBlockParam + | AnthropicDocumentBlockParam + > + readonly "is_error"?: boolean + readonly "tool_use_id": string + readonly "type": "tool_result" + } + | { readonly "signature": string; readonly "thinking": string; readonly "type": "thinking" } + | { readonly "data": string; readonly "type": "redacted_thinking" } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "id": string + readonly "input"?: Schema.Json + readonly "name": string + readonly "type": "server_tool_use" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "content": ReadonlyArray | { + readonly "error_code": + | "invalid_tool_input" + | "unavailable" + | "max_uses_exceeded" + | "too_many_requests" + | "query_too_long" + readonly "type": "web_search_tool_result_error" + } + readonly "tool_use_id": string + readonly "type": "web_search_tool_result" + } + | AnthropicSearchResultBlockParam + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "content": string | null + readonly "type": "compaction" + } + | MessagesAdvisorToolResultBlock + > + readonly "role": "user" | "assistant" | "system" +} +export const MessagesMessageParam = Schema.Struct({ + "content": Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([ + AnthropicTextBlockParam, + AnthropicImageBlockParam, + AnthropicDocumentBlockParam, + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("tool_use") + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "content": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array( + Schema.Union([ + AnthropicTextBlockParam, + AnthropicImageBlockParam, + Schema.Struct({ "tool_name": Schema.String, "type": Schema.Literal("tool_reference") }), + AnthropicSearchResultBlockParam, + AnthropicDocumentBlockParam + ]) + ) + ]) + ), + "is_error": Schema.optionalKey(Schema.Boolean), + "tool_use_id": Schema.String, + "type": Schema.Literal("tool_result") + }), + Schema.Struct({ "signature": Schema.String, "thinking": Schema.String, "type": Schema.Literal("thinking") }), + Schema.Struct({ "data": Schema.String, "type": Schema.Literal("redacted_thinking") }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "id": Schema.String, + "input": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "name": Schema.String, + "type": Schema.Literal("server_tool_use") + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "content": Schema.Union([ + Schema.Array(AnthropicWebSearchResultBlockParam), + Schema.Struct({ + "error_code": Schema.Literals([ + "invalid_tool_input", + "unavailable", + "max_uses_exceeded", + "too_many_requests", + "query_too_long" + ]), + "type": Schema.Literal("web_search_tool_result_error") + }) + ]), + "tool_use_id": Schema.String, + "type": Schema.Literal("web_search_tool_result") + }), + AnthropicSearchResultBlockParam, + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "content": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("compaction") + }), + MessagesAdvisorToolResultBlock + ], { mode: "oneOf" }) + ) + ]), + "role": Schema.Literals(["user", "assistant", "system"]) +}).annotate({ "description": "Anthropic message with OpenRouter extensions", "identifier": "MessagesMessageParam" }) +export type Union_7 = ReadonlyArray | null +export const Union_7 = Schema.Union([Schema.Array(AnthropicUsageIteration), Schema.Null]) +export type MessagesDeltaEvent = { + readonly "delta": { + readonly "container": AnthropicContainer + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": ORAnthropicStopReason + readonly "stop_sequence": string | null + } + readonly "type": "message_delta" + readonly "usage": { + readonly "cache_creation"?: AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "input_tokens": number | null + readonly "iterations"?: ReadonlyArray + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": { + readonly "web_fetch_requests": number + readonly "web_search_requests": number + readonly [x: string]: Schema.Json + } | null + } +} +export const MessagesDeltaEvent = Schema.Struct({ + "delta": Schema.Struct({ + "container": AnthropicContainer, + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": ORAnthropicStopReason, + "stop_sequence": Schema.Union([Schema.String, Schema.Null]) + }), + "type": Schema.Literal("message_delta"), + "usage": Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicCacheCreation), + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "iterations": Schema.optionalKey(Schema.Array(AnthropicUsageIteration)), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "web_fetch_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "web_search_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null + ]) + }) +}).annotate({ + "description": "Event sent when the message metadata changes (e.g., stop_reason)", + "identifier": "MessagesDeltaEvent" +}) +export type CreateObservabilityDestinationResponse = { readonly "data": ObservabilityDestination } +export const CreateObservabilityDestinationResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => ObservabilityDestination).annotate({ + "description": "The newly created observability destination." + }) +}).annotate({ "identifier": "CreateObservabilityDestinationResponse" }) +export type GetObservabilityDestinationResponse = { readonly "data": ObservabilityDestination } +export const GetObservabilityDestinationResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => ObservabilityDestination).annotate({ + "description": "The observability destination." + }) +}).annotate({ "identifier": "GetObservabilityDestinationResponse" }) +export type ListObservabilityDestinationsResponse = { + readonly "data": ReadonlyArray + readonly "total_count": number +} +export const ListObservabilityDestinationsResponse = Schema.Struct({ + "data": Schema.Array(ObservabilityDestination).annotate({ "description": "List of observability destinations." }), + "total_count": Schema.Number.annotate({ "description": "Total number of destinations matching the filters." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "identifier": "ListObservabilityDestinationsResponse" }) +export type UpdateObservabilityDestinationResponse = { readonly "data": ObservabilityDestination } +export const UpdateObservabilityDestinationResponse = Schema.Struct({ + "data": Schema.suspend((): Schema.Codec => ObservabilityDestination).annotate({ + "description": "The updated observability destination." + }) +}).annotate({ "identifier": "UpdateObservabilityDestinationResponse" }) +export type MessagesErrorEvent = { + readonly "error": { readonly "error_type"?: ApiErrorType; readonly "message": string; readonly "type": string } + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "type": "error" +} +export const MessagesErrorEvent = Schema.Struct({ + "error": Schema.Struct({ + "error_type": Schema.optionalKey(ApiErrorType), + "message": Schema.String, + "type": Schema.String + }), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "type": Schema.Literal("error") +}).annotate({ "description": "Error event in the stream", "identifier": "MessagesErrorEvent" }) +export type MessagesStopEvent = { readonly "openrouter_metadata"?: OpenRouterMetadata; readonly "type": "message_stop" } +export const MessagesStopEvent = Schema.Struct({ + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "type": Schema.Literal("message_stop") +}).annotate({ "description": "Event sent when the message is complete", "identifier": "MessagesStopEvent" }) +export type ChatToolMessage = { + readonly "content": string | ReadonlyArray + readonly "role": "tool" + readonly "tool_call_id": string +} +export const ChatToolMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentItems)]).annotate({ + "description": "Tool response content" + }), + "role": Schema.Literal("tool"), + "tool_call_id": Schema.String.annotate({ + "description": "ID of the assistant message tool call this message responds to" + }) +}).annotate({ "description": "Tool response message", "identifier": "ChatToolMessage" }) +export type ChatUserMessage = { + readonly "content": string | ReadonlyArray + readonly "name"?: string + readonly "role": "user" +} +export const ChatUserMessage = Schema.Struct({ + "content": Schema.Union([Schema.String, Schema.Array(ChatContentItems)]).annotate({ + "description": "User message content" + }), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the user" })), + "role": Schema.Literal("user") +}).annotate({ "description": "User message", "identifier": "ChatUserMessage" }) +export type StoredPromptTemplate = Objects_151 | null +export const StoredPromptTemplate = Schema.Union([Objects_151, Schema.Null]).annotate({ + "identifier": "StoredPromptTemplate" +}) +export type ModelResponse = { readonly "data": Model } +export const ModelResponse = Schema.Struct({ "data": Model }).annotate({ + "description": "Single model response", + "identifier": "ModelResponse" +}) +export type ModelsListResponseData = ReadonlyArray +export const ModelsListResponseData = Schema.Array(Model).annotate({ + "description": "List of available models", + "identifier": "ModelsListResponseData" +}) +export type ChatAssistantMessage = { + readonly "audio"?: ChatAudioOutput + readonly "content"?: string | ReadonlyArray | null + readonly "images"?: + | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> + | null + readonly "model"?: string + readonly "name"?: string + readonly "reasoning"?: string | null + readonly "reasoning_details"?: ChatReasoningDetails + readonly "refusal"?: string | null + readonly "role": "assistant" + readonly "tool_calls"?: ReadonlyArray + readonly "annotations"?: + | ReadonlyArray< + { + readonly "type": "url_citation" + readonly "url_citation": { + readonly "url": string + readonly "title"?: string + readonly "start_index"?: number + readonly "end_index"?: number + readonly "content"?: string + } + } | { + readonly "type": "file_annotation" + readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } + } | { + readonly "type": "file" + readonly "file": { + readonly "hash": string + readonly "name": string + readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> + } + } + > + | null +} +export const ChatAssistantMessage = Schema.Struct({ + "audio": Schema.optionalKey(ChatAudioOutput), + "content": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Array(ChatContentItems), Schema.Null]).annotate({ + "description": "Assistant message content" + }) + ), + "images": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) + ), + Schema.Null + ]) + ), + "model": Schema.optionalKey(Schema.String.annotate({ "description": "Model that generated this assistant message" })), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "Optional name for the assistant" })), + "reasoning": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Reasoning output" }) + ), + "reasoning_details": Schema.optionalKey(ChatReasoningDetails), + "refusal": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Refusal message if content was refused" }) + ), + "role": Schema.Literal("assistant"), + "tool_calls": Schema.optionalKey( + Schema.Array(ChatToolCall).annotate({ "description": "Tool calls made by the assistant" }) + ), + "annotations": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("url_citation"), + "url_citation": Schema.Struct({ + "url": Schema.String, + "title": Schema.optionalKey(Schema.String), + "start_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "end_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "content": Schema.optionalKey(Schema.String) + }) + }), + Schema.Struct({ + "type": Schema.Literal("file_annotation"), + "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) + }), + Schema.Struct({ + "type": Schema.Literal("file"), + "file": Schema.Struct({ + "hash": Schema.String, + "name": Schema.String, + "content": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) + ) + }) + }) + ], { mode: "oneOf" })), + Schema.Null + ])) +}).annotate({ "description": "Assistant message for requests and responses", "identifier": "ChatAssistantMessage" }) +export type ChatStreamDelta = { + readonly "audio"?: ChatAudioOutput + readonly "content"?: string | null + readonly "reasoning"?: string | null + readonly "reasoning_details"?: ChatStreamReasoningDetails + readonly "refusal"?: string | null + readonly "role"?: "assistant" + readonly "tool_calls"?: ReadonlyArray + readonly "images"?: + | ReadonlyArray<{ readonly "type": "image_url"; readonly "image_url": { readonly "url": string } }> + | null + readonly "annotations"?: + | ReadonlyArray< + { + readonly "type": "url_citation" + readonly "url_citation": { + readonly "url": string + readonly "title"?: string + readonly "start_index"?: number + readonly "end_index"?: number + readonly "content"?: string + } + } | { + readonly "type": "file_annotation" + readonly "file_annotation": { readonly "file_id": string; readonly "quote"?: string } + } | { + readonly "type": "file" + readonly "file": { + readonly "hash": string + readonly "name": string + readonly "content"?: ReadonlyArray<{ readonly "type": string; readonly "text"?: string }> + } + } + > + | null +} +export const ChatStreamDelta = Schema.Struct({ + "audio": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ChatAudioOutput).annotate({ + "description": "Audio output data" + }) + ), + "content": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Message content delta" }) + ), + "reasoning": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Reasoning content delta" }) + ), + "reasoning_details": Schema.optionalKey(ChatStreamReasoningDetails), + "refusal": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Refusal message delta" }) + ), + "role": Schema.optionalKey(Schema.Literal("assistant").annotate({ "description": "The role of the message author" })), + "tool_calls": Schema.optionalKey(Schema.Array(ChatStreamToolCall).annotate({ "description": "Tool calls delta" })), + "images": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Struct({ "type": Schema.Literal("image_url"), "image_url": Schema.Struct({ "url": Schema.String }) }) + ), + Schema.Null + ]) + ), + "annotations": Schema.optionalKey(Schema.Union([ + Schema.Array(Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("url_citation"), + "url_citation": Schema.Struct({ + "url": Schema.String, + "title": Schema.optionalKey(Schema.String), + "start_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "end_index": Schema.optionalKey( + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "content": Schema.optionalKey(Schema.String) + }) + }), + Schema.Struct({ + "type": Schema.Literal("file_annotation"), + "file_annotation": Schema.Struct({ "file_id": Schema.String, "quote": Schema.optionalKey(Schema.String) }) + }), + Schema.Struct({ + "type": Schema.Literal("file"), + "file": Schema.Struct({ + "hash": Schema.String, + "name": Schema.String, + "content": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.String, "text": Schema.optionalKey(Schema.String) })) + ) + }) + }) + ], { mode: "oneOf" })), + Schema.Null + ])) +}).annotate({ "description": "Delta changes in streaming response", "identifier": "ChatStreamDelta" }) +export type AdditionalToolsItem = { + readonly "id"?: string | null + readonly "role": "unknown" | "user" | "assistant" | "system" | "critic" | "discriminator" | "developer" | "tool" + readonly "tools": ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + | AdvisorServerTool_OpenRouter + | SubagentServerTool_OpenRouter + | DatetimeServerTool + | FilesServerTool + | FusionServerTool_OpenRouter + | ImageGenerationServerTool_OpenRouter + | SearchModelsServerTool_OpenRouter + | WebFetchServerTool + | WebSearchServerTool_OpenRouter + | ApplyPatchServerTool_OpenRouter + | BashServerTool + | ShellServerTool_OpenRouter + | { readonly "type": string } + > + readonly "type": "additional_tools" +} +export const AdditionalToolsItem = Schema.Struct({ + "id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "role": Schema.Literals(["unknown", "user", "assistant", "system", "critic", "discriminator", "developer", "tool"]), + "tools": Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool, + AdvisorServerTool_OpenRouter, + SubagentServerTool_OpenRouter, + DatetimeServerTool, + FilesServerTool, + FusionServerTool_OpenRouter, + ImageGenerationServerTool_OpenRouter, + SearchModelsServerTool_OpenRouter, + WebFetchServerTool, + WebSearchServerTool_OpenRouter, + ApplyPatchServerTool_OpenRouter, + BashServerTool, + ShellServerTool_OpenRouter, + Schema.Struct({ "type": Schema.String }) + ])), + "type": Schema.Literal("additional_tools") +}).annotate({ + "description": "Additional tools made available to the model at this point in the input", + "identifier": "AdditionalToolsItem" +}) +export type ChatFunctionTool = + | { + readonly "cache_control"?: ChatContentCacheControl + readonly "function": { + readonly "description"?: string + readonly "name": string + readonly "parameters"?: {} + readonly "strict"?: boolean | null + } + readonly "type": "function" + } + | AdvisorServerTool_OpenRouter + | BashServerTool + | DatetimeServerTool + | FilesServerTool + | FusionServerTool_OpenRouter + | ImageGenerationServerTool_OpenRouter + | ChatSearchModelsServerTool + | SubagentServerTool_OpenRouter + | WebFetchServerTool + | OpenRouterWebSearchServerTool + | ChatWebSearchShorthand +export const ChatFunctionTool = Schema.Union([ + Schema.Struct({ + "cache_control": Schema.optionalKey(ChatContentCacheControl), + "function": Schema.Struct({ + "description": Schema.optionalKey( + Schema.String.annotate({ "description": "Function description for the model" }) + ), + "name": Schema.String.annotate({ + "description": "Function name (a-z, A-Z, 0-9, underscores, dashes, max 64 chars)" + }).check(Schema.isMaxLength(64).annotate({ "expected": "a value with a length of at most 64" })), + "parameters": Schema.optionalKey( + Schema.Struct({}).annotate({ "description": "Function parameters as JSON Schema object" }) + ), + "strict": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Enable strict schema adherence" }) + ) + }).annotate({ "description": "Function definition for tool calling" }), + "type": Schema.Literal("function") + }), + AdvisorServerTool_OpenRouter, + BashServerTool, + DatetimeServerTool, + FilesServerTool, + FusionServerTool_OpenRouter, + ImageGenerationServerTool_OpenRouter, + ChatSearchModelsServerTool, + SubagentServerTool_OpenRouter, + WebFetchServerTool, + OpenRouterWebSearchServerTool, + ChatWebSearchShorthand +]).annotate({ + "description": "Tool definition for function calling (regular function or OpenRouter built-in server tool)", + "identifier": "ChatFunctionTool" +}) +export type BaseInputs = + | string + | ReadonlyArray< + | { + readonly "content": ReadonlyArray | string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "user" | "system" | "assistant" | "developer" + readonly "type"?: "message" + } + | OpenAIResponseInputMessageItem + | OpenAIResponseFunctionToolCallOutput + | OpenAIResponseFunctionToolCall + | OutputItemImageGenerationCall + | OutputMessage + | OpenAIResponseCustomToolCall + | OpenAIResponseCustomToolCallOutput + | ApplyPatchCallItem + | ApplyPatchCallOutputItem + > + | null +export const BaseInputs = Schema.Union([ + Schema.String, + Schema.Array(Schema.Union([ + Schema.Struct({ + "content": Schema.Union([ + Schema.Array(Schema.Union([InputText, InputImage, InputFile, InputAudio], { mode: "oneOf" })), + Schema.String + ]), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]) + ), + "role": Schema.Literals(["user", "system", "assistant", "developer"]), + "type": Schema.optionalKey(Schema.Literal("message")) + }), + OpenAIResponseInputMessageItem, + OpenAIResponseFunctionToolCallOutput, + OpenAIResponseFunctionToolCall, + OutputItemImageGenerationCall, + OutputMessage, + OpenAIResponseCustomToolCall, + OpenAIResponseCustomToolCallOutput, + ApplyPatchCallItem, + ApplyPatchCallOutputItem + ])), + Schema.Null +]).annotate({ "identifier": "BaseInputs" }) +export type OutputItemAddedEvent = { + readonly "item": + | OutputMessage + | OutputItemReasoning + | OutputItemFunctionCall + | OutputItemCustomToolCall + | OutputItemWebSearchCall + | OutputItemFileSearchCall + | OutputItemImageGenerationCall + | OutputItemApplyPatchCall + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.added" +} +export const OutputItemAddedEvent = Schema.Struct({ + "item": Schema.Union([ + OutputMessage, + OutputItemReasoning, + OutputItemFunctionCall, + OutputItemCustomToolCall, + OutputItemWebSearchCall, + OutputItemFileSearchCall, + OutputItemImageGenerationCall, + OutputItemApplyPatchCall + ], { mode: "oneOf" }), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.added") +}).annotate({ + "description": "Event emitted when a new output item is added to the response", + "identifier": "OutputItemAddedEvent" +}) +export type OutputItemDoneEvent = { + readonly "item": + | OutputMessage + | OutputItemReasoning + | OutputItemFunctionCall + | OutputItemCustomToolCall + | OutputItemWebSearchCall + | OutputItemFileSearchCall + | OutputItemImageGenerationCall + | OutputItemApplyPatchCall + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.done" +} +export const OutputItemDoneEvent = Schema.Struct({ + "item": Schema.Union([ + OutputMessage, + OutputItemReasoning, + OutputItemFunctionCall, + OutputItemCustomToolCall, + OutputItemWebSearchCall, + OutputItemFileSearchCall, + OutputItemImageGenerationCall, + OutputItemApplyPatchCall + ], { mode: "oneOf" }), + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.done") +}).annotate({ "description": "Event emitted when an output item is complete", "identifier": "OutputItemDoneEvent" }) +export type OutputItems = + | OutputMessageItem + | OutputReasoningItem + | OutputFunctionCallItem + | OutputWebSearchCallItem + | OutputFileSearchCallItem + | OutputImageGenerationCallItem + | OutputCodeInterpreterCallItem + | OutputComputerCallItem + | OutputDatetimeItem + | OutputWebSearchServerToolItem + | OutputCodeInterpreterServerToolItem + | OutputFileSearchServerToolItem + | OutputImageGenerationServerToolItem + | OutputBrowserUseServerToolItem + | OutputBashServerToolItem + | OutputTextEditorServerToolItem + | OutputApplyPatchServerToolItem + | OutputApplyPatchCallItem + | OutputShellCallItem + | OutputShellCallOutputItem + | OutputWebFetchServerToolItem + | OutputToolSearchServerToolItem + | OutputMemoryServerToolItem + | OutputMcpServerToolItem + | OutputSearchModelsServerToolItem + | OutputFusionServerToolItem + | OutputAdvisorServerToolItem + | OutputSubagentServerToolItem + | OutputFilesServerToolItem + | OutputCustomToolCallItem +export const OutputItems = Schema.Union([ + OutputMessageItem, + OutputReasoningItem, + OutputFunctionCallItem, + OutputWebSearchCallItem, + OutputFileSearchCallItem, + OutputImageGenerationCallItem, + OutputCodeInterpreterCallItem, + OutputComputerCallItem, + OutputDatetimeItem, + OutputWebSearchServerToolItem, + OutputCodeInterpreterServerToolItem, + OutputFileSearchServerToolItem, + OutputImageGenerationServerToolItem, + OutputBrowserUseServerToolItem, + OutputBashServerToolItem, + OutputTextEditorServerToolItem, + OutputApplyPatchServerToolItem, + OutputApplyPatchCallItem, + OutputShellCallItem, + OutputShellCallOutputItem, + OutputWebFetchServerToolItem, + OutputToolSearchServerToolItem, + OutputMemoryServerToolItem, + OutputMcpServerToolItem, + OutputSearchModelsServerToolItem, + OutputFusionServerToolItem, + OutputAdvisorServerToolItem, + OutputSubagentServerToolItem, + OutputFilesServerToolItem, + OutputCustomToolCallItem +], { mode: "oneOf" }).annotate({ "description": "An output item from the response", "identifier": "OutputItems" }) +export type AnthropicWebFetchToolResult = { + readonly "caller": AnthropicCaller + readonly "content": AnthropicWebFetchContent + readonly "tool_use_id": string + readonly "type": "web_fetch_tool_result" +} +export const AnthropicWebFetchToolResult = Schema.Struct({ + "caller": AnthropicCaller, + "content": AnthropicWebFetchContent, + "tool_use_id": Schema.String, + "type": Schema.Literal("web_fetch_tool_result") +}).annotate({ "identifier": "AnthropicWebFetchToolResult" }) +export type MessagesRequest = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "context_management"?: { + readonly "edits"?: ReadonlyArray< + { + readonly "clear_at_least"?: AnthropicInputTokensClearAtLeast + readonly "clear_tool_inputs"?: boolean | ReadonlyArray | null + readonly "exclude_tools"?: ReadonlyArray | null + readonly "keep"?: AnthropicToolUsesKeep + readonly "trigger"?: AnthropicInputTokensTrigger | AnthropicToolUsesTrigger + readonly "type": "clear_tool_uses_20250919" + } | { + readonly "keep"?: AnthropicThinkingTurns | { readonly "type": "all" } | "all" + readonly "type": "clear_thinking_20251015" + } | { + readonly "instructions"?: string | null + readonly "pause_after_compaction"?: boolean + readonly "trigger"?: { readonly "type": "input_tokens"; readonly "value": number } | null + readonly "type": "compact_20260112" + } + > + readonly [x: string]: Schema.Json + } | null + readonly "fallbacks"?: ReadonlyArray | null + readonly "max_tokens"?: number + readonly "messages": ReadonlyArray | null + readonly "metadata"?: { readonly "user_id"?: string | null } + readonly "model": string + readonly "models"?: ReadonlyArray + readonly "output_config"?: MessagesOutputConfig + readonly "plugins"?: ReadonlyArray< + | AutoRouterPlugin + | AutoBetaRouterPlugin + | ModerationPlugin + | WebSearchPlugin + | WebFetchPlugin + | FileParserPlugin + | ResponseHealingPlugin + | ContextCompressionPlugin + | ParetoRouterPlugin + | FusionPlugin + > + readonly "provider"?: ProviderPreferences + readonly "route"?: DeprecatedRoute + readonly "service_tier"?: string + readonly "session_id"?: string + readonly "speed"?: AnthropicSpeed + readonly "stop_sequences"?: ReadonlyArray + readonly "stop_server_tools_when"?: StopServerToolsWhen + readonly "stream"?: boolean + readonly "system"?: string | ReadonlyArray + readonly "temperature"?: number + readonly "thinking"?: + | { readonly "budget_tokens": number; readonly "display"?: AnthropicThinkingDisplay; readonly "type": "enabled" } + | { readonly "type": "disabled" } + | { readonly "display"?: AnthropicThinkingDisplay; readonly "type": "adaptive" } + readonly "tool_choice"?: + | { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "auto" } + | { readonly "disable_parallel_tool_use"?: boolean; readonly "type": "any" } + | { readonly "type": "none" } + | { readonly "disable_parallel_tool_use"?: boolean; readonly "name": string; readonly "type": "tool" } + readonly "tools"?: ReadonlyArray< + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "description"?: string + readonly "input_schema": { + readonly "properties"?: Schema.Json + readonly "required"?: ReadonlyArray | null + readonly "type"?: string + } + readonly "name": string + readonly "type"?: "custom" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "name": "bash" + readonly "type": "bash_20250124" + } + | { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "name": "str_replace_editor" + readonly "type": "text_editor_20250124" + } + | { + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "type": "web_search_20250305" + readonly "user_location"?: AnthropicWebSearchToolUserLocation + } + | { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "allowed_domains"?: ReadonlyArray | null + readonly "blocked_domains"?: ReadonlyArray | null + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "max_uses"?: number | null + readonly "name": "web_search" + readonly "type": "web_search_20260209" + readonly "user_location"?: AnthropicWebSearchToolUserLocation + } + | { + readonly "allowed_callers"?: AnthropicAllowedCallers + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "caching"?: AnthropicCacheControlDirective | null + readonly "defer_loading"?: boolean + readonly "max_uses"?: number + readonly "model": string + readonly "name": "advisor" + readonly "type": "advisor_20260301" + } + | BashServerTool + | DatetimeServerTool + | ImageGenerationServerTool_OpenRouter + | MessagesSearchModelsServerTool + | WebFetchServerTool + | OpenRouterWebSearchServerTool + | { readonly "type": string } + | AnthropicToolSearchToolBm25 + | AnthropicToolSearchToolRegex + > + readonly "top_k"?: number + readonly "top_p"?: number + readonly "trace"?: TraceConfig + readonly "user"?: string +} +export const MessagesRequest = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "context_management": Schema.optionalKey(Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "edits": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Struct({ + "clear_at_least": Schema.optionalKey(AnthropicInputTokensClearAtLeast), + "clear_tool_inputs": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Array(Schema.String), Schema.Null]) + ), + "exclude_tools": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "keep": Schema.optionalKey(AnthropicToolUsesKeep), + "trigger": Schema.optionalKey( + Schema.Union([AnthropicInputTokensTrigger, AnthropicToolUsesTrigger], { mode: "oneOf" }) + ), + "type": Schema.Literal("clear_tool_uses_20250919") + }), + Schema.Struct({ + "keep": Schema.optionalKey( + Schema.Union([ + AnthropicThinkingTurns, + Schema.Struct({ "type": Schema.Literal("all") }), + Schema.Literal("all") + ]) + ), + "type": Schema.Literal("clear_thinking_20251015") + }), + Schema.Struct({ + "instructions": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "pause_after_compaction": Schema.optionalKey(Schema.Boolean), + "trigger": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "type": Schema.Literal("input_tokens"), + "value": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Null + ]) + ), + "type": Schema.Literal("compact_20260112") + }) + ], { mode: "oneOf" }))) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null + ])), + "fallbacks": Schema.optionalKey( + Schema.Union([Schema.Array(MessagesFallbackParam), Schema.Null]).annotate({ + "description": + "Fallback models to try if the primary model fails or refuses, in order. Handled by OpenRouter multi-model routing rather than Anthropic server-side fallbacks; cannot be combined with `models`. Each entry accepts only `model`. Maximum of 3 entries." + }) + ), + "max_tokens": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "messages": Schema.Union([Schema.Array(MessagesMessageParam), Schema.Null]), + "metadata": Schema.optionalKey( + Schema.Struct({ "user_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) }) + ), + "model": Schema.String, + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "output_config": Schema.optionalKey(MessagesOutputConfig), + "plugins": Schema.optionalKey( + Schema.Array( + Schema.Union([ + AutoRouterPlugin, + AutoBetaRouterPlugin, + ModerationPlugin, + WebSearchPlugin, + WebFetchPlugin, + FileParserPlugin, + ResponseHealingPlugin, + ContextCompressionPlugin, + ParetoRouterPlugin, + FusionPlugin + ], { mode: "oneOf" }) + ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) + ), + "provider": Schema.optionalKey(ProviderPreferences), + "route": Schema.optionalKey(DeprecatedRoute), + "service_tier": Schema.optionalKey(Schema.String), + "session_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier for grouping related requests (e.g., a conversation or agent workflow). When provided, OpenRouter uses it as the sticky routing key, routing all requests in the session to the same provider to maximize prompt cache hits. Also used for observability grouping. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ), + "speed": Schema.optionalKey( + Schema.suspend((): Schema.Codec => AnthropicSpeed).annotate({ + "description": + "Controls output generation speed. When set to `fast`, uses a higher-speed inference configuration at premium pricing. Defaults to `standard` when omitted." + }) + ), + "stop_sequences": Schema.optionalKey(Schema.Array(Schema.String)), + "stop_server_tools_when": Schema.optionalKey(StopServerToolsWhen), + "stream": Schema.optionalKey(Schema.Boolean), + "system": Schema.optionalKey(Schema.Union([Schema.String, Schema.Array(AnthropicTextBlockParam)])), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "thinking": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "budget_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "display": Schema.optionalKey(AnthropicThinkingDisplay), + "type": Schema.Literal("enabled") + }), + Schema.Struct({ "type": Schema.Literal("disabled") }), + Schema.Struct({ "display": Schema.optionalKey(AnthropicThinkingDisplay), "type": Schema.Literal("adaptive") }) + ], { mode: "oneOf" }) + ), + "tool_choice": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean), + "type": Schema.Literal("auto") + }), + Schema.Struct({ "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean), "type": Schema.Literal("any") }), + Schema.Struct({ "type": Schema.Literal("none") }), + Schema.Struct({ + "disable_parallel_tool_use": Schema.optionalKey(Schema.Boolean), + "name": Schema.String, + "type": Schema.Literal("tool") + }) + ], { mode: "oneOf" }) + ), + "tools": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "description": Schema.optionalKey(Schema.String), + "input_schema": Schema.Struct({ + "properties": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "required": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "type": Schema.optionalKey(Schema.String) + }), + "name": Schema.String, + "type": Schema.optionalKey(Schema.Literal("custom")) + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "name": Schema.Literal("bash"), + "type": Schema.Literal("bash_20250124") + }), + Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "name": Schema.Literal("str_replace_editor"), + "type": Schema.Literal("text_editor_20250124") + }), + Schema.Struct({ + "allowed_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "blocked_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "name": Schema.Literal("web_search"), + "type": Schema.Literal("web_search_20250305"), + "user_location": Schema.optionalKey(AnthropicWebSearchToolUserLocation) + }), + Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "allowed_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "blocked_domains": Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "max_uses": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "name": Schema.Literal("web_search"), + "type": Schema.Literal("web_search_20260209"), + "user_location": Schema.optionalKey(AnthropicWebSearchToolUserLocation) + }), + Schema.Struct({ + "allowed_callers": Schema.optionalKey(AnthropicAllowedCallers), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "caching": Schema.optionalKey(Schema.Union([AnthropicCacheControlDirective, Schema.Null])), + "defer_loading": Schema.optionalKey(Schema.Boolean), + "max_uses": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "model": Schema.String, + "name": Schema.Literal("advisor"), + "type": Schema.Literal("advisor_20260301") + }), + BashServerTool, + DatetimeServerTool, + ImageGenerationServerTool_OpenRouter, + MessagesSearchModelsServerTool, + WebFetchServerTool, + OpenRouterWebSearchServerTool, + Schema.Struct({ "type": Schema.String }), + AnthropicToolSearchToolBm25, + AnthropicToolSearchToolRegex + ]))), + "top_k": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_p": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + ), + "trace": Schema.optionalKey(TraceConfig), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ) +}).annotate({ "description": "Request schema for Anthropic Messages API endpoint", "identifier": "MessagesRequest" }) +export type ImageGenerationUsage = { + readonly "cache_creation"?: AnthropicCacheCreation + readonly "completion_tokens": number + readonly "completion_tokens_details"?: Union_6 + readonly "cost"?: number | null + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + readonly "iterations"?: Union_7 + readonly "prompt_tokens": number + readonly "prompt_tokens_details"?: Union_8 + readonly "server_tool_use"?: Union_9 + readonly "service_tier"?: string | null + readonly "speed"?: AnthropicSpeed + readonly "total_tokens": number +} +export const ImageGenerationUsage = Schema.Struct({ + "cache_creation": Schema.optionalKey(AnthropicCacheCreation), + "completion_tokens": Schema.Number.annotate({ "description": "The tokens generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "completion_tokens_details": Schema.optionalKey(Union_6), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether a request was made using a Bring Your Own Key configuration" }) + ), + "iterations": Schema.optionalKey(Union_7), + "prompt_tokens": Schema.Number.annotate({ "description": "Including images, input audio, and tools if any" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "prompt_tokens_details": Schema.optionalKey(Union_8), + "server_tool_use": Schema.optionalKey(Union_9), + "service_tier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The service tier used by the upstream provider for this request" + }) + ), + "speed": Schema.optionalKey(AnthropicSpeed), + "total_tokens": Schema.Number.annotate({ "description": "Sum of the above two fields" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ + "description": "Token and cost usage for the image generation request, when available", + "identifier": "ImageGenerationUsage" +}) +export type ModelsListResponse = { + readonly "data": ModelsListResponseData + readonly "links": { readonly "next": string | null } + readonly "total_count": number +} +export const ModelsListResponse = Schema.Struct({ + "data": ModelsListResponseData, + "links": Schema.Struct({ + "next": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "URL for the next page of results, or null if this is the last page" + }) + }).annotate({ "description": "Pagination links" }), + "total_count": Schema.Number.annotate({ "description": "Total number of models matching the query" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}).annotate({ "description": "List of available models", "identifier": "ModelsListResponse" }) +export type ChatChoice = { + readonly "finish_reason": ChatFinishReasonEnum + readonly "index": number + readonly "logprobs"?: ChatTokenLogprobs + readonly "message": ChatAssistantMessage +} +export const ChatChoice = Schema.Struct({ + "finish_reason": ChatFinishReasonEnum, + "index": Schema.Number.annotate({ "description": "Choice index" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "logprobs": Schema.optionalKey(ChatTokenLogprobs), + "message": ChatAssistantMessage +}).annotate({ "description": "Chat completion choice", "identifier": "ChatChoice" }) +export type ChatMessages = + | ChatSystemMessage + | ChatUserMessage + | ChatDeveloperMessage + | ChatAssistantMessage + | ChatToolMessage +export const ChatMessages = Schema.Union([ + ChatSystemMessage, + ChatUserMessage, + ChatDeveloperMessage, + ChatAssistantMessage, + ChatToolMessage +], { mode: "oneOf" }).annotate({ + "description": "Chat completion message with role-based discrimination", + "identifier": "ChatMessages" +}) +export type ChatStreamChoice = { + readonly "delta": ChatStreamDelta + readonly "finish_reason"?: ChatFinishReasonEnum + readonly "index": number + readonly "logprobs"?: ChatTokenLogprobs +} +export const ChatStreamChoice = Schema.Struct({ + "delta": ChatStreamDelta, + "finish_reason": Schema.optionalKey(ChatFinishReasonEnum), + "index": Schema.Number.annotate({ "description": "Choice index" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "logprobs": Schema.optionalKey(ChatTokenLogprobs) +}).annotate({ "description": "Streaming completion choice chunk", "identifier": "ChatStreamChoice" }) +export type Arrays_8 = ReadonlyArray< + | ReasoningItem + | EasyInputMessage + | InputMessageItem + | FunctionCallItem + | FunctionCallOutputItem + | ApplyPatchCallItem + | ApplyPatchCallOutputItem + | { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray< + { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": "file_citation" + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "filename": string + readonly "index": number + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "filename": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": "url_citation" + readonly "url": string + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "index": number + } | { readonly "file_id": string; readonly "index": number; readonly "type": "file_path" } + > + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": "output_text" + } | { + readonly "refusal": string + readonly "type": never + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + } | { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + readonly "refusal": string + } | { readonly "refusal": string; readonly "type": "refusal" } + > + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" + } + | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray<{ readonly "text": string; readonly "type": "summary_text" }> + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + } + | OutputFunctionCallItem + | OutputCustomToolCallItem + | OutputWebSearchCallItem + | OutputFileSearchCallItem + | OutputImageGenerationCallItem + | OutputCodeInterpreterCallItem + | OutputComputerCallItem + | OutputDatetimeItem + | OutputWebSearchServerToolItem + | OutputCodeInterpreterServerToolItem + | OutputFileSearchServerToolItem + | OutputImageGenerationServerToolItem + | OutputBrowserUseServerToolItem + | OutputBashServerToolItem + | OutputTextEditorServerToolItem + | OutputApplyPatchServerToolItem + | OutputWebFetchServerToolItem + | OutputToolSearchServerToolItem + | OutputMemoryServerToolItem + | OutputMcpServerToolItem + | OutputSearchModelsServerToolItem + | OutputFusionServerToolItem + | OutputAdvisorServerToolItem + | OutputSubagentServerToolItem + | OutputFilesServerToolItem + | LocalShellCallItem + | LocalShellCallOutputItem + | ShellCallItem + | ShellCallOutputItem + | McpListToolsItem + | McpApprovalRequestItem + | McpApprovalResponseItem + | McpCallItem + | CustomToolCallItem + | CustomToolCallOutputItem + | CompactionItem + | ContextCompactionItem + | ItemReferenceItem + | AdditionalToolsItem + | AgentMessageItem +> +export const Arrays_8 = Schema.Array( + Schema.Union([ + ReasoningItem, + EasyInputMessage, + InputMessageItem, + FunctionCallItem, + FunctionCallOutputItem, + ApplyPatchCallItem, + ApplyPatchCallOutputItem, + Schema.Struct({ + "content": Schema.Union([Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_citation") + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "filename": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Literal("url_citation"), + "url": Schema.String + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_path") + }) + ]) + ]) + ) + ), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Literal("output_text") + }), + Schema.Struct({ + "refusal": Schema.String, + "type": Schema.Never, + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never, + "refusal": Schema.String + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Literal("refusal") }) + ]) + ]))]), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Literal("message") + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") }))]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("summary_text") })) + ]), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + OutputFunctionCallItem, + OutputCustomToolCallItem, + OutputWebSearchCallItem, + OutputFileSearchCallItem, + OutputImageGenerationCallItem, + OutputCodeInterpreterCallItem, + OutputComputerCallItem, + OutputDatetimeItem, + OutputWebSearchServerToolItem, + OutputCodeInterpreterServerToolItem, + OutputFileSearchServerToolItem, + OutputImageGenerationServerToolItem, + OutputBrowserUseServerToolItem, + OutputBashServerToolItem, + OutputTextEditorServerToolItem, + OutputApplyPatchServerToolItem, + OutputWebFetchServerToolItem, + OutputToolSearchServerToolItem, + OutputMemoryServerToolItem, + OutputMcpServerToolItem, + OutputSearchModelsServerToolItem, + OutputFusionServerToolItem, + OutputAdvisorServerToolItem, + OutputSubagentServerToolItem, + OutputFilesServerToolItem, + LocalShellCallItem, + LocalShellCallOutputItem, + ShellCallItem, + ShellCallOutputItem, + McpListToolsItem, + McpApprovalRequestItem, + McpApprovalResponseItem, + McpCallItem, + CustomToolCallItem, + CustomToolCallOutputItem, + CompactionItem, + ContextCompactionItem, + ItemReferenceItem, + AdditionalToolsItem, + AgentMessageItem + ]) +) +export type BaseResponsesResult = { + readonly "background"?: boolean | null + readonly "completed_at": number | null + readonly "created_at": number + readonly "error": ResponsesErrorField + readonly "frequency_penalty": number | null + readonly "id": string + readonly "incomplete_details": IncompleteDetails + readonly "instructions": BaseInputs + readonly "max_output_tokens"?: number | null + readonly "max_tool_calls"?: number | null + readonly "metadata": RequestMetadata + readonly "model": string + readonly "object": "response" + readonly "output": ReadonlyArray< + | OutputMessage + | OutputItemReasoning + | OutputItemFunctionCall + | OutputItemCustomToolCall + | OutputItemWebSearchCall + | OutputItemFileSearchCall + | OutputItemImageGenerationCall + | OutputItemApplyPatchCall + > + readonly "output_text"?: string + readonly "parallel_tool_calls": boolean + readonly "presence_penalty": number | null + readonly "previous_response_id"?: string | null + readonly "prompt"?: StoredPromptTemplate + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "reasoning"?: BaseReasoningConfig + readonly "safety_identifier"?: string | null + readonly "service_tier"?: ServiceTier + readonly "status": OpenAIResponsesResponseStatus + readonly "store"?: boolean + readonly "temperature": number | null + readonly "text"?: TextConfig + readonly "tool_choice": OpenAIResponsesToolChoice + readonly "tools": ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + > + readonly "top_logprobs"?: number + readonly "top_p": number | null + readonly "truncation"?: Truncation + readonly "usage"?: OpenAIResponsesUsage + readonly "user"?: string | null +} +export const BaseResponsesResult = Schema.Struct({ + "background": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "completed_at": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "created_at": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "error": ResponsesErrorField, + "frequency_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "id": Schema.String, + "incomplete_details": IncompleteDetails, + "instructions": BaseInputs, + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "metadata": RequestMetadata, + "model": Schema.String, + "object": Schema.Literal("response"), + "output": Schema.Array( + Schema.Union([ + OutputMessage, + OutputItemReasoning, + OutputItemFunctionCall, + OutputItemCustomToolCall, + OutputItemWebSearchCall, + OutputItemFileSearchCall, + OutputItemImageGenerationCall, + OutputItemApplyPatchCall + ], { mode: "oneOf" }) + ), + "output_text": Schema.optionalKey(Schema.String), + "parallel_tool_calls": Schema.Boolean, + "presence_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "previous_response_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt": Schema.optionalKey(StoredPromptTemplate), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "reasoning": Schema.optionalKey(BaseReasoningConfig), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "service_tier": Schema.optionalKey(ServiceTier), + "status": OpenAIResponsesResponseStatus, + "store": Schema.optionalKey(Schema.Boolean), + "temperature": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "text": Schema.optionalKey(TextConfig), + "tool_choice": OpenAIResponsesToolChoice, + "tools": Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool + ], { mode: "oneOf" })), + "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_p": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "truncation": Schema.optionalKey(Truncation), + "usage": Schema.optionalKey(OpenAIResponsesUsage), + "user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) +}).annotate({ "identifier": "BaseResponsesResult" }) +export type OpenResponsesResult = { + readonly "background"?: boolean | null + readonly "completed_at": number | null + readonly "created_at": number + readonly "error": ResponsesErrorField + readonly "frequency_penalty": number | null + readonly "id": string + readonly "incomplete_details": IncompleteDetails + readonly "instructions": BaseInputs + readonly "max_output_tokens"?: number | null + readonly "max_tool_calls"?: number | null + readonly "metadata": RequestMetadata + readonly "model": string + readonly "object": "response" + readonly "output": ReadonlyArray< + { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray< + { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": "file_citation" + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "filename": string + readonly "index": number + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "filename": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": "url_citation" + readonly "url": string + } | { + readonly "file_id": string + readonly "index": number + readonly "type": never + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "url": string + } | { + readonly "file_id": string + readonly "filename": string + readonly "index": number + readonly "type": never + } | { + readonly "content"?: string + readonly "end_index": number + readonly "start_index": number + readonly "title": string + readonly "type": never + readonly "url": string + readonly "file_id": string + readonly "index": number + } | { readonly "file_id": string; readonly "index": number; readonly "type": "file_path" } + > + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": "output_text" + } | { + readonly "refusal": string + readonly "type": never + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + } | { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + readonly "refusal": string + } | { readonly "refusal": string; readonly "type": "refusal" } + > + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "message" + } | { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + } | { readonly "refusal": string; readonly "type": never; readonly "text": string } + > + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "timezone": string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "content": never + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "value"?: Schema.Json + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "toolName"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "content": ReadonlyArray + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + } | { + readonly "content": ReadonlyArray< + { + readonly "annotations"?: ReadonlyArray + readonly "logprobs"?: ReadonlyArray< + { + readonly "bytes": ReadonlyArray + readonly "logprob": number + readonly "token": string + readonly "top_logprobs": ReadonlyArray< + { readonly "bytes": ReadonlyArray; readonly "logprob": number; readonly "token": string } + > + } + > + readonly "text": string + readonly "type": never + } | { readonly "refusal": string; readonly "type": never; readonly "text": string } + > + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray<{ readonly "text": string; readonly "type": "summary_text" }> + readonly "type": "reasoning" + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "timezone": string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "content"?: never + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "value"?: Schema.Json + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "toolName"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "summary": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "content"?: ReadonlyArray + readonly "encrypted_content"?: string | null + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": "function_call" + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id"?: string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "datetime": string + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "timezone": string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "language"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: string + readonly "id"?: string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "call_id": string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "name": string + readonly "namespace"?: string + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id"?: string + readonly "key"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "value"?: Schema.Json + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "toolName"?: string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "id"?: string + readonly "query"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id"?: string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name": string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id"?: string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" | "incomplete" + readonly "type": never + readonly "arguments": string + readonly "call_id": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "arguments": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id"?: string + readonly "name": string + readonly "namespace"?: string + readonly "status"?: "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "input": string + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": WebSearchStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + readonly "type": never + readonly "prompt"?: string + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id"?: string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "incomplete" | "in_progress" + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "datetime": string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "timezone": string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "language"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "queries"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: string + readonly "id"?: string + readonly "screenshotB64"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments"?: string + readonly "call_id": string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": ToolCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": ApplyPatchCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": ShellCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": ShellCallStatus + readonly "type": never + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id"?: string + readonly "status": ToolCallStatus + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id"?: string + readonly "key"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "value"?: Schema.Json + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "id"?: string + readonly "serverLabel"?: string + readonly "status": ToolCallStatus + readonly "toolName"?: string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "arguments"?: string + readonly "id"?: string + readonly "query"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id"?: string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "id"?: string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name": string + readonly "outcome"?: string + readonly "status": ToolCallStatus + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "namespace"?: string + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id"?: string + readonly "operation"?: string + readonly "result"?: string + readonly "status": ToolCallStatus + readonly "type": never + readonly "call_id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + } | { + readonly "call_id": string + readonly "id"?: string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": "custom_tool_call" + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + | { + readonly "type": never + readonly "url"?: string | null + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + } + | { + readonly "pattern": string + readonly "type": never + readonly "url": string + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + } + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": never + readonly "url"?: string | null + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": never; readonly "url": string } + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": never + readonly "pattern": string + readonly "url": string + } + | { readonly "type": never; readonly "url": string; readonly "pattern": string } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": "web_search_call" + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" | "failed" + readonly "type": never + readonly "prompt"?: string + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } | { + readonly "type": never + readonly "url"?: string | null + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + } | { + readonly "pattern": string + readonly "type": never + readonly "url": string + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: never + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } | { + readonly "type": "open_page" + readonly "url"?: string | null + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } | { + readonly "pattern": string + readonly "type": "find_in_page" + readonly "url": string + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "action"?: never + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "status": WebSearchStatus + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "queries": ReadonlyArray + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "searching" | "in_progress" | "failed" + readonly "type": "file_search_call" + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" | "failed" + readonly "type": never + readonly "prompt"?: string + readonly "queries": ReadonlyArray + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + readonly "queries": ReadonlyArray + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "queries": ReadonlyArray + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "queries": ReadonlyArray + readonly "status": WebSearchStatus + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "result"?: string | null + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" | "failed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" | "failed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" | "generating" | "failed" + readonly "type": "image_generation_call" + readonly "prompt"?: string + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "arguments"?: string + readonly "call_id"?: string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "call_id"?: string + readonly "id": string + readonly "operation"?: ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": ApplyPatchCallOperation + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "result"?: string | null + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + readonly "result"?: string | null + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "result"?: string | null + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "result"?: string | null + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation"?: string + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "result"?: string | null + readonly "status": ImageGenerationStatus + } | { + readonly "content": ReadonlyArray + readonly "id": string + readonly "phase"?: "commentary" | "final_answer" | null + readonly "role": "assistant" + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "content"?: ReadonlyArray<{ readonly "text": string; readonly "type": "reasoning_text" }> + readonly "encrypted_content"?: string | null + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "summary": ReadonlyArray + readonly "type": never + readonly "format"?: ReasoningFormat + readonly "signature"?: string | null + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "arguments": string + readonly "call_id": string + readonly "id": string + readonly "name": string + readonly "namespace"?: string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: + | { + readonly "queries"?: ReadonlyArray + readonly "query": string + readonly "sources"?: ReadonlyArray + readonly "type": "search" + } + | { readonly "type": "open_page"; readonly "url"?: string | null } + | { readonly "pattern": string; readonly "type": "find_in_page"; readonly "url": string } + readonly "id": string + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "queries": ReadonlyArray + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "result"?: string | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "prompt"?: string + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "code": string | null + readonly "container_id": string + readonly "id": string + readonly "outputs": + | ReadonlyArray< + { readonly "type": "image"; readonly "url": string } | { readonly "logs": string; readonly "type": "logs" } + > + | null + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: Schema.Json + readonly "call_id": string + readonly "id": string + readonly "pending_safety_checks": ReadonlyArray< + { readonly "code": string; readonly "id": string; readonly "message": string } + > + readonly "status": "completed" | "in_progress" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "datetime": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "timezone": string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: { + readonly "query": string + readonly "sources"?: ReadonlyArray<{ readonly "type": "url"; readonly "url": string }> + readonly "type": "search" + } + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "code"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "language"?: string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "queries"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "imageB64"?: string + readonly "imageUrl"?: string + readonly "prompt"?: string + readonly "result"?: string | null + readonly "revisedPrompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: string + readonly "id": string + readonly "screenshotB64"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "arguments"?: string + readonly "call_id": string + readonly "command"?: string + readonly "exitCode"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "stderr"?: string + readonly "stdout"?: string + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "command"?: "view" | "create" | "str_replace" | "insert" + readonly "filePath"?: string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": + | { readonly "diff": string; readonly "path": string; readonly "type": "create_file" } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": "update_file" } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": "delete_file" } + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "created_by"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "operation": + | { readonly "diff": string; readonly "path": string; readonly "type": "create_file" } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": "update_file" } + | { readonly "path": string; readonly "type": never; readonly "diff": string } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "diff": string; readonly "path": string; readonly "type": never } + | { readonly "path": string; readonly "type": "delete_file" } + readonly "status": "in_progress" | "completed" + readonly "type": "apply_patch_call" + readonly "created_by"?: string + } | { + readonly "action"?: { + readonly "commands": ReadonlyArray + readonly "max_output_length": number | null + readonly "timeout_ms": number | null + } + readonly "call_id": string + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "call_id": string + readonly "id": string + readonly "max_output_length"?: number | null + readonly "output": ReadonlyArray< + { + readonly "outcome": { readonly "exit_code": number; readonly "type": "exit" } | { readonly "type": "timeout" } + readonly "stderr": string + readonly "stdout": string + } + > + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "content"?: string + readonly "error"?: string + readonly "httpStatus"?: number + readonly "id": string + readonly "status": "in_progress" | "completed" + readonly "title"?: string + readonly "type": never + readonly "url"?: string + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "action"?: "read" | "write" | "delete" + readonly "id": string + readonly "key"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "value"?: Schema.Json + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "id": string + readonly "serverLabel"?: string + readonly "status": "in_progress" | "completed" + readonly "toolName"?: string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "arguments"?: string + readonly "id": string + readonly "query"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "analysis"?: FusionAnalysisResult + readonly "error"?: string + readonly "failed_models"?: ReadonlyArray< + { readonly "error": string; readonly "model": string; readonly "status_code"?: number } + > + readonly "failure_reason"?: string + readonly "id": string + readonly "responses"?: ReadonlyArray<{ readonly "content"?: string; readonly "model": string }> + readonly "sources"?: ReadonlyArray + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "advice"?: string + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "prompt"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "error"?: string + readonly "id": string + readonly "instance_name"?: string + readonly "model"?: string + readonly "name"?: string + readonly "outcome"?: string + readonly "status": "in_progress" | "completed" + readonly "task_description"?: string + readonly "task_name"?: string + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + } | { + readonly "error"?: string + readonly "file_id"?: string + readonly "filename"?: string + readonly "id": string + readonly "operation": never + readonly "result"?: string + readonly "status": "in_progress" | "completed" + readonly "type": never + readonly "call_id": string + readonly "created_by"?: string + } | { + readonly "call_id": string + readonly "id": string + readonly "input": string + readonly "name": string + readonly "namespace"?: string + readonly "type": never + readonly "created_by"?: string + readonly "operation": + | ApplyPatchCreateFileOperation + | ApplyPatchUpdateFileOperation + | ApplyPatchDeleteFileOperation + readonly "status": "in_progress" | "completed" + } + > + readonly "output_text"?: string + readonly "parallel_tool_calls": boolean + readonly "presence_penalty": number | null + readonly "previous_response_id"?: string | null + readonly "prompt"?: StoredPromptTemplate + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "reasoning"?: BaseReasoningConfig + readonly "safety_identifier"?: string | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | "scale" | null + readonly "status": OpenAIResponsesResponseStatus + readonly "store"?: boolean + readonly "temperature": number | null + readonly "text"?: { + readonly "format"?: + | { readonly "type": "text" } + | { readonly "type": never } + | { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": never + } + | { readonly "type": never } + | { readonly "type": "json_object" } + | { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": never + } + | { + readonly "type": never + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + } + | { + readonly "type": never + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + } + | { + readonly "description"?: string + readonly "name": string + readonly "schema": {} + readonly "strict"?: boolean | null + readonly "type": "json_schema" + } + readonly "verbosity"?: "high" | "low" | "medium" | null + } + readonly "tool_choice": OpenAIResponsesToolChoice + readonly "tools": ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + > + readonly "top_logprobs"?: number + readonly "top_p": number | null + readonly "truncation"?: Truncation + readonly "usage"?: { + readonly "input_tokens": number + readonly "input_tokens_details": { readonly "cache_write_tokens"?: number | null; readonly "cached_tokens": number } + readonly "output_tokens": number + readonly "output_tokens_details": { readonly "reasoning_tokens": number } + readonly "total_tokens": number + readonly "cost"?: number | null + readonly "cost_details"?: { + readonly "upstream_inference_cost"?: number | null + readonly "upstream_inference_input_cost": number + readonly "upstream_inference_output_cost": number + } + readonly "is_byok"?: boolean + readonly "server_tool_use_details"?: ServerToolUseDetails + } + readonly "user"?: string | null + readonly "error_type"?: ApiErrorType + readonly "openrouter_metadata"?: OpenRouterMetadata +} +export const OpenResponsesResult = Schema.Struct({ + "background": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "completed_at": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "created_at": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "error": ResponsesErrorField, + "frequency_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "id": Schema.String, + "incomplete_details": IncompleteDetails, + "instructions": BaseInputs, + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "metadata": RequestMetadata, + "model": Schema.String, + "object": Schema.Literal("response"), + "output": Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey( + Schema.Array( + Schema.Union([ + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_citation") + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "filename": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Literal("url_citation"), + "url": Schema.String + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "url": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "file_id": Schema.String, + "filename": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Never + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "end_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "start_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "title": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + Schema.Struct({ + "file_id": Schema.String, + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("file_path") + }) + ]) + ]) + ) + ), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Literal("output_text") + }), + Schema.Struct({ + "refusal": Schema.String, + "type": Schema.Never, + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never, + "refusal": Schema.String + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Literal("refusal") }) + ]) + ])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([ + Schema.Literal("commentary").annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }), + Schema.Literal("final_answer").annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }), + Schema.Union([Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Literal("message") + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.Union([Schema.Array(Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Never, "text": Schema.String }) + ]))]), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("incomplete"), + Schema.Literal("in_progress") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.Never, + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant") + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ + Schema.Struct({ + "annotations": Schema.optionalKey(Schema.Array(OpenAIResponsesAnnotation)), + "logprobs": Schema.optionalKey(Schema.Array(Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String, + "top_logprobs": Schema.Array( + Schema.Struct({ + "bytes": Schema.Array(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "logprob": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "token": Schema.String + }) + ) + }))), + "text": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ "refusal": Schema.String, "type": Schema.Never, "text": Schema.String }) + ])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey( + Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])]) + ), + "id": Schema.String, + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "summary": Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("summary_text") })), + "type": Schema.Literal("reasoning"), + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("incomplete"), + Schema.Literal("in_progress") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.Never), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "content": Schema.optionalKey(Schema.Array(ReasoningTextContent)), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey( + Schema.Union([Schema.Literal("completed"), Schema.Literal("incomplete"), Schema.Literal("in_progress")]) + ), + "type": Schema.Literal("function_call") + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("incomplete"), + Schema.Literal("in_progress") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": "The raw tool-call arguments string as emitted by the model." + }), + "call_id": Schema.String.annotate({ + "description": "The model-generated tool call id from the originating turn." + }), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]).annotate({ "description": "Status of a shell call or its output." }), + "type": Schema.Never, + "arguments": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.optionalKey(Schema.String), + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }), + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.optionalKey(Schema.String), + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("incomplete") + ]), + "type": Schema.Never, + "arguments": Schema.String, + "call_id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "arguments": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.optionalKey(Schema.Literals(["completed", "incomplete", "in_progress"])), + "type": Schema.Never, + "input": Schema.String + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": WebSearchStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus, + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Literals(["completed", "incomplete", "in_progress"]), + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "language": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "screenshotB64": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.String.annotate({ + "description": "The model-generated tool call id from the originating turn." + }), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": ToolCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": ApplyPatchCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": ShellCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": ShellCallStatus, + "type": Schema.Never, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.optionalKey(Schema.String), + "key": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.optionalKey(Schema.String), + "serverLabel": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.optionalKey(Schema.String), + "query": Schema.optionalKey(Schema.String), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.optionalKey(Schema.String), + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.optionalKey(Schema.String), + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": ToolCallStatus, + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.optionalKey(Schema.String), + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": ToolCallStatus, + "type": Schema.Never, + "call_id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.optionalKey(Schema.String), + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Literal("custom_tool_call") + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Never, + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)) + }), + Schema.Struct({ + "pattern": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)) + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Never, "url": Schema.Union([Schema.String]) }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Never, + "pattern": Schema.String, + "url": Schema.String + }), + Schema.Struct({ "type": Schema.Never, "url": Schema.Union([Schema.String]), "pattern": Schema.String }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Literal("web_search_call") + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }), + Schema.Struct({ + "type": Schema.Never, + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ) + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }), + Schema.Struct({ + "pattern": Schema.String, + "type": Schema.Never, + "url": Schema.String, + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ) + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Never), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search"), + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + }), + Schema.Struct({ + "pattern": Schema.String, + "type": Schema.Literal("find_in_page"), + "url": Schema.String, + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + }) + ], { mode: "oneOf" })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Never), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "status": WebSearchStatus + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([ + Schema.Literal("completed"), + Schema.Literal("searching"), + Schema.Literal("in_progress"), + Schema.Literal("failed") + ]), + "type": Schema.Literal("file_search_call") + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed"), Schema.Literal("failed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "queries": Schema.Array(Schema.String) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "queries": Schema.Array(Schema.String), + "status": WebSearchStatus + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress"), Schema.Literal("failed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])])), + "status": Schema.Union([ + Schema.Literal("in_progress"), + Schema.Literal("completed"), + Schema.Literal("generating"), + Schema.Literal("failed") + ]), + "type": Schema.Literal("image_generation_call"), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The model-generated tool call id from the originating turn." }) + ), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.optionalKey(Schema.String), + "id": Schema.String, + "operation": Schema.optionalKey(ApplyPatchCallOperation), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": ApplyPatchCallOperation, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.optionalKey( + Schema.String.annotate({ "description": "The file operation performed (list, read, write, or edit)." }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String.annotate({ "description": "JSON-serialized result of the file operation." })]) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": ImageGenerationStatus + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }), + Schema.Union([ + Schema.Struct({ + "content": Schema.Array(Schema.Union([ResponseOutputText, OpenAIResponsesRefusalContent])), + "id": Schema.String, + "phase": Schema.optionalKey( + Schema.Union([Schema.Literal("commentary"), Schema.Literal("final_answer"), Schema.Null]).annotate({ + "description": + "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages." + }) + ), + "role": Schema.Literal("assistant"), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An output message item" }), + Schema.Struct({ + "content": Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Struct({ "text": Schema.String, "type": Schema.Literal("reasoning_text") })) + ]) + ), + "encrypted_content": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "summary": Schema.Array(ReasoningSummaryText), + "type": Schema.Never, + "format": Schema.optionalKey(ReasoningFormat), + "signature": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "A signature for the reasoning content, used for verification" + }) + ), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An output item containing reasoning" }), + Schema.Struct({ + "arguments": Schema.String, + "call_id": Schema.String, + "id": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Union([ + Schema.Struct({ + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "query": Schema.String, + "sources": Schema.optionalKey(Schema.Array(WebSearchSource)), + "type": Schema.Literal("search") + }), + Schema.Struct({ + "type": Schema.Literal("open_page"), + "url": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])) + }), + Schema.Struct({ "pattern": Schema.String, "type": Schema.Literal("find_in_page"), "url": Schema.String }) + ], { mode: "oneOf" }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.Array(Schema.String), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "id": Schema.String, + "result": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "code": Schema.Union([Schema.String, Schema.Null]), + "container_id": Schema.String, + "id": Schema.String, + "outputs": Schema.Union([ + Schema.Array( + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("image"), "url": Schema.String }), + Schema.Struct({ "logs": Schema.String, "type": Schema.Literal("logs") }) + ]) + ), + Schema.Null + ]), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "A code interpreter execution call with outputs" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "id": Schema.String, + "pending_safety_checks": Schema.Array( + Schema.Struct({ "code": Schema.String, "id": Schema.String, "message": Schema.String }) + ), + "status": Schema.Union([Schema.Literal("completed"), Schema.Literal("in_progress")]), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }), + Schema.Struct({ + "datetime": Schema.String.annotate({ "description": "ISO 8601 datetime string" }), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "timezone": Schema.String.annotate({ "description": "IANA timezone name" }), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:datetime server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey( + Schema.Struct({ + "query": Schema.String, + "sources": Schema.optionalKey( + Schema.Array(Schema.Struct({ "type": Schema.Literal("url"), "url": Schema.String })) + ), + "type": Schema.Literal("search") + }).annotate({ + "description": + "The search action performed, matching OpenAI web_search_call.action shape. Includes the query the model issued and optional source URLs returned by the search provider." + }) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:web_search server tool output item" }), + Schema.Struct({ + "code": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "language": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:code_interpreter server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "queries": Schema.optionalKey(Schema.Array(Schema.String)), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:file_search server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "imageB64": Schema.optionalKey(Schema.String), + "imageUrl": Schema.optionalKey(Schema.String), + "prompt": Schema.optionalKey( + Schema.String.annotate({ + "description": "The prompt (possibly rewritten) that the image was generated from." + }) + ), + "result": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The generated image as a base64-encoded string or URL, matching OpenAI image_generation_call format" + }) + ), + "revisedPrompt": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:image_generation server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.String), + "id": Schema.String, + "screenshotB64": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:browser_use server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ "description": "The raw tool-call arguments string as emitted by the model." }) + ), + "call_id": Schema.String.annotate({ + "description": "The model-generated tool call id from the originating turn." + }), + "command": Schema.optionalKey(Schema.String), + "exitCode": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "stderr": Schema.optionalKey(Schema.String), + "stdout": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:bash server tool output item" }), + Schema.Struct({ + "command": Schema.optionalKey(Schema.Literals(["view", "create", "str_replace", "insert"])), + "filePath": Schema.optionalKey(Schema.String), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:text_editor server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": Schema.Union([ + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("create_file") }) + .annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("update_file") }) + .annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Literal("delete_file") }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }).annotate({ + "description": + "The patch operation requested by an `apply_patch_call`. `create_file` and `update_file` carry a V4A diff; `delete_file` omits it." + }), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String) + }).annotate({ + "description": + "An openrouter:apply_patch server tool output item. The turn halts when validation succeeds so the client can apply the patch and echo an `apply_patch_call_output` on the next turn." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "operation": Schema.Union([ + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("create_file") }) + .annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `create_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing the new file contents." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Literal("update_file") }) + .annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Never, "diff": Schema.String }).annotate({ + "description": + "The `update_file` variant of an `apply_patch_call.operation`. Carries a V4A diff describing edits to an existing file." + }) + ], { mode: "oneOf" }), + Schema.Union([ + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "diff": Schema.String, "path": Schema.String, "type": Schema.Never }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }), + Schema.Struct({ "path": Schema.String, "type": Schema.Literal("delete_file") }).annotate({ + "description": + "The `delete_file` variant of an `apply_patch_call.operation`. Identifies the file to remove; no diff is required." + }) + ], { mode: "oneOf" }) + ], { mode: "oneOf" }).annotate({ + "description": + "The patch operation requested by an `apply_patch_call`. `create_file` and `update_file` carry a V4A diff; `delete_file` omits it." + }), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Lifecycle state of an `apply_patch_call` output item." + }), + "type": Schema.Literal("apply_patch_call"), + "created_by": Schema.optionalKey(Schema.String) + }).annotate({ + "description": + "A native `apply_patch_call` output item matching OpenAI's Responses API shape. Emitted when the client requested the `apply_patch` shorthand." + }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Struct({ + "commands": Schema.Array(Schema.String), + "max_output_length": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "timeout_ms": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]) + })), + "call_id": Schema.String, + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ + "description": + "A native `shell_call` output item matching OpenAI's Responses API shape. Emitted for the sandbox-backed `shell` tool." + }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "max_output_length": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "output": Schema.Array(Schema.Struct({ + "outcome": Schema.Union([ + Schema.Struct({ + "exit_code": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("exit") + }), + Schema.Struct({ "type": Schema.Literal("timeout") }) + ], { mode: "oneOf" }), + "stderr": Schema.String, + "stdout": Schema.String + })), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]).annotate({ + "description": "Status of a shell call or its output." + }), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ + "description": + "A native `shell_call_output` item matching OpenAI's Responses API shape. Carries per-command stdout, stderr, and the exit/timeout outcome." + }), + Schema.Struct({ + "content": Schema.optionalKey(Schema.String), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "The error message if the fetch failed." }) + ), + "httpStatus": Schema.optionalKey( + Schema.Number.annotate({ "description": "The HTTP status code returned by the upstream URL fetch." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "id": Schema.String, + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "title": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "url": Schema.optionalKey(Schema.String), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:web_fetch server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:tool_search server tool output item" }), + Schema.Struct({ + "action": Schema.optionalKey(Schema.Literals(["read", "write", "delete"])), + "id": Schema.String, + "key": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "value": Schema.optionalKey(Schema.Json.annotate({ "expected": "JSON value" })), + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:memory server tool output item" }), + Schema.Struct({ + "id": Schema.String, + "serverLabel": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "toolName": Schema.optionalKey(Schema.String), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:mcp server tool output item" }), + Schema.Struct({ + "arguments": Schema.optionalKey( + Schema.String.annotate({ + "description": "The JSON arguments submitted to the search tool (e.g. {\"query\":\"Claude\"})" + }) + ), + "id": Schema.String, + "query": Schema.optionalKey(Schema.String), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:experimental__search_models server tool output item" }), + Schema.Struct({ + "analysis": Schema.optionalKey(FusionAnalysisResult), + "error": Schema.optionalKey( + Schema.String.annotate({ + "description": "Error message when the fusion run did not produce an analysis result." + }) + ), + "failed_models": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "error": Schema.String.annotate({ "description": "Error message describing why the model failed." }), + "model": Schema.String.annotate({ "description": "Slug of the analysis model that failed." }), + "status_code": Schema.optionalKey( + Schema.Number.annotate({ + "description": "HTTP status code from the upstream response, when available (e.g. 402, 429)." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + })).annotate({ + "description": + "Models that were requested as part of the analysis panel but did not produce a response. Present when at least one requested analysis model failed. The fusion result is still usable but was produced from a degraded panel." + }) + ), + "failure_reason": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Typed failure reason when the fusion run failed. Possible values include: all_panels_failed, insufficient_credits, rate_limited, judge_not_valid_json, judge_schema_mismatch, judge_upstream_error, judge_empty_completion." + }) + ), + "id": Schema.String, + "responses": Schema.optionalKey( + Schema.Array(Schema.Struct({ "content": Schema.optionalKey(Schema.String), "model": Schema.String })) + .annotate({ + "description": + "Analysis models that produced a response in this fusion run, with each model's full panel content." + }) + ), + "sources": Schema.optionalKey( + Schema.Array(FusionSource).annotate({ + "description": + "Web pages the analysis panels and judge retrieved via web search during this fusion run, deduplicated by URL across the whole run. Present when at least one model cited a source." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:fusion server tool output item" }), + Schema.Struct({ + "advice": Schema.optionalKey( + Schema.String.annotate({ + "description": "The advisor model's response (the advice text returned to the executor)." + }) + ), + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the advisor call did not produce advice." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific advisor instance that produced this item (e.g. `openrouter_advisor__1`). Present only when more than one advisor tool is configured; omitted for the default single advisor. Echo this field back unchanged so the advisor's cross-request memory stays namespaced to the correct instance. This identity is positional: it is derived from the index of the advisor entry in the request `tools` array, so clients must keep the order of advisor tool entries stable across requests in a conversation. Reordering or inserting advisor entries shifts these names and causes each advisor's cross-request memory to be attributed to the wrong instance." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the advisor model that was consulted." }) + ), + "prompt": Schema.optionalKey( + Schema.String.annotate({ "description": "The prompt the executor sent to the advisor." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:advisor server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the subagent task did not produce an outcome." }) + ), + "id": Schema.String, + "instance_name": Schema.optionalKey(Schema.String.annotate({ + "description": + "Provider-safe function name of the specific subagent instance that produced this item (e.g. `openrouter_subagent__1`). Present only on items from non-default instances — the second and later subagent entries in the request `tools` array. The first (default) instance omits it, even when multiple subagents are configured. When a replayed item echoes this field back, the transcript rehydrates the call under that instance's tool. This identity is positional: it is derived from the index of the subagent entry in the request `tools` array, so keep the order of subagent entries stable across requests in a conversation." + })), + "model": Schema.optionalKey( + Schema.String.annotate({ "description": "Slug of the worker model that executed the task." }) + ), + "name": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Configured name of the subagent that executed the task (the `name` on its tool entry). Present only for named subagents; omitted for an unnamed (default) subagent." + }) + ), + "outcome": Schema.optionalKey( + Schema.String.annotate({ + "description": "The worker model's result (the outcome text returned to the delegating model)." + }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "task_description": Schema.optionalKey( + Schema.String.annotate({ "description": "The task description the delegating model sent to the worker." }) + ), + "task_name": Schema.optionalKey( + Schema.String.annotate({ "description": "The short task identifier the delegating model supplied." }) + ), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }) + }).annotate({ "description": "An openrouter:subagent server tool output item" }), + Schema.Struct({ + "error": Schema.optionalKey( + Schema.String.annotate({ "description": "Error message when the file operation failed." }) + ), + "file_id": Schema.optionalKey( + Schema.String.annotate({ "description": "The target file id supplied in the tool-call arguments." }) + ), + "filename": Schema.optionalKey( + Schema.String.annotate({ "description": "The target filename supplied in the tool-call arguments." }) + ), + "id": Schema.String, + "operation": Schema.Never, + "result": Schema.optionalKey( + Schema.String.annotate({ "description": "JSON-serialized result of the file operation." }) + ), + "status": Schema.Union([Schema.Literal("in_progress"), Schema.Literal("completed")]), + "type": Schema.Never, + "call_id": Schema.String, + "created_by": Schema.optionalKey(Schema.String) + }).annotate({ "description": "An openrouter:files server tool output item" }), + Schema.Struct({ + "call_id": Schema.String, + "id": Schema.String, + "input": Schema.String, + "name": Schema.String, + "namespace": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Namespace qualifier for tools registered as part of a namespace tool group (e.g. an MCP server)" + }) + ), + "type": Schema.Never, + "created_by": Schema.optionalKey(Schema.String), + "operation": Schema.Union([ + ApplyPatchCreateFileOperation, + ApplyPatchUpdateFileOperation, + ApplyPatchDeleteFileOperation + ], { mode: "oneOf" }), + "status": Schema.Literals(["in_progress", "completed"]) + }).annotate({ + "description": + "A call to a custom (freeform-grammar) tool created by the model — distinct from `function_call`. Used for tools like Codex CLI's `apply_patch` whose payload is opaque text rather than JSON arguments." + }) + ], { mode: "oneOf" }).annotate({ "description": "An output item from the response" }) + ], { mode: "oneOf" })), + "output_text": Schema.optionalKey(Schema.String), + "parallel_tool_calls": Schema.Boolean, + "presence_penalty": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "previous_response_id": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt": Schema.optionalKey(StoredPromptTemplate), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "reasoning": Schema.optionalKey(BaseReasoningConfig), + "safety_identifier": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Union([Schema.Null]) + ]) + ), + "status": OpenAIResponsesResponseStatus, + "store": Schema.optionalKey(Schema.Boolean), + "temperature": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "text": Schema.optionalKey( + Schema.Struct({ + "format": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.Struct({ "type": Schema.Literal("text") }).annotate({ "description": "Plain text response format" }), + Schema.Struct({ "type": Schema.Never }).annotate({ "description": "Plain text response format" }), + Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Never + }).annotate({ "description": "Plain text response format" }) + ]).annotate({ "description": "Text response format configuration" }), + Schema.Union([ + Schema.Struct({ "type": Schema.Never }).annotate({ "description": "JSON object response format" }), + Schema.Struct({ "type": Schema.Literal("json_object") }).annotate({ + "description": "JSON object response format" + }), + Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Never + }).annotate({ "description": "JSON object response format" }) + ]).annotate({ "description": "Text response format configuration" }), + Schema.Union([ + Schema.Struct({ + "type": Schema.Never, + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) + }).annotate({ "description": "JSON schema constrained response format" }), + Schema.Struct({ + "type": Schema.Never, + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) + }).annotate({ "description": "JSON schema constrained response format" }), + Schema.Struct({ + "description": Schema.optionalKey(Schema.String), + "name": Schema.String, + "schema": Schema.Struct({}), + "strict": Schema.optionalKey(Schema.Union([Schema.Union([Schema.Boolean]), Schema.Union([Schema.Null])])), + "type": Schema.Literal("json_schema") + }).annotate({ "description": "JSON schema constrained response format" }) + ]).annotate({ "description": "Text response format configuration" }) + ]).annotate({ "description": "Text response format configuration" }) + ), + "verbosity": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.Literal("high")]), + Schema.Union([Schema.Literal("low")]), + Schema.Union([Schema.Literal("medium")]), + Schema.Union([Schema.Union([Schema.Null])]) + ]) + ) + }).annotate({ "description": "Text output configuration including format and verbosity" }) + ), + "tool_choice": OpenAIResponsesToolChoice, + "tools": Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool + ], { mode: "oneOf" })), + "top_logprobs": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_p": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }), + "truncation": Schema.optionalKey(Truncation), + "usage": Schema.optionalKey( + Schema.Union([Schema.Struct({ + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "input_tokens_details": Schema.Struct({ + "cache_write_tokens": Schema.optionalKey( + Schema.Union([ + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))]), + Schema.Union([Schema.Null]) + ]) + ), + "cached_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Schema.Struct({ + "reasoning_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + "total_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Cost of the completion", "format": "double" }) + ), + "cost_details": Schema.optionalKey(Schema.Struct({ + "upstream_inference_cost": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "format": "double" }) + ), + "upstream_inference_input_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "upstream_inference_output_cost": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + })), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Whether a request was made using a Bring Your Own Key configuration" + }) + ), + "server_tool_use_details": Schema.optionalKey(ServerToolUseDetails) + })]).annotate({ "description": "Token usage information for the response" }) + ), + "user": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "error_type": Schema.optionalKey(ApiErrorType), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata) +}).annotate({ + "description": "Complete non-streaming response from the Responses API", + "identifier": "OpenResponsesResult" +}) +export type StreamEventsResponseOutputItemAdded = { + readonly "item": OutputItems + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.added" +} +export const StreamEventsResponseOutputItemAdded = Schema.Struct({ + "item": OutputItems, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.added") +}).annotate({ + "description": "Event emitted when a new output item is added to the response", + "identifier": "StreamEventsResponseOutputItemAdded" +}) +export type StreamEventsResponseOutputItemDone = { + readonly "item": OutputItems + readonly "output_index": number + readonly "sequence_number": number + readonly "type": "response.output_item.done" +} +export const StreamEventsResponseOutputItemDone = Schema.Struct({ + "item": OutputItems, + "output_index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.output_item.done") +}).annotate({ + "description": "Event emitted when an output item is complete", + "identifier": "StreamEventsResponseOutputItemDone" +}) +export type MessagesContentBlockStartEvent = { + readonly "content_block": + | AnthropicTextBlock + | AnthropicToolUseBlock + | AnthropicThinkingBlock + | AnthropicRedactedThinkingBlock + | ORAnthropicServerToolUseBlock + | AnthropicWebSearchToolResult + | AnthropicWebFetchToolResult + | AnthropicCodeExecutionToolResult + | AnthropicBashCodeExecutionToolResult + | AnthropicTextEditorCodeExecutionToolResult + | AnthropicToolSearchToolResult + | AnthropicContainerUpload + | AnthropicCompactionBlock + | AnthropicAdvisorToolResult + | { readonly "content": string | null; readonly "type": "compaction" } + readonly "index": number + readonly "type": "content_block_start" +} +export const MessagesContentBlockStartEvent = Schema.Struct({ + "content_block": Schema.Union([ + AnthropicTextBlock, + AnthropicToolUseBlock, + AnthropicThinkingBlock, + AnthropicRedactedThinkingBlock, + ORAnthropicServerToolUseBlock, + AnthropicWebSearchToolResult, + AnthropicWebFetchToolResult, + AnthropicCodeExecutionToolResult, + AnthropicBashCodeExecutionToolResult, + AnthropicTextEditorCodeExecutionToolResult, + AnthropicToolSearchToolResult, + AnthropicContainerUpload, + AnthropicCompactionBlock, + AnthropicAdvisorToolResult, + Schema.Struct({ "content": Schema.Union([Schema.String, Schema.Null]), "type": Schema.Literal("compaction") }) + ]), + "index": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("content_block_start") +}).annotate({ + "description": "Event sent when a new content block starts", + "identifier": "MessagesContentBlockStartEvent" +}) +export type ORAnthropicContentBlock = + | AnthropicTextBlock + | AnthropicToolUseBlock + | AnthropicThinkingBlock + | AnthropicRedactedThinkingBlock + | ORAnthropicServerToolUseBlock + | AnthropicWebSearchToolResult + | AnthropicWebFetchToolResult + | AnthropicCodeExecutionToolResult + | AnthropicBashCodeExecutionToolResult + | AnthropicTextEditorCodeExecutionToolResult + | AnthropicToolSearchToolResult + | AnthropicContainerUpload + | AnthropicCompactionBlock + | AnthropicAdvisorToolResult +export const ORAnthropicContentBlock = Schema.Union([ + AnthropicTextBlock, + AnthropicToolUseBlock, + AnthropicThinkingBlock, + AnthropicRedactedThinkingBlock, + ORAnthropicServerToolUseBlock, + AnthropicWebSearchToolResult, + AnthropicWebFetchToolResult, + AnthropicCodeExecutionToolResult, + AnthropicBashCodeExecutionToolResult, + AnthropicTextEditorCodeExecutionToolResult, + AnthropicToolSearchToolResult, + AnthropicContainerUpload, + AnthropicCompactionBlock, + AnthropicAdvisorToolResult +], { mode: "oneOf" }).annotate({ "identifier": "ORAnthropicContentBlock" }) +export type ImageGenCompletedEvent = { + readonly "b64_json": string + readonly "created": number + readonly "media_type"?: string + readonly "type": "image_generation.completed" + readonly "usage"?: ImageGenerationUsage +} +export const ImageGenCompletedEvent = Schema.Struct({ + "b64_json": Schema.String.annotate({ "description": "Base64-encoded final image data" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp (seconds) when the image was generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "media_type": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Media type (MIME type) of the image, e.g. `image/png`, `image/jpeg`, `image/webp`, `image/svg+xml`. May be omitted if the format could not be determined." + }) + ), + "type": Schema.Literal("image_generation.completed").annotate({ "description": "The event type" }), + "usage": Schema.optionalKey(ImageGenerationUsage) +}).annotate({ + "description": "Emitted when generation completes and the final image is available", + "identifier": "ImageGenCompletedEvent" +}) +export type ImageGenerationResponse = { + readonly "created": number + readonly "data": ReadonlyArray<{ readonly "b64_json": string; readonly "media_type"?: string }> + readonly "usage"?: ImageGenerationUsage +} +export const ImageGenerationResponse = Schema.Struct({ + "created": Schema.Number.annotate({ "description": "Unix timestamp (seconds) when the image was generated" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "data": Schema.Array(Schema.Struct({ + "b64_json": Schema.String.annotate({ "description": "Base64-encoded image bytes" }), + "media_type": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Media type (MIME type) of the image, e.g. `image/png`, `image/jpeg`, `image/webp`, `image/svg+xml`. May be omitted if the format could not be determined." + }) + ) + })).annotate({ "description": "Generated images" }), + "usage": Schema.optionalKey(ImageGenerationUsage) +}).annotate({ "description": "Image generation response", "identifier": "ImageGenerationResponse" }) +export type ChatResult = { + readonly "choices": ReadonlyArray + readonly "created": number + readonly "id": string + readonly "model": string + readonly "object": "chat.completion" + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "service_tier"?: string | null + readonly "system_fingerprint": string | null + readonly "usage"?: ChatUsage +} +export const ChatResult = Schema.Struct({ + "choices": Schema.Array(ChatChoice).annotate({ "description": "List of completion choices" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of creation" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "id": Schema.String.annotate({ "description": "Unique completion identifier" }), + "model": Schema.String.annotate({ "description": "Model used for completion" }), + "object": Schema.Literal("chat.completion"), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "service_tier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The service tier used by the upstream provider for this request" + }) + ), + "system_fingerprint": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "System fingerprint" }), + "usage": Schema.optionalKey(ChatUsage) +}).annotate({ "description": "Chat completion response", "identifier": "ChatResult" }) +export type ChatRequest = { + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "debug"?: ChatDebugOptions + readonly "frequency_penalty"?: number | null + readonly "image_config"?: ImageConfig + readonly "logit_bias"?: { readonly [x: string]: number } | null + readonly "logprobs"?: boolean | null + readonly "max_completion_tokens"?: number | null + readonly "max_tokens"?: number | null + readonly "messages": ReadonlyArray + readonly "metadata"?: {} + readonly "min_p"?: number | null + readonly "modalities"?: ReadonlyArray<"text" | "image" | "audio"> + readonly "model"?: ModelName + readonly "models"?: ChatModelNames + readonly "parallel_tool_calls"?: boolean | null + readonly "plugins"?: ReadonlyArray< + | AutoRouterPlugin + | AutoBetaRouterPlugin + | ModerationPlugin + | WebSearchPlugin + | WebFetchPlugin + | FileParserPlugin + | ResponseHealingPlugin + | ContextCompressionPlugin + | ParetoRouterPlugin + | FusionPlugin + > + readonly "prediction"?: Prediction + readonly "presence_penalty"?: number | null + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "provider"?: ProviderPreferences + readonly "reasoning"?: { + readonly "effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null + readonly "summary"?: ChatReasoningSummaryVerbosityEnum + } + readonly "reasoning_effort"?: "max" | "xhigh" | "high" | "medium" | "low" | "minimal" | "none" | null + readonly "repetition_penalty"?: number | null + readonly "response_format"?: + | ChatFormatTextConfig + | ChatFormatJsonObjectConfig + | ChatFormatJsonSchemaConfig + | ChatFormatGrammarConfig + | ChatFormatPythonConfig + readonly "route"?: DeprecatedRoute + readonly "seed"?: number | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | "scale" | null + readonly "session_id"?: string + readonly "stop"?: string | ReadonlyArray | null + readonly "stop_server_tools_when"?: StopServerToolsWhen + readonly "stream"?: boolean + readonly "stream_options"?: ChatStreamOptions + readonly "temperature"?: number | null + readonly "tool_choice"?: ChatToolChoice + readonly "tools"?: ReadonlyArray + readonly "top_a"?: number | null + readonly "top_k"?: number | null + readonly "top_logprobs"?: number | null + readonly "top_p"?: number | null + readonly "trace"?: TraceConfig + readonly "user"?: string +} +export const ChatRequest = Schema.Struct({ + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "debug": Schema.optionalKey(ChatDebugOptions), + "frequency_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Frequency penalty (-2.0 to 2.0)", "format": "double" }) + ), + "image_config": Schema.optionalKey(ImageConfig), + "logit_bias": Schema.optionalKey( + Schema.Union([ + Schema.Record( + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + Schema.Null + ]).annotate({ "description": "Token logit bias adjustments" }) + ), + "logprobs": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ "description": "Return log probabilities" }) + ), + "max_completion_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Maximum tokens in completion" + }) + ), + "max_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Maximum tokens (deprecated, use max_completion_tokens). Note: some providers enforce a minimum of 16." + }) + ), + "messages": Schema.Array(ChatMessages).annotate({ "description": "List of messages for the conversation" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "metadata": Schema.optionalKey( + Schema.Struct({}).annotate({ + "description": "Key-value pairs for additional object information (max 16 pairs, 64 char keys, 512 char values)" + }) + ), + "min_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ + "description": + "Minimum probability threshold relative to the most likely token. Tokens with probability below min_p * (probability of top token) are filtered out. Not all providers support this parameter.", + "format": "double" + }) + ), + "modalities": Schema.optionalKey( + Schema.Array(Schema.Literals(["text", "image", "audio"])).annotate({ + "description": "Output modalities for the response. Supported values are \"text\", \"image\", and \"audio\"." + }) + ), + "model": Schema.optionalKey(ModelName), + "models": Schema.optionalKey(ChatModelNames), + "parallel_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]).annotate({ + "description": + "Whether to enable parallel function calling during tool use. When true, the model may generate multiple tool calls in a single response." + }) + ), + "plugins": Schema.optionalKey( + Schema.Array( + Schema.Union([ + AutoRouterPlugin, + AutoBetaRouterPlugin, + ModerationPlugin, + WebSearchPlugin, + WebFetchPlugin, + FileParserPlugin, + ResponseHealingPlugin, + ContextCompressionPlugin, + ParetoRouterPlugin, + FusionPlugin + ], { mode: "oneOf" }) + ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) + ), + "prediction": Schema.optionalKey(Prediction), + "presence_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Presence penalty (-2.0 to 2.0)", "format": "double" }) + ), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "provider": Schema.optionalKey(ProviderPreferences), + "reasoning": Schema.optionalKey( + Schema.Struct({ + "effort": Schema.optionalKey( + Schema.Union([ + Schema.Literal("max"), + Schema.Literal("xhigh"), + Schema.Literal("high"), + Schema.Literal("medium"), + Schema.Literal("low"), + Schema.Literal("minimal"), + Schema.Literal("none"), + Schema.Null + ]).annotate({ "description": "Constrains effort on reasoning for reasoning models" }) + ), + "summary": Schema.optionalKey(ChatReasoningSummaryVerbosityEnum) + }).annotate({ "description": "Configuration options for reasoning models" }) + ), + "reasoning_effort": Schema.optionalKey( + Schema.Union([ + Schema.Literal("max"), + Schema.Literal("xhigh"), + Schema.Literal("high"), + Schema.Literal("medium"), + Schema.Literal("low"), + Schema.Literal("minimal"), + Schema.Literal("none"), + Schema.Null + ]).annotate({ + "description": + "Shorthand for setting reasoning effort. Equivalent to setting reasoning.effort. Cannot be used simultaneously with reasoning.effort if they differ." + }) + ), + "repetition_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ + "description": + "Penalizes tokens based on how much they have already appeared in the text. A value of 1.0 means no penalty. Values above 1.0 penalize repeated tokens more strongly. Not all providers support this parameter.", + "format": "double" + }) + ), + "response_format": Schema.optionalKey( + Schema.Union([ + ChatFormatTextConfig, + ChatFormatJsonObjectConfig, + ChatFormatJsonSchemaConfig, + ChatFormatGrammarConfig, + ChatFormatPythonConfig + ], { mode: "oneOf" }).annotate({ "description": "Response format configuration" }) + ), + "route": Schema.optionalKey(DeprecatedRoute), + "seed": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Random seed for deterministic outputs" + }) + ), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Null + ]).annotate({ "description": "The service tier to use for processing this request." }) + ), + "session_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier for grouping related requests (e.g., a conversation or agent workflow). When provided, OpenRouter uses it as the sticky routing key, routing all requests in the session to the same provider to maximize prompt cache hits. Also used for observability grouping. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ), + "stop": Schema.optionalKey( + Schema.Union([ + Schema.String, + Schema.Array(Schema.String).check( + Schema.isMaxLength(4).annotate({ "expected": "a value with a length of at most 4" }) + ), + Schema.Null + ]).annotate({ "description": "Stop sequences (up to 4)" }) + ), + "stop_server_tools_when": Schema.optionalKey(StopServerToolsWhen), + "stream": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Enable streaming response" })), + "stream_options": Schema.optionalKey(ChatStreamOptions), + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Sampling temperature (0-2)", "format": "double" }) + ), + "tool_choice": Schema.optionalKey(ChatToolChoice), + "tools": Schema.optionalKey( + Schema.Array(ChatFunctionTool).annotate({ "description": "Available tools for function calling" }) + ), + "top_a": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ + "description": + "Consider only tokens with \"sufficiently high\" probabilities based on the probability of the most likely token. Not all providers support this parameter.", + "format": "double" + }) + ), + "top_k": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Limits the model to choose from the top K most likely tokens at each step. A value of 1 means the model will always pick the most likely next token. Not all providers support this parameter." + }) + ), + "top_logprobs": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": "Number of top log probabilities to return (0-20)" + }) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Nucleus sampling parameter (0-1)", "format": "double" }) + ), + "trace": Schema.optionalKey(TraceConfig), + "user": Schema.optionalKey(Schema.String.annotate({ + "description": + "Per-end-user identifier for abuse isolation. Use a stable ID, hash, or pseudonym. When a provider requires a user identity, OpenRouter folds it into the hashed identity sent upstream and never forwards it raw. If omitted, requests use an account-level identity, so provider policy blocks can affect the whole account." + })) +}).annotate({ "description": "Chat completion request parameters", "identifier": "ChatRequest" }) +export type ChatStreamChunk = { + readonly "choices": ReadonlyArray + readonly "created": number + readonly "error"?: { + readonly "code": number + readonly "message": string + readonly "metadata"?: { readonly "error_type": ApiErrorType; readonly "provider_code"?: string } + } + readonly "id": string + readonly "model": string + readonly "object": "chat.completion.chunk" + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "service_tier"?: string | null + readonly "system_fingerprint"?: string + readonly "usage"?: ChatUsage +} +export const ChatStreamChunk = Schema.Struct({ + "choices": Schema.Array(ChatStreamChoice).annotate({ "description": "List of streaming chunk choices" }), + "created": Schema.Number.annotate({ "description": "Unix timestamp of creation" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "error": Schema.optionalKey( + Schema.Struct({ + "code": Schema.Number.annotate({ "description": "Error code", "format": "int32" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "message": Schema.String.annotate({ "description": "Error message" }), + "metadata": Schema.optionalKey( + Schema.Struct({ + "error_type": ApiErrorType, + "provider_code": Schema.optionalKey( + Schema.String.annotate({ "description": "Upstream provider-specific error code, when available" }) + ) + }).annotate({ "description": "Structured error metadata" }) + ) + }).annotate({ "description": "Error information" }) + ), + "id": Schema.String.annotate({ "description": "Unique chunk identifier" }), + "model": Schema.String.annotate({ "description": "Model used for completion" }), + "object": Schema.Literal("chat.completion.chunk"), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "service_tier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "The service tier used by the upstream provider for this request" + }) + ), + "system_fingerprint": Schema.optionalKey(Schema.String.annotate({ "description": "System fingerprint" })), + "usage": Schema.optionalKey(ChatUsage) +}).annotate({ "description": "Streaming chat completion chunk", "identifier": "ChatStreamChunk" }) +export type Inputs = string | Arrays_8 +export const Inputs = Schema.Union([Schema.String, Arrays_8]).annotate({ + "description": "Input for a response request - can be a string or array of items", + "identifier": "Inputs" +}) +export type CompletedEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.completed" +} +export const CompletedEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.completed") +}).annotate({ + "description": "Event emitted when a response has completed successfully", + "identifier": "CompletedEvent" +}) +export type CreatedEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.created" +} +export const CreatedEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.created") +}).annotate({ "description": "Event emitted when a response is created", "identifier": "CreatedEvent" }) +export type FailedEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.failed" +} +export const FailedEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.failed") +}).annotate({ "description": "Event emitted when a response has failed", "identifier": "FailedEvent" }) +export type IncompleteEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.incomplete" +} +export const IncompleteEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.incomplete") +}).annotate({ "description": "Event emitted when a response is incomplete", "identifier": "IncompleteEvent" }) +export type InProgressEvent = { + readonly "response": BaseResponsesResult + readonly "sequence_number": number + readonly "type": "response.in_progress" +} +export const InProgressEvent = Schema.Struct({ + "response": BaseResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.in_progress") +}).annotate({ "description": "Event emitted when a response is in progress", "identifier": "InProgressEvent" }) +export type OpenResponsesCreatedEvent = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.created" +} +export const OpenResponsesCreatedEvent = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.created") +}).annotate({ "description": "Event emitted when a response is created", "identifier": "OpenResponsesCreatedEvent" }) +export type OpenResponsesInProgressEvent = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.in_progress" +} +export const OpenResponsesInProgressEvent = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.in_progress") +}).annotate({ + "description": "Event emitted when a response is in progress", + "identifier": "OpenResponsesInProgressEvent" +}) +export type StreamEventsResponseCompleted = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.completed" +} +export const StreamEventsResponseCompleted = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.completed") +}).annotate({ + "description": "Event emitted when a response has completed successfully", + "identifier": "StreamEventsResponseCompleted" +}) +export type StreamEventsResponseFailed = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.failed" +} +export const StreamEventsResponseFailed = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.failed") +}).annotate({ "description": "Event emitted when a response has failed", "identifier": "StreamEventsResponseFailed" }) +export type StreamEventsResponseIncomplete = { + readonly "response": OpenResponsesResult + readonly "sequence_number": number + readonly "type": "response.incomplete" +} +export const StreamEventsResponseIncomplete = Schema.Struct({ + "response": OpenResponsesResult, + "sequence_number": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "type": Schema.Literal("response.incomplete") +}).annotate({ + "description": "Event emitted when a response is incomplete", + "identifier": "StreamEventsResponseIncomplete" +}) +export type BaseMessagesResult = { + readonly "container": AnthropicContainer + readonly "content": ReadonlyArray + readonly "id": string + readonly "model": string + readonly "role": "assistant" + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": ORAnthropicStopReason + readonly "stop_sequence": string | null + readonly "type": "message" + readonly "usage": { + readonly "cache_creation": AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": AnthropicServerToolUsage + readonly "service_tier": AnthropicServiceTier + readonly "iterations"?: ReadonlyArray + readonly "speed"?: AnthropicSpeed + } +} +export const BaseMessagesResult = Schema.Struct({ + "container": AnthropicContainer, + "content": Schema.Array(ORAnthropicContentBlock), + "id": Schema.String, + "model": Schema.String, + "role": Schema.Literal("assistant"), + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": ORAnthropicStopReason, + "stop_sequence": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("message"), + "usage": Schema.Struct({ + "cache_creation": AnthropicCacheCreation, + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "inference_geo": Schema.Union([Schema.String, Schema.Null]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": AnthropicServerToolUsage, + "service_tier": AnthropicServiceTier, + "iterations": Schema.optionalKey(Schema.Array(AnthropicUsageIteration)), + "speed": Schema.optionalKey(AnthropicSpeed) + }) +}).annotate({ + "description": "Base Anthropic Messages API response before OpenRouter extensions", + "identifier": "BaseMessagesResult" +}) +export type MessagesResult = { + readonly "container": AnthropicContainer + readonly "content": ReadonlyArray + readonly "id": string + readonly "model": string + readonly "role": "assistant" + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": ORAnthropicStopReason + readonly "stop_sequence": string | null + readonly "type": "message" + readonly "usage": { + readonly "cache_creation": { + readonly "ephemeral_1h_input_tokens": number + readonly "ephemeral_5m_input_tokens": number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": { readonly "thinking_tokens": number; readonly [x: string]: Schema.Json } | null + readonly "server_tool_use": { + readonly "web_fetch_requests": number + readonly "web_search_requests": number + readonly [x: string]: Schema.Json + } | null + readonly "service_tier": "standard" | "priority" | "batch" | null + readonly "iterations"?: ReadonlyArray< + { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": never + readonly "model"?: string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": "message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "message" + readonly "model"?: string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": never + readonly "model": string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": never + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": "advisor_message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "advisor_message" + readonly "model": string + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": "compaction" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model"?: string + readonly "type": "message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "model": string + readonly "type": "advisor_message" + } | { + readonly "cache_creation"?: { + readonly "ephemeral_1h_input_tokens"?: number + readonly "ephemeral_5m_input_tokens"?: number + readonly [x: string]: Schema.Json + } | null + readonly "cache_creation_input_tokens"?: number + readonly "cache_read_input_tokens"?: number + readonly "input_tokens"?: number + readonly "output_tokens"?: number + readonly "type": string + } + > + readonly "speed"?: "fast" | "standard" | null + readonly "cost"?: number | null + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + } + readonly "context_management"?: { + readonly "applied_edits": ReadonlyArray<{ readonly "type": string }> + readonly [x: string]: Schema.Json + } | null + readonly "openrouter_metadata"?: OpenRouterMetadata + readonly "provider"?: ProviderName +} +export const MessagesResult = Schema.Struct({ + "container": AnthropicContainer, + "content": Schema.Array(ORAnthropicContentBlock), + "id": Schema.String, + "model": Schema.String, + "role": Schema.Literal("assistant"), + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": ORAnthropicStopReason, + "stop_sequence": Schema.Union([Schema.String, Schema.Null]), + "type": Schema.Literal("message"), + "usage": Schema.Struct({ + "cache_creation": Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "ephemeral_5m_input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]), + "cache_creation_input_tokens": Schema.Union([ + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))]), + Schema.Union([Schema.Null]) + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))]), + Schema.Union([Schema.Null]) + ]), + "inference_geo": Schema.Union([Schema.Union([Schema.String]), Schema.Union([Schema.Null])]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "thinking_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]), + "server_tool_use": Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "web_fetch_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "web_search_requests": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]), + "service_tier": Schema.Union([ + Schema.Union([Schema.Literal("standard")]), + Schema.Union([Schema.Literal("priority")]), + Schema.Union([Schema.Literal("batch")]), + Schema.Union([Schema.Union([Schema.Null])]) + ]), + "iterations": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("compaction") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("compaction") + }) + ]), + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Never, + "model": Schema.optionalKey(Schema.String) + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Literal("message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("message"), + "model": Schema.optionalKey(Schema.String) + }) + ]), + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Never, + "model": Schema.String + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Never + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Literal("advisor_message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("advisor_message"), + "model": Schema.String + }) + ]), + Schema.Union([ + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.Literal("compaction") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.optionalKey(Schema.String), + "type": Schema.Literal("message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "model": Schema.String, + "type": Schema.Literal("advisor_message") + }), + Schema.Struct({ + "cache_creation": Schema.optionalKey( + Schema.Union([ + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ + "ephemeral_1h_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "ephemeral_5m_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ) + }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ) + ]), + Schema.Union([Schema.Null]) + ]) + ), + "cache_creation_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "cache_read_input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "input_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "output_tokens": Schema.optionalKey( + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "type": Schema.String + }) + ]) + ]))), + "speed": Schema.optionalKey( + Schema.Union([Schema.Literal("fast"), Schema.Literal("standard"), Schema.Union([Schema.Null])]) + ), + "cost": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey(Schema.Boolean) + }), + "context_management": Schema.optionalKey( + Schema.Union([ + Schema.StructWithRest( + Schema.Struct({ "applied_edits": Schema.Array(Schema.Struct({ "type": Schema.String })) }), + [Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" }))] + ), + Schema.Null + ]) + ), + "openrouter_metadata": Schema.optionalKey(OpenRouterMetadata), + "provider": Schema.optionalKey(ProviderName) +}).annotate({ + "description": "Non-streaming response from the Anthropic Messages API with OpenRouter extensions", + "identifier": "MessagesResult" +}) +export type MessagesStartEvent = { + readonly "message": { + readonly "container": AnthropicContainer + readonly "content": ReadonlyArray + readonly "id": string + readonly "model": string + readonly "provider"?: + | "AnyScale" + | "Atoma" + | "Cent-ML" + | "CrofAI" + | "Enfer" + | "GoPomelo" + | "HuggingFace" + | "Hyperbolic" + | "Hyperbolic 2" + | "InoCloud" + | "Kluster" + | "Lambda" + | "Lepton" + | "Lynn 2" + | "Lynn" + | "Mancer" + | "Modal" + | "Nineteen" + | "OctoAI" + | "Recursal" + | "Reflection" + | "Replicate" + | "SambaNova 2" + | "SF Compute" + | "Targon" + | "Together 2" + | "Ubicloud" + | "01.AI" + | "AkashML" + | "AI21" + | "AionLabs" + | "Alibaba" + | "Ambient" + | "Baidu" + | "Amazon Bedrock" + | "Amazon Nova" + | "Anthropic" + | "Arcee AI" + | "AtlasCloud" + | "Avian" + | "Azure" + | "BaseTen" + | "BytePlus" + | "Black Forest Labs" + | "Cerebras" + | "Chutes" + | "Cirrascale" + | "Clarifai" + | "Cloudflare" + | "Cohere" + | "CoreWeave" + | "Crucible" + | "Crusoe" + | "Darkbloom" + | "Decart" + | "Deepgram" + | "DeepInfra" + | "DeepSeek" + | "DekaLLM" + | "DigitalOcean" + | "Featherless" + | "Fireworks" + | "Fish Audio" + | "Friendli" + | "GMICloud" + | "Google" + | "Google AI Studio" + | "Groq" + | "HeyGen" + | "Inception" + | "Inceptron" + | "InferenceNet" + | "Ionstream" + | "Infermatic" + | "Io Net" + | "Inferact vLLM" + | "Inflection" + | "Liquid" + | "Mara" + | "Mancer 2" + | "Meta" + | "Minimax" + | "ModelRun" + | "Mistral" + | "Modular" + | "Moonshot AI" + | "Morph" + | "NCompass" + | "Nebius" + | "Nex AGI" + | "NextBit" + | "Novita" + | "Nvidia" + | "OpenAI" + | "OpenInference" + | "Parasail" + | "Poolside" + | "Perceptron" + | "Perplexity" + | "Phala" + | "Recraft" + | "Reka" + | "Relace" + | "Sail Research" + | "Sakana AI" + | "SambaNova" + | "Seed" + | "SiliconFlow" + | "Sourceful" + | "StepFun" + | "Stealth" + | "StreamLake" + | "Switchpoint" + | "Tencent" + | "Tenstorrent" + | "Together" + | "Upstage" + | "Venice" + | "Wafer" + | "WandB" + | "Quiver" + | "Krea" + | "Runway" + | "Xiaomi" + | "xAI" + | "Z.AI" + | "FakeProvider" + readonly "role": "assistant" + readonly "stop_details": AnthropicRefusalStopDetails + readonly "stop_reason": Schema.Json + readonly "stop_sequence": Schema.Json + readonly "type": "message" + readonly "usage": { + readonly "cache_creation": AnthropicCacheCreation + readonly "cache_creation_input_tokens": number | null + readonly "cache_read_input_tokens": number | null + readonly "inference_geo": string | null + readonly "input_tokens": number + readonly "output_tokens": number + readonly "output_tokens_details": AnthropicOutputTokensDetails + readonly "server_tool_use": AnthropicServerToolUsage + readonly "service_tier": AnthropicServiceTier + readonly "iterations"?: ReadonlyArray + readonly "speed"?: AnthropicSpeed + } + } + readonly "type": "message_start" +} +export const MessagesStartEvent = Schema.Struct({ + "message": Schema.Struct({ + "container": AnthropicContainer, + "content": Schema.Array(ORAnthropicContentBlock), + "id": Schema.String, + "model": Schema.String, + "provider": Schema.optionalKey( + Schema.Literals([ + "AnyScale", + "Atoma", + "Cent-ML", + "CrofAI", + "Enfer", + "GoPomelo", + "HuggingFace", + "Hyperbolic", + "Hyperbolic 2", + "InoCloud", + "Kluster", + "Lambda", + "Lepton", + "Lynn 2", + "Lynn", + "Mancer", + "Modal", + "Nineteen", + "OctoAI", + "Recursal", + "Reflection", + "Replicate", + "SambaNova 2", + "SF Compute", + "Targon", + "Together 2", + "Ubicloud", + "01.AI", + "AkashML", + "AI21", + "AionLabs", + "Alibaba", + "Ambient", + "Baidu", + "Amazon Bedrock", + "Amazon Nova", + "Anthropic", + "Arcee AI", + "AtlasCloud", + "Avian", + "Azure", + "BaseTen", + "BytePlus", + "Black Forest Labs", + "Cerebras", + "Chutes", + "Cirrascale", + "Clarifai", + "Cloudflare", + "Cohere", + "CoreWeave", + "Crucible", + "Crusoe", + "Darkbloom", + "Decart", + "Deepgram", + "DeepInfra", + "DeepSeek", + "DekaLLM", + "DigitalOcean", + "Featherless", + "Fireworks", + "Fish Audio", + "Friendli", + "GMICloud", + "Google", + "Google AI Studio", + "Groq", + "HeyGen", + "Inception", + "Inceptron", + "InferenceNet", + "Ionstream", + "Infermatic", + "Io Net", + "Inferact vLLM", + "Inflection", + "Liquid", + "Mara", + "Mancer 2", + "Meta", + "Minimax", + "ModelRun", + "Mistral", + "Modular", + "Moonshot AI", + "Morph", + "NCompass", + "Nebius", + "Nex AGI", + "NextBit", + "Novita", + "Nvidia", + "OpenAI", + "OpenInference", + "Parasail", + "Poolside", + "Perceptron", + "Perplexity", + "Phala", + "Recraft", + "Reka", + "Relace", + "Sail Research", + "Sakana AI", + "SambaNova", + "Seed", + "SiliconFlow", + "Sourceful", + "StepFun", + "Stealth", + "StreamLake", + "Switchpoint", + "Tencent", + "Tenstorrent", + "Together", + "Upstage", + "Venice", + "Wafer", + "WandB", + "Quiver", + "Krea", + "Runway", + "Xiaomi", + "xAI", + "Z.AI", + "FakeProvider" + ]) + ), + "role": Schema.Literal("assistant"), + "stop_details": AnthropicRefusalStopDetails, + "stop_reason": Schema.Json.annotate({ "expected": "JSON value" }), + "stop_sequence": Schema.Json.annotate({ "expected": "JSON value" }), + "type": Schema.Literal("message"), + "usage": Schema.Struct({ + "cache_creation": AnthropicCacheCreation, + "cache_creation_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "cache_read_input_tokens": Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + Schema.Null + ]), + "inference_geo": Schema.Union([Schema.String, Schema.Null]), + "input_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "output_tokens_details": AnthropicOutputTokensDetails, + "server_tool_use": AnthropicServerToolUsage, + "service_tier": AnthropicServiceTier, + "iterations": Schema.optionalKey(Schema.Array(AnthropicUsageIteration)), + "speed": Schema.optionalKey(AnthropicSpeed) + }) + }), + "type": Schema.Literal("message_start") +}).annotate({ "description": "Event sent at the start of a streaming message", "identifier": "MessagesStartEvent" }) +export type ImageStreamingResponse = { + readonly "data": + | ImageGenPartialImageEvent + | ImageGenTextChunkEvent + | ImageGenCompletedEvent + | ImageGenStreamErrorEvent +} +export const ImageStreamingResponse = Schema.Struct({ + "data": Schema.Union([ + ImageGenPartialImageEvent, + ImageGenTextChunkEvent, + ImageGenCompletedEvent, + ImageGenStreamErrorEvent + ]) +}).annotate({ "identifier": "ImageStreamingResponse" }) +export type ChatStreamingResponse = { readonly "data": ChatStreamChunk } +export const ChatStreamingResponse = Schema.Struct({ "data": ChatStreamChunk }).annotate({ + "identifier": "ChatStreamingResponse" +}) +export type ResponsesRequest = { + readonly "background"?: boolean | null + readonly "cache_control"?: AnthropicCacheControlDirective + readonly "debug"?: ChatDebugOptions + readonly "frequency_penalty"?: number | null + readonly "image_config"?: ImageConfig + readonly "include"?: ReadonlyArray | null + readonly "input"?: Inputs + readonly "instructions"?: string | null + readonly "max_output_tokens"?: number | null + readonly "max_tool_calls"?: number | null + readonly "metadata"?: RequestMetadata + readonly "modalities"?: ReadonlyArray + readonly "model"?: string + readonly "models"?: ReadonlyArray + readonly "parallel_tool_calls"?: boolean | null + readonly "plugins"?: ReadonlyArray< + | AutoRouterPlugin + | AutoBetaRouterPlugin + | ModerationPlugin + | WebSearchPlugin + | WebFetchPlugin + | FileParserPlugin + | ResponseHealingPlugin + | ContextCompressionPlugin + | ParetoRouterPlugin + | FusionPlugin + > + readonly "presence_penalty"?: number | null + readonly "previous_response_id"?: Schema.Json + readonly "prompt"?: StoredPromptTemplate + readonly "prompt_cache_key"?: string | null + readonly "prompt_cache_options"?: PromptCacheOptions + readonly "provider"?: ProviderPreferences + readonly "reasoning"?: ReasoningConfig + readonly "route"?: DeprecatedRoute + readonly "safety_identifier"?: string | null + readonly "service_tier"?: "auto" | "default" | "flex" | "priority" | "scale" | null + readonly "session_id"?: string + readonly "stop_server_tools_when"?: StopServerToolsWhen + readonly "store"?: false + readonly "stream"?: boolean + readonly "temperature"?: number | null + readonly "text"?: TextExtendedConfig + readonly "tool_choice"?: OpenAIResponsesToolChoice + readonly "tools"?: ReadonlyArray< + | { + readonly "description"?: string | null + readonly "name": string + readonly "parameters": { readonly [x: string]: Schema.Json } | null + readonly "strict"?: boolean | null + readonly "type": "function" + } + | Preview_WebSearchServerTool + | Preview_20250311_WebSearchServerTool + | Legacy_WebSearchServerTool + | WebSearchServerTool + | FileSearchServerTool + | ComputerUseServerTool + | CodeInterpreterServerTool + | McpServerTool + | ImageGenerationServerTool + | CodexLocalShellTool + | ShellServerTool + | ApplyPatchServerTool + | CustomTool + | NamespaceTool + | AdvisorServerTool_OpenRouter + | SubagentServerTool_OpenRouter + | DatetimeServerTool + | FilesServerTool + | FusionServerTool_OpenRouter + | ImageGenerationServerTool_OpenRouter + | SearchModelsServerTool_OpenRouter + | WebFetchServerTool + | WebSearchServerTool_OpenRouter + | ApplyPatchServerTool_OpenRouter + | BashServerTool + | ShellServerTool_OpenRouter + > + readonly "top_k"?: number + readonly "top_logprobs"?: number | null + readonly "top_p"?: number | null + readonly "trace"?: TraceConfig + readonly "truncation"?: OpenAIResponsesTruncation + readonly "user"?: string +} +export const ResponsesRequest = Schema.Struct({ + "background": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "cache_control": Schema.optionalKey(AnthropicCacheControlDirective), + "debug": Schema.optionalKey(ChatDebugOptions), + "frequency_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "image_config": Schema.optionalKey(ImageConfig), + "include": Schema.optionalKey(Schema.Union([Schema.Array(ResponseIncludesEnum), Schema.Null])), + "input": Schema.optionalKey(Inputs), + "instructions": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "max_output_tokens": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "max_tool_calls": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]).annotate({ + "description": + "Maximum number of server-tool (e.g. `openrouter:web_search`) agent steps the model may take during a request. Defaults to 30, which is also the maximum. Ignored when `stop_server_tools_when` is set." + }) + ), + "metadata": Schema.optionalKey(RequestMetadata), + "modalities": Schema.optionalKey( + Schema.Array(OutputModalityEnum).annotate({ + "description": "Output modalities for the response. Supported values are \"text\" and \"image\"." + }) + ), + "model": Schema.optionalKey(Schema.String), + "models": Schema.optionalKey(Schema.Array(Schema.String)), + "parallel_tool_calls": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "plugins": Schema.optionalKey( + Schema.Array( + Schema.Union([ + AutoRouterPlugin, + AutoBetaRouterPlugin, + ModerationPlugin, + WebSearchPlugin, + WebFetchPlugin, + FileParserPlugin, + ResponseHealingPlugin, + ContextCompressionPlugin, + ParetoRouterPlugin, + FusionPlugin + ], { mode: "oneOf" }) + ).annotate({ "description": "Plugins you want to enable for this request, including their settings." }) + ), + "presence_penalty": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "previous_response_id": Schema.optionalKey( + Schema.Json.annotate({ + "expected": "JSON value", + "description": + "Not supported. The Responses API is stateless: no responses are stored, so a previous response cannot be referenced. Requests with a non-null value are rejected with a 400 error. Send the full conversation history in `input` instead." + }) + ), + "prompt": Schema.optionalKey(StoredPromptTemplate), + "prompt_cache_key": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "prompt_cache_options": Schema.optionalKey(PromptCacheOptions), + "provider": Schema.optionalKey(ProviderPreferences), + "reasoning": Schema.optionalKey(ReasoningConfig), + "route": Schema.optionalKey(DeprecatedRoute), + "safety_identifier": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Recommended per-end-user identifier for abuse isolation. Use a stable ID, hash, or pseudonym. When a provider requires a user identity, OpenRouter folds it into the hashed identity sent upstream and never forwards it raw. If omitted, requests use an account-level identity, so provider policy blocks can affect the whole account." + }) + ), + "service_tier": Schema.optionalKey( + Schema.Union([ + Schema.Literal("auto"), + Schema.Literal("default"), + Schema.Literal("flex"), + Schema.Literal("priority"), + Schema.Literal("scale"), + Schema.Null + ]) + ), + "session_id": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier for grouping related requests (e.g., a conversation or agent workflow). When provided, OpenRouter uses it as the sticky routing key, routing all requests in the session to the same provider to maximize prompt cache hits. Also used for observability grouping. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ), + "stop_server_tools_when": Schema.optionalKey(StopServerToolsWhen), + "store": Schema.optionalKey(Schema.Literal(false)), + "stream": Schema.optionalKey(Schema.Boolean), + "temperature": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "text": Schema.optionalKey(TextExtendedConfig), + "tool_choice": Schema.optionalKey(OpenAIResponsesToolChoice), + "tools": Schema.optionalKey(Schema.Array(Schema.Union([ + Schema.Struct({ + "description": Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + "name": Schema.String, + "parameters": Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ "expected": "JSON value" })), + Schema.Null + ]), + "strict": Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + "type": Schema.Literal("function") + }).annotate({ "description": "Function tool definition" }), + Preview_WebSearchServerTool, + Preview_20250311_WebSearchServerTool, + Legacy_WebSearchServerTool, + WebSearchServerTool, + FileSearchServerTool, + ComputerUseServerTool, + CodeInterpreterServerTool, + McpServerTool, + ImageGenerationServerTool, + CodexLocalShellTool, + ShellServerTool, + ApplyPatchServerTool, + CustomTool, + NamespaceTool, + AdvisorServerTool_OpenRouter, + SubagentServerTool_OpenRouter, + DatetimeServerTool, + FilesServerTool, + FusionServerTool_OpenRouter, + ImageGenerationServerTool_OpenRouter, + SearchModelsServerTool_OpenRouter, + WebFetchServerTool, + WebSearchServerTool_OpenRouter, + ApplyPatchServerTool_OpenRouter, + BashServerTool, + ShellServerTool_OpenRouter + ]))), + "top_k": Schema.optionalKey(Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" }))), + "top_logprobs": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), Schema.Null]) + ), + "top_p": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "format": "double" }) + ), + "trace": Schema.optionalKey(TraceConfig), + "truncation": Schema.optionalKey(OpenAIResponsesTruncation), + "user": Schema.optionalKey( + Schema.String.annotate({ + "description": + "A unique identifier representing your end-user, which helps distinguish between different users of your app. This allows your app to identify specific users in case of abuse reports, preventing your entire app from being affected by the actions of individual users. Maximum of 256 characters." + }).check(Schema.isMaxLength(256).annotate({ "expected": "a value with a length of at most 256" })) + ) +}).annotate({ "description": "Request schema for Responses endpoint", "identifier": "ResponsesRequest" }) +export type StreamEvents = + | OpenResponsesCreatedEvent + | OpenResponsesInProgressEvent + | StreamEventsResponseCompleted + | StreamEventsResponseIncomplete + | StreamEventsResponseFailed + | ErrorEvent + | StreamEventsResponseOutputItemAdded + | StreamEventsResponseOutputItemDone + | ContentPartAddedEvent + | ContentPartDoneEvent + | TextDeltaEvent + | TextDoneEvent + | RefusalDeltaEvent + | RefusalDoneEvent + | AnnotationAddedEvent + | FunctionCallArgsDeltaEvent + | FunctionCallArgsDoneEvent + | ReasoningDeltaEvent + | ReasoningDoneEvent + | ReasoningSummaryPartAddedEvent + | ReasoningSummaryPartDoneEvent + | ReasoningSummaryTextDeltaEvent + | ReasoningSummaryTextDoneEvent + | ImageGenCallInProgressEvent + | ImageGenCallGeneratingEvent + | ImageGenCallPartialImageEvent + | ImageGenCallCompletedEvent + | WebSearchCallInProgressEvent + | WebSearchCallSearchingEvent + | WebSearchCallCompletedEvent + | CustomToolCallInputDeltaEvent + | CustomToolCallInputDoneEvent + | ApplyPatchCallOperationDiffDeltaEvent + | ApplyPatchCallOperationDiffDoneEvent + | FusionCallInProgressEvent + | FusionCallPanelAddedEvent + | FusionCallPanelDeltaEvent + | FusionCallPanelReasoningDeltaEvent + | FusionCallPanelCompletedEvent + | FusionCallPanelFailedEvent + | FusionCallAnalysisInProgressEvent + | FusionCallAnalysisCompletedEvent + | FusionCallCompletedEvent + | DebugEvent +export const StreamEvents: Schema.Schema = Schema.Union([ + OpenResponsesCreatedEvent, + OpenResponsesInProgressEvent, + StreamEventsResponseCompleted, + StreamEventsResponseIncomplete, + StreamEventsResponseFailed, + ErrorEvent, + StreamEventsResponseOutputItemAdded, + StreamEventsResponseOutputItemDone, + ContentPartAddedEvent, + ContentPartDoneEvent, + TextDeltaEvent, + TextDoneEvent, + RefusalDeltaEvent, + RefusalDoneEvent, + AnnotationAddedEvent, + FunctionCallArgsDeltaEvent, + FunctionCallArgsDoneEvent, + ReasoningDeltaEvent, + ReasoningDoneEvent, + ReasoningSummaryPartAddedEvent, + ReasoningSummaryPartDoneEvent, + ReasoningSummaryTextDeltaEvent, + ReasoningSummaryTextDoneEvent, + ImageGenCallInProgressEvent, + ImageGenCallGeneratingEvent, + ImageGenCallPartialImageEvent, + ImageGenCallCompletedEvent, + WebSearchCallInProgressEvent, + WebSearchCallSearchingEvent, + WebSearchCallCompletedEvent, + CustomToolCallInputDeltaEvent, + CustomToolCallInputDoneEvent, + ApplyPatchCallOperationDiffDeltaEvent, + ApplyPatchCallOperationDiffDoneEvent, + FusionCallInProgressEvent, + FusionCallPanelAddedEvent, + FusionCallPanelDeltaEvent, + FusionCallPanelReasoningDeltaEvent, + FusionCallPanelCompletedEvent, + FusionCallPanelFailedEvent, + FusionCallAnalysisInProgressEvent, + FusionCallAnalysisCompletedEvent, + FusionCallCompletedEvent, + DebugEvent +], { mode: "oneOf" }).annotate({ + "description": "Union of all possible event types emitted during response streaming", + "identifier": "StreamEvents" +}) +export type MessagesStreamEvents = + | MessagesStartEvent + | MessagesDeltaEvent + | MessagesStopEvent + | MessagesContentBlockStartEvent + | MessagesContentBlockDeltaEvent + | MessagesContentBlockStopEvent + | MessagesPingEvent + | MessagesErrorEvent +export const MessagesStreamEvents = Schema.Union([ + MessagesStartEvent, + MessagesDeltaEvent, + MessagesStopEvent, + MessagesContentBlockStartEvent, + MessagesContentBlockDeltaEvent, + MessagesContentBlockStopEvent, + MessagesPingEvent, + MessagesErrorEvent +], { mode: "oneOf" }).annotate({ + "description": "Union of all possible streaming events", + "identifier": "MessagesStreamEvents" +}) +export type ResponsesStreamingResponse = { readonly "data": StreamEvents } +export const ResponsesStreamingResponse: Schema.Schema = Schema.Struct({ + "data": StreamEvents +}).annotate({ "identifier": "ResponsesStreamingResponse" }) +export type MessagesStreamingResponse = { readonly "data": MessagesStreamEvents; readonly "event": string } +export const MessagesStreamingResponse = Schema.Struct({ "data": MessagesStreamEvents, "event": Schema.String }) + .annotate({ "identifier": "MessagesStreamingResponse" }) +// schemas +export type GetUserActivityParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "date"?: string + readonly "api_key_hash"?: string + readonly "user_id"?: string +} +export const GetUserActivityParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "date": Schema.optionalKey( + Schema.String.annotate({ "description": "Filter by a single UTC date in the last 30 days (YYYY-MM-DD format)." }) + ), + "api_key_hash": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter by API key hash (SHA-256 hex string, as returned by the keys API)." + }) + ), + "user_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter by org member user ID. Only applicable for organization accounts." + }) + ) +}) +export type GetUserActivity200 = ActivityResponse +export const GetUserActivity200 = ActivityResponse +export type GetUserActivity400 = BadRequestResponse +export const GetUserActivity400 = BadRequestResponse +export type GetUserActivity401 = UnauthorizedResponse +export const GetUserActivity401 = UnauthorizedResponse +export type GetUserActivity403 = ForbiddenResponse +export const GetUserActivity403 = ForbiddenResponse +export type GetUserActivity404 = NotFoundResponse +export const GetUserActivity404 = NotFoundResponse +export type GetUserActivity500 = InternalServerResponse +export const GetUserActivity500 = InternalServerResponse +export type GetAnalyticsMetaParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetAnalyticsMetaParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetAnalyticsMeta200 = { + readonly "data": { + readonly "dimensions": ReadonlyArray<{ readonly "display_label": string; readonly "name": string }> + readonly "granularities": ReadonlyArray< + { readonly "display_label": string; readonly "name": "minute" | "hour" | "day" | "week" | "month" } + > + readonly "metrics": ReadonlyArray< + { + readonly "display_format": "number" | "currency" | "percent" | "latency" | "throughput" + readonly "display_label": string + readonly "is_rate": boolean + readonly "name": string + } + > + readonly "operators": ReadonlyArray< + { + readonly "name": "eq" | "neq" | "in" | "not_in" | "gt" | "gte" | "lt" | "lte" + readonly "value_type": "scalar" | "array" + } + > + } +} +export const GetAnalyticsMeta200 = Schema.Struct({ + "data": Schema.Struct({ + "dimensions": Schema.Array( + Schema.Struct({ + "display_label": Schema.String.annotate({ "description": "Human-readable label" }), + "name": Schema.String.annotate({ "description": "Dimension identifier used in query requests" }) + }) + ), + "granularities": Schema.Array( + Schema.Struct({ + "display_label": Schema.String.annotate({ "description": "Human-readable label" }), + "name": Schema.Literals(["minute", "hour", "day", "week", "month"]).annotate({ + "description": "Granularity identifier" + }) + }) + ), + "metrics": Schema.Array(Schema.Struct({ + "display_format": Schema.Literals(["number", "currency", "percent", "latency", "throughput"]).annotate({ + "description": + "How this metric value should be formatted for display (e.g. percent → multiply by 100 and append %, currency → prefix with $)" + }), + "display_label": Schema.String.annotate({ "description": "Human-readable label" }), + "is_rate": Schema.Boolean.annotate({ + "description": "Whether this metric is a rate/ratio (averaged, not summed)" + }), + "name": Schema.String.annotate({ "description": "Metric identifier used in query requests" }) + })), + "operators": Schema.Array(Schema.Struct({ + "name": Schema.Literals(["eq", "neq", "in", "not_in", "gt", "gte", "lt", "lte"]).annotate({ + "description": "Operator identifier used in filter definitions" + }), + "value_type": Schema.Literals(["scalar", "array"]).annotate({ + "description": "Whether the operator expects a single value or an array" + }) + })) + }) +}) +export type GetAnalyticsMeta401 = UnauthorizedResponse +export const GetAnalyticsMeta401 = UnauthorizedResponse +export type GetAnalyticsMeta403 = ForbiddenResponse +export const GetAnalyticsMeta403 = ForbiddenResponse +export type GetAnalyticsMeta500 = InternalServerResponse +export const GetAnalyticsMeta500 = InternalServerResponse +export type QueryAnalyticsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const QueryAnalyticsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type QueryAnalyticsRequestJson = { + readonly "classifier_dimensions"?: { + readonly "classifier_id": string + readonly "dimension_names"?: ReadonlyArray + readonly "include_nulls"?: boolean + } + readonly "classifier_filters"?: { + readonly "classifier_id": string + readonly "filters": ReadonlyArray< + { + readonly "field": string + readonly "operator": string + readonly "value": string | number | ReadonlyArray + } + > + } + readonly "dimensions"?: ReadonlyArray + readonly "filters"?: ReadonlyArray< + { + readonly "field": string + readonly "operator": string + readonly "value": string | number | ReadonlyArray + } + > + readonly "granularity"?: string + readonly "group_limit"?: number + readonly "limit"?: number + readonly "metrics": ReadonlyArray + readonly "order_by"?: { readonly "direction": "asc" | "desc"; readonly "field": string } + readonly "time_range"?: { readonly "end": string; readonly "start": string } +} +export const QueryAnalyticsRequestJson = Schema.Struct({ + "classifier_dimensions": Schema.optionalKey( + Schema.Struct({ + "classifier_id": Schema.String.annotate({ + "description": "UUID of the classifier whose tags to group by.", + "format": "uuid" + }), + "dimension_names": Schema.optionalKey( + Schema.Array( + Schema.String.annotate({ + "description": + "Classifier dimension name (snake_case identifier). When exactly one name is provided, the response uses it as the column key; with multiple names or none, the response uses `clf_dimension_name`/`clf_dimension_value` columns." + }) + ).check(Schema.isMaxLength(10).annotate({ "expected": "a value with a length of at most 10" })) + ), + "include_nulls": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": + "When true, also include generations that have no tag from this classifier. Defaults to false, which returns only classified generations." + }) + ) + }).annotate({ + "description": + "Group results by custom classifier tags, breaking down metrics by the specified dimension values. Requires an active classifier on the workspace." + }) + ), + "classifier_filters": Schema.optionalKey( + Schema.Struct({ + "classifier_id": Schema.String.annotate({ + "description": + "UUID of the classifier whose tags to filter by. Must match classifier_dimensions.classifier_id when both are specified.", + "format": "uuid" + }), + "filters": Schema.Array(Schema.Struct({ + "field": Schema.String.annotate({ + "description": + "Classifier dimension name to filter on (snake_case identifier, e.g. \"department\", \"work_type\")." + }), + "operator": Schema.String.annotate({ + "description": + "Filter operator. Only equality/set operators are supported (eq, neq, in, not_in) — ordered comparisons are not available because classification values are strings." + }), + "value": Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + Schema.Array( + Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ]) + ) + ]).annotate({ + "description": "Filter value. Use a scalar (string or number) for eq/neq, or an array for in/not_in." + }) + })).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })).check( + Schema.isMaxLength(10).annotate({ "expected": "a value with a length of at most 10" }) + ) + }).annotate({ + "description": + "Filter results to generations with specific classifier tag values. Can be combined with classifier_dimensions (must use the same classifier_id) or used independently with standard dimensions." + }) + ), + "dimensions": Schema.optionalKey( + Schema.Array( + Schema.String.annotate({ + "description": "Dimension to group by (up to 2). Use the /meta endpoint for available dimensions." + }) + ).check(Schema.isMaxLength(2).annotate({ "expected": "a value with a length of at most 2" })) + ), + "filters": Schema.optionalKey( + Schema.Array(Schema.Struct({ + "field": Schema.String.annotate({ + "description": "Dimension to filter on. Use the /meta endpoint for available dimensions." + }), + "operator": Schema.String.annotate({ "description": "Filter operator" }), + "value": Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + Schema.Array( + Schema.Union([ + Schema.String, + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ]) + ) + ]).annotate({ + "description": + "Filter value (scalar or array depending on operator). Several dimensions are enriched in responses (returned as human-readable labels), but filters must use the underlying ID: `api_key_id` — numeric ID (from generation metadata) or key hash (64-char hex from GET /api/v1/keys, resolved server-side); `user` — Clerk user ID (e.g. \"user_abc123\"), not the display name; `workspace` — workspace UUID, not the workspace name; `app` — numeric app ID, not the app title; `model` — permaslug (e.g. \"openai/gpt-4o\"), not the display name. Other dimensions (provider, origin, country, etc.) are not enriched and accept the value as returned." + }) + })).check(Schema.isMaxLength(20).annotate({ "expected": "a value with a length of at most 20" })) + ), + "granularity": Schema.optionalKey(Schema.String.annotate({ "description": "Time granularity" })), + "group_limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum rows per distinct combination of dimensions. When omitted on time-series queries (granularity + dimensions), auto-computed to avoid truncating time windows. Explicit values override the default and may truncate time buckets if set lower than the number of buckets in the range. Ignored when no dimensions are specified." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum total rows returned. Defaults to 1000. On time-series queries with dimensions and no explicit group_limit, the server may raise this to accommodate the expected number of unique time-bucket/dimension combinations." + }).check(Schema.isInt().annotate({ "expected": "an integer" })) + ), + "metrics": Schema.Array(Schema.String.annotate({ "description": "Metric name" })).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "order_by": Schema.optionalKey( + Schema.Struct({ + "direction": Schema.Literals(["asc", "desc"]), + "field": Schema.String.annotate({ + "description": + "Field to order by: a metric included in `metrics` (or \"request_count\", which may be ordered by without being requested), a requested dimension, or \"date\"." + }) + }) + ), + "time_range": Schema.optionalKey( + Schema.Struct({ + "end": Schema.String.annotate({ "format": "date-time" }), + "start": Schema.String.annotate({ "format": "date-time" }) + }) + ) +}) +export type QueryAnalytics200 = { + readonly "data": { + readonly "cachedAt"?: number + readonly "data": ReadonlyArray<{}> + readonly "metadata": { + readonly "query_time_ms": number + readonly "row_count": number + readonly "truncated": boolean + } + readonly "warnings"?: ReadonlyArray + } +} +export const QueryAnalytics200 = Schema.Struct({ + "data": Schema.Struct({ + "cachedAt": Schema.optionalKey( + Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "data": Schema.Array( + Schema.Struct({}).annotate({ "description": "A row of analytics data with metric/dimension values" }) + ), + "metadata": Schema.Struct({ + "query_time_ms": Schema.Number.annotate({ "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "row_count": Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })), + "truncated": Schema.Boolean + }), + "warnings": Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + "description": + "Warnings about filter resolution issues (e.g. unresolvable api_key_id hashes). The query still runs normally; these inform the caller that some filter values could not be resolved." + }) + ) + }) +}) +export type QueryAnalytics400 = BadRequestResponse +export const QueryAnalytics400 = BadRequestResponse +export type QueryAnalytics401 = UnauthorizedResponse +export const QueryAnalytics401 = UnauthorizedResponse +export type QueryAnalytics403 = ForbiddenResponse +export const QueryAnalytics403 = ForbiddenResponse +export type QueryAnalytics408 = RequestTimeoutResponse +export const QueryAnalytics408 = RequestTimeoutResponse +export type QueryAnalytics500 = InternalServerResponse +export const QueryAnalytics500 = InternalServerResponse +export type CreateAudioSpeechParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateAudioSpeechParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateAudioSpeechRequestJson = SpeechRequest +export const CreateAudioSpeechRequestJson = SpeechRequest +export type CreateAudioSpeech400 = BadRequestResponse +export const CreateAudioSpeech400 = BadRequestResponse +export type CreateAudioSpeech401 = UnauthorizedResponse +export const CreateAudioSpeech401 = UnauthorizedResponse +export type CreateAudioSpeech402 = PaymentRequiredResponse +export const CreateAudioSpeech402 = PaymentRequiredResponse +export type CreateAudioSpeech404 = NotFoundResponse +export const CreateAudioSpeech404 = NotFoundResponse +export type CreateAudioSpeech429 = TooManyRequestsResponse +export const CreateAudioSpeech429 = TooManyRequestsResponse +export type CreateAudioSpeech500 = InternalServerResponse +export const CreateAudioSpeech500 = InternalServerResponse +export type CreateAudioSpeech502 = BadGatewayResponse +export const CreateAudioSpeech502 = BadGatewayResponse +export type CreateAudioSpeech503 = ServiceUnavailableResponse +export const CreateAudioSpeech503 = ServiceUnavailableResponse +export type CreateAudioSpeech524 = EdgeNetworkTimeoutResponse +export const CreateAudioSpeech524 = EdgeNetworkTimeoutResponse +export type CreateAudioSpeech529 = ProviderOverloadedResponse +export const CreateAudioSpeech529 = ProviderOverloadedResponse +export type CreateAudioTranscriptionsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateAudioTranscriptionsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateAudioTranscriptionsRequestJson = STTRequest +export const CreateAudioTranscriptionsRequestJson = STTRequest +export type CreateAudioTranscriptionsRequestFormData = { + readonly "file": string + readonly "language"?: string + readonly "model": string + readonly "response_format"?: "json" | "verbose_json" + readonly "temperature"?: number + readonly "timestamp_granularities[]"?: ReadonlyArray<"word" | "segment"> +} +export const CreateAudioTranscriptionsRequestFormData = Schema.Struct({ + "file": Schema.String.annotate({ + "description": + "The audio file to transcribe. The format is derived from the filename extension or the file part content type. Max 25 MB; send larger files as base64 JSON via input_audio.", + "format": "binary" + }), + "language": Schema.optionalKey( + Schema.String.annotate({ "description": "The language of the input audio (ISO-639-1)." }) + ), + "model": Schema.String.annotate({ "description": "The model to use for transcription." }), + "response_format": Schema.optionalKey( + Schema.Literals(["json", "verbose_json"]).annotate({ + "description": + "The response format. \"json\" (default) returns { text, usage }; \"verbose_json\" additionally returns task, language, duration, and segment-level timestamps (OpenAI-compatible providers only)." + }) + ), + "temperature": Schema.optionalKey( + Schema.Number.annotate({ "description": "The sampling temperature." }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "timestamp_granularities[]": Schema.optionalKey( + Schema.Array(Schema.Literals(["word", "segment"])).annotate({ + "description": + "Timestamp detail levels to include when response_format is \"verbose_json\". \"word\" additionally returns word-level timestamps in the words array." + }) + ) +}) +export type CreateAudioTranscriptions200 = STTResponse +export const CreateAudioTranscriptions200 = STTResponse +export type CreateAudioTranscriptions400 = BadRequestResponse +export const CreateAudioTranscriptions400 = BadRequestResponse +export type CreateAudioTranscriptions401 = UnauthorizedResponse +export const CreateAudioTranscriptions401 = UnauthorizedResponse +export type CreateAudioTranscriptions402 = PaymentRequiredResponse +export const CreateAudioTranscriptions402 = PaymentRequiredResponse +export type CreateAudioTranscriptions404 = NotFoundResponse +export const CreateAudioTranscriptions404 = NotFoundResponse +export type CreateAudioTranscriptions429 = TooManyRequestsResponse +export const CreateAudioTranscriptions429 = TooManyRequestsResponse +export type CreateAudioTranscriptions500 = InternalServerResponse +export const CreateAudioTranscriptions500 = InternalServerResponse +export type CreateAudioTranscriptions502 = BadGatewayResponse +export const CreateAudioTranscriptions502 = BadGatewayResponse +export type CreateAudioTranscriptions503 = ServiceUnavailableResponse +export const CreateAudioTranscriptions503 = ServiceUnavailableResponse +export type CreateAudioTranscriptions524 = EdgeNetworkTimeoutResponse +export const CreateAudioTranscriptions524 = EdgeNetworkTimeoutResponse +export type CreateAudioTranscriptions529 = ProviderOverloadedResponse +export const CreateAudioTranscriptions529 = ProviderOverloadedResponse +export type ExchangeAuthCodeForAPIKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ExchangeAuthCodeForAPIKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ExchangeAuthCodeForAPIKeyRequestJson = { + readonly "code": string + readonly "code_challenge_method"?: "S256" | "plain" | null + readonly "code_verifier"?: string +} +export const ExchangeAuthCodeForAPIKeyRequestJson = Schema.Struct({ + "code": Schema.String.annotate({ "description": "The authorization code received from the OAuth redirect" }), + "code_challenge_method": Schema.optionalKey( + Schema.Union([Schema.Literal("S256"), Schema.Literal("plain"), Schema.Null]).annotate({ + "description": "The method used to generate the code challenge" + }) + ), + "code_verifier": Schema.optionalKey( + Schema.String.annotate({ + "description": "The code verifier if code_challenge was used in the authorization request" + }) + ) +}) +export type ExchangeAuthCodeForAPIKey200 = { readonly "key": string; readonly "user_id": string | null } +export const ExchangeAuthCodeForAPIKey200 = Schema.Struct({ + "key": Schema.String.annotate({ "description": "The API key to use for OpenRouter requests" }), + "user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "User ID associated with the API key" + }) +}) +export type ExchangeAuthCodeForAPIKey400 = BadRequestResponse +export const ExchangeAuthCodeForAPIKey400 = BadRequestResponse +export type ExchangeAuthCodeForAPIKey403 = ForbiddenResponse +export const ExchangeAuthCodeForAPIKey403 = ForbiddenResponse +export type ExchangeAuthCodeForAPIKey500 = InternalServerResponse +export const ExchangeAuthCodeForAPIKey500 = InternalServerResponse +export type CreateAuthKeysCodeParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateAuthKeysCodeParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateAuthKeysCodeRequestJson = { + readonly "callback_url": string + readonly "code_challenge"?: string + readonly "code_challenge_method"?: "S256" | "plain" + readonly "expires_at"?: string | null + readonly "key_label"?: string + readonly "limit"?: number + readonly "spawn_agent"?: string + readonly "spawn_cloud"?: string + readonly "usage_limit_type"?: "daily" | "weekly" | "monthly" + readonly "workspace_id"?: string +} +export const CreateAuthKeysCodeRequestJson = Schema.Struct({ + "callback_url": Schema.String.annotate({ + "description": + "The callback URL to redirect to after authorization. Supports https URLs and localhost/127.0.0.1 URLs on any port for local CLI tools.", + "format": "uri" + }), + "code_challenge": Schema.optionalKey( + Schema.String.annotate({ "description": "PKCE code challenge for enhanced security" }) + ), + "code_challenge_method": Schema.optionalKey( + Schema.Literals(["S256", "plain"]).annotate({ "description": "The method used to generate the code challenge" }) + ), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Optional expiration time for the API key to be created", + "format": "date-time" + }) + ), + "key_label": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional custom label for the API key. Defaults to the app name if not provided." + }).check(Schema.isMaxLength(100).annotate({ "expected": "a value with a length of at most 100" })) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Credit limit for the API key to be created", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "spawn_agent": Schema.optionalKey(Schema.String.annotate({ "description": "Agent identifier for spawn telemetry" })), + "spawn_cloud": Schema.optionalKey(Schema.String.annotate({ "description": "Cloud identifier for spawn telemetry" })), + "usage_limit_type": Schema.optionalKey( + Schema.Literals(["daily", "weekly", "monthly"]).annotate({ + "description": "Optional credit limit reset interval. When set, the credit limit resets on this interval." + }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ "description": "Optional workspace ID to associate the API key with", "format": "uuid" }) + ) +}) +export type CreateAuthKeysCode200 = { + readonly "data": { readonly "app_id": number; readonly "created_at": string; readonly "id": string } +} +export const CreateAuthKeysCode200 = Schema.Struct({ + "data": Schema.Struct({ + "app_id": Schema.Number.annotate({ "description": "The application ID associated with this auth code" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the auth code was created" }), + "id": Schema.String.annotate({ "description": "The authorization code ID to use in the exchange request" }) + }).annotate({ "description": "Auth code data" }) +}) +export type CreateAuthKeysCode400 = BadRequestResponse +export const CreateAuthKeysCode400 = BadRequestResponse +export type CreateAuthKeysCode401 = UnauthorizedResponse +export const CreateAuthKeysCode401 = UnauthorizedResponse +export type CreateAuthKeysCode403 = ForbiddenResponse +export const CreateAuthKeysCode403 = ForbiddenResponse +export type CreateAuthKeysCode409 = ConflictResponse +export const CreateAuthKeysCode409 = ConflictResponse +export type CreateAuthKeysCode500 = InternalServerResponse +export const CreateAuthKeysCode500 = InternalServerResponse +export type GetBenchmarksParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "source"?: "artificial-analysis" | "design-arena" + readonly "task_type"?: "coding" | "intelligence" | "agentic" + readonly "arena"?: "models" | "builders" | "agents" + readonly "category"?: string + readonly "max_results"?: number +} +export const GetBenchmarksParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "source": Schema.optionalKey( + Schema.Literals(["artificial-analysis", "design-arena"]).annotate({ + "description": + "Benchmark source to query. Determines the shape of the returned items. When omitted, returns results from all sources." + }) + ), + "task_type": Schema.optionalKey( + Schema.Literals(["coding", "intelligence", "agentic"]).annotate({ + "description": + "Filter results by task type. For Artificial Analysis, maps to the corresponding index. For Design Arena, maps to the matching category." + }) + ), + "arena": Schema.optionalKey( + Schema.Literals(["models", "builders", "agents"]).annotate({ + "description": "Design Arena only: arena to query. Defaults to `models` when source is `design-arena`." + }) + ), + "category": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Design Arena only: category within the arena (e.g. `codecategories`, `uicomponent`, `gamedev`, `3d`, `dataviz`, `image`, `video`, `svg`). When omitted, returns all categories." + }) + ), + "max_results": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Maximum number of items to return. When omitted, all matching results are returned." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ) + ) +}) +export type GetBenchmarks200 = UnifiedBenchmarksResponse +export const GetBenchmarks200 = UnifiedBenchmarksResponse +export type GetBenchmarks400 = BadRequestResponse +export const GetBenchmarks400 = BadRequestResponse +export type GetBenchmarks401 = UnauthorizedResponse +export const GetBenchmarks401 = UnauthorizedResponse +export type GetBenchmarks429 = TooManyRequestsResponse +export const GetBenchmarks429 = TooManyRequestsResponse +export type GetBenchmarks500 = InternalServerResponse +export const GetBenchmarks500 = InternalServerResponse +export type ListBYOKKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "workspace_id"?: string + readonly "provider"?: + | "ai21" + | "aion-labs" + | "akashml" + | "alibaba" + | "amazon-bedrock" + | "amazon-nova" + | "ambient" + | "anthropic" + | "arcee-ai" + | "atlas-cloud" + | "avian" + | "azure" + | "baidu" + | "baseten" + | "black-forest-labs" + | "byteplus" + | "cerebras" + | "chutes" + | "cirrascale" + | "clarifai" + | "cloudflare" + | "cohere" + | "coreweave" + | "crusoe" + | "darkbloom" + | "decart" + | "deepgram" + | "deepinfra" + | "deepseek" + | "dekallm" + | "digitalocean" + | "featherless" + | "fireworks" + | "fish-audio" + | "friendli" + | "gmicloud" + | "google-ai-studio" + | "google-vertex" + | "groq" + | "heygen" + | "inception" + | "inceptron" + | "inferact-vllm" + | "inference-net" + | "infermatic" + | "inflection" + | "io-net" + | "ionstream" + | "krea" + | "liquid" + | "mancer" + | "mara" + | "meta" + | "minimax" + | "mistral" + | "modelrun" + | "modular" + | "moonshotai" + | "morph" + | "ncompass" + | "nebius" + | "nex-agi" + | "nextbit" + | "novita" + | "nvidia" + | "open-inference" + | "openai" + | "parasail" + | "perceptron" + | "perplexity" + | "phala" + | "poolside" + | "quiver" + | "recraft" + | "reka" + | "relace" + | "runway" + | "sail-research" + | "sakana" + | "sambanova" + | "seed" + | "siliconflow" + | "sourceful" + | "stepfun" + | "streamlake" + | "switchpoint" + | "tencent" + | "tenstorrent" + | "together" + | "upstage" + | "venice" + | "wafer" + | "wandb" + | "xai" + | "xiaomi" + | "z-ai" +} +export const ListBYOKKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID to filter by. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ), + "provider": Schema.optionalKey( + Schema.Literals([ + "ai21", + "aion-labs", + "akashml", + "alibaba", + "amazon-bedrock", + "amazon-nova", + "ambient", + "anthropic", + "arcee-ai", + "atlas-cloud", + "avian", + "azure", + "baidu", + "baseten", + "black-forest-labs", + "byteplus", + "cerebras", + "chutes", + "cirrascale", + "clarifai", + "cloudflare", + "cohere", + "coreweave", + "crusoe", + "darkbloom", + "decart", + "deepgram", + "deepinfra", + "deepseek", + "dekallm", + "digitalocean", + "featherless", + "fireworks", + "fish-audio", + "friendli", + "gmicloud", + "google-ai-studio", + "google-vertex", + "groq", + "heygen", + "inception", + "inceptron", + "inferact-vllm", + "inference-net", + "infermatic", + "inflection", + "io-net", + "ionstream", + "krea", + "liquid", + "mancer", + "mara", + "meta", + "minimax", + "mistral", + "modelrun", + "modular", + "moonshotai", + "morph", + "ncompass", + "nebius", + "nex-agi", + "nextbit", + "novita", + "nvidia", + "open-inference", + "openai", + "parasail", + "perceptron", + "perplexity", + "phala", + "poolside", + "quiver", + "recraft", + "reka", + "relace", + "runway", + "sail-research", + "sakana", + "sambanova", + "seed", + "siliconflow", + "sourceful", + "stepfun", + "streamlake", + "switchpoint", + "tencent", + "tenstorrent", + "together", + "upstage", + "venice", + "wafer", + "wandb", + "xai", + "xiaomi", + "z-ai" + ]).annotate({ + "description": "Optional provider slug to filter by (e.g. `openai`, `anthropic`, `amazon-bedrock`)." + }) + ) +}) +export type ListBYOKKeys200 = ListBYOKKeysResponse +export const ListBYOKKeys200 = ListBYOKKeysResponse +export type ListBYOKKeys400 = BadRequestResponse +export const ListBYOKKeys400 = BadRequestResponse +export type ListBYOKKeys401 = UnauthorizedResponse +export const ListBYOKKeys401 = UnauthorizedResponse +export type ListBYOKKeys500 = InternalServerResponse +export const ListBYOKKeys500 = InternalServerResponse +export type CreateBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateBYOKKeyRequestJson = CreateBYOKKeyRequest +export const CreateBYOKKeyRequestJson = CreateBYOKKeyRequest +export type CreateBYOKKey201 = CreateBYOKKeyResponse +export const CreateBYOKKey201 = CreateBYOKKeyResponse +export type CreateBYOKKey400 = BadRequestResponse +export const CreateBYOKKey400 = BadRequestResponse +export type CreateBYOKKey401 = UnauthorizedResponse +export const CreateBYOKKey401 = UnauthorizedResponse +export type CreateBYOKKey403 = ForbiddenResponse +export const CreateBYOKKey403 = ForbiddenResponse +export type CreateBYOKKey500 = InternalServerResponse +export const CreateBYOKKey500 = InternalServerResponse +export type GetBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetBYOKKey200 = GetBYOKKeyResponse +export const GetBYOKKey200 = GetBYOKKeyResponse +export type GetBYOKKey401 = UnauthorizedResponse +export const GetBYOKKey401 = UnauthorizedResponse +export type GetBYOKKey404 = NotFoundResponse +export const GetBYOKKey404 = NotFoundResponse +export type GetBYOKKey500 = InternalServerResponse +export const GetBYOKKey500 = InternalServerResponse +export type DeleteBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteBYOKKey200 = DeleteBYOKKeyResponse +export const DeleteBYOKKey200 = DeleteBYOKKeyResponse +export type DeleteBYOKKey401 = UnauthorizedResponse +export const DeleteBYOKKey401 = UnauthorizedResponse +export type DeleteBYOKKey404 = NotFoundResponse +export const DeleteBYOKKey404 = NotFoundResponse +export type DeleteBYOKKey500 = InternalServerResponse +export const DeleteBYOKKey500 = InternalServerResponse +export type UpdateBYOKKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateBYOKKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateBYOKKeyRequestJson = UpdateBYOKKeyRequest +export const UpdateBYOKKeyRequestJson = UpdateBYOKKeyRequest +export type UpdateBYOKKey200 = UpdateBYOKKeyResponse +export const UpdateBYOKKey200 = UpdateBYOKKeyResponse +export type UpdateBYOKKey400 = BadRequestResponse +export const UpdateBYOKKey400 = BadRequestResponse +export type UpdateBYOKKey401 = UnauthorizedResponse +export const UpdateBYOKKey401 = UnauthorizedResponse +export type UpdateBYOKKey404 = NotFoundResponse +export const UpdateBYOKKey404 = NotFoundResponse +export type UpdateBYOKKey500 = InternalServerResponse +export const UpdateBYOKKey500 = InternalServerResponse +export type SendChatCompletionRequestParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "X-OpenRouter-Metadata"?: MetadataLevel +} +export const SendChatCompletionRequestParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "X-OpenRouter-Metadata": Schema.optionalKey(MetadataLevel) +}) +export type SendChatCompletionRequestRequestJson = ChatRequest +export const SendChatCompletionRequestRequestJson = ChatRequest +export type SendChatCompletionRequest200 = ChatResult +export const SendChatCompletionRequest200 = ChatResult +export type SendChatCompletionRequest200Sse = ChatStreamingResponse +export const SendChatCompletionRequest200Sse = ChatStreamingResponse +export type SendChatCompletionRequest400 = BadRequestResponse +export const SendChatCompletionRequest400 = BadRequestResponse +export type SendChatCompletionRequest401 = UnauthorizedResponse +export const SendChatCompletionRequest401 = UnauthorizedResponse +export type SendChatCompletionRequest402 = PaymentRequiredResponse +export const SendChatCompletionRequest402 = PaymentRequiredResponse +export type SendChatCompletionRequest403 = ForbiddenResponse +export const SendChatCompletionRequest403 = ForbiddenResponse +export type SendChatCompletionRequest404 = NotFoundResponse +export const SendChatCompletionRequest404 = NotFoundResponse +export type SendChatCompletionRequest408 = RequestTimeoutResponse +export const SendChatCompletionRequest408 = RequestTimeoutResponse +export type SendChatCompletionRequest413 = PayloadTooLargeResponse +export const SendChatCompletionRequest413 = PayloadTooLargeResponse +export type SendChatCompletionRequest422 = UnprocessableEntityResponse +export const SendChatCompletionRequest422 = UnprocessableEntityResponse +export type SendChatCompletionRequest429 = TooManyRequestsResponse +export const SendChatCompletionRequest429 = TooManyRequestsResponse +export type SendChatCompletionRequest500 = InternalServerResponse +export const SendChatCompletionRequest500 = InternalServerResponse +export type SendChatCompletionRequest502 = BadGatewayResponse +export const SendChatCompletionRequest502 = BadGatewayResponse +export type SendChatCompletionRequest503 = ServiceUnavailableResponse +export const SendChatCompletionRequest503 = ServiceUnavailableResponse +export type SendChatCompletionRequest524 = EdgeNetworkTimeoutResponse +export const SendChatCompletionRequest524 = EdgeNetworkTimeoutResponse +export type SendChatCompletionRequest529 = ProviderOverloadedResponse +export const SendChatCompletionRequest529 = ProviderOverloadedResponse +export type GetTaskClassificationsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "window"?: "7d" +} +export const GetTaskClassificationsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "window": Schema.optionalKey( + Schema.Literal("7d").annotate({ + "description": + "Trailing time window for the classification data. Currently only `7d` (trailing 7 days) is supported." + }) + ) +}) +export type GetTaskClassifications200 = TaskClassificationResponse +export const GetTaskClassifications200 = TaskClassificationResponse +export type GetTaskClassifications400 = BadRequestResponse +export const GetTaskClassifications400 = BadRequestResponse +export type GetTaskClassifications401 = UnauthorizedResponse +export const GetTaskClassifications401 = UnauthorizedResponse +export type GetTaskClassifications429 = TooManyRequestsResponse +export const GetTaskClassifications429 = TooManyRequestsResponse +export type GetTaskClassifications500 = InternalServerResponse +export const GetTaskClassifications500 = InternalServerResponse +export type GetCreditsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetCreditsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetCredits200 = { readonly "data": { readonly "total_credits": number; readonly "total_usage": number } } +export const GetCredits200 = Schema.Struct({ + "data": Schema.Struct({ + "total_credits": Schema.Number.annotate({ "description": "Total credits purchased", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ), + "total_usage": Schema.Number.annotate({ "description": "Total credits used", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + }) +}).annotate({ "description": "Total credits purchased and used" }) +export type GetCredits401 = UnauthorizedResponse +export const GetCredits401 = UnauthorizedResponse +export type GetCredits403 = ForbiddenResponse +export const GetCredits403 = ForbiddenResponse +export type GetCredits500 = InternalServerResponse +export const GetCredits500 = InternalServerResponse +export type CreateCoinbaseChargeParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateCoinbaseChargeParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateCoinbaseCharge410 = GoneResponse +export const CreateCoinbaseCharge410 = GoneResponse +export type GetAppRankingsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "category"?: "coding" | "creative" | "productivity" | "entertainment" + readonly "subcategory"?: + | "cli-agent" + | "ide-extension" + | "cloud-agent" + | "programming-app" + | "native-app-builder" + | "creative-writing" + | "video-gen" + | "image-gen" + | "audio-gen" + | "roleplay" + | "game" + | "writing-assistant" + | "general-chat" + | "personal-agent" + | "legal" + readonly "sort"?: "popular" | "trending" + readonly "start_date"?: string + readonly "end_date"?: string + readonly "limit"?: number + readonly "offset"?: number | null +} +export const GetAppRankingsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "category": Schema.optionalKey( + Schema.Literals(["coding", "creative", "productivity", "entertainment"]).annotate({ + "description": + "Marketplace category group to filter by (e.g. `coding`). Only apps tagged with a subcategory inside this group are returned. Mutually combinable with `subcategory` — when both are supplied the `subcategory` must belong to the `category` group." + }) + ), + "subcategory": Schema.optionalKey( + Schema.Literals([ + "cli-agent", + "ide-extension", + "cloud-agent", + "programming-app", + "native-app-builder", + "creative-writing", + "video-gen", + "image-gen", + "audio-gen", + "roleplay", + "game", + "writing-assistant", + "general-chat", + "personal-agent", + "legal" + ]).annotate({ + "description": + "Marketplace subcategory to filter by (e.g. `cli-agent`). Takes precedence over `category` for the actual filter; when `category` is also supplied the pair must be consistent." + }) + ), + "sort": Schema.optionalKey( + Schema.Literals(["popular", "trending"]).annotate({ + "description": + "`popular` ranks apps by total token volume inside the date window. `trending` ranks apps by absolute excess token growth: window volume minus the average volume of the three equal-length periods immediately preceding the window. Apps with no excess growth are omitted from `trending` results." + }) + ), + "start_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Start of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to 30 days before `end_date`. The dataset begins at 2025-01-01; earlier values are clamped forward to that floor and the resolved value is echoed in `meta.start_date`." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "end_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "End of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to the most recent completed UTC day. Must be on or after 2025-01-01; earlier values are rejected with a 400." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of apps to return (1-100). Defaults to 50." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" })), + Schema.Null + ]).annotate({ + "description": + "Number of ranked apps to skip before the first returned row (0-100). Defaults to 0. `rank` stays absolute, so the first row of `offset=50` is `rank: 51`." + }) + ) +}) +export type GetAppRankings200 = AppRankingsResponse +export const GetAppRankings200 = AppRankingsResponse +export type GetAppRankings400 = BadRequestResponse +export const GetAppRankings400 = BadRequestResponse +export type GetAppRankings401 = UnauthorizedResponse +export const GetAppRankings401 = UnauthorizedResponse +export type GetAppRankings429 = TooManyRequestsResponse +export const GetAppRankings429 = TooManyRequestsResponse +export type GetAppRankings500 = InternalServerResponse +export const GetAppRankings500 = InternalServerResponse +export type GetRankingsDailyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "start_date"?: string + readonly "end_date"?: string + readonly "period"?: "day" | "week" | "month" + readonly "modality"?: "text" | "image" | "image_output" | "audio" | "tool_calling" + readonly "context_bucket"?: "1K" | "10K" | "100K" | "1M" | "10M" + readonly "category"?: + | "programming" + | "roleplay" + | "marketing" + | "marketing/seo" + | "technology" + | "science" + | "translation" + | "legal" + | "finance" + | "health" + | "trivia" + | "academia" + readonly "language_type"?: "natural" | "programming" +} +export const GetRankingsDailyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "start_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Start of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to 30 days before `end_date`. The dataset begins at 2025-01-01; earlier values are clamped forward to that floor and the resolved value is echoed in `meta.start_date`." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "end_date": Schema.optionalKey( + Schema.String.annotate({ + "description": + "End of the date window in YYYY-MM-DD (UTC), inclusive. Defaults to the most recent completed UTC day. Must be on or after 2025-01-01; earlier values are rejected with a 400." + }).check( + Schema.isPattern(new RegExp("^\\d{4}-\\d{2}-\\d{2}$")).annotate({ + "expected": "a string matching the RegExp ^\\d{4}-\\d{2}-\\d{2}$" + }) + ) + ), + "period": Schema.optionalKey( + Schema.Literals(["day", "week", "month"]).annotate({ + "description": + "Time grain of each row. `day` (default) returns the per-UTC-day series; `week` buckets by ISO week start; `month` buckets by month start. With `category` or `language_type` only `week` (default) and `month` are available — `day` is rejected with a 400 because those datasets are aggregated weekly. For those sampled datasets `period=month` buckets each week by its week-start month, so totals are approximate at month boundaries." + }) + ), + "modality": Schema.optionalKey( + Schema.Literals(["text", "image", "image_output", "audio", "tool_calling"]).annotate({ + "description": + "Restrict to models for a modality surface: `text` / `image_output` match output modality, `image` / `audio` match input modality, and `tool_calling` keeps only rows that recorded at least one tool call. Exact dataset — cannot be combined with `category` or `language_type`." + }) + ), + "context_bucket": Schema.optionalKey( + Schema.Literals(["1K", "10K", "100K", "1M", "10M"]).annotate({ + "description": + "Restrict to requests whose context length falls in this bucket (`1K`, `10K`, `100K`, `1M`, or `10M`). Exact dataset — cannot be combined with `category` or `language_type`." + }) + ), + "category": Schema.optionalKey( + Schema.Literals([ + "programming", + "roleplay", + "marketing", + "marketing/seo", + "technology", + "science", + "translation", + "legal", + "finance", + "health", + "trivia", + "academia" + ]).annotate({ + "description": + "Restrict to a use-case category (e.g. `programming`, `roleplay`). Sourced from a sampled, upsampled dataset, so `total_tokens` is an estimate and is aggregated weekly (the trailing weekly bucket may include traffic past `end_date`). Cannot be combined with `modality`, `context_bucket`, or `language_type`." + }) + ), + "language_type": Schema.optionalKey( + Schema.Literals(["natural", "programming"]).annotate({ + "description": + "Restrict to natural-language or programming-language tagged activity. Sourced from a sampled, upsampled dataset, so `total_tokens` is an estimate and is aggregated weekly (the trailing weekly bucket may include traffic past `end_date`). Cannot be combined with `modality`, `context_bucket`, or `category`." + }) + ) +}) +export type GetRankingsDaily200 = RankingsDailyResponse +export const GetRankingsDaily200 = RankingsDailyResponse +export type GetRankingsDaily400 = BadRequestResponse +export const GetRankingsDaily400 = BadRequestResponse +export type GetRankingsDaily401 = UnauthorizedResponse +export const GetRankingsDaily401 = UnauthorizedResponse +export type GetRankingsDaily429 = TooManyRequestsResponse +export const GetRankingsDaily429 = TooManyRequestsResponse +export type GetRankingsDaily500 = InternalServerResponse +export const GetRankingsDaily500 = InternalServerResponse +export type CreateEmbeddingsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateEmbeddingsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateEmbeddingsRequestJson = { + readonly "dimensions"?: number + readonly "encoding_format"?: "float" | "base64" + readonly "input": + | string + | ReadonlyArray + | ReadonlyArray + | ReadonlyArray> + | ReadonlyArray< + { + readonly "content": ReadonlyArray< + | { readonly "text": string; readonly "type": "text" } + | { readonly "image_url": { readonly "url": string }; readonly "type": "image_url" } + | ContentPartInputAudio + | ContentPartInputVideo + | ContentPartInputFile + > + } + > + readonly "input_type"?: string + readonly "model": string + readonly "provider"?: ProviderPreferences + readonly "user"?: string +} +export const CreateEmbeddingsRequestJson = Schema.Struct({ + "dimensions": Schema.optionalKey( + Schema.Number.annotate({ "description": "The number of dimensions for the output embeddings" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })) + ), + "encoding_format": Schema.optionalKey( + Schema.Literals(["float", "base64"]).annotate({ "description": "The format of the output embeddings" }) + ), + "input": Schema.Union([ + Schema.String, + Schema.Array(Schema.String), + Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), + Schema.Array(Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })))), + Schema.Array(Schema.Struct({ + "content": Schema.Array( + Schema.Union([ + Schema.Struct({ "text": Schema.String, "type": Schema.Literal("text") }), + Schema.Struct({ "image_url": Schema.Struct({ "url": Schema.String }), "type": Schema.Literal("image_url") }), + ContentPartInputAudio, + ContentPartInputVideo, + ContentPartInputFile + ], { mode: "oneOf" }) + ) + })) + ]).annotate({ "description": "Text, token, or multimodal input(s) to embed" }), + "input_type": Schema.optionalKey( + Schema.String.annotate({ "description": "The type of input (e.g. search_query, search_document)" }) + ), + "model": Schema.String.annotate({ "description": "The model to use for embeddings" }), + "provider": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ProviderPreferences).annotate({ + "description": "Provider routing preferences for the request." + }) + ), + "user": Schema.optionalKey(Schema.String.annotate({ "description": "A unique identifier for the end-user" })) +}).annotate({ "description": "Embeddings request input" }) +export type CreateEmbeddings200 = { + readonly "data": ReadonlyArray< + { readonly "embedding": ReadonlyArray | string; readonly "index"?: number; readonly "object": "embedding" } + > + readonly "id"?: string + readonly "model": string + readonly "object": "list" + readonly "usage"?: { + readonly "cost"?: number + readonly "cost_details"?: CostDetails + readonly "is_byok"?: boolean + readonly "prompt_tokens": number + readonly "prompt_tokens_details"?: { + readonly "audio_tokens"?: number + readonly "file_tokens"?: number + readonly "image_tokens"?: number + readonly "text_tokens"?: number + readonly "video_tokens"?: number + } + readonly "total_tokens": number + } +} +export const CreateEmbeddings200 = Schema.Struct({ + "data": Schema.Array( + Schema.Struct({ + "embedding": Schema.Union([ + Schema.Array(Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" }))), + Schema.String + ]).annotate({ "description": "Embedding vector as an array of floats or a base64 string" }), + "index": Schema.optionalKey( + Schema.Number.annotate({ "description": "Index of the embedding in the input list" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "object": Schema.Literal("embedding") + }).annotate({ "description": "A single embedding object" }) + ).annotate({ "description": "List of embedding objects" }), + "id": Schema.optionalKey(Schema.String.annotate({ "description": "Unique identifier for the embeddings response" })), + "model": Schema.String.annotate({ "description": "The model used for embeddings" }), + "object": Schema.Literal("list"), + "usage": Schema.optionalKey( + Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cost of the request in credits", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "cost_details": Schema.optionalKey(CostDetails), + "is_byok": Schema.optionalKey( + Schema.Boolean.annotate({ + "description": "Whether a request was made using a Bring Your Own Key configuration" + }) + ), + "prompt_tokens": Schema.Number.annotate({ "description": "Number of tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "prompt_tokens_details": Schema.optionalKey( + Schema.Struct({ + "audio_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of audio tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "file_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of file/document tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "image_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of image tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "text_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of text tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "video_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of video tokens in the input" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) + }).annotate({ + "description": + "Per-modality token breakdown. Only present when the input contains 2+ modalities (e.g. text + image) and the upstream provider returns modality-level usage data. Only non-zero modality counts are included." + }) + ), + "total_tokens": Schema.Number.annotate({ "description": "Total number of tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }).annotate({ "description": "Token usage statistics" }) + ) +}).annotate({ "description": "Embeddings response containing embedding vectors" }) +export type CreateEmbeddings200Sse = string +export const CreateEmbeddings200Sse = Schema.String.annotate({ + "description": "Not used for embeddings - embeddings do not support streaming" +}) +export type CreateEmbeddings400 = BadRequestResponse +export const CreateEmbeddings400 = BadRequestResponse +export type CreateEmbeddings401 = UnauthorizedResponse +export const CreateEmbeddings401 = UnauthorizedResponse +export type CreateEmbeddings402 = PaymentRequiredResponse +export const CreateEmbeddings402 = PaymentRequiredResponse +export type CreateEmbeddings404 = NotFoundResponse +export const CreateEmbeddings404 = NotFoundResponse +export type CreateEmbeddings429 = TooManyRequestsResponse +export const CreateEmbeddings429 = TooManyRequestsResponse +export type CreateEmbeddings500 = InternalServerResponse +export const CreateEmbeddings500 = InternalServerResponse +export type CreateEmbeddings502 = BadGatewayResponse +export const CreateEmbeddings502 = BadGatewayResponse +export type CreateEmbeddings503 = ServiceUnavailableResponse +export const CreateEmbeddings503 = ServiceUnavailableResponse +export type CreateEmbeddings524 = EdgeNetworkTimeoutResponse +export const CreateEmbeddings524 = EdgeNetworkTimeoutResponse +export type CreateEmbeddings529 = ProviderOverloadedResponse +export const CreateEmbeddings529 = ProviderOverloadedResponse +export type ListEmbeddingsModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListEmbeddingsModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ + "description": + "Number of records to skip for pagination. When both offset and limit are omitted, the full list is returned" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of records to return (max 1000). When both offset and limit are omitted, the full list is returned" + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" })) + ) +}) +export type ListEmbeddingsModels200 = ModelsListResponse +export const ListEmbeddingsModels200 = ModelsListResponse +export type ListEmbeddingsModels400 = BadRequestResponse +export const ListEmbeddingsModels400 = BadRequestResponse +export type ListEmbeddingsModels500 = InternalServerResponse +export const ListEmbeddingsModels500 = InternalServerResponse +export type ListEndpointsZdrParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListEndpointsZdrParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListEndpointsZdr200 = { readonly "data": ReadonlyArray } +export const ListEndpointsZdr200 = Schema.Struct({ "data": Schema.Array(PublicEndpoint) }) +export type ListEndpointsZdr403 = ForbiddenResponse +export const ListEndpointsZdr403 = ForbiddenResponse +export type ListEndpointsZdr500 = InternalServerResponse +export const ListEndpointsZdr500 = InternalServerResponse +export type ListFilesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "limit"?: number + readonly "cursor"?: string + readonly "workspace_id"?: string +} +export const ListFilesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of files to return (1–1000)." }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" }) + ) + ), + "cursor": Schema.optionalKey( + Schema.String.annotate({ "description": "Opaque pagination cursor from a previous response." }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type ListFiles200 = FileListResponse +export const ListFiles200 = FileListResponse +export type ListFiles400 = BadRequestResponse +export const ListFiles400 = BadRequestResponse +export type ListFiles401 = UnauthorizedResponse +export const ListFiles401 = UnauthorizedResponse +export type ListFiles429 = TooManyRequestsResponse +export const ListFiles429 = TooManyRequestsResponse +export type ListFiles500 = InternalServerResponse +export const ListFiles500 = InternalServerResponse +export type UploadFileParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const UploadFileParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type UploadFileRequestFormData = { readonly "file": string } +export const UploadFileRequestFormData = Schema.Struct({ "file": Schema.String.annotate({ "format": "binary" }) }) +export type UploadFile200 = FileMetadata +export const UploadFile200 = FileMetadata +export type UploadFile400 = BadRequestResponse +export const UploadFile400 = BadRequestResponse +export type UploadFile401 = UnauthorizedResponse +export const UploadFile401 = UnauthorizedResponse +export type UploadFile403 = ForbiddenResponse +export const UploadFile403 = ForbiddenResponse +export type UploadFile413 = PayloadTooLargeResponse +export const UploadFile413 = PayloadTooLargeResponse +export type UploadFile429 = TooManyRequestsResponse +export const UploadFile429 = TooManyRequestsResponse +export type UploadFile500 = InternalServerResponse +export const UploadFile500 = InternalServerResponse +export type GetFileMetadataParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const GetFileMetadataParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type GetFileMetadata200 = FileMetadata +export const GetFileMetadata200 = FileMetadata +export type GetFileMetadata401 = UnauthorizedResponse +export const GetFileMetadata401 = UnauthorizedResponse +export type GetFileMetadata404 = NotFoundResponse +export const GetFileMetadata404 = NotFoundResponse +export type GetFileMetadata429 = TooManyRequestsResponse +export const GetFileMetadata429 = TooManyRequestsResponse +export type GetFileMetadata500 = InternalServerResponse +export const GetFileMetadata500 = InternalServerResponse +export type DeleteFileParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const DeleteFileParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type DeleteFile200 = FileDeleteResponse +export const DeleteFile200 = FileDeleteResponse +export type DeleteFile401 = UnauthorizedResponse +export const DeleteFile401 = UnauthorizedResponse +export type DeleteFile404 = NotFoundResponse +export const DeleteFile404 = NotFoundResponse +export type DeleteFile429 = TooManyRequestsResponse +export const DeleteFile429 = TooManyRequestsResponse +export type DeleteFile500 = InternalServerResponse +export const DeleteFile500 = InternalServerResponse +export type DownloadFileContentParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "workspace_id"?: string +} +export const DownloadFileContentParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Workspace to scope the request to. Defaults to the caller’s default workspace.", + "format": "uuid" + }) + ) +}) +export type DownloadFileContent400 = BadRequestResponse +export const DownloadFileContent400 = BadRequestResponse +export type DownloadFileContent401 = UnauthorizedResponse +export const DownloadFileContent401 = UnauthorizedResponse +export type DownloadFileContent404 = NotFoundResponse +export const DownloadFileContent404 = NotFoundResponse +export type DownloadFileContent429 = TooManyRequestsResponse +export const DownloadFileContent429 = TooManyRequestsResponse +export type DownloadFileContent500 = InternalServerResponse +export const DownloadFileContent500 = InternalServerResponse +export type GetGenerationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "id": string +} +export const GetGenerationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "id": Schema.String.annotate({ "description": "The generation ID" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}) +export type GetGeneration200 = GenerationResponse +export const GetGeneration200 = GenerationResponse +export type GetGeneration401 = UnauthorizedResponse +export const GetGeneration401 = UnauthorizedResponse +export type GetGeneration402 = PaymentRequiredResponse +export const GetGeneration402 = PaymentRequiredResponse +export type GetGeneration404 = NotFoundResponse +export const GetGeneration404 = NotFoundResponse +export type GetGeneration429 = TooManyRequestsResponse +export const GetGeneration429 = TooManyRequestsResponse +export type GetGeneration500 = InternalServerResponse +export const GetGeneration500 = InternalServerResponse +export type GetGeneration502 = BadGatewayResponse +export const GetGeneration502 = BadGatewayResponse +export type GetGeneration524 = EdgeNetworkTimeoutResponse +export const GetGeneration524 = EdgeNetworkTimeoutResponse +export type GetGeneration529 = ProviderOverloadedResponse +export const GetGeneration529 = ProviderOverloadedResponse +export type ListGenerationContentParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "id": string +} +export const ListGenerationContentParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "id": Schema.String.annotate({ "description": "The generation ID" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ) +}) +export type ListGenerationContent200 = GenerationContentResponse +export const ListGenerationContent200 = GenerationContentResponse +export type ListGenerationContent401 = UnauthorizedResponse +export const ListGenerationContent401 = UnauthorizedResponse +export type ListGenerationContent403 = ForbiddenResponse +export const ListGenerationContent403 = ForbiddenResponse +export type ListGenerationContent404 = NotFoundResponse +export const ListGenerationContent404 = NotFoundResponse +export type ListGenerationContent429 = TooManyRequestsResponse +export const ListGenerationContent429 = TooManyRequestsResponse +export type ListGenerationContent500 = InternalServerResponse +export const ListGenerationContent500 = InternalServerResponse +export type ListGenerationContent502 = BadGatewayResponse +export const ListGenerationContent502 = BadGatewayResponse +export type ListGenerationContent524 = EdgeNetworkTimeoutResponse +export const ListGenerationContent524 = EdgeNetworkTimeoutResponse +export type ListGenerationContent529 = ProviderOverloadedResponse +export const ListGenerationContent529 = ProviderOverloadedResponse +export type SubmitGenerationFeedbackParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const SubmitGenerationFeedbackParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type SubmitGenerationFeedbackRequestJson = SubmitGenerationFeedbackRequest +export const SubmitGenerationFeedbackRequestJson = SubmitGenerationFeedbackRequest +export type SubmitGenerationFeedback200 = SubmitGenerationFeedbackResponse +export const SubmitGenerationFeedback200 = SubmitGenerationFeedbackResponse +export type SubmitGenerationFeedback400 = BadRequestResponse +export const SubmitGenerationFeedback400 = BadRequestResponse +export type SubmitGenerationFeedback401 = UnauthorizedResponse +export const SubmitGenerationFeedback401 = UnauthorizedResponse +export type SubmitGenerationFeedback404 = NotFoundResponse +export const SubmitGenerationFeedback404 = NotFoundResponse +export type SubmitGenerationFeedback429 = TooManyRequestsResponse +export const SubmitGenerationFeedback429 = TooManyRequestsResponse +export type SubmitGenerationFeedback500 = InternalServerResponse +export const SubmitGenerationFeedback500 = InternalServerResponse +export type ListGuardrailsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "workspace_id"?: string +} +export const ListGuardrailsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter guardrails by workspace ID. By default, guardrails in the default workspace are returned.", + "format": "uuid" + }) + ) +}) +export type ListGuardrails200 = ListGuardrailsResponse +export const ListGuardrails200 = ListGuardrailsResponse +export type ListGuardrails400 = BadRequestResponse +export const ListGuardrails400 = BadRequestResponse +export type ListGuardrails401 = UnauthorizedResponse +export const ListGuardrails401 = UnauthorizedResponse +export type ListGuardrails500 = InternalServerResponse +export const ListGuardrails500 = InternalServerResponse +export type CreateGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateGuardrailRequestJson = CreateGuardrailRequest +export const CreateGuardrailRequestJson = CreateGuardrailRequest +export type CreateGuardrail201 = CreateGuardrailResponse +export const CreateGuardrail201 = CreateGuardrailResponse +export type CreateGuardrail400 = BadRequestResponse +export const CreateGuardrail400 = BadRequestResponse +export type CreateGuardrail401 = UnauthorizedResponse +export const CreateGuardrail401 = UnauthorizedResponse +export type CreateGuardrail403 = ForbiddenResponse +export const CreateGuardrail403 = ForbiddenResponse +export type CreateGuardrail500 = InternalServerResponse +export const CreateGuardrail500 = InternalServerResponse +export type GetGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetGuardrail200 = GetGuardrailResponse +export const GetGuardrail200 = GetGuardrailResponse +export type GetGuardrail401 = UnauthorizedResponse +export const GetGuardrail401 = UnauthorizedResponse +export type GetGuardrail404 = NotFoundResponse +export const GetGuardrail404 = NotFoundResponse +export type GetGuardrail500 = InternalServerResponse +export const GetGuardrail500 = InternalServerResponse +export type DeleteGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteGuardrail200 = DeleteGuardrailResponse +export const DeleteGuardrail200 = DeleteGuardrailResponse +export type DeleteGuardrail401 = UnauthorizedResponse +export const DeleteGuardrail401 = UnauthorizedResponse +export type DeleteGuardrail404 = NotFoundResponse +export const DeleteGuardrail404 = NotFoundResponse +export type DeleteGuardrail500 = InternalServerResponse +export const DeleteGuardrail500 = InternalServerResponse +export type UpdateGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateGuardrailRequestJson = UpdateGuardrailRequest +export const UpdateGuardrailRequestJson = UpdateGuardrailRequest +export type UpdateGuardrail200 = UpdateGuardrailResponse +export const UpdateGuardrail200 = UpdateGuardrailResponse +export type UpdateGuardrail400 = BadRequestResponse +export const UpdateGuardrail400 = BadRequestResponse +export type UpdateGuardrail401 = UnauthorizedResponse +export const UpdateGuardrail401 = UnauthorizedResponse +export type UpdateGuardrail404 = NotFoundResponse +export const UpdateGuardrail404 = NotFoundResponse +export type UpdateGuardrail500 = InternalServerResponse +export const UpdateGuardrail500 = InternalServerResponse +export type ListGuardrailKeyAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListGuardrailKeyAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListGuardrailKeyAssignments200 = ListKeyAssignmentsResponse +export const ListGuardrailKeyAssignments200 = ListKeyAssignmentsResponse +export type ListGuardrailKeyAssignments401 = UnauthorizedResponse +export const ListGuardrailKeyAssignments401 = UnauthorizedResponse +export type ListGuardrailKeyAssignments404 = NotFoundResponse +export const ListGuardrailKeyAssignments404 = NotFoundResponse +export type ListGuardrailKeyAssignments500 = InternalServerResponse +export const ListGuardrailKeyAssignments500 = InternalServerResponse +export type BulkAssignKeysToGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkAssignKeysToGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkAssignKeysToGuardrailRequestJson = BulkAssignKeysRequest +export const BulkAssignKeysToGuardrailRequestJson = BulkAssignKeysRequest +export type BulkAssignKeysToGuardrail200 = BulkAssignKeysResponse +export const BulkAssignKeysToGuardrail200 = BulkAssignKeysResponse +export type BulkAssignKeysToGuardrail400 = BadRequestResponse +export const BulkAssignKeysToGuardrail400 = BadRequestResponse +export type BulkAssignKeysToGuardrail401 = UnauthorizedResponse +export const BulkAssignKeysToGuardrail401 = UnauthorizedResponse +export type BulkAssignKeysToGuardrail404 = NotFoundResponse +export const BulkAssignKeysToGuardrail404 = NotFoundResponse +export type BulkAssignKeysToGuardrail500 = InternalServerResponse +export const BulkAssignKeysToGuardrail500 = InternalServerResponse +export type BulkUnassignKeysFromGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkUnassignKeysFromGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkUnassignKeysFromGuardrailRequestJson = BulkUnassignKeysRequest +export const BulkUnassignKeysFromGuardrailRequestJson = BulkUnassignKeysRequest +export type BulkUnassignKeysFromGuardrail200 = BulkUnassignKeysResponse +export const BulkUnassignKeysFromGuardrail200 = BulkUnassignKeysResponse +export type BulkUnassignKeysFromGuardrail400 = BadRequestResponse +export const BulkUnassignKeysFromGuardrail400 = BadRequestResponse +export type BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse +export const BulkUnassignKeysFromGuardrail401 = UnauthorizedResponse +export type BulkUnassignKeysFromGuardrail404 = NotFoundResponse +export const BulkUnassignKeysFromGuardrail404 = NotFoundResponse +export type BulkUnassignKeysFromGuardrail500 = InternalServerResponse +export const BulkUnassignKeysFromGuardrail500 = InternalServerResponse +export type ListGuardrailMemberAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListGuardrailMemberAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListGuardrailMemberAssignments200 = ListMemberAssignmentsResponse +export const ListGuardrailMemberAssignments200 = ListMemberAssignmentsResponse +export type ListGuardrailMemberAssignments401 = UnauthorizedResponse +export const ListGuardrailMemberAssignments401 = UnauthorizedResponse +export type ListGuardrailMemberAssignments404 = NotFoundResponse +export const ListGuardrailMemberAssignments404 = NotFoundResponse +export type ListGuardrailMemberAssignments500 = InternalServerResponse +export const ListGuardrailMemberAssignments500 = InternalServerResponse +export type BulkAssignMembersToGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkAssignMembersToGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkAssignMembersToGuardrailRequestJson = BulkAssignMembersRequest +export const BulkAssignMembersToGuardrailRequestJson = BulkAssignMembersRequest +export type BulkAssignMembersToGuardrail200 = BulkAssignMembersResponse +export const BulkAssignMembersToGuardrail200 = BulkAssignMembersResponse +export type BulkAssignMembersToGuardrail400 = BadRequestResponse +export const BulkAssignMembersToGuardrail400 = BadRequestResponse +export type BulkAssignMembersToGuardrail401 = UnauthorizedResponse +export const BulkAssignMembersToGuardrail401 = UnauthorizedResponse +export type BulkAssignMembersToGuardrail404 = NotFoundResponse +export const BulkAssignMembersToGuardrail404 = NotFoundResponse +export type BulkAssignMembersToGuardrail500 = InternalServerResponse +export const BulkAssignMembersToGuardrail500 = InternalServerResponse +export type BulkUnassignMembersFromGuardrailParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkUnassignMembersFromGuardrailParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkUnassignMembersFromGuardrailRequestJson = BulkUnassignMembersRequest +export const BulkUnassignMembersFromGuardrailRequestJson = BulkUnassignMembersRequest +export type BulkUnassignMembersFromGuardrail200 = BulkUnassignMembersResponse +export const BulkUnassignMembersFromGuardrail200 = BulkUnassignMembersResponse +export type BulkUnassignMembersFromGuardrail400 = BadRequestResponse +export const BulkUnassignMembersFromGuardrail400 = BadRequestResponse +export type BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse +export const BulkUnassignMembersFromGuardrail401 = UnauthorizedResponse +export type BulkUnassignMembersFromGuardrail404 = NotFoundResponse +export const BulkUnassignMembersFromGuardrail404 = NotFoundResponse +export type BulkUnassignMembersFromGuardrail500 = InternalServerResponse +export const BulkUnassignMembersFromGuardrail500 = InternalServerResponse +export type ListKeyAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListKeyAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListKeyAssignments200 = ListKeyAssignmentsResponse +export const ListKeyAssignments200 = ListKeyAssignmentsResponse +export type ListKeyAssignments401 = UnauthorizedResponse +export const ListKeyAssignments401 = UnauthorizedResponse +export type ListKeyAssignments500 = InternalServerResponse +export const ListKeyAssignments500 = InternalServerResponse +export type ListMemberAssignmentsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListMemberAssignmentsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListMemberAssignments200 = ListMemberAssignmentsResponse +export const ListMemberAssignments200 = ListMemberAssignmentsResponse +export type ListMemberAssignments401 = UnauthorizedResponse +export const ListMemberAssignments401 = UnauthorizedResponse +export type ListMemberAssignments500 = InternalServerResponse +export const ListMemberAssignments500 = InternalServerResponse +export type CreateImagesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateImagesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateImagesRequestJson = ImageGenerationRequest +export const CreateImagesRequestJson = ImageGenerationRequest +export type CreateImages200 = ImageGenerationResponse +export const CreateImages200 = ImageGenerationResponse +export type CreateImages200Sse = ImageStreamingResponse +export const CreateImages200Sse = ImageStreamingResponse +export type CreateImages400 = BadRequestResponse +export const CreateImages400 = BadRequestResponse +export type CreateImages401 = UnauthorizedResponse +export const CreateImages401 = UnauthorizedResponse +export type CreateImages402 = PaymentRequiredResponse +export const CreateImages402 = PaymentRequiredResponse +export type CreateImages403 = ForbiddenResponse +export const CreateImages403 = ForbiddenResponse +export type CreateImages404 = NotFoundResponse +export const CreateImages404 = NotFoundResponse +export type CreateImages413 = PayloadTooLargeResponse +export const CreateImages413 = PayloadTooLargeResponse +export type CreateImages429 = TooManyRequestsResponse +export const CreateImages429 = TooManyRequestsResponse +export type CreateImages500 = InternalServerResponse +export const CreateImages500 = InternalServerResponse +export type CreateImages502 = BadGatewayResponse +export const CreateImages502 = BadGatewayResponse +export type CreateImages524 = EdgeNetworkTimeoutResponse +export const CreateImages524 = EdgeNetworkTimeoutResponse +export type CreateImages529 = ProviderOverloadedResponse +export const CreateImages529 = ProviderOverloadedResponse +export type ListImageModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListImageModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListImageModels200 = ImageModelsListResponse +export const ListImageModels200 = ImageModelsListResponse +export type ListImageModels500 = InternalServerResponse +export const ListImageModels500 = InternalServerResponse +export type ListImageModelEndpointsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListImageModelEndpointsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListImageModelEndpoints200 = ImageModelEndpointsResponse +export const ListImageModelEndpoints200 = ImageModelEndpointsResponse +export type ListImageModelEndpoints404 = NotFoundResponse +export const ListImageModelEndpoints404 = NotFoundResponse +export type ListImageModelEndpoints500 = InternalServerResponse +export const ListImageModelEndpoints500 = InternalServerResponse +export type GetCurrentKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetCurrentKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetCurrentKey200 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "creator_user_id": string | null + readonly "expires_at"?: string | null + readonly "include_byok_in_limit": boolean + readonly "is_free_tier": boolean + readonly "is_management_key": boolean + readonly "is_provisioning_key": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "rate_limit": { readonly "interval": string; readonly "note": string; readonly "requests": number } + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + } +} +export const GetCurrentKey200 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "is_free_tier": Schema.Boolean.annotate({ "description": "Whether this is a free tier API key" }), + "is_management_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), + "is_provisioning_key": Schema.Boolean.annotate({ "description": "Whether this is a management key" }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "rate_limit": Schema.Struct({ + "interval": Schema.String.annotate({ "description": "Rate limit interval" }), + "note": Schema.String.annotate({ "description": "Note about the rate limit" }), + "requests": Schema.Number.annotate({ "description": "Number of requests allowed per interval" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + }).annotate({ "description": "Legacy rate limit information about a key. Will always return -1." }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + }).annotate({ "description": "Current API key information" }) +}) +export type GetCurrentKey401 = UnauthorizedResponse +export const GetCurrentKey401 = UnauthorizedResponse +export type GetCurrentKey500 = InternalServerResponse +export const GetCurrentKey500 = InternalServerResponse +export type ListParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "include_disabled"?: boolean + readonly "offset"?: number | null + readonly "workspace_id"?: string +} +export const ListParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "include_disabled": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to include disabled API keys in the response" }) + ), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of API keys to skip for pagination" }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter API keys by workspace ID. By default, keys in the default workspace are returned.", + "format": "uuid" + }) + ) +}) +export type List200 = { + readonly "data": ReadonlyArray< + { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } + > +} +export const List200 = Schema.Struct({ + "data": Schema.Array(Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + })).annotate({ "description": "List of API keys" }) +}) +export type List400 = BadRequestResponse +export const List400 = BadRequestResponse +export type List401 = UnauthorizedResponse +export const List401 = UnauthorizedResponse +export type List429 = TooManyRequestsResponse +export const List429 = TooManyRequestsResponse +export type List500 = InternalServerResponse +export const List500 = InternalServerResponse +export type CreateKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateKeysRequestJson = { + readonly "creator_user_id"?: string | null + readonly "expires_at"?: string | null + readonly "include_byok_in_limit"?: boolean + readonly "limit"?: number | null + readonly "limit_reset"?: "daily" | "weekly" | "monthly" | null + readonly "name": string + readonly "workspace_id"?: string +} +export const CreateKeysRequestJson = Schema.Struct({ + "creator_user_id": Schema.optionalKey( + Schema.Union([ + Schema.String.check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + Schema.Null + ]).annotate({ + "description": + "Optional user ID of the key creator. Only meaningful for organization-owned keys where a specific member is creating the key." + }) + ), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "Optional ISO 8601 UTC timestamp when the API key should expire. Must be UTC, other timezones will be rejected", + "format": "date-time" + }) + ), + "include_byok_in_limit": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) + ), + "limit": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "Optional spending limit for the API key in USD", "format": "double" }) + ), + "limit_reset": Schema.optionalKey( + Schema.Union([Schema.Literal("daily"), Schema.Literal("weekly"), Schema.Literal("monthly"), Schema.Null]).annotate({ + "description": + "Type of limit reset for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." + }) + ), + "name": Schema.String.annotate({ "description": "Name for the new API key" }).check( + Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" }) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "The workspace to create the API key in. Defaults to the default workspace if not provided.", + "format": "uuid" + }) + ) +}) +export type CreateKeys201 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } + readonly "key": string +} +export const CreateKeys201 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + }).annotate({ "description": "The created API key information" }), + "key": Schema.String.annotate({ "description": "The actual API key string (only shown once)" }) +}) +export type CreateKeys400 = BadRequestResponse +export const CreateKeys400 = BadRequestResponse +export type CreateKeys401 = UnauthorizedResponse +export const CreateKeys401 = UnauthorizedResponse +export type CreateKeys403 = ForbiddenResponse +export const CreateKeys403 = ForbiddenResponse +export type CreateKeys429 = TooManyRequestsResponse +export const CreateKeys429 = TooManyRequestsResponse +export type CreateKeys500 = InternalServerResponse +export const CreateKeys500 = InternalServerResponse +export type GetKeyParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetKeyParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetKey200 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } +} +export const GetKey200 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + }).annotate({ "description": "The API key information" }) +}) +export type GetKey401 = UnauthorizedResponse +export const GetKey401 = UnauthorizedResponse +export type GetKey404 = NotFoundResponse +export const GetKey404 = NotFoundResponse +export type GetKey429 = TooManyRequestsResponse +export const GetKey429 = TooManyRequestsResponse +export type GetKey500 = InternalServerResponse +export const GetKey500 = InternalServerResponse +export type DeleteKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteKeys200 = { readonly "deleted": true } +export const DeleteKeys200 = Schema.Struct({ + "deleted": Schema.Literal(true).annotate({ "description": "Confirmation that the API key was deleted" }) +}) +export type DeleteKeys401 = UnauthorizedResponse +export const DeleteKeys401 = UnauthorizedResponse +export type DeleteKeys404 = NotFoundResponse +export const DeleteKeys404 = NotFoundResponse +export type DeleteKeys429 = TooManyRequestsResponse +export const DeleteKeys429 = TooManyRequestsResponse +export type DeleteKeys500 = InternalServerResponse +export const DeleteKeys500 = InternalServerResponse +export type UpdateKeysParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateKeysParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateKeysRequestJson = { + readonly "disabled"?: boolean + readonly "include_byok_in_limit"?: boolean + readonly "limit"?: number | null + readonly "limit_reset"?: "daily" | "weekly" | "monthly" | null + readonly "name"?: string +} +export const UpdateKeysRequestJson = Schema.Struct({ + "disabled": Schema.optionalKey(Schema.Boolean.annotate({ "description": "Whether to disable the API key" })), + "include_byok_in_limit": Schema.optionalKey( + Schema.Boolean.annotate({ "description": "Whether to include BYOK usage in the limit" }) + ), + "limit": Schema.optionalKey( + Schema.Union([Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), Schema.Null]) + .annotate({ "description": "New spending limit for the API key in USD", "format": "double" }) + ), + "limit_reset": Schema.optionalKey( + Schema.Union([Schema.Literal("daily"), Schema.Literal("weekly"), Schema.Literal("monthly"), Schema.Null]).annotate({ + "description": + "New limit reset type for the API key (daily, weekly, monthly, or null for no reset). Resets happen automatically at midnight UTC, and weeks are Monday through Sunday." + }) + ), + "name": Schema.optionalKey(Schema.String.annotate({ "description": "New name for the API key" })) +}) +export type UpdateKeys200 = { + readonly "data": { + readonly "byok_usage": number + readonly "byok_usage_daily": number + readonly "byok_usage_monthly": number + readonly "byok_usage_weekly": number + readonly "created_at": string + readonly "creator_user_id": string | null + readonly "disabled": boolean + readonly "expires_at"?: string | null + readonly "hash": string + readonly "include_byok_in_limit": boolean + readonly "label": string + readonly "limit": number | null + readonly "limit_remaining": number | null + readonly "limit_reset": string | null + readonly "name": string + readonly "updated_at": string | null + readonly "usage": number + readonly "usage_daily": number + readonly "usage_monthly": number + readonly "usage_weekly": number + readonly "workspace_id": string + } +} +export const UpdateKeys200 = Schema.Struct({ + "data": Schema.Struct({ + "byok_usage": Schema.Number.annotate({ + "description": "Total external BYOK usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_daily": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_monthly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "byok_usage_weekly": Schema.Number.annotate({ + "description": "External BYOK usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "created_at": Schema.String.annotate({ "description": "ISO 8601 timestamp of when the API key was created" }), + "creator_user_id": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": + "The user ID of the key creator. For organization-owned keys, this is the member who created the key. For individual users, this is the user's own ID." + }), + "disabled": Schema.Boolean.annotate({ "description": "Whether the API key is disabled" }), + "expires_at": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 UTC timestamp when the API key expires, or null if no expiration", + "format": "date-time" + }) + ), + "hash": Schema.String.annotate({ "description": "Unique hash identifier for the API key" }), + "include_byok_in_limit": Schema.Boolean.annotate({ + "description": "Whether to include external BYOK usage in the credit limit" + }), + "label": Schema.String.annotate({ "description": "Human-readable label for the API key" }), + "limit": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Spending limit for the API key in USD", "format": "double" }), + "limit_remaining": Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })), + Schema.Null + ]).annotate({ "description": "Remaining spending limit in USD", "format": "double" }), + "limit_reset": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "Type of limit reset for the API key" + }), + "name": Schema.String.annotate({ "description": "Name of the API key" }), + "updated_at": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "ISO 8601 timestamp of when the API key was last updated" + }), + "usage": Schema.Number.annotate({ + "description": "Total OpenRouter credit usage (in USD) for the API key", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_daily": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC day", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_monthly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC month", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "usage_weekly": Schema.Number.annotate({ + "description": "OpenRouter credit usage (in USD) for the current UTC week (Monday-Sunday)", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })), + "workspace_id": Schema.String.annotate({ "description": "The workspace ID this API key belongs to." }) + }).annotate({ "description": "The updated API key information" }) +}) +export type UpdateKeys400 = BadRequestResponse +export const UpdateKeys400 = BadRequestResponse +export type UpdateKeys401 = UnauthorizedResponse +export const UpdateKeys401 = UnauthorizedResponse +export type UpdateKeys404 = NotFoundResponse +export const UpdateKeys404 = NotFoundResponse +export type UpdateKeys429 = TooManyRequestsResponse +export const UpdateKeys429 = TooManyRequestsResponse +export type UpdateKeys500 = InternalServerResponse +export const UpdateKeys500 = InternalServerResponse +export type CreateMessagesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "X-OpenRouter-Metadata"?: MetadataLevel +} +export const CreateMessagesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "X-OpenRouter-Metadata": Schema.optionalKey(MetadataLevel) +}) +export type CreateMessagesRequestJson = MessagesRequest +export const CreateMessagesRequestJson = MessagesRequest +export type CreateMessages200 = MessagesResult +export const CreateMessages200 = MessagesResult +export type CreateMessages200Sse = MessagesStreamingResponse +export const CreateMessages200Sse = MessagesStreamingResponse +export type CreateMessages400 = MessagesErrorResponse +export const CreateMessages400 = MessagesErrorResponse +export type CreateMessages401 = MessagesErrorResponse +export const CreateMessages401 = MessagesErrorResponse +export type CreateMessages403 = ForbiddenResponse +export const CreateMessages403 = ForbiddenResponse +export type CreateMessages404 = MessagesErrorResponse +export const CreateMessages404 = MessagesErrorResponse +export type CreateMessages429 = MessagesErrorResponse +export const CreateMessages429 = MessagesErrorResponse +export type CreateMessages500 = MessagesErrorResponse +export const CreateMessages500 = MessagesErrorResponse +export type CreateMessages503 = MessagesErrorResponse +export const CreateMessages503 = MessagesErrorResponse +export type CreateMessages529 = MessagesErrorResponse +export const CreateMessages529 = MessagesErrorResponse +export type GetModelParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetModelParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetModel200 = ModelResponse +export const GetModel200 = ModelResponse +export type GetModel403 = ForbiddenResponse +export const GetModel403 = ForbiddenResponse +export type GetModel404 = NotFoundResponse +export const GetModel404 = NotFoundResponse +export type GetModel500 = InternalServerResponse +export const GetModel500 = InternalServerResponse +export type GetModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "category"?: + | "programming" + | "roleplay" + | "marketing" + | "marketing/seo" + | "technology" + | "science" + | "translation" + | "legal" + | "finance" + | "health" + | "trivia" + | "academia" + readonly "supported_parameters"?: string + readonly "output_modalities"?: string + readonly "sort"?: + | "most-popular" + | "newest" + | "top-weekly" + | "pricing-low-to-high" + | "pricing-high-to-low" + | "context-high-to-low" + | "throughput-high-to-low" + | "latency-low-to-high" + | "intelligence-high-to-low" + | "coding-high-to-low" + | "agentic-high-to-low" + | "design-arena-elo-high-to-low" + readonly "q"?: string + readonly "input_modalities"?: string + readonly "context"?: number + readonly "min_price"?: number | null + readonly "max_price"?: number | null + readonly "arch"?: string + readonly "model_authors"?: string + readonly "providers"?: string + readonly "distillable"?: "true" | "false" + readonly "zdr"?: "true" + readonly "region"?: "eu" + readonly "min_output_price"?: number | null + readonly "max_output_price"?: number | null + readonly "min_age_days"?: number | null + readonly "max_age_days"?: number | null + readonly "min_intelligence_index"?: number | null + readonly "max_intelligence_index"?: number | null + readonly "min_coding_index"?: number | null + readonly "max_coding_index"?: number | null + readonly "min_agentic_index"?: number | null + readonly "max_agentic_index"?: number | null + readonly "min_tool_success_rate"?: number | null + readonly "max_tool_success_rate"?: number | null +} +export const GetModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ + "description": + "Number of records to skip for pagination. When both offset and limit are omitted, the full list is returned" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of records to return (max 1000). When both offset and limit are omitted, the full list is returned" + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" })) + ), + "category": Schema.optionalKey( + Schema.Literals([ + "programming", + "roleplay", + "marketing", + "marketing/seo", + "technology", + "science", + "translation", + "legal", + "finance", + "health", + "trivia", + "academia" + ]).annotate({ "description": "Filter models by use case category" }) + ), + "supported_parameters": Schema.optionalKey( + Schema.String.annotate({ "description": "Filter models by supported parameter (comma-separated)" }) + ), + "output_modalities": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Filter models by output modality. Accepts a comma-separated list of modalities (text, image, audio, embeddings) or \"all\" to include all models. Defaults to \"text\"." + }) + ), + "sort": Schema.optionalKey( + Schema.Literals([ + "most-popular", + "newest", + "top-weekly", + "pricing-low-to-high", + "pricing-high-to-low", + "context-high-to-low", + "throughput-high-to-low", + "latency-low-to-high", + "intelligence-high-to-low", + "coding-high-to-low", + "agentic-high-to-low", + "design-arena-elo-high-to-low" + ]).annotate({ + "description": + "Sort the returned models server-side. Prefer this over fetching the full list and sorting client-side. Options: pricing-low-to-high, pricing-high-to-low (average prompt/completion price), context-high-to-low (context length), throughput-high-to-low, latency-low-to-high (recent median performance), most-popular, top-weekly (tokens processed in the last week), newest (creation date), intelligence-high-to-low, coding-high-to-low, agentic-high-to-low (Artificial Analysis indices), design-arena-elo-high-to-low (best Design Arena ELO across arenas). Models without a score for the chosen benchmark are placed last. When omitted, the existing default ordering is preserved." + }) + ), + "q": Schema.optionalKey(Schema.String.annotate({ "description": "Free-text search by model name or slug." })), + "input_modalities": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by input modality. Comma-separated list of: text, image, audio, file." + }) + ), + "context": Schema.optionalKey( + Schema.Number.annotate({ + "description": "Minimum context length (tokens). Models with smaller context are excluded." + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ) + ), + "min_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum prompt price in $/M tokens." }) + ), + "max_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum prompt price in $/M tokens." }) + ), + "arch": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by architecture/model family (e.g. GPT, Claude, Gemini, Llama)." + }) + ), + "model_authors": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by the organization that created the model. Comma-separated list of author slugs." + }) + ), + "providers": Schema.optionalKey( + Schema.String.annotate({ + "description": "Filter models by hosting provider. Comma-separated list of provider names." + }) + ), + "distillable": Schema.optionalKey( + Schema.Literals(["true", "false"]).annotate({ + "description": + "Filter by distillation capability. \"true\" returns only distillable models, \"false\" excludes them." + }) + ), + "zdr": Schema.optionalKey( + Schema.Literal("true").annotate({ + "description": "When set to \"true\", return only models with zero data retention endpoints." + }) + ), + "region": Schema.optionalKey( + Schema.Literal("eu").annotate({ + "description": "Filter to models with endpoints in the given data region. Currently only \"eu\" is supported." + }) + ), + "min_output_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum completion (output) price in $/M tokens." }) + ), + "max_output_price": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum completion (output) price in $/M tokens." }) + ), + "min_age_days": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum model age in days since its creation date." }) + ), + "max_age_days": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum model age in days since its creation date." }) + ), + "min_intelligence_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum Artificial Analysis intelligence index." }) + ), + "max_intelligence_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum Artificial Analysis intelligence index." }) + ), + "min_coding_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum Artificial Analysis coding index." }) + ), + "max_coding_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum Artificial Analysis coding index." }) + ), + "min_agentic_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Minimum Artificial Analysis agentic index." }) + ), + "max_agentic_index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Maximum Artificial Analysis agentic index." }) + ), + "min_tool_success_rate": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(1).annotate({ "expected": "a value less than or equal to 1" })), + Schema.Null + ]).annotate({ + "description": + "Minimum tool-calling success rate, as a fraction in [0, 1] (e.g. 0.9 = 90% of requests finishing with a tool_calls finish reason)." + }) + ), + "max_tool_success_rate": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isFinite().annotate({ "expected": "a finite number" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ).check(Schema.isLessThanOrEqualTo(1).annotate({ "expected": "a value less than or equal to 1" })), + Schema.Null + ]).annotate({ "description": "Maximum tool-calling success rate, as a fraction in [0, 1]." }) + ) +}) +export type GetModels200 = ModelsListResponse +export const GetModels200 = ModelsListResponse +export type GetModels400 = BadRequestResponse +export const GetModels400 = BadRequestResponse +export type GetModels403 = ForbiddenResponse +export const GetModels403 = ForbiddenResponse +export type GetModels500 = InternalServerResponse +export const GetModels500 = InternalServerResponse +export type ListEndpointsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListEndpointsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListEndpoints200 = { readonly "data": ListEndpointsResponse } +export const ListEndpoints200 = Schema.Struct({ "data": ListEndpointsResponse }) +export type ListEndpoints403 = ForbiddenResponse +export const ListEndpoints403 = ForbiddenResponse +export type ListEndpoints404 = NotFoundResponse +export const ListEndpoints404 = NotFoundResponse +export type ListEndpoints500 = InternalServerResponse +export const ListEndpoints500 = InternalServerResponse +export type ListModelsCountParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "output_modalities"?: string +} +export const ListModelsCountParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "output_modalities": Schema.optionalKey( + Schema.String.annotate({ + "description": + "Filter models by output modality. Accepts a comma-separated list of modalities (text, image, audio, embeddings) or \"all\" to include all models. Defaults to \"text\"." + }) + ) +}) +export type ListModelsCount200 = ModelsCountResponse +export const ListModelsCount200 = ModelsCountResponse +export type ListModelsCount400 = BadRequestResponse +export const ListModelsCount400 = BadRequestResponse +export type ListModelsCount403 = ForbiddenResponse +export const ListModelsCount403 = ForbiddenResponse +export type ListModelsCount500 = InternalServerResponse +export const ListModelsCount500 = InternalServerResponse +export type ListModelsUserParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListModelsUserParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ + "description": + "Number of records to skip for pagination. When both offset and limit are omitted, the full list is returned" + }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ + "description": + "Maximum number of records to return (max 1000). When both offset and limit are omitted, the full list is returned" + }).check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" }) + ).check(Schema.isLessThanOrEqualTo(1000).annotate({ "expected": "a value less than or equal to 1000" })) + ) +}) +export type ListModelsUser200 = ModelsListResponse +export const ListModelsUser200 = ModelsListResponse +export type ListModelsUser401 = UnauthorizedResponse +export const ListModelsUser401 = UnauthorizedResponse +export type ListModelsUser403 = ForbiddenResponse +export const ListModelsUser403 = ForbiddenResponse +export type ListModelsUser404 = NotFoundResponse +export const ListModelsUser404 = NotFoundResponse +export type ListModelsUser500 = InternalServerResponse +export const ListModelsUser500 = InternalServerResponse +export type ListObservabilityDestinationsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number + readonly "workspace_id"?: string +} +export const ListObservabilityDestinationsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ), + "workspace_id": Schema.optionalKey( + Schema.String.annotate({ + "description": "Optional workspace ID to filter by. Defaults to the authenticated entity's default workspace.", + "format": "uuid" + }) + ) +}) +export type ListObservabilityDestinations200 = ListObservabilityDestinationsResponse +export const ListObservabilityDestinations200 = ListObservabilityDestinationsResponse +export type ListObservabilityDestinations400 = BadRequestResponse +export const ListObservabilityDestinations400 = BadRequestResponse +export type ListObservabilityDestinations401 = UnauthorizedResponse +export const ListObservabilityDestinations401 = UnauthorizedResponse +export type ListObservabilityDestinations500 = InternalServerResponse +export const ListObservabilityDestinations500 = InternalServerResponse +export type CreateObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateObservabilityDestinationRequestJson = CreateObservabilityDestinationRequest +export const CreateObservabilityDestinationRequestJson = CreateObservabilityDestinationRequest +export type CreateObservabilityDestination201 = CreateObservabilityDestinationResponse +export const CreateObservabilityDestination201 = CreateObservabilityDestinationResponse +export type CreateObservabilityDestination400 = BadRequestResponse +export const CreateObservabilityDestination400 = BadRequestResponse +export type CreateObservabilityDestination401 = UnauthorizedResponse +export const CreateObservabilityDestination401 = UnauthorizedResponse +export type CreateObservabilityDestination403 = ForbiddenResponse +export const CreateObservabilityDestination403 = ForbiddenResponse +export type CreateObservabilityDestination409 = ConflictResponse +export const CreateObservabilityDestination409 = ConflictResponse +export type CreateObservabilityDestination500 = InternalServerResponse +export const CreateObservabilityDestination500 = InternalServerResponse +export type GetObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetObservabilityDestination200 = GetObservabilityDestinationResponse +export const GetObservabilityDestination200 = GetObservabilityDestinationResponse +export type GetObservabilityDestination401 = UnauthorizedResponse +export const GetObservabilityDestination401 = UnauthorizedResponse +export type GetObservabilityDestination404 = NotFoundResponse +export const GetObservabilityDestination404 = NotFoundResponse +export type GetObservabilityDestination500 = InternalServerResponse +export const GetObservabilityDestination500 = InternalServerResponse +export type DeleteObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteObservabilityDestination200 = DeleteObservabilityDestinationResponse +export const DeleteObservabilityDestination200 = DeleteObservabilityDestinationResponse +export type DeleteObservabilityDestination401 = UnauthorizedResponse +export const DeleteObservabilityDestination401 = UnauthorizedResponse +export type DeleteObservabilityDestination404 = NotFoundResponse +export const DeleteObservabilityDestination404 = NotFoundResponse +export type DeleteObservabilityDestination500 = InternalServerResponse +export const DeleteObservabilityDestination500 = InternalServerResponse +export type UpdateObservabilityDestinationParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateObservabilityDestinationParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateObservabilityDestinationRequestJson = UpdateObservabilityDestinationRequest +export const UpdateObservabilityDestinationRequestJson = UpdateObservabilityDestinationRequest +export type UpdateObservabilityDestination200 = UpdateObservabilityDestinationResponse +export const UpdateObservabilityDestination200 = UpdateObservabilityDestinationResponse +export type UpdateObservabilityDestination400 = BadRequestResponse +export const UpdateObservabilityDestination400 = BadRequestResponse +export type UpdateObservabilityDestination401 = UnauthorizedResponse +export const UpdateObservabilityDestination401 = UnauthorizedResponse +export type UpdateObservabilityDestination404 = NotFoundResponse +export const UpdateObservabilityDestination404 = NotFoundResponse +export type UpdateObservabilityDestination409 = ConflictResponse +export const UpdateObservabilityDestination409 = ConflictResponse +export type UpdateObservabilityDestination500 = InternalServerResponse +export const UpdateObservabilityDestination500 = InternalServerResponse +export type ListOrganizationMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListOrganizationMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListOrganizationMembers200 = { + readonly "data": ReadonlyArray< + { + readonly "email": string + readonly "first_name": string | null + readonly "id": string + readonly "last_name": string | null + readonly "role": "org:admin" | "org:member" + } + > + readonly "total_count": number +} +export const ListOrganizationMembers200 = Schema.Struct({ + "data": Schema.Array(Schema.Struct({ + "email": Schema.String.annotate({ "description": "Email address of the member" }), + "first_name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "First name of the member" }), + "id": Schema.String.annotate({ "description": "User ID of the organization member" }), + "last_name": Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "Last name of the member" }), + "role": Schema.Literals(["org:admin", "org:member"]).annotate({ + "description": "Role of the member in the organization" + }) + })).annotate({ "description": "List of organization members" }), + "total_count": Schema.Number.annotate({ "description": "Total number of members in the organization" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) +}) +export type ListOrganizationMembers401 = UnauthorizedResponse +export const ListOrganizationMembers401 = UnauthorizedResponse +export type ListOrganizationMembers404 = NotFoundResponse +export const ListOrganizationMembers404 = NotFoundResponse +export type ListOrganizationMembers500 = InternalServerResponse +export const ListOrganizationMembers500 = InternalServerResponse +export type ListPresetsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListPresetsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListPresets200 = ListPresetsResponse +export const ListPresets200 = ListPresetsResponse +export type ListPresets400 = BadRequestResponse +export const ListPresets400 = BadRequestResponse +export type ListPresets401 = UnauthorizedResponse +export const ListPresets401 = UnauthorizedResponse +export type ListPresets500 = InternalServerResponse +export const ListPresets500 = InternalServerResponse +export type GetPresetParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetPresetParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetPreset200 = GetPresetResponse +export const GetPreset200 = GetPresetResponse +export type GetPreset400 = BadRequestResponse +export const GetPreset400 = BadRequestResponse +export type GetPreset401 = UnauthorizedResponse +export const GetPreset401 = UnauthorizedResponse +export type GetPreset404 = NotFoundResponse +export const GetPreset404 = NotFoundResponse +export type GetPreset500 = InternalServerResponse +export const GetPreset500 = InternalServerResponse +export type CreatePresetsChatCompletionsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreatePresetsChatCompletionsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreatePresetsChatCompletionsRequestJson = ChatRequest +export const CreatePresetsChatCompletionsRequestJson = ChatRequest +export type CreatePresetsChatCompletions200 = CreatePresetFromInferenceResponse +export const CreatePresetsChatCompletions200 = CreatePresetFromInferenceResponse +export type CreatePresetsChatCompletions400 = BadRequestResponse +export const CreatePresetsChatCompletions400 = BadRequestResponse +export type CreatePresetsChatCompletions401 = UnauthorizedResponse +export const CreatePresetsChatCompletions401 = UnauthorizedResponse +export type CreatePresetsChatCompletions403 = ForbiddenResponse +export const CreatePresetsChatCompletions403 = ForbiddenResponse +export type CreatePresetsChatCompletions404 = NotFoundResponse +export const CreatePresetsChatCompletions404 = NotFoundResponse +export type CreatePresetsChatCompletions409 = ConflictResponse +export const CreatePresetsChatCompletions409 = ConflictResponse +export type CreatePresetsChatCompletions500 = InternalServerResponse +export const CreatePresetsChatCompletions500 = InternalServerResponse +export type CreatePresetsMessagesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreatePresetsMessagesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreatePresetsMessagesRequestJson = MessagesRequest +export const CreatePresetsMessagesRequestJson = MessagesRequest +export type CreatePresetsMessages200 = CreatePresetFromInferenceResponse +export const CreatePresetsMessages200 = CreatePresetFromInferenceResponse +export type CreatePresetsMessages400 = BadRequestResponse +export const CreatePresetsMessages400 = BadRequestResponse +export type CreatePresetsMessages401 = UnauthorizedResponse +export const CreatePresetsMessages401 = UnauthorizedResponse +export type CreatePresetsMessages403 = ForbiddenResponse +export const CreatePresetsMessages403 = ForbiddenResponse +export type CreatePresetsMessages404 = NotFoundResponse +export const CreatePresetsMessages404 = NotFoundResponse +export type CreatePresetsMessages409 = ConflictResponse +export const CreatePresetsMessages409 = ConflictResponse +export type CreatePresetsMessages500 = InternalServerResponse +export const CreatePresetsMessages500 = InternalServerResponse +export type CreatePresetsResponsesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreatePresetsResponsesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreatePresetsResponsesRequestJson = ResponsesRequest +export const CreatePresetsResponsesRequestJson = ResponsesRequest +export type CreatePresetsResponses200 = CreatePresetFromInferenceResponse +export const CreatePresetsResponses200 = CreatePresetFromInferenceResponse +export type CreatePresetsResponses400 = BadRequestResponse +export const CreatePresetsResponses400 = BadRequestResponse +export type CreatePresetsResponses401 = UnauthorizedResponse +export const CreatePresetsResponses401 = UnauthorizedResponse +export type CreatePresetsResponses403 = ForbiddenResponse +export const CreatePresetsResponses403 = ForbiddenResponse +export type CreatePresetsResponses404 = NotFoundResponse +export const CreatePresetsResponses404 = NotFoundResponse +export type CreatePresetsResponses409 = ConflictResponse +export const CreatePresetsResponses409 = ConflictResponse +export type CreatePresetsResponses500 = InternalServerResponse +export const CreatePresetsResponses500 = InternalServerResponse +export type ListPresetVersionsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListPresetVersionsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListPresetVersions200 = ListPresetVersionsResponse +export const ListPresetVersions200 = ListPresetVersionsResponse +export type ListPresetVersions400 = BadRequestResponse +export const ListPresetVersions400 = BadRequestResponse +export type ListPresetVersions401 = UnauthorizedResponse +export const ListPresetVersions401 = UnauthorizedResponse +export type ListPresetVersions404 = NotFoundResponse +export const ListPresetVersions404 = NotFoundResponse +export type ListPresetVersions500 = InternalServerResponse +export const ListPresetVersions500 = InternalServerResponse +export type GetPresetVersionParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetPresetVersionParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetPresetVersion200 = GetPresetVersionResponse +export const GetPresetVersion200 = GetPresetVersionResponse +export type GetPresetVersion400 = BadRequestResponse +export const GetPresetVersion400 = BadRequestResponse +export type GetPresetVersion401 = UnauthorizedResponse +export const GetPresetVersion401 = UnauthorizedResponse +export type GetPresetVersion404 = NotFoundResponse +export const GetPresetVersion404 = NotFoundResponse +export type GetPresetVersion500 = InternalServerResponse +export const GetPresetVersion500 = InternalServerResponse +export type ListProvidersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListProvidersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListProviders200 = { + readonly "data": ReadonlyArray< + { + readonly "datacenters"?: + | ReadonlyArray< + | "AD" + | "AE" + | "AF" + | "AG" + | "AI" + | "AL" + | "AM" + | "AO" + | "AQ" + | "AR" + | "AS" + | "AT" + | "AU" + | "AW" + | "AX" + | "AZ" + | "BA" + | "BB" + | "BD" + | "BE" + | "BF" + | "BG" + | "BH" + | "BI" + | "BJ" + | "BL" + | "BM" + | "BN" + | "BO" + | "BQ" + | "BR" + | "BS" + | "BT" + | "BV" + | "BW" + | "BY" + | "BZ" + | "CA" + | "CC" + | "CD" + | "CF" + | "CG" + | "CH" + | "CI" + | "CK" + | "CL" + | "CM" + | "CN" + | "CO" + | "CR" + | "CU" + | "CV" + | "CW" + | "CX" + | "CY" + | "CZ" + | "DE" + | "DJ" + | "DK" + | "DM" + | "DO" + | "DZ" + | "EC" + | "EE" + | "EG" + | "EH" + | "ER" + | "ES" + | "ET" + | "FI" + | "FJ" + | "FK" + | "FM" + | "FO" + | "FR" + | "GA" + | "GB" + | "GD" + | "GE" + | "GF" + | "GG" + | "GH" + | "GI" + | "GL" + | "GM" + | "GN" + | "GP" + | "GQ" + | "GR" + | "GS" + | "GT" + | "GU" + | "GW" + | "GY" + | "HK" + | "HM" + | "HN" + | "HR" + | "HT" + | "HU" + | "ID" + | "IE" + | "IL" + | "IM" + | "IN" + | "IO" + | "IQ" + | "IR" + | "IS" + | "IT" + | "JE" + | "JM" + | "JO" + | "JP" + | "KE" + | "KG" + | "KH" + | "KI" + | "KM" + | "KN" + | "KP" + | "KR" + | "KW" + | "KY" + | "KZ" + | "LA" + | "LB" + | "LC" + | "LI" + | "LK" + | "LR" + | "LS" + | "LT" + | "LU" + | "LV" + | "LY" + | "MA" + | "MC" + | "MD" + | "ME" + | "MF" + | "MG" + | "MH" + | "MK" + | "ML" + | "MM" + | "MN" + | "MO" + | "MP" + | "MQ" + | "MR" + | "MS" + | "MT" + | "MU" + | "MV" + | "MW" + | "MX" + | "MY" + | "MZ" + | "NA" + | "NC" + | "NE" + | "NF" + | "NG" + | "NI" + | "NL" + | "NO" + | "NP" + | "NR" + | "NU" + | "NZ" + | "OM" + | "PA" + | "PE" + | "PF" + | "PG" + | "PH" + | "PK" + | "PL" + | "PM" + | "PN" + | "PR" + | "PS" + | "PT" + | "PW" + | "PY" + | "QA" + | "RE" + | "RO" + | "RS" + | "RU" + | "RW" + | "SA" + | "SB" + | "SC" + | "SD" + | "SE" + | "SG" + | "SH" + | "SI" + | "SJ" + | "SK" + | "SL" + | "SM" + | "SN" + | "SO" + | "SR" + | "SS" + | "ST" + | "SV" + | "SX" + | "SY" + | "SZ" + | "TC" + | "TD" + | "TF" + | "TG" + | "TH" + | "TJ" + | "TK" + | "TL" + | "TM" + | "TN" + | "TO" + | "TR" + | "TT" + | "TV" + | "TW" + | "TZ" + | "UA" + | "UG" + | "UM" + | "US" + | "UY" + | "UZ" + | "VA" + | "VC" + | "VE" + | "VG" + | "VI" + | "VN" + | "VU" + | "WF" + | "WS" + | "YE" + | "YT" + | "ZA" + | "ZM" + | "ZW" + > + | null + readonly "headquarters"?: + | "AD" + | "AE" + | "AF" + | "AG" + | "AI" + | "AL" + | "AM" + | "AO" + | "AQ" + | "AR" + | "AS" + | "AT" + | "AU" + | "AW" + | "AX" + | "AZ" + | "BA" + | "BB" + | "BD" + | "BE" + | "BF" + | "BG" + | "BH" + | "BI" + | "BJ" + | "BL" + | "BM" + | "BN" + | "BO" + | "BQ" + | "BR" + | "BS" + | "BT" + | "BV" + | "BW" + | "BY" + | "BZ" + | "CA" + | "CC" + | "CD" + | "CF" + | "CG" + | "CH" + | "CI" + | "CK" + | "CL" + | "CM" + | "CN" + | "CO" + | "CR" + | "CU" + | "CV" + | "CW" + | "CX" + | "CY" + | "CZ" + | "DE" + | "DJ" + | "DK" + | "DM" + | "DO" + | "DZ" + | "EC" + | "EE" + | "EG" + | "EH" + | "ER" + | "ES" + | "ET" + | "FI" + | "FJ" + | "FK" + | "FM" + | "FO" + | "FR" + | "GA" + | "GB" + | "GD" + | "GE" + | "GF" + | "GG" + | "GH" + | "GI" + | "GL" + | "GM" + | "GN" + | "GP" + | "GQ" + | "GR" + | "GS" + | "GT" + | "GU" + | "GW" + | "GY" + | "HK" + | "HM" + | "HN" + | "HR" + | "HT" + | "HU" + | "ID" + | "IE" + | "IL" + | "IM" + | "IN" + | "IO" + | "IQ" + | "IR" + | "IS" + | "IT" + | "JE" + | "JM" + | "JO" + | "JP" + | "KE" + | "KG" + | "KH" + | "KI" + | "KM" + | "KN" + | "KP" + | "KR" + | "KW" + | "KY" + | "KZ" + | "LA" + | "LB" + | "LC" + | "LI" + | "LK" + | "LR" + | "LS" + | "LT" + | "LU" + | "LV" + | "LY" + | "MA" + | "MC" + | "MD" + | "ME" + | "MF" + | "MG" + | "MH" + | "MK" + | "ML" + | "MM" + | "MN" + | "MO" + | "MP" + | "MQ" + | "MR" + | "MS" + | "MT" + | "MU" + | "MV" + | "MW" + | "MX" + | "MY" + | "MZ" + | "NA" + | "NC" + | "NE" + | "NF" + | "NG" + | "NI" + | "NL" + | "NO" + | "NP" + | "NR" + | "NU" + | "NZ" + | "OM" + | "PA" + | "PE" + | "PF" + | "PG" + | "PH" + | "PK" + | "PL" + | "PM" + | "PN" + | "PR" + | "PS" + | "PT" + | "PW" + | "PY" + | "QA" + | "RE" + | "RO" + | "RS" + | "RU" + | "RW" + | "SA" + | "SB" + | "SC" + | "SD" + | "SE" + | "SG" + | "SH" + | "SI" + | "SJ" + | "SK" + | "SL" + | "SM" + | "SN" + | "SO" + | "SR" + | "SS" + | "ST" + | "SV" + | "SX" + | "SY" + | "SZ" + | "TC" + | "TD" + | "TF" + | "TG" + | "TH" + | "TJ" + | "TK" + | "TL" + | "TM" + | "TN" + | "TO" + | "TR" + | "TT" + | "TV" + | "TW" + | "TZ" + | "UA" + | "UG" + | "UM" + | "US" + | "UY" + | "UZ" + | "VA" + | "VC" + | "VE" + | "VG" + | "VI" + | "VN" + | "VU" + | "WF" + | "WS" + | "YE" + | "YT" + | "ZA" + | "ZM" + | "ZW" + | null + readonly "name": string + readonly "privacy_policy_url": string | null + readonly "slug": string + readonly "status_page_url"?: string | null + readonly "terms_of_service_url"?: string | null + } + > +} +export const ListProviders200 = Schema.Struct({ + "data": Schema.Array(Schema.Struct({ + "datacenters": Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Literals([ + "AD", + "AE", + "AF", + "AG", + "AI", + "AL", + "AM", + "AO", + "AQ", + "AR", + "AS", + "AT", + "AU", + "AW", + "AX", + "AZ", + "BA", + "BB", + "BD", + "BE", + "BF", + "BG", + "BH", + "BI", + "BJ", + "BL", + "BM", + "BN", + "BO", + "BQ", + "BR", + "BS", + "BT", + "BV", + "BW", + "BY", + "BZ", + "CA", + "CC", + "CD", + "CF", + "CG", + "CH", + "CI", + "CK", + "CL", + "CM", + "CN", + "CO", + "CR", + "CU", + "CV", + "CW", + "CX", + "CY", + "CZ", + "DE", + "DJ", + "DK", + "DM", + "DO", + "DZ", + "EC", + "EE", + "EG", + "EH", + "ER", + "ES", + "ET", + "FI", + "FJ", + "FK", + "FM", + "FO", + "FR", + "GA", + "GB", + "GD", + "GE", + "GF", + "GG", + "GH", + "GI", + "GL", + "GM", + "GN", + "GP", + "GQ", + "GR", + "GS", + "GT", + "GU", + "GW", + "GY", + "HK", + "HM", + "HN", + "HR", + "HT", + "HU", + "ID", + "IE", + "IL", + "IM", + "IN", + "IO", + "IQ", + "IR", + "IS", + "IT", + "JE", + "JM", + "JO", + "JP", + "KE", + "KG", + "KH", + "KI", + "KM", + "KN", + "KP", + "KR", + "KW", + "KY", + "KZ", + "LA", + "LB", + "LC", + "LI", + "LK", + "LR", + "LS", + "LT", + "LU", + "LV", + "LY", + "MA", + "MC", + "MD", + "ME", + "MF", + "MG", + "MH", + "MK", + "ML", + "MM", + "MN", + "MO", + "MP", + "MQ", + "MR", + "MS", + "MT", + "MU", + "MV", + "MW", + "MX", + "MY", + "MZ", + "NA", + "NC", + "NE", + "NF", + "NG", + "NI", + "NL", + "NO", + "NP", + "NR", + "NU", + "NZ", + "OM", + "PA", + "PE", + "PF", + "PG", + "PH", + "PK", + "PL", + "PM", + "PN", + "PR", + "PS", + "PT", + "PW", + "PY", + "QA", + "RE", + "RO", + "RS", + "RU", + "RW", + "SA", + "SB", + "SC", + "SD", + "SE", + "SG", + "SH", + "SI", + "SJ", + "SK", + "SL", + "SM", + "SN", + "SO", + "SR", + "SS", + "ST", + "SV", + "SX", + "SY", + "SZ", + "TC", + "TD", + "TF", + "TG", + "TH", + "TJ", + "TK", + "TL", + "TM", + "TN", + "TO", + "TR", + "TT", + "TV", + "TW", + "TZ", + "UA", + "UG", + "UM", + "US", + "UY", + "UZ", + "VA", + "VC", + "VE", + "VG", + "VI", + "VN", + "VU", + "WF", + "WS", + "YE", + "YT", + "ZA", + "ZM", + "ZW" + ]) + ), + Schema.Null + ]).annotate({ "description": "ISO 3166-1 Alpha-2 country codes of the provider datacenter locations" }) + ), + "headquarters": Schema.optionalKey( + Schema.Union([ + Schema.Literal("AD"), + Schema.Literal("AE"), + Schema.Literal("AF"), + Schema.Literal("AG"), + Schema.Literal("AI"), + Schema.Literal("AL"), + Schema.Literal("AM"), + Schema.Literal("AO"), + Schema.Literal("AQ"), + Schema.Literal("AR"), + Schema.Literal("AS"), + Schema.Literal("AT"), + Schema.Literal("AU"), + Schema.Literal("AW"), + Schema.Literal("AX"), + Schema.Literal("AZ"), + Schema.Literal("BA"), + Schema.Literal("BB"), + Schema.Literal("BD"), + Schema.Literal("BE"), + Schema.Literal("BF"), + Schema.Literal("BG"), + Schema.Literal("BH"), + Schema.Literal("BI"), + Schema.Literal("BJ"), + Schema.Literal("BL"), + Schema.Literal("BM"), + Schema.Literal("BN"), + Schema.Literal("BO"), + Schema.Literal("BQ"), + Schema.Literal("BR"), + Schema.Literal("BS"), + Schema.Literal("BT"), + Schema.Literal("BV"), + Schema.Literal("BW"), + Schema.Literal("BY"), + Schema.Literal("BZ"), + Schema.Literal("CA"), + Schema.Literal("CC"), + Schema.Literal("CD"), + Schema.Literal("CF"), + Schema.Literal("CG"), + Schema.Literal("CH"), + Schema.Literal("CI"), + Schema.Literal("CK"), + Schema.Literal("CL"), + Schema.Literal("CM"), + Schema.Literal("CN"), + Schema.Literal("CO"), + Schema.Literal("CR"), + Schema.Literal("CU"), + Schema.Literal("CV"), + Schema.Literal("CW"), + Schema.Literal("CX"), + Schema.Literal("CY"), + Schema.Literal("CZ"), + Schema.Literal("DE"), + Schema.Literal("DJ"), + Schema.Literal("DK"), + Schema.Literal("DM"), + Schema.Literal("DO"), + Schema.Literal("DZ"), + Schema.Literal("EC"), + Schema.Literal("EE"), + Schema.Literal("EG"), + Schema.Literal("EH"), + Schema.Literal("ER"), + Schema.Literal("ES"), + Schema.Literal("ET"), + Schema.Literal("FI"), + Schema.Literal("FJ"), + Schema.Literal("FK"), + Schema.Literal("FM"), + Schema.Literal("FO"), + Schema.Literal("FR"), + Schema.Literal("GA"), + Schema.Literal("GB"), + Schema.Literal("GD"), + Schema.Literal("GE"), + Schema.Literal("GF"), + Schema.Literal("GG"), + Schema.Literal("GH"), + Schema.Literal("GI"), + Schema.Literal("GL"), + Schema.Literal("GM"), + Schema.Literal("GN"), + Schema.Literal("GP"), + Schema.Literal("GQ"), + Schema.Literal("GR"), + Schema.Literal("GS"), + Schema.Literal("GT"), + Schema.Literal("GU"), + Schema.Literal("GW"), + Schema.Literal("GY"), + Schema.Literal("HK"), + Schema.Literal("HM"), + Schema.Literal("HN"), + Schema.Literal("HR"), + Schema.Literal("HT"), + Schema.Literal("HU"), + Schema.Literal("ID"), + Schema.Literal("IE"), + Schema.Literal("IL"), + Schema.Literal("IM"), + Schema.Literal("IN"), + Schema.Literal("IO"), + Schema.Literal("IQ"), + Schema.Literal("IR"), + Schema.Literal("IS"), + Schema.Literal("IT"), + Schema.Literal("JE"), + Schema.Literal("JM"), + Schema.Literal("JO"), + Schema.Literal("JP"), + Schema.Literal("KE"), + Schema.Literal("KG"), + Schema.Literal("KH"), + Schema.Literal("KI"), + Schema.Literal("KM"), + Schema.Literal("KN"), + Schema.Literal("KP"), + Schema.Literal("KR"), + Schema.Literal("KW"), + Schema.Literal("KY"), + Schema.Literal("KZ"), + Schema.Literal("LA"), + Schema.Literal("LB"), + Schema.Literal("LC"), + Schema.Literal("LI"), + Schema.Literal("LK"), + Schema.Literal("LR"), + Schema.Literal("LS"), + Schema.Literal("LT"), + Schema.Literal("LU"), + Schema.Literal("LV"), + Schema.Literal("LY"), + Schema.Literal("MA"), + Schema.Literal("MC"), + Schema.Literal("MD"), + Schema.Literal("ME"), + Schema.Literal("MF"), + Schema.Literal("MG"), + Schema.Literal("MH"), + Schema.Literal("MK"), + Schema.Literal("ML"), + Schema.Literal("MM"), + Schema.Literal("MN"), + Schema.Literal("MO"), + Schema.Literal("MP"), + Schema.Literal("MQ"), + Schema.Literal("MR"), + Schema.Literal("MS"), + Schema.Literal("MT"), + Schema.Literal("MU"), + Schema.Literal("MV"), + Schema.Literal("MW"), + Schema.Literal("MX"), + Schema.Literal("MY"), + Schema.Literal("MZ"), + Schema.Literal("NA"), + Schema.Literal("NC"), + Schema.Literal("NE"), + Schema.Literal("NF"), + Schema.Literal("NG"), + Schema.Literal("NI"), + Schema.Literal("NL"), + Schema.Literal("NO"), + Schema.Literal("NP"), + Schema.Literal("NR"), + Schema.Literal("NU"), + Schema.Literal("NZ"), + Schema.Literal("OM"), + Schema.Literal("PA"), + Schema.Literal("PE"), + Schema.Literal("PF"), + Schema.Literal("PG"), + Schema.Literal("PH"), + Schema.Literal("PK"), + Schema.Literal("PL"), + Schema.Literal("PM"), + Schema.Literal("PN"), + Schema.Literal("PR"), + Schema.Literal("PS"), + Schema.Literal("PT"), + Schema.Literal("PW"), + Schema.Literal("PY"), + Schema.Literal("QA"), + Schema.Literal("RE"), + Schema.Literal("RO"), + Schema.Literal("RS"), + Schema.Literal("RU"), + Schema.Literal("RW"), + Schema.Literal("SA"), + Schema.Literal("SB"), + Schema.Literal("SC"), + Schema.Literal("SD"), + Schema.Literal("SE"), + Schema.Literal("SG"), + Schema.Literal("SH"), + Schema.Literal("SI"), + Schema.Literal("SJ"), + Schema.Literal("SK"), + Schema.Literal("SL"), + Schema.Literal("SM"), + Schema.Literal("SN"), + Schema.Literal("SO"), + Schema.Literal("SR"), + Schema.Literal("SS"), + Schema.Literal("ST"), + Schema.Literal("SV"), + Schema.Literal("SX"), + Schema.Literal("SY"), + Schema.Literal("SZ"), + Schema.Literal("TC"), + Schema.Literal("TD"), + Schema.Literal("TF"), + Schema.Literal("TG"), + Schema.Literal("TH"), + Schema.Literal("TJ"), + Schema.Literal("TK"), + Schema.Literal("TL"), + Schema.Literal("TM"), + Schema.Literal("TN"), + Schema.Literal("TO"), + Schema.Literal("TR"), + Schema.Literal("TT"), + Schema.Literal("TV"), + Schema.Literal("TW"), + Schema.Literal("TZ"), + Schema.Literal("UA"), + Schema.Literal("UG"), + Schema.Literal("UM"), + Schema.Literal("US"), + Schema.Literal("UY"), + Schema.Literal("UZ"), + Schema.Literal("VA"), + Schema.Literal("VC"), + Schema.Literal("VE"), + Schema.Literal("VG"), + Schema.Literal("VI"), + Schema.Literal("VN"), + Schema.Literal("VU"), + Schema.Literal("WF"), + Schema.Literal("WS"), + Schema.Literal("YE"), + Schema.Literal("YT"), + Schema.Literal("ZA"), + Schema.Literal("ZM"), + Schema.Literal("ZW"), + Schema.Null + ]).annotate({ "description": "ISO 3166-1 Alpha-2 country code of the provider headquarters" }) + ), + "name": Schema.String.annotate({ "description": "Display name of the provider" }), + "privacy_policy_url": Schema.Union([Schema.String, Schema.Null]).annotate({ + "description": "URL to the provider's privacy policy" + }), + "slug": Schema.String.annotate({ "description": "URL-friendly identifier for the provider" }), + "status_page_url": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "URL to the provider's status page" }) + ), + "terms_of_service_url": Schema.optionalKey( + Schema.Union([Schema.String, Schema.Null]).annotate({ "description": "URL to the provider's terms of service" }) + ) + })) +}) +export type ListProviders500 = InternalServerResponse +export const ListProviders500 = InternalServerResponse +export type CreateRerankParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateRerankParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateRerankRequestJson = { + readonly "documents": ReadonlyArray + readonly "model": string + readonly "provider"?: ProviderPreferences + readonly "query": string + readonly "top_n"?: number +} +export const CreateRerankRequestJson = Schema.Struct({ + "documents": Schema.Array( + Schema.Union([ + Schema.String, + Schema.Struct({ + "image": Schema.optionalKey( + Schema.String.annotate({ + "description": + "An image associated with the document, as a remote URL (http/https) or a base64-encoded data URI (data:image/...)." + }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The document text" })) + }).annotate({ + "description": + "A structured document with optional text and/or image content. At least one of `text` or `image` must be provided." + }) + ]).annotate({ + "description": + "A document to rerank. Either a plain string, or a structured object with optional `text` and/or `image`." + }) + ).annotate({ + "description": + "The list of documents to rerank. Documents may be plain strings, or structured objects with `text` and/or `image` for multimodal models." + }).check(Schema.isMinLength(1).annotate({ "expected": "a value with a length of at least 1" })), + "model": Schema.String.annotate({ "description": "The rerank model to use" }), + "provider": Schema.optionalKey( + Schema.suspend((): Schema.Codec => ProviderPreferences).annotate({ + "description": "Provider routing preferences for the request." + }) + ), + "query": Schema.String.annotate({ "description": "The search query to rerank documents against" }), + "top_n": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of most relevant documents to return" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })) + ) +}).annotate({ "description": "Rerank request input" }) +export type CreateRerank200 = { + readonly "id"?: string + readonly "model": string + readonly "provider"?: string + readonly "results": ReadonlyArray< + { + readonly "document": { readonly "image"?: string; readonly "text"?: string } + readonly "index": number + readonly "relevance_score": number + } + > + readonly "usage"?: { readonly "cost"?: number; readonly "search_units"?: number; readonly "total_tokens"?: number } +} +export const CreateRerank200 = Schema.Struct({ + "id": Schema.optionalKey( + Schema.String.annotate({ "description": "Unique identifier for the rerank response (ORID format)" }) + ), + "model": Schema.String.annotate({ "description": "The model used for reranking" }), + "provider": Schema.optionalKey( + Schema.String.annotate({ "description": "The provider that served the rerank request" }) + ), + "results": Schema.Array( + Schema.Struct({ + "document": Schema.Struct({ + "image": Schema.optionalKey( + Schema.String.annotate({ "description": "The image (URL or data URI) from the original document" }) + ), + "text": Schema.optionalKey(Schema.String.annotate({ "description": "The document text" })) + }).annotate({ "description": "The document object echoing the original input (text and/or image)" }), + "index": Schema.Number.annotate({ "description": "Index of the document in the original input list" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ), + "relevance_score": Schema.Number.annotate({ + "description": "Relevance score of the document to the query", + "format": "double" + }).check(Schema.isFinite().annotate({ "expected": "a finite number" })) + }).annotate({ "description": "A single rerank result" }) + ).annotate({ "description": "List of rerank results sorted by relevance" }), + "usage": Schema.optionalKey( + Schema.Struct({ + "cost": Schema.optionalKey( + Schema.Number.annotate({ "description": "Cost of the request in credits", "format": "double" }).check( + Schema.isFinite().annotate({ "expected": "a finite number" }) + ) + ), + "search_units": Schema.optionalKey( + Schema.Number.annotate({ "description": "Number of search units consumed (Cohere billing)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ), + "total_tokens": Schema.optionalKey( + Schema.Number.annotate({ "description": "Total number of tokens used" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ) + ) + }).annotate({ "description": "Usage statistics" }) + ) +}).annotate({ "description": "Rerank response containing ranked results" }) +export type CreateRerank200Sse = string +export const CreateRerank200Sse = Schema.String.annotate({ + "description": "Not used for rerank - rerank does not support streaming" +}) +export type CreateRerank400 = BadRequestResponse +export const CreateRerank400 = BadRequestResponse +export type CreateRerank401 = UnauthorizedResponse +export const CreateRerank401 = UnauthorizedResponse +export type CreateRerank402 = PaymentRequiredResponse +export const CreateRerank402 = PaymentRequiredResponse +export type CreateRerank404 = NotFoundResponse +export const CreateRerank404 = NotFoundResponse +export type CreateRerank429 = TooManyRequestsResponse +export const CreateRerank429 = TooManyRequestsResponse +export type CreateRerank500 = InternalServerResponse +export const CreateRerank500 = InternalServerResponse +export type CreateRerank502 = BadGatewayResponse +export const CreateRerank502 = BadGatewayResponse +export type CreateRerank503 = ServiceUnavailableResponse +export const CreateRerank503 = ServiceUnavailableResponse +export type CreateRerank524 = EdgeNetworkTimeoutResponse +export const CreateRerank524 = EdgeNetworkTimeoutResponse +export type CreateRerank529 = ProviderOverloadedResponse +export const CreateRerank529 = ProviderOverloadedResponse +export type CreateResponsesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "X-OpenRouter-Metadata"?: MetadataLevel +} +export const CreateResponsesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "X-OpenRouter-Metadata": Schema.optionalKey(MetadataLevel) +}) +export type CreateResponsesRequestJson = ResponsesRequest +export const CreateResponsesRequestJson = ResponsesRequest +export type CreateResponses200 = OpenResponsesResult +export const CreateResponses200 = OpenResponsesResult +export type CreateResponses200Sse = ResponsesStreamingResponse +export const CreateResponses200Sse: Schema.Schema = ResponsesStreamingResponse +export type CreateResponses400 = BadRequestResponse +export const CreateResponses400 = BadRequestResponse +export type CreateResponses401 = UnauthorizedResponse +export const CreateResponses401 = UnauthorizedResponse +export type CreateResponses402 = PaymentRequiredResponse +export const CreateResponses402 = PaymentRequiredResponse +export type CreateResponses403 = ForbiddenResponse +export const CreateResponses403 = ForbiddenResponse +export type CreateResponses404 = NotFoundResponse +export const CreateResponses404 = NotFoundResponse +export type CreateResponses408 = RequestTimeoutResponse +export const CreateResponses408 = RequestTimeoutResponse +export type CreateResponses413 = PayloadTooLargeResponse +export const CreateResponses413 = PayloadTooLargeResponse +export type CreateResponses422 = UnprocessableEntityResponse +export const CreateResponses422 = UnprocessableEntityResponse +export type CreateResponses429 = TooManyRequestsResponse +export const CreateResponses429 = TooManyRequestsResponse +export type CreateResponses500 = InternalServerResponse +export const CreateResponses500 = InternalServerResponse +export type CreateResponses502 = BadGatewayResponse +export const CreateResponses502 = BadGatewayResponse +export type CreateResponses503 = ServiceUnavailableResponse +export const CreateResponses503 = ServiceUnavailableResponse +export type CreateResponses524 = EdgeNetworkTimeoutResponse +export const CreateResponses524 = EdgeNetworkTimeoutResponse +export type CreateResponses529 = ProviderOverloadedResponse +export const CreateResponses529 = ProviderOverloadedResponse +export type CreateVideosParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateVideosParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateVideosRequestJson = VideoGenerationRequest +export const CreateVideosRequestJson = VideoGenerationRequest +export type CreateVideos202 = VideoGenerationResponse +export const CreateVideos202 = VideoGenerationResponse +export type CreateVideos400 = BadRequestResponse +export const CreateVideos400 = BadRequestResponse +export type CreateVideos401 = UnauthorizedResponse +export const CreateVideos401 = UnauthorizedResponse +export type CreateVideos402 = PaymentRequiredResponse +export const CreateVideos402 = PaymentRequiredResponse +export type CreateVideos404 = NotFoundResponse +export const CreateVideos404 = NotFoundResponse +export type CreateVideos429 = TooManyRequestsResponse +export const CreateVideos429 = TooManyRequestsResponse +export type CreateVideos500 = InternalServerResponse +export const CreateVideos500 = InternalServerResponse +export type GetVideosParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetVideosParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetVideos200 = VideoGenerationResponse +export const GetVideos200 = VideoGenerationResponse +export type GetVideos401 = UnauthorizedResponse +export const GetVideos401 = UnauthorizedResponse +export type GetVideos404 = NotFoundResponse +export const GetVideos404 = NotFoundResponse +export type GetVideos500 = InternalServerResponse +export const GetVideos500 = InternalServerResponse +export type ListVideosContentParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "index"?: number | null +} +export const ListVideosContentParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "index": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]) + ) +}) +export type ListVideosContent400 = BadRequestResponse +export const ListVideosContent400 = BadRequestResponse +export type ListVideosContent401 = UnauthorizedResponse +export const ListVideosContent401 = UnauthorizedResponse +export type ListVideosContent404 = NotFoundResponse +export const ListVideosContent404 = NotFoundResponse +export type ListVideosContent500 = InternalServerResponse +export const ListVideosContent500 = InternalServerResponse +export type ListVideosContent502 = BadGatewayResponse +export const ListVideosContent502 = BadGatewayResponse +export type ListVideosModelsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListVideosModelsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListVideosModels200 = VideoModelsListResponse +export const ListVideosModels200 = VideoModelsListResponse +export type ListVideosModels400 = BadRequestResponse +export const ListVideosModels400 = BadRequestResponse +export type ListVideosModels500 = InternalServerResponse +export const ListVideosModels500 = InternalServerResponse +export type ListWorkspacesParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListWorkspacesParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListWorkspaces200 = ListWorkspacesResponse +export const ListWorkspaces200 = ListWorkspacesResponse +export type ListWorkspaces401 = UnauthorizedResponse +export const ListWorkspaces401 = UnauthorizedResponse +export type ListWorkspaces500 = InternalServerResponse +export const ListWorkspaces500 = InternalServerResponse +export type CreateWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const CreateWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type CreateWorkspaceRequestJson = CreateWorkspaceRequest +export const CreateWorkspaceRequestJson = CreateWorkspaceRequest +export type CreateWorkspace201 = CreateWorkspaceResponse +export const CreateWorkspace201 = CreateWorkspaceResponse +export type CreateWorkspace400 = BadRequestResponse +export const CreateWorkspace400 = BadRequestResponse +export type CreateWorkspace401 = UnauthorizedResponse +export const CreateWorkspace401 = UnauthorizedResponse +export type CreateWorkspace403 = ForbiddenResponse +export const CreateWorkspace403 = ForbiddenResponse +export type CreateWorkspace500 = InternalServerResponse +export const CreateWorkspace500 = InternalServerResponse +export type GetWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const GetWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type GetWorkspace200 = GetWorkspaceResponse +export const GetWorkspace200 = GetWorkspaceResponse +export type GetWorkspace401 = UnauthorizedResponse +export const GetWorkspace401 = UnauthorizedResponse +export type GetWorkspace404 = NotFoundResponse +export const GetWorkspace404 = NotFoundResponse +export type GetWorkspace500 = InternalServerResponse +export const GetWorkspace500 = InternalServerResponse +export type DeleteWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteWorkspace200 = DeleteWorkspaceResponse +export const DeleteWorkspace200 = DeleteWorkspaceResponse +export type DeleteWorkspace400 = BadRequestResponse +export const DeleteWorkspace400 = BadRequestResponse +export type DeleteWorkspace401 = UnauthorizedResponse +export const DeleteWorkspace401 = UnauthorizedResponse +export type DeleteWorkspace403 = ForbiddenResponse +export const DeleteWorkspace403 = ForbiddenResponse +export type DeleteWorkspace404 = NotFoundResponse +export const DeleteWorkspace404 = NotFoundResponse +export type DeleteWorkspace500 = InternalServerResponse +export const DeleteWorkspace500 = InternalServerResponse +export type UpdateWorkspaceParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpdateWorkspaceParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpdateWorkspaceRequestJson = UpdateWorkspaceRequest +export const UpdateWorkspaceRequestJson = UpdateWorkspaceRequest +export type UpdateWorkspace200 = UpdateWorkspaceResponse +export const UpdateWorkspace200 = UpdateWorkspaceResponse +export type UpdateWorkspace400 = BadRequestResponse +export const UpdateWorkspace400 = BadRequestResponse +export type UpdateWorkspace401 = UnauthorizedResponse +export const UpdateWorkspace401 = UnauthorizedResponse +export type UpdateWorkspace403 = ForbiddenResponse +export const UpdateWorkspace403 = ForbiddenResponse +export type UpdateWorkspace404 = NotFoundResponse +export const UpdateWorkspace404 = NotFoundResponse +export type UpdateWorkspace500 = InternalServerResponse +export const UpdateWorkspace500 = InternalServerResponse +export type ListWorkspaceBudgetsParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const ListWorkspaceBudgetsParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type ListWorkspaceBudgets200 = ListWorkspaceBudgetsResponse +export const ListWorkspaceBudgets200 = ListWorkspaceBudgetsResponse +export type ListWorkspaceBudgets401 = UnauthorizedResponse +export const ListWorkspaceBudgets401 = UnauthorizedResponse +export type ListWorkspaceBudgets404 = NotFoundResponse +export const ListWorkspaceBudgets404 = NotFoundResponse +export type ListWorkspaceBudgets500 = InternalServerResponse +export const ListWorkspaceBudgets500 = InternalServerResponse +export type UpsertWorkspaceBudgetParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const UpsertWorkspaceBudgetParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type UpsertWorkspaceBudgetRequestJson = UpsertWorkspaceBudgetRequest +export const UpsertWorkspaceBudgetRequestJson = UpsertWorkspaceBudgetRequest +export type UpsertWorkspaceBudget200 = UpsertWorkspaceBudgetResponse +export const UpsertWorkspaceBudget200 = UpsertWorkspaceBudgetResponse +export type UpsertWorkspaceBudget400 = BadRequestResponse +export const UpsertWorkspaceBudget400 = BadRequestResponse +export type UpsertWorkspaceBudget401 = UnauthorizedResponse +export const UpsertWorkspaceBudget401 = UnauthorizedResponse +export type UpsertWorkspaceBudget404 = NotFoundResponse +export const UpsertWorkspaceBudget404 = NotFoundResponse +export type UpsertWorkspaceBudget500 = InternalServerResponse +export const UpsertWorkspaceBudget500 = InternalServerResponse +export type DeleteWorkspaceBudgetParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const DeleteWorkspaceBudgetParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type DeleteWorkspaceBudget200 = DeleteWorkspaceBudgetResponse +export const DeleteWorkspaceBudget200 = DeleteWorkspaceBudgetResponse +export type DeleteWorkspaceBudget401 = UnauthorizedResponse +export const DeleteWorkspaceBudget401 = UnauthorizedResponse +export type DeleteWorkspaceBudget404 = NotFoundResponse +export const DeleteWorkspaceBudget404 = NotFoundResponse +export type DeleteWorkspaceBudget500 = InternalServerResponse +export const DeleteWorkspaceBudget500 = InternalServerResponse +export type ListWorkspaceMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string + readonly "offset"?: number | null + readonly "limit"?: number +} +export const ListWorkspaceMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String), + "offset": Schema.optionalKey( + Schema.Union([ + Schema.Number.check(Schema.isInt().annotate({ "expected": "an integer" })).check( + Schema.isGreaterThanOrEqualTo(0).annotate({ "expected": "a value greater than or equal to 0" }) + ), + Schema.Null + ]).annotate({ "description": "Number of records to skip for pagination" }) + ), + "limit": Schema.optionalKey( + Schema.Number.annotate({ "description": "Maximum number of records to return (max 100)" }).check( + Schema.isInt().annotate({ "expected": "an integer" }) + ).check(Schema.isGreaterThanOrEqualTo(1).annotate({ "expected": "a value greater than or equal to 1" })).check( + Schema.isLessThanOrEqualTo(100).annotate({ "expected": "a value less than or equal to 100" }) + ) + ) +}) +export type ListWorkspaceMembers200 = ListWorkspaceMembersResponse +export const ListWorkspaceMembers200 = ListWorkspaceMembersResponse +export type ListWorkspaceMembers401 = UnauthorizedResponse +export const ListWorkspaceMembers401 = UnauthorizedResponse +export type ListWorkspaceMembers403 = ForbiddenResponse +export const ListWorkspaceMembers403 = ForbiddenResponse +export type ListWorkspaceMembers404 = NotFoundResponse +export const ListWorkspaceMembers404 = NotFoundResponse +export type ListWorkspaceMembers500 = InternalServerResponse +export const ListWorkspaceMembers500 = InternalServerResponse +export type BulkAddWorkspaceMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkAddWorkspaceMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkAddWorkspaceMembersRequestJson = BulkAddWorkspaceMembersRequest +export const BulkAddWorkspaceMembersRequestJson = BulkAddWorkspaceMembersRequest +export type BulkAddWorkspaceMembers200 = BulkAddWorkspaceMembersResponse +export const BulkAddWorkspaceMembers200 = BulkAddWorkspaceMembersResponse +export type BulkAddWorkspaceMembers400 = BadRequestResponse +export const BulkAddWorkspaceMembers400 = BadRequestResponse +export type BulkAddWorkspaceMembers401 = UnauthorizedResponse +export const BulkAddWorkspaceMembers401 = UnauthorizedResponse +export type BulkAddWorkspaceMembers403 = ForbiddenResponse +export const BulkAddWorkspaceMembers403 = ForbiddenResponse +export type BulkAddWorkspaceMembers404 = NotFoundResponse +export const BulkAddWorkspaceMembers404 = NotFoundResponse +export type BulkAddWorkspaceMembers500 = InternalServerResponse +export const BulkAddWorkspaceMembers500 = InternalServerResponse +export type BulkRemoveWorkspaceMembersParams = { + readonly "HTTP-Referer"?: string + readonly "X-OpenRouter-Title"?: string + readonly "X-OpenRouter-Categories"?: string +} +export const BulkRemoveWorkspaceMembersParams = Schema.Struct({ + "HTTP-Referer": Schema.optionalKey(Schema.String), + "X-OpenRouter-Title": Schema.optionalKey(Schema.String), + "X-OpenRouter-Categories": Schema.optionalKey(Schema.String) +}) +export type BulkRemoveWorkspaceMembersRequestJson = BulkRemoveWorkspaceMembersRequest +export const BulkRemoveWorkspaceMembersRequestJson = BulkRemoveWorkspaceMembersRequest +export type BulkRemoveWorkspaceMembers200 = BulkRemoveWorkspaceMembersResponse +export const BulkRemoveWorkspaceMembers200 = BulkRemoveWorkspaceMembersResponse +export type BulkRemoveWorkspaceMembers400 = BadRequestResponse +export const BulkRemoveWorkspaceMembers400 = BadRequestResponse +export type BulkRemoveWorkspaceMembers401 = UnauthorizedResponse +export const BulkRemoveWorkspaceMembers401 = UnauthorizedResponse +export type BulkRemoveWorkspaceMembers403 = ForbiddenResponse +export const BulkRemoveWorkspaceMembers403 = ForbiddenResponse +export type BulkRemoveWorkspaceMembers404 = NotFoundResponse +export const BulkRemoveWorkspaceMembers404 = NotFoundResponse +export type BulkRemoveWorkspaceMembers500 = InternalServerResponse +export const BulkRemoveWorkspaceMembers500 = InternalServerResponse + +export interface OperationConfig { + /** + * Whether or not the response should be included in the value returned from + * an operation. + * + * If set to `true`, a tuple of `[A, HttpClientResponse]` will be returned, + * where `A` is the success type of the operation. + * + * If set to `false`, only the success type of the operation will be returned. + */ + readonly includeResponse?: boolean | undefined +} + +/** + * A utility type which optionally includes the response in the return result + * of an operation based upon the value of the `includeResponse` configuration + * option. + */ +export type WithOptionalResponse = Config extends { + readonly includeResponse: true +} ? [A, HttpClientResponse.HttpClientResponse] : + A + +export const make = ( + httpClient: HttpClient.HttpClient, + options: { + readonly transformClient?: ((client: HttpClient.HttpClient) => Effect.Effect) | undefined + } = {} +): OpenRouterClient => { + const unexpectedStatus = (response: HttpClientResponse.HttpClientResponse) => + Effect.flatMap( + Effect.orElseSucceed(response.json, () => "Unexpected status code"), + (description) => + Effect.fail( + new HttpClientError.HttpClientError({ + reason: new HttpClientError.StatusCodeError({ + request: response.request, + response, + description: typeof description === "string" ? description : JSON.stringify(description) + }) + }) + ) + ) + const withResponse = (config: Config | undefined) => + ( + f: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect + ): (request: HttpClientRequest.HttpClientRequest) => Effect.Effect => { + const withOptionalResponse = ( + config?.includeResponse + ? (response: HttpClientResponse.HttpClientResponse) => Effect.map(f(response), (a) => [a, response]) + : (response: HttpClientResponse.HttpClientResponse) => f(response) + ) as any + return options?.transformClient + ? (request) => + Effect.flatMap( + Effect.flatMap(options.transformClient!(httpClient), (client) => client.execute(request)), + withOptionalResponse + ) + : (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse) + } + const sseRequest = < + Type, + DecodingServices + >( + schema: Schema.ConstraintDecoder + ) => + ( + request: HttpClientRequest.HttpClientRequest + ): Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + DecodingServices + > => + HttpClient.filterStatusOk(httpClient).execute(request).pipe( + Effect.map((response) => response.stream), + Stream.unwrap, + Stream.decodeText(), + Stream.pipeThroughChannel(Sse.decodeDataSchema(schema)) + ) + const binaryRequest = ( + request: HttpClientRequest.HttpClientRequest + ): Stream.Stream => + HttpClient.filterStatusOk(httpClient).execute(request).pipe( + Effect.map((response) => response.stream), + Stream.unwrap + ) + const decodeSuccess = + (schema: Schema) => (response: HttpClientResponse.HttpClientResponse) => + HttpClientResponse.schemaBodyJson(schema)(response) + const decodeError = + (tag: Tag, schema: Schema) => + (response: HttpClientResponse.HttpClientResponse) => + Effect.flatMap( + HttpClientResponse.schemaBodyJson(schema)(response), + (cause) => Effect.fail(OpenRouterClientError(tag, cause, response)) + ) + return { + httpClient, + "getUserActivity": (options) => + HttpClientRequest.get(`/activity`).pipe( + HttpClientRequest.setUrlParams({ + "date": options?.params?.["date"] as any, + "api_key_hash": options?.params?.["api_key_hash"] as any, + "user_id": options?.params?.["user_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetUserActivity200), + "400": decodeError("GetUserActivity400", GetUserActivity400), + "401": decodeError("GetUserActivity401", GetUserActivity401), + "403": decodeError("GetUserActivity403", GetUserActivity403), + "404": decodeError("GetUserActivity404", GetUserActivity404), + "500": decodeError("GetUserActivity500", GetUserActivity500), + orElse: unexpectedStatus + })) + ), + "getAnalyticsMeta": (options) => + HttpClientRequest.get(`/analytics/meta`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetAnalyticsMeta200), + "401": decodeError("GetAnalyticsMeta401", GetAnalyticsMeta401), + "403": decodeError("GetAnalyticsMeta403", GetAnalyticsMeta403), + "500": decodeError("GetAnalyticsMeta500", GetAnalyticsMeta500), + orElse: unexpectedStatus + })) + ), + "queryAnalytics": (options) => + HttpClientRequest.post(`/analytics/query`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(QueryAnalytics200), + "400": decodeError("QueryAnalytics400", QueryAnalytics400), + "401": decodeError("QueryAnalytics401", QueryAnalytics401), + "403": decodeError("QueryAnalytics403", QueryAnalytics403), + "408": decodeError("QueryAnalytics408", QueryAnalytics408), + "500": decodeError("QueryAnalytics500", QueryAnalytics500), + orElse: unexpectedStatus + })) + ), + "createAudioSpeech": (options) => + HttpClientRequest.post(`/audio/speech`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "400": decodeError("CreateAudioSpeech400", CreateAudioSpeech400), + "401": decodeError("CreateAudioSpeech401", CreateAudioSpeech401), + "402": decodeError("CreateAudioSpeech402", CreateAudioSpeech402), + "404": decodeError("CreateAudioSpeech404", CreateAudioSpeech404), + "429": decodeError("CreateAudioSpeech429", CreateAudioSpeech429), + "500": decodeError("CreateAudioSpeech500", CreateAudioSpeech500), + "502": decodeError("CreateAudioSpeech502", CreateAudioSpeech502), + "503": decodeError("CreateAudioSpeech503", CreateAudioSpeech503), + "524": decodeError("CreateAudioSpeech524", CreateAudioSpeech524), + "529": decodeError("CreateAudioSpeech529", CreateAudioSpeech529), + orElse: unexpectedStatus + })) + ), + "createAudioTranscriptions": (options) => + HttpClientRequest.post(`/audio/transcriptions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateAudioTranscriptions200), + "400": decodeError("CreateAudioTranscriptions400", CreateAudioTranscriptions400), + "401": decodeError("CreateAudioTranscriptions401", CreateAudioTranscriptions401), + "402": decodeError("CreateAudioTranscriptions402", CreateAudioTranscriptions402), + "404": decodeError("CreateAudioTranscriptions404", CreateAudioTranscriptions404), + "429": decodeError("CreateAudioTranscriptions429", CreateAudioTranscriptions429), + "500": decodeError("CreateAudioTranscriptions500", CreateAudioTranscriptions500), + "502": decodeError("CreateAudioTranscriptions502", CreateAudioTranscriptions502), + "503": decodeError("CreateAudioTranscriptions503", CreateAudioTranscriptions503), + "524": decodeError("CreateAudioTranscriptions524", CreateAudioTranscriptions524), + "529": decodeError("CreateAudioTranscriptions529", CreateAudioTranscriptions529), + orElse: unexpectedStatus + })) + ), + "exchangeAuthCodeForAPIKey": (options) => + HttpClientRequest.post(`/auth/keys`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ExchangeAuthCodeForAPIKey200), + "400": decodeError("ExchangeAuthCodeForAPIKey400", ExchangeAuthCodeForAPIKey400), + "403": decodeError("ExchangeAuthCodeForAPIKey403", ExchangeAuthCodeForAPIKey403), + "500": decodeError("ExchangeAuthCodeForAPIKey500", ExchangeAuthCodeForAPIKey500), + orElse: unexpectedStatus + })) + ), + "createAuthKeysCode": (options) => + HttpClientRequest.post(`/auth/keys/code`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateAuthKeysCode200), + "400": decodeError("CreateAuthKeysCode400", CreateAuthKeysCode400), + "401": decodeError("CreateAuthKeysCode401", CreateAuthKeysCode401), + "403": decodeError("CreateAuthKeysCode403", CreateAuthKeysCode403), + "409": decodeError("CreateAuthKeysCode409", CreateAuthKeysCode409), + "500": decodeError("CreateAuthKeysCode500", CreateAuthKeysCode500), + orElse: unexpectedStatus + })) + ), + "getBenchmarks": (options) => + HttpClientRequest.get(`/benchmarks`).pipe( + HttpClientRequest.setUrlParams({ + "source": options?.params?.["source"] as any, + "task_type": options?.params?.["task_type"] as any, + "arena": options?.params?.["arena"] as any, + "category": options?.params?.["category"] as any, + "max_results": options?.params?.["max_results"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetBenchmarks200), + "400": decodeError("GetBenchmarks400", GetBenchmarks400), + "401": decodeError("GetBenchmarks401", GetBenchmarks401), + "429": decodeError("GetBenchmarks429", GetBenchmarks429), + "500": decodeError("GetBenchmarks500", GetBenchmarks500), + orElse: unexpectedStatus + })) + ), + "listBYOKKeys": (options) => + HttpClientRequest.get(`/byok`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "workspace_id": options?.params?.["workspace_id"] as any, + "provider": options?.params?.["provider"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListBYOKKeys200), + "400": decodeError("ListBYOKKeys400", ListBYOKKeys400), + "401": decodeError("ListBYOKKeys401", ListBYOKKeys401), + "500": decodeError("ListBYOKKeys500", ListBYOKKeys500), + orElse: unexpectedStatus + })) + ), + "createBYOKKey": (options) => + HttpClientRequest.post(`/byok`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateBYOKKey201), + "400": decodeError("CreateBYOKKey400", CreateBYOKKey400), + "401": decodeError("CreateBYOKKey401", CreateBYOKKey401), + "403": decodeError("CreateBYOKKey403", CreateBYOKKey403), + "500": decodeError("CreateBYOKKey500", CreateBYOKKey500), + orElse: unexpectedStatus + })) + ), + "getBYOKKey": (id, options) => + HttpClientRequest.get(`/byok/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetBYOKKey200), + "401": decodeError("GetBYOKKey401", GetBYOKKey401), + "404": decodeError("GetBYOKKey404", GetBYOKKey404), + "500": decodeError("GetBYOKKey500", GetBYOKKey500), + orElse: unexpectedStatus + })) + ), + "deleteBYOKKey": (id, options) => + HttpClientRequest.delete(`/byok/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteBYOKKey200), + "401": decodeError("DeleteBYOKKey401", DeleteBYOKKey401), + "404": decodeError("DeleteBYOKKey404", DeleteBYOKKey404), + "500": decodeError("DeleteBYOKKey500", DeleteBYOKKey500), + orElse: unexpectedStatus + })) + ), + "updateBYOKKey": (id, options) => + HttpClientRequest.patch(`/byok/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateBYOKKey200), + "400": decodeError("UpdateBYOKKey400", UpdateBYOKKey400), + "401": decodeError("UpdateBYOKKey401", UpdateBYOKKey401), + "404": decodeError("UpdateBYOKKey404", UpdateBYOKKey404), + "500": decodeError("UpdateBYOKKey500", UpdateBYOKKey500), + orElse: unexpectedStatus + })) + ), + "sendChatCompletionRequest": (options) => + HttpClientRequest.post(`/chat/completions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(SendChatCompletionRequest200), + "400": decodeError("SendChatCompletionRequest400", SendChatCompletionRequest400), + "401": decodeError("SendChatCompletionRequest401", SendChatCompletionRequest401), + "402": decodeError("SendChatCompletionRequest402", SendChatCompletionRequest402), + "403": decodeError("SendChatCompletionRequest403", SendChatCompletionRequest403), + "404": decodeError("SendChatCompletionRequest404", SendChatCompletionRequest404), + "408": decodeError("SendChatCompletionRequest408", SendChatCompletionRequest408), + "413": decodeError("SendChatCompletionRequest413", SendChatCompletionRequest413), + "422": decodeError("SendChatCompletionRequest422", SendChatCompletionRequest422), + "429": decodeError("SendChatCompletionRequest429", SendChatCompletionRequest429), + "500": decodeError("SendChatCompletionRequest500", SendChatCompletionRequest500), + "502": decodeError("SendChatCompletionRequest502", SendChatCompletionRequest502), + "503": decodeError("SendChatCompletionRequest503", SendChatCompletionRequest503), + "524": decodeError("SendChatCompletionRequest524", SendChatCompletionRequest524), + "529": decodeError("SendChatCompletionRequest529", SendChatCompletionRequest529), + orElse: unexpectedStatus + })) + ), + "sendChatCompletionRequestSse": (options) => + HttpClientRequest.post(`/chat/completions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(SendChatCompletionRequest200Sse) + ), + "getTaskClassifications": (options) => + HttpClientRequest.get(`/classifications/task`).pipe( + HttpClientRequest.setUrlParams({ "window": options?.params?.["window"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetTaskClassifications200), + "400": decodeError("GetTaskClassifications400", GetTaskClassifications400), + "401": decodeError("GetTaskClassifications401", GetTaskClassifications401), + "429": decodeError("GetTaskClassifications429", GetTaskClassifications429), + "500": decodeError("GetTaskClassifications500", GetTaskClassifications500), + orElse: unexpectedStatus + })) + ), + "getCredits": (options) => + HttpClientRequest.get(`/credits`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetCredits200), + "401": decodeError("GetCredits401", GetCredits401), + "403": decodeError("GetCredits403", GetCredits403), + "500": decodeError("GetCredits500", GetCredits500), + orElse: unexpectedStatus + })) + ), + "createCoinbaseCharge": (options) => + HttpClientRequest.post(`/credits/coinbase`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "410": decodeError("CreateCoinbaseCharge410", CreateCoinbaseCharge410), + "200": () => Effect.void, + orElse: unexpectedStatus + })) + ), + "getAppRankings": (options) => + HttpClientRequest.get(`/datasets/app-rankings`).pipe( + HttpClientRequest.setUrlParams({ + "category": options?.params?.["category"] as any, + "subcategory": options?.params?.["subcategory"] as any, + "sort": options?.params?.["sort"] as any, + "start_date": options?.params?.["start_date"] as any, + "end_date": options?.params?.["end_date"] as any, + "limit": options?.params?.["limit"] as any, + "offset": options?.params?.["offset"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetAppRankings200), + "400": decodeError("GetAppRankings400", GetAppRankings400), + "401": decodeError("GetAppRankings401", GetAppRankings401), + "429": decodeError("GetAppRankings429", GetAppRankings429), + "500": decodeError("GetAppRankings500", GetAppRankings500), + orElse: unexpectedStatus + })) + ), + "getRankingsDaily": (options) => + HttpClientRequest.get(`/datasets/rankings-daily`).pipe( + HttpClientRequest.setUrlParams({ + "start_date": options?.params?.["start_date"] as any, + "end_date": options?.params?.["end_date"] as any, + "period": options?.params?.["period"] as any, + "modality": options?.params?.["modality"] as any, + "context_bucket": options?.params?.["context_bucket"] as any, + "category": options?.params?.["category"] as any, + "language_type": options?.params?.["language_type"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetRankingsDaily200), + "400": decodeError("GetRankingsDaily400", GetRankingsDaily400), + "401": decodeError("GetRankingsDaily401", GetRankingsDaily401), + "429": decodeError("GetRankingsDaily429", GetRankingsDaily429), + "500": decodeError("GetRankingsDaily500", GetRankingsDaily500), + orElse: unexpectedStatus + })) + ), + "createEmbeddings": (options) => + HttpClientRequest.post(`/embeddings`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateEmbeddings200), + "400": decodeError("CreateEmbeddings400", CreateEmbeddings400), + "401": decodeError("CreateEmbeddings401", CreateEmbeddings401), + "402": decodeError("CreateEmbeddings402", CreateEmbeddings402), + "404": decodeError("CreateEmbeddings404", CreateEmbeddings404), + "429": decodeError("CreateEmbeddings429", CreateEmbeddings429), + "500": decodeError("CreateEmbeddings500", CreateEmbeddings500), + "502": decodeError("CreateEmbeddings502", CreateEmbeddings502), + "503": decodeError("CreateEmbeddings503", CreateEmbeddings503), + "524": decodeError("CreateEmbeddings524", CreateEmbeddings524), + "529": decodeError("CreateEmbeddings529", CreateEmbeddings529), + orElse: unexpectedStatus + })) + ), + "createEmbeddingsSse": (options) => + HttpClientRequest.post(`/embeddings`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateEmbeddings200Sse) + ), + "listEmbeddingsModels": (options) => + HttpClientRequest.get(`/embeddings/models`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEmbeddingsModels200), + "400": decodeError("ListEmbeddingsModels400", ListEmbeddingsModels400), + "500": decodeError("ListEmbeddingsModels500", ListEmbeddingsModels500), + orElse: unexpectedStatus + })) + ), + "listEndpointsZdr": (options) => + HttpClientRequest.get(`/endpoints/zdr`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEndpointsZdr200), + "403": decodeError("ListEndpointsZdr403", ListEndpointsZdr403), + "500": decodeError("ListEndpointsZdr500", ListEndpointsZdr500), + orElse: unexpectedStatus + })) + ), + "listFiles": (options) => + HttpClientRequest.get(`/files`).pipe( + HttpClientRequest.setUrlParams({ + "limit": options?.params?.["limit"] as any, + "cursor": options?.params?.["cursor"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListFiles200), + "400": decodeError("ListFiles400", ListFiles400), + "401": decodeError("ListFiles401", ListFiles401), + "429": decodeError("ListFiles429", ListFiles429), + "500": decodeError("ListFiles500", ListFiles500), + orElse: unexpectedStatus + })) + ), + "uploadFile": (options) => + HttpClientRequest.post(`/files`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyFormData(options.payload as any), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UploadFile200), + "400": decodeError("UploadFile400", UploadFile400), + "401": decodeError("UploadFile401", UploadFile401), + "403": decodeError("UploadFile403", UploadFile403), + "413": decodeError("UploadFile413", UploadFile413), + "429": decodeError("UploadFile429", UploadFile429), + "500": decodeError("UploadFile500", UploadFile500), + orElse: unexpectedStatus + })) + ), + "getFileMetadata": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetFileMetadata200), + "401": decodeError("GetFileMetadata401", GetFileMetadata401), + "404": decodeError("GetFileMetadata404", GetFileMetadata404), + "429": decodeError("GetFileMetadata429", GetFileMetadata429), + "500": decodeError("GetFileMetadata500", GetFileMetadata500), + orElse: unexpectedStatus + })) + ), + "deleteFile": (fileId, options) => + HttpClientRequest.delete(`/files/${fileId}`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteFile200), + "401": decodeError("DeleteFile401", DeleteFile401), + "404": decodeError("DeleteFile404", DeleteFile404), + "429": decodeError("DeleteFile429", DeleteFile429), + "500": decodeError("DeleteFile500", DeleteFile500), + orElse: unexpectedStatus + })) + ), + "downloadFileContent": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}/content`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "400": decodeError("DownloadFileContent400", DownloadFileContent400), + "401": decodeError("DownloadFileContent401", DownloadFileContent401), + "404": decodeError("DownloadFileContent404", DownloadFileContent404), + "429": decodeError("DownloadFileContent429", DownloadFileContent429), + "500": decodeError("DownloadFileContent500", DownloadFileContent500), + orElse: unexpectedStatus + })) + ), + "downloadFileContentStream": (fileId, options) => + HttpClientRequest.get(`/files/${fileId}/content`).pipe( + HttpClientRequest.setUrlParams({ "workspace_id": options?.params?.["workspace_id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + binaryRequest + ), + "getGeneration": (options) => + HttpClientRequest.get(`/generation`).pipe( + HttpClientRequest.setUrlParams({ "id": options.params["id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetGeneration200), + "401": decodeError("GetGeneration401", GetGeneration401), + "402": decodeError("GetGeneration402", GetGeneration402), + "404": decodeError("GetGeneration404", GetGeneration404), + "429": decodeError("GetGeneration429", GetGeneration429), + "500": decodeError("GetGeneration500", GetGeneration500), + "502": decodeError("GetGeneration502", GetGeneration502), + "524": decodeError("GetGeneration524", GetGeneration524), + "529": decodeError("GetGeneration529", GetGeneration529), + orElse: unexpectedStatus + })) + ), + "listGenerationContent": (options) => + HttpClientRequest.get(`/generation/content`).pipe( + HttpClientRequest.setUrlParams({ "id": options.params["id"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGenerationContent200), + "401": decodeError("ListGenerationContent401", ListGenerationContent401), + "403": decodeError("ListGenerationContent403", ListGenerationContent403), + "404": decodeError("ListGenerationContent404", ListGenerationContent404), + "429": decodeError("ListGenerationContent429", ListGenerationContent429), + "500": decodeError("ListGenerationContent500", ListGenerationContent500), + "502": decodeError("ListGenerationContent502", ListGenerationContent502), + "524": decodeError("ListGenerationContent524", ListGenerationContent524), + "529": decodeError("ListGenerationContent529", ListGenerationContent529), + orElse: unexpectedStatus + })) + ), + "submitGenerationFeedback": (options) => + HttpClientRequest.post(`/generation/feedback`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(SubmitGenerationFeedback200), + "400": decodeError("SubmitGenerationFeedback400", SubmitGenerationFeedback400), + "401": decodeError("SubmitGenerationFeedback401", SubmitGenerationFeedback401), + "404": decodeError("SubmitGenerationFeedback404", SubmitGenerationFeedback404), + "429": decodeError("SubmitGenerationFeedback429", SubmitGenerationFeedback429), + "500": decodeError("SubmitGenerationFeedback500", SubmitGenerationFeedback500), + orElse: unexpectedStatus + })) + ), + "listGuardrails": (options) => + HttpClientRequest.get(`/guardrails`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGuardrails200), + "400": decodeError("ListGuardrails400", ListGuardrails400), + "401": decodeError("ListGuardrails401", ListGuardrails401), + "500": decodeError("ListGuardrails500", ListGuardrails500), + orElse: unexpectedStatus + })) + ), + "createGuardrail": (options) => + HttpClientRequest.post(`/guardrails`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateGuardrail201), + "400": decodeError("CreateGuardrail400", CreateGuardrail400), + "401": decodeError("CreateGuardrail401", CreateGuardrail401), + "403": decodeError("CreateGuardrail403", CreateGuardrail403), + "500": decodeError("CreateGuardrail500", CreateGuardrail500), + orElse: unexpectedStatus + })) + ), + "getGuardrail": (id, options) => + HttpClientRequest.get(`/guardrails/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetGuardrail200), + "401": decodeError("GetGuardrail401", GetGuardrail401), + "404": decodeError("GetGuardrail404", GetGuardrail404), + "500": decodeError("GetGuardrail500", GetGuardrail500), + orElse: unexpectedStatus + })) + ), + "deleteGuardrail": (id, options) => + HttpClientRequest.delete(`/guardrails/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteGuardrail200), + "401": decodeError("DeleteGuardrail401", DeleteGuardrail401), + "404": decodeError("DeleteGuardrail404", DeleteGuardrail404), + "500": decodeError("DeleteGuardrail500", DeleteGuardrail500), + orElse: unexpectedStatus + })) + ), + "updateGuardrail": (id, options) => + HttpClientRequest.patch(`/guardrails/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateGuardrail200), + "400": decodeError("UpdateGuardrail400", UpdateGuardrail400), + "401": decodeError("UpdateGuardrail401", UpdateGuardrail401), + "404": decodeError("UpdateGuardrail404", UpdateGuardrail404), + "500": decodeError("UpdateGuardrail500", UpdateGuardrail500), + orElse: unexpectedStatus + })) + ), + "listGuardrailKeyAssignments": (id, options) => + HttpClientRequest.get(`/guardrails/${id}/assignments/keys`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGuardrailKeyAssignments200), + "401": decodeError("ListGuardrailKeyAssignments401", ListGuardrailKeyAssignments401), + "404": decodeError("ListGuardrailKeyAssignments404", ListGuardrailKeyAssignments404), + "500": decodeError("ListGuardrailKeyAssignments500", ListGuardrailKeyAssignments500), + orElse: unexpectedStatus + })) + ), + "bulkAssignKeysToGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/keys`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkAssignKeysToGuardrail200), + "400": decodeError("BulkAssignKeysToGuardrail400", BulkAssignKeysToGuardrail400), + "401": decodeError("BulkAssignKeysToGuardrail401", BulkAssignKeysToGuardrail401), + "404": decodeError("BulkAssignKeysToGuardrail404", BulkAssignKeysToGuardrail404), + "500": decodeError("BulkAssignKeysToGuardrail500", BulkAssignKeysToGuardrail500), + orElse: unexpectedStatus + })) + ), + "bulkUnassignKeysFromGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/keys/remove`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkUnassignKeysFromGuardrail200), + "400": decodeError("BulkUnassignKeysFromGuardrail400", BulkUnassignKeysFromGuardrail400), + "401": decodeError("BulkUnassignKeysFromGuardrail401", BulkUnassignKeysFromGuardrail401), + "404": decodeError("BulkUnassignKeysFromGuardrail404", BulkUnassignKeysFromGuardrail404), + "500": decodeError("BulkUnassignKeysFromGuardrail500", BulkUnassignKeysFromGuardrail500), + orElse: unexpectedStatus + })) + ), + "listGuardrailMemberAssignments": (id, options) => + HttpClientRequest.get(`/guardrails/${id}/assignments/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListGuardrailMemberAssignments200), + "401": decodeError("ListGuardrailMemberAssignments401", ListGuardrailMemberAssignments401), + "404": decodeError("ListGuardrailMemberAssignments404", ListGuardrailMemberAssignments404), + "500": decodeError("ListGuardrailMemberAssignments500", ListGuardrailMemberAssignments500), + orElse: unexpectedStatus + })) + ), + "bulkAssignMembersToGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/members`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkAssignMembersToGuardrail200), + "400": decodeError("BulkAssignMembersToGuardrail400", BulkAssignMembersToGuardrail400), + "401": decodeError("BulkAssignMembersToGuardrail401", BulkAssignMembersToGuardrail401), + "404": decodeError("BulkAssignMembersToGuardrail404", BulkAssignMembersToGuardrail404), + "500": decodeError("BulkAssignMembersToGuardrail500", BulkAssignMembersToGuardrail500), + orElse: unexpectedStatus + })) + ), + "bulkUnassignMembersFromGuardrail": (id, options) => + HttpClientRequest.post(`/guardrails/${id}/assignments/members/remove`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkUnassignMembersFromGuardrail200), + "400": decodeError("BulkUnassignMembersFromGuardrail400", BulkUnassignMembersFromGuardrail400), + "401": decodeError("BulkUnassignMembersFromGuardrail401", BulkUnassignMembersFromGuardrail401), + "404": decodeError("BulkUnassignMembersFromGuardrail404", BulkUnassignMembersFromGuardrail404), + "500": decodeError("BulkUnassignMembersFromGuardrail500", BulkUnassignMembersFromGuardrail500), + orElse: unexpectedStatus + })) + ), + "listKeyAssignments": (options) => + HttpClientRequest.get(`/guardrails/assignments/keys`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListKeyAssignments200), + "401": decodeError("ListKeyAssignments401", ListKeyAssignments401), + "500": decodeError("ListKeyAssignments500", ListKeyAssignments500), + orElse: unexpectedStatus + })) + ), + "listMemberAssignments": (options) => + HttpClientRequest.get(`/guardrails/assignments/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListMemberAssignments200), + "401": decodeError("ListMemberAssignments401", ListMemberAssignments401), + "500": decodeError("ListMemberAssignments500", ListMemberAssignments500), + orElse: unexpectedStatus + })) + ), + "createImages": (options) => + HttpClientRequest.post(`/images`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateImages200), + "400": decodeError("CreateImages400", CreateImages400), + "401": decodeError("CreateImages401", CreateImages401), + "402": decodeError("CreateImages402", CreateImages402), + "403": decodeError("CreateImages403", CreateImages403), + "404": decodeError("CreateImages404", CreateImages404), + "413": decodeError("CreateImages413", CreateImages413), + "429": decodeError("CreateImages429", CreateImages429), + "500": decodeError("CreateImages500", CreateImages500), + "502": decodeError("CreateImages502", CreateImages502), + "524": decodeError("CreateImages524", CreateImages524), + "529": decodeError("CreateImages529", CreateImages529), + orElse: unexpectedStatus + })) + ), + "createImagesSse": (options) => + HttpClientRequest.post(`/images`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateImages200Sse) + ), + "listImageModels": (options) => + HttpClientRequest.get(`/images/models`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListImageModels200), + "500": decodeError("ListImageModels500", ListImageModels500), + orElse: unexpectedStatus + })) + ), + "listImageModelEndpoints": (author, slug, options) => + HttpClientRequest.get(`/images/models/${author}/${slug}/endpoints`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListImageModelEndpoints200), + "404": decodeError("ListImageModelEndpoints404", ListImageModelEndpoints404), + "500": decodeError("ListImageModelEndpoints500", ListImageModelEndpoints500), + orElse: unexpectedStatus + })) + ), + "getCurrentKey": (options) => + HttpClientRequest.get(`/key`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetCurrentKey200), + "401": decodeError("GetCurrentKey401", GetCurrentKey401), + "500": decodeError("GetCurrentKey500", GetCurrentKey500), + orElse: unexpectedStatus + })) + ), + "list": (options) => + HttpClientRequest.get(`/keys`).pipe( + HttpClientRequest.setUrlParams({ + "include_disabled": options?.params?.["include_disabled"] as any, + "offset": options?.params?.["offset"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(List200), + "400": decodeError("List400", List400), + "401": decodeError("List401", List401), + "429": decodeError("List429", List429), + "500": decodeError("List500", List500), + orElse: unexpectedStatus + })) + ), + "createKeys": (options) => + HttpClientRequest.post(`/keys`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateKeys201), + "400": decodeError("CreateKeys400", CreateKeys400), + "401": decodeError("CreateKeys401", CreateKeys401), + "403": decodeError("CreateKeys403", CreateKeys403), + "429": decodeError("CreateKeys429", CreateKeys429), + "500": decodeError("CreateKeys500", CreateKeys500), + orElse: unexpectedStatus + })) + ), + "getKey": (hash, options) => + HttpClientRequest.get(`/keys/${hash}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetKey200), + "401": decodeError("GetKey401", GetKey401), + "404": decodeError("GetKey404", GetKey404), + "429": decodeError("GetKey429", GetKey429), + "500": decodeError("GetKey500", GetKey500), + orElse: unexpectedStatus + })) + ), + "deleteKeys": (hash, options) => + HttpClientRequest.delete(`/keys/${hash}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteKeys200), + "401": decodeError("DeleteKeys401", DeleteKeys401), + "404": decodeError("DeleteKeys404", DeleteKeys404), + "429": decodeError("DeleteKeys429", DeleteKeys429), + "500": decodeError("DeleteKeys500", DeleteKeys500), + orElse: unexpectedStatus + })) + ), + "updateKeys": (hash, options) => + HttpClientRequest.patch(`/keys/${hash}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateKeys200), + "400": decodeError("UpdateKeys400", UpdateKeys400), + "401": decodeError("UpdateKeys401", UpdateKeys401), + "404": decodeError("UpdateKeys404", UpdateKeys404), + "429": decodeError("UpdateKeys429", UpdateKeys429), + "500": decodeError("UpdateKeys500", UpdateKeys500), + orElse: unexpectedStatus + })) + ), + "createMessages": (options) => + HttpClientRequest.post(`/messages`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateMessages200), + "400": decodeError("CreateMessages400", CreateMessages400), + "401": decodeError("CreateMessages401", CreateMessages401), + "403": decodeError("CreateMessages403", CreateMessages403), + "404": decodeError("CreateMessages404", CreateMessages404), + "429": decodeError("CreateMessages429", CreateMessages429), + "500": decodeError("CreateMessages500", CreateMessages500), + "503": decodeError("CreateMessages503", CreateMessages503), + "529": decodeError("CreateMessages529", CreateMessages529), + orElse: unexpectedStatus + })) + ), + "createMessagesSse": (options) => + HttpClientRequest.post(`/messages`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateMessages200Sse) + ), + "getModel": (author, slug, options) => + HttpClientRequest.get(`/model/${author}/${slug}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetModel200), + "403": decodeError("GetModel403", GetModel403), + "404": decodeError("GetModel404", GetModel404), + "500": decodeError("GetModel500", GetModel500), + orElse: unexpectedStatus + })) + ), + "getModels": (options) => + HttpClientRequest.get(`/models`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "category": options?.params?.["category"] as any, + "supported_parameters": options?.params?.["supported_parameters"] as any, + "output_modalities": options?.params?.["output_modalities"] as any, + "sort": options?.params?.["sort"] as any, + "q": options?.params?.["q"] as any, + "input_modalities": options?.params?.["input_modalities"] as any, + "context": options?.params?.["context"] as any, + "min_price": options?.params?.["min_price"] as any, + "max_price": options?.params?.["max_price"] as any, + "arch": options?.params?.["arch"] as any, + "model_authors": options?.params?.["model_authors"] as any, + "providers": options?.params?.["providers"] as any, + "distillable": options?.params?.["distillable"] as any, + "zdr": options?.params?.["zdr"] as any, + "region": options?.params?.["region"] as any, + "min_output_price": options?.params?.["min_output_price"] as any, + "max_output_price": options?.params?.["max_output_price"] as any, + "min_age_days": options?.params?.["min_age_days"] as any, + "max_age_days": options?.params?.["max_age_days"] as any, + "min_intelligence_index": options?.params?.["min_intelligence_index"] as any, + "max_intelligence_index": options?.params?.["max_intelligence_index"] as any, + "min_coding_index": options?.params?.["min_coding_index"] as any, + "max_coding_index": options?.params?.["max_coding_index"] as any, + "min_agentic_index": options?.params?.["min_agentic_index"] as any, + "max_agentic_index": options?.params?.["max_agentic_index"] as any, + "min_tool_success_rate": options?.params?.["min_tool_success_rate"] as any, + "max_tool_success_rate": options?.params?.["max_tool_success_rate"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetModels200), + "400": decodeError("GetModels400", GetModels400), + "403": decodeError("GetModels403", GetModels403), + "500": decodeError("GetModels500", GetModels500), + orElse: unexpectedStatus + })) + ), + "listEndpoints": (author, slug, options) => + HttpClientRequest.get(`/models/${author}/${slug}/endpoints`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListEndpoints200), + "403": decodeError("ListEndpoints403", ListEndpoints403), + "404": decodeError("ListEndpoints404", ListEndpoints404), + "500": decodeError("ListEndpoints500", ListEndpoints500), + orElse: unexpectedStatus + })) + ), + "listModelsCount": (options) => + HttpClientRequest.get(`/models/count`).pipe( + HttpClientRequest.setUrlParams({ "output_modalities": options?.params?.["output_modalities"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListModelsCount200), + "400": decodeError("ListModelsCount400", ListModelsCount400), + "403": decodeError("ListModelsCount403", ListModelsCount403), + "500": decodeError("ListModelsCount500", ListModelsCount500), + orElse: unexpectedStatus + })) + ), + "listModelsUser": (options) => + HttpClientRequest.get(`/models/user`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListModelsUser200), + "401": decodeError("ListModelsUser401", ListModelsUser401), + "403": decodeError("ListModelsUser403", ListModelsUser403), + "404": decodeError("ListModelsUser404", ListModelsUser404), + "500": decodeError("ListModelsUser500", ListModelsUser500), + orElse: unexpectedStatus + })) + ), + "listObservabilityDestinations": (options) => + HttpClientRequest.get(`/observability/destinations`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any, + "workspace_id": options?.params?.["workspace_id"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListObservabilityDestinations200), + "400": decodeError("ListObservabilityDestinations400", ListObservabilityDestinations400), + "401": decodeError("ListObservabilityDestinations401", ListObservabilityDestinations401), + "500": decodeError("ListObservabilityDestinations500", ListObservabilityDestinations500), + orElse: unexpectedStatus + })) + ), + "createObservabilityDestination": (options) => + HttpClientRequest.post(`/observability/destinations`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateObservabilityDestination201), + "400": decodeError("CreateObservabilityDestination400", CreateObservabilityDestination400), + "401": decodeError("CreateObservabilityDestination401", CreateObservabilityDestination401), + "403": decodeError("CreateObservabilityDestination403", CreateObservabilityDestination403), + "409": decodeError("CreateObservabilityDestination409", CreateObservabilityDestination409), + "500": decodeError("CreateObservabilityDestination500", CreateObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "getObservabilityDestination": (id, options) => + HttpClientRequest.get(`/observability/destinations/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetObservabilityDestination200), + "401": decodeError("GetObservabilityDestination401", GetObservabilityDestination401), + "404": decodeError("GetObservabilityDestination404", GetObservabilityDestination404), + "500": decodeError("GetObservabilityDestination500", GetObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "deleteObservabilityDestination": (id, options) => + HttpClientRequest.delete(`/observability/destinations/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteObservabilityDestination200), + "401": decodeError("DeleteObservabilityDestination401", DeleteObservabilityDestination401), + "404": decodeError("DeleteObservabilityDestination404", DeleteObservabilityDestination404), + "500": decodeError("DeleteObservabilityDestination500", DeleteObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "updateObservabilityDestination": (id, options) => + HttpClientRequest.patch(`/observability/destinations/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateObservabilityDestination200), + "400": decodeError("UpdateObservabilityDestination400", UpdateObservabilityDestination400), + "401": decodeError("UpdateObservabilityDestination401", UpdateObservabilityDestination401), + "404": decodeError("UpdateObservabilityDestination404", UpdateObservabilityDestination404), + "409": decodeError("UpdateObservabilityDestination409", UpdateObservabilityDestination409), + "500": decodeError("UpdateObservabilityDestination500", UpdateObservabilityDestination500), + orElse: unexpectedStatus + })) + ), + "listOrganizationMembers": (options) => + HttpClientRequest.get(`/organization/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListOrganizationMembers200), + "401": decodeError("ListOrganizationMembers401", ListOrganizationMembers401), + "404": decodeError("ListOrganizationMembers404", ListOrganizationMembers404), + "500": decodeError("ListOrganizationMembers500", ListOrganizationMembers500), + orElse: unexpectedStatus + })) + ), + "listPresets": (options) => + HttpClientRequest.get(`/presets`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListPresets200), + "400": decodeError("ListPresets400", ListPresets400), + "401": decodeError("ListPresets401", ListPresets401), + "500": decodeError("ListPresets500", ListPresets500), + orElse: unexpectedStatus + })) + ), + "getPreset": (slug, options) => + HttpClientRequest.get(`/presets/${slug}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetPreset200), + "400": decodeError("GetPreset400", GetPreset400), + "401": decodeError("GetPreset401", GetPreset401), + "404": decodeError("GetPreset404", GetPreset404), + "500": decodeError("GetPreset500", GetPreset500), + orElse: unexpectedStatus + })) + ), + "createPresetsChatCompletions": (slug, options) => + HttpClientRequest.post(`/presets/${slug}/chat/completions`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreatePresetsChatCompletions200), + "400": decodeError("CreatePresetsChatCompletions400", CreatePresetsChatCompletions400), + "401": decodeError("CreatePresetsChatCompletions401", CreatePresetsChatCompletions401), + "403": decodeError("CreatePresetsChatCompletions403", CreatePresetsChatCompletions403), + "404": decodeError("CreatePresetsChatCompletions404", CreatePresetsChatCompletions404), + "409": decodeError("CreatePresetsChatCompletions409", CreatePresetsChatCompletions409), + "500": decodeError("CreatePresetsChatCompletions500", CreatePresetsChatCompletions500), + orElse: unexpectedStatus + })) + ), + "createPresetsMessages": (slug, options) => + HttpClientRequest.post(`/presets/${slug}/messages`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreatePresetsMessages200), + "400": decodeError("CreatePresetsMessages400", CreatePresetsMessages400), + "401": decodeError("CreatePresetsMessages401", CreatePresetsMessages401), + "403": decodeError("CreatePresetsMessages403", CreatePresetsMessages403), + "404": decodeError("CreatePresetsMessages404", CreatePresetsMessages404), + "409": decodeError("CreatePresetsMessages409", CreatePresetsMessages409), + "500": decodeError("CreatePresetsMessages500", CreatePresetsMessages500), + orElse: unexpectedStatus + })) + ), + "createPresetsResponses": (slug, options) => + HttpClientRequest.post(`/presets/${slug}/responses`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreatePresetsResponses200), + "400": decodeError("CreatePresetsResponses400", CreatePresetsResponses400), + "401": decodeError("CreatePresetsResponses401", CreatePresetsResponses401), + "403": decodeError("CreatePresetsResponses403", CreatePresetsResponses403), + "404": decodeError("CreatePresetsResponses404", CreatePresetsResponses404), + "409": decodeError("CreatePresetsResponses409", CreatePresetsResponses409), + "500": decodeError("CreatePresetsResponses500", CreatePresetsResponses500), + orElse: unexpectedStatus + })) + ), + "listPresetVersions": (slug, options) => + HttpClientRequest.get(`/presets/${slug}/versions`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListPresetVersions200), + "400": decodeError("ListPresetVersions400", ListPresetVersions400), + "401": decodeError("ListPresetVersions401", ListPresetVersions401), + "404": decodeError("ListPresetVersions404", ListPresetVersions404), + "500": decodeError("ListPresetVersions500", ListPresetVersions500), + orElse: unexpectedStatus + })) + ), + "getPresetVersion": (slug, version, options) => + HttpClientRequest.get(`/presets/${slug}/versions/${version}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetPresetVersion200), + "400": decodeError("GetPresetVersion400", GetPresetVersion400), + "401": decodeError("GetPresetVersion401", GetPresetVersion401), + "404": decodeError("GetPresetVersion404", GetPresetVersion404), + "500": decodeError("GetPresetVersion500", GetPresetVersion500), + orElse: unexpectedStatus + })) + ), + "listProviders": (options) => + HttpClientRequest.get(`/providers`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListProviders200), + "500": decodeError("ListProviders500", ListProviders500), + orElse: unexpectedStatus + })) + ), + "createRerank": (options) => + HttpClientRequest.post(`/rerank`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateRerank200), + "400": decodeError("CreateRerank400", CreateRerank400), + "401": decodeError("CreateRerank401", CreateRerank401), + "402": decodeError("CreateRerank402", CreateRerank402), + "404": decodeError("CreateRerank404", CreateRerank404), + "429": decodeError("CreateRerank429", CreateRerank429), + "500": decodeError("CreateRerank500", CreateRerank500), + "502": decodeError("CreateRerank502", CreateRerank502), + "503": decodeError("CreateRerank503", CreateRerank503), + "524": decodeError("CreateRerank524", CreateRerank524), + "529": decodeError("CreateRerank529", CreateRerank529), + orElse: unexpectedStatus + })) + ), + "createRerankSse": (options) => + HttpClientRequest.post(`/rerank`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateRerank200Sse) + ), + "createResponses": (options) => + HttpClientRequest.post(`/responses`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateResponses200), + "400": decodeError("CreateResponses400", CreateResponses400), + "401": decodeError("CreateResponses401", CreateResponses401), + "402": decodeError("CreateResponses402", CreateResponses402), + "403": decodeError("CreateResponses403", CreateResponses403), + "404": decodeError("CreateResponses404", CreateResponses404), + "408": decodeError("CreateResponses408", CreateResponses408), + "413": decodeError("CreateResponses413", CreateResponses413), + "422": decodeError("CreateResponses422", CreateResponses422), + "429": decodeError("CreateResponses429", CreateResponses429), + "500": decodeError("CreateResponses500", CreateResponses500), + "502": decodeError("CreateResponses502", CreateResponses502), + "503": decodeError("CreateResponses503", CreateResponses503), + "524": decodeError("CreateResponses524", CreateResponses524), + "529": decodeError("CreateResponses529", CreateResponses529), + orElse: unexpectedStatus + })) + ), + "createResponsesSse": (options) => + HttpClientRequest.post(`/responses`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined, + "X-OpenRouter-Metadata": options.params?.["X-OpenRouter-Metadata"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + sseRequest(CreateResponses200Sse) + ), + "createVideos": (options) => + HttpClientRequest.post(`/videos`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateVideos202), + "400": decodeError("CreateVideos400", CreateVideos400), + "401": decodeError("CreateVideos401", CreateVideos401), + "402": decodeError("CreateVideos402", CreateVideos402), + "404": decodeError("CreateVideos404", CreateVideos404), + "429": decodeError("CreateVideos429", CreateVideos429), + "500": decodeError("CreateVideos500", CreateVideos500), + orElse: unexpectedStatus + })) + ), + "getVideos": (jobId, options) => + HttpClientRequest.get(`/videos/${jobId}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetVideos200), + "401": decodeError("GetVideos401", GetVideos401), + "404": decodeError("GetVideos404", GetVideos404), + "500": decodeError("GetVideos500", GetVideos500), + orElse: unexpectedStatus + })) + ), + "listVideosContent": (jobId, options) => + HttpClientRequest.get(`/videos/${jobId}/content`).pipe( + HttpClientRequest.setUrlParams({ "index": options?.params?.["index"] as any }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "400": decodeError("ListVideosContent400", ListVideosContent400), + "401": decodeError("ListVideosContent401", ListVideosContent401), + "404": decodeError("ListVideosContent404", ListVideosContent404), + "500": decodeError("ListVideosContent500", ListVideosContent500), + "502": decodeError("ListVideosContent502", ListVideosContent502), + orElse: unexpectedStatus + })) + ), + "listVideosModels": (options) => + HttpClientRequest.get(`/videos/models`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListVideosModels200), + "400": decodeError("ListVideosModels400", ListVideosModels400), + "500": decodeError("ListVideosModels500", ListVideosModels500), + orElse: unexpectedStatus + })) + ), + "listWorkspaces": (options) => + HttpClientRequest.get(`/workspaces`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListWorkspaces200), + "401": decodeError("ListWorkspaces401", ListWorkspaces401), + "500": decodeError("ListWorkspaces500", ListWorkspaces500), + orElse: unexpectedStatus + })) + ), + "createWorkspace": (options) => + HttpClientRequest.post(`/workspaces`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(CreateWorkspace201), + "400": decodeError("CreateWorkspace400", CreateWorkspace400), + "401": decodeError("CreateWorkspace401", CreateWorkspace401), + "403": decodeError("CreateWorkspace403", CreateWorkspace403), + "500": decodeError("CreateWorkspace500", CreateWorkspace500), + orElse: unexpectedStatus + })) + ), + "getWorkspace": (id, options) => + HttpClientRequest.get(`/workspaces/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(GetWorkspace200), + "401": decodeError("GetWorkspace401", GetWorkspace401), + "404": decodeError("GetWorkspace404", GetWorkspace404), + "500": decodeError("GetWorkspace500", GetWorkspace500), + orElse: unexpectedStatus + })) + ), + "deleteWorkspace": (id, options) => + HttpClientRequest.delete(`/workspaces/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteWorkspace200), + "400": decodeError("DeleteWorkspace400", DeleteWorkspace400), + "401": decodeError("DeleteWorkspace401", DeleteWorkspace401), + "403": decodeError("DeleteWorkspace403", DeleteWorkspace403), + "404": decodeError("DeleteWorkspace404", DeleteWorkspace404), + "500": decodeError("DeleteWorkspace500", DeleteWorkspace500), + orElse: unexpectedStatus + })) + ), + "updateWorkspace": (id, options) => + HttpClientRequest.patch(`/workspaces/${id}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpdateWorkspace200), + "400": decodeError("UpdateWorkspace400", UpdateWorkspace400), + "401": decodeError("UpdateWorkspace401", UpdateWorkspace401), + "403": decodeError("UpdateWorkspace403", UpdateWorkspace403), + "404": decodeError("UpdateWorkspace404", UpdateWorkspace404), + "500": decodeError("UpdateWorkspace500", UpdateWorkspace500), + orElse: unexpectedStatus + })) + ), + "listWorkspaceBudgets": (id, options) => + HttpClientRequest.get(`/workspaces/${id}/budgets`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListWorkspaceBudgets200), + "401": decodeError("ListWorkspaceBudgets401", ListWorkspaceBudgets401), + "404": decodeError("ListWorkspaceBudgets404", ListWorkspaceBudgets404), + "500": decodeError("ListWorkspaceBudgets500", ListWorkspaceBudgets500), + orElse: unexpectedStatus + })) + ), + "upsertWorkspaceBudget": (id, interval, options) => + HttpClientRequest.put(`/workspaces/${id}/budgets/${interval}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(UpsertWorkspaceBudget200), + "400": decodeError("UpsertWorkspaceBudget400", UpsertWorkspaceBudget400), + "401": decodeError("UpsertWorkspaceBudget401", UpsertWorkspaceBudget401), + "404": decodeError("UpsertWorkspaceBudget404", UpsertWorkspaceBudget404), + "500": decodeError("UpsertWorkspaceBudget500", UpsertWorkspaceBudget500), + orElse: unexpectedStatus + })) + ), + "deleteWorkspaceBudget": (id, interval, options) => + HttpClientRequest.delete(`/workspaces/${id}/budgets/${interval}`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(DeleteWorkspaceBudget200), + "401": decodeError("DeleteWorkspaceBudget401", DeleteWorkspaceBudget401), + "404": decodeError("DeleteWorkspaceBudget404", DeleteWorkspaceBudget404), + "500": decodeError("DeleteWorkspaceBudget500", DeleteWorkspaceBudget500), + orElse: unexpectedStatus + })) + ), + "listWorkspaceMembers": (id, options) => + HttpClientRequest.get(`/workspaces/${id}/members`).pipe( + HttpClientRequest.setUrlParams({ + "offset": options?.params?.["offset"] as any, + "limit": options?.params?.["limit"] as any + }), + HttpClientRequest.setHeaders({ + "HTTP-Referer": options?.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options?.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options?.params?.["X-OpenRouter-Categories"] ?? undefined + }), + withResponse(options?.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(ListWorkspaceMembers200), + "401": decodeError("ListWorkspaceMembers401", ListWorkspaceMembers401), + "403": decodeError("ListWorkspaceMembers403", ListWorkspaceMembers403), + "404": decodeError("ListWorkspaceMembers404", ListWorkspaceMembers404), + "500": decodeError("ListWorkspaceMembers500", ListWorkspaceMembers500), + orElse: unexpectedStatus + })) + ), + "bulkAddWorkspaceMembers": (id, options) => + HttpClientRequest.post(`/workspaces/${id}/members/add`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkAddWorkspaceMembers200), + "400": decodeError("BulkAddWorkspaceMembers400", BulkAddWorkspaceMembers400), + "401": decodeError("BulkAddWorkspaceMembers401", BulkAddWorkspaceMembers401), + "403": decodeError("BulkAddWorkspaceMembers403", BulkAddWorkspaceMembers403), + "404": decodeError("BulkAddWorkspaceMembers404", BulkAddWorkspaceMembers404), + "500": decodeError("BulkAddWorkspaceMembers500", BulkAddWorkspaceMembers500), + orElse: unexpectedStatus + })) + ), + "bulkRemoveWorkspaceMembers": (id, options) => + HttpClientRequest.post(`/workspaces/${id}/members/remove`).pipe( + HttpClientRequest.setHeaders({ + "HTTP-Referer": options.params?.["HTTP-Referer"] ?? undefined, + "X-OpenRouter-Title": options.params?.["X-OpenRouter-Title"] ?? undefined, + "X-OpenRouter-Categories": options.params?.["X-OpenRouter-Categories"] ?? undefined + }), + HttpClientRequest.bodyJsonUnsafe(options.payload), + withResponse(options.config)(HttpClientResponse.matchStatus({ + "2xx": decodeSuccess(BulkRemoveWorkspaceMembers200), + "400": decodeError("BulkRemoveWorkspaceMembers400", BulkRemoveWorkspaceMembers400), + "401": decodeError("BulkRemoveWorkspaceMembers401", BulkRemoveWorkspaceMembers401), + "403": decodeError("BulkRemoveWorkspaceMembers403", BulkRemoveWorkspaceMembers403), + "404": decodeError("BulkRemoveWorkspaceMembers404", BulkRemoveWorkspaceMembers404), + "500": decodeError("BulkRemoveWorkspaceMembers500", BulkRemoveWorkspaceMembers500), + orElse: unexpectedStatus + })) + ) + } +} + +export interface OpenRouterClient { + readonly httpClient: HttpClient.HttpClient + /** + * Returns user activity data grouped by endpoint for the last 30 (completed) UTC days. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getUserActivity": ( + options: { + readonly params?: typeof GetUserActivityParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetUserActivity400", typeof GetUserActivity400.Type> + | OpenRouterClientError<"GetUserActivity401", typeof GetUserActivity401.Type> + | OpenRouterClientError<"GetUserActivity403", typeof GetUserActivity403.Type> + | OpenRouterClientError<"GetUserActivity404", typeof GetUserActivity404.Type> + | OpenRouterClientError<"GetUserActivity500", typeof GetUserActivity500.Type> + > + /** + * Returns the available metrics, dimensions, filter operators, and granularities for the analytics query endpoint. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getAnalyticsMeta": ( + options: { + readonly params?: typeof GetAnalyticsMetaParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetAnalyticsMeta401", typeof GetAnalyticsMeta401.Type> + | OpenRouterClientError<"GetAnalyticsMeta403", typeof GetAnalyticsMeta403.Type> + | OpenRouterClientError<"GetAnalyticsMeta500", typeof GetAnalyticsMeta500.Type> + > + /** + * Execute an analytics query with specified metrics, dimensions, filters, and time range. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "queryAnalytics": ( + options: { + readonly params?: typeof QueryAnalyticsParams.Encoded | undefined + readonly payload: typeof QueryAnalyticsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"QueryAnalytics400", typeof QueryAnalytics400.Type> + | OpenRouterClientError<"QueryAnalytics401", typeof QueryAnalytics401.Type> + | OpenRouterClientError<"QueryAnalytics403", typeof QueryAnalytics403.Type> + | OpenRouterClientError<"QueryAnalytics408", typeof QueryAnalytics408.Type> + | OpenRouterClientError<"QueryAnalytics500", typeof QueryAnalytics500.Type> + > + /** + * Synthesizes audio from the input text. Returns a raw audio bytestream in the requested format (e.g. mp3, pcm, wav). + */ + readonly "createAudioSpeech": ( + options: { + readonly params?: typeof CreateAudioSpeechParams.Encoded | undefined + readonly payload: typeof CreateAudioSpeechRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateAudioSpeech400", typeof CreateAudioSpeech400.Type> + | OpenRouterClientError<"CreateAudioSpeech401", typeof CreateAudioSpeech401.Type> + | OpenRouterClientError<"CreateAudioSpeech402", typeof CreateAudioSpeech402.Type> + | OpenRouterClientError<"CreateAudioSpeech404", typeof CreateAudioSpeech404.Type> + | OpenRouterClientError<"CreateAudioSpeech429", typeof CreateAudioSpeech429.Type> + | OpenRouterClientError<"CreateAudioSpeech500", typeof CreateAudioSpeech500.Type> + | OpenRouterClientError<"CreateAudioSpeech502", typeof CreateAudioSpeech502.Type> + | OpenRouterClientError<"CreateAudioSpeech503", typeof CreateAudioSpeech503.Type> + | OpenRouterClientError<"CreateAudioSpeech524", typeof CreateAudioSpeech524.Type> + | OpenRouterClientError<"CreateAudioSpeech529", typeof CreateAudioSpeech529.Type> + > + /** + * Transcribes audio into text. Accepts base64-encoded audio input as JSON or an OpenAI-style multipart/form-data file upload, and returns the transcribed text. + */ + readonly "createAudioTranscriptions": ( + options: { + readonly params?: typeof CreateAudioTranscriptionsParams.Encoded | undefined + readonly payload: typeof CreateAudioTranscriptionsRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateAudioTranscriptions400", typeof CreateAudioTranscriptions400.Type> + | OpenRouterClientError<"CreateAudioTranscriptions401", typeof CreateAudioTranscriptions401.Type> + | OpenRouterClientError<"CreateAudioTranscriptions402", typeof CreateAudioTranscriptions402.Type> + | OpenRouterClientError<"CreateAudioTranscriptions404", typeof CreateAudioTranscriptions404.Type> + | OpenRouterClientError<"CreateAudioTranscriptions429", typeof CreateAudioTranscriptions429.Type> + | OpenRouterClientError<"CreateAudioTranscriptions500", typeof CreateAudioTranscriptions500.Type> + | OpenRouterClientError<"CreateAudioTranscriptions502", typeof CreateAudioTranscriptions502.Type> + | OpenRouterClientError<"CreateAudioTranscriptions503", typeof CreateAudioTranscriptions503.Type> + | OpenRouterClientError<"CreateAudioTranscriptions524", typeof CreateAudioTranscriptions524.Type> + | OpenRouterClientError<"CreateAudioTranscriptions529", typeof CreateAudioTranscriptions529.Type> + > + /** + * Exchange an authorization code from the PKCE flow for a user-controlled API key + */ + readonly "exchangeAuthCodeForAPIKey": ( + options: { + readonly params?: typeof ExchangeAuthCodeForAPIKeyParams.Encoded | undefined + readonly payload: typeof ExchangeAuthCodeForAPIKeyRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ExchangeAuthCodeForAPIKey400", typeof ExchangeAuthCodeForAPIKey400.Type> + | OpenRouterClientError<"ExchangeAuthCodeForAPIKey403", typeof ExchangeAuthCodeForAPIKey403.Type> + | OpenRouterClientError<"ExchangeAuthCodeForAPIKey500", typeof ExchangeAuthCodeForAPIKey500.Type> + > + /** + * Create an authorization code for the PKCE flow to generate a user-controlled API key + */ + readonly "createAuthKeysCode": ( + options: { + readonly params?: typeof CreateAuthKeysCodeParams.Encoded | undefined + readonly payload: typeof CreateAuthKeysCodeRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateAuthKeysCode400", typeof CreateAuthKeysCode400.Type> + | OpenRouterClientError<"CreateAuthKeysCode401", typeof CreateAuthKeysCode401.Type> + | OpenRouterClientError<"CreateAuthKeysCode403", typeof CreateAuthKeysCode403.Type> + | OpenRouterClientError<"CreateAuthKeysCode409", typeof CreateAuthKeysCode409.Type> + | OpenRouterClientError<"CreateAuthKeysCode500", typeof CreateAuthKeysCode500.Type> + > + /** + * Unified benchmark endpoint that aggregates scores from multiple benchmark sources (Artificial Analysis, Design Arena). Filter by source to reproduce the exact shapes from the legacy per-source endpoints, or use task_type to find models suited for specific workloads. Authenticate with any valid OpenRouter API key. Rate-limited to 30 requests/minute per key and 500 requests/day per account. + */ + readonly "getBenchmarks": ( + options: + | { readonly params?: typeof GetBenchmarksParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetBenchmarks400", typeof GetBenchmarks400.Type> + | OpenRouterClientError<"GetBenchmarks401", typeof GetBenchmarks401.Type> + | OpenRouterClientError<"GetBenchmarks429", typeof GetBenchmarks429.Type> + | OpenRouterClientError<"GetBenchmarks500", typeof GetBenchmarks500.Type> + > + /** + * List the bring-your-own-key (BYOK) provider credentials for the authenticated entity's default workspace. Use the `workspace_id` query parameter to scope the result to a different workspace, or the `provider` query parameter to filter by upstream provider. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listBYOKKeys": ( + options: + | { readonly params?: typeof ListBYOKKeysParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListBYOKKeys400", typeof ListBYOKKeys400.Type> + | OpenRouterClientError<"ListBYOKKeys401", typeof ListBYOKKeys401.Type> + | OpenRouterClientError<"ListBYOKKeys500", typeof ListBYOKKeys500.Type> + > + /** + * Create a new bring-your-own-key (BYOK) provider credential. The raw key is encrypted at rest and never returned in API responses. Defaults to the authenticated entity's default workspace; use the `workspace_id` body field to scope to a different workspace. Treat the raw key as write-only; it is never returned after creation. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "createBYOKKey": ( + options: { + readonly params?: typeof CreateBYOKKeyParams.Encoded | undefined + readonly payload: typeof CreateBYOKKeyRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateBYOKKey400", typeof CreateBYOKKey400.Type> + | OpenRouterClientError<"CreateBYOKKey401", typeof CreateBYOKKey401.Type> + | OpenRouterClientError<"CreateBYOKKey403", typeof CreateBYOKKey403.Type> + | OpenRouterClientError<"CreateBYOKKey500", typeof CreateBYOKKey500.Type> + > + /** + * Get a single bring-your-own-key (BYOK) provider credential by its `id`. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getBYOKKey": ( + id: string, + options: + | { readonly params?: typeof GetBYOKKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetBYOKKey401", typeof GetBYOKKey401.Type> + | OpenRouterClientError<"GetBYOKKey404", typeof GetBYOKKey404.Type> + | OpenRouterClientError<"GetBYOKKey500", typeof GetBYOKKey500.Type> + > + /** + * Delete (soft-delete) a bring-your-own-key (BYOK) provider credential by its `id`. The encrypted key material is wiped and the record is marked as deleted. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteBYOKKey": ( + id: string, + options: + | { readonly params?: typeof DeleteBYOKKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteBYOKKey401", typeof DeleteBYOKKey401.Type> + | OpenRouterClientError<"DeleteBYOKKey404", typeof DeleteBYOKKey404.Type> + | OpenRouterClientError<"DeleteBYOKKey500", typeof DeleteBYOKKey500.Type> + > + /** + * Update an existing bring-your-own-key (BYOK) provider credential by its `id`. Include the `key` field to rotate the raw provider API key in-place (the previous key material is overwritten). [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateBYOKKey": ( + id: string, + options: { + readonly params?: typeof UpdateBYOKKeyParams.Encoded | undefined + readonly payload: typeof UpdateBYOKKeyRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpdateBYOKKey400", typeof UpdateBYOKKey400.Type> + | OpenRouterClientError<"UpdateBYOKKey401", typeof UpdateBYOKKey401.Type> + | OpenRouterClientError<"UpdateBYOKKey404", typeof UpdateBYOKKey404.Type> + | OpenRouterClientError<"UpdateBYOKKey500", typeof UpdateBYOKKey500.Type> + > + /** + * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. + */ + readonly "sendChatCompletionRequest": ( + options: { + readonly params?: typeof SendChatCompletionRequestParams.Encoded | undefined + readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"SendChatCompletionRequest400", typeof SendChatCompletionRequest400.Type> + | OpenRouterClientError<"SendChatCompletionRequest401", typeof SendChatCompletionRequest401.Type> + | OpenRouterClientError<"SendChatCompletionRequest402", typeof SendChatCompletionRequest402.Type> + | OpenRouterClientError<"SendChatCompletionRequest403", typeof SendChatCompletionRequest403.Type> + | OpenRouterClientError<"SendChatCompletionRequest404", typeof SendChatCompletionRequest404.Type> + | OpenRouterClientError<"SendChatCompletionRequest408", typeof SendChatCompletionRequest408.Type> + | OpenRouterClientError<"SendChatCompletionRequest413", typeof SendChatCompletionRequest413.Type> + | OpenRouterClientError<"SendChatCompletionRequest422", typeof SendChatCompletionRequest422.Type> + | OpenRouterClientError<"SendChatCompletionRequest429", typeof SendChatCompletionRequest429.Type> + | OpenRouterClientError<"SendChatCompletionRequest500", typeof SendChatCompletionRequest500.Type> + | OpenRouterClientError<"SendChatCompletionRequest502", typeof SendChatCompletionRequest502.Type> + | OpenRouterClientError<"SendChatCompletionRequest503", typeof SendChatCompletionRequest503.Type> + | OpenRouterClientError<"SendChatCompletionRequest524", typeof SendChatCompletionRequest524.Type> + | OpenRouterClientError<"SendChatCompletionRequest529", typeof SendChatCompletionRequest529.Type> + > + /** + * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. + */ + readonly "sendChatCompletionRequestSse": ( + options: { + readonly params?: typeof SendChatCompletionRequestParams.Encoded | undefined + readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded + } + ) => Stream.Stream< + { + readonly event: string + readonly id: string | undefined + readonly data: typeof SendChatCompletionRequest200Sse.Type + }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof SendChatCompletionRequest200Sse.DecodingServices + > + /** + * Returns the market-share breakdown of OpenRouter traffic by task classification + * (e.g. code generation, web search, summarization) over a trailing time window. + * + * Each classification reports its share of classified sampled requests (`usage_share`) + * and classified sampled token volume (`token_share`) as fractions between 0 and 1. + * The unclassified `other` bucket is excluded. Absolute volumes are not exposed + * because the underlying data is sampled. + * + * Each classification also includes a `models` array listing the top models by + * request volume within that classification, with their within-tag usage and token shares. + * + * Classifications are grouped into macro-categories (Code, Data, Agent, General) + * with aggregate shares provided for each. + * + * Authenticate with any valid OpenRouter API key (same key used for inference). + * Rate-limited to 30 requests/minute per key and 500 requests/day per account. + * + * When republishing or quoting this data, cite as: + * "Source: OpenRouter (openrouter.ai/rankings), as of {as_of}." + */ + readonly "getTaskClassifications": ( + options: { + readonly params?: typeof GetTaskClassificationsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetTaskClassifications400", typeof GetTaskClassifications400.Type> + | OpenRouterClientError<"GetTaskClassifications401", typeof GetTaskClassifications401.Type> + | OpenRouterClientError<"GetTaskClassifications429", typeof GetTaskClassifications429.Type> + | OpenRouterClientError<"GetTaskClassifications500", typeof GetTaskClassifications500.Type> + > + /** + * Get total credits purchased and used for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getCredits": ( + options: + | { readonly params?: typeof GetCreditsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetCredits401", typeof GetCredits401.Type> + | OpenRouterClientError<"GetCredits403", typeof GetCredits403.Type> + | OpenRouterClientError<"GetCredits500", typeof GetCredits500.Type> + > + /** + * Deprecated. The Coinbase APIs used by this endpoint have been deprecated, so Coinbase Commerce charges have been removed. Use the web credits purchase flow instead. + */ + readonly "createCoinbaseCharge": ( + options: { + readonly params?: typeof CreateCoinbaseChargeParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateCoinbaseCharge410", typeof CreateCoinbaseCharge410.Type> + > + /** + * Returns the top public apps on OpenRouter ranked by token usage inside the requested + * date window, matching the public apps marketplace on openrouter.ai/apps. Token totals + * are `prompt_tokens + completion_tokens`; hidden and private apps are excluded and + * traffic from related app aliases is merged into the canonical visible app. + * + * `sort=popular` (default) ranks by total token volume inside the window. + * `sort=trending` ranks by absolute excess token growth: window volume minus the average + * volume of the three equal-length periods immediately preceding the window. Apps with + * no excess growth are omitted, so `trending` may return fewer than `limit` rows. + * + * Filter with `category` (marketplace category group, e.g. `coding`) or `subcategory` + * (e.g. `cli-agent`). Ranks are re-numbered 1..N after filtering. Page with `offset` — + * `rank` stays absolute, so the first row of `offset=50` is `rank: 51`. + * + * Authenticate with any valid OpenRouter API key (same key used for inference). + * Rate-limited to 30 requests/minute per key and 500 requests/day per account. + * + * When republishing or quoting this dataset, OpenRouter must be cited as: + * "Source: OpenRouter (openrouter.ai/apps), as of {as_of}." + * + * Token counts come from each upstream provider's own tokenizer, so a token attributed + * to one app is not directly comparable to a token attributed to another app whose + * traffic flows through a different provider. + */ + readonly "getAppRankings": ( + options: + | { readonly params?: typeof GetAppRankingsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetAppRankings400", typeof GetAppRankings400.Type> + | OpenRouterClientError<"GetAppRankings401", typeof GetAppRankings401.Type> + | OpenRouterClientError<"GetAppRankings429", typeof GetAppRankings429.Type> + | OpenRouterClientError<"GetAppRankings500", typeof GetAppRankings500.Type> + > + /** + * Returns the top 50 public models per day by total token usage on OpenRouter, plus a + * single aggregated `other` row per day that sums every model outside that top 50. + * Token totals are `prompt_tokens + completion_tokens`, matching the public rankings + * chart on openrouter.ai/rankings. + * + * Each row is a distinct `(date, model_permaslug)` pair. The `other` row uses the + * reserved permaslug `other` and is always returned last within its date, so callers + * can compute `top-50 traffic / total daily traffic` without a second request. + * + * Optional filters slice the dataset. `period` (`day`/`week`/`month`) sets the time + * grain. `modality` and `context_bucket` narrow the exact dataset by output/input + * modality (or tool-calling activity) and request context length. `category` and + * `language_type` instead read a sampled, upsampled dataset whose `total_tokens` are + * weekly-grain estimates — they cannot be combined with each other or with the exact + * filters, and reject `period=day` with a 400. + * + * Authenticate with any valid OpenRouter API key (same key used for inference). + * Rate-limited to 30 requests/minute per key and 500 requests/day per account. + * + * When republishing or quoting this dataset, OpenRouter must be cited as: + * "Source: OpenRouter (openrouter.ai/rankings), as of {as_of}." + * + * Token counts come from each upstream provider's own tokenizer (Anthropic counts + * are as reported by Anthropic, OpenAI counts are as reported by OpenAI, etc.), so + * a token in one row is not directly comparable to a token in another row from a + * different provider. + */ + readonly "getRankingsDaily": ( + options: { + readonly params?: typeof GetRankingsDailyParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetRankingsDaily400", typeof GetRankingsDaily400.Type> + | OpenRouterClientError<"GetRankingsDaily401", typeof GetRankingsDaily401.Type> + | OpenRouterClientError<"GetRankingsDaily429", typeof GetRankingsDaily429.Type> + | OpenRouterClientError<"GetRankingsDaily500", typeof GetRankingsDaily500.Type> + > + /** + * Submits an embedding request to the embeddings router + */ + readonly "createEmbeddings": ( + options: { + readonly params?: typeof CreateEmbeddingsParams.Encoded | undefined + readonly payload: typeof CreateEmbeddingsRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateEmbeddings400", typeof CreateEmbeddings400.Type> + | OpenRouterClientError<"CreateEmbeddings401", typeof CreateEmbeddings401.Type> + | OpenRouterClientError<"CreateEmbeddings402", typeof CreateEmbeddings402.Type> + | OpenRouterClientError<"CreateEmbeddings404", typeof CreateEmbeddings404.Type> + | OpenRouterClientError<"CreateEmbeddings429", typeof CreateEmbeddings429.Type> + | OpenRouterClientError<"CreateEmbeddings500", typeof CreateEmbeddings500.Type> + | OpenRouterClientError<"CreateEmbeddings502", typeof CreateEmbeddings502.Type> + | OpenRouterClientError<"CreateEmbeddings503", typeof CreateEmbeddings503.Type> + | OpenRouterClientError<"CreateEmbeddings524", typeof CreateEmbeddings524.Type> + | OpenRouterClientError<"CreateEmbeddings529", typeof CreateEmbeddings529.Type> + > + /** + * Submits an embedding request to the embeddings router + */ + readonly "createEmbeddingsSse": ( + options: { + readonly params?: typeof CreateEmbeddingsParams.Encoded | undefined + readonly payload: typeof CreateEmbeddingsRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateEmbeddings200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateEmbeddings200Sse.DecodingServices + > + /** + * Returns a list of all available embeddings models and their properties + */ + readonly "listEmbeddingsModels": ( + options: { + readonly params?: typeof ListEmbeddingsModelsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListEmbeddingsModels400", typeof ListEmbeddingsModels400.Type> + | OpenRouterClientError<"ListEmbeddingsModels500", typeof ListEmbeddingsModels500.Type> + > + /** + * Preview the impact of ZDR on the available endpoints + */ + readonly "listEndpointsZdr": ( + options: { + readonly params?: typeof ListEndpointsZdrParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListEndpointsZdr403", typeof ListEndpointsZdr403.Type> + | OpenRouterClientError<"ListEndpointsZdr500", typeof ListEndpointsZdr500.Type> + > + /** + * Lists files belonging to the workspace of the authenticating API key. + */ + readonly "listFiles": ( + options: + | { readonly params?: typeof ListFilesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListFiles400", typeof ListFiles400.Type> + | OpenRouterClientError<"ListFiles401", typeof ListFiles401.Type> + | OpenRouterClientError<"ListFiles429", typeof ListFiles429.Type> + | OpenRouterClientError<"ListFiles500", typeof ListFiles500.Type> + > + /** + * Uploads a file to be referenced in future API calls. The file is stored under the workspace of the authenticating API key. Maximum file size: 100 MB. + */ + readonly "uploadFile": ( + options: { + readonly params?: typeof UploadFileParams.Encoded | undefined + readonly payload: typeof UploadFileRequestFormData.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UploadFile400", typeof UploadFile400.Type> + | OpenRouterClientError<"UploadFile401", typeof UploadFile401.Type> + | OpenRouterClientError<"UploadFile403", typeof UploadFile403.Type> + | OpenRouterClientError<"UploadFile413", typeof UploadFile413.Type> + | OpenRouterClientError<"UploadFile429", typeof UploadFile429.Type> + | OpenRouterClientError<"UploadFile500", typeof UploadFile500.Type> + > + /** + * Retrieves metadata for a single file owned by the requesting workspace. + */ + readonly "getFileMetadata": ( + fileId: string, + options: { + readonly params?: typeof GetFileMetadataParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetFileMetadata401", typeof GetFileMetadata401.Type> + | OpenRouterClientError<"GetFileMetadata404", typeof GetFileMetadata404.Type> + | OpenRouterClientError<"GetFileMetadata429", typeof GetFileMetadata429.Type> + | OpenRouterClientError<"GetFileMetadata500", typeof GetFileMetadata500.Type> + > + /** + * Deletes a file owned by the requesting workspace. Deletion is irreversible. + */ + readonly "deleteFile": ( + fileId: string, + options: + | { readonly params?: typeof DeleteFileParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteFile401", typeof DeleteFile401.Type> + | OpenRouterClientError<"DeleteFile404", typeof DeleteFile404.Type> + | OpenRouterClientError<"DeleteFile429", typeof DeleteFile429.Type> + | OpenRouterClientError<"DeleteFile500", typeof DeleteFile500.Type> + > + /** + * Downloads the raw bytes of a file. Only files created server-side are downloadable; uploaded files return 400. + */ + readonly "downloadFileContent": ( + fileId: string, + options: { + readonly params?: typeof DownloadFileContentParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DownloadFileContent400", typeof DownloadFileContent400.Type> + | OpenRouterClientError<"DownloadFileContent401", typeof DownloadFileContent401.Type> + | OpenRouterClientError<"DownloadFileContent404", typeof DownloadFileContent404.Type> + | OpenRouterClientError<"DownloadFileContent429", typeof DownloadFileContent429.Type> + | OpenRouterClientError<"DownloadFileContent500", typeof DownloadFileContent500.Type> + > + /** + * Downloads the raw bytes of a file. Only files created server-side are downloadable; uploaded files return 400. + */ + readonly "downloadFileContentStream": ( + fileId: string, + options: { readonly params?: typeof DownloadFileContentParams.Encoded | undefined } | undefined + ) => Stream.Stream + /** + * Get request & usage metadata for a generation + */ + readonly "getGeneration": ( + options: { readonly params: typeof GetGenerationParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetGeneration401", typeof GetGeneration401.Type> + | OpenRouterClientError<"GetGeneration402", typeof GetGeneration402.Type> + | OpenRouterClientError<"GetGeneration404", typeof GetGeneration404.Type> + | OpenRouterClientError<"GetGeneration429", typeof GetGeneration429.Type> + | OpenRouterClientError<"GetGeneration500", typeof GetGeneration500.Type> + | OpenRouterClientError<"GetGeneration502", typeof GetGeneration502.Type> + | OpenRouterClientError<"GetGeneration524", typeof GetGeneration524.Type> + | OpenRouterClientError<"GetGeneration529", typeof GetGeneration529.Type> + > + /** + * Get stored prompt and completion content for a generation + */ + readonly "listGenerationContent": ( + options: { readonly params: typeof ListGenerationContentParams.Encoded; readonly config?: Config | undefined } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGenerationContent401", typeof ListGenerationContent401.Type> + | OpenRouterClientError<"ListGenerationContent403", typeof ListGenerationContent403.Type> + | OpenRouterClientError<"ListGenerationContent404", typeof ListGenerationContent404.Type> + | OpenRouterClientError<"ListGenerationContent429", typeof ListGenerationContent429.Type> + | OpenRouterClientError<"ListGenerationContent500", typeof ListGenerationContent500.Type> + | OpenRouterClientError<"ListGenerationContent502", typeof ListGenerationContent502.Type> + | OpenRouterClientError<"ListGenerationContent524", typeof ListGenerationContent524.Type> + | OpenRouterClientError<"ListGenerationContent529", typeof ListGenerationContent529.Type> + > + /** + * Submit structured feedback on a generation the authenticated user made. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "submitGenerationFeedback": ( + options: { + readonly params?: typeof SubmitGenerationFeedbackParams.Encoded | undefined + readonly payload: typeof SubmitGenerationFeedbackRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"SubmitGenerationFeedback400", typeof SubmitGenerationFeedback400.Type> + | OpenRouterClientError<"SubmitGenerationFeedback401", typeof SubmitGenerationFeedback401.Type> + | OpenRouterClientError<"SubmitGenerationFeedback404", typeof SubmitGenerationFeedback404.Type> + | OpenRouterClientError<"SubmitGenerationFeedback429", typeof SubmitGenerationFeedback429.Type> + | OpenRouterClientError<"SubmitGenerationFeedback500", typeof SubmitGenerationFeedback500.Type> + > + /** + * List all guardrails for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listGuardrails": ( + options: + | { readonly params?: typeof ListGuardrailsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGuardrails400", typeof ListGuardrails400.Type> + | OpenRouterClientError<"ListGuardrails401", typeof ListGuardrails401.Type> + | OpenRouterClientError<"ListGuardrails500", typeof ListGuardrails500.Type> + > + /** + * Create a new guardrail for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "createGuardrail": ( + options: { + readonly params?: typeof CreateGuardrailParams.Encoded | undefined + readonly payload: typeof CreateGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateGuardrail400", typeof CreateGuardrail400.Type> + | OpenRouterClientError<"CreateGuardrail401", typeof CreateGuardrail401.Type> + | OpenRouterClientError<"CreateGuardrail403", typeof CreateGuardrail403.Type> + | OpenRouterClientError<"CreateGuardrail500", typeof CreateGuardrail500.Type> + > + /** + * Get a single guardrail by ID. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getGuardrail": ( + id: string, + options: + | { readonly params?: typeof GetGuardrailParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetGuardrail401", typeof GetGuardrail401.Type> + | OpenRouterClientError<"GetGuardrail404", typeof GetGuardrail404.Type> + | OpenRouterClientError<"GetGuardrail500", typeof GetGuardrail500.Type> + > + /** + * Delete an existing guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteGuardrail": ( + id: string, + options: { + readonly params?: typeof DeleteGuardrailParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteGuardrail401", typeof DeleteGuardrail401.Type> + | OpenRouterClientError<"DeleteGuardrail404", typeof DeleteGuardrail404.Type> + | OpenRouterClientError<"DeleteGuardrail500", typeof DeleteGuardrail500.Type> + > + /** + * Update an existing guardrail. Collection fields use replace semantics: send the full desired set on every update. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateGuardrail": ( + id: string, + options: { + readonly params?: typeof UpdateGuardrailParams.Encoded | undefined + readonly payload: typeof UpdateGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpdateGuardrail400", typeof UpdateGuardrail400.Type> + | OpenRouterClientError<"UpdateGuardrail401", typeof UpdateGuardrail401.Type> + | OpenRouterClientError<"UpdateGuardrail404", typeof UpdateGuardrail404.Type> + | OpenRouterClientError<"UpdateGuardrail500", typeof UpdateGuardrail500.Type> + > + /** + * List all API key assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listGuardrailKeyAssignments": ( + id: string, + options: { + readonly params?: typeof ListGuardrailKeyAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGuardrailKeyAssignments401", typeof ListGuardrailKeyAssignments401.Type> + | OpenRouterClientError<"ListGuardrailKeyAssignments404", typeof ListGuardrailKeyAssignments404.Type> + | OpenRouterClientError<"ListGuardrailKeyAssignments500", typeof ListGuardrailKeyAssignments500.Type> + > + /** + * Assign multiple API keys to a specific guardrail. A key may hold at most one guardrail; assigning replaces any existing assignment. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkAssignKeysToGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkAssignKeysToGuardrailParams.Encoded | undefined + readonly payload: typeof BulkAssignKeysToGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkAssignKeysToGuardrail400", typeof BulkAssignKeysToGuardrail400.Type> + | OpenRouterClientError<"BulkAssignKeysToGuardrail401", typeof BulkAssignKeysToGuardrail401.Type> + | OpenRouterClientError<"BulkAssignKeysToGuardrail404", typeof BulkAssignKeysToGuardrail404.Type> + | OpenRouterClientError<"BulkAssignKeysToGuardrail500", typeof BulkAssignKeysToGuardrail500.Type> + > + /** + * Unassign multiple API keys from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkUnassignKeysFromGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkUnassignKeysFromGuardrailParams.Encoded | undefined + readonly payload: typeof BulkUnassignKeysFromGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail400", typeof BulkUnassignKeysFromGuardrail400.Type> + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail401", typeof BulkUnassignKeysFromGuardrail401.Type> + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail404", typeof BulkUnassignKeysFromGuardrail404.Type> + | OpenRouterClientError<"BulkUnassignKeysFromGuardrail500", typeof BulkUnassignKeysFromGuardrail500.Type> + > + /** + * List all organization member assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listGuardrailMemberAssignments": ( + id: string, + options: { + readonly params?: typeof ListGuardrailMemberAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListGuardrailMemberAssignments401", typeof ListGuardrailMemberAssignments401.Type> + | OpenRouterClientError<"ListGuardrailMemberAssignments404", typeof ListGuardrailMemberAssignments404.Type> + | OpenRouterClientError<"ListGuardrailMemberAssignments500", typeof ListGuardrailMemberAssignments500.Type> + > + /** + * Assign multiple organization members to a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkAssignMembersToGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkAssignMembersToGuardrailParams.Encoded | undefined + readonly payload: typeof BulkAssignMembersToGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkAssignMembersToGuardrail400", typeof BulkAssignMembersToGuardrail400.Type> + | OpenRouterClientError<"BulkAssignMembersToGuardrail401", typeof BulkAssignMembersToGuardrail401.Type> + | OpenRouterClientError<"BulkAssignMembersToGuardrail404", typeof BulkAssignMembersToGuardrail404.Type> + | OpenRouterClientError<"BulkAssignMembersToGuardrail500", typeof BulkAssignMembersToGuardrail500.Type> + > + /** + * Unassign multiple organization members from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "bulkUnassignMembersFromGuardrail": ( + id: string, + options: { + readonly params?: typeof BulkUnassignMembersFromGuardrailParams.Encoded | undefined + readonly payload: typeof BulkUnassignMembersFromGuardrailRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail400", typeof BulkUnassignMembersFromGuardrail400.Type> + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail401", typeof BulkUnassignMembersFromGuardrail401.Type> + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail404", typeof BulkUnassignMembersFromGuardrail404.Type> + | OpenRouterClientError<"BulkUnassignMembersFromGuardrail500", typeof BulkUnassignMembersFromGuardrail500.Type> + > + /** + * List all API key guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listKeyAssignments": ( + options: { + readonly params?: typeof ListKeyAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListKeyAssignments401", typeof ListKeyAssignments401.Type> + | OpenRouterClientError<"ListKeyAssignments500", typeof ListKeyAssignments500.Type> + > + /** + * List all organization member guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "listMemberAssignments": ( + options: { + readonly params?: typeof ListMemberAssignmentsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListMemberAssignments401", typeof ListMemberAssignments401.Type> + | OpenRouterClientError<"ListMemberAssignments500", typeof ListMemberAssignments500.Type> + > + /** + * Generates an image from a text prompt via the image generation router + */ + readonly "createImages": ( + options: { + readonly params?: typeof CreateImagesParams.Encoded | undefined + readonly payload: typeof CreateImagesRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateImages400", typeof CreateImages400.Type> + | OpenRouterClientError<"CreateImages401", typeof CreateImages401.Type> + | OpenRouterClientError<"CreateImages402", typeof CreateImages402.Type> + | OpenRouterClientError<"CreateImages403", typeof CreateImages403.Type> + | OpenRouterClientError<"CreateImages404", typeof CreateImages404.Type> + | OpenRouterClientError<"CreateImages413", typeof CreateImages413.Type> + | OpenRouterClientError<"CreateImages429", typeof CreateImages429.Type> + | OpenRouterClientError<"CreateImages500", typeof CreateImages500.Type> + | OpenRouterClientError<"CreateImages502", typeof CreateImages502.Type> + | OpenRouterClientError<"CreateImages524", typeof CreateImages524.Type> + | OpenRouterClientError<"CreateImages529", typeof CreateImages529.Type> + > + /** + * Generates an image from a text prompt via the image generation router + */ + readonly "createImagesSse": ( + options: { + readonly params?: typeof CreateImagesParams.Encoded | undefined + readonly payload: typeof CreateImagesRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateImages200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateImages200Sse.DecodingServices + > + /** + * Lists every image generation model with its top-level supported-parameter superset and a URL to its full per-endpoint records. + */ + readonly "listImageModels": ( + options: { + readonly params?: typeof ListImageModelsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListImageModels500", typeof ListImageModels500.Type> + > + /** + * Returns the full per-endpoint records for an image model: each endpoint's definitive supported parameters, pricing, and passthrough allowlist. + */ + readonly "listImageModelEndpoints": ( + author: string, + slug: string, + options: { + readonly params?: typeof ListImageModelEndpointsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListImageModelEndpoints404", typeof ListImageModelEndpoints404.Type> + | OpenRouterClientError<"ListImageModelEndpoints500", typeof ListImageModelEndpoints500.Type> + > + /** + * Get information on the API key associated with the current authentication session + */ + readonly "getCurrentKey": ( + options: + | { readonly params?: typeof GetCurrentKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetCurrentKey401", typeof GetCurrentKey401.Type> + | OpenRouterClientError<"GetCurrentKey500", typeof GetCurrentKey500.Type> + > /** - * Creates a streaming or non-streaming response using OpenResponses API format + * List all API keys for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "createResponses": ( - options: { readonly payload: typeof CreateResponsesRequestJson.Encoded; readonly config?: Config | undefined } + readonly "list": ( + options: + | { readonly params?: typeof ListParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateResponses400", typeof CreateResponses400.Type> - | OpenRouterClientError<"CreateResponses401", typeof CreateResponses401.Type> - | OpenRouterClientError<"CreateResponses402", typeof CreateResponses402.Type> - | OpenRouterClientError<"CreateResponses404", typeof CreateResponses404.Type> - | OpenRouterClientError<"CreateResponses408", typeof CreateResponses408.Type> - | OpenRouterClientError<"CreateResponses413", typeof CreateResponses413.Type> - | OpenRouterClientError<"CreateResponses422", typeof CreateResponses422.Type> - | OpenRouterClientError<"CreateResponses429", typeof CreateResponses429.Type> - | OpenRouterClientError<"CreateResponses500", typeof CreateResponses500.Type> - | OpenRouterClientError<"CreateResponses502", typeof CreateResponses502.Type> - | OpenRouterClientError<"CreateResponses503", typeof CreateResponses503.Type> - | OpenRouterClientError<"CreateResponses524", typeof CreateResponses524.Type> - | OpenRouterClientError<"CreateResponses529", typeof CreateResponses529.Type> + | OpenRouterClientError<"List400", typeof List400.Type> + | OpenRouterClientError<"List401", typeof List401.Type> + | OpenRouterClientError<"List429", typeof List429.Type> + | OpenRouterClientError<"List500", typeof List500.Type> > /** - * Creates a streaming or non-streaming response using OpenResponses API format + * Create a new API key for the authenticated user. The plaintext `key` is returned only in this response. Treat it as a write-only, sensitive value; it cannot be retrieved later. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "createResponsesSse": ( - options: { readonly payload: typeof CreateResponsesRequestJson.Encoded } - ) => Stream.Stream< - { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateResponses200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - typeof CreateResponses200Sse.DecodingServices + readonly "createKeys": ( + options: { + readonly params?: typeof CreateKeysParams.Encoded | undefined + readonly payload: typeof CreateKeysRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"CreateKeys400", typeof CreateKeys400.Type> + | OpenRouterClientError<"CreateKeys401", typeof CreateKeys401.Type> + | OpenRouterClientError<"CreateKeys403", typeof CreateKeys403.Type> + | OpenRouterClientError<"CreateKeys429", typeof CreateKeys429.Type> + | OpenRouterClientError<"CreateKeys500", typeof CreateKeys500.Type> + > + /** + * Get a single API key by hash. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "getKey": ( + hash: string, + options: + | { readonly params?: typeof GetKeyParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetKey401", typeof GetKey401.Type> + | OpenRouterClientError<"GetKey404", typeof GetKey404.Type> + | OpenRouterClientError<"GetKey429", typeof GetKey429.Type> + | OpenRouterClientError<"GetKey500", typeof GetKey500.Type> + > + /** + * Delete an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteKeys": ( + hash: string, + options: + | { readonly params?: typeof DeleteKeysParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"DeleteKeys401", typeof DeleteKeys401.Type> + | OpenRouterClientError<"DeleteKeys404", typeof DeleteKeys404.Type> + | OpenRouterClientError<"DeleteKeys429", typeof DeleteKeys429.Type> + | OpenRouterClientError<"DeleteKeys500", typeof DeleteKeys500.Type> + > + /** + * Update an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "updateKeys": ( + hash: string, + options: { + readonly params?: typeof UpdateKeysParams.Encoded | undefined + readonly payload: typeof UpdateKeysRequestJson.Encoded + readonly config?: Config | undefined + } + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"UpdateKeys400", typeof UpdateKeys400.Type> + | OpenRouterClientError<"UpdateKeys401", typeof UpdateKeys401.Type> + | OpenRouterClientError<"UpdateKeys404", typeof UpdateKeys404.Type> + | OpenRouterClientError<"UpdateKeys429", typeof UpdateKeys429.Type> + | OpenRouterClientError<"UpdateKeys500", typeof UpdateKeys500.Type> > /** * Creates a message using the Anthropic Messages API format. Supports text, images, PDFs, tools, and extended thinking. */ readonly "createMessages": ( - options: { readonly payload: typeof CreateMessagesRequestJson.Encoded; readonly config?: Config | undefined } + options: { + readonly params?: typeof CreateMessagesParams.Encoded | undefined + readonly payload: typeof CreateMessagesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< WithOptionalResponse, | HttpClientError.HttpClientError @@ -9462,542 +35613,693 @@ export interface OpenRouterClient { * Creates a message using the Anthropic Messages API format. Supports text, images, PDFs, tools, and extended thinking. */ readonly "createMessagesSse": ( - options: { readonly payload: typeof CreateMessagesRequestJson.Encoded } + options: { + readonly params?: typeof CreateMessagesParams.Encoded | undefined + readonly payload: typeof CreateMessagesRequestJson.Encoded + } ) => Stream.Stream< { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateMessages200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof CreateMessages200Sse.DecodingServices > /** - * Returns user activity data grouped by endpoint for the last 30 (completed) UTC days. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Returns full details for a single model identified by its author and slug (e.g. openai/gpt-4). Supports variant suffixes (e.g. openai/gpt-4:free) and resolves known slug aliases. */ - readonly "getUserActivity": ( - options: { - readonly params?: typeof GetUserActivityParams.Encoded | undefined - readonly config?: Config | undefined - } | undefined + readonly "getModel": ( + author: string, + slug: string, + options: + | { readonly params?: typeof GetModelParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetUserActivity400", typeof GetUserActivity400.Type> - | OpenRouterClientError<"GetUserActivity401", typeof GetUserActivity401.Type> - | OpenRouterClientError<"GetUserActivity403", typeof GetUserActivity403.Type> - | OpenRouterClientError<"GetUserActivity500", typeof GetUserActivity500.Type> + | OpenRouterClientError<"GetModel403", typeof GetModel403.Type> + | OpenRouterClientError<"GetModel404", typeof GetModel404.Type> + | OpenRouterClientError<"GetModel500", typeof GetModel500.Type> > /** - * Get total credits purchased and used for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all models and their properties */ - readonly "getCredits": ( - options: { readonly config?: Config | undefined } | undefined + readonly "getModels": ( + options: + | { readonly params?: typeof GetModelsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetCredits401", typeof GetCredits401.Type> - | OpenRouterClientError<"GetCredits403", typeof GetCredits403.Type> - | OpenRouterClientError<"GetCredits500", typeof GetCredits500.Type> + | OpenRouterClientError<"GetModels400", typeof GetModels400.Type> + | OpenRouterClientError<"GetModels403", typeof GetModels403.Type> + | OpenRouterClientError<"GetModels500", typeof GetModels500.Type> > /** - * Create a Coinbase charge for crypto payment + * List all endpoints for a model */ - readonly "createCoinbaseCharge": ( - options: { readonly payload: typeof CreateCoinbaseChargeRequestJson.Encoded; readonly config?: Config | undefined } + readonly "listEndpoints": ( + author: string, + slug: string, + options: + | { readonly params?: typeof ListEndpointsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateCoinbaseCharge400", typeof CreateCoinbaseCharge400.Type> - | OpenRouterClientError<"CreateCoinbaseCharge401", typeof CreateCoinbaseCharge401.Type> - | OpenRouterClientError<"CreateCoinbaseCharge429", typeof CreateCoinbaseCharge429.Type> - | OpenRouterClientError<"CreateCoinbaseCharge500", typeof CreateCoinbaseCharge500.Type> + | OpenRouterClientError<"ListEndpoints403", typeof ListEndpoints403.Type> + | OpenRouterClientError<"ListEndpoints404", typeof ListEndpoints404.Type> + | OpenRouterClientError<"ListEndpoints500", typeof ListEndpoints500.Type> > /** - * Submits an embedding request to the embeddings router + * Get total count of available models */ - readonly "createEmbeddings": ( - options: { readonly payload: typeof CreateEmbeddingsRequestJson.Encoded; readonly config?: Config | undefined } + readonly "listModelsCount": ( + options: { + readonly params?: typeof ListModelsCountParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateEmbeddings400", typeof CreateEmbeddings400.Type> - | OpenRouterClientError<"CreateEmbeddings401", typeof CreateEmbeddings401.Type> - | OpenRouterClientError<"CreateEmbeddings402", typeof CreateEmbeddings402.Type> - | OpenRouterClientError<"CreateEmbeddings404", typeof CreateEmbeddings404.Type> - | OpenRouterClientError<"CreateEmbeddings429", typeof CreateEmbeddings429.Type> - | OpenRouterClientError<"CreateEmbeddings500", typeof CreateEmbeddings500.Type> - | OpenRouterClientError<"CreateEmbeddings502", typeof CreateEmbeddings502.Type> - | OpenRouterClientError<"CreateEmbeddings503", typeof CreateEmbeddings503.Type> - | OpenRouterClientError<"CreateEmbeddings524", typeof CreateEmbeddings524.Type> - | OpenRouterClientError<"CreateEmbeddings529", typeof CreateEmbeddings529.Type> + | OpenRouterClientError<"ListModelsCount400", typeof ListModelsCount400.Type> + | OpenRouterClientError<"ListModelsCount403", typeof ListModelsCount403.Type> + | OpenRouterClientError<"ListModelsCount500", typeof ListModelsCount500.Type> > /** - * Submits an embedding request to the embeddings router + * List models filtered by user provider preferences, [privacy settings](https://openrouter.ai/docs/guides/privacy/provider-logging), and [guardrails](https://openrouter.ai/docs/guides/features/guardrails). If requesting through `eu.openrouter.ai/api/v1/...` the results will be filtered to models that satisfy [EU in-region routing](https://openrouter.ai/docs/guides/privacy/provider-logging#enterprise-eu-in-region-routing). */ - readonly "createEmbeddingsSse": ( - options: { readonly payload: typeof CreateEmbeddingsRequestJson.Encoded } - ) => Stream.Stream< - { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateEmbeddings200Sse.Type }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - typeof CreateEmbeddings200Sse.DecodingServices + readonly "listModelsUser": ( + options: + | { readonly params?: typeof ListModelsUserParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"ListModelsUser401", typeof ListModelsUser401.Type> + | OpenRouterClientError<"ListModelsUser403", typeof ListModelsUser403.Type> + | OpenRouterClientError<"ListModelsUser404", typeof ListModelsUser404.Type> + | OpenRouterClientError<"ListModelsUser500", typeof ListModelsUser500.Type> > /** - * Returns a list of all available embeddings models and their properties + * List the observability destinations configured for the authenticated entity's default workspace. Use the `workspace_id` query parameter to scope the result to a different workspace. Only destinations with stable release status are surfaced — destinations of other types are excluded. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listEmbeddingsModels": ( - options: { readonly config?: Config | undefined } | undefined + readonly "listObservabilityDestinations": ( + options: { + readonly params?: typeof ListObservabilityDestinationsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListEmbeddingsModels400", typeof ListEmbeddingsModels400.Type> - | OpenRouterClientError<"ListEmbeddingsModels500", typeof ListEmbeddingsModels500.Type> + | OpenRouterClientError<"ListObservabilityDestinations400", typeof ListObservabilityDestinations400.Type> + | OpenRouterClientError<"ListObservabilityDestinations401", typeof ListObservabilityDestinations401.Type> + | OpenRouterClientError<"ListObservabilityDestinations500", typeof ListObservabilityDestinations500.Type> > /** - * Get request & usage metadata for a generation + * Create a new observability destination. A maximum of 5 destinations per type is allowed. Defaults to the authenticated entity's default workspace; use the `workspace_id` body field to scope to a different workspace. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "getGeneration": ( - options: { readonly params: typeof GetGenerationParams.Encoded; readonly config?: Config | undefined } + readonly "createObservabilityDestination": ( + options: { + readonly params?: typeof CreateObservabilityDestinationParams.Encoded | undefined + readonly payload: typeof CreateObservabilityDestinationRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetGeneration401", typeof GetGeneration401.Type> - | OpenRouterClientError<"GetGeneration402", typeof GetGeneration402.Type> - | OpenRouterClientError<"GetGeneration404", typeof GetGeneration404.Type> - | OpenRouterClientError<"GetGeneration429", typeof GetGeneration429.Type> - | OpenRouterClientError<"GetGeneration500", typeof GetGeneration500.Type> - | OpenRouterClientError<"GetGeneration502", typeof GetGeneration502.Type> - | OpenRouterClientError<"GetGeneration524", typeof GetGeneration524.Type> - | OpenRouterClientError<"GetGeneration529", typeof GetGeneration529.Type> + | OpenRouterClientError<"CreateObservabilityDestination400", typeof CreateObservabilityDestination400.Type> + | OpenRouterClientError<"CreateObservabilityDestination401", typeof CreateObservabilityDestination401.Type> + | OpenRouterClientError<"CreateObservabilityDestination403", typeof CreateObservabilityDestination403.Type> + | OpenRouterClientError<"CreateObservabilityDestination409", typeof CreateObservabilityDestination409.Type> + | OpenRouterClientError<"CreateObservabilityDestination500", typeof CreateObservabilityDestination500.Type> > /** - * Get total count of available models + * Fetch a single observability destination by its UUID. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listModelsCount": ( - options: { readonly config?: Config | undefined } | undefined + readonly "getObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof GetObservabilityDestinationParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListModelsCount500", typeof ListModelsCount500.Type> + | OpenRouterClientError<"GetObservabilityDestination401", typeof GetObservabilityDestination401.Type> + | OpenRouterClientError<"GetObservabilityDestination404", typeof GetObservabilityDestination404.Type> + | OpenRouterClientError<"GetObservabilityDestination500", typeof GetObservabilityDestination500.Type> > /** - * List all models and their properties + * Delete an existing observability destination. This performs a soft delete. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "getModels": ( - options: - | { readonly params?: typeof GetModelsParams.Encoded | undefined; readonly config?: Config | undefined } - | undefined + readonly "deleteObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof DeleteObservabilityDestinationParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetModels400", typeof GetModels400.Type> - | OpenRouterClientError<"GetModels500", typeof GetModels500.Type> + | OpenRouterClientError<"DeleteObservabilityDestination401", typeof DeleteObservabilityDestination401.Type> + | OpenRouterClientError<"DeleteObservabilityDestination404", typeof DeleteObservabilityDestination404.Type> + | OpenRouterClientError<"DeleteObservabilityDestination500", typeof DeleteObservabilityDestination500.Type> > /** - * List models filtered by user provider preferences, [privacy settings](https://openrouter.ai/docs/guides/privacy/logging), and [guardrails](https://openrouter.ai/docs/guides/features/guardrails). If requesting through `eu.openrouter.ai/api/v1/...` the results will be filtered to models that satisfy [EU in-region routing](https://openrouter.ai/docs/guides/privacy/logging#enterprise-eu-in-region-routing). + * Update an existing observability destination. Only the fields provided in the request body are updated. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listModelsUser": ( - options: { readonly config?: Config | undefined } | undefined + readonly "updateObservabilityDestination": ( + id: string, + options: { + readonly params?: typeof UpdateObservabilityDestinationParams.Encoded | undefined + readonly payload: typeof UpdateObservabilityDestinationRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListModelsUser401", typeof ListModelsUser401.Type> - | OpenRouterClientError<"ListModelsUser404", typeof ListModelsUser404.Type> - | OpenRouterClientError<"ListModelsUser500", typeof ListModelsUser500.Type> + | OpenRouterClientError<"UpdateObservabilityDestination400", typeof UpdateObservabilityDestination400.Type> + | OpenRouterClientError<"UpdateObservabilityDestination401", typeof UpdateObservabilityDestination401.Type> + | OpenRouterClientError<"UpdateObservabilityDestination404", typeof UpdateObservabilityDestination404.Type> + | OpenRouterClientError<"UpdateObservabilityDestination409", typeof UpdateObservabilityDestination409.Type> + | OpenRouterClientError<"UpdateObservabilityDestination500", typeof UpdateObservabilityDestination500.Type> > /** - * List all endpoints for a model + * List all members of the organization associated with the authenticated management key. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listEndpoints": ( - author: string, - slug: string, - options: { readonly config?: Config | undefined } | undefined + readonly "listOrganizationMembers": ( + options: { + readonly params?: typeof ListOrganizationMembersParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListEndpoints404", typeof ListEndpoints404.Type> - | OpenRouterClientError<"ListEndpoints500", typeof ListEndpoints500.Type> + | OpenRouterClientError<"ListOrganizationMembers401", typeof ListOrganizationMembers401.Type> + | OpenRouterClientError<"ListOrganizationMembers404", typeof ListOrganizationMembers404.Type> + | OpenRouterClientError<"ListOrganizationMembers500", typeof ListOrganizationMembers500.Type> > /** - * Preview the impact of ZDR on the available endpoints + * Lists all presets for the authenticated user, ordered by most recently updated first. */ - readonly "listEndpointsZdr": ( - options: { readonly config?: Config | undefined } | undefined + readonly "listPresets": ( + options: + | { readonly params?: typeof ListPresetsParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListEndpointsZdr500", typeof ListEndpointsZdr500.Type> + | OpenRouterClientError<"ListPresets400", typeof ListPresets400.Type> + | OpenRouterClientError<"ListPresets401", typeof ListPresets401.Type> + | OpenRouterClientError<"ListPresets500", typeof ListPresets500.Type> > /** - * List all providers + * Retrieves a preset by its slug with its currently designated version inline. */ - readonly "listProviders": ( - options: { readonly config?: Config | undefined } | undefined + readonly "getPreset": ( + slug: string, + options: + | { readonly params?: typeof GetPresetParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListProviders500", typeof ListProviders500.Type> + | OpenRouterClientError<"GetPreset400", typeof GetPreset400.Type> + | OpenRouterClientError<"GetPreset401", typeof GetPreset401.Type> + | OpenRouterClientError<"GetPreset404", typeof GetPreset404.Type> + | OpenRouterClientError<"GetPreset500", typeof GetPreset500.Type> > /** - * List all API keys for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a preset (or a new version of an existing one) from an inference request body. Only fields that overlap with the preset config are persisted; other fields (e.g. `messages`, `stream`, `prompt`) are silently ignored. */ - readonly "list": ( - options: - | { readonly params?: typeof ListParams.Encoded | undefined; readonly config?: Config | undefined } - | undefined + readonly "createPresetsChatCompletions": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsChatCompletionsParams.Encoded | undefined + readonly payload: typeof CreatePresetsChatCompletionsRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"List401", typeof List401.Type> - | OpenRouterClientError<"List429", typeof List429.Type> - | OpenRouterClientError<"List500", typeof List500.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions400", typeof CreatePresetsChatCompletions400.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions401", typeof CreatePresetsChatCompletions401.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions403", typeof CreatePresetsChatCompletions403.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions404", typeof CreatePresetsChatCompletions404.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions409", typeof CreatePresetsChatCompletions409.Type> + | OpenRouterClientError<"CreatePresetsChatCompletions500", typeof CreatePresetsChatCompletions500.Type> > /** - * Create a new API key for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a preset (or a new version of an existing one) from an inference request body. Only fields that overlap with the preset config are persisted; other fields (e.g. `messages`, `stream`, `prompt`) are silently ignored. */ - readonly "createKeys": ( - options: { readonly payload: typeof CreateKeysRequestJson.Encoded; readonly config?: Config | undefined } + readonly "createPresetsMessages": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsMessagesParams.Encoded | undefined + readonly payload: typeof CreatePresetsMessagesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateKeys400", typeof CreateKeys400.Type> - | OpenRouterClientError<"CreateKeys401", typeof CreateKeys401.Type> - | OpenRouterClientError<"CreateKeys429", typeof CreateKeys429.Type> - | OpenRouterClientError<"CreateKeys500", typeof CreateKeys500.Type> + | OpenRouterClientError<"CreatePresetsMessages400", typeof CreatePresetsMessages400.Type> + | OpenRouterClientError<"CreatePresetsMessages401", typeof CreatePresetsMessages401.Type> + | OpenRouterClientError<"CreatePresetsMessages403", typeof CreatePresetsMessages403.Type> + | OpenRouterClientError<"CreatePresetsMessages404", typeof CreatePresetsMessages404.Type> + | OpenRouterClientError<"CreatePresetsMessages409", typeof CreatePresetsMessages409.Type> + | OpenRouterClientError<"CreatePresetsMessages500", typeof CreatePresetsMessages500.Type> > /** - * Get a single API key by hash. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a preset (or a new version of an existing one) from an inference request body. Only fields that overlap with the preset config are persisted; other fields (e.g. `messages`, `stream`, `prompt`) are silently ignored. */ - readonly "getKey": ( - hash: string, - options: { readonly config?: Config | undefined } | undefined + readonly "createPresetsResponses": ( + slug: string, + options: { + readonly params?: typeof CreatePresetsResponsesParams.Encoded | undefined + readonly payload: typeof CreatePresetsResponsesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetKey401", typeof GetKey401.Type> - | OpenRouterClientError<"GetKey404", typeof GetKey404.Type> - | OpenRouterClientError<"GetKey429", typeof GetKey429.Type> - | OpenRouterClientError<"GetKey500", typeof GetKey500.Type> + | OpenRouterClientError<"CreatePresetsResponses400", typeof CreatePresetsResponses400.Type> + | OpenRouterClientError<"CreatePresetsResponses401", typeof CreatePresetsResponses401.Type> + | OpenRouterClientError<"CreatePresetsResponses403", typeof CreatePresetsResponses403.Type> + | OpenRouterClientError<"CreatePresetsResponses404", typeof CreatePresetsResponses404.Type> + | OpenRouterClientError<"CreatePresetsResponses409", typeof CreatePresetsResponses409.Type> + | OpenRouterClientError<"CreatePresetsResponses500", typeof CreatePresetsResponses500.Type> > /** - * Delete an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Lists all versions of a preset, ordered by version number ascending (oldest first). */ - readonly "deleteKeys": ( - hash: string, - options: { readonly config?: Config | undefined } | undefined + readonly "listPresetVersions": ( + slug: string, + options: { + readonly params?: typeof ListPresetVersionsParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"DeleteKeys401", typeof DeleteKeys401.Type> - | OpenRouterClientError<"DeleteKeys404", typeof DeleteKeys404.Type> - | OpenRouterClientError<"DeleteKeys429", typeof DeleteKeys429.Type> - | OpenRouterClientError<"DeleteKeys500", typeof DeleteKeys500.Type> + | OpenRouterClientError<"ListPresetVersions400", typeof ListPresetVersions400.Type> + | OpenRouterClientError<"ListPresetVersions401", typeof ListPresetVersions401.Type> + | OpenRouterClientError<"ListPresetVersions404", typeof ListPresetVersions404.Type> + | OpenRouterClientError<"ListPresetVersions500", typeof ListPresetVersions500.Type> > /** - * Update an existing API key. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Retrieves a specific version of a preset by its slug and version number. */ - readonly "updateKeys": ( - hash: string, - options: { readonly payload: typeof UpdateKeysRequestJson.Encoded; readonly config?: Config | undefined } + readonly "getPresetVersion": ( + slug: string, + version: string, + options: { + readonly params?: typeof GetPresetVersionParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"UpdateKeys400", typeof UpdateKeys400.Type> - | OpenRouterClientError<"UpdateKeys401", typeof UpdateKeys401.Type> - | OpenRouterClientError<"UpdateKeys404", typeof UpdateKeys404.Type> - | OpenRouterClientError<"UpdateKeys429", typeof UpdateKeys429.Type> - | OpenRouterClientError<"UpdateKeys500", typeof UpdateKeys500.Type> + | OpenRouterClientError<"GetPresetVersion400", typeof GetPresetVersion400.Type> + | OpenRouterClientError<"GetPresetVersion401", typeof GetPresetVersion401.Type> + | OpenRouterClientError<"GetPresetVersion404", typeof GetPresetVersion404.Type> + | OpenRouterClientError<"GetPresetVersion500", typeof GetPresetVersion500.Type> > /** - * List all guardrails for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all providers */ - readonly "listGuardrails": ( + readonly "listProviders": ( options: - | { readonly params?: typeof ListGuardrailsParams.Encoded | undefined; readonly config?: Config | undefined } + | { readonly params?: typeof ListProvidersParams.Encoded | undefined; readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListGuardrails401", typeof ListGuardrails401.Type> - | OpenRouterClientError<"ListGuardrails500", typeof ListGuardrails500.Type> + | OpenRouterClientError<"ListProviders500", typeof ListProviders500.Type> > /** - * Create a new guardrail for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Submits a rerank request to the rerank router */ - readonly "createGuardrail": ( - options: { readonly payload: typeof CreateGuardrailRequestJson.Encoded; readonly config?: Config | undefined } + readonly "createRerank": ( + options: { + readonly params?: typeof CreateRerankParams.Encoded | undefined + readonly payload: typeof CreateRerankRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateGuardrail400", typeof CreateGuardrail400.Type> - | OpenRouterClientError<"CreateGuardrail401", typeof CreateGuardrail401.Type> - | OpenRouterClientError<"CreateGuardrail500", typeof CreateGuardrail500.Type> + | OpenRouterClientError<"CreateRerank400", typeof CreateRerank400.Type> + | OpenRouterClientError<"CreateRerank401", typeof CreateRerank401.Type> + | OpenRouterClientError<"CreateRerank402", typeof CreateRerank402.Type> + | OpenRouterClientError<"CreateRerank404", typeof CreateRerank404.Type> + | OpenRouterClientError<"CreateRerank429", typeof CreateRerank429.Type> + | OpenRouterClientError<"CreateRerank500", typeof CreateRerank500.Type> + | OpenRouterClientError<"CreateRerank502", typeof CreateRerank502.Type> + | OpenRouterClientError<"CreateRerank503", typeof CreateRerank503.Type> + | OpenRouterClientError<"CreateRerank524", typeof CreateRerank524.Type> + | OpenRouterClientError<"CreateRerank529", typeof CreateRerank529.Type> > /** - * Get a single guardrail by ID. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Submits a rerank request to the rerank router */ - readonly "getGuardrail": ( - id: string, - options: { readonly config?: Config | undefined } | undefined + readonly "createRerankSse": ( + options: { + readonly params?: typeof CreateRerankParams.Encoded | undefined + readonly payload: typeof CreateRerankRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateRerank200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateRerank200Sse.DecodingServices + > + /** + * Creates a streaming or non-streaming response using OpenResponses API format + */ + readonly "createResponses": ( + options: { + readonly params?: typeof CreateResponsesParams.Encoded | undefined + readonly payload: typeof CreateResponsesRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetGuardrail401", typeof GetGuardrail401.Type> - | OpenRouterClientError<"GetGuardrail404", typeof GetGuardrail404.Type> - | OpenRouterClientError<"GetGuardrail500", typeof GetGuardrail500.Type> + | OpenRouterClientError<"CreateResponses400", typeof CreateResponses400.Type> + | OpenRouterClientError<"CreateResponses401", typeof CreateResponses401.Type> + | OpenRouterClientError<"CreateResponses402", typeof CreateResponses402.Type> + | OpenRouterClientError<"CreateResponses403", typeof CreateResponses403.Type> + | OpenRouterClientError<"CreateResponses404", typeof CreateResponses404.Type> + | OpenRouterClientError<"CreateResponses408", typeof CreateResponses408.Type> + | OpenRouterClientError<"CreateResponses413", typeof CreateResponses413.Type> + | OpenRouterClientError<"CreateResponses422", typeof CreateResponses422.Type> + | OpenRouterClientError<"CreateResponses429", typeof CreateResponses429.Type> + | OpenRouterClientError<"CreateResponses500", typeof CreateResponses500.Type> + | OpenRouterClientError<"CreateResponses502", typeof CreateResponses502.Type> + | OpenRouterClientError<"CreateResponses503", typeof CreateResponses503.Type> + | OpenRouterClientError<"CreateResponses524", typeof CreateResponses524.Type> + | OpenRouterClientError<"CreateResponses529", typeof CreateResponses529.Type> > /** - * Delete an existing guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Creates a streaming or non-streaming response using OpenResponses API format */ - readonly "deleteGuardrail": ( - id: string, - options: { readonly config?: Config | undefined } | undefined + readonly "createResponsesSse": ( + options: { + readonly params?: typeof CreateResponsesParams.Encoded | undefined + readonly payload: typeof CreateResponsesRequestJson.Encoded + } + ) => Stream.Stream< + { readonly event: string; readonly id: string | undefined; readonly data: typeof CreateResponses200Sse.Type }, + HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, + typeof CreateResponses200Sse.DecodingServices + > + /** + * Submits a video generation request and returns a polling URL to check status + */ + readonly "createVideos": ( + options: { + readonly params?: typeof CreateVideosParams.Encoded | undefined + readonly payload: typeof CreateVideosRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"DeleteGuardrail401", typeof DeleteGuardrail401.Type> - | OpenRouterClientError<"DeleteGuardrail404", typeof DeleteGuardrail404.Type> - | OpenRouterClientError<"DeleteGuardrail500", typeof DeleteGuardrail500.Type> + | OpenRouterClientError<"CreateVideos400", typeof CreateVideos400.Type> + | OpenRouterClientError<"CreateVideos401", typeof CreateVideos401.Type> + | OpenRouterClientError<"CreateVideos402", typeof CreateVideos402.Type> + | OpenRouterClientError<"CreateVideos404", typeof CreateVideos404.Type> + | OpenRouterClientError<"CreateVideos429", typeof CreateVideos429.Type> + | OpenRouterClientError<"CreateVideos500", typeof CreateVideos500.Type> > /** - * Update an existing guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Returns job status and content URLs when completed */ - readonly "updateGuardrail": ( - id: string, - options: { readonly payload: typeof UpdateGuardrailRequestJson.Encoded; readonly config?: Config | undefined } + readonly "getVideos": ( + jobId: string, + options: + | { readonly params?: typeof GetVideosParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"UpdateGuardrail400", typeof UpdateGuardrail400.Type> - | OpenRouterClientError<"UpdateGuardrail401", typeof UpdateGuardrail401.Type> - | OpenRouterClientError<"UpdateGuardrail404", typeof UpdateGuardrail404.Type> - | OpenRouterClientError<"UpdateGuardrail500", typeof UpdateGuardrail500.Type> + | OpenRouterClientError<"GetVideos401", typeof GetVideos401.Type> + | OpenRouterClientError<"GetVideos404", typeof GetVideos404.Type> + | OpenRouterClientError<"GetVideos500", typeof GetVideos500.Type> > /** - * List all API key guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Streams the generated video content from the upstream provider */ - readonly "listKeyAssignments": ( + readonly "listVideosContent": ( + jobId: string, options: { - readonly params?: typeof ListKeyAssignmentsParams.Encoded | undefined + readonly params?: typeof ListVideosContentParams.Encoded | undefined readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListKeyAssignments401", typeof ListKeyAssignments401.Type> - | OpenRouterClientError<"ListKeyAssignments500", typeof ListKeyAssignments500.Type> + | OpenRouterClientError<"ListVideosContent400", typeof ListVideosContent400.Type> + | OpenRouterClientError<"ListVideosContent401", typeof ListVideosContent401.Type> + | OpenRouterClientError<"ListVideosContent404", typeof ListVideosContent404.Type> + | OpenRouterClientError<"ListVideosContent500", typeof ListVideosContent500.Type> + | OpenRouterClientError<"ListVideosContent502", typeof ListVideosContent502.Type> > /** - * List all organization member guardrail assignments for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Returns a list of all available video generation models and their properties */ - readonly "listMemberAssignments": ( + readonly "listVideosModels": ( options: { - readonly params?: typeof ListMemberAssignmentsParams.Encoded | undefined + readonly params?: typeof ListVideosModelsParams.Encoded | undefined readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListMemberAssignments401", typeof ListMemberAssignments401.Type> - | OpenRouterClientError<"ListMemberAssignments500", typeof ListMemberAssignments500.Type> + | OpenRouterClientError<"ListVideosModels400", typeof ListVideosModels400.Type> + | OpenRouterClientError<"ListVideosModels500", typeof ListVideosModels500.Type> > /** - * List all API key assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all workspaces for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listGuardrailKeyAssignments": ( - id: string, - options: { - readonly params?: typeof ListGuardrailKeyAssignmentsParams.Encoded | undefined - readonly config?: Config | undefined - } | undefined + readonly "listWorkspaces": ( + options: + | { readonly params?: typeof ListWorkspacesParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListGuardrailKeyAssignments401", typeof ListGuardrailKeyAssignments401.Type> - | OpenRouterClientError<"ListGuardrailKeyAssignments404", typeof ListGuardrailKeyAssignments404.Type> - | OpenRouterClientError<"ListGuardrailKeyAssignments500", typeof ListGuardrailKeyAssignments500.Type> + | OpenRouterClientError<"ListWorkspaces401", typeof ListWorkspaces401.Type> + | OpenRouterClientError<"ListWorkspaces500", typeof ListWorkspaces500.Type> > /** - * Assign multiple API keys to a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Create a new workspace for the authenticated user. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkAssignKeysToGuardrail": ( - id: string, + readonly "createWorkspace": ( options: { - readonly payload: typeof BulkAssignKeysToGuardrailRequestJson.Encoded + readonly params?: typeof CreateWorkspaceParams.Encoded | undefined + readonly payload: typeof CreateWorkspaceRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkAssignKeysToGuardrail400", typeof BulkAssignKeysToGuardrail400.Type> - | OpenRouterClientError<"BulkAssignKeysToGuardrail401", typeof BulkAssignKeysToGuardrail401.Type> - | OpenRouterClientError<"BulkAssignKeysToGuardrail404", typeof BulkAssignKeysToGuardrail404.Type> - | OpenRouterClientError<"BulkAssignKeysToGuardrail500", typeof BulkAssignKeysToGuardrail500.Type> + | OpenRouterClientError<"CreateWorkspace400", typeof CreateWorkspace400.Type> + | OpenRouterClientError<"CreateWorkspace401", typeof CreateWorkspace401.Type> + | OpenRouterClientError<"CreateWorkspace403", typeof CreateWorkspace403.Type> + | OpenRouterClientError<"CreateWorkspace500", typeof CreateWorkspace500.Type> > /** - * List all organization member assignments for a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Get a single workspace by ID or slug. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "listGuardrailMemberAssignments": ( + readonly "getWorkspace": ( + id: string, + options: + | { readonly params?: typeof GetWorkspaceParams.Encoded | undefined; readonly config?: Config | undefined } + | undefined + ) => Effect.Effect< + WithOptionalResponse, + | HttpClientError.HttpClientError + | SchemaError + | OpenRouterClientError<"GetWorkspace401", typeof GetWorkspace401.Type> + | OpenRouterClientError<"GetWorkspace404", typeof GetWorkspace404.Type> + | OpenRouterClientError<"GetWorkspace500", typeof GetWorkspace500.Type> + > + /** + * Delete an existing workspace. The default workspace cannot be deleted. Workspaces with active API keys cannot be deleted; remove the keys first. [Management key](/docs/guides/overview/auth/management-api-keys) required. + */ + readonly "deleteWorkspace": ( id: string, options: { - readonly params?: typeof ListGuardrailMemberAssignmentsParams.Encoded | undefined + readonly params?: typeof DeleteWorkspaceParams.Encoded | undefined readonly config?: Config | undefined } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ListGuardrailMemberAssignments401", typeof ListGuardrailMemberAssignments401.Type> - | OpenRouterClientError<"ListGuardrailMemberAssignments404", typeof ListGuardrailMemberAssignments404.Type> - | OpenRouterClientError<"ListGuardrailMemberAssignments500", typeof ListGuardrailMemberAssignments500.Type> + | OpenRouterClientError<"DeleteWorkspace400", typeof DeleteWorkspace400.Type> + | OpenRouterClientError<"DeleteWorkspace401", typeof DeleteWorkspace401.Type> + | OpenRouterClientError<"DeleteWorkspace403", typeof DeleteWorkspace403.Type> + | OpenRouterClientError<"DeleteWorkspace404", typeof DeleteWorkspace404.Type> + | OpenRouterClientError<"DeleteWorkspace500", typeof DeleteWorkspace500.Type> > /** - * Assign multiple organization members to a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Update an existing workspace by ID or slug. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkAssignMembersToGuardrail": ( + readonly "updateWorkspace": ( id: string, options: { - readonly payload: typeof BulkAssignMembersToGuardrailRequestJson.Encoded + readonly params?: typeof UpdateWorkspaceParams.Encoded | undefined + readonly payload: typeof UpdateWorkspaceRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkAssignMembersToGuardrail400", typeof BulkAssignMembersToGuardrail400.Type> - | OpenRouterClientError<"BulkAssignMembersToGuardrail401", typeof BulkAssignMembersToGuardrail401.Type> - | OpenRouterClientError<"BulkAssignMembersToGuardrail404", typeof BulkAssignMembersToGuardrail404.Type> - | OpenRouterClientError<"BulkAssignMembersToGuardrail500", typeof BulkAssignMembersToGuardrail500.Type> + | OpenRouterClientError<"UpdateWorkspace400", typeof UpdateWorkspace400.Type> + | OpenRouterClientError<"UpdateWorkspace401", typeof UpdateWorkspace401.Type> + | OpenRouterClientError<"UpdateWorkspace403", typeof UpdateWorkspace403.Type> + | OpenRouterClientError<"UpdateWorkspace404", typeof UpdateWorkspace404.Type> + | OpenRouterClientError<"UpdateWorkspace500", typeof UpdateWorkspace500.Type> > /** - * Unassign multiple API keys from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * List all budgets configured for a workspace. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkUnassignKeysFromGuardrail": ( + readonly "listWorkspaceBudgets": ( id: string, options: { - readonly payload: typeof BulkUnassignKeysFromGuardrailRequestJson.Encoded + readonly params?: typeof ListWorkspaceBudgetsParams.Encoded | undefined readonly config?: Config | undefined - } + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail400", typeof BulkUnassignKeysFromGuardrail400.Type> - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail401", typeof BulkUnassignKeysFromGuardrail401.Type> - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail404", typeof BulkUnassignKeysFromGuardrail404.Type> - | OpenRouterClientError<"BulkUnassignKeysFromGuardrail500", typeof BulkUnassignKeysFromGuardrail500.Type> + | OpenRouterClientError<"ListWorkspaceBudgets401", typeof ListWorkspaceBudgets401.Type> + | OpenRouterClientError<"ListWorkspaceBudgets404", typeof ListWorkspaceBudgets404.Type> + | OpenRouterClientError<"ListWorkspaceBudgets500", typeof ListWorkspaceBudgets500.Type> > /** - * Unassign multiple organization members from a specific guardrail. [Management key](/docs/guides/overview/auth/management-api-keys) required. + * Create or update the budget for a given interval. Budget limits must strictly decrease as the interval narrows (lifetime > monthly > weekly > daily). [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "bulkUnassignMembersFromGuardrail": ( + readonly "upsertWorkspaceBudget": ( id: string, + interval: string, options: { - readonly payload: typeof BulkUnassignMembersFromGuardrailRequestJson.Encoded + readonly params?: typeof UpsertWorkspaceBudgetParams.Encoded | undefined + readonly payload: typeof UpsertWorkspaceBudgetRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail400", typeof BulkUnassignMembersFromGuardrail400.Type> - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail401", typeof BulkUnassignMembersFromGuardrail401.Type> - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail404", typeof BulkUnassignMembersFromGuardrail404.Type> - | OpenRouterClientError<"BulkUnassignMembersFromGuardrail500", typeof BulkUnassignMembersFromGuardrail500.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget400", typeof UpsertWorkspaceBudget400.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget401", typeof UpsertWorkspaceBudget401.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget404", typeof UpsertWorkspaceBudget404.Type> + | OpenRouterClientError<"UpsertWorkspaceBudget500", typeof UpsertWorkspaceBudget500.Type> > /** - * Get information on the API key associated with the current authentication session + * Remove the budget for a given interval. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "getCurrentKey": ( - options: { readonly config?: Config | undefined } | undefined + readonly "deleteWorkspaceBudget": ( + id: string, + interval: string, + options: { + readonly params?: typeof DeleteWorkspaceBudgetParams.Encoded | undefined + readonly config?: Config | undefined + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"GetCurrentKey401", typeof GetCurrentKey401.Type> - | OpenRouterClientError<"GetCurrentKey500", typeof GetCurrentKey500.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget401", typeof DeleteWorkspaceBudget401.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget404", typeof DeleteWorkspaceBudget404.Type> + | OpenRouterClientError<"DeleteWorkspaceBudget500", typeof DeleteWorkspaceBudget500.Type> > /** - * Exchange an authorization code from the PKCE flow for a user-controlled API key + * List all members of a workspace. Returns paginated results. For the default workspace, returns all organization members (implicit membership). [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "exchangeAuthCodeForAPIKey": ( + readonly "listWorkspaceMembers": ( + id: string, options: { - readonly payload: typeof ExchangeAuthCodeForAPIKeyRequestJson.Encoded + readonly params?: typeof ListWorkspaceMembersParams.Encoded | undefined readonly config?: Config | undefined - } + } | undefined ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"ExchangeAuthCodeForAPIKey400", typeof ExchangeAuthCodeForAPIKey400.Type> - | OpenRouterClientError<"ExchangeAuthCodeForAPIKey403", typeof ExchangeAuthCodeForAPIKey403.Type> - | OpenRouterClientError<"ExchangeAuthCodeForAPIKey500", typeof ExchangeAuthCodeForAPIKey500.Type> + | OpenRouterClientError<"ListWorkspaceMembers401", typeof ListWorkspaceMembers401.Type> + | OpenRouterClientError<"ListWorkspaceMembers403", typeof ListWorkspaceMembers403.Type> + | OpenRouterClientError<"ListWorkspaceMembers404", typeof ListWorkspaceMembers404.Type> + | OpenRouterClientError<"ListWorkspaceMembers500", typeof ListWorkspaceMembers500.Type> > /** - * Create an authorization code for the PKCE flow to generate a user-controlled API key + * Add multiple organization members to a workspace. Members are assigned the same role they hold in the organization. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "createAuthKeysCode": ( - options: { readonly payload: typeof CreateAuthKeysCodeRequestJson.Encoded; readonly config?: Config | undefined } + readonly "bulkAddWorkspaceMembers": ( + id: string, + options: { + readonly params?: typeof BulkAddWorkspaceMembersParams.Encoded | undefined + readonly payload: typeof BulkAddWorkspaceMembersRequestJson.Encoded + readonly config?: Config | undefined + } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"CreateAuthKeysCode400", typeof CreateAuthKeysCode400.Type> - | OpenRouterClientError<"CreateAuthKeysCode401", typeof CreateAuthKeysCode401.Type> - | OpenRouterClientError<"CreateAuthKeysCode500", typeof CreateAuthKeysCode500.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers400", typeof BulkAddWorkspaceMembers400.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers401", typeof BulkAddWorkspaceMembers401.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers403", typeof BulkAddWorkspaceMembers403.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers404", typeof BulkAddWorkspaceMembers404.Type> + | OpenRouterClientError<"BulkAddWorkspaceMembers500", typeof BulkAddWorkspaceMembers500.Type> > /** - * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. + * Remove multiple members from a workspace. Members with active API keys in the workspace cannot be removed. SCIM-managed members cannot be removed; changes must be made in your identity provider. [Management key](/docs/guides/overview/auth/management-api-keys) required. */ - readonly "sendChatCompletionRequest": ( + readonly "bulkRemoveWorkspaceMembers": ( + id: string, options: { - readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded + readonly params?: typeof BulkRemoveWorkspaceMembersParams.Encoded | undefined + readonly payload: typeof BulkRemoveWorkspaceMembersRequestJson.Encoded readonly config?: Config | undefined } ) => Effect.Effect< - WithOptionalResponse, + WithOptionalResponse, | HttpClientError.HttpClientError | SchemaError - | OpenRouterClientError<"SendChatCompletionRequest400", typeof SendChatCompletionRequest400.Type> - | OpenRouterClientError<"SendChatCompletionRequest401", typeof SendChatCompletionRequest401.Type> - | OpenRouterClientError<"SendChatCompletionRequest429", typeof SendChatCompletionRequest429.Type> - | OpenRouterClientError<"SendChatCompletionRequest500", typeof SendChatCompletionRequest500.Type> - > - /** - * Sends a request for a model response for the given chat conversation. Supports both streaming and non-streaming modes. - */ - readonly "sendChatCompletionRequestSse": ( - options: { readonly payload: typeof SendChatCompletionRequestRequestJson.Encoded } - ) => Stream.Stream< - { - readonly event: string - readonly id: string | undefined - readonly data: typeof SendChatCompletionRequest200Sse.Type - }, - HttpClientError.HttpClientError | SchemaError | Sse.Retry, - typeof SendChatCompletionRequest200Sse.DecodingServices + | OpenRouterClientError<"BulkRemoveWorkspaceMembers400", typeof BulkRemoveWorkspaceMembers400.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers401", typeof BulkRemoveWorkspaceMembers401.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers403", typeof BulkRemoveWorkspaceMembers403.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers404", typeof BulkRemoveWorkspaceMembers404.Type> + | OpenRouterClientError<"BulkRemoveWorkspaceMembers500", typeof BulkRemoveWorkspaceMembers500.Type> > } diff --git a/packages/ai/openrouter/src/OpenRouterClient.ts b/packages/ai/openrouter/src/OpenRouterClient.ts index ec461a1eb..8a2e66429 100644 --- a/packages/ai/openrouter/src/OpenRouterClient.ts +++ b/packages/ai/openrouter/src/OpenRouterClient.ts @@ -37,21 +37,21 @@ import { OpenRouterConfig } from "./OpenRouterConfig.ts" * Provides methods for interacting with OpenRouter's Chat Completions API, * including both synchronous and streaming message creation. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { readonly client: Generated.OpenRouterClient readonly createChatCompletion: ( - options: typeof Generated.ChatGenerationParams.Encoded + options: typeof Generated.ChatRequest.Encoded ) => Effect.Effect< [body: typeof Generated.SendChatCompletionRequest200.Type, response: HttpClientResponse.HttpClientResponse], AiError.AiError > readonly createChatCompletionStream: ( - options: Omit + options: Omit ) => Effect.Effect< [ response: HttpClientResponse.HttpClientResponse, @@ -72,7 +72,7 @@ export interface Service { * @category models * @since 4.0.0 */ -export type ChatStreamingResponseChunkData = typeof Generated.ChatStreamingResponseChunk.fields.data.Type +export type ChatStreamingResponseChunkData = typeof Generated.ChatStreamingResponse.fields.data.Type // ============================================================================= // Service Identifier @@ -197,8 +197,18 @@ export const make = Effect.fnUntraced( Effect.catchTags({ SendChatCompletionRequest400: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), SendChatCompletionRequest401: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest402: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest403: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest404: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest408: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest413: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest422: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), SendChatCompletionRequest429: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), SendChatCompletionRequest500: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest502: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest503: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest524: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), + SendChatCompletionRequest529: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")), HttpClientError: (error) => Errors.mapHttpClientError(error, "createChatCompletion"), SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createChatCompletion")) }) @@ -218,6 +228,7 @@ export const make = Effect.fnUntraced( Stream.catchTags({ // TODO: handle SSE retries Retry: (error) => Stream.die(error), + SseError: (error) => Stream.fail(Errors.mapSseError(error, "createChatCompletionStream")), HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createChatCompletionStream")), SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createChatCompletionStream")) }) @@ -346,7 +357,7 @@ export const layerConfig = (options?: { // Internal Utilities // ============================================================================= -const ChatStreamingResponseChunkDataFromString = Schema.fromJsonString(Generated.ChatStreamingResponseChunk.fields.data) +const ChatStreamingResponseChunkDataFromString = Schema.fromJsonString(Generated.ChatStreamingResponse.fields.data) const decodeChatStreamingResponseChunkData = Schema.decodeUnknownEffect(ChatStreamingResponseChunkDataFromString) const decodeChatCompletionSseData = ( diff --git a/packages/ai/openrouter/src/OpenRouterConfig.ts b/packages/ai/openrouter/src/OpenRouterConfig.ts index 7ca997872..e58445718 100644 --- a/packages/ai/openrouter/src/OpenRouterConfig.ts +++ b/packages/ai/openrouter/src/OpenRouterConfig.ts @@ -36,7 +36,7 @@ export class OpenRouterConfig extends Context.Service< */ static readonly getOrUndefined: Effect.Effect = Effect.map( Effect.context(), - (services) => services.mapUnsafe.get(OpenRouterConfig.key) + Context.getOrUndefined(OpenRouterConfig) ) } @@ -50,7 +50,7 @@ export declare namespace OpenRouterConfig { * Configuration values read by OpenRouter provider operations when resolving * the generated HTTP client. * - * @category models + * @category services * @since 4.0.0 */ export interface Service { diff --git a/packages/ai/openrouter/src/OpenRouterError.ts b/packages/ai/openrouter/src/OpenRouterError.ts index 00db3860d..63d0555e0 100644 --- a/packages/ai/openrouter/src/OpenRouterError.ts +++ b/packages/ai/openrouter/src/OpenRouterError.ts @@ -51,7 +51,7 @@ declare module "effect/unstable/ai/AiError" { * information from responses where the provider rejected the request because * a limit was reached. * - * @category configuration + * @category models * @since 4.0.0 */ export interface RateLimitErrorMetadata { @@ -69,7 +69,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failures caused by exhausted account, * billing, or usage quota. * - * @category configuration + * @category models * @since 4.0.0 */ export interface QuotaExhaustedErrorMetadata { @@ -87,7 +87,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for failed API key, authorization, or * permission checks. * - * @category configuration + * @category models * @since 4.0.0 */ export interface AuthenticationErrorMetadata { @@ -105,7 +105,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenRouter rejects input or output * because it violates a content policy. * - * @category configuration + * @category models * @since 4.0.0 */ export interface ContentPolicyErrorMetadata { @@ -123,7 +123,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for malformed requests, unsupported * parameters, or other request validation failures reported by OpenRouter. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidRequestErrorMetadata { @@ -141,7 +141,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenRouter-side failures such as * transient server errors or overload responses. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InternalProviderErrorMetadata { @@ -159,7 +159,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an OpenRouter response cannot be * parsed or validated as the expected output. * - * @category configuration + * @category models * @since 4.0.0 */ export interface InvalidOutputErrorMetadata { @@ -177,7 +177,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when OpenRouter returns content that does * not satisfy the requested structured output schema. * - * @category configuration + * @category models * @since 4.0.0 */ export interface StructuredOutputErrorMetadata { @@ -195,7 +195,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details when an unsupported schema failure is * associated with an OpenRouter response. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnsupportedSchemaErrorMetadata { @@ -213,7 +213,7 @@ declare module "effect/unstable/ai/AiError" { * Preserves provider error details for OpenRouter failures that do not map * cleanly to a more specific AI error category. * - * @category configuration + * @category models * @since 4.0.0 */ export interface UnknownErrorMetadata { diff --git a/packages/ai/openrouter/src/OpenRouterLanguageModel.ts b/packages/ai/openrouter/src/OpenRouterLanguageModel.ts index f937ca4e7..eb5e9a60f 100644 --- a/packages/ai/openrouter/src/OpenRouterLanguageModel.ts +++ b/packages/ai/openrouter/src/OpenRouterLanguageModel.ts @@ -62,7 +62,7 @@ export class Config extends Context.Service< Simplify< & Partial< Omit< - typeof Generated.ChatGenerationParams.Encoded, + typeof Generated.ChatRequest.Encoded, "messages" | "response_format" | "tools" | "tool_choice" | "stream" | "stream_options" > > @@ -89,7 +89,7 @@ export class Config extends Context.Service< * @category models * @since 4.0.0 */ -export type ReasoningDetails = Exclude +export type ReasoningDetails = Exclude /** * File annotations emitted on OpenRouter assistant messages and exposed in @@ -99,7 +99,7 @@ export type ReasoningDetails = Exclude[number], + NonNullable[number], { type: "file" } > @@ -112,7 +112,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating system instructions into * OpenRouter chat messages. * - * @category request + * @category models * @since 4.0.0 */ export interface SystemMessageOptions extends ProviderOptions { @@ -123,7 +123,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -135,7 +135,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when translating user content into OpenRouter chat * messages. * - * @category request + * @category models * @since 4.0.0 */ export interface UserMessageOptions extends ProviderOptions { @@ -146,7 +146,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -158,7 +158,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves reasoning metadata when assistant messages are replayed in later * OpenRouter requests. * - * @category request + * @category models * @since 4.0.0 */ export interface AssistantMessageOptions extends ProviderOptions { @@ -169,7 +169,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null /** * Reasoning details associated with the assistant message. */ @@ -185,7 +185,7 @@ declare module "effect/unstable/ai/Prompt" { * These options are used when converting tool results into OpenRouter chat * messages. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolMessageOptions extends ProviderOptions { @@ -196,7 +196,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -207,7 +207,7 @@ declare module "effect/unstable/ai/Prompt" { * * Use when you use these options to control how text content is sent to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface TextPartOptions extends ProviderOptions { @@ -218,7 +218,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -230,7 +230,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves provider reasoning blocks so reasoning-aware conversations can * continue across OpenRouter requests. * - * @category request + * @category models * @since 4.0.0 */ export interface ReasoningPartOptions extends ProviderOptions { @@ -241,7 +241,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null /** * Reasoning details associated with the reasoning part. */ @@ -256,7 +256,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls file naming and prompt caching for files sent to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface FilePartOptions extends ProviderOptions { @@ -272,7 +272,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } @@ -284,7 +284,7 @@ declare module "effect/unstable/ai/Prompt" { * Preserves reasoning details associated with tool calls when a conversation * is sent back to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolCallPartOptions extends ProviderOptions { @@ -306,7 +306,7 @@ declare module "effect/unstable/ai/Prompt" { * * Controls prompt caching for tool results sent to OpenRouter. * - * @category request + * @category models * @since 4.0.0 */ export interface ToolResultPartOptions extends ProviderOptions { @@ -317,7 +317,7 @@ declare module "effect/unstable/ai/Prompt" { /** * A breakpoint which marks the end of reusable content eligible for caching. */ - readonly cacheControl?: typeof Generated.ChatMessageContentItemCacheControl.Encoded | null + readonly cacheControl?: typeof Generated.ChatContentCacheControl.Encoded | null } | null } } @@ -330,7 +330,7 @@ declare module "effect/unstable/ai/Response" { * * Preserves provider reasoning details that can be sent back in later turns. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningPartMetadata extends ProviderMetadata { @@ -352,7 +352,7 @@ declare module "effect/unstable/ai/Response" { * * Carries the first reasoning detail chunk when OpenRouter exposes one. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningStartPartMetadata extends ProviderMetadata { @@ -374,7 +374,7 @@ declare module "effect/unstable/ai/Response" { * * Carries provider reasoning detail chunks as they arrive from OpenRouter. * - * @category response + * @category models * @since 4.0.0 */ export interface ReasoningDeltaPartMetadata extends ProviderMetadata { @@ -397,7 +397,7 @@ declare module "effect/unstable/ai/Response" { * Associates tool calls with provider reasoning details when the model emits * reasoning and tool calls together. * - * @category response + * @category models * @since 4.0.0 */ export interface ToolCallPartMetadata extends ProviderMetadata { @@ -420,7 +420,7 @@ declare module "effect/unstable/ai/Response" { * Includes citation text and offsets returned by providers that support URL * annotations. * - * @category response + * @category models * @since 4.0.0 */ export interface UrlSourcePartMetadata extends ProviderMetadata { @@ -451,7 +451,7 @@ declare module "effect/unstable/ai/Response" { * Exposes provider response details that are not represented by the common * Effect AI finish part fields. * - * @category response + * @category models * @since 4.0.0 */ export interface FinishPartMetadata extends ProviderMetadata { @@ -466,7 +466,7 @@ declare module "effect/unstable/ai/Response" { /** * Raw token usage reported by OpenRouter. */ - readonly usage?: typeof Generated.ChatGenerationTokenUsage.Encoded | null + readonly usage?: typeof Generated.ChatUsage.Encoded | null /** * File annotations returned by the provider. */ @@ -545,21 +545,21 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig const client = yield* OpenRouterClient const codecTransformer = getCodecTransformer(model) - const makeConfig = Effect.gen(function*() { - const services = yield* Effect.context() - return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) } - }) + const makeConfig = Effect.contextWith((services: Context.Context) => + Effect.succeed({ model, ...providerConfig, ...Context.getOrUndefined(services, Config) }) + ) const makeRequest = Effect.fnUntraced( function*({ config, options }: { readonly config: typeof Config.Service readonly options: LanguageModel.ProviderOptions - }): Effect.fn.Return { + }): Effect.fn.Return { const messages = yield* prepareMessages({ options }) const { tools, toolChoice } = yield* prepareTools({ options, transformer: codecTransformer }) const responseFormat = yield* getResponseFormat({ config, options, transformer: codecTransformer }) - const request: typeof Generated.ChatGenerationParams.Encoded = { - ...config, + const { strictJsonSchema: _sjs, ...apiConfig } = config + const request: typeof Generated.ChatRequest.Encoded = { + ...apiConfig, messages, ...(Predicate.isNotUndefined(responseFormat) ? { response_format: responseFormat } : undefined), ...(Predicate.isNotUndefined(tools) ? { tools } : undefined), @@ -667,8 +667,8 @@ export const withConfigOverride: { const prepareMessages = Effect.fnUntraced( function*({ options }: { readonly options: LanguageModel.ProviderOptions - }): Effect.fn.Return, AiError.AiError> { - const messages: Array = [] + }): Effect.fn.Return, AiError.AiError> { + const messages: Array = [] const reasoningDetailsTracker = new ReasoningDetailsDuplicateTracker() @@ -690,7 +690,7 @@ const prepareMessages = Effect.fnUntraced( } case "user": { - const content: Array = [] + const content: Array = [] // Get the message-level cache control const messageCacheControl = getCacheControl(message) @@ -824,7 +824,7 @@ const prepareMessages = Effect.fnUntraced( case "assistant": { let text = "" let reasoning = "" - const toolCalls: Array = [] + const toolCalls: Array = [] for (const part of message.content) { switch (part.type) { @@ -900,7 +900,7 @@ const prepareMessages = Effect.fnUntraced( messages.push({ role: "tool", tool_call_id: part.id, - content: JSON.stringify(part.result) + content: typeof part.result === "string" ? part.result : JSON.stringify(part.result) }) } @@ -1047,7 +1047,6 @@ const makeResponse = Effect.fnUntraced( method: "makeResponse", reason: new AiError.ToolParameterValidationError({ toolName, - toolParams: {}, description: `Failed to securely JSON parse tool parameters: ${cause}` }) }) @@ -1320,7 +1319,7 @@ const makeStreamResponse = Effect.fnUntraced( // The signature typically arrives in the last reasoning delta, // but reasoning-start only carries the first delta's metadata. metadata: accumulatedReasoningDetails.length > 0 - ? { openRouter: { reasoningDetails: accumulatedReasoningDetails } } + ? { openrouter: { reasoningDetails: accumulatedReasoningDetails } } : undefined }) reasoningStarted = false @@ -1361,7 +1360,7 @@ const makeStreamResponse = Effect.fnUntraced( ? { startIndex: annotation.url_citation.start_index } : undefined), ...(Predicate.isNotUndefined(annotation.url_citation.end_index) - ? { startIndex: annotation.url_citation.end_index } + ? { endIndex: annotation.url_citation.end_index } : undefined) } } @@ -1377,6 +1376,7 @@ const makeStreamResponse = Effect.fnUntraced( for (const toolCall of toolCalls) { const index = toolCall.index ?? toolCalls.length - 1 let activeToolCall = activeToolCalls[index] + const argumentsDelta = toolCall.function?.arguments ?? "" // Tool call start - OpenRouter returns all information except the // tool call parameters in the first chunk @@ -1415,7 +1415,7 @@ const makeStreamResponse = Effect.fnUntraced( id: toolCall.id, type: "function", name: toolCall.function.name, - params: toolCall.function.arguments ?? "" + params: argumentsDelta } activeToolCalls[index] = activeToolCall @@ -1425,23 +1425,16 @@ const makeStreamResponse = Effect.fnUntraced( id: activeToolCall.id, name: activeToolCall.name }) - - // Emit a tool call delta part if parameters were also sent - if (activeToolCall.params.length > 0) { - parts.push({ - type: "tool-params-delta", - id: activeToolCall.id, - delta: activeToolCall.params - }) - } } else { - // If an active tool call was found, update and emit the delta for - // the tool call's parameters - activeToolCall.params += toolCall.function?.arguments ?? "" + activeToolCall.params += argumentsDelta + } + + // Emit a tool call delta part if parameters were also sent + if (argumentsDelta.length > 0) { parts.push({ type: "tool-params-delta", id: activeToolCall.id, - delta: activeToolCall.params + delta: argumentsDelta }) } @@ -1502,7 +1495,7 @@ const makeStreamResponse = Effect.fnUntraced( (detail) => detail.type === "reasoning.encrypted" && detail.data.length > 0 ) if (totalToolCalls > 0 && hasEncryptedReasoning && finishReason === "stop") { - finishReason = resolveFinishReason("tool-calls") + finishReason = "tool-calls" } // Forward any unsent tool calls if finish reason is 'tool-calls' @@ -1588,8 +1581,8 @@ const prepareTools = Effect.fnUntraced( readonly options: LanguageModel.ProviderOptions readonly transformer: LanguageModel.CodecTransformer }): Effect.fn.Return<{ - readonly tools: ReadonlyArray | undefined - readonly toolChoice: typeof Generated.ToolChoiceOption.Encoded | undefined + readonly tools: ReadonlyArray | undefined + readonly toolChoice: typeof Generated.ChatToolChoice.Encoded | undefined }, AiError.AiError> { if (options.tools.length === 0) { return { tools: undefined, toolChoice: undefined } @@ -1607,12 +1600,12 @@ const prepareTools = Effect.fnUntraced( }) } - let tools: Array = [] - let toolChoice: typeof Generated.ToolChoiceOption.Encoded | undefined = undefined + let tools: Array> = [] + let toolChoice: typeof Generated.ChatToolChoice.Encoded | undefined = undefined for (const tool of options.tools) { const description = Tool.getDescription(tool) - const parameters = yield* tryJsonSchema(tool.parametersSchema, "prepareTools", transformer) + const parameters = yield* tryToolJsonSchema(tool, "prepareTools", transformer) const strict = Tool.getStrictMode(tool) ?? null tools.push({ @@ -1650,7 +1643,7 @@ const prepareTools = Effect.fnUntraced( const annotateRequest = ( span: Span, - request: typeof Generated.ChatGenerationParams.Encoded + request: typeof Generated.ChatRequest.Encoded ): void => { addGenAIAnnotations(span, { system: "openrouter", @@ -1717,7 +1710,7 @@ const getCacheControl = ( | Prompt.ReasoningPart | Prompt.FilePart | Prompt.ToolResultPart -): typeof Generated.ChatMessageContentItemCacheControl.Encoded | null => part.options.openrouter?.cacheControl ?? null +): typeof Generated.ChatContentCacheControl.Encoded | null => part.options.openrouter?.cacheControl ?? null const findFirstReasoningDetails = (content: ReadonlyArray): ReasoningDetails | null => { for (const part of content) { @@ -1776,11 +1769,17 @@ const tryJsonSchema = ( catch: (error) => unsupportedSchemaError(error, method) }) +const tryToolJsonSchema = (tool: T, method: string, transformer: LanguageModel.CodecTransformer) => + Effect.try({ + try: () => Tool.getJsonSchema(tool, { transformer }), + catch: (error) => unsupportedSchemaError(error, method) + }) + const getResponseFormat = Effect.fnUntraced(function*({ config, options, transformer }: { readonly config: typeof Config.Service readonly options: LanguageModel.ProviderOptions readonly transformer: LanguageModel.CodecTransformer -}): Effect.fn.Return { +}): Effect.fn.Return { if (options.responseFormat.type === "json") { const description = SchemaAST.resolveDescription(options.responseFormat.schema.ast) const jsonSchema = yield* tryJsonSchema(options.responseFormat.schema, "getResponseFormat", transformer) @@ -1833,7 +1832,7 @@ const getBase64FromDataUrl = (dataUrl: string): string => { return match ? match[1]! : dataUrl } -const getUsage = (usage: Generated.ChatGenerationTokenUsage | undefined): Response.Usage => { +const getUsage = (usage: Generated.ChatUsage | undefined): Response.Usage => { if (Predicate.isUndefined(usage)) { return { inputTokens: { uncached: undefined, total: 0, cacheRead: undefined, cacheWrite: undefined }, diff --git a/packages/ai/openrouter/src/internal/errors.ts b/packages/ai/openrouter/src/internal/errors.ts index d01128e12..27caabb28 100644 --- a/packages/ai/openrouter/src/internal/errors.ts +++ b/packages/ai/openrouter/src/internal/errors.ts @@ -8,6 +8,7 @@ import * as Redactable from "effect/Redactable" import * as Schema from "effect/Schema" import * as AiError from "effect/unstable/ai/AiError" import type * as Response from "effect/unstable/ai/Response" +import type * as Sse from "effect/unstable/encoding/Sse" import type * as HttpClientError from "effect/unstable/http/HttpClientError" import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest" import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" @@ -23,7 +24,7 @@ export const OpenRouterErrorBody = Schema.Struct({ error: Schema.Struct({ message: Schema.String, type: Schema.optional(Schema.NullOr(Schema.String)), - code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Number.check(Schema.isFinite())]))) + code: Schema.optional(Schema.NullOr(Schema.Union([Schema.String, Schema.Finite]))) }) }) @@ -52,6 +53,17 @@ export const mapSchemaError = dual< reason: AiError.InvalidOutputError.fromSchemaError(error) })) +/** @internal */ +export const mapSseError = dual< + (method: string) => (error: Sse.SseError) => AiError.AiError, + (error: Sse.SseError, method: string) => AiError.AiError +>(2, (error, method) => + AiError.make({ + module: "OpenRouterClient", + method, + reason: new AiError.InvalidOutputError({ description: error.message }) + })) + /** @internal */ export const mapClientError = dual< (method: string) => (error: Generated.OpenRouterClientError) => AiError.AiError, @@ -244,46 +256,6 @@ export const buildHttpContext = (params: { // HTTP Status Code // ============================================================================= -const buildInvalidRequestDescription = (params: { - readonly status: number - readonly message: string | undefined - readonly method: string - readonly url: string - readonly errorCode: string | number | null - readonly errorType: string | null - readonly requestId: string | null - readonly body: string | undefined -}): string => { - const parts: Array = [] - - if (params.message) { - parts.push(params.message) - } else { - parts.push(`HTTP ${params.status}`) - } - - parts.push(`(${params.method} ${params.url})`) - - if (params.errorCode) { - parts.push(`[code: ${params.errorCode}]`) - } else if (params.errorType) { - parts.push(`[type: ${params.errorType}]`) - } - - if (params.requestId) { - parts.push(`[requestId: ${params.requestId}]`) - } - - if (!params.message && params.body) { - const truncated = params.body.length > 200 - ? params.body.slice(0, 200) + "..." - : params.body - parts.push(`Response: ${truncated}`) - } - - return parts.join(" ") -} - /** @internal */ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: { readonly status: number @@ -292,7 +264,7 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http readonly metadata: OpenRouterErrorMetadata readonly http: typeof AiError.HttpContext.Type }): AiError.AiErrorReason => { - const invalidRequestDescription = buildInvalidRequestDescription({ + const errorDescription = AiError.buildErrorDescription({ status, message, method: http.request.method, @@ -306,19 +278,21 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http switch (status) { case 400: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openrouter: metadata }, http }) case 401: return new AiError.AuthenticationError({ kind: "InvalidKey", + description: errorDescription, metadata: { openrouter: metadata }, http }) case 403: return new AiError.AuthenticationError({ kind: "InsufficientPermissions", + description: errorDescription, metadata: { openrouter: metadata }, http }) @@ -326,7 +300,7 @@ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http case 409: case 422: return new AiError.InvalidRequestError({ - description: invalidRequestDescription, + description: errorDescription, metadata: { openrouter: metadata }, http }) diff --git a/packages/ai/openrouter/src/internal/utilities.ts b/packages/ai/openrouter/src/internal/utilities.ts index 5df64c53b..1d5112e94 100644 --- a/packages/ai/openrouter/src/internal/utilities.ts +++ b/packages/ai/openrouter/src/internal/utilities.ts @@ -14,10 +14,12 @@ const finishReasonMap: Record = { /** @internal */ export const resolveFinishReason = ( finishReason: string | null | undefined -): Response.FinishReason => - Predicate.isNotNullish(finishReason) - ? finishReasonMap[finishReason] - : "other" +): Response.FinishReason => { + if (Predicate.isNullish(finishReason)) { + return "other" + } + return Object.hasOwn(finishReasonMap, finishReason) ? finishReasonMap[finishReason] : "unknown" +} /** * Tracks ReasoningDetailUnion entries and deduplicates them based diff --git a/packages/ai/openrouter/test/Generated.test.ts b/packages/ai/openrouter/test/Generated.test.ts new file mode 100644 index 000000000..0db46d615 --- /dev/null +++ b/packages/ai/openrouter/test/Generated.test.ts @@ -0,0 +1,158 @@ +import { Generated, OpenRouterClient, OpenRouterLanguageModel } from "@effect/ai-openrouter" +import { describe, it } from "@effect/vitest" +import { deepStrictEqual } from "@effect/vitest/utils" +import { Effect, Schema, Stream } from "effect" +import { type AiError, LanguageModel, type Response, Tool, Toolkit } from "effect/unstable/ai" +import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +describe("Generated", () => { + it("decodes nullable generation statistics", () => { + const response: Generated.GetGeneration200 = { + data: { + id: "gen-test", + upstream_id: null, + total_cost: 0.003294, + cache_discount: null, + upstream_inference_cost: null, + created_at: "2026-07-24T12:00:00Z", + data_region: "global", + model: "openrouter/auto", + app_id: null, + streamed: null, + cancelled: null, + provider_name: null, + http_referer: null, + latency: null, + moderation_latency: null, + generation_time: null, + finish_reason: null, + tokens_prompt: null, + tokens_completion: null, + native_tokens_prompt: null, + native_tokens_completion: null, + native_tokens_completion_images: null, + native_tokens_reasoning: null, + native_tokens_cached: null, + num_fetches: null, + num_media_prompt: null, + num_input_audio_prompt: null, + num_media_completion: null, + num_search_results: null, + origin: "https://openrouter.ai/", + preset_id: null, + usage: 0.003294, + is_byok: false, + native_finish_reason: null, + external_user: null, + api_type: null, + request_id: null, + response_cache_source_id: null, + router: null, + service_tier: null, + session_id: null, + provider_responses: null, + user_agent: null, + web_search_engine: null + } + } + + deepStrictEqual(Schema.decodeUnknownSync(Generated.GetGeneration200)(response), response) + }) + + it("preserves streamed usage cost fields", () => { + const usage = { + completion_tokens: 11, + prompt_tokens: 7, + total_tokens: 18, + cost: 0.000365, + is_byok: false, + prompt_tokens_details: { + cached_tokens: 0, + cache_write_tokens: 0 + } + } + + deepStrictEqual(Schema.decodeUnknownSync(Generated.ChatUsage)(usage), usage) + }) + + for ( + const { encrypted, mode, reason, tool } of [ + { mode: "stream", encrypted: true, tool: true, reason: "tool-calls" }, + { mode: "stream", encrypted: false, tool: true, reason: "stop" }, + { mode: "stream", encrypted: true, tool: false, reason: "stop" }, + { mode: "generate", encrypted: true, tool: true, reason: "tool-calls" } + ] as const + ) { + it.effect(`${mode}: encrypted=${encrypted}, tool=${tool} finishes with ${reason}`, () => + Effect.gen(function*() { + const metadata = { id: "response-1", model: "test/reasoning-model", created: 1 } + const reasoningDetails = encrypted + ? [{ type: "reasoning.encrypted", data: "opaque-signature", format: "unknown" } as const] + : [] + const toolCalls = tool + ? [{ + index: 0, + id: "call-1", + type: "function" as const, + function: { name: "ProbeTool", arguments: "{\"value\":1}" } + }] + : [] + const usage = { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } + const chunks = yield* Schema.decodeUnknownEffect(Schema.Array(Generated.ChatStreamChunk))([ + { + ...metadata, + object: "chat.completion.chunk", + choices: [{ index: 0, delta: { reasoning_details: reasoningDetails, tool_calls: toolCalls } }] + }, + { + ...metadata, + object: "chat.completion.chunk", + choices: [{ index: 0, delta: {}, finish_reason: "stop" }], + usage + } + ]) + const body = yield* Schema.decodeUnknownEffect(Generated.SendChatCompletionRequest200)({ + ...metadata, + object: "chat.completion", + system_fingerprint: null, + choices: [{ + index: 0, + finish_reason: "stop", + message: { role: "assistant", reasoning_details: reasoningDetails, tool_calls: toolCalls } + }], + usage + }) + const response = HttpClientResponse.fromWeb( + HttpClientRequest.post("https://example.com/chat/completions"), + new globalThis.Response() + ) + const toolkit = Toolkit.make(Tool.make("ProbeTool", { + parameters: Schema.Struct({ value: Schema.Number }), + success: Schema.String + })) + const client = OpenRouterClient.OpenRouterClient.of({ + client: Generated.make(HttpClient.make(() => Effect.die("Unexpected HTTP request"))), + createChatCompletion: () => Effect.succeed([body, response]), + createChatCompletionStream: () => Effect.succeed([response, Stream.fromIterable(chunks)]) + }) + const options = { prompt: "Use the probe tool", toolkit, disableToolCallResolution: true } as const + const operation: Effect.Effect, AiError.AiError, LanguageModel.LanguageModel> = + mode === "stream" + ? LanguageModel.streamText(options).pipe(Stream.runCollect) + : LanguageModel.generateText(options).pipe(Effect.map((result) => result.content)) + const parts = globalThis.Array.from( + yield* operation.pipe( + Effect.provide(OpenRouterLanguageModel.model(metadata.model)), + Effect.provide(toolkit.toLayer({ ProbeTool: () => Effect.succeed("ok") })), + Effect.provideService(OpenRouterClient.OpenRouterClient, client) + ) + ) + + deepStrictEqual( + parts.filter((part) => part.type === "tool-call").map((part) => part.name), + tool ? ["ProbeTool"] : [] + ) + deepStrictEqual(parts.filter((part) => part.type === "finish").map((part) => part.reason), [reason]) + })) + } +}) diff --git a/packages/ai/openrouter/test/OpenRouterClient.test.ts b/packages/ai/openrouter/test/OpenRouterClient.test.ts new file mode 100644 index 000000000..3e5eac5a4 --- /dev/null +++ b/packages/ai/openrouter/test/OpenRouterClient.test.ts @@ -0,0 +1,203 @@ +import { OpenRouterClient } from "@effect/ai-openrouter" +import * as Errors from "@effect/ai-openrouter/internal/errors" +import { assert, describe, it } from "@effect/vitest" +import { Context, Effect, Layer, Redacted, type Schema } from "effect" +import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +describe("OpenRouterClient", () => { + it.effect("redacts the API key in AI error context", () => + Effect.gen(function*() { + const client = yield* OpenRouterClient.OpenRouterClient + + const result = yield* client.createChatCompletion({ + model: "openai/gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "InvalidRequestError") + if (result.reason._tag !== "InvalidRequestError" || result.reason.http === undefined) { + return yield* Effect.die(new Error("Expected InvalidRequestError with HTTP context")) + } + const requests = yield* MockHttpClient.requests + assert.include(requests[0]?.url, "/chat/completions") + assert.strictEqual(String(result.reason.http.request.headers["authorization"]), "") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 400, + body: { + error: { + code: 400, + message: "Bad request" + } + } + })))) + + it.effect("surfaces the provider message on 401 AuthenticationError", () => + Effect.gen(function*() { + const client = yield* OpenRouterClient.OpenRouterClient + + const result = yield* client.createChatCompletion({ + model: "openai/gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "AuthenticationError") + if (result.reason._tag !== "AuthenticationError") { + return yield* Effect.die(new Error("Expected AuthenticationError")) + } + assert.strictEqual(result.reason.kind, "InvalidKey") + assert.strictEqual( + result.reason.description, + "No auth credentials found (POST https://openrouter.ai/api/v1/chat/completions) [code: 401] [requestId: req_openrouter]" + ) + assert.include(result.reason.message, "No auth credentials found") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 401, + body: { + error: { + code: 401, + message: "No auth credentials found" + } + }, + headers: { "x-request-id": "req_openrouter" } + })))) + + it("preserves and truncates a fallback HTTP response", () => { + const body = `${"a".repeat(200)}b` + const reason = Errors.mapStatusCodeToReason({ + status: 400, + headers: {}, + message: undefined, + metadata: { errorCode: null, errorType: null, requestId: null }, + http: makeHttpContext("https://openrouter.ai/api/v1/chat/completions", body) + }) + + assert.strictEqual(reason._tag, "InvalidRequestError") + if (reason._tag !== "InvalidRequestError") { + throw new Error("Expected InvalidRequestError") + } + assert.strictEqual( + reason.description, + `HTTP 400 (POST https://openrouter.ai/api/v1/chat/completions) Response: ${"a".repeat(200)}...` + ) + }) + + it.effect("surfaces the provider message on 403 AuthenticationError", () => + Effect.gen(function*() { + const client = yield* OpenRouterClient.OpenRouterClient + + const result = yield* client.createChatCompletion({ + model: "openai/gpt-4o-mini", + messages: [{ role: "user", content: "hello" }] + }).pipe(Effect.flip) + + assert.strictEqual(result.reason._tag, "AuthenticationError") + if (result.reason._tag !== "AuthenticationError") { + return yield* Effect.die(new Error("Expected AuthenticationError")) + } + assert.strictEqual(result.reason.kind, "InsufficientPermissions") + assert.include(result.reason.description ?? "", "Key does not have permission") + assert.include(result.reason.message, "Key does not have permission") + }).pipe(Effect.provide(makeTestLayer({ + _tag: "Json", + status: 403, + body: { + error: { + code: 403, + message: "Key does not have permission" + } + } + })))) +}) + +type MockResponse = + | { + readonly _tag: "Json" + readonly body: Schema.Json + readonly status?: number | undefined + readonly headers?: Record | undefined + } + | { + readonly _tag: "Sse" + readonly events: ReadonlyArray + readonly status?: number | undefined + readonly headers?: Record | undefined + } + +class MockOpenRouterResponse extends Context.Service()("MockOpenRouterResponse") {} + +class MockHttpClient extends Context.Service> +}>()("MockHttpClient") { + static requests = MockHttpClient.use((client) => client.requests) +} + +const makeHttpClientContext = Effect.gen(function*() { + const capturedRequests: Array = [] + const mock = yield* MockOpenRouterResponse + + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + capturedRequests.push(request) + return makeResponse(request, mock.response) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + + const mockHttpClient: MockHttpClient["Service"] = { + requests: Effect.sync(() => capturedRequests) + } + + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, mockHttpClient) + ) +}) + +const HttpClientLayer = Layer.effectContext(makeHttpClientContext) + +const makeTestLayer = ( + response: MockResponse, + options: OpenRouterClient.Options = { apiKey: Redacted.make("sk-test-key") } +) => + OpenRouterClient.layer(options).pipe( + Layer.provideMerge(HttpClientLayer), + Layer.provide(Layer.succeed(MockOpenRouterResponse, { response })) + ) + +const makeResponse = ( + request: HttpClientRequest.HttpClientRequest, + response: MockResponse +): HttpClientResponse.HttpClientResponse => { + const contentType = response._tag === "Json" + ? "application/json" + : "text/event-stream" + const body = response._tag === "Json" + ? JSON.stringify(response.body) + : response.events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + + return HttpClientResponse.fromWeb( + request, + new Response(body, { + status: response.status ?? 200, + headers: { + "content-type": contentType, + ...response.headers + } + }) + ) +} + +const makeHttpContext = (url: string, body: string) => ({ + request: { + method: "POST" as const, + url, + urlParams: [], + hash: undefined, + headers: {} + }, + body +}) diff --git a/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts b/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts index 2937111af..7874a8296 100644 --- a/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts +++ b/packages/ai/openrouter/test/OpenRouterLanguageModel.test.ts @@ -1,11 +1,54 @@ import { Generated, OpenRouterClient, OpenRouterLanguageModel } from "@effect/ai-openrouter" import { assert, describe, it } from "@effect/vitest" import { deepStrictEqual, strictEqual } from "@effect/vitest/utils" -import { Array, Context, Effect, Layer, Redacted, Ref, Schema } from "effect" -import { LanguageModel, Prompt } from "effect/unstable/ai" +import { Array, Context, Effect, Layer, Redacted, Ref, Schema, Stream } from "effect" +import { LanguageModel, Prompt, Tool, Toolkit } from "effect/unstable/ai" import { HttpClient, type HttpClientError, type HttpClientRequest, HttpClientResponse } from "effect/unstable/http" describe("OpenRouterLanguageModel", () => { + describe("strictJsonSchema", () => { + it.effect("omits false from requests while preserving response strictness", () => + Effect.gen(function*() { + yield* LanguageModel.generateObject({ + prompt: "Give me a name", + schema: Schema.Struct({ name: Schema.String }) + }).pipe(Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini", { strictJsonSchema: false }))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + strictEqual(body.response_format.json_schema.strict, false) + assert.notProperty(body, "strictJsonSchema") + }).pipe(Effect.provide(makeTestLayer({ + body: { + choices: [{ + finish_reason: "stop", + index: 0, + message: { role: "assistant", content: JSON.stringify({ name: "Alice" }) } + }] + } + })))) + + it.effect("omits true from streaming requests while preserving tool strictness", () => + Effect.gen(function*() { + const tool = Tool.make("FlexibleTool", { parameters: Schema.Struct({ query: Schema.String }) }) + .annotate(Tool.Strict, false) + yield* LanguageModel.streamText({ + prompt: "Use a tool", + toolkit: Toolkit.make(tool), + disableToolCallResolution: true + }).pipe( + Stream.runDrain, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini", { strictJsonSchema: true })) + ) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + strictEqual(body.stream, true) + strictEqual(body.tools[0].function.strict, false) + assert.notProperty(body, "strictJsonSchema") + }).pipe(Effect.provide(makeStreamTestLayer([])))) + }) + describe("generateText", () => { describe("message preparation", () => { describe("audio file parts", () => { @@ -171,8 +214,216 @@ describe("OpenRouterLanguageModel", () => { }]) }).pipe(Effect.provide(makeTestLayer()))) }) + + it.effect("preserves string tool results", () => + Effect.gen(function*() { + yield* LanguageModel.generateText({ + prompt: Prompt.make([ + { role: "user", content: "Use the tool" }, + { + role: "assistant", + content: [Prompt.toolCallPart({ + id: "call_text", + name: "text_tool", + params: {}, + providerExecuted: false + })] + }, + { + role: "tool", + content: [Prompt.toolResultPart({ + id: "call_text", + name: "text_tool", + result: "PLAIN_TEXT_SENTINEL\n", + isFailure: false, + providerExecuted: false + })] + } + ]), + disableToolCallResolution: true + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + const toolResult = body.messages.find((message: any) => message.role === "tool") + + assert.isDefined(toolResult) + strictEqual(toolResult.content, "PLAIN_TEXT_SENTINEL\n") + }).pipe(Effect.provide(makeTestLayer()))) + }) + + describe("tool preparation", () => { + it.effect("passes raw JSON schema for dynamic tools", () => + Effect.gen(function*() { + const inputSchema = { + type: "object", + properties: { + query: { type: "string" }, + limit: { type: "number" } + }, + required: ["query"], + additionalProperties: false + } as const + + const DynamicTool = Tool.dynamic("DynamicTool", { + description: "A dynamic tool", + parameters: inputSchema + }) + + yield* LanguageModel.generateText({ + prompt: "Use the dynamic tool", + toolkit: Toolkit.make(DynamicTool), + disableToolCallResolution: true + }).pipe(Effect.provide(OpenRouterLanguageModel.model("google/gemini-2.5-flash"))) + + const requests = yield* MockHttpClient.requests + const body = yield* getRequestBody(requests[0]) + + const tool = body.tools?.find((entry: any) => + entry.type === "function" && entry.function.name === "DynamicTool" + ) + assert.isDefined(tool) + strictEqual(tool.function.description, "A dynamic tool") + deepStrictEqual(tool.function.parameters, inputSchema) + }).pipe(Effect.provide(makeTestLayer()))) }) }) + + describe("streamText", () => { + it.effect("preserves streamed citation start and end indexes", () => + Effect.gen(function*() { + const parts = yield* LanguageModel.streamText({ prompt: "cite a source" }).pipe( + Stream.runCollect, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini")), + Effect.provide(makeStreamTestLayer([{ + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + annotations: [{ + type: "url_citation", + url_citation: { + url: "https://example.com/source", + title: "source", + start_index: 2, + end_index: 9 + } + }] + } + }] + }])) + ) + + const source = globalThis.Array.from(parts).find((part) => part.type === "source") + assert.isDefined(source) + if (source?.type === "source") { + assert.deepStrictEqual(source.metadata, { + openrouter: { startIndex: 2, endIndex: 9 } + }) + } + })) + + it.effect("uses lowercase openrouter reasoning-end metadata", () => + Effect.gen(function*() { + const reasoningDetails = [{ + type: "reasoning.text", + text: "thinking", + signature: "signature-final", + format: "unknown" + }] as const + const parts = yield* LanguageModel.streamText({ prompt: "reason then answer" }).pipe( + Stream.runCollect, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini")), + Effect.provide(makeStreamTestLayer([ + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ index: 0, delta: { reasoning_details: reasoningDetails } }] + }, + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ index: 0, finish_reason: "stop", delta: { content: "answer" } }] + } + ])) + ) + + const reasoningEnd = parts.find((part) => part.type === "reasoning-end") + deepStrictEqual(reasoningEnd?.metadata, { openrouter: { reasoningDetails } }) + })) + + it.effect("emits incremental tool parameter fragments", () => + Effect.gen(function*() { + const ProbeTool = Tool.make("ProbeTool", { + parameters: Schema.Struct({ a: Schema.Number }), + success: Schema.String + }) + const toolkit = Toolkit.make(ProbeTool) + const parts = yield* LanguageModel.streamText({ + prompt: "call the tool", + toolkit, + disableToolCallResolution: true + }).pipe( + Stream.runCollect, + Effect.provide(OpenRouterLanguageModel.model("openai/gpt-4o-mini")), + Effect.provide(toolkit.toLayer({ ProbeTool: () => Effect.succeed("ok") })), + Effect.provide(makeStreamTestLayer([ + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { + tool_calls: [{ + index: 0, + id: "call-1", + type: "function", + function: { name: "ProbeTool", arguments: "{\"a\":" } + }] + } + }] + }, + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + delta: { tool_calls: [{ index: 0 }] } + }] + }, + { + id: "response-1", + object: "chat.completion.chunk", + model: "openai/gpt-4o-mini", + created: 1, + choices: [{ + index: 0, + finish_reason: "tool_calls", + delta: { tool_calls: [{ index: 0, function: { arguments: "1}" } }] } + }] + } + ])) + ) + + deepStrictEqual( + globalThis.Array.from(parts) + .filter((part) => part.type === "tool-params-delta") + .map((part) => part.delta), + ["{\"a\":", "1}"] + ) + })) + }) }) // ============================================================================= @@ -237,6 +488,7 @@ const makeDefaultResponse = ( created: 1234567890, model: "google/gemini-2.5-flash", object: "chat.completion", + system_fingerprint: null, ...overrides }) @@ -263,3 +515,30 @@ const getRequestBody = (request: HttpClientRequest.HttpClientRequest) => } return yield* Effect.die(new Error("Expected Uint8Array body")) }) + +const makeStreamTestLayer = (events: ReadonlyArray) => { + const body = events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join("") + "data: [DONE]\n\n" + const httpClientLayer = Layer.effectContext(Effect.gen(function*() { + const capturedRequests = yield* Ref.make>([]) + const httpClient = HttpClient.makeWith( + Effect.fnUntraced(function*(requestEffect) { + const request = yield* requestEffect + yield* Ref.update(capturedRequests, Array.append(request)) + return HttpClientResponse.fromWeb( + request, + new Response(body, { + status: 200, + headers: { "content-type": "text/event-stream" } + }) + ) + }), + Effect.succeed as HttpClient.HttpClient.Preprocess + ) + return Context.make(HttpClient.HttpClient, httpClient).pipe( + Context.add(MockHttpClient, MockHttpClient.of({ requests: Ref.get(capturedRequests) })) + ) + })) + return OpenRouterClient.layer({ apiKey: Redacted.make("sk-test-key") }).pipe( + Layer.provideMerge(httpClientLayer) + ) +} diff --git a/packages/ai/openrouter/tsconfig.json b/packages/ai/openrouter/tsconfig.json index a9b59a318..1cf21756b 100644 --- a/packages/ai/openrouter/tsconfig.json +++ b/packages/ai/openrouter/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/ai/openrouter/vitest.config.ts b/packages/ai/openrouter/vitest.config.ts deleted file mode 100644 index c8a52c182..000000000 --- a/packages/ai/openrouter/vitest.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { mergeConfig, type ViteUserConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -const config: ViteUserConfig = {} - -export default mergeConfig(shared, config) diff --git a/packages/atom/react/CHANGELOG.md b/packages/atom/react/CHANGELOG.md index 03b89810c..ba66b0a15 100644 --- a/packages/atom/react/CHANGELOG.md +++ b/packages/atom/react/CHANGELOG.md @@ -1,5 +1,165 @@ # @effect/atom-react +## 4.0.0-rc.112 + +### Patch Changes + +- [#7435](https://github.com/Effect-TS/effect/pull/7435) [`4148e21`](https://github.com/Effect-TS/effect/commit/4148e21eb5f86ef37e07086ec9f3cc7e55d24e90) Thanks @mattrobrob! - Relax react peer dependency range +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7094](https://github.com/Effect-TS/effect/pull/7094) [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24) Thanks @fubhy! - Update peer dependencies +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6672](https://github.com/Effect-TS/effect/pull/6672) [`83d571c`](https://github.com/Effect-TS/effect/commit/83d571c9500d200e9f08aaf64632c502ae6f5afe) Thanks @andrskr! - Scope `useAtomSuspense` promises to their atom registry so concurrent registries resolve independently. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/atom/react/README.md b/packages/atom/react/README.md index 1cd2f2005..f3b2b6c49 100644 --- a/packages/atom/react/README.md +++ b/packages/atom/react/README.md @@ -1,7 +1,14 @@ -# `@effect/atom-react` +# @effect/atom-react -React bindings for the Effect Atom modules. +[React](https://react.dev) bindings for Atom, the reactive state management modules for Effect. Includes hooks for reading and updating atoms, and helpers for server-side rendering hydration. + +## Installation + +```sh +npm install effect@rc @effect/atom-react@rc +``` ## Documentation -- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/atom-react). +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/atom-react) diff --git a/packages/atom/react/docgen.json b/packages/atom/react/docgen.json deleted file mode 100644 index d41436876..000000000 --- a/packages/atom/react/docgen.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/atom/react/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["node"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/atom/react/package.json b/packages/atom/react/package.json index 8b98e6b70..cffc768ce 100644 --- a/packages/atom/react/package.json +++ b/packages/atom/react/package.json @@ -1,17 +1,17 @@ { "name": "@effect/atom-react", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "React bindings for the Effect Atom modules", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/atom/react" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,34 +50,32 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^", - "react": "^19.2.4", - "scheduler": "*" + "react": ">=19.0.0 <20.0.0", + "scheduler": ">=0.25.0 <0.28.0" }, "devDependencies": { "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.2", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.2", + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.7", "@types/scheduler": "^0.26.0", "effect": "workspace:^", - "jsdom": "^29.1.1", - "react": "19.2.6", - "react-dom": "19.2.6", - "react-error-boundary": "^6.1.1", + "jsdom": "^30.0.1", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-error-boundary": "^6.1.5", "scheduler": "^0.27.0" } } diff --git a/packages/atom/react/src/Hooks.ts b/packages/atom/react/src/Hooks.ts index 381d86d6c..5fe03b33b 100644 --- a/packages/atom/react/src/Hooks.ts +++ b/packages/atom/react/src/Hooks.ts @@ -293,8 +293,14 @@ export const useAtom = , Promise>(), - default: new Map, Promise>() + suspendOnWaiting: new WeakMap< + AtomRegistry.AtomRegistry, + WeakMap, Promise> + >(), + default: new WeakMap< + AtomRegistry.AtomRegistry, + WeakMap, Promise> + >() } function atomToPromise( @@ -302,7 +308,12 @@ function atomToPromise( atom: Atom.Atom>, suspendOnWaiting: boolean ) { - const map = suspendOnWaiting ? atomPromiseMap.suspendOnWaiting : atomPromiseMap.default + const registries = suspendOnWaiting ? atomPromiseMap.suspendOnWaiting : atomPromiseMap.default + let map = registries.get(registry) + if (map === undefined) { + map = new WeakMap() + registries.set(registry, map) + } let promise = map.get(atom) if (promise !== undefined) { return promise @@ -424,8 +435,8 @@ export const useAtomSubscribe = ( * @since 4.0.0 */ export const useAtomRef = (ref: AtomRef.ReadonlyRef): A => { - const [, setValue] = React.useState(ref.value) - React.useEffect(() => ref.subscribe(setValue), [ref]) + const [, forceUpdate] = React.useReducer((n) => n + 1, 0) + React.useEffect(() => ref.subscribe(forceUpdate), [ref]) return ref.value } diff --git a/packages/atom/react/src/ScopedAtom.ts b/packages/atom/react/src/ScopedAtom.ts index 8a6048229..9e7e3e7a0 100644 --- a/packages/atom/react/src/ScopedAtom.ts +++ b/packages/atom/react/src/ScopedAtom.ts @@ -42,10 +42,11 @@ export const TypeId: TypeId = "~@effect/atom-react/ScopedAtom" * * **Example** (Providing and reading a scoped atom) * - * ```ts + * ```ts import.meta.vitest * import { make, useAtomValue } from "@effect/atom-react" * import { Atom } from "effect/unstable/reactivity" * import * as React from "react" + * import { renderToStaticMarkup } from "react-dom/server" * * const Counter = make(() => Atom.make(0)) * @@ -58,6 +59,8 @@ export const TypeId: TypeId = "~@effect/atom-react/ScopedAtom" * export function App() { * return React.createElement(Counter.Provider, null, React.createElement(View)) * } + * + * renderToStaticMarkup(React.createElement(App)) // => "

" * ``` * * @category models @@ -92,10 +95,11 @@ export interface ScopedAtom, Input = never> { * * **Example** (Creating a scoped atom with input) * - * ```ts + * ```ts import.meta.vitest * import { make, useAtomValue } from "@effect/atom-react" * import { Atom } from "effect/unstable/reactivity" * import * as React from "react" + * import { renderToStaticMarkup } from "react-dom/server" * * const User = make((name: string) => Atom.make(name)) * @@ -112,6 +116,8 @@ export interface ScopedAtom, Input = never> { * React.createElement(UserName) * ) * } + * + * renderToStaticMarkup(React.createElement(App)) // => "Ada" * ``` * * @category constructors diff --git a/packages/atom/react/test/index.test.tsx b/packages/atom/react/test/index.test.tsx index df4d8ca9d..f00e71a6f 100644 --- a/packages/atom/react/test/index.test.tsx +++ b/packages/atom/react/test/index.test.tsx @@ -4,6 +4,7 @@ import { Cause, Context, Effect, Latch, Layer } from "effect" import * as Schema from "effect/Schema" import * as AsyncResult from "effect/unstable/reactivity/AsyncResult" import * as Atom from "effect/unstable/reactivity/Atom" +import * as AtomRef from "effect/unstable/reactivity/AtomRef" import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" import * as Hydration from "effect/unstable/reactivity/Hydration" import * as React from "react" @@ -11,10 +12,18 @@ import { Suspense } from "react" import { renderToString } from "react-dom/server" import { ErrorBoundary } from "react-error-boundary" import { beforeEach, describe, expect, it, test, vi } from "vitest" -import { HydrationBoundary, RegistryContext, RegistryProvider, useAtomSuspense, useAtomValue } from "../src/index.ts" +import { + HydrationBoundary, + RegistryContext, + RegistryProvider, + useAtomRef, + useAtomSuspense, + useAtomValue +} from "../src/index.ts" import * as ScopedAtom from "../src/ScopedAtom.ts" -describe("atom-react", () => { +// Tests share the DOM and registry. +describe("atom-react", { concurrent: false }, () => { let registry: AtomRegistry.AtomRegistry beforeEach(() => { @@ -135,6 +144,67 @@ describe("atom-react", () => { expect(screen.getByTestId("loading")).toBeInTheDocument() }) + + test("suspense subscriptions are isolated per registry", async () => { + const atom = Atom.make(AsyncResult.initial()) + const firstRegistry = AtomRegistry.make() + const secondRegistry = AtomRegistry.make() + + function TestComponent({ id }: { readonly id: string }) { + const value = useAtomSuspense(atom).value + return
{value}
+ } + + render( + + Loading...}> + + + + ) + render( + + Loading...}> + + + + ) + + act(() => { + secondRegistry.set(atom, AsyncResult.success("second")) + }) + + await waitFor(() => { + expect(screen.getByTestId("second-value")).toHaveTextContent("second") + }) + expect(screen.getByTestId("first-loading")).toBeInTheDocument() + + act(() => { + firstRegistry.set(atom, AsyncResult.success("first")) + }) + + await waitFor(() => { + expect(screen.getByTestId("first-value")).toHaveTextContent("first") + }) + }) + }) + + test("useAtomRef updates after switching refs", () => { + const first = AtomRef.make(0) + const second = AtomRef.make(1) + + function TestComponent({ source }: { readonly source: AtomRef.ReadonlyRef }) { + return
{useAtomRef(source)}
+ } + + const { rerender } = render() + rerender() + + act(() => { + second.set(0) + }) + + expect(screen.getByTestId("value")).toHaveTextContent("0") }) describe("ScopedAtom", () => { diff --git a/packages/atom/react/tsconfig.json b/packages/atom/react/tsconfig.json index 19a2f5dbc..e2a8ca19a 100644 --- a/packages/atom/react/tsconfig.json +++ b/packages/atom/react/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/atom/react/vitest.config.ts b/packages/atom/react/vitest.config.ts deleted file mode 100644 index d9ba9429e..000000000 --- a/packages/atom/react/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -export default mergeConfig(shared, { - test: { - environment: "jsdom", - setupFiles: ["./vitest.setup.ts"] - } -}) diff --git a/packages/atom/solid/CHANGELOG.md b/packages/atom/solid/CHANGELOG.md index b03148bec..849829970 100644 --- a/packages/atom/solid/CHANGELOG.md +++ b/packages/atom/solid/CHANGELOG.md @@ -1,5 +1,162 @@ # @effect/atom-solid +## 4.0.0-rc.112 + +### Patch Changes + +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7094](https://github.com/Effect-TS/effect/pull/7094) [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24) Thanks @fubhy! - Update peer dependencies +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/atom/solid/README.md b/packages/atom/solid/README.md index a4239bf9c..4d984b0de 100644 --- a/packages/atom/solid/README.md +++ b/packages/atom/solid/README.md @@ -1,7 +1,14 @@ -# `@effect/atom-solid` +# @effect/atom-solid -SolidJS bindings for the Effect Atom modules. +[SolidJS](https://www.solidjs.com) bindings for Atom, the reactive state management modules for Effect. + +## Installation + +```sh +npm install effect@rc @effect/atom-solid@rc +``` ## Documentation -- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/atom-solid). +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/atom-solid) diff --git a/packages/atom/solid/docgen.json b/packages/atom/solid/docgen.json deleted file mode 100644 index ba9091a04..000000000 --- a/packages/atom/solid/docgen.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "projectHomepage": "https://effect-ts.github.io/effect/docs/atom-solid", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/atom/solid/src/", - "enforceVersion": true, - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/atom/solid/package.json b/packages/atom/solid/package.json index 3b3f573f1..0bfe68b26 100644 --- a/packages/atom/solid/package.json +++ b/packages/atom/solid/package.json @@ -1,17 +1,17 @@ { "name": "@effect/atom-solid", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "SolidJS bindings for the Effect Atom modules", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/atom/solid" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,27 +50,25 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "peerDependencies": { "effect": "workspace:^", - "solid-js": ">=1 <2" + "solid-js": ">=1.9.14 <2.0.0" }, "devDependencies": { - "@solidjs/testing-library": "^0.8.0", + "@solidjs/testing-library": "^0.8.10", "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "^6.9.1", + "@testing-library/jest-dom": "^7.0.1", "effect": "workspace:^", - "jsdom": "^29.1.1", - "solid-js": "^1.9.12" + "jsdom": "^30.0.1", + "solid-js": "^1.9.15" } } diff --git a/packages/atom/solid/src/RegistryContext.ts b/packages/atom/solid/src/RegistryContext.ts index 7c1b6c0f2..111cfab28 100644 --- a/packages/atom/solid/src/RegistryContext.ts +++ b/packages/atom/solid/src/RegistryContext.ts @@ -68,7 +68,7 @@ export const RegistryProvider = (options: { scheduleTask: options.scheduleTask, initialValues: options.initialValues, timeoutResolution: options.timeoutResolution, - defaultIdleTTL: options.defaultIdleTTL ?? 400 + defaultIdleTTL: options.defaultIdleTTL }) onCleanup(() => registry.dispose()) return createComponent(RegistryContext.Provider, { diff --git a/packages/atom/solid/tsconfig.json b/packages/atom/solid/tsconfig.json index 19a2f5dbc..e2a8ca19a 100644 --- a/packages/atom/solid/tsconfig.json +++ b/packages/atom/solid/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/atom/solid/vitest.config.ts b/packages/atom/solid/vitest.config.ts deleted file mode 100644 index 48bb912d7..000000000 --- a/packages/atom/solid/vitest.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -export default mergeConfig(shared, { - resolve: { - conditions: ["browser"] - }, - esbuild: { - target: "es2022" - }, - test: { - environment: "jsdom", - setupFiles: ["./vitest.setup.ts"] - } -}) diff --git a/packages/atom/vue/CHANGELOG.md b/packages/atom/vue/CHANGELOG.md index e5c9aff92..0cfa84462 100644 --- a/packages/atom/vue/CHANGELOG.md +++ b/packages/atom/vue/CHANGELOG.md @@ -1,5 +1,162 @@ # @effect/atom-vue +## 4.0.0-rc.112 + +### Patch Changes + +- Updated dependencies [[`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16), [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb), [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306), [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd), [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64), [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae), [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4), [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100), [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46), [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3), [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8), [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712), [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87), [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8), [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27), [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e), [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9), [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd), [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07), [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78), [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a), [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1), [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365), [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74), [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906)]: + - effect@4.0.0-rc.112 + +## 4.0.0-rc.111 + +### Patch Changes + +- Updated dependencies [[`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8), [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d), [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236), [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4), [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9), [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2), [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101), [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c), [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd), [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4), [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933), [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed), [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7), [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32), [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58), [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8), [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e), [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa), [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c), [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7), [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1), [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560), [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c), [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668), [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a), [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b), [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865), [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2), [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37), [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949), [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9), [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010)]: + - effect@4.0.0-rc.111 + +## 4.0.0-rc.110 + +### Patch Changes + +- Updated dependencies [[`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179), [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d), [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9), [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48), [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979), [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5), [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f), [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9), [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051), [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765), [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4), [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd), [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f), [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f)]: + - effect@4.0.0-rc.110 + +## 4.0.0-rc.109 + +### Patch Changes + +- Updated dependencies [[`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac), [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7), [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa), [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb), [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575), [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8), [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c), [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3), [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03), [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0)]: + - effect@4.0.0-rc.109 + +## 4.0.0-rc.108 + +### Patch Changes + +- Updated dependencies [[`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8), [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa), [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75), [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed), [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1), [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd), [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a), [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66), [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576), [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b), [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc), [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120), [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887)]: + - effect@4.0.0-rc.108 + +## 4.0.0-beta.107 + +### Patch Changes + +- Updated dependencies [[`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8), [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb), [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018), [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede), [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9)]: + - effect@4.0.0-beta.107 + +## 4.0.0-beta.106 + +### Patch Changes + +- Updated dependencies [[`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7), [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651), [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f), [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d), [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266), [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae), [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d), [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03), [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1), [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7), [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1), [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997), [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675), [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752), [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf), [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d), [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6), [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b), [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726)]: + - effect@4.0.0-beta.106 + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7094](https://github.com/Effect-TS/effect/pull/7094) [`31efc5c`](https://github.com/Effect-TS/effect/commit/31efc5c4eabbe37f9ddab030ef760926e6ff3d24) Thanks @fubhy! - Update peer dependencies +- Updated dependencies [[`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4), [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1), [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8), [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2), [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6), [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a)]: + - effect@4.0.0-beta.105 + +## 4.0.0-beta.104 + +### Patch Changes + +- Updated dependencies [[`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20), [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4), [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322), [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56), [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5), [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c), [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006), [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924), [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7), [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb), [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8), [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8), [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7), [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a), [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370), [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210), [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e), [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899), [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0), [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93), [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86), [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7), [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667), [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5), [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba), [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812), [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20), [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a), [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747), [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62), [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7), [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2), [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349), [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8), [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e), [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f), [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced), [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf), [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411), [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac), [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3), [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1), [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34), [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18), [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635), [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916), [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336), [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf), [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006), [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738), [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250), [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830), [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3), [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a), [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38), [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9), [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748), [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb), [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d), [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5), [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc), [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5), [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a), [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d), [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd), [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01), [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f), [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21), [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5), [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f), [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646), [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad), [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a), [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9), [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17), [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36), [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1), [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8), [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3), [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172), [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d), [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06), [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351), [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8), [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da), [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2), [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e), [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d), [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c), [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948), [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a), [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191), [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217), [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731), [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d), [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a), [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee), [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99), [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693), [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114)]: + - effect@4.0.0-beta.104 + +## 4.0.0-beta.103 + +### Patch Changes + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- Updated dependencies [[`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944), [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6), [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923), [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539), [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe), [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006), [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147), [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7), [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588), [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15), [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e), [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5), [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9), [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9), [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab), [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7), [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f), [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd), [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319), [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30), [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2), [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec), [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7), [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c), [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37), [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b), [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f), [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff), [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e), [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b), [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4), [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772), [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e), [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd), [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43), [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7), [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3), [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024), [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90), [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab), [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984), [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4), [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f), [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91), [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570), [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819), [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786), [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4), [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322), [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe), [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf), [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014), [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676), [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68), [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8), [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2), [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289), [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3), [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596), [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999), [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250), [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8), [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3), [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6), [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac), [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e), [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff), [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21), [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2), [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376), [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d), [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4), [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5), [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444), [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c), [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273), [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452), [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9), [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a), [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c), [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046), [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea), [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146), [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0)]: + - effect@4.0.0-beta.103 + +## 4.0.0-beta.102 + +### Patch Changes + +- Updated dependencies [[`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef), [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628), [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128), [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065), [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999), [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6), [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f), [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246), [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470), [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c), [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5), [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5), [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123), [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5), [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d), [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f), [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a), [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252), [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06), [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a), [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e), [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c), [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580), [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260), [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b), [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333), [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1), [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85), [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7), [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0), [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375), [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20), [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38), [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797)]: + - effect@4.0.0-beta.102 + +## 4.0.0-beta.101 + +### Patch Changes + +- Updated dependencies [[`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462), [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa), [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4), [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436)]: + - effect@4.0.0-beta.101 + +## 4.0.0-beta.100 + +### Patch Changes + +- Updated dependencies [[`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745), [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8), [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb), [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f), [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d), [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754), [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9), [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd), [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826), [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2), [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70), [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb), [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf), [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108), [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400), [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b), [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f)]: + - effect@4.0.0-beta.100 + +## 4.0.0-beta.99 + +### Patch Changes + +- Updated dependencies [[`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4), [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f), [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c), [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a), [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e), [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162), [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210), [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde), [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af), [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4), [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf), [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1), [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38), [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c), [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd), [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456), [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f), [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd), [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209), [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206), [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015), [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e), [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400), [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d), [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91), [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199), [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a), [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604)]: + - effect@4.0.0-beta.99 + +## 4.0.0-beta.98 + +### Patch Changes + +- Updated dependencies [[`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842), [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e), [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe), [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f), [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709), [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c), [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f), [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b), [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7), [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7), [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc), [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786), [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f), [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e), [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953), [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba), [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9), [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de), [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3), [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf), [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e), [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76), [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79), [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7), [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5)]: + - effect@4.0.0-beta.98 + +## 4.0.0-beta.97 + +### Patch Changes + +- Updated dependencies []: + - effect@4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- Updated dependencies [[`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62), [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5), [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805)]: + - effect@4.0.0-beta.96 + +## 4.0.0-beta.95 + +### Patch Changes + +- Updated dependencies [[`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7), [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590), [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102), [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d), [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b), [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95), [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7), [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa), [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d), [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2)]: + - effect@4.0.0-beta.95 + +## 4.0.0-beta.94 + +### Patch Changes + +- Updated dependencies [[`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63), [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d), [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528), [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c), [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814), [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d), [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63)]: + - effect@4.0.0-beta.94 + +## 4.0.0-beta.93 + +### Patch Changes + +- Updated dependencies [[`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677), [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3), [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc), [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e), [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f), [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c)]: + - effect@4.0.0-beta.93 + +## 4.0.0-beta.92 + +### Patch Changes + +- Updated dependencies [[`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9)]: + - effect@4.0.0-beta.92 + +## 4.0.0-beta.91 + +### Patch Changes + +- Updated dependencies [[`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075), [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150), [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6)]: + - effect@4.0.0-beta.91 + ## 4.0.0-beta.90 ### Patch Changes diff --git a/packages/atom/vue/README.md b/packages/atom/vue/README.md index 5ef604acc..c6e27a485 100644 --- a/packages/atom/vue/README.md +++ b/packages/atom/vue/README.md @@ -1,7 +1,14 @@ -# `@effect/atom-vue` +# @effect/atom-vue -Vue bindings for the Effect Atom modules. +[Vue](https://vuejs.org) bindings for Atom, the reactive state management modules for Effect. + +## Installation + +```sh +npm install effect@rc @effect/atom-vue@rc +``` ## Documentation -- **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/atom-vue). +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/atom-vue) diff --git a/packages/atom/vue/docgen.json b/packages/atom/vue/docgen.json deleted file mode 100644 index a02edec82..000000000 --- a/packages/atom/vue/docgen.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../../../node_modules/@effect/docgen/schema.json", - "srcLink": "https://github.com/Effect-TS/effect/tree/main/packages/atom/vue/src/", - "exclude": ["src/internal/**/*.ts"], - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"] - }, - "plugins": [ - { "name": "@effect/language-service", "includeSuggestionsInTsc": false } - ] - } -} diff --git a/packages/atom/vue/package.json b/packages/atom/vue/package.json index 392da91b9..97d57eaab 100644 --- a/packages/atom/vue/package.json +++ b/packages/atom/vue/package.json @@ -1,17 +1,17 @@ { "name": "@effect/atom-vue", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "type": "module", "license": "MIT", "description": "Vue bindings for the Effect Atom modules", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/atom/vue" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -29,6 +29,7 @@ ".": "./src/index.ts", "./*": "./src/*.ts", "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -36,7 +37,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -46,23 +50,21 @@ ".": "./dist/index.js", "./*": "./dist/*.js", "./internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest", - "coverage": "vitest --coverage" + "check": "tsc -b tsconfig.json" }, "devDependencies": { "effect": "workspace:^", - "vue": "^3.5.34" + "vue": "^3.5.42" }, "peerDependencies": { "effect": "workspace:^", - "vue": "^3.5.0" + "vue": ">=3.5.39 <4.0.0" } } diff --git a/packages/atom/vue/src/index.ts b/packages/atom/vue/src/index.ts index 799c9ebd4..b4fff5a91 100644 --- a/packages/atom/vue/src/index.ts +++ b/packages/atom/vue/src/index.ts @@ -12,25 +12,25 @@ import { computed, type ComputedRef, inject, type InjectionKey, type Ref, shallo /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AsyncResult from "effect/unstable/reactivity/AsyncResult" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as Atom from "effect/unstable/reactivity/Atom" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AtomRef from "effect/unstable/reactivity/AtomRef" @@ -42,25 +42,25 @@ export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi" /** * @since 4.0.0 - * @category modules + * @category re-exports */ export * as AtomRpc from "effect/unstable/reactivity/AtomRpc" /** * @since 4.0.0 - * @category registry + * @category symbols */ export const registryKey = Symbol.for("@effect/atom-vue/registryKey") as InjectionKey /** * @since 4.0.0 - * @category registry + * @category constants */ export const defaultRegistry: AtomRegistry.AtomRegistry = AtomRegistry.make() /** * @since 4.0.0 - * @category registry + * @category accessors */ export const injectRegistry = (): AtomRegistry.AtomRegistry => { return inject(registryKey, defaultRegistry) @@ -206,6 +206,7 @@ export const useAtomRef =
(atomRef: () => AtomRef.ReadonlyRef): Readonly { value.value = next })) + value.value = ref.value }) return value as Readonly> } diff --git a/packages/atom/vue/test/index.test.ts b/packages/atom/vue/test/index.test.ts index 79cf1df55..ae0b2f885 100644 --- a/packages/atom/vue/test/index.test.ts +++ b/packages/atom/vue/test/index.test.ts @@ -1,5 +1,24 @@ -import { describe, test } from "vitest" +import { useAtomRef } from "@effect/atom-vue" +import { assert, describe, it } from "@effect/vitest" +import * as AtomRef from "effect/unstable/reactivity/AtomRef" +import { effectScope, nextTick, shallowRef } from "vue" describe("atom-vue", () => { - test("", () => {}) + describe("useAtomRef", () => { + it("publishes the current value when the selected ref changes", async () => { + const first = AtomRef.make(1) + const second = AtomRef.make(10) + const selected = shallowRef(first) + const scope = effectScope() + try { + const value = scope.run(() => useAtomRef(() => selected.value))! + + selected.value = second + await nextTick() + assert.strictEqual(value.value, 10) + } finally { + scope.stop() + } + }) + }) }) diff --git a/packages/atom/vue/tsconfig.json b/packages/atom/vue/tsconfig.json index d3687aaaa..dc6af14d2 100644 --- a/packages/atom/vue/tsconfig.json +++ b/packages/atom/vue/tsconfig.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://json.schemastore.org/tsconfig", "extends": "../../../tsconfig.base.json", "include": ["src"], "references": [ diff --git a/packages/atom/vue/vitest.config.ts b/packages/atom/vue/vitest.config.ts deleted file mode 100644 index d5870de3d..000000000 --- a/packages/atom/vue/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { mergeConfig } from "vitest/config" -import shared from "../../../vitest.shared.ts" - -export default mergeConfig(shared, { - test: { - environment: "happy-dom" - } -}) diff --git a/packages/effect/ARBITRARY-FOLLOW-UPS.md b/packages/effect/ARBITRARY-FOLLOW-UPS.md new file mode 100644 index 000000000..862a47e3a --- /dev/null +++ b/packages/effect/ARBITRARY-FOLLOW-UPS.md @@ -0,0 +1,62 @@ +# Native Arbitrary Follow-ups + +This file tracks unfinished work for the native Schema-first Arbitrary implementation. Settled behavior and technical +decisions belong in [ARBITRARY.md](ARBITRARY.md); migration guidance belongs in +[ARBITRARY-MIGRATION.md](ARBITRARY-MIGRATION.md). Remove an item from this file when it is resolved rather than keeping +completed implementation history here. + +Every production change must preserve the existing guarantees: + +- Schema remains the only catalog of primitive and structural generator constructors; +- discarded roots are bounded by `maxDiscards`; +- inspected shrink candidates, including rejected nodes, are bounded by `maxShrinks`; +- shrinking and replay remain deterministic for supported pure callbacks; +- recursive and mutually recursive Schemas retain their productivity guarantees; +- `Sample`, the shrink carrier, the PRNG, generation budgets, and compiler metadata remain private; +- runtime performance and bundle cost are measured before and after the change. + +## Conditional research + +These items are intentionally dormant until their trigger is observed. + +### Finite-domain metadata + +Evaluate private finite-domain metadata only if constructive unique generation demonstrates a real exhaustion or +productivity problem. Do not add public cardinality vocabulary preemptively. + +### Decoded collection and Declaration profiling + +Profile collection generation Links and Declaration decoding only when a new runtime baseline identifies a regression. +ReadonlyMap and ReadonlySet are compiler-owned; Effect-specific HashMap, HashSet, and Chunk keep declaration-local +generation Links. + +### Schema-scoped distribution customization + +Revisit application-owned distribution overrides, including deterministic Faker integration, only after a concrete +use case establishes the required scope and bundle boundary. A future design must work for checked and nested Schemas, +must not overload filter or declaration annotations with a second contract, and must not require synthetic Schemas in +test integrations. Prefer a derivation-time override mechanism over executable metadata captured by production Schema +modules. + +### Trace-informed shrinking + +Compare the current `Sample` tree with private structural spans or trace-informed shrinking only when a reproducible +case shows poor shrunk output or excessive candidate traversal. + +### Concrete failing-input persistence + +Evaluate persistence and reuse of concrete failing inputs last. After `map` or `flatMap`, an Arbitrary may no longer +have a Schema or codec capable of serializing its output, while the existing opaque replay token remains persistable. + +## Verification policy + +For every activated item, use the narrowest representative validation and record exact commands and artifacts: + +- focused Arbitrary runtime tests and typetests when the public types change; +- package type checking and linting; +- seeded generation, shrinking, and replay characterization relevant to the slice; +- focused warm and cold runtime scenarios; +- focused Arbitrary bundle fixtures plus production Schema bundle sentinels. + +Do not update public JSDoc, [ARBITRARY.md](ARBITRARY.md), the migration guide, or the changeset until the corresponding +behavior has passed its semantic, runtime, and bundle gates. diff --git a/packages/effect/ARBITRARY-MIGRATION.md b/packages/effect/ARBITRARY-MIGRATION.md new file mode 100644 index 000000000..c40815c2d --- /dev/null +++ b/packages/effect/ARBITRARY-MIGRATION.md @@ -0,0 +1,335 @@ +# Migrating to Native Arbitrary + +This guide covers migration from the fast-check bridge published in `effect@4.0.0-rc.109` to the native, +Schema-first module at `effect/unstable/arbitrary`. + +The new module removes fast-check from the `effect` package. Applications may still install and use fast-check +directly, but Effect Schema generation and `@effect/vitest` property tests no longer depend on it. + +For the new API and its semantics, see [Arbitrary in Effect](ARBITRARY.md). + +## Import Changes + +The following APIs have been removed: + +- `effect/testing/FastCheck`; +- `Schema.toArbitrary`; +- `Schema.Arbitrary`; +- the legacy `Schema.Annotations.ToArbitrary` contract and declaration-level `toArbitrary` annotation; +- the legacy `arbitrary` filter annotation; +- raw fast-check arbitrary inputs and `fastCheck` options in `@effect/vitest`. + +Import the native module explicitly: + +```ts +import { Arbitrary } from "effect/unstable/arbitrary" +``` + +If other tests still use fast-check-specific APIs, add fast-check as a direct development dependency and import it +from `"fast-check"`. Do not import it through Effect. + +## Generating Samples + +Previously, `Schema.toArbitrary` returned a factory that needed the fast-check module: + +```ts +import { Schema } from "effect" +import { FastCheck } from "effect/testing" + +const Person = Schema.Struct({ + name: Schema.String, + age: Schema.Int +}) + +const personArbitrary = Schema.toArbitrary(Person)(FastCheck) +const samples = FastCheck.sample(personArbitrary, { numRuns: 20, seed: 42 }) +``` + +Now derive and sample through the Effect-native module: + +```ts +import { Effect, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const Person = Schema.Struct({ + name: Schema.String, + age: Schema.Int +}) + +const personArbitrary = Arbitrary.schema(Person) +const samples = await Effect.runPromise( + Arbitrary.sampleEffect(personArbitrary, { count: 20, seed: 42 }) +) +``` + +`Arbitrary.sampleEffect` returns an `Effect` because sampling is interruptible, uses Effect `Random` when no seed is +provided, and reports bounded generation exhaustion as a typed `SampleError`. + +The generated values still use the decoded Schema `Type`. The sequence and distribution are not compatible with +fast-check, even when the same numeric seed is used. + +## Checking Properties + +Previously, fast-check owned both the property and the runner: + +```ts +import { Schema } from "effect" +import { FastCheck } from "effect/testing" + +const integer = Schema.toArbitrary(Schema.Int)(FastCheck) + +FastCheck.assert( + FastCheck.property(integer, (value) => Number.isInteger(value)), + { numRuns: 100, seed: 42 } +) +``` + +Now `Arbitrary.checkEffect` runs a pure or Effectful property and returns a structured result: + +```ts +import { Effect, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const result = await Effect.runPromise( + Arbitrary.checkEffect( + Arbitrary.schema(Schema.Int), + (value) => Number.isInteger(value), + { runs: 100, seed: 42 } + ) +) +``` + +Unlike `FastCheck.assert`, `Arbitrary.checkEffect` does not throw for an ordinary falsification. Handle `Passed`, +`Falsified`, `Exhausted`, and `ReplayMismatch` explicitly, or use `@effect/vitest`, which converts non-passing results +into test failures. + +Typed failures from Effectful properties are preserved in `Falsified.failure`. Defects and interruption continue +through the returned Effect. + +## Option Mapping + +The most common options map as follows: + +| Previous fast-check option | Native option | Migration note | +| -------------------------- | -------------------- | ------------------------------------------------------------------ | +| `numRuns` | `count` or `runs` | Use `count` for `sampleEffect` and `runs` for `checkEffect`. | +| `seed` | `seed` | The type is compatible, but generated sequences are not. | +| `path` | `replay` | Existing fast-check paths cannot be converted. | +| `maxSkipsPerRun` | `maxDiscards` | Native uses one absolute discard budget, not a multiplier per run. | +| `examples` | No direct equivalent | Keep explicit regression cases as ordinary tests. | +| `endOnFailure` | `maxShrinks` | Use `maxShrinks: 0` to stop at the initial failure. | +| `interruptAfterTimeLimit` | Effect interruption | Apply an Effect or test timeout around the check. | +| `skipAllAfterTimeLimit` | No direct equivalent | Prefer explicit run and discard bounds. | +| `verbose` | No direct equivalent | Inspect `CheckResult` or use `@effect/vitest` failure output. | + +Review any less common fast-check runner option manually. The native API deliberately does not reproduce the complete +`fc.Parameters` surface. + +## Replay Migration + +Fast-check replay used a seed plus a shrink `path`. Native replay uses one opaque token returned by a `Falsified` +result: + +```ts +const replayed = Arbitrary.checkEffect(arbitrary, property, { + replay: previousFailure.replay +}) +``` + +There is no conversion from a fast-check seed and path to a native replay token. Re-run the property with the native +engine, then record the new token from its `Falsified` result. + +Replay tokens are intended for reproducing and diagnosing a current failure. Because the module is unstable, they are +not guaranteed to survive upgrades. Preserve important failing inputs as explicit regression tests. + +## Migrating Declaration Annotations + +The old `toArbitrary` annotation directly constructed a fast-check arbitrary and exposed fast-check recursion and +constraint details: + +```ts +import { Schema } from "effect" + +class UserId { + readonly value: number + constructor(value: number) { + this.value = value + } +} + +const UserIdSchema = Schema.instanceOf(UserId, { + toArbitrary: () => (fc) => fc.integer({ min: 1, max: 1_000_000 }).map((value) => new UserId(value)) +}) +``` + +The native `toCodecArbitrary` annotation describes a generatable representation as a Schema `Link`: + +```ts +import { Schema, SchemaTransformation } from "effect" + +class UserId { + readonly value: number + constructor(value: number) { + this.value = value + } +} + +const UserIdSchema = Schema.instanceOf(UserId, { + toCodecArbitrary: () => + Schema.link()( + Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1_000_000 })), + SchemaTransformation.transform({ + decode: (value) => new UserId(value), + encode: (id) => id.value + }) + ) +}) +``` + +Before adding `toCodecArbitrary`, check whether the declaration already has a useful `toCodecJson` or `toCodec`. +Native derivation falls back to those canonical codecs automatically. Add an arbitrary-specific Link only when the +canonical representation is opaque or generates valid values too rarely. + +The migration changes where generation logic lives: + +| Legacy contract | Native contract | +| ---------------------------------------------- | -------------------------------------------------------------- | +| Returns a `fast-check.Arbitrary` | Returns a `SchemaAST.Link` through `Schema.link`. | +| Receives generated arbitrary type parameters | Receives decoded Schema type parameters. | +| Receives fast-check constraints and recursion | Receives normalized constraints. | +| Manages terminal recursive branches explicitly | Leaves recursion analysis and budgets to the native compiler. | +| Uses arbitrary combinators | Uses Schema constructors, checks, and a Schema transformation. | + +The original declaration remains authoritative. Values decoded by the Link are checked against it. Failed decodes and +rejected values become bounded discards. + +### Custom Filter Metadata + +The old `arbitrary` filter annotation has been replaced by `arbitraryConstraint`. Ordinary custom filters continue to +work as residual filters without generation metadata: + +```ts +import { Schema } from "effect" + +const Even = Schema.Int.check( + Schema.makeFilter((value) => value % 2 === 0) +) +``` + +Residual filtering is bounded, so a very selective or impossible predicate may produce `SampleError` or `Exhausted`. + +If the previous annotation supplied a recognized constructive constraint, move it to `arbitraryConstraint` and adapt +its shape. The predicate remains authoritative: + +```ts +import { Order, Schema } from "effect" + +const Positive = Schema.Number.check( + Schema.makeFilter( + (value) => value > 0, + { + arbitraryConstraint: { + order: Order.Number, + minimum: 0, + exclusiveMinimum: true + } + } + ) +) +``` + +The main constraint-shape changes are: + +| Previous field | Native field | +| ------------------------------------ | ------------------------------------------------------------------- | +| `ordered.order` | `order` | +| `ordered.minimum` / `maximum` | `minimum` / `maximum` | +| `ordered.exclusiveMinimum` / maximum | `exclusiveMinimum: true` / `exclusiveMaximum: true` | +| `integer: true` | `number: "integer"` | +| `noNaN` and `noInfinity` | `number: "finite"` when both restrictions apply | +| collection `minLength` / `maxLength` | `minLength`, `minSize`, or `minProperties` and its matching maximum | +| string pattern | `{ source, flags }` in `patterns` | +| `unique: true` | `uniqueBy: identity` | +| `candidate` | No direct equivalent | + +Choose the cardinality field that matches the Schema domain: `minLength` and `maxLength` for strings and arrays, +`minSize` and `maxSize` for sized collections, and `minProperties` and `maxProperties` for object properties. + +For an opaque declaration that needs a reusable statistically better source domain, express that source as a Schema +Link with `toCodecArbitrary`. + +## Migrating `@effect/vitest` + +Property inputs may be Schemas, native Arbitraries, or mixtures of both. + +Schema-only properties need only an option rename: + +```ts +// Before +it.prop( + "commutative", + [Schema.Int, Schema.Int], + ([a, b]) => a + b === b + a, + { fastCheck: { numRuns: 200, seed: 42 } } +) + +// After +it.prop( + "commutative", + [Schema.Int, Schema.Int], + ([a, b]) => a + b === b + a, + { arbitrary: { runs: 200, seed: 42 } } +) +``` + +Raw or mixed fast-check inputs are no longer accepted: + +```ts +// No longer supported +it.prop("raw arbitrary", [fc.integer()], ([value]) => Number.isInteger(value)) +it.prop("mixed", [Schema.String, fc.integer()], ([text, value]) => true) +``` + +Replace those inputs with Schemas when they describe a domain supported by Schema, or compose a native Arbitrary: + +```ts +import { Arbitrary } from "effect/unstable/arbitrary" + +const integer = Arbitrary.schema(Schema.Int) + +it.prop("native arbitrary", [integer], ([value]) => Number.isInteger(value)) +it.prop("mixed", [Schema.String, integer], ([text, value]) => typeof text === "string" && Number.isInteger(value)) +``` + +If a test genuinely needs a fast-check-specific arbitrary or runner feature, use fast-check directly with Vitest +rather than passing it through `@effect/vitest`. + +`it.prop`, `it.effect.prop`, and `it.live.prop` all accept native check options under `arbitrary`. + +## Behavioral Differences to Review + +Migration is not only an import rename. Review the following differences: + +- native generation and shrinking have different distributions and may find different shrunk inputs; +- native checking returns structured results instead of using fast-check's assertion exceptions; +- generation that cannot find enough valid samples is bounded and reports `SampleError` or `Exhausted`; +- pure and Effectful properties share one interruptible runner; +- recursive and mutually recursive Schemas are analyzed as a graph and must have a finite generation path; +- replay tokens, seeds, and shrink paths are not compatible with fast-check; +- generated values are the decoded Schema `Type`; +- properties must not mutate generated values. + +## Migration Checklist + +1. Replace `effect/testing/FastCheck` imports. Use the native Arbitrary module for Schema generation and import + `"fast-check"` directly only where it is still independently required. +2. Replace `Schema.toArbitrary(schema)(FastCheck)` with `Arbitrary.schema(schema)`. +3. Replace `FastCheck.sample` with `Arbitrary.sampleEffect` and run the returned Effect. +4. Replace `FastCheck.check` or `FastCheck.assert` for Schema-derived inputs with `Arbitrary.checkEffect`, then handle its + structured result. +5. Rename `@effect/vitest` options from `fastCheck` to `arbitrary` and convert `numRuns` to `runs`. +6. Replace raw fast-check inputs in `@effect/vitest` with Schemas or native Arbitraries. +7. Migrate declaration-level `toArbitrary` callbacks to the `toCodecArbitrary` Link-returning contract and replace old + filter-level `arbitrary` annotations with `arbitraryConstraint`. +8. Re-run properties with the native engine and record new replay tokens or explicit regression examples. +9. Review discard limits for selective custom filters. diff --git a/packages/effect/ARBITRARY.md b/packages/effect/ARBITRARY.md new file mode 100644 index 000000000..9c7778024 --- /dev/null +++ b/packages/effect/ARBITRARY.md @@ -0,0 +1,1300 @@ +# From Examples to Laws: Property-Based Testing with Effect's `Arbitrary` + +Most unit tests choose a few inputs by hand and check the expected result for each one. Property-based testing asks the +computer to try many inputs for us. + +Instead of listing every expected answer, we write a **property**: a rule that should be true for every allowed input. +If the rule fails, the test tries simpler versions of the failing input. This simplification step is called +**shrinking**, and the failing input reported to the user is called a **counterexample**. + +Effect divides the work into three parts: + +- `Schema` describes which inputs are allowed. +- `Arbitrary` describes how to produce values of type `A` and how to simplify them after a failure. You can think of + it as an input generator with built-in shrinking. +- `it.prop`, `it.effect.prop`, or `Arbitrary.checkEffect` runs the rule against generated inputs. + +The API used here is currently available from `effect/unstable/arbitrary`. The `unstable` segment matters: +the ideas are stable, but names, result types, generation policies, and replay format may still change before this +module is promoted. + +If you are upgrading from the earlier Schema arbitrary integration available in `effect@4.0.0-rc.109`, see the +[migration guide](ARBITRARY-MIGRATION.md). + +## Writing a First Property + +Consider the rule “adding zero does not change an integer.” With `@effect/vitest`, we can write it directly: + +```ts +import { it } from "@effect/vitest" +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const integer = Arbitrary.schema(Schema.Int) + +it.prop("adding zero is identity", [integer], ([value]) => value + 0 === value) +``` + +`Arbitrary.schema` turns a Schema into a generator for the values represented by that Schema. `it.prop` tries the rule +100 times by default. It starts with small values, gradually tries more complex ones, and simplifies the first failing +input it finds. + +Effect keeps the allowed inputs visible. Even a one-argument property receives its inputs from an array or record: + +```ts +it.prop( + "addition is commutative", + { left: integer, right: integer }, + ({ left, right }) => left + right === right + left +) +``` + +That explicitness becomes useful as a test grows. Input names remain visible, and each input may come from either a +Schema or a pre-built `Arbitrary`. + +## Choosing the Inputs + +The rule and its allowed inputs belong together. A rule can be mathematically correct and still be unsuitable for the +values used by the program. + +For example, JavaScript cannot represent integers of every size exactly. If we want to test ordinary integer arithmetic +without overflow or loss of precision, we should restrict the inputs accordingly: + +```ts +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const SmallInt = Schema.Int.check( + Schema.isBetween({ minimum: -100, maximum: 100 }) +) + +const smallInt = Arbitrary.schema(SmallInt) +``` + +For common checks, such as numeric bounds and collection lengths, Effect generates matching values directly instead of +generating unsuitable values and rejecting them afterward. + +Use Schema to describe basic values and data structures. Then use the `Arbitrary` operations below when you need to +combine or transform those generated values. + +## Looking at Generated Values + +Sampling is useful while choosing your inputs. It is not a test by itself; it simply lets you see whether the generated +values have the shape and size you expected. + +```ts +import { Effect } from "effect" + +const examples = await Effect.runPromise( + Arbitrary.sampleEffect(smallInt, { + count: 10, + size: 5, + seed: "small-integers" + }) +) + +console.log(examples) +``` + +The same generator, seed, size, and Effect version produce the same sequence. A fixed seed is therefore useful in +documentation and while investigating a problem. Ordinary tests usually do not need one: after a failure, Effect +returns a **replay token**, a string that can reproduce both the failing input and the simplification steps that +followed. + +## Combining Generated Values + +In the rest of this guide, “generator” means an `Arbitrary` value. + +Use `Arbitrary.Constant` when a generator should always return an existing value. It is especially useful inside +`flatMap`, where one generated value chooses what should be generated next. A constant does not use randomness and +cannot be simplified further. If it contains an object, every run receives the same object, so the property must not +modify it. + +Use: + +- `map` to transform every generated value; +- `filter` to keep only values that pass a condition; +- `filterMap` to transform a value when the transformation may reject it. + +```ts +import { Result, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const integers = Arbitrary.schema(Schema.Int) + +const integerOrZero = Arbitrary.schema(Schema.Boolean).pipe( + Arbitrary.flatMap((useFallback) => useFallback ? Arbitrary.Constant(0) : integers) +) + +const nonNegativeLabels = integers.pipe( + Arbitrary.filter((value) => value >= 0), + Arbitrary.map((value) => `integer:${value}`) +) + +const positiveLabels = Arbitrary.filterMap( + integers, + (value) => value > 0 ? Result.succeed(`positive:${value}`) : Result.fail(value) +) +``` + +These operations also apply while Effect simplifies a failing value. A value rejected by `filter` or `filterMap` is not +passed to the property. Effect limits how many generated values may be rejected, so an impossible condition stops with +`SampleError` or `Exhausted` instead of searching forever. + +Prefer a Schema check when it can describe the allowed inputs directly. Effect can often generate matching values +immediately, whereas `filter` must first generate a value and then test it. + +Use `all` to generate independent Arbitraries together. It accepts tuples, other iterables, and records while preserving +their shape: + +```ts +const point = Arbitrary.all([ + Arbitrary.schema(Schema.Number), + Arbitrary.schema(Schema.Number) +]) + +const person = Arbitrary.all({ + name: Arbitrary.schema(Schema.String), + age: Arbitrary.schema(Schema.Int) +}) +``` + +Effect may generate the members in a different internal order, but the returned tuple positions and record keys always +match the input. After a failure, it simplifies one member at a time. Empty tuples and records produce empty values. + +Effect occasionally creates generated records without inherited `Object` methods. This can reveal code that assumes +methods such as `hasOwnProperty` always exist. Prefer `Object.hasOwn(value, key)` when checking generated objects. + +Use `flatMap` when one generated value decides what can be generated next. Create the possible generators before the +callback when you can, because creating a Schema inside the callback repeats that work each time it runs: + +```ts +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const Length = Arbitrary.schema( + Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 4 })) +) + +const StringsByLength = globalThis.Array.from({ length: 4 }, (_, index) => { + const length = index + 1 + return Arbitrary.schema( + Schema.String.check(Schema.isMinLength(length), Schema.isMaxLength(length)) + ) +}) + +const SizedString = Length.pipe( + Arbitrary.flatMap((length) => StringsByLength[length - 1]) +) +``` + +After a failure, Effect first tries simpler values from the first generator and rebuilds the dependent value. It then +tries simpler values from the selected dependent generator. This usually produces a small pair of related values +without breaking the relationship between them. + +Callbacks passed to these operations must return normally, always produce the same result for the same input, finish +in a reasonable time, and avoid modifying generated values. Effect may call them again while simplifying or replaying +a failure. If a callback throws, `sampleEffect` or `checkEffect` reports a **defect**, Effect's term for an unexpected +failure. + +### Targeting Rare Scenarios + +Some behavior can only be tested with a particular combination or sequence of inputs. Making one value appear more +often does not guarantee that combination. For example, generating `Remove` more often still does not guarantee that +it follows an `Insert` for the same key. + +Keep a general property over all command types, then add a focused property whose input already contains the +important transition: + +```ts +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const Key = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 20 })) + +const Insert = Schema.Struct({ + _tag: Schema.Literal("Insert"), + value: Key +}) + +const Remove = Schema.Struct({ + _tag: Schema.Literal("Remove"), + value: Key +}) + +const Contains = Schema.Struct({ + _tag: Schema.Literal("Contains"), + value: Key +}) + +const Command = Schema.Union([Insert, Remove, Contains]) + +const arbitraryHistory = Arbitrary.schema( + Schema.Array(Command).check(Schema.isMaxLength(50)) +) + +const RemoveExistingScenario = Schema.Struct({ + before: Schema.Array(Command).check(Schema.isMaxLength(20)), + key: Key, + after: Schema.Array(Command).check(Schema.isMaxLength(20)) +}) + +const historyContainingRemoveExisting = Arbitrary.schema(RemoveExistingScenario).pipe( + Arbitrary.map(({ before, key, after }) => [ + ...before, + { _tag: "Insert" as const, value: key }, + { _tag: "Remove" as const, value: key }, + ...after + ]) +) +``` + +Use `arbitraryHistory` to explore interactions that were not anticipated. Use `historyContainingRemoveExisting` in a +separate property for behavior that specifically requires removing an existing key. + +This is more reliable than a `frequency` operation, which would only make one choice more likely: + +- every focused run contains the required sequence; +- requirements such as using the same key are visible in the generated input; +- `map` rebuilds the required `Insert` and `Remove` after each simplification, so the final counterexample still tests + the intended behavior; +- the general property remains free to explore all command types. + +If you need to reproduce production traffic proportions, measure throughput, or simulate a random process, use a +workload generator or simulation instead. Those tasks care about exact frequencies; a property test cares about +finding a small input that breaks a rule. + +## Running a Property Directly + +`it.prop` is the shortest way to use a property in a test. Use `Arbitrary.checkEffect` when your program needs to inspect +the result instead of immediately failing a Vitest test. It accepts a function that returns either a boolean or an +`Effect`, and it simplifies the first failing input: + +```ts +import { Effect, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const values = Arbitrary.schema(Schema.Array(Schema.Int)) + +const program = Arbitrary.checkEffect( + values, + (input) => input.slice().reverse().reverse().every((value, index) => value === input[index]), + { runs: 100, seed: "reverse" } +) + +await Effect.runPromise(program) +// { _tag: "Passed", runs: 100, discards: 0 } +``` + +A property may also return an `Effect`, so it can use Effect services or fail through the Effect error channel: + +```ts +import { Effect, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const program = Arbitrary.checkEffect( + Arbitrary.schema(Schema.String), + (value) => Effect.succeed(value.length >= 0) +) +``` + +A failed check is returned as a value. Read its `_tag` to see what happened: + +| Result | Meaning | +| ---------------- | -------------------------------------------------------------------------------------- | +| `Passed` | Every requested run passed. | +| `Falsified` | The rule returned `false` or its Effect failed. Includes the simplified failing input. | +| `Exhausted` | Too many generated values were rejected. Includes the seed needed to repeat the run. | +| `ReplayMismatch` | A replay token no longer leads to the same kind of failure. | + +When the returned Effect fails, `Falsified.failure` is a `PropertyError` containing its error value. Returning `false` +produces `ReturnedFalse`. + +Effect keeps those two kinds of failure separate while simplifying an input. A proposed simpler input is not accepted +as the new counterexample if it changes from “returned false” to “Effect failed,” or the other way around. The actual +error value may change; only the kind of failure must stay the same. + +Defects and Effect interruption continue through the Effect returned by `checkEffect` instead of becoming `CheckResult` +values. This allows timeouts and the Effect that started the check to interrupt it normally. + +For the same input and environment, a property must always produce the same result. It must also treat generated values +as read-only. Effect may evaluate a value more than once while simplifying or replaying a failure, and it does not undo +changes made by the property. + +## Turning Requirements into Properties + +Many useful properties are equations. Requirements often contain words such as “same,” “independent of order,” +“reversible,” or “normalizes.” Each word suggests a rule that compares two or more executions of the same code. Such a +rule is often called a **law**. + +### Changing Order or Grouping (Commutativity and Associativity) + +```ts +import { it } from "@effect/vitest" +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const smallInt = Arbitrary.schema( + Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })) +) + +it.prop( + "addition is commutative", + { a: smallInt, b: smallInt }, + ({ a, b }) => a + b === b + a +) + +it.prop( + "addition is associative", + { a: smallInt, b: smallInt, c: smallInt }, + ({ a, b, c }) => (a + b) + c === a + (b + c) +) +``` + +The bounds are part of the meaning of these tests. They keep the generated values inside the region where JavaScript +integer arithmetic behaves like the algebra we intend to test. + +### Running an Operation Twice Changes Nothing (Idempotence) + +Normalization is commonly idempotent: once a value is normalized, applying the operation again should do nothing. + +```ts +const clampNonNegative = (value: number): number => Math.max(0, value) + +it.prop( + "clamping is idempotent", + [smallInt], + ([value]) => clampNonNegative(clampNonNegative(value)) === clampNonNegative(value) +) +``` + +### Applying an Operation Twice Returns the Original (Involution) + +An involution returns to the original value when applied twice. Negation is the smallest example: + +```ts +it.prop( + "negation is an involution", + [smallInt], + ([value]) => -(-value) === value +) +``` + +### Comparing Two Implementations + +When replacing or optimizing an implementation, compare the two functions over the same generated inputs: + +```ts +const doubleByAddition = (value: number): number => value + value +const doubleByMultiplication = (value: number): number => value * 2 + +it.prop( + "the two double implementations agree", + [smallInt], + ([value]) => doubleByAddition(value) === doubleByMultiplication(value) +) +``` + +### Operations That Undo Each Other + +```ts +const increment = (value: number): number => value + 1 +const decrement = (value: number): number => value - 1 + +it.prop( + "increment and decrement are inverses", + [smallInt], + ([value]) => decrement(increment(value)) === value +) +``` + +The `Arbitrary` module does not provide a separate helper for each kind of law. These comparisons are short to write +directly in TypeScript. A project that applies the same laws to many data types can still define its own reusable test +functions. + +## Required Inputs and Common Traps + +Each property should be able to fail, be easy to explain, and come from a real requirement. Adding more conditions +without clarifying the allowed inputs often makes a test harder to understand rather than more useful. + +### Operations That Reject Some Inputs + +If an operation only accepts some inputs, describe that restriction in the Schema when possible. For division, a union +can generate non-zero integers directly: + +```ts +const NonZeroSmallInt = Schema.Union([ + Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: -1 })), + Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })) +]) + +const nonZeroSmallInt = Arbitrary.schema(NonZeroSmallInt) + +it.prop("a non-zero integer divided by itself is one", [nonZeroSmallInt], ([value]) => value / value === 1) +``` + +Use `Arbitrary.filter` when Schema cannot express the condition: + +```ts +const odd = smallInt.pipe( + Arbitrary.filter((value) => value % 2 !== 0) +) +``` + +Rejected generated values count against `maxDiscards`. If very few values pass the filter, the check may return +`Exhausted` before running the property enough times. Prefer a Schema check or `flatMap` when either can generate valid +values directly. + +### Floating-Point Laws + +Floating-point arithmetic needs a different rule. Use `Schema.Finite` when `NaN` and infinities are outside the allowed +inputs. When exact equality is not the real requirement, compare results with an acceptable error tolerance. + +### Mutable Properties + +Effect does not clone generated inputs before evaluating them. A property that changes its input may change the +reported counterexample and make simplification or replay unreliable. Treat generated values as read-only, and create +mutable test data inside each property evaluation rather than sharing it between runs. + +## Laws Can Still Miss Visible Bugs + +Passing a familiar list of laws does not prove that every public operation is correct. Two values may represent the +same logical result while an operation accidentally depends on how they are stored internally. + +Consider an immutable first-in-first-out queue represented by a front array and a reversed rear array. The balancing +and update operations below are correct, but `front` deliberately reads the last front element instead of the first: + +```ts +import { assert, describe, it } from "@effect/vitest" +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +interface Queue { + readonly front: ReadonlyArray + readonly rear: ReadonlyArray +} + +const balance = (front: ReadonlyArray, rear: ReadonlyArray): Queue => + front.length === 0 ? { front: [...rear].reverse(), rear: [] } : { front, rear } + +const empty = (): Queue => balance([], []) +const isEmpty = (queue: Queue): boolean => queue.front.length === 0 + +const enqueue = (value: number, queue: Queue): Queue => balance(queue.front, [value, ...queue.rear]) + +const dequeue = (queue: Queue): Queue => balance(queue.front.slice(1), queue.rear) + +// Deliberately wrong: a FIFO queue should return queue.front[0]. +const front = (queue: Queue): number => queue.front[queue.front.length - 1]! + +const toArray = (queue: Queue): ReadonlyArray => [ + ...queue.front, + ...[...queue.rear].reverse() +] + +const equals = (left: Queue, right: Queue): boolean => { + const a = toArray(left) + const b = toArray(right) + return a.length === b.length && a.every((value, index) => value === b[index]) +} + +const Item = Schema.Int.check( + Schema.isBetween({ minimum: -100, maximum: 100 }) +) +const Items = Schema.Array(Item).check(Schema.isMaxLength(8)) +const NonEmptyItems = Items.check(Schema.isMinLength(1)) + +const item = Arbitrary.schema(Item) +const items = Arbitrary.schema(Items) +const nonEmptyItems = Arbitrary.schema(NonEmptyItems) + +const queue = Arbitrary.all({ + front: items, + rear: items +}).pipe( + Arbitrary.map(({ front, rear }) => balance(front, rear)) +) + +const nonEmptyQueue = Arbitrary.all({ + front: nonEmptyItems, + rear: items +}).pipe( + Arbitrary.map(({ front, rear }) => balance(front, rear)) +) +``` + +The queue is intended to satisfy these equations: + +1. `isEmpty(empty()) === true` +2. `isEmpty(enqueue(x, q)) === false` +3. `front(enqueue(x, empty())) === x` +4. For non-empty `q`, `front(enqueue(x, q)) === front(q)` +5. `dequeue(enqueue(x, empty()))` equals `empty()` +6. For non-empty `q`, `dequeue(enqueue(x, q))` equals `enqueue(x, dequeue(q))` + +All six pass, even with the broken `front` operation: + +```ts +describe("queue laws", () => { + it("Q1", () => assert.isTrue(isEmpty(empty()))) + + it.prop("Q2", [item, queue], ([x, q]) => !isEmpty(enqueue(x, q))) + + it.prop("Q3", [item], ([x]) => front(enqueue(x, empty())) === x) + + it.prop( + "Q4", + [item, nonEmptyQueue], + ([x, q]) => front(enqueue(x, q)) === front(q) + ) + + it.prop( + "Q5", + [item], + ([x]) => equals(dequeue(enqueue(x, empty())), empty()) + ) + + it.prop( + "Q6", + [item, nonEmptyQueue], + ([x, q]) => equals(dequeue(enqueue(x, q)), enqueue(x, dequeue(q))) + ) +}) +``` + +The equations compare queues through `equals`, which checks their logical sequence. But replacing a queue with an equal +queue must not change the answer returned by `front`. + +We can test that hidden requirement by substituting both sides of Q6 into `front`: + +```ts +it.prop( + "front agrees after the Q6 rewrite", + [item, nonEmptyQueue], + ([x, q]) => { + const left = dequeue(enqueue(x, q)) + const right = enqueue(x, dequeue(q)) + return front(left) === front(right) + }, + { + // This flag belongs only in the tutorial while `front` is intentionally broken. + fails: true, + arbitrary: { + runs: 1_000, + size: 10, + seed: "front-after-rewrite" + } + } +) +``` + +This property fails and simplifies to a small queue. The first six laws say that two queue expressions are equal. The +new property also checks that `front` gives the same answer for both expressions. + +The general lesson is simple: whenever a law says `left` and `right` are equal, try both values as inputs to public +operations. Their results should remain equal. This does not prove every possible combination, but it can reveal code +that accidentally reads an internal representation. + +## Comparing with a Simple Model + +Some systems are easier to check against a small, straightforward implementation than against a list of equations. +This simpler implementation is called a **model**. It may be slower than the real code; its advantage is that it is +easy to understand and trust. + +The following property checks Effect's immutable `HashSet` against JavaScript's mutable `Set`. Instead of generating +sets directly, it generates command sequences and runs the same history against both implementations. + +```ts +import { it } from "@effect/vitest" +import { HashSet, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const Key = Schema.Int.check( + Schema.isBetween({ minimum: 0, maximum: 20 }) +) + +const Command = Schema.Union([ + Schema.Struct({ _tag: Schema.Literal("Insert"), value: Key }), + Schema.Struct({ _tag: Schema.Literal("Remove"), value: Key }), + Schema.Struct({ _tag: Schema.Literal("Contains"), value: Key }) +]) + +const commands = Arbitrary.schema( + Schema.Array(Command).check(Schema.isMaxLength(50)) +) + +it.prop("HashSet agrees with the Set model", [commands], ([input]) => { + const model = new Set() + let actual = HashSet.empty() + const modelTrace: Array = [] + const actualTrace: Array = [] + + for (const command of input) { + switch (command._tag) { + case "Insert": + model.add(command.value) + actual = HashSet.add(actual, command.value) + break + + case "Remove": + model.delete(command.value) + actual = HashSet.remove(actual, command.value) + break + + case "Contains": + modelTrace.push(model.has(command.value)) + actualTrace.push(HashSet.has(actual, command.value)) + break + } + } + + return modelTrace.length === actualTrace.length && + modelTrace.every((value, index) => value === actualTrace[index]) && + model.size === HashSet.size(actual) && + [...model].every((value) => HashSet.has(actual, value)) +}) +``` + +The property compares two things: + +- the recorded answers verify each `Contains` operation; +- the final membership and size checks verify that both states ended with the same contents. + +When this test fails, Effect simplifies the command list and its values. The final counterexample is often a short +history that clearly shows which state change the implementation handled incorrectly. + +## Replaying a Failure + +Every `Falsified` result contains a `replay` token. It is a string produced by Effect and is not meant to be edited. The +token records enough information to generate the same initial input and repeat the simplification steps that led to the +reported counterexample: + +```ts +import { Effect, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const arbitrary = Arbitrary.schema(Schema.Int) + +const program = Effect.gen(function*() { + const first = yield* Arbitrary.checkEffect(arbitrary, (value) => value < 10, { + seed: "integer-bound" + }) + + if (first._tag === "Falsified") { + const replayed = yield* Arbitrary.checkEffect( + arbitrary, + (value) => value < 10, + { replay: first.replay } + ) + return replayed + } + + return first +}) +``` + +Store the token in logs or failure output when you want to reproduce a failure locally. Because this module is +unstable, a token created by one Effect release may not work with another. For a permanent regression test, copy the +final failing input into an ordinary example-based test. + +When `replay` is present, its recorded settings replace `runs`, `size`, `maxDiscards`, `maxShrinks`, and `seed`. + +Effect returns `ReplayMismatch` when the Schema, property, or generator has changed enough that the token no longer +leads to the same kind of failure. The token does not store the exact final input or exact Effect error, so replay may +still succeed if the error value changes while the property continues to fail in the same way. + +## Sampling Options + +`Arbitrary.sampleEffect` accepts the following options: + +| Option | Default | Meaning | +| ------------- | ---------------------------- | ---------------------------------------------------------- | +| `count` | `10` | Number of values to return. | +| `size` | `10` | Rough complexity of each generated value. | +| `maxDiscards` | `max(100, count * 10)` | Maximum rejected values before failing with `SampleError`. | +| `seed` | A value from Effect `Random` | String or number used to reproduce the generated sequence. | + +If too many values are rejected, the Effect fails with a `SampleError`. The error contains the number of accepted and +rejected values and the seed used for the run. Passing that seed to another `sampleEffect` call repeats the run even if +the first call did not specify a seed. + +## Check Options + +`Arbitrary.checkEffect` accepts the following options: + +| Option | Default | Meaning | +| ------------- | ---------------------------- | ---------------------------------------------------------------------- | +| `runs` | `100` | Number of successful generations and property evaluations to complete. | +| `size` | `10` | Largest approximate input complexity. It grows during the check. | +| `maxDiscards` | `max(100, runs * 10)` | Maximum rejected values before returning `Exhausted`. | +| `maxShrinks` | `100` | Maximum simplifications tried after the first failure. | +| `seed` | A value from Effect `Random` | String or number used to reproduce generation. | +| `replay` | None | Token from a previous `Falsified` result. | + +Rejected values do not count toward `runs`, and the input size does not grow after a rejection. When `runs` is `1`, +Effect uses the configured `size` immediately. + +After a failure, `maxShrinks` counts every simpler input that Effect examines, including values rejected by a filter and +values that fail in a different way. The `shrinks` field in a `Falsified` result counts only the simplifications that +became the new best counterexample. + +`size` is a guide rather than a maximum length for the whole result. Separate strings, collections, and object fields +may each use it. Recursive structures share it so that the complete value remains finite. Explicit Schema minimum and +maximum checks still take priority. + +## How Effect Builds a Generator from Schema + +This section is useful when a Schema contains custom checks, recursion, or declarations. If you only use ordinary +Schemas, you can skip to [Using `@effect/vitest`](#using-effectvitest). + +`Arbitrary.schema` generates the value represented by a Schema after decoding. For example, +`Schema.NumberFromString` represents a number encoded as a string, so its generator produces numbers rather than +strings. + +Effect prepares the generator when `Arbitrary.schema` is called. If it cannot support the Schema, the call throws +immediately rather than returning a generator that fails later. + +### Schema Checks and Rejected Values + +Effect can use many common Schema checks while generating values, including: + +- minimum and maximum values, including values that use a custom `Order`; +- finite and integer numbers; +- minimum and maximum lengths for strings, collections, and object properties; +- supported regular-expression patterns; +- checks that collection values are unique. + +For these checks, Effect produces matching values directly. It applies other custom checks after generation. A value +that fails one of those checks is rejected. + +`maxDiscards` limits how many values may be rejected. An impossible or very selective check therefore produces +`SampleError` or `Exhausted` instead of searching forever. + +### Custom Shrinking + +The default simplification is usually enough: Effect removes collection items, moves numbers and strings toward +simpler values, and simplifies object or tuple fields one at a time while preserving Schema checks. Use the `shrink` +option when your application has a useful simplification that Effect cannot infer from the data structure. + +For example, an addition expression can be replaced directly by either side. The default behavior cannot infer that +meaning from the object shape: + +```ts +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +interface Literal { + readonly _tag: "Literal" + readonly value: number +} + +interface Add { + readonly _tag: "Add" + readonly left: Expression + readonly right: Expression +} + +type Expression = Literal | Add + +const Expression: Schema.Codec = Schema.suspend(() => + Schema.Union([ + Schema.Struct({ + _tag: Schema.Literal("Literal"), + value: Schema.Int + }), + Schema.Struct({ + _tag: Schema.Literal("Add"), + left: Expression, + right: Expression + }) + ]) +) + +const shrinkExpression = (expression: Expression): ReadonlyArray => { + switch (expression._tag) { + case "Literal": + return expression.value === 0 ? [] : [{ _tag: "Literal", value: 0 }] + case "Add": + return [ + expression.left, + expression.right, + ...shrinkExpression(expression.left).map((left) => ({ ...expression, left })), + ...shrinkExpression(expression.right).map((right) => ({ ...expression, right })) + ] + } +} + +const expressions = Arbitrary.schema(Expression, { + shrink: shrinkExpression +}) +``` + +Initial values still come from `Expression`. After a property fails, `shrinkExpression` tells Effect which simpler +values to try next. Replacing `Add(left, right)` with `left` or `right` can reach a useful counterexample much faster +than changing one field at a time. + +Before the property sees a proposed value, Effect checks it with `Expression`. Invalid values are skipped and consume +one unit of `maxShrinks`; Effect does not ask the callback to simplify them further. The Schema therefore remains the +final authority even if the callback contains a cast or calls untyped code. + +Effect calls the callback only after a property fails. It may call it again during simplification or replay. The +callback must therefore finish normally, always return the same proposed values for the same input, and avoid modifying +data. It should list the most useful simplifications first. Returning `[]` means that the current value cannot be +simplified. + +A custom `shrink` callback replaces the default behavior. Keep the default when simplifying fields and collection +items is enough. Prefer `map` or `toCodecArbitrary` when you can generate a simpler representation and transform it. +Use `shrink` only for application-specific shortcuts that those approaches cannot express clearly. + +### Recursive Schemas + +Recursive Schemas are supported as long as there is a way for generation to stop: + +```ts +import { Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +interface Node { + readonly value: string + readonly children: ReadonlyArray +} + +const Node: Schema.Codec = Schema.Struct({ + value: Schema.String, + children: Schema.Array(Schema.suspend(() => Node)).check(Schema.isMaxLength(3)) +}) + +const nodes = Arbitrary.schema(Node) +``` + +Here, an empty `children` array stops the recursion. Effect limits recursion across the whole generated value so that +separate branches cannot each grow without regard for the others. + +Effect ignores a recursive alternative that can never stop. If the complete Schema has no way to produce a finite +value, `Arbitrary.schema` throws immediately. You do not need to provide a special terminal generator or depth marker. + +### Declaration Schemas + +`Schema.declare` can describe a type whose internal structure is hidden from the generic Schema machinery. To generate +such a value, Effect looks for a simpler Schema representation in this order: + +1. an explicit `toCodecArbitrary`; +2. a representation provided by Effect for one of its built-in types; +3. `toCodecJson`; +4. `toCodec`. + +Most declarations already provide a usable conversion and need no Arbitrary-specific setup. Add `toCodecArbitrary` +only when the usual representation cannot be generated or produces poor test inputs. + +If `toCodecJson` is present but returns `undefined`, the declaration explicitly says that its JSON representation is +hidden. Generation stops with an error instead of trying `toCodec`. + +`toCodecArbitrary` returns a Schema `Link`, not an `Arbitrary`. The first Schema in the link describes values that are +easy to generate, and the transformation converts them into the declared type: + +```ts +import { Schema, SchemaTransformation } from "effect" + +class UserId { + readonly value: number + constructor(value: number) { + this.value = value + } +} + +const UserIdSchema = Schema.instanceOf(UserId, { + toCodecArbitrary: () => + Schema.link()( + Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1_000_000 })), + SchemaTransformation.transform({ + decode: (value) => new UserId(value), + encode: (id) => id.value + }) + ) +}) +``` + +Effect checks converted values against the original declaration. The conversion may reject some values; those values +count toward `maxDiscards`, and Effect can continue with others. Normal decode errors reject a value, while unexpected +defects and interruption fail the sampling or checking Effect. + +The callback also receives: + +- the value types represented by declarations with type parameters; +- the common Schema checks that Effect recognized for the declaration. + +Built-in collection types use arrays as their simpler generation representation. Map keys and set values remain +unique. Effect collections such as `HashMap`, `HashSet`, and `Chunk`, and types such as `Graph`, `BigDecimal`, and date +and time values, provide their own links when that produces better inputs. A declaration whose usual conversion already +works needs no Arbitrary-specific annotation. + +## Using `@effect/vitest` + +`@effect/vitest` accepts arrays or records containing Schemas, Arbitraries, or both: + +```ts +import { assert, it } from "@effect/vitest" +import { Effect, Schema } from "effect" +import { Arbitrary } from "effect/unstable/arbitrary" + +const Name = Arbitrary.schema(Schema.Literals(["Ada", "Grace"])) + +it.prop( + "integer addition is commutative", + [Schema.Int, Schema.Int], + ([a, b]) => a + b === b + a, + { arbitrary: { runs: 200, seed: "addition" } } +) + +it.effect.prop( + "generated values can be checked in an Effect", + { name: Name, value: Schema.Int }, + ({ name, value }) => + Effect.sync(() => { + assert.include(["Ada", "Grace"], name) + assert.isTrue(Number.isInteger(value)) + }), + { arbitrary: { runs: 50 } } +) +``` + +Generators from other property-testing libraries are not supported. Use Effect Arbitraries when an input needs more +composition than a Schema alone can provide. + +`@effect/vitest` turns `Falsified`, `Exhausted`, and `ReplayMismatch` results into test failures. The failure message +includes the simplified failing input and replay token. Diagnostic values use `Formatter.format`, so strings remain quoted and +escaped, collections such as `Map` and `Set` retain their structure, and application values can provide a custom +`toString`. + +The Vitest integration reports a property failure when the property returns `false`, throws, or returns a failed Effect +for a reason other than interruption. It then simplifies and reports the failing input. Effect interruption remains an +interruption. Any normal return value other than `false`, including `void`, passes for that input. + +The Vitest `timeout` interrupts the Effect fiber that generates inputs, runs the property, and simplifies failures. +Effect finalizers still run. A timeout cannot stop a synchronous JavaScript callback until that callback returns. + +## Current Limitations + +The module starts from Schema and currently does not provide: + +- separate Arbitrary constructors for strings, numbers, arrays, and objects; describe those inputs with Schema; +- a public low-level generator constructor or direct access to simplification steps; +- weighted choice; use [targeted scenarios](#targeting-rare-scenarios) to guarantee that a test reaches an important + situation; +- support for generators from other property-testing libraries; +- running generated inputs in parallel; +- automatic test failure messages outside `@effect/vitest`; +- replay tokens guaranteed to work across releases of this unstable module. + +These limitations may change while the module remains unstable. + +## Summary + +Property-based testing is most useful when the property says more than “this function did not throw.” Start by choosing +the allowed inputs and a rule that comes from the requirement: + +- use Schema checks to generate valid values; +- combine independent inputs with records, tuples, or `Arbitrary.all`; +- use `it.prop` and `it.effect.prop` in tests, and `checkEffect` when result values or replay are part of the + program; +- write laws directly and remember the limits of JavaScript numbers; +- when two values are considered equal, check that public operations treat them the same way; +- use command sequences and a simple model for stateful behavior; +- pay attention to rejected values, simplification, and replay because they affect what the test actually checks. + +The most important choice is not how many random inputs to generate. It is choosing inputs and a rule that make a small +counterexample useful. + +## Appendix: Design and Implementation Decisions + +This appendix is for people who maintain or extend the module. You can use `Arbitrary` without reading it. Internal type +names appear only where they help connect a decision to the code, and each one is explained when it first appears. + +### Why Effect Has Its Own Implementation + +Effect owns the property-testing implementation so it can provide: + +- **Automatic support for recursion.** Effect finds the paths that let recursive Schemas stop. If no such path exists, + `Arbitrary.schema` fails immediately. All recursive branches in one value share the same limit. +- **A limit on rejected values.** `maxDiscards` stops generation when a condition is impossible or accepts too few + values. `maxShrinks` also limits rejected values examined while simplifying a failure. +- **Normal Effect behavior.** Sampling and checking can be interrupted, use services, and fail through Effect. Direct + checks, `TestSchema`, and `@effect/vitest` use the same implementation. +- **Direct composition.** `all` combines existing Arbitraries without creating a temporary Schema. +- **One replay token.** The token records the initial attempt and every accepted simplification. Replay regenerates the + initial value, runs the property again, and follows those simplifications. +- **Useful behavior for `flatMap`.** When the first generated value becomes simpler, Effect rebuilds the value that + depends on it while keeping later random choices stable. Nested `flatMap` calls share the same recursion limit. +- **Small common paths.** Common Schema shapes use direct loops, and sampling does not build simplification data. Bundle + and runtime benchmarks watch these paths, but do not promise an exact performance level. + +### Interface Design Decisions + +These decisions explain why the public API contains some operations and omits others. Each section also says what new +use case would justify changing the decision. + +#### Use Schema as the Public Generation Language + +**Status:** Use Schema as the single public language for describing generated data. + +Schema already describes primitive values, data structures, checks, conversions, declarations, and recursion. +Duplicating those features with `Arbitrary.String`, `Arbitrary.Array`, and similar constructors would give users two +different ways to describe the same inputs. + +The module therefore exposes `schema` and a small set of operations for combining existing Arbitraries. A declaration +whose structure is hidden can provide a simpler Schema through `toCodecArbitrary`; it does not need a second language +for generators. + +The implementation stored inside `Arbitrary` remains private. Users can combine and run an Arbitrary without +depending on how Effect currently generates or simplifies values. Reconsider this decision only if an important input +cannot be described by Schema or built clearly from existing Arbitraries. + +#### Keep Low-Level Arbitrary Construction Internal + +**Status:** Do not expose `Arbitrary.make` for the current internal generator type. + +Applications can already build inputs through: + +- `Arbitrary.schema` for primitive values, data structures, checked values, and recursive values; +- `Constant` for lifting an already constructed value into dependent generation; +- `map`, `filter`, `filterMap`, `flatMap`, and `all` for composition; +- `toCodecArbitrary` when a Schema declaration hides its structure and needs a simpler Schema for generation. + +The internal `Generator` does more than turn random numbers into values. It also tracks the minimum space needed for +recursion, rejected values, later simplifications, interruption, and replay positions. Exposing it would require every +custom generator to understand those rules. + +A simpler public constructor that ignores some rules would create Arbitraries that behave differently when combined or +simplified. Reconsider this decision when at least two real generators cannot be expressed through Schema and the +existing operations. Any proposal should keep the internal bookkeeping out of application code. + +#### Provide a Constant Constructor + +**Status:** Provide `Arbitrary.Constant` for an existing value. + +Without `Constant`, callers must generate an unrelated value and map it to the desired constant. `Schema.Literal` +supports only literal values and is awkward to rebuild repeatedly inside `flatMap`. + +`Constant` uses no randomness and offers no simpler values of its own. The value that selected it through `flatMap` can +still be simplified. Every run returns the same value, so objects are not cloned and properties must not modify them. + +#### Use Schema Union for Static Choice + +**Status:** Do not add a second operation for choosing uniformly among fixed alternatives. + +`Arbitrary.schema(Schema.Union([...]))` already chooses among alternatives. Because the alternatives remain Schemas, +Effect can see their checks, recursion needs, and validation rules. `Arbitrary.oneOf` would duplicate that behavior +without supporting a new use case. + +A weighted choice would add probabilities, but probabilities do not guarantee that a property reaches an important +situation. The later decision about weighted choice explains the preferred alternative. + +#### Keep Size Management Internal + +**Status:** Do not pass the current `size` to an `Arbitrary.sized` callback. + +The runner's `size` option already lets strings, collections, and recursive Schemas become more complex as a check +progresses. Most users therefore do not need to write their own size rules. + +Passing this value to callbacks would make the runner's growth policy part of the public composition API. It is mainly +useful for low-level custom generators, which the module does not currently expose. Reconsider this decision when an +important input needs a size rule that Schema checks and recursion cannot describe. + +#### Keep Custom Shrinking at the Schema Boundary + +**Status:** Add custom simplification only through `Arbitrary.schema(schema, { shrink })`. + +The Schema produces every initial value and checks every value proposed by the callback. Invalid proposals are skipped; +valid proposals can be simplified again with the same callback. Effect calls the callback only when needed and records +its stable order for replay. + +The callback replaces the default simplification order. Mixing two independent orders would make `maxShrinks` and +replay positions hard to predict. + +A general `Arbitrary.reshrink` could not provide the same automatic validation. An Arbitrary built with `Constant`, +`map`, `flatMap`, or `all` does not retain one original Schema. Reconsider a general operation only when a real input +cannot be represented by Schema and needs its own simplification. A proposal must explain how values are checked, how +the two simplification strategies interact, and how replay remains stable. + +#### Prefer Targeted Scenarios to Weighted Choice + +**Status:** Do not add `Arbitrary.frequency` merely to reach important test cases more often. + +Weights only change how often a branch is chosen. They cannot guarantee the combination of values that makes a behavior +important. A general property over `Schema.Union`, plus a focused property whose input already contains the important +situation, states the requirement directly and preserves it during simplification. + +Workloads that must match real probabilities belong in simulations or benchmarks. Reconsider weighted generation only +if a correctness rule itself depends on a distribution, rather than merely needing better test coverage. + +#### Name Effectful Runners Explicitly + +**Status:** Keep the `Effect` suffix on `sampleEffect` and `checkEffect`. + +The names show that these functions return an Effect. They also leave clear names available if a real need for +synchronous versions appears later; no such version is currently promised. + +#### Current Interface Scope + +The module focuses on inputs described by Schema and checks executed with Effect. It guarantees that generated values +match their Schema, rejection is limited, recursion stops, and replay works within the same implementation. Exact +generated sequences, probabilities, and intermediate simplifications may change. + +The unstable interface does not currently expose: + +- separate Arbitrary constructors for primitive values and structures, or direct access to internal samples and + simplification steps; +- weighted distribution controls; +- custom simplification that is not attached to a Schema; +- support for generators from other libraries or a broad set of runner settings; +- parallel property evaluation; +- test-runner assertion integration outside `@effect/vitest`; +- replay tokens guaranteed to work across releases. + +These omissions are not necessarily permanent. They let Schema remain responsible for describing valid values and let +Effect change the internal generator while each proposed API addition is considered separately. + +### Technical Decisions + +The remaining sections describe the current implementation. These details matter to maintainers because changing them +can alter generated values, simplification, replay, performance, or bundle size without changing the public types. + +#### Representing Generation + +- The internal `Generator` stores `minCost`, the minimum space needed to produce a value, and `generate`, the + generation function. A Schema being prepared uses `Compiled`, which also records its dependencies. Arbitrary + operations outside Schema do not join that dependency graph. +- One call returns `Generated` when it produced a value or `Discarded` when it rejected the attempt. It returns directly + when possible and uses an Effect only when needed. Internal mapping uses Effect's eager operations so an immediate + result remains synchronous. +- A generated value may provide a `Pull`: an internal operation that returns one proposed simplification at a time. + Effect creates this sequence only when needed. Sampling does not create it, and rejected proposals remain visible to + the runner so they count toward `maxShrinks`. Operations such as `filter` can skip a rejected proposal and continue + with simpler values that follow from it. + +#### Preparing a Schema + +- Preparation starts with `SchemaAST.toType`, which selects the value after decoding rather than its encoded form. +- The implementation reads Schema's existing internal syntax tree, called its AST, instead of creating a second tree + for Arbitrary. This preserves the original order of checks, declaration links, type parameters, recursive references, + and error paths. +- Preparation happens immediately and reuses work for repeated nodes in the same Schema. Unsupported declarations, + contradictory bounds, and recursion with no stopping point fail during `Arbitrary.schema`. Combinations that are + valid but impossible to satisfy eventually return `SampleError` or `Exhausted`. +- Effect builds common checks into generation and still runs the original checks afterward. Ordinary Schema nodes do + not use the complete Schema parser for every value. Declaration links are converted and then checked against the + original declaration. +- For supported regular expressions, Effect generates matching text directly and checks the pattern afterward. It + chooses uniformly among supported patterns. Unsupported patterns act as ordinary filters instead of making the + complete Schema unsupported. +- Prepared patterns cache valid UTF-16 lengths and reusable character information. The same information helps simplify + generated strings without changing which strings the pattern accepts. + +#### Declarations + +- Effect looks for a declaration representation in this order: `toCodecArbitrary`, a built-in representation, + `toCodecJson`, then `toCodec`. If `toCodecJson()` returns `undefined`, the declaration says its JSON form is hidden, + so preparation stops instead of silently trying another conversion. +- `toCodecArbitrary` returns a Schema `Link`. Effect generates the source Schema, converts it, and checks the result + against the original declaration. Rejected initial values count toward `maxDiscards`; rejected simplifications count + toward `maxShrinks` while later valid proposals remain available. +- The callback receives the represented type parameters and the common checks Effect recognized. `ReadonlyMap` and + `ReadonlySet` use arrays internally. `HashMap`, `HashSet`, `Chunk`, and other specialized types keep local links when + that avoids loading their implementation into every use of the generic Schema compiler. +- Effect may use an `Order` while combining bounds, but passes only the final bounds to `toCodecArbitrary`. The link is + responsible for producing suitable values, and the original declaration rejects incompatible results. +- Links used only for generation cannot encode values; generation calls only their decode direction. + +#### Recursion and Size + +- Effect examines recursive references as a graph and computes the minimum space needed to reach a non-recursive value. + A recursive alternative that can never stop is ignored; a complete Schema that can never stop is rejected + immediately. +- Each attempt receives the minimum required space plus the configured `size`. Crossing a recursive reference consumes + some of that allowance. Effect reserves enough for required child values before generating optional siblings. + Recursive siblings are tried in a changing order so one declaration position does not always receive more space, but + the returned object keeps its declared order. +- Every recursive branch in the generated value shares one allowance. Nested composition does not create fresh space. +- Strings and collections also use `size` as a rough complexity target. Sampling uses a fixed value; checking raises it + after successful runs. Schema minimums are always respected, maximums remain limits, and rejected attempts do not + increase the size. + +#### Randomness and Probabilities + +- The runner starts from one seed and creates a separate random-number state for each attempt. Replay can therefore jump + directly to a recorded attempt, and a property's use of Effect `Random` cannot change later generated inputs. +- On some predictable attempts, generated records have no prototype. Choosing this case does not consume a random + number, and every record produced during that attempt uses the same choice, including simplified values. This applies + to `Schema.Struct`, `Schema.Record`, `Schema.Json`, and record-shaped `all`, but not to arrays, tuples, declarations, or + collection classes. +- Integer and BigInt generation avoids favoring some values accidentally. It tries boundary values more often on some + runs. Number generation includes signed zero, very small values, infinities, and `NaN` when the Schema permits them. + Finite and integer checks exclude the values they promise to exclude. +- The magnitude of unbounded integers grows with `size`. Ordinary strings combine printable ASCII with a fixed set of + JavaScript edge cases. Regular-expression length is measured in UTF-16 code units, matching JavaScript strings. +- Exact probabilities, the value produced by a particular seed, and the order of simplifications may change. Source + code comments credit algorithms adapted from other property-testing and random-number implementations. + +#### Generating and Simplifying Data Structures + +- Arrays first remove optional or repeated items and then simplify remaining items. Objects choose optional properties + without favoring earlier declarations. If the first choice is too large for the available recursion space, Effect + uses the smallest choice that still satisfies the Schema instead of rejecting a Schema that can produce a value. + Simplification removes optional properties, simplifies values, and then simplifies generated keys while keeping keys + unique. +- Generation of unique collections has a retry limit. `Schema.isUnique()` compares complete values; + `Schema.isUniqueKey()` compares the keys of Map entries. Both use Effect equality. Primitive values use specialized + tracking, while objects use Effect `Hash` and `Equal`. +- A Schema union chooses uniformly among alternatives that fit in the remaining recursion space. During simplification, + it first tries the earliest alternative with the lowest required space when that alternative is cheaper, then + simplifies the selected alternative. Separate random-number state keeps unrelated later values from changing this + fallback. For `oneOf`, a value must match exactly one alternative; overlaps are rejected. +- `all` adds the minimum space required by its members and makes them share one recursion allowance. It changes their + internal generation order for fairness, then restores tuple positions and record keys. Simplification changes one + member at a time. +- `map` transforms every proposed value without consuming randomness or changing replay positions. `filter` and + `filterMap` reject values that do not pass their condition. During simplification, Effect skips rejected proposals and + can continue to later valid ones. `map` keeps duplicate transformed values because removing them would change replay + positions. Each rejection consumes one unit of `maxShrinks` without running the property. +- `filterMap` ignores the failure value in `Result`. Separate implementations for `map` and `filter` avoid allocating a + `Result` in these common cases. +- `Arbitrary.schema(schema, { shrink })` keeps normal generation but replaces default simplification. Effect calls the + callback only when needed and checks each proposal against the Schema value after decoding. Invalid proposals consume + `maxShrinks`, never reach the property, and are not simplified further. + +#### Dependent Generation with `flatMap` + +- Effect first generates the source value, calls the callback, and then runs the Arbitrary selected by that callback. + The selected Arbitrary always receives enough space to produce its smallest possible value. +- Simplification starts with the source. Each simpler source value selects a new dependent Arbitrary. Effect later tries + simpler values from the current dependent Arbitrary. Once it accepts one of those dependent values, it no longer + returns to source simplification on that path. +- If the initial source or dependent Arbitrary rejects its value, the complete attempt is rejected. If a dependent value + selected during simplification is rejected, it consumes one unit of `maxShrinks`, and Effect continues with later + simplifications of the source. +- Sampling needs no saved random state because it does not simplify values. Checking saves the random state immediately + after source generation. The initial dependent value and every dependent value chosen from a simpler source receive a + separate copy, so trying one simplification cannot change another or affect later generated inputs. +- The dependent Arbitrary temporarily receives the minimum extra recursion space it needs. Any unused extra space is + removed afterward, so nested `flatMap` calls still share one overall allowance. +- Calling `Arbitrary.schema` inside the callback prepares that Schema on every callback call. Effect does not cache it + automatically. + +#### Running, Replaying, and Interrupting Checks + +- `sampleEffect` fails with `SampleError`. `checkEffect` returns `Passed`, `Falsified`, `Exhausted`, or + `ReplayMismatch`. `SampleError` and `Exhausted` include the seed so the run can be repeated. Only the boolean `true` + passes. Returning `false` and returning a failed Effect are different kinds of property failure, and simplification + preserves the original kind. The exact Effect error may change. Unexpected defects and interruption remain Effect + failures rather than result values. +- After a failure, Effect follows the first proposed simpler value that fails in the same way. `maxShrinks` counts every + proposal examined, including rejected values; `shrinks` counts only proposals accepted as the new counterexample. + Property runs do not include these extra evaluations. When the limit is reached, Effect returns the most simplified + failing input found so far. It does not clone or freeze generated values. +- A replay token records the seed, attempt number, size, original kind of failure, and the position of every accepted + simplification. Replay rebuilds those values instead of storing them in the token. It returns `ReplayMismatch` when a + position no longer exists or no longer fails in the same way. It does not compare the exact final input or Effect + error. Malformed tokens may fail with a defect, and tokens need not work across releases while the module is unstable. + Replay follows the recorded positions directly, so it ignores `maxShrinks`. +- Long synchronous generation loops occasionally yield control according to `Scheduler.MaxOpsBeforeYield`. Generation + that uses Effects, declaration conversion, property evaluation, and simplification can all be interrupted normally. diff --git a/packages/effect/CHANGELOG.md b/packages/effect/CHANGELOG.md index b030f25ef..39af4f04f 100644 --- a/packages/effect/CHANGELOG.md +++ b/packages/effect/CHANGELOG.md @@ -1,5 +1,1755 @@ # effect +## 4.0.0-rc.112 + +### Minor Changes + +- [#7390](https://github.com/Effect-TS/effect/pull/7390) [`a5f78d3`](https://github.com/Effect-TS/effect/commit/a5f78d3fcbaa792d49e80d103ab438e0b50812fd) Thanks @tim-smart! - Make RPC serialization schema-aware. + + Add `codecFor` to RPC serialization and client/server protocols so RPC and cluster + network payloads use the transport's schema codec. Framing, cluster storage, and + existing built-in wire formats remain unchanged. + +### Patch Changes + +- [#7411](https://github.com/Effect-TS/effect/pull/7411) [`20cb4f2`](https://github.com/Effect-TS/effect/commit/20cb4f260e45d37fa417c292c57be015314efe16) Thanks @altendky! - Add `RcMap.getOption` and `LayerMap.contextEffectOption` for atomically retaining + entries only when they are already cached. + +- [#7437](https://github.com/Effect-TS/effect/pull/7437) [`44675cb`](https://github.com/Effect-TS/effect/commit/44675cbce3dabfb85c68a3703b5de525768336fb) Thanks @wmaurer! - Add an optional `description` to `AiError.AuthenticationError`, rendered after the kind-based suggestion, and pass the provider's own error text through it on HTTP 401 and 403, so authentication failures report what actually went wrong instead of only a category. + +- [#7393](https://github.com/Effect-TS/effect/pull/7393) [`b6bf5e1`](https://github.com/Effect-TS/effect/commit/b6bf5e14492643076454131148f97cde24ad5306) Thanks @wmaurer! - Fix `Prompt.autoComplete` swallowing `j` and `k` while typing a filter query. + +- [#7401](https://github.com/Effect-TS/effect/pull/7401) [`0b9f780`](https://github.com/Effect-TS/effect/commit/0b9f780ff28b71042241791a9e8bcb5b631be2bd) Thanks @gjermundgaraba! - Retry transient EventLog remote write failures so pending local entries are synchronized after recovery. + +- [#7384](https://github.com/Effect-TS/effect/pull/7384) [`150e92c`](https://github.com/Effect-TS/effect/commit/150e92c4169c245e701da02575eef0b69c3ecd64) Thanks @tim-smart! - Improve synchronous Schema decode and encode performance by preserving completed parser exits and using a direct loop for common struct parsers. + +- [#7386](https://github.com/Effect-TS/effect/pull/7386) [`6740db2`](https://github.com/Effect-TS/effect/commit/6740db247ed20cb85da43c9f48ade8fecfd8c1ae) Thanks @tim-smart! - Add `Schema.TaggedUnion.matchOrElse` for partial case matching with a typed fallback. + +- [#7389](https://github.com/Effect-TS/effect/pull/7389) [`d57bba1`](https://github.com/Effect-TS/effect/commit/d57bba1486fa60971b6e0bf7459a329cfd5acdc4) Thanks @tim-smart! - Improve `SchemaError` construction performance by skipping stack frame capture. + +- [#7402](https://github.com/Effect-TS/effect/pull/7402) [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4) Thanks @tim-smart! - Improve Pool acquisition and release performance. Pool now tracks usage + incrementally, stores available items in an intrusive FIFO, and skips work for + fixed and empty pools. This changes the public `Pool.State` and `Pool.PoolItem` + interfaces. + +- [#7402](https://github.com/Effect-TS/effect/pull/7402) [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4) Thanks @tim-smart! - Add `Pool.use`, which borrows an item while an effect runs and returns it on any + exit. Unlike `Effect.scoped(Pool.get(pool))`, it does not require a `Scope`. + +- [#7402](https://github.com/Effect-TS/effect/pull/7402) [`be75d5e`](https://github.com/Effect-TS/effect/commit/be75d5ea6e516c25e3affec25806d31c2b203bc4) Thanks @tim-smart! - Reduce scoped resource acquisition allocations by storing the first Scope + finalizer inline and allocating a Map only when a second is added. This changes + the public `Scope.State.Open` interface. + +- [#7424](https://github.com/Effect-TS/effect/pull/7424) [`02a5146`](https://github.com/Effect-TS/effect/commit/02a5146d6933c7f6052553550bce5658225e4100) Thanks @tim-smart! - Skip remote event journal write callbacks when there are no uncommitted entries and return an `Option` indicating + whether the callback ran. + +- [#7312](https://github.com/Effect-TS/effect/pull/7312) [`15272a6`](https://github.com/Effect-TS/effect/commit/15272a66adf02501e7747761e2a3c41bff67bb46) Thanks @godu! - Fix shell completion for choice values containing quotes, spaces, word-break characters, Unicode, and shell metacharacters. + + Bash now quotes candidates for readline, keeps choice values intact when reconstructing words, and supports Bash 3.2 without associative arrays. Fish and Zsh escape choices across both parsing rounds, and Fish hides value-taking flags after use without suppressing their value completions. + +- [#7395](https://github.com/Effect-TS/effect/pull/7395) [`436f10d`](https://github.com/Effect-TS/effect/commit/436f10d1efccec308426532ff3f88df9a96434f3) Thanks @wmaurer! - Fix `Prompt.file` swallowing `j` and `k` while typing a filter query. + +- [#7406](https://github.com/Effect-TS/effect/pull/7406) [`058fb15`](https://github.com/Effect-TS/effect/commit/058fb15647fa01ad771277bd368783fcf5f262e8) Thanks @gcanti! - Preserve finite string and unique symbol key unions in the return types of `Array.groupBy` and `Iterable.groupBy`. + + Previously, grouping widened finite keys to `string` or `symbol`, which lost known-key autocomplete and allowed access to keys that the selector could never produce. The new `Record.ReadonlyRecord.GroupByResult` keeps finite keys and marks their properties optional because any group may be absent at runtime, while open `string` and `symbol` selectors retain their existing record index signatures. + +- [#7415](https://github.com/Effect-TS/effect/pull/7415) [`4d89bb8`](https://github.com/Effect-TS/effect/commit/4d89bb8ffb4cf567a1d11072246b6161ce638712) Thanks @gcanti! - Reject unsupported JSON Schema references instead of resolving them by their final path segment, closes [#7409](https://github.com/Effect-TS/effect/issues/7409). + +- [#7420](https://github.com/Effect-TS/effect/pull/7420) [`480fb15`](https://github.com/Effect-TS/effect/commit/480fb156590785cf98f67bdec4fc282a608e2d87) Thanks @gcanti! - Make JSON Schema dialect conversions preserve custom keywords, translate conditionals, contains, dependencies, identifiers, and tuples where representable, relocate local references after structural changes, and throw instead of silently changing unsupported constraints. + +- [#7417](https://github.com/Effect-TS/effect/pull/7417) [`f77ec19`](https://github.com/Effect-TS/effect/commit/f77ec19cff1cbbeeae928e3bd0ece00a7d22bab8) Thanks @Makisuo! - Defer built-in OpenAPI response generation until the documentation route is first requested, retrying after generation defects. + +- [#7388](https://github.com/Effect-TS/effect/pull/7388) [`925b82a`](https://github.com/Effect-TS/effect/commit/925b82a81f59a4d459b488621030f24ba99d6a27) Thanks @ebramanti! - Fix MCP initialize rejected over the protocol version header + + `McpServer.layerHttp` validated the `MCP-Protocol-Version` header on every POST, including + the `initialize` request. That header reports the version negotiated by an earlier + `initialize`, so on a fresh connection a client can only send its own default. Whenever + that default was not among the server's registered protocols the `initialize` returned + `400` and never reached version negotiation, even when the body offered a version the + server supports. + + The header check now applies only to requests after initialization, where the + specification requires it. An `initialize` negotiates from the version offered in its + body, through the protocol registry, and reports the selected version in the response. + +- [#7403](https://github.com/Effect-TS/effect/pull/7403) [`7455246`](https://github.com/Effect-TS/effect/commit/7455246f352385f5cbbdd8299555265ee289490e) Thanks @hsyntax! - Add support for explicit cache breakpoints on the OpenAI responses API for GPT-5.6-or-later. + +- [#7442](https://github.com/Effect-TS/effect/pull/7442) [`118124d`](https://github.com/Effect-TS/effect/commit/118124d913d0a02ac5c1f7799a39bd90031769d9) Thanks @tim-smart! - Redact password prompt values from CLI wizard command output. + +- [#7366](https://github.com/Effect-TS/effect/pull/7366) [`0dd7825`](https://github.com/Effect-TS/effect/commit/0dd7825e4da4d3a00fa9bd410a1d55f3d4874d07) Thanks @tim-smart! - Add `SchemaBinary`, a compact schema-derived codec with streaming, optional fingerprints and dictionaries, and RPC support. + +- [#7404](https://github.com/Effect-TS/effect/pull/7404) [`b722eca`](https://github.com/Effect-TS/effect/commit/b722eca6d283a88970ad0efba0b4e921915eca78) Thanks @gcanti! - Add a public `StandardSchema` module containing the vendored Standard Schema V1 specification and remove the direct dependency on `@standard-schema/spec`. + +- [#7436](https://github.com/Effect-TS/effect/pull/7436) [`811d579`](https://github.com/Effect-TS/effect/commit/811d579c432856a9e3fc05b517fd8e924cbf991a) Thanks @gcanti! - Fix JSON Schema imports: + + - Type-specific keywords no longer imply a type. For example, `minLength` validates strings without rejecting + non-string values. + - Constraints next to `const`, `enum`, and `$ref` are now applied instead of being ignored. + - Disjoint and linear union intersections are imported without a Cartesian expansion. Other overlapping union + intersections fail with an explicit error. + - References to definitions without unions no longer make otherwise linear intersections fail. + - Imported `oneOf` schemas remain `oneOf` when exported again. + - `minItems` is preserved when `prefixItems` does not fully enforce it. + +- [#7382](https://github.com/Effect-TS/effect/pull/7382) [`043b587`](https://github.com/Effect-TS/effect/commit/043b587e6e93f6624bf974bcd7ed976eaa17f0e1) Thanks @tim-smart! - Replace per-prompt prefix options with a context-based theme for CLI prompt symbols and colors. + +- [#7373](https://github.com/Effect-TS/effect/pull/7373) [`8583727`](https://github.com/Effect-TS/effect/commit/85837274fa929a921985464585513a68c261e365) Thanks @ChubbyDuck! - Drop unreachable concurrency guard in iteratorEagerImpl + +- [#7429](https://github.com/Effect-TS/effect/pull/7429) [`d9d2cfc`](https://github.com/Effect-TS/effect/commit/d9d2cfcb732754001b7323cf8afaccc48539bb74) Thanks @gcanti! - Reject unsupported JSON Schema validation keywords and object or array `const` / `enum` values during import instead of + silently weakening validation. + +- [#7428](https://github.com/Effect-TS/effect/pull/7428) [`5c4b7a0`](https://github.com/Effect-TS/effect/commit/5c4b7a0b17931cd1538c6595a54b21ffe9c1e906) Thanks @ebramanti! - Return workflow execution IDs from generated RPC and HTTP discard endpoints. + +## 4.0.0-rc.111 + +### Patch Changes + +- [#7311](https://github.com/Effect-TS/effect/pull/7311) [`0ce3b00`](https://github.com/Effect-TS/effect/commit/0ce3b007531563ce33f94c6a057492d379cfd9e8) Thanks @fubhy! - Reject graph shortest-path calculations that overflow or underflow the finite number range. + +- [#7352](https://github.com/Effect-TS/effect/pull/7352) [`d846331`](https://github.com/Effect-TS/effect/commit/d84633171b4333e87a05f8d74c02c95d83d1b50d) Thanks @nikhilsnayak! - Preserve the `Context.mapUnsafe` accessor when code is compiled with loose object spread transforms. + +- [#7300](https://github.com/Effect-TS/effect/pull/7300) [`f93616f`](https://github.com/Effect-TS/effect/commit/f93616f31f42345af844b8396fb19259556ae236) Thanks @fubhy! - Fix graph index exhaustion, A* path consistency, snapshot validation, Mermaid line endings, and topological initials. + +- [#7336](https://github.com/Effect-TS/effect/pull/7336) [`16bf1ef`](https://github.com/Effect-TS/effect/commit/16bf1efdf41ba1d000b8588a8cc052a394856ba4) Thanks @gcanti! - Compact JSON Schema check constraints when they can be safely merged without keyword collisions. + +- [#7360](https://github.com/Effect-TS/effect/pull/7360) [`d568968`](https://github.com/Effect-TS/effect/commit/d5689682e963f43157571cd71d4b422247ae09b9) Thanks @gcanti! - Add configurable schema representation reference policies and propagate them through JSON Schema and OpenAPI generation. By default, only schemas with resolved identifiers become references. Closes [#7357](https://github.com/Effect-TS/effect/issues/7357). + +- [#7304](https://github.com/Effect-TS/effect/pull/7304) [`bc06292`](https://github.com/Effect-TS/effect/commit/bc062920ac47f956786d12c6d0bacb9468fa66e2) Thanks @fubhy! - Add graph snapshots, low-link connectivity analysis, bipartite matching, maximum flow, and minimum cut APIs. + +- [#7364](https://github.com/Effect-TS/effect/pull/7364) [`e03ea90`](https://github.com/Effect-TS/effect/commit/e03ea907720b58d19d99929695df159c42fff101) Thanks @kitlangton! - Fix `Deferred` completion skipping waiters when an earlier waiter dies during resume. Completing a `Deferred` with an interrupt cause kills a suspended waiter synchronously inside its resume; the dying waiter's `await` cleanup spliced the shared `resumes` array mid-iteration, so the next waiter was never resumed and hung forever. Completion now clears `resumes` before resuming waiters. + +- [#7347](https://github.com/Effect-TS/effect/pull/7347) [`9b10fc8`](https://github.com/Effect-TS/effect/commit/9b10fc85dde7866fb60e8142a93a6ed6a184537c) Thanks @tim-smart! - Shut down the internal effects queue when ordered concurrent channel mapping closes. + +- [#7335](https://github.com/Effect-TS/effect/pull/7335) [`770c6d0`](https://github.com/Effect-TS/effect/commit/770c6d0f0ddcab7dbdde9fe701aded08deaee3fd) Thanks @tim-smart! - Fix `Effect.fn` binding the final transform as the generator body when using the `{ self }` overload. + +- [#7344](https://github.com/Effect-TS/effect/pull/7344) [`7425bcb`](https://github.com/Effect-TS/effect/commit/7425bcb7f74bdccc1941883bc7f9472fbe6ae8b4) Thanks @tim-smart! - Ensure fiber observer cancellation during exit does not skip remaining observers. + +- [#7301](https://github.com/Effect-TS/effect/pull/7301) [`563815a`](https://github.com/Effect-TS/effect/commit/563815a250e10a02e2a719c7d9b159478b0da933) Thanks @fubhy! - Preserve depth-first traversal order with finite radii and validate A* heuristics for trivial paths. + +- [#7350](https://github.com/Effect-TS/effect/pull/7350) [`1e83ca1`](https://github.com/Effect-TS/effect/commit/1e83ca110d5cb74641bae148a08e7eb6b3c2e2ed) Thanks @tim-smart! - Align in-memory workflow interrupt finalization with the cluster workflow engine. + +- [#7316](https://github.com/Effect-TS/effect/pull/7316) [`550a41a`](https://github.com/Effect-TS/effect/commit/550a41a2547ce0dd5f085f1637a6f8a5794acba7) Thanks @tim-smart! - Update dependencies across the Effect workspace. + +- [#7306](https://github.com/Effect-TS/effect/pull/7306) [`45d79c7`](https://github.com/Effect-TS/effect/commit/45d79c7cbd9c56c643361c6e6d1689f1aa62ae32) Thanks @fubhy! - Add bulk node and edge removal operations, and disallow graph mutations from callbacks that traverse or transform the same graph. + +- [#7317](https://github.com/Effect-TS/effect/pull/7317) [`aac8584`](https://github.com/Effect-TS/effect/commit/aac8584fd997f3ce3341aeb077b816219832de58) Thanks @tim-smart! - Fix `Match.value` terminal combinators failing to typecheck when the input + contains a generic type parameter. + + The fifth type argument of `Matcher` for value matchers is now `ValueFlavor`, + and `ValueMatcher` has a seventh flavor argument; update hand-written + annotations accordingly. + +- [#7361](https://github.com/Effect-TS/effect/pull/7361) [`7f87022`](https://github.com/Effect-TS/effect/commit/7f870225bb10a7ee7cb54b4835cb3bf64c0cacd8) Thanks @tim-smart! - Merge effect and finalizer failures during cleanup, preserving other failures alongside `Cause.Done`. + +- [#7326](https://github.com/Effect-TS/effect/pull/7326) [`425457c`](https://github.com/Effect-TS/effect/commit/425457c1e9eec7c176df130f7ec6ad3d616c086e) Thanks @tim-smart! - Emit mixed struct and record schema types as intersections, preventing optional + properties in open OpenAPI objects from conflicting with their index signature. + +- [#7324](https://github.com/Effect-TS/effect/pull/7324) [`008c423`](https://github.com/Effect-TS/effect/commit/008c423e413fe311085c4bfc07c19b9c70529dfa) Thanks @tim-smart! - Allow path-level common parameters in OpenAPI generator input types. + +- [#7359](https://github.com/Effect-TS/effect/pull/7359) [`4f6ae04`](https://github.com/Effect-TS/effect/commit/4f6ae041a2886c27e941c5d9e4916aa2a32e0a1c) Thanks @gcanti! - Add dual standalone functions for reading and updating values through optics, closes [#7299](https://github.com/Effect-TS/effect/issues/7299). + +- [#7250](https://github.com/Effect-TS/effect/pull/7250) [`b6b63e1`](https://github.com/Effect-TS/effect/commit/b6b63e151595b5599ad2d65069ab6d298cfd7be7) Thanks @xianjianlf2! - Preserve `JSON.rawJSON` values when cloning cached OpenAPI specs. + +- [#7351](https://github.com/Effect-TS/effect/pull/7351) [`92922ee`](https://github.com/Effect-TS/effect/commit/92922ee7de0a2d932121e37677f3e75ea38482f1) Thanks @tim-smart! - Preserve unsafe in-memory workflow interrupts across replay. + +- [#7328](https://github.com/Effect-TS/effect/pull/7328) [`859c02f`](https://github.com/Effect-TS/effect/commit/859c02fe9020b0f28f713aa85910a0cc2d002560) Thanks @fubhy! - Keep graph caches consistent during bulk removals and validate graph kinds at runtime. + +- [#7358](https://github.com/Effect-TS/effect/pull/7358) [`ffc8235`](https://github.com/Effect-TS/effect/commit/ffc823537a0925171cf41d0841643a0b26895a4c) Thanks @tim-smart! - Bound framed RPC server HTTP response streams to 16 items by default, with a configurable buffer size or an unbounded opt-out. + +- [#6324](https://github.com/Effect-TS/effect/pull/6324) [`a29eb70`](https://github.com/Effect-TS/effect/commit/a29eb702ffe3fc58bd28c4d7857298cd65d73668) Thanks @tim-smart! - Add scoped Redis pub/sub subscriptions that expose received messages through an Effect queue. + +- [#7354](https://github.com/Effect-TS/effect/pull/7354) [`0be2303`](https://github.com/Effect-TS/effect/commit/0be2303dd39f9f2f7d8f96a28e25c938cd914e1a) Thanks @tim-smart! - Add support for server-originated RPC requests and notifications. Buffered + JSON-RPC HTTP drops notifications until streaming responses are available. + +- [#7349](https://github.com/Effect-TS/effect/pull/7349) [`b44636f`](https://github.com/Effect-TS/effect/commit/b44636ff7233763fad060bc0f9de10512454a29b) Thanks @gcanti! - When canonical JSON derivation adds a transformation for a schema without a direct JSON representation, keep source checks and annotations on the source side. This prevents duplicate check execution and ensures generated JSON Schema documents describe only the encoded target, closes [#7192](https://github.com/Effect-TS/effect/issues/7192). + +- [#7337](https://github.com/Effect-TS/effect/pull/7337) [`b19ccc7`](https://github.com/Effect-TS/effect/commit/b19ccc7325469a5dea091349bda4ae2ec00ec865) Thanks @gcanti! - Add `Schema.JsonObject` for readonly string-keyed records containing JSON-compatible values. This provides a canonical, + reusable schema instead of requiring callers to repeatedly compose `Schema.Record(Schema.String, Schema.Json)`. + +- [#7330](https://github.com/Effect-TS/effect/pull/7330) [`ff98f0b`](https://github.com/Effect-TS/effect/commit/ff98f0b0e2beb331209e37e42095d8d6e8e0b6c2) Thanks @gcanti! - Preserve JSON Schema object keyword scopes when importing `allOf` intersections, including closed empty objects and required-only keys. Emit intersecting index signatures without weakening their constraints, and reject object scope intersections that cannot be represented faithfully. + +- [#7363](https://github.com/Effect-TS/effect/pull/7363) [`a47cbf1`](https://github.com/Effect-TS/effect/commit/a47cbf1feafd10e305c9753fa02a5f5f977d2c37) Thanks @tim-smart! - Add `Match.fn` for reusable matchers that select a value from multiple arguments. + +- [#7362](https://github.com/Effect-TS/effect/pull/7362) [`39b55f8`](https://github.com/Effect-TS/effect/commit/39b55f850aecbecf2e3b0c5f3ae2d1a77e6da949) Thanks @tim-smart! - Preserve encoded AI tool call parameters when automatic tool call resolution is disabled, and update + `Toolkit.handle` to accept the encoded parameter type it decodes at runtime. + +- [#7305](https://github.com/Effect-TS/effect/pull/7305) [`c6c49c9`](https://github.com/Effect-TS/effect/commit/c6c49c96990d009341341a53ed26beb140c267a9) Thanks @fubhy! - Fix mutable graph cache consistency and guard weighted pathfinding against inconsistent snapshots and numeric overflow. + +- [#7342](https://github.com/Effect-TS/effect/pull/7342) [`bf23ba7`](https://github.com/Effect-TS/effect/commit/bf23ba7033a4b426caf379bdcf84c58bb64d7010) Thanks @misterclayt0n! - Forward every worker-runner client disconnect to the RPC server, not just the first one. + +## 4.0.0-rc.110 + +### Patch Changes + +- [#7234](https://github.com/Effect-TS/effect/pull/7234) [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d) Thanks @lloydrichards! - MCP servers can now use the 2025-11-25 protocol, including sampling with tools and both form- and URL-based elicitation. + + Enable it by adding `McpProtocol.v2025_11_25` to the server's `protocols` option. + +- [#7234](https://github.com/Effect-TS/effect/pull/7234) [`6eebd0a`](https://github.com/Effect-TS/effect/commit/6eebd0a618308a91f95947bae6e0fb206ae3939d) Thanks @lloydrichards! - MCP servers can now provide icons for server information, resources, resource templates, prompts, and tools using `McpSchema.Icon`. + + Each icon can specify its source URI, MIME type, supported sizes, and light or dark theme. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Include traversed edge indexes in graph shortest-path results. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add deterministic, index-preserving `Graph.minimumSpanningForest`. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add index-preserving transitive reduction for directed acyclic graphs. + +- [#7261](https://github.com/Effect-TS/effect/pull/7261) [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179) Thanks @fubhy! - Add `Graph.Snapshot` and `Graph.fromSnapshot` for constructing immutable graphs with explicit node and edge indexes, and simplify `Graph.Edge` to a type-only structural interface. + +- [#7261](https://github.com/Effect-TS/effect/pull/7261) [`189b003`](https://github.com/Effect-TS/effect/commit/189b003a2367fa44dd4b8544aa62979f0345d179) Thanks @fubhy! - Add `Schema.Graph` for schema-based encoding and decoding of immutable directed and undirected graphs. + +- [#7267](https://github.com/Effect-TS/effect/pull/7267) [`0a127b8`](https://github.com/Effect-TS/effect/commit/0a127b8847fb213d46621a5018c069019414c00d) Thanks @tim-smart! - Allow customizing the prefix displayed by CLI prompts. + +- [#7272](https://github.com/Effect-TS/effect/pull/7272) [`e491deb`](https://github.com/Effect-TS/effect/commit/e491deb1cd1717bc442aaee96e5d7b4001710cc9) Thanks @fubhy! - Preserve scoped Graph mutation callback errors when the callback manually finalizes its mutable handle. + +- [#7266](https://github.com/Effect-TS/effect/pull/7266) [`f99c508`](https://github.com/Effect-TS/effect/commit/f99c508a09e9fccddb09edd16fa3393891c55d48) Thanks @tim-smart! - Fix SQL persisted queue delivery on SQLite builds without `SQLITE_ENABLE_UPDATE_DELETE_LIMIT`. + +- [#7199](https://github.com/Effect-TS/effect/pull/7199) [`7e3f07c`](https://github.com/Effect-TS/effect/commit/7e3f07cf3e4b6a907840aface36ae5ee67ca8979) Thanks @rekram1-node! - Fix Zsh completions for CLI commands with both positional arguments and subcommands. + +- [#7274](https://github.com/Effect-TS/effect/pull/7274) [`a894fe1`](https://github.com/Effect-TS/effect/commit/a894fe1df8974077293d9c1b924a7f80fc7fe7f1) Thanks @fubhy! - Ignore removed allocator history when comparing and hashing immutable Graph values with the same active indexed structure. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add `Graph.findCycle` with exact node and edge witnesses. + +- [#7294](https://github.com/Effect-TS/effect/pull/7294) [`7e9923b`](https://github.com/Effect-TS/effect/commit/7e9923b1a0adf1f842216b298d7542bed980c9a5) Thanks @tim-smart! - Add custom reviver support to HTTP JSON parsing APIs. + +- [#7200](https://github.com/Effect-TS/effect/pull/7200) [`f064121`](https://github.com/Effect-TS/effect/commit/f0641210f960563f813ac6f728804e5447c03341) Thanks @mikearnaldi! - Support narrowing schedule input and output types with type guard predicates passed to `Schedule.while`. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add index-preserving `Graph.inducedSubgraph`. + +- [#7244](https://github.com/Effect-TS/effect/pull/7244) [`b660bf0`](https://github.com/Effect-TS/effect/commit/b660bf01d9c427552296cd9e0195e21504f45e1f) Thanks @AnnaSuSu! - Normalize unbounded PubSub replay capacities to positive integers. + +- [#7293](https://github.com/Effect-TS/effect/pull/7293) [`f4fbe9c`](https://github.com/Effect-TS/effect/commit/f4fbe9ca0209cdfaaaff54fc3805221be80f1ffa) Thanks @tim-smart! - Support standalone Effect.forEach data-last usage + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add bounded lazy enumeration of simple paths and all tied shortest paths. + +- [#7259](https://github.com/Effect-TS/effect/pull/7259) [`e811353`](https://github.com/Effect-TS/effect/commit/e811353e3b79ba392af1d9fb8d7f93fd729dabf9) Thanks @fubhy! - Prevent graph edge reads from exposing internal edge records and reject non-finite A* heuristic values. + +- [#7251](https://github.com/Effect-TS/effect/pull/7251) [`9761c3c`](https://github.com/Effect-TS/effect/commit/9761c3c4787b3815346c1b650d8984efec8f1051) Thanks @tim-smart! - Add `Encoding.randomHex`, a lightweight non-cryptographic generator that coerces lengths to unsigned 32-bit multiples of 8. + +- [#7296](https://github.com/Effect-TS/effect/pull/7296) [`baa99fc`](https://github.com/Effect-TS/effect/commit/baa99fc176efed4986c19c78726d1dbe7bd17765) Thanks @tim-smart! - Make unstable CLI boolean flags required when omitted, allowing optional, default, config, and prompt fallbacks to handle absence consistently. + +- [#7246](https://github.com/Effect-TS/effect/pull/7246) [`7fd79b2`](https://github.com/Effect-TS/effect/commit/7fd79b2cc95973f85b3db43b0cf3c5cb7d2f69c4) Thanks @tim-smart! - Add `Effect.head` for retrieving the first element of an iterable produced by an effect. + +- [#7273](https://github.com/Effect-TS/effect/pull/7273) [`a82ffc0`](https://github.com/Effect-TS/effect/commit/a82ffc03df0fb34e02f16773b96bfa3e0208e427) Thanks @fubhy! - Validate Graph traversal radii, isolate traversal start configuration, and prioritize the first supplied DFS root. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Throw `GraphError` when a negative cycle affects a Bellman-Ford target, reserving `Option.none()` for unreachable paths. + +- [#7248](https://github.com/Effect-TS/effect/pull/7248) [`4026e2d`](https://github.com/Effect-TS/effect/commit/4026e2dde3b098e70ffb3f1fcddd200176acdefd) Thanks @tim-smart! - Improve tracing performance in span creation and HTTP middleware. + +- [#7276](https://github.com/Effect-TS/effect/pull/7276) [`397bf1e`](https://github.com/Effect-TS/effect/commit/397bf1ebd95c0d6d58dc53e4f33c8ad3f34746f6) Thanks @fubhy! - Deduplicate directed neighbor-node queries while preserving first edge occurrence order. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add incident-edge, edges-between, and directed and undirected degree queries to `Graph`. + +- [#7291](https://github.com/Effect-TS/effect/pull/7291) [`d10ceb0`](https://github.com/Effect-TS/effect/commit/d10ceb06d56108c11100868f591d2b42ddff5e9f) Thanks @fubhy! - Add unweighted reachability, explicit weak and strong connectivity predicates, weak components, and tree detection to `Graph`. + +## 4.0.0-rc.109 + +### Patch Changes + +- [#7219](https://github.com/Effect-TS/effect/pull/7219) [`a0743f2`](https://github.com/Effect-TS/effect/commit/a0743f2b9f20fb5d150f35510e68819f01630bac) Thanks @tim-smart! - Add SQL, HttpApi testing, and CLI schema examples to the published AI documentation. + +- [#7241](https://github.com/Effect-TS/effect/pull/7241) [`17892e7`](https://github.com/Effect-TS/effect/commit/17892e75a8d584f79127805506c42b19320990a7) Thanks @tim-smart! - Use Context mapUnsafe in less call sites + +- [#7240](https://github.com/Effect-TS/effect/pull/7240) [`4d8a230`](https://github.com/Effect-TS/effect/commit/4d8a2306dfff8fd5406ab75d0c8d22e5300d1faa) Thanks @tim-smart! - Fix `Effect.fromOption` data-first inference for inline `Option` expressions. + +- [#7216](https://github.com/Effect-TS/effect/pull/7216) [`f21f9c9`](https://github.com/Effect-TS/effect/commit/f21f9c9f73dc59db922bda411b6d5245dae37cdb) Thanks @tim-smart! - Add a `HttpStatus` module to `effect/unstable/http` that centralizes the mapping from HTTP status literal names to numeric codes and exports `HttpStatus.fromLiteral`. `HttpApiSchema.status` now consumes the new module. + +- [#6829](https://github.com/Effect-TS/effect/pull/6829) [`18270dd`](https://github.com/Effect-TS/effect/commit/18270ddeaf4ad28723ca8a40aff04a5d4707b575) Thanks @lloydrichards! - MCP servers now support the 2024-11-05 and 2025-03-26 RPC revisions through version-specific protocol adapters. + +- [#7218](https://github.com/Effect-TS/effect/pull/7218) [`26db404`](https://github.com/Effect-TS/effect/commit/26db404a3284cfdbf4a3f351ccd05afc0de743b8) Thanks @tim-smart! - Run SQL `PersistedQueue` table creation through versioned migrations so future schema changes can be applied safely. + +- [#7210](https://github.com/Effect-TS/effect/pull/7210) [`2670398`](https://github.com/Effect-TS/effect/commit/26703982612e12954360382e4b1d177002699d1c) Thanks @tim-smart! - Preserve nanosecond precision when adjusting `TestClock` with large durations. + +- [#7205](https://github.com/Effect-TS/effect/pull/7205) [`3702bed`](https://github.com/Effect-TS/effect/commit/3702bedd8f6bcb3f603b87c640c521878d824eb3) Thanks @tim-smart! - Remove the `kubernetes-types` dependency by vendoring the Kubernetes Pod declarations used by the cluster helpers and exporting them from `effect/unstable/cluster/K8sTypes`. + +- [#7236](https://github.com/Effect-TS/effect/pull/7236) [`ccae60e`](https://github.com/Effect-TS/effect/commit/ccae60e5edb2bef553f4af52afb509dfd443cd03) Thanks @roninjin10! - Propagate a failed `BEGIN` or `SAVEPOINT` from `SqlClient.withTransaction` as a typed `SqlError`. + + `makeWithTransaction` wrapped the `begin` step together with the transaction body, so a + failed `BEGIN` took the rollback branch. No transaction was active at that point, the + `ROLLBACK` failed, and its `Effect.orDie` wrapper replaced the original typed error with a + defect (`cannot rollback - no transaction is active`). Callers could no longer classify the + failure as retryable. The path became reachable when the sqlite client started using + `BEGIN IMMEDIATE`, which acquires a write lock and can fail with `SQLITE_BUSY`. + + Commit and rollback now run only after `begin` or `savepoint` succeeds. A failed `begin` or + `savepoint` fails with its original `SqlError`, leaves the wrapped effect unexecuted, and + still closes the acquired connection scope. + +- [#7206](https://github.com/Effect-TS/effect/pull/7206) [`6ff5396`](https://github.com/Effect-TS/effect/commit/6ff53968138bbd7d4728ce8014e35eae8d6ca5d0) Thanks @tim-smart! - Bound cluster runner entity residency and storage reads. + + `ShardingConfig` gains two knobs: + + - `maxResidentEntities` (default `10_000`): the maximum number of entities + that can be resident on a runner at the same time. At the cap, the storage + read loop stops admitting messages for new entity addresses (they stay in + storage until a slot frees up) and volatile sends to new addresses fail with + `MailboxFull`. Persisted sends still succeed. `"unbounded"` restores the + previous behaviour and can only be set programmatically. + - `unprocessedMessageBatchSize` (default `1024`): the maximum number of + unprocessed messages read from storage in a single poll. + + `MessageStorage.unprocessedMessages` accepts an optional + `{ limit, addresses }` argument, and only claims the messages it actually + returns. The memory implementation now applies the same ten-minute claim + window as SQL, so bounded reads advance past in-flight requests; resetting an + address or shard makes its claimed messages immediately eligible again. + + The encoded driver contract replaces `Encoded.resetAddress` with the batched + `Encoded.resetAddresses` operation. `SqlMessageStorage.makeEncoded` constructs + the SQL encoded driver directly for custom storage composition. + + `ClusterWorkflowEngine` entities (workflows and the durable clock) now use a + fixed ten-second idle time, so completed and suspended executions release their + entity slots quickly. Their state is durable, so an evicted execution is + rebuilt from storage when its next message arrives. + +## 4.0.0-rc.108 + +### Patch Changes + +- [#6546](https://github.com/Effect-TS/effect/pull/6546) [`dfb173e`](https://github.com/Effect-TS/effect/commit/dfb173efffd20c4feded4efe409018dd55acdca8) Thanks @xianjianlf2! - Handle BigInt values safely and consistently across JSON diagnostics and logger formats. + +- [#7174](https://github.com/Effect-TS/effect/pull/7174) [`005e090`](https://github.com/Effect-TS/effect/commit/005e0902cace9f8960a4f43573665a3a9b53b6fa) Thanks @tim-smart! - Fix `Queue.await` failing with `Cause.Done` when registered before the queue ends. + +- [#7180](https://github.com/Effect-TS/effect/pull/7180) [`c82c532`](https://github.com/Effect-TS/effect/commit/c82c53228dc1c50cc99654ce6de7766b4de09e75) Thanks @gcanti! - Prioritize redacted representations in formatters and normalize text logger levels to uppercase. + +- [#7193](https://github.com/Effect-TS/effect/pull/7193) [`22b579f`](https://github.com/Effect-TS/effect/commit/22b579f6c582e6e2d951784791fea6f1802517ed) Thanks @kitlangton! - Fix `Deferred.await` dying with a `TypeError` when a waiter is interrupted after the `Deferred` has been completed. + +- [#7179](https://github.com/Effect-TS/effect/pull/7179) [`3e19539`](https://github.com/Effect-TS/effect/commit/3e19539205082b1006d84553045d1b03db9cc8a1) Thanks @tim-smart! - Fix `DurableDeferred.raceAll` so a completed deferred can wake an active workflow without changing success-biased race semantics + +- [#7189](https://github.com/Effect-TS/effect/pull/7189) [`08a3c74`](https://github.com/Effect-TS/effect/commit/08a3c74133206fc1cc728e0aa96d02e672fd80bd) Thanks @gcanti! - Fix `HttpApi` query decoding for array parameters with a single value. + +- [#6550](https://github.com/Effect-TS/effect/pull/6550) [`eb0bae0`](https://github.com/Effect-TS/effect/commit/eb0bae08d543d58754c9bb7a57e67c1e2bb3f55a) Thanks @xianjianlf2! - Return fresh OpenAPI specs from cached `OpenApi.fromApi` calls. + +- [#7188](https://github.com/Effect-TS/effect/pull/7188) [`97b544d`](https://github.com/Effect-TS/effect/commit/97b544d8b636587647b90691d669305c0eb4fc66) Thanks @gcanti! - Mark the internal `~sentinels` Schema annotation as `@internal` so release declaration stripping removes it together with `SchemaAST.Sentinel`. This keeps the published declarations self-consistent for consumers that type-check dependencies with `skipLibCheck: false`. + +- [#7158](https://github.com/Effect-TS/effect/pull/7158) [`4f6d131`](https://github.com/Effect-TS/effect/commit/4f6d131e85d74ab0ec0300e52e503a5f943fc576) Thanks @k3dom! - Improve Union candidate selection: a nested union member is dispatched by the sentinels common to all its members, and candidates whose sentinel the input contradicts are excluded. + +- [#7178](https://github.com/Effect-TS/effect/pull/7178) [`fad4b7c`](https://github.com/Effect-TS/effect/commit/fad4b7c5138b3f38c2427436da2e0685c1ca4e9b) Thanks @tim-smart! - Use Promise microtasks for synchronous Scheduler dispatch. + +- [#7181](https://github.com/Effect-TS/effect/pull/7181) [`accf447`](https://github.com/Effect-TS/effect/commit/accf4474513064e2a21d14b1937503261b4f34dc) Thanks @gcanti! - Move `SchemaError` into the `Schema` module and remove the standalone `SchemaError` module. + +- [#7195](https://github.com/Effect-TS/effect/pull/7195) [`31b27e4`](https://github.com/Effect-TS/effect/commit/31b27e49903c351588435f666c953aaac28f6120) Thanks @tim-smart! - Ensure discarded non-persisted cluster messages complete without waiting for the entity reply. + +- [#7191](https://github.com/Effect-TS/effect/pull/7191) [`8458951`](https://github.com/Effect-TS/effect/commit/84589518c3966c63d7f3679a5296d380eb1ba887) Thanks @Digifox03! - Fix `HttpRouter.Middleware.layer` to provide request error services for errors declared in `handles`, and expose global + middleware errors from `HttpRouter.toHttpEffect`. + +## 4.0.0-beta.107 + +### Patch Changes + +- [#7156](https://github.com/Effect-TS/effect/pull/7156) [`596f3f9`](https://github.com/Effect-TS/effect/commit/596f3f92d7fe355811b815cb212332b082268ce8) Thanks @tim-smart! - Terminate active multipart file streams when a parser limit is exceeded or the body ends unexpectedly, so file parts fail instead of hanging. + +- [#7153](https://github.com/Effect-TS/effect/pull/7153) [`9611ed4`](https://github.com/Effect-TS/effect/commit/9611ed42d11300546b339ab13492a0f7bdb1ebfb) Thanks @rajanpanth! - Fix `Duration`'s `Hash.symbol` implementation to hash a canonical nanoseconds form instead of the raw internal `Millis`/`Nanos` representation. Two durations that `Duration.equals`/`Equal.equals` consider equal (e.g. `Duration.seconds(5)` and `Duration.nanos(5_000_000_000n)`) previously hashed differently, violating the Hash/Equal contract and silently breaking `HashSet`/`HashMap` lookups keyed by `Duration`. + +- [#7166](https://github.com/Effect-TS/effect/pull/7166) [`8b91605`](https://github.com/Effect-TS/effect/commit/8b9160548556e4b0ec7ee2f2707716776be49018) Thanks @CDVolvik! - Import migrations through a file URL in `Migrator.fromFileSystem`, so absolute Windows paths are accepted by the ESM loader. + + Previously the directory and file name were passed to `import` as a plain path. On Windows that produced a specifier such as `D:\migrations\1_init.ts`, which the ESM loader rejects with `Only URLs with a scheme in: file, data, and node are supported`. + + `fromFileSystem` now resolves the specifier through the `Path` service, so its type widens from `Loader` to `Loader`. Callers that already provide an aggregate platform layer such as `NodeServices.layer` are unaffected; callers that provide `FileSystem` on its own now also need a `Path` layer, and on Windows it must be a platform-aware one rather than the POSIX `Path.layer`. + +- [#7157](https://github.com/Effect-TS/effect/pull/7157) [`d901928`](https://github.com/Effect-TS/effect/commit/d901928efa44f573ed1247f53fdb203a8e4fcede) Thanks @tim-smart! - Add `Channel.mkUint8Array` and reuse it from `Stream` and multipart file collection. This also fixes quadratic buffering in `File.contentEffect`, improving collection of a 16 MiB chunked upload by approximately 90x. + +- [#7149](https://github.com/Effect-TS/effect/pull/7149) [`b32bdef`](https://github.com/Effect-TS/effect/commit/b32bdef0d119a1ad1463dc01a46763ffee1f9bd9) Thanks @gcanti! - Require explicit handling for regular expression pattern constraints translated from JSON Schema documents, with modes to apply trusted patterns or ignore their constraints. + +## 4.0.0-beta.106 + +### Patch Changes + +- [#7110](https://github.com/Effect-TS/effect/pull/7110) [`2695168`](https://github.com/Effect-TS/effect/commit/269516851b24916d72771f8a554b88722e3732e7) Thanks @fubhy! - Ensure concurrent first `RcRef` borrowers share the same resource generation. + +- [#7114](https://github.com/Effect-TS/effect/pull/7114) [`6310a8c`](https://github.com/Effect-TS/effect/commit/6310a8c68c74dcf1d23948ec9243ac5f407a1651) Thanks @fubhy! - Report buffered worker send failures as `WorkerError` values. + +- [#7117](https://github.com/Effect-TS/effect/pull/7117) [`c2071b1`](https://github.com/Effect-TS/effect/commit/c2071b1647e2326568c1d0689274ef62b8a7183f) Thanks @fubhy! - Make `TxQueue.shutdown` safe to call after a queue has already been interrupted. + +- [#7119](https://github.com/Effect-TS/effect/pull/7119) [`7aff81a`](https://github.com/Effect-TS/effect/commit/7aff81a9cefe681483ef8abf717d786fd10e7e8d) Thanks @fubhy! - Prevent SQL resolvers from invoking non-empty batch callbacks when every request fails encoding. + +- [#7105](https://github.com/Effect-TS/effect/pull/7105) [`a1d4057`](https://github.com/Effect-TS/effect/commit/a1d4057711935a544ef441bc2d0ac3565dfa9266) Thanks @tim-smart! - Add `ConfigProvider.fromEnvRecord` for building a provider from an explicit environment record. + +- [#7111](https://github.com/Effect-TS/effect/pull/7111) [`abf77b0`](https://github.com/Effect-TS/effect/commit/abf77b04009dcb4d67a258f9d8ada778e9f4ffae) Thanks @fubhy! - Preserve input fiber error types in `Fiber.joinAll`. + +- [#7134](https://github.com/Effect-TS/effect/pull/7134) [`6c60375`](https://github.com/Effect-TS/effect/commit/6c60375e68683a32d54554150cc493e16550a06d) Thanks @marbemac! - Fix cluster shutdown hangs by failing abandoned non-discard requests and stream chunk acknowledgements with `EntityNotAssignedToRunner`, including persisted requests sent after runner unregistration. This adds `EntityNotAssignedToRunner` to the typed error channel of entity clients and request-only `EntityProxy` RPC/HTTP endpoints; discard endpoints remain unchanged. + +- [#7107](https://github.com/Effect-TS/effect/pull/7107) [`22f4897`](https://github.com/Effect-TS/effect/commit/22f4897bbae24783d4516f6bef353f1db4ec6d03) Thanks @fubhy! - Preserve FormData bodies when converting client requests through HttpServerRequest. + +- [#7120](https://github.com/Effect-TS/effect/pull/7120) [`615d1d5`](https://github.com/Effect-TS/effect/commit/615d1d5d0256ec8160f2e08d0dcf5dc83acb7bf1) Thanks @fubhy! - Fix `SqlResolver.findById` failing to complete duplicate requests when id encoding fails, which surfaced as a `RequestResolver did not complete request` defect instead of the underlying `SchemaError`. + +- [#7131](https://github.com/Effect-TS/effect/pull/7131) [`3a86757`](https://github.com/Effect-TS/effect/commit/3a867573ddeed5888dabdeb3225a9ebbf00491e7) Thanks @fubhy! - Ignore MCP cancellation notifications for unknown request identifiers. + +- [#7104](https://github.com/Effect-TS/effect/pull/7104) [`f4a9762`](https://github.com/Effect-TS/effect/commit/f4a9762bb9dfad59c215f2e099dcc829d74f4ed1) Thanks @gcanti! - Add `Function.memoizeIdempotent` and use it to avoid reprocessing canonical Schema ASTs, including optional and mutable property modifiers. Cache Config schema cursor AST compilation. + +- [#7144](https://github.com/Effect-TS/effect/pull/7144) [`0bcf6ed`](https://github.com/Effect-TS/effect/commit/0bcf6ed57c22e8a36964726b15464101d90f5997) Thanks @fubhy! - Stop multipart parsing after part count, part size, or field size limits are exceeded. + +- [#7121](https://github.com/Effect-TS/effect/pull/7121) [`ba9cb63`](https://github.com/Effect-TS/effect/commit/ba9cb63b87d45ce2df872dd8ef0905da147cc675) Thanks @fubhy! - Prevent execution-plan event observer defects from changing attempt outcomes or leaving attempt events unpaired. + +- [#7147](https://github.com/Effect-TS/effect/pull/7147) [`42c810d`](https://github.com/Effect-TS/effect/commit/42c810dd372275b822dd99c7d7e774e153f0a752) Thanks @tim-smart! - Release worker pool entries when an RPC worker's receive loop fails. + +- [#7148](https://github.com/Effect-TS/effect/pull/7148) [`1416ccd`](https://github.com/Effect-TS/effect/commit/1416ccd474bc9da8979f51b72b5e53fb3ac56edf) Thanks @gcanti! - Consolidate schema arbitrary derivation into `Schema.toArbitrary`, which now returns a `Schema.Arbitrary` factory that accepts the fast-check module. Remove `Schema.toArbitraryLazy` and arbitrary derivation reports. + +- [#7109](https://github.com/Effect-TS/effect/pull/7109) [`08d0d39`](https://github.com/Effect-TS/effect/commit/08d0d39a225deccb9db213ab5fcf55edb9f9ba5d) Thanks @fubhy! - Fix `RcRef` leaking resources acquired before a failed acquisition. + +- [#7146](https://github.com/Effect-TS/effect/pull/7146) [`548908a`](https://github.com/Effect-TS/effect/commit/548908a71d9337cb7defe7fc93b2fba8f6a04b6f) Thanks @gcanti! - Improve Schema representation identity, anonymous-reference eligibility, and JSON Schema alias finalization. + +- [#6862](https://github.com/Effect-TS/effect/pull/6862) [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999) Thanks @fubhy! - Ensure `ScopedRef.set` releases a replacement when the previous value's finalizer defects. + +- [#7060](https://github.com/Effect-TS/effect/pull/7060) [`d170596`](https://github.com/Effect-TS/effect/commit/d17059615cca37ca2776654078fe0501ac5202e6) Thanks @fubhy! - Preserve `maxItems` semantics when importing JSON Schema `prefixItems`. + +- [#7116](https://github.com/Effect-TS/effect/pull/7116) [`aea89d0`](https://github.com/Effect-TS/effect/commit/aea89d0c42ee0ac707a4962cd348fd3158cb469b) Thanks @fubhy! - Keep span end times at zero when tracer timing is disabled. + +- [#7124](https://github.com/Effect-TS/effect/pull/7124) [`deed5fb`](https://github.com/Effect-TS/effect/commit/deed5fbdc91cf8bf8c5fce7dfa5d6527ac944726) Thanks @fubhy! - Use a distinct AES-GCM initialization vector for each encrypted event log entry. `EventLogEncryption.encrypt` now returns each IV with its ciphertext, and encrypted event log clients and servers must be upgraded together because the `WriteEntries` wire shape changed. + +## 4.0.0-beta.105 + +### Patch Changes + +- [#7087](https://github.com/Effect-TS/effect/pull/7087) [`0418564`](https://github.com/Effect-TS/effect/commit/04185644dabb8e4169f1ef6cbbc0b36c4db2f7f4) Thanks @tim-smart! - Recognize tagged Config and RPC errors across duplicated `effect` package copies. + +- [#6827](https://github.com/Effect-TS/effect/pull/6827) [`d334a85`](https://github.com/Effect-TS/effect/commit/d334a8593aafcd60753995a9449b654c67bfdcc1) Thanks @jaipaljadeja! - Add bounded 429 retries and custom response header names to `HttpClient.withRateLimiter`. + +- [#7084](https://github.com/Effect-TS/effect/pull/7084) [`f0be855`](https://github.com/Effect-TS/effect/commit/f0be8554da6ee00293a6b23869ac46a0b0d97dc8) Thanks @tim-smart! - Stop capturing definition-location stack frames in `Context.Service`. + +- [#7090](https://github.com/Effect-TS/effect/pull/7090) [`b206fa5`](https://github.com/Effect-TS/effect/commit/b206fa5d7655c1634c9993410a9203f6616a5ca2) Thanks @tim-smart! - Expose `stdinIsTerminal` and `stdoutIsTerminal` effects through the `Stdio` service. + +- [#7093](https://github.com/Effect-TS/effect/pull/7093) [`b938c8a`](https://github.com/Effect-TS/effect/commit/b938c8ad2823bd88493187922f7d9090eff037b6) Thanks @gcanti! - Add the opt-in `reportInput` parse option for retaining rejected inputs in enumerable fields on value-bearing schema issues and including them in default formatted messages. Value-bearing issue constructors accept the rejected input and parse options directly, and `Schema.Annotations.Issue` now supports `expected` for default messages. + + Schema issues no longer format implicitly through `Issue#toString`. Use `SchemaIssue.makeFormatterDefault()` when a human-readable message is needed. The throwing and Promise-based adapters in `SchemaParser` now use the generic message `"Schema validation failed"` and expose the structured `SchemaIssue.Issue` as the error `cause`; consumers that previously read the formatted error message should inspect and explicitly format that cause instead. + + `Schema.makeEffect` now returns `SchemaIssue.Issue` failures instead of wrapping them in `SchemaError`, and `Schema.withConstructorDefault` accepts an `Effect` that fails with `SchemaIssue.Issue`. Fallible `Optic` operations return structured `SchemaIssue.Issue` failures, while schema failures from `Schema.toIso` and `Schema.toDifferJsonPatch` use the generic error message and preserve the issue in `cause` instead of formatting it internally. + +- [#7097](https://github.com/Effect-TS/effect/pull/7097) [`8525f05`](https://github.com/Effect-TS/effect/commit/8525f05d1e14ea12298e9e1a0df497bfaac2ce9a) Thanks @tim-smart! - Add `Cron.format` for converting a `Cron` instance to a cron expression, with an option to include the seconds field. + +## 4.0.0-beta.104 + +### Minor Changes + +- [#7076](https://github.com/Effect-TS/effect/pull/7076) [`0f721d4`](https://github.com/Effect-TS/effect/commit/0f721d406df8703ea92ca28777b3f09599e2056d) Thanks @tim-smart! - Return the new file offset as a `Size` from `File.seek`. + +### Patch Changes + +- [#6934](https://github.com/Effect-TS/effect/pull/6934) [`1001bcc`](https://github.com/Effect-TS/effect/commit/1001bccb9e874918d59dbb36860f1c5d4499ac20) Thanks @tim-smart! - httpapi: add typed response headers across handlers, generated clients (including `HttpApiTest`), streaming responses, and OpenAPI with `HttpApiSchema.WithHeaders`. Add `HttpApiSchema.encodeToWithHeaders` for folding response headers into domain types such as error classes. Explicit `content-type` and `content-length` values applied with `HttpServerResponse.setHeader` or `setHeaders` now override body-derived values. + +- [#7044](https://github.com/Effect-TS/effect/pull/7044) [`993ba60`](https://github.com/Effect-TS/effect/commit/993ba60ee6c7ca6eb84522040f8b0d268b6ba7d4) Thanks @fubhy! - Commit SQL event journal entries only after their write callback succeeds. + +- [#6957](https://github.com/Effect-TS/effect/pull/6957) [`67faacd`](https://github.com/Effect-TS/effect/commit/67faacd4679242559bee31717c05a5b10b990322) Thanks @fubhy! - Select Bash completions for the active positional argument. + +- [#6941](https://github.com/Effect-TS/effect/pull/6941) [`b78acdf`](https://github.com/Effect-TS/effect/commit/b78acdf422568f10ae8684fd3f10d52b065f0b56) Thanks @fubhy! - Generate even and odd safe integers in Crypto random APIs. + +- [#6965](https://github.com/Effect-TS/effect/pull/6965) [`fbb9ce5`](https://github.com/Effect-TS/effect/commit/fbb9ce5e625d1a7d6b7005bda42cbb6cd31476c5) Thanks @fubhy! - Correct the runtime tag spelling for `CliError.UnknownSubcommand`. + +- [#6963](https://github.com/Effect-TS/effect/pull/6963) [`722ea48`](https://github.com/Effect-TS/effect/commit/722ea484c9d01364c9242d929c0a564f7831a57c) Thanks @fubhy! - Exclude disabled choices from multi-select prompt selection and submission. + +- [#7001](https://github.com/Effect-TS/effect/pull/7001) [`3058fd5`](https://github.com/Effect-TS/effect/commit/3058fd594f5a683034212d71d957017fcc084006) Thanks @fubhy! - Keep ordered SQL resolver results aligned when batched request encoding fails. + +- [#6937](https://github.com/Effect-TS/effect/pull/6937) [`62d0575`](https://github.com/Effect-TS/effect/commit/62d057566c241405c23ecf0bf4156186bd2be924) Thanks @fubhy! - Fix the encoded output type of `TestSchema.Encoding.encodeUnknownEffect`. + +- [#7014](https://github.com/Effect-TS/effect/pull/7014) [`99dd6b5`](https://github.com/Effect-TS/effect/commit/99dd6b580434f97c5b40adc919f429e4abc3dfe7) Thanks @tim-smart! - Add lightweight INI, YAML, and TOML parsers under `effect/unstable/encoding` and remove their runtime dependencies. + +- [#7053](https://github.com/Effect-TS/effect/pull/7053) [`7963ce1`](https://github.com/Effect-TS/effect/commit/7963ce1cd95f037fbefea67a29ead49cce4d16cb) Thanks @fubhy! - Fix arbitrary generation for tuples with multiple optional elements. + +- [#7047](https://github.com/Effect-TS/effect/pull/7047) [`af14e75`](https://github.com/Effect-TS/effect/commit/af14e752edd65e2b652e960411afafc88975a8d8) Thanks @fubhy! - Fix `Tuple.pick` return types to preserve the requested index order and duplicate indices. + +- [#7066](https://github.com/Effect-TS/effect/pull/7066) [`24e22d2`](https://github.com/Effect-TS/effect/commit/24e22d23a73a2e93ebf6d8edd2246a4a406942c8) Thanks @fubhy! - Close `ResourceMap` acquisition scopes when a lookup fails. + +- [#7036](https://github.com/Effect-TS/effect/pull/7036) [`647d14e`](https://github.com/Effect-TS/effect/commit/647d14e572c8004fa92fba256e00552b42bf34b7) Thanks @fubhy! - Fix scoped reentrant lock finalizers releasing under the wrong fiber owner. + +- [#6983](https://github.com/Effect-TS/effect/pull/6983) [`1434eec`](https://github.com/Effect-TS/effect/commit/1434eecbd368e00839c24b3950f0b7a69218669a) Thanks @fubhy! - Apply byte range and chunk size options to default Web file responses. + +- [#7071](https://github.com/Effect-TS/effect/pull/7071) [`a5278b1`](https://github.com/Effect-TS/effect/commit/a5278b18242011d1b2b08304c7c128151f9a4370) Thanks @fubhy! - Fix MCP sampling metadata optionality and validate it as an object. + +- [#6946](https://github.com/Effect-TS/effect/pull/6946) [`6af04a5`](https://github.com/Effect-TS/effect/commit/6af04a50bd019238f6acdb9cbda40439a3c09210) Thanks @fubhy! - Defer memoized Layer state installation until Effect execution. + +- [#6943](https://github.com/Effect-TS/effect/pull/6943) [`cb6c837`](https://github.com/Effect-TS/effect/commit/cb6c8376b2f322d4e7cbfc0973fc3b4f2951ee6e) Thanks @fubhy! - Reject zero execution attempts in `ExecutionPlan` steps. + +- [#7026](https://github.com/Effect-TS/effect/pull/7026) [`d44cead`](https://github.com/Effect-TS/effect/commit/d44cead7e0e0ce61f0d980906e494f49a07e7899) Thanks @tim-smart! - Add execution-plan lifecycle events via an optional `onEvent` handler on `Effect.withExecutionPlan` and `Stream.withExecutionPlan`. + + The handler receives an `ExecutionPlan.Event`, a tagged union of `AttemptStart`, `AttemptSuccess`, and `AttemptFailure`, allowing attempt outcomes to be observed from outside the effect for logging and metrics: + + ```ts + import { Effect } from "effect" + + Effect.withExecutionPlan(program, plan, { + onEvent: (event) => Effect.log("execution plan event", event) + }) + ``` + + Every `AttemptStart` is followed by exactly one terminal event. `AttemptFailure` carries the full failure `Cause`, so defects and interruption are reported as well as expected errors, and terminal events run like finalizers so they are emitted even when the attempt is interrupted. Event numbering matches `ExecutionPlan.CurrentMetadata`: `attempt` is cumulative across steps, while `stepAttempt` is 1-based within the current step. + +- [#7077](https://github.com/Effect-TS/effect/pull/7077) [`88c7632`](https://github.com/Effect-TS/effect/commit/88c7632c2b59a49fcc40d250865bd8d0dccf31b0) Thanks @tim-smart! - Rename `Schedule.andThen` and `Schedule.andThenResult` to `Schedule.concat` and `Schedule.concatResult`. + +- [#6975](https://github.com/Effect-TS/effect/pull/6975) [`abcbb2a`](https://github.com/Effect-TS/effect/commit/abcbb2abe16f1b6c587c15007df14371e1e70e93) Thanks @fubhy! - Encode SSE events with empty data as dispatchable events. + +- [#7037](https://github.com/Effect-TS/effect/pull/7037) [`8f63cce`](https://github.com/Effect-TS/effect/commit/8f63cce636700fde26b140b82e350ef916989d86) Thanks @fubhy! - Preserve OTLP metric delta checkpoints when an export fails. + +- [#7057](https://github.com/Effect-TS/effect/pull/7057) [`d56dfcf`](https://github.com/Effect-TS/effect/commit/d56dfcf54c2b9c53c3d098ce4b0ffcc84496c5f7) Thanks @fubhy! - Fix the error type exposed by the curried `Sink.catch` overload. + +- [#6947](https://github.com/Effect-TS/effect/pull/6947) [`a98cda9`](https://github.com/Effect-TS/effect/commit/a98cda9422e1352f22e81696f759f326ffcfb667) Thanks @fubhy! - Check symbol-keyed properties in Match object patterns. + +- [#6956](https://github.com/Effect-TS/effect/pull/6956) [`6704bb8`](https://github.com/Effect-TS/effect/commit/6704bb84c320547f83cf50e8586ffc4c5e4c3cc5) Thanks @fubhy! - Emit valid CSI sequences from the unstable CLI `cursorTo` helper. + +- [#7008](https://github.com/Effect-TS/effect/pull/7008) [`6143de2`](https://github.com/Effect-TS/effect/commit/6143de21ee22038b45a8d4eba86f5aade6238eba) Thanks @tim-smart! - Prevent Bash completions from treating flag values as subcommands. + +- [#7032](https://github.com/Effect-TS/effect/pull/7032) [`936b135`](https://github.com/Effect-TS/effect/commit/936b1358396eb0a1a7c8e0878ba63297e2106812) Thanks @marbemac! - Fix a `@effect/cluster` shutdown deadlock on single-runner topologies (e.g. single-node deployments and `TestRunner`), where `Sharding.sendOutgoing` retried `EntityNotAssignedToRunner` forever during teardown. + +- [#6940](https://github.com/Effect-TS/effect/pull/6940) [`1bbae84`](https://github.com/Effect-TS/effect/commit/1bbae84f88b577a26d04ceb2e76d3143d09c4a20) Thanks @fubhy! - Omit services removed by `Context.addOrOmit` from the returned context type. + +- [#7065](https://github.com/Effect-TS/effect/pull/7065) [`d795ee7`](https://github.com/Effect-TS/effect/commit/d795ee771701ea62bd187ef7c0307d9737f68c1a) Thanks @tim-smart! - Fix DevTools span requests to preserve their state when queued for sending. + +- [#7016](https://github.com/Effect-TS/effect/pull/7016) [`0a82d88`](https://github.com/Effect-TS/effect/commit/0a82d88b7da73278b6f270118e396d5ed4a64747) Thanks @brandon-julio-t! - Normalize cluster durable clock wake-up timestamps to whole milliseconds. + +- [#6945](https://github.com/Effect-TS/effect/pull/6945) [`9215bc5`](https://github.com/Effect-TS/effect/commit/9215bc5da7dd10aa45f07fe44b98f06b6e433d62) Thanks @fubhy! - Preserve integral precision when parsing decimal nano and micro duration inputs + +- [#7050](https://github.com/Effect-TS/effect/pull/7050) [`a1b5df2`](https://github.com/Effect-TS/effect/commit/a1b5df2064d92431cfc6e638af613cc3114313d7) Thanks @fubhy! - Include schedule errors in the error channel of `Effect.schedule` and `Effect.scheduleFrom`. + +- [#7062](https://github.com/Effect-TS/effect/pull/7062) [`92a9ac5`](https://github.com/Effect-TS/effect/commit/92a9ac5ac0aa63d8975b9ba7a094d6a8f59a98f2) Thanks @fubhy! - Fix the inspectable JSON identity of `FiberSet`. + +- [#6959](https://github.com/Effect-TS/effect/pull/6959) [`6bde7f2`](https://github.com/Effect-TS/effect/commit/6bde7f27f3243427203e53fe74472990e5c2a349) Thanks @fubhy! - Match Fish completions against the full nested command path. + +- [#6951](https://github.com/Effect-TS/effect/pull/6951) [`a712131`](https://github.com/Effect-TS/effect/commit/a7121310dbb60cbd819bbd702f97663098ec7bb8) Thanks @fubhy! - Use the supplied hash for `HashMap.modifyHash` insertions, updates, and removals. + +- [#6989](https://github.com/Effect-TS/effect/pull/6989) [`2e6f760`](https://github.com/Effect-TS/effect/commit/2e6f760dcb44e2b984f3311a8af03a1d68a2ec7e) Thanks @fubhy! - Support standard `BodyInit` values when reading converted client request bodies through `HttpServerRequest`. + +- [#6986](https://github.com/Effect-TS/effect/pull/6986) [`aa05804`](https://github.com/Effect-TS/effect/commit/aa0580497e027ed30b756058db0067c3fe07664f) Thanks @fubhy! - Synchronize HTTP server response content headers when replacing the body. + +- [#6944](https://github.com/Effect-TS/effect/pull/6944) [`badd3bf`](https://github.com/Effect-TS/effect/commit/badd3bf65fac4dd1e66e1f602db43659722dfced) Thanks @fubhy! - Make `Iterable.flatten` stack safe across empty iterables. + +- [#6968](https://github.com/Effect-TS/effect/pull/6968) [`02b0265`](https://github.com/Effect-TS/effect/commit/02b02651ede46a5a2dd3ef8081d0ad89648d0cbf) Thanks @fubhy! - Allow MCP tool calls to omit optional arguments. + +- [#7033](https://github.com/Effect-TS/effect/pull/7033) [`3437e21`](https://github.com/Effect-TS/effect/commit/3437e21a56d805781c5e5946a6189795a1dfd411) Thanks @fubhy! - Fix memory journal conflict detection skipping the first newer entry. + +- [#7034](https://github.com/Effect-TS/effect/pull/7034) [`41a550d`](https://github.com/Effect-TS/effect/commit/41a550d1fed31e829929a8f5362b5340303164ac) Thanks @fubhy! - Return the first unused remote sequence from the in-memory event journal. + +- [#7042](https://github.com/Effect-TS/effect/pull/7042) [`17b5d50`](https://github.com/Effect-TS/effect/commit/17b5d50219ad49533cf9e33d01924a3e16af5eb3) Thanks @fubhy! - Relay entries imported into an in-memory event journal to other remotes. + +- [#7074](https://github.com/Effect-TS/effect/pull/7074) [`96e5e95`](https://github.com/Effect-TS/effect/commit/96e5e9576b0315c747462761a61940ff9fe32dd1) Thanks @fubhy! - Preserve and update runner health in the in-memory cluster runner storage. + +- [#7038](https://github.com/Effect-TS/effect/pull/7038) [`e4d589e`](https://github.com/Effect-TS/effect/commit/e4d589e0ea08dc57c4793053b395dc0fcc499f34) Thanks @fubhy! - Clear in-memory message primary-key indexes when clearing an entity address. + +- [#7005](https://github.com/Effect-TS/effect/pull/7005) [`ae4cf7b`](https://github.com/Effect-TS/effect/commit/ae4cf7b5e2cb5f8c55657e31a61789ad21c38c18) Thanks @fubhy! - Generate valid MSSQL upserts for multi-table persistence. + +- [#6998](https://github.com/Effect-TS/effect/pull/6998) [`6ef5f1a`](https://github.com/Effect-TS/effect/commit/6ef5f1a041f3a40bf03fadd0b1feb275c277c635) Thanks @fubhy! - Decode split UTF-8 sequences correctly in NDJSON streams. + +- [#6972](https://github.com/Effect-TS/effect/pull/6972) [`2235a29`](https://github.com/Effect-TS/effect/commit/2235a29502c3f33cf6468511ad931089013a7916) Thanks @tim-smart! - Persist a serializable defect when a cluster reply cannot be encoded, preventing persisted entity callers from hanging. + +- [#6962](https://github.com/Effect-TS/effect/pull/6962) [`b32f4cb`](https://github.com/Effect-TS/effect/commit/b32f4cb7b2d8ebe817075322622498e3beb05336) Thanks @fubhy! - Support empty records and non-array iterables in `Prompt.all`. + +- [#7023](https://github.com/Effect-TS/effect/pull/7023) [`7f4c095`](https://github.com/Effect-TS/effect/commit/7f4c095b62da43780dd7fc2a5d1785ddfce60edf) Thanks @tim-smart! - Rename `RateLimiter.makeSleep` to `RateLimiter.sleep` and support self-first partially applied and uncurried usage. + +- [#7041](https://github.com/Effect-TS/effect/pull/7041) [`5f3fb81`](https://github.com/Effect-TS/effect/commit/5f3fb814d18d8a54946c1c1cd0b41459cdb24006) Thanks @fubhy! - End runner streams after emitting their terminal replies. + +- [#7020](https://github.com/Effect-TS/effect/pull/7020) [`17f0b91`](https://github.com/Effect-TS/effect/commit/17f0b91a243ccfe4a38d27debdc983adf434e738) Thanks @gcanti! - Fix `Schema.make` to preserve existing nested `Schema.Class` instances, including in array fields, while recursively constructing plain class inputs provided at runtime inside unions. Constructor defaults remain scoped to structural field and element occurrences, with `SchemaAST.Context.constructorDefault` representing the single default link for each occurrence. + + Optimize `Function.memoize` to use a single `WeakMap` lookup for cached values. Its callback no longer accepts `undefined` as a return type because `undefined` represents a cache miss. + + The performance of the two array paths can be reproduced by saving the following program as + `scratchpad/schema-make-6890-benchmark.ts` and running `node scratchpad/schema-make-6890-benchmark.ts` from the repository + root: + + ```ts + import { Schema } from "effect" + import { performance } from "node:perf_hooks" + + class Row extends Schema.Class("Row")({ value: Schema.String }) {} + class DirectTable extends Schema.Class("DirectTable")({ rows: Schema.Array(Row) }) {} + class UnionTable extends Schema.Class("UnionTable")({ rows: Schema.Array(Schema.Union([Row])) }) {} + + const rows = Array.from({ length: 30_000 }, (_, value) => Row.make({ value: String(value) })) + + function benchmark(label: string, make: () => { readonly rows: ReadonlyArray }) { + const samples: Array = [] + for (let i = 0; i < 6; i++) { + const start = performance.now() + const result = make() + samples.push(performance.now() - start) + if (result.rows[0] !== rows[0] || result.rows.at(-1) !== rows.at(-1)) { + throw new Error(`${label} did not preserve Row identity`) + } + } + console.log(`${label}: ${samples.slice(1).map((n) => n.toFixed(3)).join(", ")} ms`) + } + + benchmark("Array(Class)", () => DirectTable.make({ rows })) + benchmark("Array(Union([Class]))", () => UnionTable.make({ rows })) + ``` + + Representative local results on Node 24.12.0 (six runs, with the first discarded): + + ```text + Array(Class): 0.639, 0.498, 0.447, 0.448, 0.451 ms + Array(Union([Class])): 3.141, 2.195, 2.126, 2.108, 2.057 ms + ``` + +- [#7055](https://github.com/Effect-TS/effect/pull/7055) [`0cdadd7`](https://github.com/Effect-TS/effect/commit/0cdadd75bc8abbbcad7956a4bc71f4e7a9b13250) Thanks @fubhy! - Fix `Stream.slidingSize` to produce the same windows regardless of upstream chunk boundaries. + +- [#6978](https://github.com/Effect-TS/effect/pull/6978) [`39b57d7`](https://github.com/Effect-TS/effect/commit/39b57d7857358040558b67dd33eafc7bb5457830) Thanks @fubhy! - Retain the last SSE event ID across dispatched events. + +- [#6976](https://github.com/Effect-TS/effect/pull/6976) [`5a6a573`](https://github.com/Effect-TS/effect/commit/5a6a5738e5bfc39e3a37ae7ba99081601fa19ac3) Thanks @fubhy! - Recognize and ignore a leading UTF-8 byte order mark in server-sent event streams. + +- [#7048](https://github.com/Effect-TS/effect/pull/7048) [`59f5e99`](https://github.com/Effect-TS/effect/commit/59f5e9981913b92d7a9beb2214a21d658b999d3a) Thanks @fubhy! - Ignore malformed retry directives when parsing server-sent event streams. + +- [#7028](https://github.com/Effect-TS/effect/pull/7028) [`45379d6`](https://github.com/Effect-TS/effect/commit/45379d6179ee4df2cbd3f848bd39ff7149c24a38) Thanks @fubhy! - Fix `Trie.insert` to replace existing values without mutating the original trie or increasing its size. + +- [#6973](https://github.com/Effect-TS/effect/pull/6973) [`1949439`](https://github.com/Effect-TS/effect/commit/1949439175809ef81ab9c6411ed5559109edb4c9) Thanks @fubhy! - Separate the default `VariantSchema` cache from named variant entries. + +- [#7072](https://github.com/Effect-TS/effect/pull/7072) [`e443403`](https://github.com/Effect-TS/effect/commit/e443403cf0e4effea14bb6cd950c5ac1c86cc748) Thanks @fubhy! - Keep MCP tool calls that return void successful. + +- [#7000](https://github.com/Effect-TS/effect/pull/7000) [`03af7e8`](https://github.com/Effect-TS/effect/commit/03af7e85551204c605ea2fa2c43c10a4538ac8fb) Thanks @fubhy! - Close suspended workflow scopes after resumed completion. + +- [#7027](https://github.com/Effect-TS/effect/pull/7027) [`130b28d`](https://github.com/Effect-TS/effect/commit/130b28df552d7053407b041a96ff09dae82575e5) Thanks @pawelblaszczyk5! - Prevent Effect.updateService and Effect.updateServiceScoped supertype widening + +- [#6948](https://github.com/Effect-TS/effect/pull/6948) [`c987a12`](https://github.com/Effect-TS/effect/commit/c987a12a01b6a52ad53d29edf02613b03574dbcc) Thanks @fubhy! - Honor numeric zero time-to-live values in `Cache.make` and `ScopedCache.make`. + +- [#6974](https://github.com/Effect-TS/effect/pull/6974) [`4158562`](https://github.com/Effect-TS/effect/commit/41585620977de9b84171f76619b72e29cc2284e5) Thanks @fubhy! - Handle accepted undefined fields during variant extraction. + +- [#7013](https://github.com/Effect-TS/effect/pull/7013) [`306014a`](https://github.com/Effect-TS/effect/commit/306014a1ce4d5cb956c76bdc20e4e28ab3e61a6a) Thanks @tim-smart! - Fix several edge cases in the vendored FindMyWay router. + +- [#6949](https://github.com/Effect-TS/effect/pull/6949) [`729a663`](https://github.com/Effect-TS/effect/commit/729a663275dd31f2357c446fe69664429220a83d) Thanks @fubhy! - Keep TestClock nanosecond access total after infinite adjustments. + +- [#6997](https://github.com/Effect-TS/effect/pull/6997) [`caf84b6`](https://github.com/Effect-TS/effect/commit/caf84b660044089e8d7f4067b279b27b8b50e8fd) Thanks @fubhy! - Isolate compiled SQL fragment caches by compiler instance. + +- [#6960](https://github.com/Effect-TS/effect/pull/6960) [`ce067f7`](https://github.com/Effect-TS/effect/commit/ce067f799ea27735d4194345298a216aaf429f01) Thanks @fubhy! - Mark omittable CLI flags and arguments as optional in structured help. + +- [#7025](https://github.com/Effect-TS/effect/pull/7025) [`7a41f5a`](https://github.com/Effect-TS/effect/commit/7a41f5aa72d540ecf2746992ecc3fa3e6b40d31f) Thanks @pawelblaszczyk5! - Prevent Effect.provideServiceEffect supertype widening + +- [#7056](https://github.com/Effect-TS/effect/pull/7056) [`781022a`](https://github.com/Effect-TS/effect/commit/781022acdd3537ca18c88e2fa3681bafa6ef1b21) Thanks @fubhy! - Fix Map and Set equality allowing a right-side entry to match multiple left-side entries. + +- [#7063](https://github.com/Effect-TS/effect/pull/7063) [`39f1297`](https://github.com/Effect-TS/effect/commit/39f1297acc08864feb12de6b8cf2bf73434f6cf5) Thanks @fubhy! - Preserve literal element types in `Tuple.make`. + +- [#6955](https://github.com/Effect-TS/effect/pull/6955) [`2db266b`](https://github.com/Effect-TS/effect/commit/2db266b1bfbc81868bc1778c37c76032a267c79f) Thanks @fubhy! - Include plain variant structs in the default variant union. + +- [#6954](https://github.com/Effect-TS/effect/pull/6954) [`2141e28`](https://github.com/Effect-TS/effect/commit/2141e28903754d72604acf81673ceb2c62a56646) Thanks @fubhy! - Preserve CRLF state across SSE input chunk boundaries. + +- [#6950](https://github.com/Effect-TS/effect/pull/6950) [`3c5e429`](https://github.com/Effect-TS/effect/commit/3c5e429878669ffcf5e0da4ddfbf50bde5bbcaad) Thanks @fubhy! - Preserve nanosecond precision for large `TestClock` wall-clock timestamps. + +- [#6958](https://github.com/Effect-TS/effect/pull/6958) [`20ddc63`](https://github.com/Effect-TS/effect/commit/20ddc630584f8fe488162ba384adcae53fc6810a) Thanks @fubhy! - Preserve hidden command metadata when adding subcommands or shared flags. + +- [#6939](https://github.com/Effect-TS/effect/pull/6939) [`841b3ea`](https://github.com/Effect-TS/effect/commit/841b3ea6ae19a784bc1c20497b02f632af0c91e9) Thanks @fubhy! - Preserve sibling provider input evidence when `Config.all` evaluates a failing child. + +- [#6836](https://github.com/Effect-TS/effect/pull/6836) [`82a3fbf`](https://github.com/Effect-TS/effect/commit/82a3fbfce8b9df33e587076b7d7168ecd6799e17) Thanks @mkdynamic! - Route provider-executed tool results into the assistant message in `Prompt.fromResponseParts` + +- [#7003](https://github.com/Effect-TS/effect/pull/7003) [`eb9ee83`](https://github.com/Effect-TS/effect/commit/eb9ee83b38844a71d1cd5653a229309cfcb04a36) Thanks @fubhy! - Persist permanent entries in KVS `setMany` operations. + +- [#7039](https://github.com/Effect-TS/effect/pull/7039) [`64dc7c7`](https://github.com/Effect-TS/effect/commit/64dc7c76dc5c89887b9e7c181d1873dcbb7820d1) Thanks @fubhy! - Fix failed `ResourceRef` rebuilds permanently blocking waiters. + +- [#6971](https://github.com/Effect-TS/effect/pull/6971) [`84dc8ab`](https://github.com/Effect-TS/effect/commit/84dc8ab7accc682bc668c78a97e4a1776b633be8) Thanks @tim-smart! - Serialize concurrent nested SQL transactions to prevent savepoint collisions. Cross-dependent sibling nested + transactions now deadlock instead of interleaving and risking silent data corruption. + +- [#6952](https://github.com/Effect-TS/effect/pull/6952) [`b4463f4`](https://github.com/Effect-TS/effect/commit/b4463f46fc33d3b01ea5eadd7d012a5abda347a3) Thanks @fubhy! - Register alternate flags used by `Param.orElse` and `Param.orElseResult`. + +- [#6732](https://github.com/Effect-TS/effect/pull/6732) [`592dd36`](https://github.com/Effect-TS/effect/commit/592dd361645739ac0cd8e6babb084cd27403c172) Thanks @tim-smart! - Rename the Schema error constructors to align with their `Data` counterparts. + + - `Schema.ErrorClass` is now `Schema.Error`. + - `Schema.TaggedErrorClass` is now `Schema.TaggedError`. + - The JavaScript `Error` instance schema is now `Schema.ErrorInstance`. + - `Schema.ErrorReviver` is now `Schema.ErrorInstanceReviver`. + +- [#7068](https://github.com/Effect-TS/effect/pull/7068) [`85d2b44`](https://github.com/Effect-TS/effect/commit/85d2b446e3059de4919be730105868f79728308d) Thanks @tim-smart! - Report retried RPC socket open failures through the `onTransientError` protocol hook and fail in-flight requests when the retry policy is exhausted. + +- [#7006](https://github.com/Effect-TS/effect/pull/7006) [`32e4a69`](https://github.com/Effect-TS/effect/commit/32e4a69b3151b7ec4058af2213b96a41d11e9e06) Thanks @fubhy! - Scope custom persisted queue ID deduplication to each named queue. + +- [#6938](https://github.com/Effect-TS/effect/pull/6938) [`13c5872`](https://github.com/Effect-TS/effect/commit/13c5872ed30830360367ad89af2dab68a003c351) Thanks @fubhy! - Honor populated variables before dotenv expansion defaults in `ConfigProvider`. + +- [#7040](https://github.com/Effect-TS/effect/pull/7040) [`3454cdb`](https://github.com/Effect-TS/effect/commit/3454cdb528fdb5d3ed0c5c5c8169bc47de41fbd8) Thanks @fubhy! - Fix `SynchronizedRef.getAndUpdateSome` to update its backing ref. + +- [#7018](https://github.com/Effect-TS/effect/pull/7018) [`e930804`](https://github.com/Effect-TS/effect/commit/e9308045be1d8a00c0b4046f1e8ff22cf68c93da) Thanks @tim-smart! - Hold persisted cluster messages while entity layers are still registering, while retaining a bounded failure when + registration never begins. + +- [#6987](https://github.com/Effect-TS/effect/pull/6987) [`7f12d4b`](https://github.com/Effect-TS/effect/commit/7f12d4b4e731dc3a213ae5c3f60db9edc50292d2) Thanks @fubhy! - Map WebSocket send exceptions and transform stream write rejections to typed `SocketError` failures. + +- [#6977](https://github.com/Effect-TS/effect/pull/6977) [`181c9ef`](https://github.com/Effect-TS/effect/commit/181c9ef5e5d4ab247bf4aec06424f15b0a1e802e) Thanks @fubhy! - Default empty Server-Sent Event types to `message`. + +- [#7010](https://github.com/Effect-TS/effect/pull/7010) [`dd9f891`](https://github.com/Effect-TS/effect/commit/dd9f891e23f316abb6192893008f0e33ece9d97d) Thanks @tim-smart! - Rename `Command.withHidden` to `Command.unlisted`, along with the `hidden` command property which is now `unlisted`. + +- [#7054](https://github.com/Effect-TS/effect/pull/7054) [`433fb81`](https://github.com/Effect-TS/effect/commit/433fb81ca4c15c681a8ae097ce3ff9bd3a9c9aa5) Thanks @fubhy! - Fix the return type of `Channel.runCount` to expose its numeric result. + +- [#7012](https://github.com/Effect-TS/effect/pull/7012) [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217) Thanks @tim-smart! - Vendor the multipart parser as `effect/unstable/http/MultipartParser`, add the Node.js adapter at `@effect/platform-node/NodeMultipartParser`, and remove the external `multipasta` dependency. + +- [#6953](https://github.com/Effect-TS/effect/pull/6953) [`6124ab3`](https://github.com/Effect-TS/effect/commit/6124ab39eb64688fbd5d688d24766542f9cb5a2c) Thanks @fubhy! - Reject truncated MessagePack frames at the end of a stream. + +- [#6961](https://github.com/Effect-TS/effect/pull/6961) [`01bd954`](https://github.com/Effect-TS/effect/commit/01bd9546f142706fca1628f7261e6d1cb9638948) Thanks @fubhy! - Preserve file and directory semantics in CLI completion descriptors. + +- [#6990](https://github.com/Effect-TS/effect/pull/6990) [`ba2c3aa`](https://github.com/Effect-TS/effect/commit/ba2c3aa05eb87ec05d263b960017ecf29746f66a) Thanks @fubhy! - Generate unique persisted paths for multipart files with duplicate filenames. + +- [#7019](https://github.com/Effect-TS/effect/pull/7019) [`0a45ef3`](https://github.com/Effect-TS/effect/commit/0a45ef3bb4a1ae9b345c43c548db4336a31b3191) Thanks @tim-smart! - Round Redis persistence TTLs up to whole milliseconds before passing them to integer-only expiration commands. + +- [#7012](https://github.com/Effect-TS/effect/pull/7012) [`8459cdb`](https://github.com/Effect-TS/effect/commit/8459cdbae8a476dc04b6247fffe6a1668dcb1217) Thanks @tim-smart! - Prevent malformed encoded multipart filenames from throwing during parsing. + +- [#7029](https://github.com/Effect-TS/effect/pull/7029) [`eaa7e71`](https://github.com/Effect-TS/effect/commit/eaa7e71b88bf59b24610128c6115a2a126432731) Thanks @fubhy! - Fix unencrypted event log conflict scanning to inspect the newer history suffix. + +- [#6988](https://github.com/Effect-TS/effect/pull/6988) [`db4c2cc`](https://github.com/Effect-TS/effect/commit/db4c2ccdec77d813b6f4cc72a302ee7c4fe6e39d) Thanks @fubhy! - Preserve lexical ordering in streaming template interpolation. + +- [#6964](https://github.com/Effect-TS/effect/pull/6964) [`22f150a`](https://github.com/Effect-TS/effect/commit/22f150a0936cef30517e87eaca73bff1c5e4873a) Thanks @fubhy! - Correct year, ordinal, and meridiem date-mask formatting. + +- [#6966](https://github.com/Effect-TS/effect/pull/6966) [`90ffb08`](https://github.com/Effect-TS/effect/commit/90ffb083b3091c211300f50a42ba7bf56536c0ee) Thanks @fubhy! - Preserve fractional leading zeros while editing float prompts. + +- [#6982](https://github.com/Effect-TS/effect/pull/6982) [`d517692`](https://github.com/Effect-TS/effect/commit/d517692ef75f45d5f6d9d68b32d41fa0ccc56c99) Thanks @fubhy! - Reject NDJSON values without a JSON representation. + +- [#6942](https://github.com/Effect-TS/effect/pull/6942) [`01af079`](https://github.com/Effect-TS/effect/commit/01af079c189d1fc5067d3b1933b2870c4baf2693) Thanks @fubhy! - Validate object-based DateTime instants before construction. + +- [#6985](https://github.com/Effect-TS/effect/pull/6985) [`32a59e8`](https://github.com/Effect-TS/effect/commit/32a59e8058b1ec9738cb083cf1cb116b393ca114) Thanks @fubhy! - Preserve original HTTP response bytes when reading response text first. + +## 4.0.0-beta.103 + +### Minor Changes + +- [#6793](https://github.com/Effect-TS/effect/pull/6793) [`b2f95a9`](https://github.com/Effect-TS/effect/commit/b2f95a9c2f2581deb89dc3bae9e89cf819e82923) Thanks @tim-smart! - Add `Semaphore.takeIfAvailable` for non-blocking manual permit acquisition. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - Expose object-shaped Toolkit success schemas as MCP tool output schemas. + +- [#6807](https://github.com/Effect-TS/effect/pull/6807) [`d0f1a22`](https://github.com/Effect-TS/effect/commit/d0f1a2295155c350b04efb46852cb40032805273) Thanks @alecbuffi! - Separate wall-clock timestamps from monotonic elapsed time. + + `Clock.Clock` now requires `monotonicTimeNanosUnsafe()` and `monotonicTimeNanos` for measuring elapsed time. Custom `Clock` implementations must provide both members. The live clock's `currentTimeNanos` now re-anchors its high-resolution Unix wall-clock timestamp when it drifts from `Date.now()`, while `Effect.timed`, duration metric tracking, and `Sink.withDuration` use monotonic time so wall-clock corrections do not distort elapsed durations. + +### Patch Changes + +- [#6697](https://github.com/Effect-TS/effect/pull/6697) [`e56cd8f`](https://github.com/Effect-TS/effect/commit/e56cd8f90c3559baccf8fcf2852ea911235d5944) Thanks @schickling-assistant! - Add a configurable filter for HTTP client request and response header span attributes. + +- [#6883](https://github.com/Effect-TS/effect/pull/6883) [`f77c120`](https://github.com/Effect-TS/effect/commit/f77c120d8e04779ddeb8bce8e9cde932f268e4b6) Thanks @gcanti! - Add support for converting JSON Schema documents to Draft-04, preserve literal `$ref` values, `$ref` sibling constraints, `not`, `readOnly`, and `writeOnly` in Draft-07 conversions, correct the Draft-07 meta-schema URI, and prevent OpenAPI component-key collisions during conversion. + +- [#6564](https://github.com/Effect-TS/effect/pull/6564) [`04fd44a`](https://github.com/Effect-TS/effect/commit/04fd44a42abfa8dc2642300dcf49ee48c8ef4539) Thanks @AVtheking! - Run shared-table SQL persistence expiration cleanup in indexed, bounded background batches. + +- [#6911](https://github.com/Effect-TS/effect/pull/6911) [`b74333d`](https://github.com/Effect-TS/effect/commit/b74333d83e15b9d042e4698ad23040de60454afe) Thanks @fubhy! - Update existing `HashRing` nodes when adding a value with the same primary key. + +- [#6909](https://github.com/Effect-TS/effect/pull/6909) [`1c40b28`](https://github.com/Effect-TS/effect/commit/1c40b2809503d6aa1358777196fc66317906e657) Thanks @AlfGoto! - Add `DateTime.toEpochSeconds` and `DateTime.fromEpochSeconds` for converting date-time values to and from Unix epoch seconds. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP tool handler defects now return a stable internal error without exposing defect details. + +- [#6874](https://github.com/Effect-TS/effect/pull/6874) [`b3901d2`](https://github.com/Effect-TS/effect/commit/b3901d29c543fd5bd05ceec669a17896c8e19006) Thanks @fubhy! - Fix `Equal.equals` and `Hash.hash` to handle invalid dates and `DataView` values without throwing. + +- [#6869](https://github.com/Effect-TS/effect/pull/6869) [`4a0984a`](https://github.com/Effect-TS/effect/commit/4a0984af62738fedf4bd3e87adb4d4d641ce9147) Thanks @fubhy! - Fix SQL-backed Persistence `getMany` to preserve duplicate key positions. + +- [#6868](https://github.com/Effect-TS/effect/pull/6868) [`fffd88b`](https://github.com/Effect-TS/effect/commit/fffd88b3135abdf928ca7c4b0e00e610985091c7) Thanks @fubhy! - Ensure clearing an empty Redis-backed persistence store succeeds. + +- [#6903](https://github.com/Effect-TS/effect/pull/6903) [`f3f6c1e`](https://github.com/Effect-TS/effect/commit/f3f6c1e02cb543423fcffef5dc2db03fac503588) Thanks @fubhy! - Preserve equals signs in inline CLI option values after the first separator. + +- [#6876](https://github.com/Effect-TS/effect/pull/6876) [`ef07642`](https://github.com/Effect-TS/effect/commit/ef07642dfe671d5258b65d1c1480c4d05c495f15) Thanks @fubhy! - Fix `Sink.reduceWhileArray` applying its reducer more than once per input array. + +- [#6802](https://github.com/Effect-TS/effect/pull/6802) [`f1bc827`](https://github.com/Effect-TS/effect/commit/f1bc8274a608813d7b09d28dcca04adbf62f8c92) Thanks @tim-smart! - Cap incomplete RPC frames buffered by the NDJSON and MessagePack streaming decoders, and close socket transports when the limit is exceeded. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - RPC servers now suppress responses after a client cancels an in-flight request. + +- [#6723](https://github.com/Effect-TS/effect/pull/6723) [`081f4d8`](https://github.com/Effect-TS/effect/commit/081f4d8cd06a2ac222d2810b46e61efcee26939e) Thanks @tim-smart! - add platform literal to HttpPlatform + +- [#6788](https://github.com/Effect-TS/effect/pull/6788) [`5287b24`](https://github.com/Effect-TS/effect/commit/5287b24f5f8fa094ba20e117bfb1a80fba6d2cf5) Thanks @gcanti! - Refine the `ConfigProvider` interface so lookup absence uses `undefined` and + path transformation is provider behavior. + + `ConfigProvider.load` and the lookup function accepted by + `ConfigProvider.make` now return `Node | undefined`. Use `undefined` when a path + does not exist and return the `Node` directly when it does. + + `ConfigProvider` now exposes `mapInput` as a capability. The exported + `ConfigProvider.mapInput` combinator delegates to it, preserving transformation + order and composition through `orElse` without requiring provider + representation state. + +- [#6863](https://github.com/Effect-TS/effect/pull/6863) [`13d31cf`](https://github.com/Effect-TS/effect/commit/13d31cfc2dde46210e94391b5b6767ae9aeaf2c9) Thanks @fubhy! - Decode percent-encoded OTLP environment header values. + +- [#6781](https://github.com/Effect-TS/effect/pull/6781) [`acee269`](https://github.com/Effect-TS/effect/commit/acee26944bc89ee554d7b9fadab7443f9edc28a9) Thanks @gcanti! - Deduplicate equivalent fallback definitions when compiling JSON Schema, and reconstruct only definitions reachable from multi-document roots. + + Remove `SchemaMultiDocument` and `fromSchemaMultiDocument`; multi-document import and revival now return the ordered root schemas directly. + + Stop the OpenAPI generator from emitting component schemas that are not reachable from a generated root. + +- [#6717](https://github.com/Effect-TS/effect/pull/6717) [`31170c1`](https://github.com/Effect-TS/effect/commit/31170c19b236c37abb5476c821bc6f5bfa2735ab) Thanks @IMax153! - Document that `CommandOptions.extendEnv` defaults to `false` and that providing `env` without enabling it replaces the inherited child environment. + +- [#6657](https://github.com/Effect-TS/effect/pull/6657) [`205ebc7`](https://github.com/Effect-TS/effect/commit/205ebc776062012581e98fced7ced19adfc44ee7) Thanks @tim-smart! - Use cancellable microtasks when dispatching yielded work from synchronous Effect runs. + +- [#6661](https://github.com/Effect-TS/effect/pull/6661) [`ed0ebf8`](https://github.com/Effect-TS/effect/commit/ed0ebf8e5c864d46fed1f232e99c0e680f10a58f) Thanks @tim-smart! - Fix hydrated atoms with `Atom.withReactivity` to refresh after reactive mutations. + +- [#6665](https://github.com/Effect-TS/effect/pull/6665) [`a3fd084`](https://github.com/Effect-TS/effect/commit/a3fd08482157bd78b089f77c7b173d54ef68b5cd) Thanks @tim-smart! - Fix `HttpRouter.toWebHandler` context inference for services provided by the application layer. + +- [#6681](https://github.com/Effect-TS/effect/pull/6681) [`ee29ddf`](https://github.com/Effect-TS/effect/commit/ee29ddf862c3723ad466abc93ab6f6fe723b2319) Thanks @tim-smart! - Add Web Stream interoperability for `Channel` and `Sink`, plus byte limiting and `ArrayBuffer` collection for `Stream`. + +- [#6730](https://github.com/Effect-TS/effect/pull/6730) [`6086309`](https://github.com/Effect-TS/effect/commit/60863090af8e5af0bfa1435f08dc5390f9993e30) Thanks @tim-smart! - Support replaying initial WebSocket messages and normalize `ArrayBuffer` frames to `Uint8Array`. + +- [#6763](https://github.com/Effect-TS/effect/pull/6763) [`4a57af2`](https://github.com/Effect-TS/effect/commit/4a57af24011db1d66e947289d2f7ffc2074696d2) Thanks @tim-smart! - Validate cookie names, domains, and paths before constructing or serializing cookies. + +- [#6771](https://github.com/Effect-TS/effect/pull/6771) [`660875b`](https://github.com/Effect-TS/effect/commit/660875b4325e6eebb3f04513998301cd2a0847ec) Thanks @tim-smart! - Strip credential headers on cross-origin HTTP redirects and align redirected request methods with fetch. + +- [#6777](https://github.com/Effect-TS/effect/pull/6777) [`8e7c706`](https://github.com/Effect-TS/effect/commit/8e7c706b0aca855489b53d987404566d3e9cb5e7) Thanks @tim-smart! - Bound pending SSE decoder state with a configurable maximum event size. + +- [#6772](https://github.com/Effect-TS/effect/pull/6772) [`5f63adb`](https://github.com/Effect-TS/effect/commit/5f63adbe75fc9d50d23706a52b3e483ad2a1a01c) Thanks @tim-smart! - Reject empty, `.` and `..` keys in file-backed key-value stores. + +- [#6773](https://github.com/Effect-TS/effect/pull/6773) [`053bc42`](https://github.com/Effect-TS/effect/commit/053bc42e2a964755611a216e78ed214322efee37) Thanks @tim-smart! - Escape terminal control characters in unstable CLI error output. + +- [#6898](https://github.com/Effect-TS/effect/pull/6898) [`c0a1534`](https://github.com/Effect-TS/effect/commit/c0a153494484ecf9f0d0f20895a7a648b4be363b) Thanks @tim-smart! - Add HTTP response compression support. Node.js, Bun, and Deno use asynchronous + `node:zlib` one-shot compression for byte-array bodies, preserving an exact + `Content-Length`; stream and raw bodies remain streaming transforms. + +- [#6859](https://github.com/Effect-TS/effect/pull/6859) [`f1e3a37`](https://github.com/Effect-TS/effect/commit/f1e3a378c144f974a6122b299f421b75595af20f) Thanks @fubhy! - Fix `String.snakeToCamel` and `String.snakeToPascal` to return an empty string for empty input. + +- [#6746](https://github.com/Effect-TS/effect/pull/6746) [`cedb01a`](https://github.com/Effect-TS/effect/commit/cedb01a025492a1faf9e59eb23eb96bc3b5e2fff) Thanks @fubhy! - Prefer explicit OTLP resource configuration over environment configuration. + +- [#6677](https://github.com/Effect-TS/effect/pull/6677) [`1747440`](https://github.com/Effect-TS/effect/commit/1747440de9a51a56ed3660da748cc01b256adce7) Thanks @tim-smart! - Expose runtime schemas for AI prompt parts and message-specific part unions. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP servers now advertise logging and honor each client's selected log level when sending log notifications. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - Preserve MCP sampling request preferences and response content. + +- [#6878](https://github.com/Effect-TS/effect/pull/6878) [`b4f1ee2`](https://github.com/Effect-TS/effect/commit/b4f1ee238d96aa78c5f040158cb78671d75b381e) Thanks @fubhy! - Fix Array index operations handling `NaN` and fractional indexes. + +- [#6751](https://github.com/Effect-TS/effect/pull/6751) [`a4757f1`](https://github.com/Effect-TS/effect/commit/a4757f1c47067d8d016a6c4a2c541bb8ae520f9b) Thanks @tim-smart! - Fix Atom dependency tracking and re-entrant invalidation during batch rebuilds. + +- [#6870](https://github.com/Effect-TS/effect/pull/6870) [`cd122b9`](https://github.com/Effect-TS/effect/commit/cd122b90300d995a237993a2edb7a049785ab6a4) Thanks @fubhy! - Ensure `BigInt.gcd` and `BigInt.lcm` return non-negative values and handle zero operands in `BigInt.lcm`. + +- [#6844](https://github.com/Effect-TS/effect/pull/6844) [`5de588b`](https://github.com/Effect-TS/effect/commit/5de588b2472fb0f4eb919766eb8472583a044772) Thanks @fubhy! - Prevent an interrupted cache lookup from removing a newer value written with `Cache.set`. + +- [#6879](https://github.com/Effect-TS/effect/pull/6879) [`3895b9c`](https://github.com/Effect-TS/effect/commit/3895b9cf179262cd277a9c6daafe9050dcf8265e) Thanks @fubhy! - Preserve failure annotations when mapping errors with `Cause.map`. + +- [#6820](https://github.com/Effect-TS/effect/pull/6820) [`89ce5f3`](https://github.com/Effect-TS/effect/commit/89ce5f3e16e23a193daa475dc72ea8133ae1dacd) Thanks @fubhy! - Fix `ChannelSchema.decodeUnknown` to accept unknown input chunks while keeping `ChannelSchema.decode` typed to the schema's encoded input. + +- [#6899](https://github.com/Effect-TS/effect/pull/6899) [`985de09`](https://github.com/Effect-TS/effect/commit/985de097d75906db2aed784841f81e23cc978b43) Thanks @fubhy! - Ensure `Chunk.take` and `Chunk.drop` produce valid chunks for fractional counts. + +- [#6579](https://github.com/Effect-TS/effect/pull/6579) [`9800e3a`](https://github.com/Effect-TS/effect/commit/9800e3acc8f36530f671bc8b91558cb112f449a7) Thanks @marbemac! - Scope cluster reply serialization failures and peer-delivered defects to their own request instead of the whole runner connection + +- [#6800](https://github.com/Effect-TS/effect/pull/6800) [`4dc35f6`](https://github.com/Effect-TS/effect/commit/4dc35f64641746366f867ea3dbfedb9cd4685ada) Thanks @tim-smart! - Fix persisted cluster stream recovery when SQL drivers return a null reply kind. + +- [#6814](https://github.com/Effect-TS/effect/pull/6814) [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204) Thanks @gcanti! - Preserve provider input evidence when `Config.orElse` recovers a configuration failure. + +- [#6873](https://github.com/Effect-TS/effect/pull/6873) [`ecd9993`](https://github.com/Effect-TS/effect/commit/ecd99936112cb69efdb02de3a2fd57f47baefdf3) Thanks @fubhy! - Propagate the `FiberSet.runtime` interruption option when registering managed fibers. + +- [#6872](https://github.com/Effect-TS/effect/pull/6872) [`5ab9c08`](https://github.com/Effect-TS/effect/commit/5ab9c08463ce049c45f3502676954a7b72c6b024) Thanks @fubhy! - Fix `Formatter.format` handling of shared references and ensure `Formatter.formatJson` always returns valid JSON. + +- [#6867](https://github.com/Effect-TS/effect/pull/6867) [`f5cf965`](https://github.com/Effect-TS/effect/commit/f5cf96548afd51f4b3cf1aea11b04d7f8549ce90) Thanks @fubhy! - Remove stale `content-length` headers when replacing an HTTP client request body with one of unknown length. + +- [#6924](https://github.com/Effect-TS/effect/pull/6924) [`a94cbed`](https://github.com/Effect-TS/effect/commit/a94cbed84e9e49bea4bff925599c0f19c4e3deab) Thanks @fubhy! - Ignore `uniqueItems` when set to `false` while importing JSON Schema documents. + +- [#6871](https://github.com/Effect-TS/effect/pull/6871) [`9160ad7`](https://github.com/Effect-TS/effect/commit/9160ad7d146d4376dd12f7510c025e5b2f638a70) Thanks @fubhy! - Fix `LayerMap` preload options so configured entries are acquired during construction. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP completion handlers now receive resolved argument context, and completion responses are limited to one hundred values. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP servers now return protocol errors for invalid tool, prompt, completion, resource, and logging requests. + +- [#6901](https://github.com/Effect-TS/effect/pull/6901) [`52494be`](https://github.com/Effect-TS/effect/commit/52494be9e8eb3bb542d06a3dfefc6bca4e168984) Thanks @fubhy! - Prevent distinct metric attribute sets from sharing registry state. + +- [#6822](https://github.com/Effect-TS/effect/pull/6822) [`5441c8e`](https://github.com/Effect-TS/effect/commit/5441c8e656a6418c0d27feb2df67565a3e1155f4) Thanks @fubhy! - Fix `Metric.isMetric` to recognize metrics using their current runtime brand. + +- [#6821](https://github.com/Effect-TS/effect/pull/6821) [`c9b56ab`](https://github.com/Effect-TS/effect/commit/c9b56ab507f224426ee8388dc450da447ec4715f) Thanks @fubhy! - Fix `Metric.linearBoundaries` to space boundaries by the configured width. + +- [#6847](https://github.com/Effect-TS/effect/pull/6847) [`8ef7257`](https://github.com/Effect-TS/effect/commit/8ef72577d1f43212cab87951d659e54e3c8d7d91) Thanks @fubhy! - Fix `MutableList.prepend` on empty lists and handle non-positive `toArrayN` bounds. + +- [#6865](https://github.com/Effect-TS/effect/pull/6865) [`1519406`](https://github.com/Effect-TS/effect/commit/1519406fed6e8b017ae178dc20bcaa2cf318b570) Thanks @fubhy! - Fix `OtlpResource` to decode percent-encoded environment attributes and preserve bigint precision. + +- [#6805](https://github.com/Effect-TS/effect/pull/6805) [`9716990`](https://github.com/Effect-TS/effect/commit/97169902eec3c99baa7f0b2c7b45a0a5eae75819) Thanks @tim-smart! - Prevent replay-enabled PubSubs from retaining values beyond each subscription's replay window. + +- [#6711](https://github.com/Effect-TS/effect/pull/6711) [`733f75b`](https://github.com/Effect-TS/effect/commit/733f75b7125e3016a975fdd251c0179ae5393786) Thanks @andrskr! - Preserve serialization and retention metadata on reactive `AtomRpc` and `AtomHttpApi` queries. + +- [#6855](https://github.com/Effect-TS/effect/pull/6855) [`48155c8`](https://github.com/Effect-TS/effect/commit/48155c8ccfc12dcca8a00fa358d50b20c30874e4) Thanks @fubhy! - Fix `Schedule.during` to recur until the configured duration has elapsed. + +- [#6712](https://github.com/Effect-TS/effect/pull/6712) [`951d06b`](https://github.com/Effect-TS/effect/commit/951d06b83d459d3e8fa9024e727a5db1662d3322) Thanks @gcanti! - Make `Schema.isPattern` deterministic for regular expressions with global or sticky flags. + +- [#6782](https://github.com/Effect-TS/effect/pull/6782) [`d767b65`](https://github.com/Effect-TS/effect/commit/d767b65a7687e38be23f0b0ee3d52ab5f2360cbe) Thanks @gcanti! - SchemaRepresentation: generate references from encoded AST identity, suffix colliding identifiers instead of throwing, and preserve sharing across property-key context. This avoids false-positive duplicate identifier errors while keeping referentially distinct schemas addressable; generated fallback definitions now use the clearer `Encoded` suffix. + +- [#6704](https://github.com/Effect-TS/effect/pull/6704) [`5d52d9d`](https://github.com/Effect-TS/effect/commit/5d52d9d148aaa7f736ed8c310fc8bfa9dc81badf) Thanks @gcanti! - Fix Union candidate selection for recovering middleware and suspended members. + +- [#6848](https://github.com/Effect-TS/effect/pull/6848) [`f4151e1`](https://github.com/Effect-TS/effect/commit/f4151e1937c26de14f1d64566f8126173f1b5014) Thanks @fubhy! - Keep the current `ScopedRef` resource alive when acquiring its replacement fails. + +- [#6910](https://github.com/Effect-TS/effect/pull/6910) [`e02fbb6`](https://github.com/Effect-TS/effect/commit/e02fbb66f5a0f13dba6c33ef63528a37a17a0676) Thanks @z4p5a9! - Fix `Semaphore.withPermits` leaking permits when interrupted between acquiring them and installing their release. + +- [#6877](https://github.com/Effect-TS/effect/pull/6877) [`724ce09`](https://github.com/Effect-TS/effect/commit/724ce09650a458d4565e5c7331ea92ca04f08e68) Thanks @tim-smart! - Fix `Stream.aggregateWithin` and `Stream.groupedWithin` retaining fiber continuations on every schedule tick while upstream is idle. + +- [#6889](https://github.com/Effect-TS/effect/pull/6889) [`dbe91f6`](https://github.com/Effect-TS/effect/commit/dbe91f6961ef9f7e8da910ee5758d9c0d385fca8) Thanks @tim-smart! - Fix `Stream.withExecutionPlan` retry limits resetting after partial stream emissions. + +- [#6823](https://github.com/Effect-TS/effect/pull/6823) [`4c008d2`](https://github.com/Effect-TS/effect/commit/4c008d28b370d817f7ae4579db09836fe084c8d2) Thanks @fubhy! - Fix data-first dispatch for `Stream.mapAccumArrayEffect`. + +- [#6900](https://github.com/Effect-TS/effect/pull/6900) [`b650832`](https://github.com/Effect-TS/effect/commit/b6508328708a842f3163467b72486bd228f1a289) Thanks @fubhy! - Ensure `Stream.range` emits the full range when the chunk size is zero. + +- [#6849](https://github.com/Effect-TS/effect/pull/6849) [`b46c92f`](https://github.com/Effect-TS/effect/commit/b46c92f3b314f4ffd612b831efa55dd856c587a3) Thanks @fubhy! - Fix `SubscriptionRef.getAndUpdateSome` to return the current value when no update is selected. + +- [#6808](https://github.com/Effect-TS/effect/pull/6808) [`5335797`](https://github.com/Effect-TS/effect/commit/5335797003076d9c6fd170da98d779696d555596) Thanks @fubhy! - Fix `SubscriptionRef.getAndUpdateEffect` to execute the effectful update. + +- [#6862](https://github.com/Effect-TS/effect/pull/6862) [`4b3460d`](https://github.com/Effect-TS/effect/commit/4b3460daa434ec465a95a50704fe1103a9275999) Thanks @fubhy! - Fix `Trie.longestPrefixOf` returning a valued sibling that does not match the input key. + +- [#6856](https://github.com/Effect-TS/effect/pull/6856) [`6301fd7`](https://github.com/Effect-TS/effect/commit/6301fd710b4325718de2c42997dac28a9e9aa250) Thanks @fubhy! - Fix `Trie` to preserve entries whose value is `undefined`. + +- [#6850](https://github.com/Effect-TS/effect/pull/6850) [`aebc5c6`](https://github.com/Effect-TS/effect/commit/aebc5c61664b89a840465ec65b79ce635a5ceee8) Thanks @fubhy! - Fix `TxPubSub.publishAll` dropping values from one-shot iterables when a transaction retries. + +- [#6851](https://github.com/Effect-TS/effect/pull/6851) [`52b2d7b`](https://github.com/Effect-TS/effect/commit/52b2d7b5bd3c7cce3bd5b69c6ab3941004da70f3) Thanks @fubhy! - Ensure `TxQueue.poll` and `TxQueue.clear` complete a closing queue after draining its buffered items. + +- [#6853](https://github.com/Effect-TS/effect/pull/6853) [`eec5744`](https://github.com/Effect-TS/effect/commit/eec57445dfa0ef3c5977195ad69415b7e7d42bb6) Thanks @fubhy! - Fix `TxQueue.offerAll` to preserve one-shot iterables across transaction retries and repeated runs. + +- [#6783](https://github.com/Effect-TS/effect/pull/6783) [`24e0e93`](https://github.com/Effect-TS/effect/commit/24e0e93dc307dc2c2ae86caacb7289e1dab3c103) Thanks @tim-smart! - Propagate trace context through persisted cluster workflow requests. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP servers now return standard JSON-RPC errors for malformed requests, unknown methods, and invalid parameters. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP servers now enforce revision-specific JSON-RPC batch and protocol-version header requirements. + +- [#6707](https://github.com/Effect-TS/effect/pull/6707) [`1a7ce81`](https://github.com/Effect-TS/effect/commit/1a7ce8150e3977586c44d8ccb9a8384389bb4d49) Thanks @gcanti! - Mark `Schema.UnknownFromJsonString` as internal and remove its type-level interface. Use `Schema.fromJsonString(Schema.Unknown)` instead. Add `reviver`, callback or array `replacer`, and `space` options to `Schema.fromJsonString`, and make `SchemaTransformation.fromJsonString` a configurable factory. + +- [#6828](https://github.com/Effect-TS/effect/pull/6828) [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a) Thanks @tim-smart! - Use layered storage for Context, making `Context.add` O(1) and eliminating per-request service map clones in the HTTP servers. Docgen now omits `@internal` option properties from generated signatures. + +- [#6780](https://github.com/Effect-TS/effect/pull/6780) [`c96b7f6`](https://github.com/Effect-TS/effect/commit/c96b7f6359662053c3e09344f61dddc7a6caf4ac) Thanks @tim-smart! - Include typed tool output schemas in MCP `tools/list` responses. + +- [#6733](https://github.com/Effect-TS/effect/pull/6733) [`6d2a942`](https://github.com/Effect-TS/effect/commit/6d2a942ed7cd33b8fd79d549edba33bc9e2a7e3e) Thanks @gcanti! - Avoid validating `Schema.Class` fields twice when decoding. + +- [#6659](https://github.com/Effect-TS/effect/pull/6659) [`cc27b19`](https://github.com/Effect-TS/effect/commit/cc27b194b9d13fa3a66ab037e853fca9d41700ff) Thanks @tim-smart! - Preserve prototype accessors when code is compiled with loose object spread transforms. + +- [#6912](https://github.com/Effect-TS/effect/pull/6912) [`8f9499f`](https://github.com/Effect-TS/effect/commit/8f9499f562729f5f7b08d8bcc4db86b4aeff8a21) Thanks @gcanti! - Remove `actual` fields from every `SchemaIssue` variant, together with + `SchemaIssue.getActual`, `SchemaIssue.redact`, and `Schema.redact`. Built-in + formatters now use static messages that do not interpolate rejected input, + while paths, AST metadata, union successes, and user-provided messages and + annotations are preserved unchanged. + + Runtime performance was measured across the 16 Effect fixtures in the + `schema-benchmarks` suite. These are the scenarios used for the cross-library + comparison with Valibot and Zod. The paired HEAD-versus-`main` run classified 3 + fixtures as improvements, 0 as regressions, and 13 as inconclusive. Negative + changes are faster. Absolute library values are medians from the same + cross-library run; `—` means that the corresponding adapter does not expose + that scenario. + + | Scenario | Effect (ns/op) | Valibot (ns/op) | Zod (ns/op) | HEAD vs main | Classification | + | ------------------------ | -------------: | --------------: | ----------: | -----------: | -------------- | + | `initialization-schema` | 108191.30 | **30549.81** | 212715.66 | -0.92% | inconclusive | + | `initialization-decoder` | **109796.34** | — | — | +1.98% | inconclusive | + | `validation-valid` | 5221.80 | **5070.81** | — | +2.06% | inconclusive | + | `validation-invalid` | 1279.77 | **234.92** | — | +0.59% | inconclusive | + | `parsing-all-valid` | **5144.58** | 5192.19 | 7176.19 | -3.79% | inconclusive | + | `parsing-all-invalid` | **7594.49** | 15236.82 | 37780.35 | -5.94% | improvement | + | `parsing-first-valid` | 5188.33 | **5135.75** | — | -1.49% | inconclusive | + | `parsing-first-invalid` | 1330.82 | **243.64** | — | +1.01% | inconclusive | + | `standard-all-valid` | 5722.01 | 5200.05 | **3801.26** | -1.78% | inconclusive | + | `standard-all-invalid` | **12024.65** | 15528.50 | 30982.17 | -7.78% | improvement | + | `standard-first-valid` | **5655.33** | — | — | +3.84% | inconclusive | + | `standard-first-invalid` | **2001.69** | — | — | -4.56% | inconclusive | + | `codec-typed-encode` | 342.59 | — | **39.29** | -7.62% | inconclusive | + | `codec-typed-decode` | 418.78 | — | **50.14** | -10.89% | improvement | + | `codec-unknown-encode` | **328.38** | — | — | -5.55% | inconclusive | + | `codec-unknown-decode` | **347.35** | — | — | -5.25% | inconclusive | + +- [#6692](https://github.com/Effect-TS/effect/pull/6692) [`3eeea73`](https://github.com/Effect-TS/effect/commit/3eeea73cfc3e9b126975c2ddbdb7f7c8c92026e2) Thanks @schickling-assistant! - Fix unstable CLI subcommands dropping operands after the `--` end-of-options terminator. + +- [#6625](https://github.com/Effect-TS/effect/pull/6625) [`0a532e5`](https://github.com/Effect-TS/effect/commit/0a532e503f165fdea485a5343fc2f420917e8376) Thanks @lloydrichards! - Add adapter-valued MCP server protocol declarations, route requests through the selected protocol before schema decoding, and add built-in support for MCP `2025-06-18`. + +- [#6864](https://github.com/Effect-TS/effect/pull/6864) [`f398149`](https://github.com/Effect-TS/effect/commit/f398149c134fd9b67b6cdc52eae3f3248d5c7bbe) Thanks @fubhy! - Honor HTTP-date `Retry-After` values when retrying OTLP exports. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP Streamable HTTP servers now validate content negotiation, session lifecycle, negotiated protocol versions, and browser Origins before dispatching requests. + +- [#6824](https://github.com/Effect-TS/effect/pull/6824) [`ace903e`](https://github.com/Effect-TS/effect/commit/ace903e09c2549ceebdec380797beb027cd29f3d) Thanks @tim-smart! - Skip HTTP server span attribute collection when the span is not sampled. + +- [#6814](https://github.com/Effect-TS/effect/pull/6814) [`e8eb62b`](https://github.com/Effect-TS/effect/commit/e8eb62b3d0ef27e9761cdc2eb93bdec52d6ee204) Thanks @gcanti! - Refine `Config` loading and absence semantics. `Config.schema` now derives a provider loading policy from the encoded `StringTree` schema, materializes mixed-shape union members independently, and leaves separated scalar parsing to `Config.Array` and `Config.Record`. Schemas whose canonical `StringTree` encoding remains opaque, such as `Schema.Any`, `Schema.Unknown`, or `Schema.Json`, are rejected when the config is constructed; use a concrete shape or `Schema.fromJsonString(Schema.Json)` for scalar JSON. Missing or unavailable representations are decoded as `undefined` before `Config.withDefault` and `Config.option` decide absence. Partially supplied `Config.all` groups are rejected, successful values such as `undefined` and explicitly present empty structures are preserved, and the internal path prefix is removed from the public `Config.parse` signature. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP servers now refresh roots after capable clients report that their root list changed. + +- [#6828](https://github.com/Effect-TS/effect/pull/6828) [`48f22a7`](https://github.com/Effect-TS/effect/commit/48f22a7d16ae57ee2175d450dafbdeb69e187d2a) Thanks @tim-smart! - Remove `Context.mutate` and `Context.getReferenceUnsafe`. Context updates now use overlays, and `Context.get` resolves reference defaults. + +- [#6649](https://github.com/Effect-TS/effect/pull/6649) [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4) Thanks @gcanti! - Remove the `keyValueCombiner` option from `Schema.Record` and the corresponding + `SchemaAST.KeyValueCombiner` and `SchemaAST.IndexSignature.merge` APIs. + For transformed key collisions, sequential parsing keeps the later selected + value, while concurrent parsing keeps the value applied last in completion + order. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP servers now support session-scoped resource subscriptions on transports that can deliver server notifications and filter resource updates by each client's subscribed URIs. + +- [#6649](https://github.com/Effect-TS/effect/pull/6649) [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4) Thanks @gcanti! - Preserve untouched `Result` branches by identity in `Result.map` and + `Result.mapError`. + +- [#6649](https://github.com/Effect-TS/effect/pull/6649) [`d48506d`](https://github.com/Effect-TS/effect/commit/d48506d97525040aa714305e928126df799795b4) Thanks @gcanti! - Improve Schema parsing, schema construction and adapter runtime performance + while preserving current parsing behavior. + + ## Runtime performance + + The `effect@beta`, Valibot and Zod timing cases from + [`open-circle/schema-benchmarks`](https://github.com/open-circle/schema-benchmarks) + were reproduced as a dedicated `runtimeperf` suite. The table includes every + case exposed by each upstream adapter; `—` means that the adapter does not + provide that benchmark. + + Effect `main` (`45e781088`) and the branch based on `d775bf4b2` were compared + with five paired processes per case, 150 ms measurement time and 50 ms warmup. + The two initially inconclusive Effect cases were repeated with 15 paired + processes, 500 ms measurement time and 150 ms warmup. Valibot and Zod values + use five processes, 300 ms measurement time and 100 ms warmup. Environment: + Node `v24.12.0`, macOS arm64, Apple M3. + + Zod parsing uses `safeParse` with `{ jitless: true }`; its Standard Schema and + codec cases use the corresponding native adapter APIs. All values are median + microseconds per operation (`µs/op`), lower is better. Cross-library values are + diagnostic because they are independent rather than paired measurements. + + | Scenario | Effect `main` | Effect branch | Valibot | Zod 4 | Delta | 95% CI | Classification | + | ------------------------------------ | ------------: | ------------: | ---------: | ---------: | ------: | ------------------ | -------------- | + | Initialize schema | 137.28 | 118.23 | **40.24** | 318.56 | -12.69% | -21.02% to -5.35% | improvement | + | Initialize schema and decoder | 144.81 | **130.50** | — | — | -10.88% | -14.22% to -3.29% | improvement | + | Validate valid product | 8.478 | **5.415** | 5.63 | — | -35.18% | -41.65% to -32.83% | improvement | + | Validate invalid product | 1.516 | 1.348 | **0.2431** | — | -11.59% | -13.81% to -6.31% | improvement | + | Parse valid product, all errors | 8.360 | 5.366 | **5.22** | 7.16 | -36.28% | -54.41% to -31.67% | improvement | + | Parse invalid product, all errors | 11.302 | **9.100** | 15.70 | 41.58 | -19.42% | -21.32% to -13.12% | improvement | + | Parse valid product, first error | 8.201 | **5.294** | 5.37 | — | -35.44% | -37.75% to -34.59% | improvement | + | Parse invalid product, first error | 1.510 | 1.352 | **0.2572** | — | -10.51% | -12.52% to -9.53% | improvement | + | Standard Schema valid, all errors | 9.284 | 5.935 | 5.35 | **3.83** | -35.96% | -53.29% to -33.49% | improvement | + | Standard Schema invalid, all errors | 16.718 | **15.203** | 16.51 | 32.85 | -11.31% | -13.97% to -7.65% | improvement | + | Standard Schema valid, first error | 8.889 | **5.843** | — | — | -34.17% | -35.13% to -33.94% | improvement | + | Standard Schema invalid, first error | 2.435 | **2.244** | — | — | -8.44% | -12.76% to -4.82% | improvement | + | Typed codec encode | 0.4692 | 0.3420 | — | **0.0405** | -27.60% | -32.35% to -22.50% | improvement | + | Typed codec decode | 0.5191 | 0.3762 | — | **0.0463** | -27.19% | -34.75% to -22.71% | improvement | + | Unknown codec encode | 0.4910 | **0.3472** | — | — | -28.58% | -30.42% to -27.59% | improvement | + | Unknown codec decode | 0.5061 | **0.3637** | — | — | -29.26% | -29.82% to -21.70% | improvement | + + Overall Effect classification: 16 improvements and no regressions. + +- [#6896](https://github.com/Effect-TS/effect/pull/6896) [`52262be`](https://github.com/Effect-TS/effect/commit/52262be2edce0e350c6ac10f8f725678606399c5) Thanks @tim-smart! - Bind event-log read and write requests to the identities authenticated on their RPC connection. + +- [#6735](https://github.com/Effect-TS/effect/pull/6735) [`1284aa1`](https://github.com/Effect-TS/effect/commit/1284aa183451955ad7921bbe01fd0e095695d444) Thanks @gcanti! - Fix three issues in the public `Optic` API: + + - Composed `Iso` and `Prism` setters no longer try to read a source value before writing. + - Calling `notUndefined` on an `Optional` now returns an `Optional`, because writing can still fail. + - The internal `node` property is no longer exposed by public optic types. + +- [#6701](https://github.com/Effect-TS/effect/pull/6701) [`9867b9f`](https://github.com/Effect-TS/effect/commit/9867b9fc69f9cc6c443594fc7eccc7be0c674d9c) Thanks @fubhy! - Removed explicit ./index entrypoints + +- [#6875](https://github.com/Effect-TS/effect/pull/6875) [`979ce39`](https://github.com/Effect-TS/effect/commit/979ce3985d7d62ce2bf240681ca19feda3027452) Thanks @fubhy! - Fix protobuf serialization of negative signed integers to use ten-byte two's-complement varints. + +- [#6696](https://github.com/Effect-TS/effect/pull/6696) [`b6d3e67`](https://github.com/Effect-TS/effect/commit/b6d3e67c7cc143cd8470cdf704324e79d23954a9) Thanks @tim-smart! - remove file descriptor type + +- [#6860](https://github.com/Effect-TS/effect/pull/6860) [`adf6c6c`](https://github.com/Effect-TS/effect/commit/adf6c6cd388af8a3c0c546492e71555368556f6a) Thanks @fubhy! - Honor custom split and strip regular expressions passed to `String.noCase`. + +- [#6866](https://github.com/Effect-TS/effect/pull/6866) [`7314d60`](https://github.com/Effect-TS/effect/commit/7314d605284717aaafe7fc34b88c3c93397e865c) Thanks @fubhy! - Fix partial file-backed HTTP bodies to report the selected byte range as their content length. + +- [#6693](https://github.com/Effect-TS/effect/pull/6693) [`aeba0c8`](https://github.com/Effect-TS/effect/commit/aeba0c8c9ffc5f125d961ae21e4ac15491e51046) Thanks @lloydrichards! - MCP HTTP servers now reject requests sent before initialization with the required lifecycle response. + +- [#6759](https://github.com/Effect-TS/effect/pull/6759) [`1acbd8b`](https://github.com/Effect-TS/effect/commit/1acbd8b44c68ebb23735e9810476b870dbe58aea) Thanks @tim-smart! - Harden JSON-RPC wire message classification against inherited properties. + +- [#6705](https://github.com/Effect-TS/effect/pull/6705) [`7bde6cc`](https://github.com/Effect-TS/effect/commit/7bde6ccb2b144fe953ff30a7ef5e1ecc97697146) Thanks @tylergibbs1! - Restore the `recursive` option for `FileSystem.watch`, with non-recursive watching as the default. + +- [#6798](https://github.com/Effect-TS/effect/pull/6798) [`a959a8b`](https://github.com/Effect-TS/effect/commit/a959a8bf21cdb976369f494dc949fa00a050d3e0) Thanks @tim-smart! - Namespace PostgreSQL advisory shard locks by the `SqlRunnerStorage` table prefix. + + This changes the advisory-lock protocol. PostgreSQL clusters using advisory locks require a full cluster stop before upgrading; a rolling deploy is unsafe because old and new runners use different lock keys and can both acquire the same shard. + +## 4.0.0-beta.102 + +### Patch Changes + +- [#6563](https://github.com/Effect-TS/effect/pull/6563) [`b6392e1`](https://github.com/Effect-TS/effect/commit/b6392e119704553edec1b4fd2869ac0dbec621ef) Thanks @tim-smart! - unstable/reactivity Atom: add `withEquality` combinator for customizing how the registry detects value changes + +- [#6574](https://github.com/Effect-TS/effect/pull/6574) [`7ed9450`](https://github.com/Effect-TS/effect/commit/7ed945044eb56aa9aeaf62d4746a011c96c58628) Thanks @tim-smart! - unstable/http HttpClientRequest: add `updateHeaders` and `removeHeader` combinators for transforming or removing request headers, closes [#6271](https://github.com/Effect-TS/effect/issues/6271) + +- [#6641](https://github.com/Effect-TS/effect/pull/6641) [`45762bd`](https://github.com/Effect-TS/effect/commit/45762bd78df9ecd87c98b8d3738cdeeac7d81128) Thanks @tim-smart! - Add manual flushing to the OTLP exporters through a shared `Flusher` service exposed by each signal layer. The signal layer output types now include `Flusher`, and `OtlpExporter.make` requires it so custom exporters register unconditionally. + +- [#6616](https://github.com/Effect-TS/effect/pull/6616) [`a6e8391`](https://github.com/Effect-TS/effect/commit/a6e8391cd31acd898fae18b3f8e7ca4c6f14f065) Thanks @tim-smart! - Add `Tool.setNeedsApproval` for replacing the approval policy of an existing tool. + +- [`4ac7e8b`](https://github.com/Effect-TS/effect/commit/4ac7e8b136c61a26c3e438c013dfd7349b38e999) Thanks @IMax153! - Add `Effect.updateServiceScoped` for updating a context service until the current scope closes, with customizable reset behavior. + +- [#6593](https://github.com/Effect-TS/effect/pull/6593) [`4cd40f5`](https://github.com/Effect-TS/effect/commit/4cd40f5692477783bef84fed3c5ef1c0cf5602e6) Thanks @tim-smart! - Fix `Channel.mergeAll` to propagate outer failures promptly and interrupt active inner channels. + +- [#6610](https://github.com/Effect-TS/effect/pull/6610) [`6956bc0`](https://github.com/Effect-TS/effect/commit/6956bc0e6cb27f53fbec39d9b18545940f9f598f) Thanks @ebramanti! - Update `McpServer.layerHttp` to return `405` for unsupported HTTP methods, reject unsupported `MCP-Protocol-Version` headers with `400`, and return an empty `202` for accepted notifications and responses. + +- [#6608](https://github.com/Effect-TS/effect/pull/6608) [`0e50ec7`](https://github.com/Effect-TS/effect/commit/0e50ec7dbb94390666f292cf9120719bf30a7246) Thanks @gcanti! - Add `Schema.Natural` for non-negative safe integers and use canonical `Schema.Int`, `Schema.Finite`, and `Schema.Natural` schemas for numeric domain values across Effect, AI protocols, and OpenAPI patches. + + Update the date, date-time, file, time-zone, cluster, event-log, persistence, socket, SQL, and DevTools schemas to reject invalid non-finite or non-integer values where appropriate. Correct the decoded schema of `Schema.NumberFromString`, and allow `Schema.DurationFromMillis` and `Schema.DurationFromNanos` to represent negative durations. + +- [#6599](https://github.com/Effect-TS/effect/pull/6599) [`9fcdade`](https://github.com/Effect-TS/effect/commit/9fcdade4a8af772b9ccd8b8a24fe8cee0e5d8470) Thanks @tim-smart! - Interrupt in-flight stream pulls when closing an async iterator. + +- [#6638](https://github.com/Effect-TS/effect/pull/6638) [`57367d5`](https://github.com/Effect-TS/effect/commit/57367d54de55047ff0c5fce9685475e236bf354c) Thanks @tim-smart! - Fix `PartitionedSemaphore.take` leaking partially acquired permits when interrupted. + +- [#6615](https://github.com/Effect-TS/effect/pull/6615) [`35c445f`](https://github.com/Effect-TS/effect/commit/35c445ff18029d192900ea0914c993f58d5cf1a5) Thanks @tim-smart! - Expose the tool call ID to AI tool handlers and `Toolkit.WithHandler.handle` wrappers. + +- [#6561](https://github.com/Effect-TS/effect/pull/6561) [`c917bb9`](https://github.com/Effect-TS/effect/commit/c917bb94a4c1c4e0a24372a8ebb8a5ca232e36b5) Thanks @hsubra89! - Reject unexpected positional arguments left after command parsing, including values exceeding `Argument.variadic` maximum bounds. + +- [#6613](https://github.com/Effect-TS/effect/pull/6613) [`bc1f358`](https://github.com/Effect-TS/effect/commit/bc1f3583e63344cb2c398d9040d9c975488ed123) Thanks @tim-smart! - Ignore duplicate chunk indexes when joining event log messages. + +- [#6552](https://github.com/Effect-TS/effect/pull/6552) [`0e0c9d7`](https://github.com/Effect-TS/effect/commit/0e0c9d7922ff463c1093d9e0576fae12cb0698d5) Thanks @xianjianlf2! - Fix a race where FiberHandle.clear could remove a newer fiber installed while the previous fiber was still interrupting. + +- [#6598](https://github.com/Effect-TS/effect/pull/6598) [`73d40aa`](https://github.com/Effect-TS/effect/commit/73d40aacd8fcae1b48c23f5b0a5c542127401d1d) Thanks @tim-smart! - Fix `LanguageModel.streamText` to apply the configured concurrency limit to tool call resolution, including approval checks. + +- [#6637](https://github.com/Effect-TS/effect/pull/6637) [`4f1e318`](https://github.com/Effect-TS/effect/commit/4f1e3183f7123591c46224e9c587df7594562a5f) Thanks @tim-smart! - Fix Latch open/release resuming waiters that registered after a subsequent close. + + `Latch.open` and `Latch.release` schedule the waiter flush on the fiber's + dispatcher. Previously the flush drained whatever waiters existed at flush + time, so a waiter that registered after the latch was closed again could be + resumed by the stale flush. The waiters are now snapshotted at schedule time, + so only waiters covered by an `open`/`release` call are resumed. + +- [#6614](https://github.com/Effect-TS/effect/pull/6614) [`9d8d85c`](https://github.com/Effect-TS/effect/commit/9d8d85c1bb7da51970845b8ea830e386e777514a) Thanks @tim-smart! - Fix histogram and summary maximum values for negative-only observations. + +- [#6634](https://github.com/Effect-TS/effect/pull/6634) [`6079fda`](https://github.com/Effect-TS/effect/commit/6079fda7b02f2f01ad91c15ab8c307336f3ba252) Thanks @fubhy! - Fix OTLP exporter shutdown to await in-flight and final buffered exports up to the configured shutdown timeout. + +- [#6567](https://github.com/Effect-TS/effect/pull/6567) [`5101e92`](https://github.com/Effect-TS/effect/commit/5101e92c9c149c153423f43dd7a94f6194653c06) Thanks @gcanti! - Add `Record.assignProperty` and safely handle dynamic record keys such as `__proto__` and inherited property names. + +- [#6592](https://github.com/Effect-TS/effect/pull/6592) [`d0b3265`](https://github.com/Effect-TS/effect/commit/d0b3265c3262670761471ab3518cf933b1b3b20a) Thanks @tim-smart! - Fix `Stream.haltWhen` to observe halt effects at pull boundaries for synchronous streams. + +- [#6618](https://github.com/Effect-TS/effect/pull/6618) [`7a03c89`](https://github.com/Effect-TS/effect/commit/7a03c893ce6492bf94c0ebfb00b63bf25dcbf83e) Thanks @tim-smart! - unstable/cluster: hash over-length SQL message deduplication keys to prevent `message_id` overflow, closes [#6317](https://github.com/Effect-TS/effect/issues/6317). + + The composed request deduplication key (`entityType/entityId/tag/primaryKey`) can legally exceed the 255-character `message_id` column — the address columns alone allow 458 characters before the RPC primary key is appended. `SqlMessageStorage` now stores a SHA-256 digest (64 hex characters) of the composed key in the unique `message_id` column when the key exceeds 255 characters, so keys of any length work on PostgreSQL, MySQL, MSSQL, and SQLite. Keys that fit are stored as plaintext, byte-compatible with rows written by previous versions, so existing deployments keep deduplicating with no migration or schema change. + + `SqlMessageStorage.layer`/`layerWith` (and consequently `SingleRunner.layer`) now require `Crypto.Crypto`. The Node and Bun cluster convenience layers provide the platform Crypto implementation internally, so their requirements are unchanged. + +- [#6577](https://github.com/Effect-TS/effect/pull/6577) [`cea1d9c`](https://github.com/Effect-TS/effect/commit/cea1d9c92601e69ebda040af8a1d860d604d885c) Thanks @tim-smart! - ManagedRuntime: add `Symbol.asyncDispose`, enabling `await using` syntax + + ```ts + import { Effect, Layer, ManagedRuntime } from "effect"; + + await using runtime = ManagedRuntime.make(Layer.empty); + + await runtime.runPromise(Effect.log("Hello, world!")); + // runtime is disposed automatically at the end of the scope + ``` + +- [#6644](https://github.com/Effect-TS/effect/pull/6644) [`078e1f5`](https://github.com/Effect-TS/effect/commit/078e1f5636e31b76a86722a636afc37a8cc25580) Thanks @gcanti! - Improve the performance of `Array.dedupe`, `Array.union`, `Array.intersection`, `Array.difference`, and Schema unique item validation by using hash-based equality lookup. + +- [#6609](https://github.com/Effect-TS/effect/pull/6609) [`97bafea`](https://github.com/Effect-TS/effect/commit/97bafeab460833b9781527b437d1cb9cbee63260) Thanks @tim-smart! - Allow embedding usage input tokens to be omitted during decoding, including after JSON serialization. + +- [#6606](https://github.com/Effect-TS/effect/pull/6606) [`fab0ab8`](https://github.com/Effect-TS/effect/commit/fab0ab8f7ab15ae596faa4ccf75615a494d11b0b) Thanks @tim-smart! - Allow optional AI response fields to be omitted during decoding, including after JSON serialization. + +- [#6607](https://github.com/Effect-TS/effect/pull/6607) [`c323d8b`](https://github.com/Effect-TS/effect/commit/c323d8b30dbbe85f9df25b67288b93d5332de333) Thanks @ebramanti! - Prevent MCP tool failures from exposing Cause rendering, stack traces, and internal paths while preserving actionable validation messages. + +- [#6576](https://github.com/Effect-TS/effect/pull/6576) [`6966353`](https://github.com/Effect-TS/effect/commit/69663534d626003eb10a5e55ab1f13e0379fead1) Thanks @tim-smart! - Record: make `fromIterableBy` dual, allowing data-last usage in `pipe` + + ```ts + import { pipe, Record } from "effect"; + + const users = [ + { id: "2", name: "name2" }, + { id: "1", name: "name1" }, + ]; + + pipe( + users, + Record.fromIterableBy((user) => user.id), + ); + ``` + +- [#6622](https://github.com/Effect-TS/effect/pull/6622) [`0444004`](https://github.com/Effect-TS/effect/commit/04440041989c1785fe4db286379f2be2c15baa85) Thanks @gcanti! - Remove the experimental `SchemaUtils` module and its `getNativeClassSchema` helper. The helper duplicated a composition already available through the primary Schema APIs and did not justify a separate public module. + +- [#6653](https://github.com/Effect-TS/effect/pull/6653) [`028bbb3`](https://github.com/Effect-TS/effect/commit/028bbb391e161185da10d974ab33381f769940d7) Thanks @tim-smart! - Remove `Effect.withConcurrency`, the `References.CurrentConcurrency` reference backing it, and the `"inherit"` option from `Types.Concurrency`. Use an explicit `number` or `"unbounded"` concurrency value instead. + +- [#6620](https://github.com/Effect-TS/effect/pull/6620) [`ff5d6e2`](https://github.com/Effect-TS/effect/commit/ff5d6e278a1fdff714315dc1a17075012f05c1f0) Thanks @gcanti! - Make `Schema.Date` reject invalid dates and remove the redundant `Schema.DateValid`, `Schema.isDateValid`, and `Schema.isDateValidReviver` APIs. + + `Schema.DateFromString` and `Schema.DateFromMillis` now fail decoding when their input would produce an invalid date. + + Remove `Schema.Annotations.ToArbitrary.GenerationConstraint.valid`; `Schema.Date` arbitraries now generate only valid dates by default. + +- [#6575](https://github.com/Effect-TS/effect/pull/6575) [`1bfce93`](https://github.com/Effect-TS/effect/commit/1bfce93e6d2bf0794c11733daf51c2390e7de375) Thanks @gcanti! - Schema: make schemas directly extendable as classes with static method support + and remove `Schema.asClass`. + + `Bottom` and `BottomLazy` now include the class-compatible `new` signature, + while `BottomWithoutNew` and `BottomLazyWithoutNew` expose the schema protocol + without it for schema types that define a specialized construct signature. + + **Example** + + ```ts + import { Schema } from "effect"; + + class MyString extends Schema.String { + static readonly decodeUnknownSync = Schema.decodeUnknownSync(this); + } + + MyString.decodeUnknownSync("a"); // "a" + ``` + +- [#6424](https://github.com/Effect-TS/effect/pull/6424) [`7ce815c`](https://github.com/Effect-TS/effect/commit/7ce815cd5af6af991dfc13b890fd22345fc77c20) Thanks @gcanti! - Refactor the `SchemaRepresentation` module to improve clarity and maintainability. + + The representation pipeline is now open and compiler-extensible. The same encoded-side representation is used for JSON persistence, runtime reconstruction, JSON Schema Draft 2020-12 compilation, TypeScript code generation, AI structured output, and HTTP / OpenAPI schemas. + + ### New representation model + - Add `RepresentationAnnotation` and `CheckRepresentationAnnotation`, which identify declarations and checks with a stable `id`, JSON `payload`, and optional schema dependencies. + - Preserve checks on every non-reference representation node instead of storing constraints in the previous closed `meta` unions. + - Add compiler hooks for checks and declarations through `SchemaRepresentation.ToJsonSchema` and `SchemaRepresentation.Generation`. + - Add `SchemaMultiDocument`, `fromSchemaMultiDocument`, and `fromRepresentations` so several live schemas and named definitions can be converted and reconstructed together. Explicit definitions are preserved even when no root references them. + - Preserve shared structural nodes, annotated recursion, union member order, identifiers, reference siblings, and structural checks when projecting encoded schemas. + + ### Persistence and revivers + - Add `toJson`, `fromJson`, `toJsonMultiDocument`, and `fromJsonMultiDocument` as the persistence boundary for representation documents. + - Live representations store literal, enum, and property-name scalars as native values. JSON persistence encodes them as `{ type, value }` tagged unions so their runtime types remain distinct across persistence formats, canonically encodes structural bigint and global symbol values, keeps JSON-valued annotations, and removes runtime-only callbacks and other non-JSON annotation values. + - Replace the generic reviver callback with typed `DeclarationReviver`, `FilterReviver`, and `FilterGroupReviver` contracts. Add `makeDeclarationReviver`, `makeFilterReviver`, and `makeFilterGroupReviver`, which infer their payload type from `payloadSchema`. + - Resolve acyclic references to concrete runtime schemas and reserve `Schema.suspend` wrappers for recursive back-edges. Acyclic alias chains may be normalized while preserving the outer reference identifier. + - Export individual revivers for built-in declarations and checks from `Schema`. Consumers opt in to exactly the revivers accepted when reconstructing persisted documents: + - declaration revivers: `OptionReviver`, `ResultReviver`, `RedactedReviver`, `CauseReasonReviver`, `CauseReviver`, `ErrorReviver`, `ExitReviver`, `ReadonlyMapReviver`, `HashMapReviver`, `ReadonlySetReviver`, `HashSetReviver`, `ChunkReviver`, `RegExpReviver`, `URLReviver`, `DateReviver`, `DurationReviver`, `BigDecimalReviver`, `FileReviver`, `FormDataReviver`, `URLSearchParamsReviver`, `Uint8ArrayReviver`, `DateTimeUtcReviver`, `TimeZoneOffsetReviver`, `TimeZoneNamedReviver`, `TimeZoneReviver`, `DateTimeZonedReviver`, `JsonReviver`, and `MutableJsonReviver` + - check revivers: `isTrimmedReviver`, `isPatternReviver`, `isStringFiniteReviver`, `isStringBigIntReviver`, `isStringSymbolReviver`, `isUUIDReviver`, `isGUIDReviver`, `isULIDReviver`, `isBase64Reviver`, `isBase64UrlReviver`, `isStartsWithReviver`, `isEndsWithReviver`, `isIncludesReviver`, `isUppercasedReviver`, `isLowercasedReviver`, `isCapitalizedReviver`, `isUncapitalizedReviver`, `isFiniteReviver`, `isGreaterThanReviver`, `isGreaterThanOrEqualToReviver`, `isLessThanReviver`, `isLessThanOrEqualToReviver`, `isBetweenReviver`, `isMultipleOfReviver`, `isIntReviver`, `isDateValidReviver`, `isGreaterThanDateReviver`, `isGreaterThanOrEqualToDateReviver`, `isLessThanDateReviver`, `isLessThanOrEqualToDateReviver`, `isBetweenDateReviver`, `isGreaterThanBigIntReviver`, `isGreaterThanOrEqualToBigIntReviver`, `isLessThanBigIntReviver`, `isLessThanOrEqualToBigIntReviver`, `isBetweenBigIntReviver`, `isMinLengthReviver`, `isMaxLengthReviver`, `isLengthBetweenReviver`, `isMinSizeReviver`, `isMaxSizeReviver`, `isSizeBetweenReviver`, `isMinPropertiesReviver`, `isMaxPropertiesReviver`, `isPropertiesLengthBetweenReviver`, `isPropertyNamesReviver`, and `isUniqueReviver` + - Validate reviver payloads with their `payloadSchema`, and report missing or duplicate reviver identifiers. + + ### JSON Schema and code generation + - Compile JSON Schema from the canonical JSON codec and the encoded-side representation. Custom checks can contribute constraints through `Annotations.Filter.toJsonSchema` without modifying a central metadata registry. + - Import JSON Schema directly as live schemas. The importer now supports shared definitions, aliases, recursion, reference siblings, and definitions that are not reachable from a root. + - Add the named `FromJsonSchemaOptions` type for the importer `onEnter` callback. + - Generate code from live `toCode` annotations on declarations and checks. Compiler callbacks receive generated type parameters or schema dependencies and can emit multiple import declarations. + - Add import artifacts to `CodeDocument` and preserve all explicit definitions during multi-document code generation. + - Reject distinct schemas that declare the same identifier instead of silently merging them or generating suffixed references. + + ### Canonical codecs and integrations + - Preserve schema identifiers, property context, key encodings, and applicable checks while deriving canonical JSON codecs. + - Treat `Schema.Json` and `Schema.MutableJson` as already canonical. JSON validation now rejects sparse arrays, and non-finite numbers decode only from the canonical strings `"Infinity"`, `"-Infinity"`, and `"NaN"` rather than raw non-finite numeric inputs. + - Declarations without `toCodecJson` or `toCodec` now use JSON validation as their fallback instead of silently encoding to `null`. `toCodecJson` callbacks may return `undefined` when a declaration is already canonical. + - Add `Annotations.Declaration.toCodecStringTree`; StringTree derivation now requires a declaration to provide a structural StringTree, JSON, or general codec instead of silently encoding an opaque declaration to `undefined`. + - Update AI structured-output, HTTP schema, HttpApi OpenAPI, and OpenAPI generator integrations to consume the same canonical encoded representation and compiler hooks. Provider-specific structured-output transforms may remove unsupported JSON Schema keywords, while the Effect codec remains the validation authority. + + ### Breaking changes + - Rename the low-level representation constructors: + - `SchemaRepresentation.fromAST` -> `SchemaRepresentation.toRepresentation` + - `SchemaRepresentation.fromASTs` -> `SchemaRepresentation.toRepresentations` + - Replace `SchemaRepresentation.toSchema` with `fromRepresentation`, and add `fromRepresentations` for multi-root documents. Both reconstruction functions require `{ revivers: [...] }`; no default reviver is installed implicitly. + - Remove `SchemaRepresentation.toSchemaDefaultReviver`. Pass the required built-in revivers exported by `Schema`, or custom revivers created with the new constructors. + - Replace `DocumentFromJson` and `MultiDocumentFromJson` with the `toJson` / `fromJson` and `toJsonMultiDocument` / `fromJsonMultiDocument` functions. + - The persisted `Document` and `MultiDocument` format is incompatible with the previous format. Nodes now contain `checks`; encoded literal values, enum values, and property signature names use tagged `{ type, value }` objects while decoded documents expose their native scalar values; declarations no longer contain `encodedSchema`; persisted opaque declarations and leaf filters require a `{ id, payload }` representation identity; and checks no longer contain closed `meta` payloads. Regenerate stored documents from their source schemas with the new API, or migrate their shape before passing them to `fromJson`. + - Replace the generic `Reviver` function type with `DeclarationReviver

`, `FilterReviver

`, `FilterGroupReviver

`, `CheckReviver

`, `Reviver

`, and `AnyReviver`. + - Remove the closed metadata types `StringMeta`, `NumberMeta`, `BigIntMeta`, `ArraysMeta`, `ObjectsMeta`, `DateMeta`, `SizeMeta`, `DeclarationMeta`, and `Meta` from `SchemaRepresentation`. + - Remove the exported representation validation schemas and `PrimitiveTree`: `$PrimitiveTree`, `$Annotations`, `$Null`, `$Undefined`, `$Void`, `$Never`, `$Unknown`, `$Any`, `$StringMeta`, `$String`, `$NumberMeta`, `$Number`, `$Boolean`, `$BigInt`, `$Symbol`, `$LiteralValue`, `$Literal`, `$UniqueSymbol`, `$ObjectKeyword`, `$Enum`, `$TemplateLiteral`, `$Element`, `$Arrays`, `$PropertySignature`, `$IndexSignature`, `$ObjectsMeta`, `$Objects`, `$Union`, `$Reference`, `$DateMeta`, `$SizeMeta`, `$DeclarationMeta`, `$Declaration`, `$Suspend`, `$Representation`, `$Document`, and `$MultiDocument`. + - Replace schema annotations as follows: + - remove `Annotations.Bottom.meta` and `Annotations.Filter.meta` + - remove `Annotations.Declaration.typeConstructor`; use `representation` + - remove `Annotations.Declaration.generation`; use the `toCode` callback + - add `Annotations.Filter.representation`, `toJsonSchema`, and `toCode` + - add `Annotations.Augment.contentSchema` as a JSON-valued annotation + - allow `Annotations.Declaration.toCodecJson` and `toCodecStringTree` to return `undefined` + - Remove the top-level `contentMediaType` and `contentSchema` fields from `SchemaRepresentation.String`. Content metadata is now carried in ordinary annotations, and `contentSchema` is a JSON Schema value rather than a nested Effect representation. + - Remove `Schema.Annotations.BuiltInMetaDefinitions`, `BuiltInMeta`, `MetaDefinitions`, and `Meta`. Custom checks should carry a representation identity and compiler callbacks instead of augmenting the metadata registry. + - `fromJsonSchemaDocument` now returns `Schema.Top` instead of a representation `Document`. `fromJsonSchemaMultiDocument` now returns `SchemaMultiDocument` instead of `MultiDocument`; call `fromSchemaMultiDocument` when a representation multi-document is required. + - `toCodeDocument` now accepts only a live `MultiDocument`; remove its `reviver` option. Reconstruct persisted documents first so revivers can restore runtime compiler callbacks. + - Rename the `generation` field of `Artifact` values for symbols and enums to `code`. Declaration generation no longer has an `Encoded` output, and `importDeclaration` is replaced by `importDeclarations` on callback output. + - Remove the exported `sanitizeJavaScriptIdentifier`, `topologicalSort`, and `TopologicalSort` helpers. + - Negative zero no longer receives special representation handling. Do not rely on preserving its sign across JSON persistence or generated code, where it may be normalized to `0`. + - With `{ errors: "all" }`, structural checks run only after their base array, object, or declaration parses successfully; they are no longer added to an already failing child parse. + +- [#6646](https://github.com/Effect-TS/effect/pull/6646) [`7271a7f`](https://github.com/Effect-TS/effect/commit/7271a7faf1080aa75f2f53ca6a0b5ec9334c1d38) Thanks @gcanti! - Precompile union formatters and equivalences, select transformed union members using their decoded type, and allow deriving an equivalence for `Never`. + +- [#6516](https://github.com/Effect-TS/effect/pull/6516) [`475fe5c`](https://github.com/Effect-TS/effect/commit/475fe5c12c2d6504c475797c0634f90da01e1797) Thanks @tim-smart! - Prevent SQL runner lock refreshes from hanging when reserved connections become unresponsive. + +## 4.0.0-beta.101 + +### Patch Changes + +- [#6545](https://github.com/Effect-TS/effect/pull/6545) [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4) Thanks @tim-smart! - Interrupt and await concurrent traversal workers when mapper or refill callbacks throw. + +- [#6545](https://github.com/Effect-TS/effect/pull/6545) [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4) Thanks @tim-smart! - Preserve current stack frame annotations on terminal root failures. + +- [#6545](https://github.com/Effect-TS/effect/pull/6545) [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4) Thanks @tim-smart! - Store interrupting fiber stack frames separately from interrupted target stack frames. + +- [#6545](https://github.com/Effect-TS/effect/pull/6545) [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4) Thanks @tim-smart! - Avoid allocating a scheduler dispatcher when `runSyncExit` completes without yielding. + +- [#6545](https://github.com/Effect-TS/effect/pull/6545) [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4) Thanks @tim-smart! - Make awaitAllChildren child selection linear in the number of fibers. + +- [#6523](https://github.com/Effect-TS/effect/pull/6523) [`b35ed29`](https://github.com/Effect-TS/effect/commit/b35ed2904f01536d303b21f288daf343cf740462) Thanks @gcanti! - Simplify the displayed `Type`, `Encoded`, and `Iso` types of required readonly `Schema.Struct` fields, closes [#6521](https://github.com/Effect-TS/effect/issues/6521). + +- [#6514](https://github.com/Effect-TS/effect/pull/6514) [`dd44624`](https://github.com/Effect-TS/effect/commit/dd446245736a0e88c807a02f03c21450bb9340fa) Thanks @tim-smart! - Fix `MutableList.filter` leaving an invalid empty bucket when no values match. + +- [#6545](https://github.com/Effect-TS/effect/pull/6545) [`731bea1`](https://github.com/Effect-TS/effect/commit/731bea19416755a904ff5e809413e5922785d0a4) Thanks @tim-smart! - Deliver pending interrupts when interruptibleMask restores fiber interruptibility. + +- [#6526](https://github.com/Effect-TS/effect/pull/6526) [`2bae1ac`](https://github.com/Effect-TS/effect/commit/2bae1accce9d3b72cf6d5aefc9b2161af6d88436) Thanks @tim-smart! - Fix `HttpRouter.toWebHandler` middleware inference to exclude request services supplied by the HTTP adapter. + +## 4.0.0-beta.100 + +### Patch Changes + +- [#6501](https://github.com/Effect-TS/effect/pull/6501) [`c1288dd`](https://github.com/Effect-TS/effect/commit/c1288dd1a52a2811ab7df57fc4ce236c6be4c745) Thanks @gcanti! - Add a `discriminants` tuple to schemas augmented with `Schema.toTaggedUnion` and reject duplicate discriminant + property keys. + +- [#6475](https://github.com/Effect-TS/effect/pull/6475) [`2b58a3d`](https://github.com/Effect-TS/effect/commit/2b58a3dab6bc99776dddaf76e27d811e0f47f3d8) Thanks @fubhy! - Normalize cron month and weekday aliases independently of the host locale. + +- [#6492](https://github.com/Effect-TS/effect/pull/6492) [`6dc83f2`](https://github.com/Effect-TS/effect/commit/6dc83f26ddf20d48db28cf761dd8f3716e5273fb) Thanks @gcanti! - Preserve nested class construction when applying constructor defaults, closes [#6491](https://github.com/Effect-TS/effect/issues/6491). + +- [#6476](https://github.com/Effect-TS/effect/pull/6476) [`c1e2fe0`](https://github.com/Effect-TS/effect/commit/c1e2fe0cf93564f4d919e3998874c3e70b0cf30f) Thanks @fubhy! - Add `Cron` day and weekday intersection semantics in inspection representations. + +- [#6474](https://github.com/Effect-TS/effect/pull/6474) [`f3fbae8`](https://github.com/Effect-TS/effect/commit/f3fbae8d7bae0d77cb4f35a1598b26c58e3bf94d) Thanks @fubhy! - Validate `Cron.make` field constraints and treat weekday `7` as Sunday consistently with cron parsing. + +- [#6472](https://github.com/Effect-TS/effect/pull/6472) [`e000f80`](https://github.com/Effect-TS/effect/commit/e000f80fd55bcd8edc699fdbf4cd109004f4f754) Thanks @fubhy! - Fix `Cron.prev` day-of-month rollover across shorter months and non-leap years. + +- [#6471](https://github.com/Effect-TS/effect/pull/6471) [`f4ee765`](https://github.com/Effect-TS/effect/commit/f4ee7655ee052cf9ba726fd602bb87c89c7c62a9) Thanks @fubhy! - Fix `Cron.prev` weekday wrapping to always return a matching instant before the input. + +- [#6477](https://github.com/Effect-TS/effect/pull/6477) [`510b55f`](https://github.com/Effect-TS/effect/commit/510b55f3e21750685dbfd5f476a130c1c5af9dbd) Thanks @fubhy! - Make Cron equality and hashing include the optional timezone consistently. + +- [#6433](https://github.com/Effect-TS/effect/pull/6433) [`31d3fc4`](https://github.com/Effect-TS/effect/commit/31d3fc4327c50867bb8d881fa7353aeb03ea2826) Thanks @coyaSONG! - Fix the published declaration for `HttpEffect.appendPreResponseHandlerUnsafe`. + +- [#6487](https://github.com/Effect-TS/effect/pull/6487) [`875e618`](https://github.com/Effect-TS/effect/commit/875e618c3764a7b817ac863d0af86924449528f2) Thanks @rvaccone! - Fix doubled `Expected: Expected ...` prefixes in CLI `InvalidValue` error messages, closes [#6312](https://github.com/Effect-TS/effect/issues/6312). + +- [#6496](https://github.com/Effect-TS/effect/pull/6496) [`688d46a`](https://github.com/Effect-TS/effect/commit/688d46afd0ef923d983ad3d7385f52f217b28d70) Thanks @tim-smart! - Port `Effect.reduce` from Effect v3. + +- [#6480](https://github.com/Effect-TS/effect/pull/6480) [`6ff5023`](https://github.com/Effect-TS/effect/commit/6ff502363b9840a5a5ee0a24bc6cae734ac3a3eb) Thanks @fubhy! - Correct the diagnostic for cron step values above a field's maximum. + +- [#6484](https://github.com/Effect-TS/effect/pull/6484) [`c0333e7`](https://github.com/Effect-TS/effect/commit/c0333e7f755f42ddcca7051e029da8b4eed527bf) Thanks @tim-smart! - Fix fiber self-interuption from inside a running operation + +- [#6493](https://github.com/Effect-TS/effect/pull/6493) [`06e7e8c`](https://github.com/Effect-TS/effect/commit/06e7e8c66015ee318f871b9d2218dee82df2b108) Thanks @tim-smart! - Make multipart errors respond with an HTTP status based on their reason and ignore them in the error reporter. + +- [#6498](https://github.com/Effect-TS/effect/pull/6498) [`eb9b102`](https://github.com/Effect-TS/effect/commit/eb9b10256c8558881b441c2fef833b7037174400) Thanks @thewilkybarkid! - Don’t create a table when it’s not needed + +- [#6494](https://github.com/Effect-TS/effect/pull/6494) [`8b155da`](https://github.com/Effect-TS/effect/commit/8b155da06e0740c354ec562957a45ab65eb4573b) Thanks @tim-smart! - only interrupt cache lookup when all awaiters are gone + +- [#6495](https://github.com/Effect-TS/effect/pull/6495) [`3a87335`](https://github.com/Effect-TS/effect/commit/3a8733564c5db35271aa20564ed0d344daa2a79f) Thanks @tim-smart! - clean up more references on fiber exit + +## 4.0.0-beta.99 + +### Patch Changes + +- [#6397](https://github.com/Effect-TS/effect/pull/6397) [`8ce4795`](https://github.com/Effect-TS/effect/commit/8ce4795ccbaebca4292757db568c005a992546a4) Thanks @IMax153! - Add a scoped `CliConfig` service for customizing the built-in global flags used by CLI command runners. + + For example, provide an explicit list that omits `GlobalFlag.LogLevel` to remove the built-in `--log-level` flag: + + ```ts + import { Effect } from "effect"; + import { CliConfig, Command, GlobalFlag } from "effect/unstable/cli"; + + const program = Command.run(command, { version: "1.0.0" }).pipe( + Effect.provide( + CliConfig.layer({ + builtIns: [GlobalFlag.Help, GlobalFlag.Version, GlobalFlag.Completions], + }), + ), + ); + ``` + +- [#6409](https://github.com/Effect-TS/effect/pull/6409) [`80b539f`](https://github.com/Effect-TS/effect/commit/80b539f8aba68f478c75c35c2b4140c4ffc4fada) Thanks @IMax153! - Reintroduce interactive CLI wizard mode through the `--wizard` flag and `Command.wizard`. + +- [#6394](https://github.com/Effect-TS/effect/pull/6394) [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199) Thanks @lloydrichards! - add a `radius` option to `Graph` search configuration, allowing `dfs`, `bfs`, and `dfsPostOrder` traversals to limit returned nodes by edge distance from the configured start nodes. Traversals can also use `direction: "undirected"` to follow edges in either direction. + +- [#6457](https://github.com/Effect-TS/effect/pull/6457) [`e6e6dba`](https://github.com/Effect-TS/effect/commit/e6e6dba6e9d86e7c2ad27dcedf289db76a19697f) Thanks @fubhy! - Improve `Graph.dijkstra` and `Graph.astar` priority queue performance. + +- [#6468](https://github.com/Effect-TS/effect/pull/6468) [`bfb203e`](https://github.com/Effect-TS/effect/commit/bfb203e95aa439f731acad37fc3a9a831a190f1c) Thanks @gcanti! - Distribute `HttpApiBuilder` handler requirements per service so request middleware layers can provide them, closes [#6464](https://github.com/Effect-TS/effect/issues/6464). + +- [#6389](https://github.com/Effect-TS/effect/pull/6389) [`2e9a34a`](https://github.com/Effect-TS/effect/commit/2e9a34ac2bece4f3a206160480c991e3841dc67a) Thanks @IMax153! - Report an error when a CLI flag, including `--completions`, is provided without its required value. + +- [#6359](https://github.com/Effect-TS/effect/pull/6359) [`55d4eb3`](https://github.com/Effect-TS/effect/commit/55d4eb34f2c64d54f6a25a305b5c5438ebd7934e) Thanks @evermake! - - Fix `Command.withSubcommands` collapsing the inferred requirements type to `never` when given more than one subcommand + - Export a `Command.Services` utility type to extract the required services from a `Command` + +- [#6462](https://github.com/Effect-TS/effect/pull/6462) [`bddb010`](https://github.com/Effect-TS/effect/commit/bddb010eac3d4436cb094edbbee7460c5440c162) Thanks @fubhy! - Fix immutable Graph equality and hashing to include future node and edge identifier allocation. + +- [#6425](https://github.com/Effect-TS/effect/pull/6425) [`a328835`](https://github.com/Effect-TS/effect/commit/a328835e50d76bc96648a1c1550456e8c9f81210) Thanks @fubhy! - Fix `Graph.bellmanFord` to detect reachable negative cycles when the source and target are the same node. + +- [#6454](https://github.com/Effect-TS/effect/pull/6454) [`5560d05`](https://github.com/Effect-TS/effect/commit/5560d05aa6abdd29466d9c3412cc5e648b0adbde) Thanks @fubhy! - Fix standalone data-last `Graph.getNode` and `Graph.getEdge` inference. + +- [#6418](https://github.com/Effect-TS/effect/pull/6418) [`8f6e3ad`](https://github.com/Effect-TS/effect/commit/8f6e3adb185b16e8820b98c509b308086f7ff1af) Thanks @fubhy! - Fix `Graph.mapEdges` and `Graph.filterMapEdges` to preserve `Graph.Edge` instances when transforming edge data. + +- [#6426](https://github.com/Effect-TS/effect/pull/6426) [`46997fa`](https://github.com/Effect-TS/effect/commit/46997fa60401f5e3c93daa4b61f7df8e31caaab4) Thanks @fubhy! - Reject `NaN` and `-Infinity` edge weights in Graph shortest-path algorithms. + +- [#6461](https://github.com/Effect-TS/effect/pull/6461) [`9e6e12d`](https://github.com/Effect-TS/effect/commit/9e6e12d75c118cd265496f2880490d1f33a5c8bf) Thanks @fubhy! - Fix mutable Graph equality and hashing to use reference identity while preserving structural semantics for immutable graphs. + +- [#6456](https://github.com/Effect-TS/effect/pull/6456) [`3394b93`](https://github.com/Effect-TS/effect/commit/3394b93d97d6f24fc38670641d1490289ffca7f1) Thanks @fubhy! - Fix topological walkers silently completing with an incomplete order when a mutable graph becomes cyclic after walker creation. + +- [#6460](https://github.com/Effect-TS/effect/pull/6460) [`febeabc`](https://github.com/Effect-TS/effect/commit/febeabc3f7c31094da000a23edeaabfe2ab00a38) Thanks @fubhy! - Restrict `Graph.topo` to directed graphs at the type level while retaining runtime validation for unsafe undirected inputs. + +- [#6455](https://github.com/Effect-TS/effect/pull/6455) [`54161c9`](https://github.com/Effect-TS/effect/commit/54161c98f6f3569e0c31842f54e6a257f9421c4c) Thanks @fubhy! - Fix `Graph.Walker` to create a fresh iterable for each direct iteration. + +- [#6414](https://github.com/Effect-TS/effect/pull/6414) [`385f7a4`](https://github.com/Effect-TS/effect/commit/385f7a4ee4a7359928597ea56d151dbaf5eb5802) Thanks @fubhy! - Fix `Graph.toGraphViz` to quote DOT graph names and escape labels as literal text. + +- [#6438](https://github.com/Effect-TS/effect/pull/6438) [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f) Thanks @tim-smart! - Fix one-shot iterable handling in Array.rotate, Iterable.cartesian, and in-memory RunnerStorage acquisition + +- [#6371](https://github.com/Effect-TS/effect/pull/6371) [`7543afe`](https://github.com/Effect-TS/effect/commit/7543afea6f4d97d1f1ad876224323838a48daadd) Thanks @polRk! - Tool: preserve the tool kind when cloning provider-defined and dynamic tools. + + `Tool.addDependency`, `setParameters`, `setSuccess`, `setFailure`, `annotate`, and `annotateMerge` previously rebuilt the tool as a user-defined tool, which flipped `Tool.isProviderDefined` to `false`, corrupted the provider `id` (e.g. `anthropic.memory_20250818`), and crashed `Tool.getStrictMode`. These operations now clone the tool while preserving its prototype, `id`, and kind. Provider-defined tools also now carry an empty annotations context so `Tool.getStrictMode`/`annotate` work on them. Closes [#2615](https://github.com/Effect-TS/effect/issues/2615). + +- [#6421](https://github.com/Effect-TS/effect/pull/6421) [`44b9cf3`](https://github.com/Effect-TS/effect/commit/44b9cf3d240d726997b4bbcd0ede48e074d3c456) Thanks @fubhy! - Preserve null edge data in Graph.floydWarshall costs. + +- [#6438](https://github.com/Effect-TS/effect/pull/6438) [`7eea4d0`](https://github.com/Effect-TS/effect/commit/7eea4d0b73ec554915d7066a71f46326ce2ba45f) Thanks @tim-smart! - ensure one-shot iterables work with Fiber apis + +- [#6420](https://github.com/Effect-TS/effect/pull/6420) [`0a8aa6a`](https://github.com/Effect-TS/effect/commit/0a8aa6acb90a72b91c24d17133c950e4cacd8abd) Thanks @fubhy! - Fix `Graph.isAcyclic` to detect cycles formed by parallel undirected edges. + +- [#6417](https://github.com/Effect-TS/effect/pull/6417) [`c8d9fcf`](https://github.com/Effect-TS/effect/commit/c8d9fcf7b030f7c474effbab2764ce7aee1c7209) Thanks @fubhy! - Reject `Graph` mutation operations on mutable handles after `Graph.endMutation` finalizes them. + +- [#6423](https://github.com/Effect-TS/effect/pull/6423) [`9ca7f9a`](https://github.com/Effect-TS/effect/commit/9ca7f9a69363e4485645966d5a93b8f9597c5206) Thanks @fubhy! - Fix Graph.isGraph narrowing for mutable and undirected graphs. + +- [#6459](https://github.com/Effect-TS/effect/pull/6459) [`e7aca89`](https://github.com/Effect-TS/effect/commit/e7aca894bb32fbb785b5830837e6061c415a6015) Thanks @fubhy! - Reject asynchronous `Graph` mutation callbacks and finalize scoped mutable handles when callbacks fail. + +- [#6458](https://github.com/Effect-TS/effect/pull/6458) [`55d7560`](https://github.com/Effect-TS/effect/commit/55d75609b8acf8a1b54c1b1c7fbbb65ec741aa3e) Thanks @fubhy! - Fix undirected `Graph` equality and hashing to ignore stored edge endpoint orientation. + +- [#6415](https://github.com/Effect-TS/effect/pull/6415) [`f809189`](https://github.com/Effect-TS/effect/commit/f809189ddf6b6011ba43a9901baaa734e315da2a) Thanks @fubhy! - Fix `Graph.Walker` iteration for receiver-sensitive iterables. + +- [#6394](https://github.com/Effect-TS/effect/pull/6394) [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199) Thanks @lloydrichards! - added graph set operations for combining and comparing graphs + - `Graph.make` - creates a graph constructor for a dynamically selected graph kind + - `Graph.compose` - composition of two graphs, merging nodes by identity + - `Graph.intersection` - intersection of two graphs, keeping only common nodes and edges + - `Graph.difference` - difference of two graphs, removing edges present in the second graph + - `Graph.symmetricDifference` - symmetric difference of two graphs, keeping edges present in exactly one graph + +- [#6395](https://github.com/Effect-TS/effect/pull/6395) [`0ebdbe7`](https://github.com/Effect-TS/effect/commit/0ebdbe74463dc84385956d0b1e8c2b79ebab5400) Thanks @Chaoran-Huang! - Fix multipart parser limit violations being silently swallowed + +- [#6419](https://github.com/Effect-TS/effect/pull/6419) [`7517d09`](https://github.com/Effect-TS/effect/commit/7517d09f12a0b183a81bd425962c4e280a68b05d) Thanks @fubhy! - Make the public Graph interfaces opaque by hiding internal mutable storage fields from their TypeScript surface. + +- [#6390](https://github.com/Effect-TS/effect/pull/6390) [`212493b`](https://github.com/Effect-TS/effect/commit/212493b9a1eb98cd1ef6959c707a2e5784a5ae91) Thanks @alvarosevilla95! - Fix Redis script evaluation so transient `SCRIPT LOAD` failures are retried instead of being cached indefinitely. + +- [#6394](https://github.com/Effect-TS/effect/pull/6394) [`88a54cc`](https://github.com/Effect-TS/effect/commit/88a54cc341006e3ebcb13482c618f62a680ce199) Thanks @lloydrichards! - add advanced graph set operations for deriving related graph structures + - `Graph.complement` - complement over the existing node set, adding missing edges between distinct nodes + - `Graph.neighborhood` - induced subgraph containing nodes within a radius of a node + - `Graph.sum` - disjoint union of two graphs without merging equal node data + +- [#6430](https://github.com/Effect-TS/effect/pull/6430) [`80ea8cb`](https://github.com/Effect-TS/effect/commit/80ea8cb9222ca73f564c8267ab2f82966fea027a) Thanks @fubhy! - Fix Graph BFS, topological sort, and DFS postorder iterators to skip nodes removed from a MutableGraph without recursive self-calls. + +- [#6465](https://github.com/Effect-TS/effect/pull/6465) [`8df19f4`](https://github.com/Effect-TS/effect/commit/8df19f4fe81d90cc33ace88b9a77e5534f82d604) Thanks @gcanti! - Fix `isInt32` to apply custom annotations only to its filter group. + +## 4.0.0-beta.98 + +### Patch Changes + +- [#2587](https://github.com/Effect-TS/effect-smol/pull/2587) [`989603b`](https://github.com/Effect-TS/effect-smol/commit/989603b60ab1197b64acf214208e0d370cd1f842) Thanks @gcanti! - Expose `SchemaError` as a public module and re-export `Schema.isSchemaError`. + + This gives consumers a stable import path and guard for schema failures without + depending on the internal schema implementation, while preserving the existing + `Schema.SchemaError` surface. + +- [#2592](https://github.com/Effect-TS/effect-smol/pull/2592) [`214c458`](https://github.com/Effect-TS/effect-smol/commit/214c458084bb6995d543cd37d1055f24be3d454e) Thanks @gcanti! - Apply `transformClient` when building an individual HttpApi endpoint client, preserving the supplied client's error and service channels. + +- [#2598](https://github.com/Effect-TS/effect-smol/pull/2598) [`a037273`](https://github.com/Effect-TS/effect-smol/commit/a0372736ac34796969b051bbba4717d7983f1ebe) Thanks @gcanti! - Preserve `__proto__` group and endpoint identifiers in HTTP APIs, generated clients, and URL builders. + +- [#2578](https://github.com/Effect-TS/effect-smol/pull/2578) [`97fdaa9`](https://github.com/Effect-TS/effect-smol/commit/97fdaa9c1f522c65e579365d314a07878e2b904f) Thanks @tim-smart! - Fix `Atom.kvs` async mode to retain its `AsyncResult` value shape after writes. + +- [#2612](https://github.com/Effect-TS/effect-smol/pull/2612) [`b24d248`](https://github.com/Effect-TS/effect-smol/commit/b24d248c8df44222ce642087cde2bd859a2dc709) Thanks @gptguy! - Fix replay of persisted `DurableDeferred.raceAll` results. + +- [#2580](https://github.com/Effect-TS/effect-smol/pull/2580) [`19c222c`](https://github.com/Effect-TS/effect-smol/commit/19c222cac2353a3d7b7733caecb00556fffe9a5c) Thanks @gcanti! - Fix HttpApi authorization decoding. + + Previously, `HttpApiBuilder.securityDecode` removed the expected scheme length and one following character from the `Authorization` header without verifying either value. A Bearer decoder could therefore pass credentials from a different scheme such as `Basic`, accept a malformed header without a separating space, or retain leading spaces when more than one separator was present. + + The decoder now validates the declared scheme before returning credentials, matches it case-insensitively as required by [RFC 9110 section 11.1](https://www.rfc-editor.org/rfc/rfc9110.html#section-11.1), and consumes one or more separating spaces. Missing, malformed, or mismatched headers produce the existing empty credential value so security middleware can reject them consistently. + + Basic authentication previously split the decoded `user-pass` value at every colon, causing otherwise valid passwords containing `:` to be discarded. It now uses only the first colon as the separator and preserves the rest of the password, following [RFC 7617 section 2](https://www.rfc-editor.org/rfc/rfc7617.html#section-2). + +- [#2581](https://github.com/Effect-TS/effect-smol/pull/2581) [`eec85dd`](https://github.com/Effect-TS/effect-smol/commit/eec85ddba09ea326fd268ee33eeffd47e50d4671) Thanks @gcanti! - Fix HttpApi client error decoding. + + Generated clients previously combined every error schema for a status into one union decoder. When schemas used different encodings, their declaration order could determine the decoded error instead of the response `Content-Type`; for example, a text decoder could accept a JSON response before the JSON decoder was tried. + + Error responses are now grouped and selected by normalized content type, matching buffered success responses. Normalization happens before grouping, so declarations that differ only by casing or parameters such as `charset` share one union decoder instead of making later schemas unreachable. + + No-content schemas are represented by a headerless alternative, allowing empty error responses without a `Content-Type` header to decode correctly. Unsupported content types preserve the existing combination of `StatusCodeError` and the response decoding failure. + +- [#2605](https://github.com/Effect-TS/effect-smol/pull/2605) [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc) Thanks @gcanti! - Fix `Number.remainder` for very small and large values formatted in scientific notation. + +- [#2611](https://github.com/Effect-TS/effect-smol/pull/2611) [`8849052`](https://github.com/Effect-TS/effect-smol/commit/884905232d1e9a365e046d8dde27bf9c5707f57f) Thanks @tim-smart! - Fix `PersistedQueue` to count schema decoding and malformed SQL payload failures as processing attempts. + +- [#2500](https://github.com/Effect-TS/effect-smol/pull/2500) [`c15e16a`](https://github.com/Effect-TS/effect-smol/commit/c15e16ad130d1fbde25d912b7ac55995066cb35b) Thanks @hsubra89! - Fix Redis-backed `PersistedQueue` reset and failed-item handling. + +- [#2602](https://github.com/Effect-TS/effect-smol/pull/2602) [`01d00a3`](https://github.com/Effect-TS/effect-smol/commit/01d00a3abfbf1f37996cdbe738ea5137c646cdd7) Thanks @gcanti! - Fix a bug where decoding bracket paths from FormData or URLSearchParams could mutate inherited object prototypes. + +- [#2588](https://github.com/Effect-TS/effect-smol/pull/2588) [`8bd4589`](https://github.com/Effect-TS/effect-smol/commit/8bd458975a1b3a8ed042eccf317b93d28ded91e7) Thanks @gcanti! - Fix `SchemaAST.isJson` to reject class instances and other non-record objects. + +- [#2605](https://github.com/Effect-TS/effect-smol/pull/2605) [`0082f4f`](https://github.com/Effect-TS/effect-smol/commit/0082f4f74fb139fd578f87f0a790e845133983dc) Thanks @gcanti! - Fix JSON Schema `allOf` imports for tuple intersections and preserve primitive refinements when combining literal constraints. + +- [#2604](https://github.com/Effect-TS/effect-smol/pull/2604) [`6e08428`](https://github.com/Effect-TS/effect-smol/commit/6e08428d980501b856f846ad3f3f0e4ea46e7786) Thanks @gcanti! - Fix `Schema.toFormatter` and `Schema.toEquivalence` indexing for tuples with multiple post-rest elements. + +- [#2603](https://github.com/Effect-TS/effect-smol/pull/2603) [`388dcf9`](https://github.com/Effect-TS/effect-smol/commit/388dcf953f65d317547f34d40e6443c5f264205f) Thanks @gcanti! - Fix union candidate selection and decoding order so that unions now: + - consider matches from every sentinel key instead of dropping valid members after the first match; + - reject ambiguous `oneOf` inputs when members with different sentinel keys both match; + - preserve declared member order when combining discriminated members with non-discriminated fallbacks; + - commit concurrent decoding results in declaration order instead of completion order. + + Reserved SSE failure event names with non-`Cause` data are now emitted as application events instead of producing a runtime defect. + +- [#2609](https://github.com/Effect-TS/effect-smol/pull/2609) [`2b7ce2b`](https://github.com/Effect-TS/effect-smol/commit/2b7ce2b513e7ec2a77822f1116dc6ffb6ba93f4e) Thanks @tim-smart! - Fix SQL-backed persisted queues to refresh locks for actively acquired elements. + +- [#2583](https://github.com/Effect-TS/effect-smol/pull/2583) [`87bea7e`](https://github.com/Effect-TS/effect-smol/commit/87bea7e16259246f3bcdf565446394751abca953) Thanks @MrGovindan! - Fixed Clock.sleep handling of large durations + +- [#2582](https://github.com/Effect-TS/effect-smol/pull/2582) [`ce38dc3`](https://github.com/Effect-TS/effect-smol/commit/ce38dc33bda805a684432cca071f4dc3c6b9a1ba) Thanks @gcanti! - Harden HttpApi documentation HTML rendering. + + Scalar descriptions and CDN versions were interpolated without attribute-safe escaping. Embedded OpenAPI JSON in Scalar and Swagger also handled only the exact `` sequence, not other valid [script end-tag forms](https://html.spec.whatwg.org/multipage/parsing.html#script-data-end-tag-name-state). + + Attribute values and CDN versions are now encoded for their contexts, and embedded JSON escapes `<` so it cannot close its script element. + +- [#2591](https://github.com/Effect-TS/effect-smol/pull/2591) [`a807cd1`](https://github.com/Effect-TS/effect-smol/commit/a807cd170341deca8a1cfb52c4222585f2431bb9) Thanks @gcanti! - Keep HttpApi composition immutable. + + `HttpApi.addHttpApi` applied annotations from the added API by mutating its shared groups. It now creates annotated group copies, keeping the source API and independently annotated variants unchanged while preserving annotation precedence. + +- [#2584](https://github.com/Effect-TS/effect-smol/pull/2584) [`fd8a356`](https://github.com/Effect-TS/effect-smol/commit/fd8a356f06a8c9ce4e7e0a13fc4021c178ed31de) Thanks @gcanti! - Normalize HttpApi payload media types. + + Payload schemas were stored under their exact declared `Content-Type`, but the server lowercased the incoming header and removed its parameters before looking it up. For example, a schema declared as `Application/Vnd.Effect+JSON; profile=declared` was stored under that value, while the server looked for `application/vnd.effect+json`. This could produce a `415` response even when the generated client and server used the same API. + + The same mismatch allowed incompatible encodings for equivalent media types to bypass validation. Generated form-urlencoded requests also ignored custom content types and always used the default one. + + Payload maps now use normalized keys for matching and conflict checks, while each encoding keeps its declared content type. Generated requests and OpenAPI use the declared values, including every parameterized variant, and custom form-urlencoded content types are preserved. + +- [#2476](https://github.com/Effect-TS/effect-smol/pull/2476) [`c2a5edc`](https://github.com/Effect-TS/effect-smol/commit/c2a5edc3abd31ad5bc123362bc1213e03e4095c3) Thanks @gcanti! - Improve unstable `HttpApi` type-level performance. + + The implementation now uses identifier-keyed maps and lighter structural + constraints in several hot type-level paths. Generated group clients consume the + concrete endpoint map directly instead of rebuilding it from the endpoint union. + + ## New Features + - Add `HttpApiBuilder.Handlers.handleAll`, which registers an identifier-keyed batch of endpoint handlers for a group. Each entry can be either a handler function or `{ handler, options }`, and the object can be supplied in multiple partial batches. Endpoint identifiers that were already handled are rejected across batches. + - `HttpApi.groups` now preserves the concrete group type for each group identifier. For example, `Api.groups.users` is typed as the `users` group instead of the full group union. + - `HttpApiGroup.endpoints` now preserves the concrete endpoint type for each endpoint identifier. For example, `Group.endpoints.getUser` is typed as the `getUser` endpoint instead of the full endpoint union. + - `HttpApiEndpoint` values can now be extended as classes, matching the class-like + runtime shape already used by `HttpApi` and `HttpApiGroup`. + + ## Measured Type-Level Performance + + Main/current comparisons use identical generated fixtures compiled once per + revision with TypeScript 7.0.2. The recorded revisions are `main` at + `97fdaa9c1f52` and the branch source at `5798fc5fafcd`. The focused pre/post + curves below were captured with the regular `httpapi` regression suite during + development. The retained suite uses representative stress points instead of + rerunning every point in those historical curves. All numbers are + type-instantiation deltas over the corresponding shared baseline. + + Endpoint declaration costs now grow with a lower slope: + + | endpoints | main | current | + | --------: | ------: | ------: | + | 10 | 4,580 | 2,808 | + | 50 | 15,500 | 9,168 | + | 100 | 29,150 | 17,118 | + | 500 | 138,350 | 80,718 | + + Class-like endpoint declarations are slightly cheaper than inline endpoint + values in the same 500-endpoint fixture shape: + + | fixture | inline | class-like | + | ------------- | -----: | ---------: | + | 500 endpoints | 82,207 | 71,850 | + + `HttpApiBuilder` fluent handler registration avoids the previous non-linear + blow-up in the cross-ref comparison: + + | fixture | main | current | + | ---------------- | ---------: | --------: | + | 10 endpoints | 37,856 | 11,582 | + | 50 endpoints | 568,576 | 63,702 | + | 100 endpoints | 2,154,476 | 182,852 | + | 500 endpoints | 51,741,676 | 3,296,052 | + | 500 raw handlers | 51,734,176 | 3,294,550 | + + In the recorded regular-suite measurements, `handleAll` remains the scalable + alternative to the equivalent fluent chain: + + | fixture | fluent | `handleAll` | + | -------------------- | --------: | ----------: | + | 10 endpoints | 11,579 | 9,146 | + | 50 endpoints | 63,699 | 25,106 | + | 100 endpoints | 182,849 | 45,056 | + | 500 endpoints | 3,296,049 | 204,656 | + | 500 eps, two batches | 3,296,049 | 223,613 | + + Generated-client type production also improves for the hot method-building + paths: + + | fixture | main | current | + | --------------------------------------- | ------: | ------: | + | client methods, 500 endpoints | 245,795 | 176,850 | + | top-level client methods, 500 endpoints | 243,651 | 179,809 | + | client endpoint method, 500 endpoints | 56,738 | 46,294 | + | client groups, 100 groups x 5 endpoints | 49,019 | 25,893 | + + The following focused curves were captured immediately before and after each + isolated type-level change. + + The focused `Client.Group` curve shows the improvement from consuming the + identifier-keyed endpoint map directly: + + | endpoints | union remapping | endpoint map | + | --------: | --------------: | -----------: | + | 10 | 12,448 | 12,294 | + | 50 | 19,169 | 18,935 | + | 100 | 27,570 | 27,236 | + | 500 | 94,770 | 93,636 | + + The focused `Client.TopLevelMethods` curve improves by reading endpoint + identifiers directly from the endpoint union: + + | endpoints | pre-change | post-change | + | --------: | ---------: | ----------: | + | 10 | 12,531 | 12,476 | + | 50 | 19,252 | 19,197 | + | 100 | 27,653 | 27,598 | + | 500 | 94,853 | 94,798 | + + The focused `HttpApiClient.endpoint` selection curve improves by reading + endpoint identifiers directly from the selected endpoint union: + + | endpoints | pre-change | post-change | + | --------: | ---------: | ----------: | + | 10 | 7,666 | 7,588 | + | 50 | 8,707 | 8,629 | + | 100 | 10,008 | 9,930 | + | 500 | 20,408 | 20,330 | + + The focused `HttpApiBuilder.endpoint` selection curve improves by reading + endpoint identifiers directly from the selected endpoint union: + + | endpoints | pre-change | post-change | + | --------: | ---------: | ----------: | + | 10 | 12,828 | 12,745 | + | 50 | 13,869 | 13,786 | + | 100 | 15,170 | 15,087 | + | 500 | 25,570 | 25,487 | + + URL builder types now avoid repeatedly expanding the full API/group shape: + + | fixture | main | current | + | ------------------------------------ | ------: | ------: | + | URL builder, 500 endpoints | 211,356 | 91,610 | + | top-level URL builder, 500 endpoints | 210,724 | 93,118 | + | builder endpoint, 500 endpoints | 62,894 | 51,952 | + + ## Breaking Changes + + These changes affect unstable `HttpApi` type-level APIs and structural API, + group, and endpoint types. + + ### Renamed Constraint Types + - Broad structural constraint exports have been renamed to align with + `Schema.Constraint` terminology: `HttpApi.Any` to `HttpApi.Constraint`, + `HttpApi.AnyWithProps` to `HttpApi.Top`, `HttpApiGroup.Any` to + `HttpApiGroup.Constraint`, `HttpApiGroup.AnyWithProps` to `HttpApiGroup.Top`, + and `HttpApiEndpoint.Any` to `HttpApiEndpoint.Constraint`. + - `HttpApiEndpoint.AnyWithProps` has been replaced by `HttpApiEndpoint.Top`, whose + schema parameters are constrained to `Schema.Top`, including success and error + schemas. + - Type guards now expose the widened runtime-prop shapes: `HttpApi.isHttpApi` + returns `HttpApi.Top`, `HttpApiGroup.isHttpApiGroup` returns + `HttpApiGroup.Top`, and `HttpApiEndpoint.isHttpApiEndpoint` returns + `HttpApiEndpoint.Top`. + - `HttpApiGroup.ApiGroup` has been renamed to `HttpApiGroup.Service`. + + ### API, Group, And Endpoint Shapes + - `HttpApi.groups` is now typed as an identifier-keyed group map instead of + `ReadonlyRecord`, and `HttpApi` tracks its group union + invariantly. Dynamic string indexing must refine the key first or cast to a + broad runtime record. + - `HttpApiGroup.endpoints` is now typed as an identifier-keyed endpoint map instead of + `ReadonlyRecord`, and `HttpApiGroup` tracks its endpoint + union invariantly. Dynamic string indexing must refine the key first or cast to + a broad runtime record. + - `HttpApiEndpoint` now exposes its stable key as `identifier` instead of `name`, + aligning endpoints with APIs and groups and leaving `name` available for future + class-based endpoint patterns. + - `HttpApiEndpoint` values are now function objects instead of plain objects. + Runtime checks such as `typeof endpoint` now return `"function"`, and + `endpoint.name` is the native function name. Use `endpoint.identifier` for the + stable endpoint key. + - Identifier helper types have been renamed from `Name` / `WithName` to + `Identifier` / `WithIdentifier`; `HttpApiGroup.Service` now exposes + `identifier` instead of `name`. + + ### Builder Handler Types + - `HttpApiBuilder.Handlers` now tracks endpoints through an identifier-keyed endpoint map and a set of handled endpoint identifiers, instead of tracking the remaining endpoint union. Its public type parameters changed from `Handlers` to `Handlers`, and its phantom fields changed from `_Endpoints` to `~EndpointsByIdentifier` / `~HandledIdentifiers`. + - The unused `HttpApiBuilder.Handlers.Any` helper type has been removed. + - The exported `HttpApiBuilder.HandlersTypeId` symbol has been removed; `Handlers` + now uses a private string type id. + - Duplicate `handle` / `handleRaw` registrations for the same endpoint are rejected + at the call site, and `handleAll` rejects endpoint identifiers that were already + handled by an earlier batch. Missing endpoint handlers are still rejected by + the final `HttpApiBuilder.group` return validation. + + ### Client Types + - `HttpApiClient.Client.Group` now derives a client from a concrete group type: `Client.Group`. The previous group-union plus group-identifier form is no longer supported. + - `HttpApiClient.Client.TopLevelMethods` now returns an identifier-keyed method record instead of a union of `[identifier, method]` tuples. + - `HttpApiClient.makeWith` removes the default `HttpClientError.HttpClientError` from custom client error types in the returned `Client`, while preserving any additional custom client errors. + + ### Endpoint Helper Types + - `HttpApiEndpoint.HttpApiEndpoint` now stores lightweight phantom metadata for middleware and request shapes: `~Middleware`, `~MiddlewareServices`, `~Request`, and `~RequestRaw`. Its type identifier field is now `readonly [TypeId]: typeof TypeId`. + - `HttpApiEndpoint.Constraint` is now a lightweight structural endpoint constraint and does not extend `Pipeable`; values typed only as `HttpApiEndpoint.Constraint` do not expose `.pipe`. + - `HttpApiEndpoint.AddError` has been removed; it was not used internally by the `HttpApi` implementation. + - `HttpApiEndpoint.Json` and `HttpApiEndpoint.StringTree` have been removed in + favor of the canonical `Schema.toCodecJson` and `Schema.toCodecStringTree` + types. + - Omitted request-part metadata now remains `never` instead of being wrapped as + `Schema.toCodecStringTree`; codec metadata is applied only when + a params, query, payload, or headers schema is present. + - Success metadata now applies `Schema.toCodecJson` only to buffered + success schemas and preserves stream success schemas unchanged, including + mixed buffered and streaming success arrays. + - Handler request parts are now flattened with `Struct.Simplify`, improving + displayed request types while reducing handler instantiations. + - Endpoint helper types now read metadata fields directly instead of re-inferring all type parameters from the full `HttpApiEndpoint` interface. This affects helpers such as `Identifier`, `Success`, `Error`, `Params`, `Query`, `Payload`, `Headers`, `Middleware`, `MiddlewareServices`, `Errors`, `ErrorServicesEncode`, `ErrorServicesDecode`, `Request`, `RequestRaw`, `ServerServices`, and `ClientServices`. + - `HttpApiClient.Client.Method` and related generated-client helpers now require endpoint types that satisfy `HttpApiEndpoint.ConstraintRequest`. Endpoint-like structural types must include the lightweight request metadata fields to be accepted. + +- [#2585](https://github.com/Effect-TS/effect-smol/pull/2585) [`5946da3`](https://github.com/Effect-TS/effect-smol/commit/5946da3804a1be5e752b05b96bd058cdba50a1bf) Thanks @gcanti! - Reuse HttpApi response schemas. + + `HttpApiBuilder` looked up cached response schemas by their source AST but stored them by the transformed AST, so the cache normally missed. It now uses the source AST consistently. + +- [#2590](https://github.com/Effect-TS/effect-smol/pull/2590) [`4ae0c5f`](https://github.com/Effect-TS/effect-smol/commit/4ae0c5ffcbe6c56ddfcb05c639112a079483539e) Thanks @IMax153! - Cleanup internals of CLI package + +- [#2607](https://github.com/Effect-TS/effect-smol/pull/2607) [`5b2a0bc`](https://github.com/Effect-TS/effect-smol/commit/5b2a0bceea3a28a33a58555210c90a415dc74a76) Thanks @tim-smart! - ensure WithTransaction wraps entire rpc handler + +- [#2613](https://github.com/Effect-TS/effect-smol/pull/2613) [`72ac585`](https://github.com/Effect-TS/effect-smol/commit/72ac585884befde6af9208da738699a93f1bae79) Thanks @tim-smart! - Add `HttpApiError.UnprocessableEntity` and `HttpApiError.UnprocessableEntityNoContent` for status 422 responses. + +- [#2594](https://github.com/Effect-TS/effect-smol/pull/2594) [`5e8c1b8`](https://github.com/Effect-TS/effect-smol/commit/5e8c1b82bfafa121311f987a49ab75395e3647a7) Thanks @gcanti! - Reject unknown and duplicate HttpApi handler registrations with descriptive errors. + +- [#2595](https://github.com/Effect-TS/effect-smol/pull/2595) [`0f9c078`](https://github.com/Effect-TS/effect-smol/commit/0f9c07841b04183f485ee6e6458de73b290b09f5) Thanks @gcanti! - Reject duplicate OpenAPI operations and operation identifiers, and reject incompatible security schemes that reuse a name. + +## 4.0.0-beta.97 + +## 4.0.0-beta.96 + +### Patch Changes + +- [#2563](https://github.com/Effect-TS/effect-smol/pull/2563) [`1503f45`](https://github.com/Effect-TS/effect-smol/commit/1503f45cb5bb2a74f4705252ec505a1f0ade7e62) Thanks @tim-smart! - update dependencies + +- [#2566](https://github.com/Effect-TS/effect-smol/pull/2566) [`57fe793`](https://github.com/Effect-TS/effect-smol/commit/57fe79316ffbc380b30626a168981fb26ae97459) Thanks @tim-smart! - change rpc ids to string | number + +- [#2561](https://github.com/Effect-TS/effect-smol/pull/2561) [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5) Thanks @tim-smart! - Remove `Schedule.elapsed`. + +- [#2561](https://github.com/Effect-TS/effect-smol/pull/2561) [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5) Thanks @tim-smart! - Remove `Schedule.tapInput` and `Schedule.tapOutput`. Use `Schedule.tap` instead. + +- [#2561](https://github.com/Effect-TS/effect-smol/pull/2561) [`0c2f78f`](https://github.com/Effect-TS/effect-smol/commit/0c2f78f695ec474e1ff5474da183577975e418f5) Thanks @tim-smart! - Update `Schedule.addDelay` and `Schedule.modifyDelay` to receive full schedule metadata instead of separate output and delay arguments. + +- [#2562](https://github.com/Effect-TS/effect-smol/pull/2562) [`97f29df`](https://github.com/Effect-TS/effect-smol/commit/97f29df457f7ffd07cfb4b379315c12c086af805) Thanks @tim-smart! - use Sets to track atom relationships + +## 4.0.0-beta.95 + +### Patch Changes + +- [#2542](https://github.com/Effect-TS/effect-smol/pull/2542) [`a482442`](https://github.com/Effect-TS/effect-smol/commit/a482442abdeb490e9652b854ec3495e4aa7273e7) Thanks @IGassmann! - Add `Schema.DateFromMillis` and `SchemaTransformation.dateFromMillis` for decoding millisecond timestamps into `Date` values. + +- [#2559](https://github.com/Effect-TS/effect-smol/pull/2559) [`fbefa85`](https://github.com/Effect-TS/effect-smol/commit/fbefa850fab2f0a302c20614496aeaaa2a8b5590) Thanks @tim-smart! - fix activity retry policy + +- [#2547](https://github.com/Effect-TS/effect-smol/pull/2547) [`0b4a32f`](https://github.com/Effect-TS/effect-smol/commit/0b4a32f4260f0d8500942a133001b0d349328102) Thanks @fubhy! - Allow cron fields like `5/15` to expand from the starting value through the field maximum. + +- [#2557](https://github.com/Effect-TS/effect-smol/pull/2557) [`18a49e1`](https://github.com/Effect-TS/effect-smol/commit/18a49e1786679456258002ff9397faf02f678c2d) Thanks @fubhy! - Fix `Schedule.cron` when the test clock is adjusted to infinity. + +- [#2560](https://github.com/Effect-TS/effect-smol/pull/2560) [`266cb90`](https://github.com/Effect-TS/effect-smol/commit/266cb90bb2c17aabc40563c32db334f09ba3d74b) Thanks @gcanti! - Treat empty strings as missing values in built-in `ConfigProvider`s by default. + + `ConfigProvider.fromEnv`, `ConfigProvider.fromDotEnvContents`, `ConfigProvider.fromDotEnv`, `ConfigProvider.fromUnknown`, and `ConfigProvider.fromDir` now treat literal empty strings as absent values when loaded as values, allowing `Config.withDefault` and `Config.option` to recover. Container discovery still reflects the source structure. Pass `preserveEmptyStrings: true` to restore the previous behavior. + + `ConfigProvider.fromDotEnv({ expandVariables: true })` now expands variables consistently with `ConfigProvider.fromDotEnvContents`. + +- [#2554](https://github.com/Effect-TS/effect-smol/pull/2554) [`912f095`](https://github.com/Effect-TS/effect-smol/commit/912f095a34572bbd3cedf6edb27878443e3e4a95) Thanks @tim-smart! - Add Schedule.upTo options for limiting schedules by duration and/or recurrence count. + +- [#2556](https://github.com/Effect-TS/effect-smol/pull/2556) [`a6718f9`](https://github.com/Effect-TS/effect-smol/commit/a6718f9e00a15ca903b0732da46116cbf3d6aca7) Thanks @fubhy! - Fix cron parsing and scheduling edge cases for whitespace, Sunday `7`, strict numeric tokens, explicit full day ranges, and month-constrained day-of-month / weekday matching. + +- [#2551](https://github.com/Effect-TS/effect-smol/pull/2551) [`bef5154`](https://github.com/Effect-TS/effect-smol/commit/bef51540a243aa2f872a00c01d0cd58b7a769baa) Thanks @tim-smart! - Remove the `Schedule.both` APIs and add `Schedule.max` for combining schedules by their slowest delay. + +- [#2553](https://github.com/Effect-TS/effect-smol/pull/2553) [`18e0564`](https://github.com/Effect-TS/effect-smol/commit/18e0564bd0f8ebbdfcaf1e2c21529948e9e4a81d) Thanks @tim-smart! - Remove some Schedule APIs: `collectInputs`, `collectOutputs`, `collectWhile`, `delays`, `reduce`, `satisfiesErrorType`, `satisfiesInputType`, `satisfiesOutputType`, `satisfiesServicesType`, and `unfold`. + +- [#2558](https://github.com/Effect-TS/effect-smol/pull/2558) [`fb50f14`](https://github.com/Effect-TS/effect-smol/commit/fb50f14fc3657c1973785aa5b72ecf0b0d28e0b2) Thanks @tim-smart! - Remove the Schedule.either APIs and add Schedule.min for fastest-duration schedule composition. + +## 4.0.0-beta.94 + +### Patch Changes + +- [#2538](https://github.com/Effect-TS/effect-smol/pull/2538) [`95a0e9b`](https://github.com/Effect-TS/effect-smol/commit/95a0e9bb62797af0e81c9998773405f248f218c5) Thanks @tim-smart! - fork memo map on nested builds + +- [#2545](https://github.com/Effect-TS/effect-smol/pull/2545) [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63) Thanks @marbemac! - Use registration context for cluster entities + +- [#2524](https://github.com/Effect-TS/effect-smol/pull/2524) [`f11ce73`](https://github.com/Effect-TS/effect-smol/commit/f11ce73af60823754dc24194f4ffc561b9ea1c2d) Thanks @gcanti! - Fix `HttpApi.make` so it stores the API identifier and starts with an empty `groups` object instead of a `Map`. This makes empty APIs match the shape they have after groups are added. + +- [#2546](https://github.com/Effect-TS/effect-smol/pull/2546) [`ff30b6e`](https://github.com/Effect-TS/effect-smol/commit/ff30b6e7c2c63ffc56a4c5818d6d86b01b5ad528) Thanks @tim-smart! - Fix ClusterWorkflowEngine partial workflow clients colliding with full workflow clients. + +- [#2523](https://github.com/Effect-TS/effect-smol/pull/2523) [`1caab3c`](https://github.com/Effect-TS/effect-smol/commit/1caab3cc30f626efbf15e59d74f539a487e5c85c) Thanks @rajzik! - Add glob to filesystem + +- [#2541](https://github.com/Effect-TS/effect-smol/pull/2541) [`aa80c47`](https://github.com/Effect-TS/effect-smol/commit/aa80c4775a04db87553e5568764cab7e32a72814) Thanks @tim-smart! - add LayerRef module + +- [#2539](https://github.com/Effect-TS/effect-smol/pull/2539) [`c2ae4fc`](https://github.com/Effect-TS/effect-smol/commit/c2ae4fce2f03a4cd1861c2b1179da7df656e662d) Thanks @gcanti! - Schema: add `Schema.Decoder` and `Schema.Encoder`, and accept simpler schema types in APIs that only decode, only encode, or only need the basic schema shape, closes [#2536](https://github.com/Effect-TS/effect-smol/issues/2536) + +- [#2545](https://github.com/Effect-TS/effect-smol/pull/2545) [`a0a3490`](https://github.com/Effect-TS/effect-smol/commit/a0a3490bbce765f199d8e077aceac504f0462e63) Thanks @marbemac! - add Effect.setContext for fully replacing the fiber context + +## 4.0.0-beta.93 + +### Patch Changes + +- [#2512](https://github.com/Effect-TS/effect-smol/pull/2512) [`00652fe`](https://github.com/Effect-TS/effect-smol/commit/00652fe95c18f87208e91343eb8bf218faa2f677) Thanks @gcanti! - Preserve content schema identifiers when emitting JSON Schema for `Schema.fromJsonString`. + + This keeps user-defined identifiers attached to the decoded JSON payload while giving the generated JSON string wrapper its own derived name, avoiding client codegen outputs where the payload type is renamed behind the transport wrapper. + +- [#2492](https://github.com/Effect-TS/effect-smol/pull/2492) [`6c58167`](https://github.com/Effect-TS/effect-smol/commit/6c5816746eaf91d2a3c7c899c5720809fa230ae3) Thanks @maxprilutskiy! - Map HttpApi json defects to SchemaError + +- [#2519](https://github.com/Effect-TS/effect-smol/pull/2519) [`2bc5415`](https://github.com/Effect-TS/effect-smol/commit/2bc541501a7ef89e542d7cb98e96beb53cd205cc) Thanks @tim-smart! - Fix structural equality for request-style values when structural hashes collide. + +- [#2507](https://github.com/Effect-TS/effect-smol/pull/2507) [`e11cccc`](https://github.com/Effect-TS/effect-smol/commit/e11cccc7d5fe631abccc7d6e3bd296938de0fa2e) Thanks @tim-smart! - ensure handler errors don't cause httpapi security middleware to fallback + +- [#2518](https://github.com/Effect-TS/effect-smol/pull/2518) [`ba7e77e`](https://github.com/Effect-TS/effect-smol/commit/ba7e77e046b8641a3a4e9750bb88ca4a1d063d3f) Thanks @tim-smart! - Move `UrlParams.makeUrl` to `Url.make` and return `Url.UrlError` for URL construction failures. + +- [#2505](https://github.com/Effect-TS/effect-smol/pull/2505) [`5713ee7`](https://github.com/Effect-TS/effect-smol/commit/5713ee7edbc3054efde407b2286bbfd45bbc6e1c) Thanks @KhraksMamtsov! - accept UrlParams.Input in some UrlParams apis + +## 4.0.0-beta.92 + +### Patch Changes + +- [#2501](https://github.com/Effect-TS/effect-smol/pull/2501) [`affdc13`](https://github.com/Effect-TS/effect-smol/commit/affdc139045cc325dce321a84a580fdc1b2da7b9) Thanks @gcanti! - Fix excess property handling in schema-backed class constructors, closes [#2499](https://github.com/Effect-TS/effect-smol/issues/2499). + +## 4.0.0-beta.91 + +### Patch Changes + +- [#2498](https://github.com/Effect-TS/effect-smol/pull/2498) [`b135b25`](https://github.com/Effect-TS/effect-smol/commit/b135b2517fca9e7839734ace3699a7dfa75b9075) Thanks @gcanti! - Fix `Schedule.andThenResult` to emit `self` outputs as `Failure` and `other` outputs as `Success`, closes [#2497](https://github.com/Effect-TS/effect-smol/issues/2497). + +- [#2488](https://github.com/Effect-TS/effect-smol/pull/2488) [`aaa21a3`](https://github.com/Effect-TS/effect-smol/commit/aaa21a369a171c600db294f2a4f640583043e150) Thanks @fubhy! - Fix `String.camelCase` and `String.pascalCase` handling of numeric word segments, and add `String.configCase` for configuration key casing. + +- [#2485](https://github.com/Effect-TS/effect-smol/pull/2485) [`3475ee6`](https://github.com/Effect-TS/effect-smol/commit/3475ee6c2bda6b05c6d7a12ce30c8bb840b5b1a6) Thanks @tim-smart! - fix RequestResolver interruption + ## 4.0.0-beta.90 ### Patch Changes @@ -1074,7 +2824,7 @@ - [#1725](https://github.com/Effect-TS/effect-smol/pull/1725) [`27fea0f`](https://github.com/Effect-TS/effect-smol/commit/27fea0f66910de5905f40fd63f8ddbb6f7ac5aba) Thanks @tim-smart! - Improve unstable HttpApi runtime failures for missing server middleware and missing group implementations. - HttpApiBuilder.applyMiddleware now resolves middleware services via Context.getUnsafe, so missing middleware fails with a clear "Service not found: " error instead of an opaque is not a function TypeError. - HttpApiBuilder.layer now reports missing groups with actionable context (group identifier, service key, suggested HttpApiBuilder.group(...) call, and available group keys). - - Added regression tests in packages/platform-node/test/HttpApi.test.ts covering: + - Added regression tests in packages/platform/node/test/HttpApi.test.ts covering: - addHttpApi + API-level middleware applied across merged groups - missing middleware service diagnostics - missing addHttpApi group layer diagnostics diff --git a/packages/effect/CONFIG.md b/packages/effect/CONFIG.md index b49172b44..bcb414521 100644 --- a/packages/effect/CONFIG.md +++ b/packages/effect/CONFIG.md @@ -17,7 +17,7 @@ The simplest case: read one value from an environment variable. import { Config, Effect } from "effect" const program = Effect.gen(function*() { - const host = yield* Config.string("HOST") + const host = yield* Config.String("HOST") console.log(host) }) @@ -35,8 +35,8 @@ Use `Config.all` to group related keys: import { Config, ConfigProvider, Effect } from "effect" const dbConfig = Config.all({ - host: Config.string("host"), - port: Config.int("port") + host: Config.String("host"), + port: Config.Int("port") }) const provider = ConfigProvider.fromUnknown({ @@ -77,36 +77,50 @@ The schema automatically decodes raw string values into their target types. For ## Config Constructors -Each constructor reads a single value and decodes it into the appropriate type. - -| Constructor | Decoded type | Notes | -| ------------------------------ | ------------------ | ------------------------------------------------------------------------ | -| `Config.string(name?)` | `string` | Any string | -| `Config.nonEmptyString(name?)` | `string` | Rejects `""` | -| `Config.number(name?)` | `number` | Includes `NaN`, `Infinity` | -| `Config.finite(name?)` | `number` | Rejects `NaN` and `Infinity` | -| `Config.int(name?)` | `number` | Integers only | -| `Config.boolean(name?)` | `boolean` | Accepts `true/false`, `yes/no`, `on/off`, `1/0`, `y/n` | -| `Config.port(name?)` | `number` | Integer in 1–65535 | -| `Config.url(name?)` | `URL` | Parsed via the `URL` constructor | -| `Config.date(name?)` | `Date` | Rejects invalid dates | -| `Config.duration(name?)` | `Duration` | Parses `"10 seconds"`, `"500 millis"`, `"Infinity"`, `"-Infinity"`, etc. | -| `Config.logLevel(name?)` | `string` | One of `All`, `Fatal`, `Error`, `Warn`, `Info`, `Debug`, `Trace`, `None` | -| `Config.redacted(name?)` | `Redacted` | Hidden from logs and `toString` | -| `Config.literal(value, name?)` | literal type | Accepts only the given literal | - -The optional `name` parameter sets the local path segment for lookup. If the config is wrapped with `Config.nested`, the nested prefix is prepended to this local path. Omit `name` when the config should decode the provider root. +Each constructor reads and decodes a configuration value into the appropriate type. + +| Constructor | Decoded type | Notes | +| -------------------------------- | ------------------ | ------------------------------------------------------------------------ | +| `Config.String(name?)` | `string` | Any string | +| `Config.NonEmptyString(name?)` | `string` | Rejects `""` | +| `Config.Number(name?)` | `number` | Includes `NaN`, `Infinity` | +| `Config.Finite(name?)` | `number` | Rejects `NaN` and `Infinity` | +| `Config.Int(name?)` | `number` | Integers only | +| `Config.Boolean(name?)` | `boolean` | Accepts `true/false`, `yes/no`, `on/off`, `1/0`, `y/n` | +| `Config.Port(name?)` | `number` | Integer in 1–65535 | +| `Config.URL(name?)` | `URL` | Parsed via the `URL` constructor | +| `Config.Date(name?)` | `Date` | Rejects invalid dates | +| `Config.Duration(name?)` | `Duration` | Parses `"10 seconds"`, `"500 millis"`, `"Infinity"`, `"-Infinity"`, etc. | +| `Config.LogLevel(name?)` | `string` | One of `All`, `Fatal`, `Error`, `Warn`, `Info`, `Debug`, `Trace`, `None` | +| `Config.Redacted(name?)` | `Redacted` | Hidden from logs and `toString` | +| `Config.Literal(value, name?)` | literal type | Accepts only the given literal | +| `Config.Literals(values, name?)` | literal union | Accepts one of the given literals | +| `Config.Array(value, ...)` | `ReadonlyArray` | Accepts structural arrays and flat separated strings | +| `Config.Record(key, value, ...)` | `Record` | Accepts structural records and flat separated key-value strings | + +The optional `name` parameter sets the local path segment for lookup. If the config is wrapped with `Config.nested`, the nested prefix is prepended to this local path. Omit `name` when the config should decode the provider root. `Config.Array` and `Config.Record` additionally accept an options object directly when no path is needed, or a path followed by the options object. + +### Parsing and Path Ownership + +A `Config` exposes `parse(provider)`; lookup prefixes are not part of this public method. Build paths declaratively with the constructor's `name` / `path` argument and `Config.nested`. + +This keeps the two path responsibilities separate: + +- `Config.schema(..., path)` and `Config.nested(name)` describe the logical path of a setting. +- `ConfigProvider.mapInput`, `ConfigProvider.nested`, and case-conversion combinators map logical paths to a source. + +The same rule applies when a `Config` is yielded as an `Effect`: the config uses the current `ConfigProvider`, while its internally composed logical path stays an implementation detail. ## Config Combinators -### `Config.withDefault` — Fallback for Missing Keys +### `Config.withDefault` — Fallback for Absent Input -Only triggers when data is missing. Validation errors (wrong type, out of range) still propagate. +Triggers when the config cannot resolve and none of its relevant provider input is present. Validation errors and partially supplied groups still propagate. ```ts import { Config, ConfigProvider, Effect } from "effect" -const port = Config.int("port").pipe(Config.withDefault(3000)) +const port = Config.Int("port").pipe(Config.withDefault(3000)) const provider = ConfigProvider.fromUnknown({}) Effect.runSync(port.parse(provider)) // 3000 @@ -114,12 +128,12 @@ Effect.runSync(port.parse(provider)) // 3000 ### `Config.option` — Optional Values -Returns `Option.some(value)` on success and `Option.none()` when data is missing. +Returns `Option.some(value)` on success and `Option.none()` when the config is absent. A successful `undefined` value is still a success, so a schema that accepts missing input produces `Option.some(undefined)`, not `Option.none()`. ```ts import { Config, ConfigProvider, Effect } from "effect" -const maybePort = Config.option(Config.int("port")) +const maybePort = Config.option(Config.Int("port")) const provider = ConfigProvider.fromUnknown({}) Effect.runSync(maybePort.parse(provider)) // { _tag: "None" } @@ -130,7 +144,7 @@ Effect.runSync(maybePort.parse(provider)) // { _tag: "None" } ```ts import { Config } from "effect" -const upperHost = Config.string("HOST").pipe( +const upperHost = Config.String("HOST").pipe( Config.map((s) => s.toUpperCase()) ) ``` @@ -142,7 +156,7 @@ Unlike `withDefault`, this catches **all** `ConfigError`s: ```ts import { Config } from "effect" -const host = Config.string("HOST").pipe( +const host = Config.String("HOST").pipe( Config.orElse(() => Config.succeed("localhost")) ) ``` @@ -155,8 +169,8 @@ Prepends a logical path segment to every key the inner config reads. The prefix import { Config, ConfigProvider, Effect } from "effect" const dbConfig = Config.all({ - host: Config.string("host"), - port: Config.int("port") + host: Config.String("host"), + port: Config.Int("port") }).pipe(Config.nested("database")) const provider = ConfigProvider.fromUnknown({ @@ -172,7 +186,7 @@ With environment variables, nesting uses `_` as separator: ```ts import { Config, ConfigProvider, Effect } from "effect" -const host = Config.string("host").pipe(Config.nested("database")) +const host = Config.String("host").pipe(Config.nested("database")) const provider = ConfigProvider.fromEnv({ env: { database_host: "localhost" } @@ -186,7 +200,7 @@ Multiple `Config.nested` calls compose with the outermost prefix first: ```ts import { Config, ConfigProvider, Effect } from "effect" -const config = Config.string("host").pipe( +const config = Config.String("host").pipe( Config.nested("database"), Config.nested("production") ) @@ -204,42 +218,116 @@ Effect.runSync(config.parse(provider)) // "localhost" ### `Config.all` — Combine Multiple Configs -Accepts a record or a tuple: +Accepts a record or a tuple. A wholly absent group can be handled by `Config.withDefault` or `Config.option`. If any child reads provider input, every other required child must also resolve; partial groups fail instead of silently replacing user input with a whole-group default. ```ts import { Config } from "effect" // As a record const appConfig = Config.all({ - host: Config.string("host"), - port: Config.int("port"), - debug: Config.boolean("debug") + host: Config.String("host"), + port: Config.Int("port"), + debug: Config.Boolean("debug") }) // As a tuple -const pair = Config.all([Config.string("a"), Config.int("b")]) +const pair = Config.all([Config.String("a"), Config.Int("b")]) ``` +For example, providing only `host` is an error here: + +```ts +import { Config } from "effect" + +const database = Config.all({ + host: Config.String("host"), + port: Config.Int("port") +}).pipe( + Config.withDefault({ host: "localhost", port: 5432 }) +) +``` + +The default applies when both keys are absent, but not when only one key is present. Defaults on individual children do not count as provider input: + +```ts +const listener = Config.all({ + host: Config.String("host"), + port: Config.Int("port").pipe(Config.withDefault(8080)) +}).pipe(Config.option) +``` + +`listener` is `None` when both keys are absent, `Some` when `host` is present, and fails when only `port` is present. + +### How Absence Is Decided + +Configuration evaluation distinguishes three situations before producing the public `Effect`: + +1. **Resolved** — decoding succeeded. The value may legitimately be `undefined`, `{}`, or `[]`. +2. **Absent** — the config could not resolve and no relevant provider representation was found. +3. **Failed** — the provider failed, input was invalid, or a combined config was only partially supplied. + +`Config.withDefault` and `Config.option` handle only the second case. `Config.orElse` handles both absence and failures. + +At the lookup path of a `Config.schema`, an unavailable representation is passed to the schema decoder as `undefined`. This includes a missing node and a present node whose shape cannot represent the schema: for example, an array node cannot represent a struct. Missing properties inside an object remain omitted so the schema's property semantics still apply. The decoder runs before absence is decided. Consequently: + +- `Config.schema(Schema.UndefinedOr(Schema.String), "key")` succeeds with `undefined` when `key` is absent. +- An explicitly present empty object can decode to `{}` when the schema permits it. +- Wrapping either successful result in `Config.option` produces `Some`, because decoding succeeded. +- If the schema rejects `undefined` and no relevant representation was found, `Config.withDefault` uses its fallback and `Config.option` returns `None`. +- Present invalid data and partially supplied `Config.all` groups are failures. +- `SourceError` is always a failure and is never replaced by `withDefault` or `option`. + +`Config.schema(Schema.Struct(...))` and `Config.all(...)` share the same decoder-first rule but describe different lookup models. A struct schema owns one structured input, so an explicitly present empty object is relevant input and its required fields are validated. `Config.all` evaluates independent child configs; an empty parent object does not make the group present when every child is absent. Field optionality in `Config.all` is expressed on each child with `Config.option` or `Config.withDefault`. + +### How Schema Input Is Loaded + +`Config.schema` converts its codec to the canonical `Schema.StringTree` codec and uses the encoded AST to decide which provider representation to load: + +- A scalar schema reads the node's scalar value. A record or array node may have a co-located scalar value in addition to its children. +- A struct loads its declared properties and omits children that the provider does not contain. A record schema also loads advertised keys that match its index signature. +- An array or tuple loads its indexed children. Missing positions are represented as `undefined` so the element schema decides whether they are valid. +- A union whose members require different shapes materializes each member independently. Schema then applies the union's declared order or `oneOf` rule and any checks attached to the original union. + +This keeps the provider responsible only for reporting what exists. Schema remains responsible for deciding whether the loaded representation is valid. + +Plain `Schema.Array` and `Schema.Record` accept structural provider input only. Use `Config.Array(Schema.String, "items")` for separated scalar input such as `"a,b,c"`, and `Config.Record(Schema.String, Schema.String, "items")` for input such as `"a=1,b=2"`. Both constructors also accept structural input. + +The canonical `StringTree` encoding must expose a concrete scalar, object, array, or union shape. `Config.schema` rejects opaque encodings such as `Schema.Any`, `Schema.Unknown`, `Schema.ObjectKeyword`, `Schema.Json`, and `Schema.MutableJson` synchronously when the config is constructed, including when they are nested in another schema. Suspended recursive schemas and declarations such as `Schema.URL` remain supported when their eventual canonical encoding has a concrete shape. To read arbitrary JSON from one scalar provider value, use `Schema.fromJsonString(Schema.Json)`. + ### Custom Config Logic -There is no public low-level `Config.make` constructor. For custom validation or transformation, start from one of the public constructors or `Config.schema`, then use `Config.map`, `Config.mapOrFail`, `Config.all`, `Config.orElse`, or `Config.withDefault`. +There is no public low-level `Config.make` constructor. For custom validation or transformation, start from one of the public constructors or `Config.schema`, then use `Config.map`, `Config.mapEffect`, `Config.all`, `Config.orElse`, or `Config.withDefault`. If you need custom lookup behavior for a new backing source, implement a `ConfigProvider` with `ConfigProvider.make` instead. -## Config Schemas +## Array and Record Constructors + +`Config.Array` accepts `{ separator? }`, while `Config.Record` accepts `{ separator?, keyValueSeparator? }`. Pass the options object directly to read the provider root, or place a string or `ConfigProvider.Path` before it to select a path: -For reusable codecs you can pass directly to `Config.schema`: +```ts +import { Config, Schema } from "effect" -| Schema | Type | Notes | -| --------------------------- | -------------- | ------------------------------------------ | -| `Config.Boolean` | `boolean` | Decodes `true/false/yes/no/on/off/1/0/y/n` | -| `Schema.DurationFromString` | `Duration` | Decodes human-readable duration strings | -| `Config.Port` | `number` | Integer in 1–65535 | -| `Config.LogLevel` | `string` | One of the standard log level literals | -| `Config.Record(key, value)` | `Record` | Also parses flat `"k1=v1,k2=v2"` strings | +const rootValues = Config.Array(Schema.String, { separator: ";" }) +const namedValues = Config.Array(Schema.String, "VALUES", { separator: ";" }) + +const rootHeaders = Config.Record(Schema.String, Schema.String, { keyValueSeparator: ":" }) +const namedHeaders = Config.Record(Schema.String, Schema.String, "HEADERS", { keyValueSeparator: ":" }) +``` ## ConfigProvider Sources +The concrete built-in source providers `fromEnv`, `fromDotEnvContents`, `fromDotEnv`, `fromUnknown`, and `fromDir` treat literal empty strings as missing values by default when they are loaded as values. Container discovery still reflects the source structure, so a key or file can appear in a `Record` or `Array` node and then load as missing. Pass `{ preserveEmptyStrings: true }` to preserve empty strings as explicit values. + +At the raw provider interface, `load(path)` succeeds with `Node | undefined`: a +`Node` means the path exists, while `undefined` means it does not. A +`SourceError` represents a failure to read the source and remains in the Effect +error channel. + +Lookup-level `undefined` is distinct from the `value` field of a found `Record` +or `Array` node. Such a container can exist while +`node.value === undefined`, which means that it has children but no co-located +scalar value. + ### `ConfigProvider.fromEnv` — Environment Variables (Default) This is the default provider. Path segments are joined with `_` for lookup. @@ -254,7 +342,7 @@ const provider = ConfigProvider.fromEnv({ } }) -const host = Config.string("HOST").parse( +const host = Config.String("HOST").parse( provider.pipe(ConfigProvider.nested("DATABASE")) ) @@ -358,7 +446,7 @@ const program = Effect.gen(function*() { Requires `Path` and `FileSystem` in the Effect context. -Missing files and directories return `undefined`, so fallback providers can handle the path. Other file-system failures are reported as `SourceError`. +Missing files and directories return `undefined`, so fallback providers can handle the path. Empty files also return `undefined` by default after trimming their contents, while directory listings still report the file names present on disk; pass `{ preserveEmptyStrings: true }` to preserve them as `Value("")`. Other file-system failures are reported as `SourceError`. ### `ConfigProvider.make` — Custom Sources @@ -381,7 +469,10 @@ const provider = ConfigProvider.make((path) => { }) ``` -Return `undefined` for "not found". Only fail with `SourceError` for actual I/O errors. +Return `undefined` for "not found" and a `Node` for a path that exists. Only +fail with `SourceError` when the source itself cannot be read. Providers created +with `make` automatically support the path-transformation behavior used by +`mapInput`, `constantCase`, and `nested`. ## ConfigProvider Combinators @@ -510,6 +601,14 @@ const upper = ConfigProvider.mapInput( ) ``` +Path transformation is a capability of the `ConfigProvider` interface. The +exported `ConfigProvider.mapInput` combinator delegates to that capability, +rather than passing an extra transformation argument to `load`. This keeps +ordinary lookup fixed as `load(path)` and allows composite providers to +preserve their own behavior without exposing representation state. Custom +source providers should normally be constructed with `ConfigProvider.make`, +which implements this capability automatically. + `mapInput` runs after earlier provider transformations, so it sees the full path produced so far: ```ts @@ -543,7 +642,7 @@ const TestLayer = ConfigProvider.layer( ) const program = Effect.gen(function*() { - const port = yield* Config.int("port") + const port = yield* Config.Int("port") return port }) @@ -578,7 +677,7 @@ import { Config, ConfigProvider, Effect } from "effect" const provider = ConfigProvider.fromUnknown({ HOST: "localhost" }) const program = Effect.gen(function*() { - const host = yield* Config.string("HOST") + const host = yield* Config.String("HOST") return host }).pipe( Effect.provideService(ConfigProvider.ConfigProvider, provider) @@ -591,17 +690,19 @@ const program = Effect.gen(function*() { ```ts const program = Effect.gen(function*() { - const host = yield* Config.string("HOST") + const host = yield* Config.String("HOST") }) ``` 2. **Call `.parse(provider)` directly** — useful for testing or when you have a specific provider: ```ts - const host = Config.string("HOST") + const host = Config.String("HOST") const result = Effect.runSync(host.parse(provider)) ``` + The method accepts only the provider. Use `Config.nested` or the path argument of `Config.schema` to scope lookups. + ## Error Handling Config operations fail with `ConfigError`, which wraps either: @@ -614,7 +715,7 @@ Check `error.cause._tag` to distinguish: ```ts import { Config, ConfigProvider, Effect } from "effect" -const program = Config.int("PORT").parse( +const program = Config.Int("PORT").parse( ConfigProvider.fromUnknown({ PORT: "not-a-number" }) ).pipe( Effect.tapError((error) => @@ -629,7 +730,7 @@ const program = Config.int("PORT").parse( ) ``` -**Important**: `Config.withDefault` and `Config.option` only recover from missing-data errors. Validation errors still propagate. +**Important**: `Config.withDefault` and `Config.option` recover only from semantic absence. They do not classify `SchemaIssue` values as “missing.” Validation errors, source failures, and partially supplied groups still propagate. ## Practical Example: Web Server Config @@ -657,7 +758,7 @@ const DbConfig = Config.schema( const AppConfig = Config.all({ server: ServerConfig, db: DbConfig, - debug: Config.boolean("debug").pipe(Config.withDefault(false)) + debug: Config.Boolean("debug").pipe(Config.withDefault(false)) }) // In production, just yield it — reads from process.env diff --git a/packages/effect/HTTPAPI.md b/packages/effect/HTTPAPI.md index a693101df..6567439ec 100644 --- a/packages/effect/HTTPAPI.md +++ b/packages/effect/HTTPAPI.md @@ -61,7 +61,7 @@ const Api = HttpApi.make("MyApi").add( ) // Implementation -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Greetings", // The name of the group to handle (handlers) => @@ -72,14 +72,14 @@ const GroupLive = HttpApiBuilder.group( ) // Server -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) // Launch -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` After running the code, open a browser and navigate to http://localhost:3000. The server will respond with: @@ -97,9 +97,9 @@ Adding a documentation layer gives you an interactive page where you (and your A To include Scalar in your server setup, provide the `HttpApiScalar.layer` when configuring the server. ```ts -const ApiLive = HttpApiBuilder.layer(Api).pipe( +const ApiLayer = HttpApiBuilder.layer(Api).pipe( // Provide the Scalar layer so clients can access auto-generated docs - Layer.provide(GroupLive), + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) @@ -115,8 +115,8 @@ This URL will display the Scalar documentation, allowing you to explore the API' To include Swagger in your server setup, provide the `HttpApiSwagger.layer` when configuring the server. ```ts -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), // Provide the Swagger layer so clients can access auto-generated docs Layer.provide(HttpApiSwagger.layer(Api)), // "/docs" is the default path. // or Layer.provide(HttpApiScalar.layer(Api)), @@ -167,19 +167,19 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Greetings", (handlers) => handlers.handle("hello", () => Effect.succeed("Hello, World!")) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // Create a program that derives and uses the client const program = Effect.gen(function*() { @@ -356,7 +356,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -366,20 +366,22 @@ const GroupLive = HttpApiBuilder.group( )) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## POST Use `HttpApiEndpoint.post` to create an endpoint that accepts data. The `payload` option describes the shape of the request body, and `success` describes what the endpoint returns. +When a group has more than one endpoint, use `.handleAll` to register all the handlers in a single call, keyed by endpoint name. Single endpoints can still be registered one at a time with `.handle`. + **Example** (Defining a POST Endpoint with Payload and Success Schemas) ```ts @@ -417,35 +419,36 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUsers", () => + handlers.handleAll({ + getUsers: () => Effect.succeed( [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] - )) - .handle("getUser", (ctx) => { + ), + getUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("createUser", (ctx) => { + }, + createUser: (ctx) => { // ┌─── User // ▼ const user = ctx.payload return Effect.succeed(user) - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## DELETE @@ -493,37 +496,38 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUsers", () => + handlers.handleAll({ + getUsers: () => Effect.succeed( [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] - )) - .handle("getUser", (ctx) => { + ), + getUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("createUser", (ctx) => { + }, + createUser: (ctx) => { const user = ctx.payload return Effect.succeed(user) - }) - .handle("deleteUser", (ctx) => { + }, + deleteUser: (ctx) => { const id = ctx.params.id return Effect.log(`Deleting user ${id}`) - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## PATCH @@ -582,41 +586,42 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUsers", () => + handlers.handleAll({ + getUsers: () => Effect.succeed( [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] - )) - .handle("getUser", (ctx) => { + ), + getUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("createUser", (ctx) => { + }, + createUser: (ctx) => { const user = ctx.payload return Effect.succeed(user) - }) - .handle("deleteUser", (ctx) => { + }, + deleteUser: (ctx) => { const id = ctx.params.id return Effect.log(`Deleting user ${id}`) - }) - .handle("updateUser", (ctx) => { + }, + updateUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## Parameters @@ -656,31 +661,32 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUsers", () => + handlers.handleAll({ + getUsers: () => Effect.succeed( [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] - )) - .handle("getUser", (ctx) => { + ), + getUser: (ctx) => { // ┌─── number // ▼ const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## Catch-All Endpoints @@ -741,44 +747,45 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUsers", () => + handlers.handleAll({ + getUsers: () => Effect.succeed( [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] - )) - .handle("getUser", (ctx) => { + ), + getUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("createUser", (ctx) => { + }, + createUser: (ctx) => { const user = ctx.payload return Effect.succeed(user) - }) - .handle("deleteUser", (ctx) => { + }, + deleteUser: (ctx) => { const id = ctx.params.id return Effect.log(`Deleting user ${id}`) - }) - .handle("updateUser", (ctx) => { + }, + updateUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("catchAll", () => { + }, + catchAll: () => { return Effect.succeed("Not found") - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` > [!IMPORTANT] @@ -819,23 +826,24 @@ const Api = HttpApi.make("MyApi") // Prefix for the entire API .prefix("/apiPrefix") -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => - handlers - .handle("endpointA", () => Effect.succeed("Endpoint A")) - .handle("endpointB", () => Effect.succeed("Endpoint B")) + handlers.handleAll({ + endpointA: () => Effect.succeed("Endpoint A"), + endpointB: () => Effect.succeed("Endpoint B") + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test this endpoint using a GET request. For example: @@ -884,7 +892,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -898,14 +906,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` #### Defining an Array of Values for a Query Parameter @@ -939,7 +947,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -952,14 +960,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test this endpoint by passing an array of values in the query string. For example: @@ -1013,7 +1021,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1023,14 +1031,14 @@ const GroupLive = HttpApiBuilder.group( )) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test the endpoint by sending the headers: @@ -1078,7 +1086,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1092,14 +1100,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test this endpoint by sending a multipart request with a file upload. For example: @@ -1150,7 +1158,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1161,14 +1169,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test this endpoint using a URL-encoded request body. For example: @@ -1203,7 +1211,7 @@ const Api = HttpApi.make("MyApi").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Greetings", (handlers) => @@ -1220,13 +1228,13 @@ const GroupLive = HttpApiBuilder.group( ) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## Validating Request Cookies @@ -1278,7 +1286,7 @@ const Api = HttpApi.make("api").add( .middleware(Auth) ) -const AuthLive = Layer.succeed( +const AuthLayer = Layer.succeed( Auth, { session: (effect, opts) => @@ -1296,7 +1304,7 @@ const AuthLive = Layer.succeed( } ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => @@ -1305,16 +1313,16 @@ const GroupLive = HttpApiBuilder.group( const user = yield* CurrentUser return { id: user.id } })) -).pipe(Layer.provide(AuthLive)) +).pipe(Layer.provide(AuthLayer)) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // Valid session: // curl "http://localhost:3000/me" --cookie "session=valid-session" @@ -1344,7 +1352,7 @@ const Api = HttpApi.make("api").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => @@ -1354,13 +1362,13 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // curl "http://localhost:3000/me" --cookie "lang=it" // "Language: it" @@ -1368,13 +1376,15 @@ Layer.launch(ApiLive).pipe(NodeRuntime.runMain) ## Streaming Requests -To receive large or continuous data from the client, define the payload as a `Uint8Array` and pipe it through `HttpApiSchema.asUint8Array()`. The handler receives the raw bytes, which you can decode as needed. +To receive large or continuous data from the client, register the endpoint with `.handleRaw`, which opts out of automatic payload decoding and exposes the raw `HttpServerRequest`. The request body is then available as a `Stream` of `Uint8Array` chunks through `request.stream`, so the handler can consume it incrementally instead of buffering it in memory. + +The payload schema still describes the endpoint in the generated documentation, but with `.handleRaw` the handler decides how the body is consumed. Note that the declared content type is no longer enforced at runtime: requests with a different `Content-Type` header are not rejected with `415`, so any such validation is up to the handler. **Example** (Handling Streaming Requests) ```ts import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" -import { Effect, Layer, Schema } from "effect" +import { Effect, Layer, Schema, Stream } from "effect" import { HttpRouter } from "effect/unstable/http" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" import { createServer } from "node:http" @@ -1391,33 +1401,37 @@ const Api = HttpApi.make("myApi").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => - handlers.handle( - "acceptStream", - (ctx) => { - // Decode the incoming binary data into a string - return Effect.succeed(new TextDecoder().decode(ctx.payload)) - } - ) + // Use `handleRaw` to opt out of payload decoding and access the raw request + handlers.handleRaw("acceptStream", (ctx) => + // Consume the request body as a stream of Uint8Array chunks + ctx.request.stream.pipe( + // Fold over the chunks as they arrive, without buffering the body + Stream.runFold(() => 0, (total, chunk) => total + chunk.length), + Effect.map((total) => `received ${total} bytes`), + // `request.stream` fails with `HttpServerError`, which the endpoint + // does not declare, so treat it as a defect + Effect.orDie + )) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test the streaming request using `curl` or any tool that supports sending binary data. For example: ```sh -echo "abc" | curl -X POST 'http://localhost:3000/stream' --data-binary @- -H "Content-Type: application/octet-stream" -# Output: abc +printf 'abc' | curl -X POST 'http://localhost:3000/stream' --data-binary @- -H "Content-Type: application/octet-stream" +# Output: "received 3 bytes" ``` # Response @@ -1458,7 +1472,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1470,14 +1484,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## Changing the Response Encoding @@ -1516,7 +1530,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1526,14 +1540,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test this endpoint using a GET request. For example: @@ -1550,7 +1564,170 @@ The following encodings are supported: ## Setting Response Headers -To add custom headers to the outgoing response, call `HttpEffect.appendPreResponseHandler` inside your handler. The callback receives the request and response objects and must return the updated response. +Response headers can be declared in the endpoint's schemas, so they are type-checked on the server, rendered in the OpenAPI documentation, and decoded by the derived client. Two mechanisms are available: + +- `HttpApiSchema.WithHeaders(schema, headers)` wraps a response schema together with a headers schema. Handlers return the body and headers as a pair. Recommended for success responses, including streams. +- `HttpApiSchema.encodeToWithHeaders` folds headers into an opaque domain type such as an error class, so handlers keep working with plain domain values. + +Only one response schema carrying headers may be declared for each status, though plain responses with different content types may share that status. + +For headers that are not part of the API contract, `HttpEffect.appendPreResponseHandler` remains available as an untyped escape hatch. + +### Declaring Response Headers with WithHeaders + +Wrap the success schema with `HttpApiSchema.WithHeaders(schema, headers)`. The headers argument accepts a fields shorthand (as below) or any schema, mirroring the request-side `headers` option. The handler then returns a value built with `HttpApiSchema.withHeaders({ body, headers })`. + +**Example** (Declaring a Response Header on a Success Schema) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const Api = HttpApi.make("MyApi").add( + HttpApiGroup.make("Users").add( + HttpApiEndpoint.get("getUsers", "/users", { + // Wrap the success schema with a response headers schema + success: HttpApiSchema.WithHeaders(Schema.Array(User), { + "x-total-count": Schema.Int + }) + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handle("getUsers", () => + // Return the body together with the declared headers + Effect.succeed(HttpApiSchema.withHeaders({ + body: [{ id: 1, name: "John" }], + headers: { "x-total-count": 1 } + }))) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) + +// curl -v "http://localhost:3000/users" 2>&1 | grep -i "x-total-count" +// < x-total-count: 1 +``` + +The derived client detects the wrapper and returns the same shape, with the headers decoded through the headers schema: + +```ts +const users = yield * client.Users.getUsers() +users.body // => [{ id: 1, name: "John" }] +users.headers // => { "x-total-count": 1 } +``` + +Things to know: + +- Header values are converted to strings at the HTTP boundary, the same way as request headers, params, and query. `Schema.Int` goes out as `"1"` and decodes back to `1` on the client. `undefined` values are omitted from the response. +- Status and encoding annotations resolve from the wrapper first, then fall through to the inner schema, so `HttpApiSchema.WithHeaders(User.pipe(HttpApiSchema.status(201)), ...)` responds with `201`. +- Declared headers are applied after the body is encoded and override headers set by the encoding on collision, including `content-type`. +- Stream success schemas (`HttpApiSchema.StreamSse`, `HttpApiSchema.StreamUint8Array`) can be wrapped too. Headers are encoded before the response starts streaming, and the client resolves to a value whose `body` is the stream. +- `WithHeaders` is also allowed on error schemas, in which case the handler fails with the wrapped value. For errors, `encodeToWithHeaders` (below) is usually more convenient because handlers can fail with the domain error directly. + +### Folding Headers into Domain Types with encodeToWithHeaders + +`HttpApiSchema.encodeToWithHeaders` encodes a schema as a `{ body, headers }` pair while its Type stays unchanged. This lets an error class carry data that travels in a response header: handlers fail with plain error instances, and the client receives the same class with the header folded back in. + +The body schema is authoritative for everything wire-level: status, content type, and response encoding resolve from the body schema's annotations. A status annotation on the source schema stops mattering once wrapped, so spell the status on the body — `HttpApiSchema.Empty(404)` declares an empty body with status 404. + +**Example** (Returning an Error With a Response Header) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schema } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +class UserNotFound extends Schema.TaggedError()("UserNotFound", { + userId: Schema.Int +}) {} + +const UserNotFoundWithHeaders = UserNotFound.pipe( + HttpApiSchema.encodeToWithHeaders({ + // The body schema is authoritative for status and content type + body: HttpApiSchema.Empty(404), + headers: { + "x-user-id": Schema.Int + } + }, { + // Pure mappings between the domain type and the { body, headers } pair + decode: ({ headers }) => new UserNotFound({ userId: headers["x-user-id"] }), + encode: (error) => ({ + headers: { "x-user-id": error.userId }, + body: undefined + }) + }) +) + +const User = Schema.Struct({ + id: Schema.Int, + name: Schema.String +}) + +const Api = HttpApi.make("MyApi").add( + HttpApiGroup.make("Users").add( + HttpApiEndpoint.get("getUser", "/user/:id", { + params: { + id: Schema.Int + }, + success: User, + error: UserNotFoundWithHeaders + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "Users", + (handlers) => + handlers.handle("getUser", (ctx) => { + const id = ctx.params.id + if (id === 1) { + // Fail with the plain error instance + return Effect.fail(new UserNotFound({ userId: id })) + } + return Effect.succeed({ id, name: `User ${id}` }) + }) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) + +// curl -v "http://localhost:3000/user/1" 2>&1 | grep -i "x-user-id" +// < x-user-id: 1 +``` + +The `decode`/`encode` mappings are pure total functions: validation lives in the body and headers schemas, the mappings only reshape valid data. The error channel is unchanged — a client calling this endpoint fails with a `UserNotFound` instance whose `userId` was decoded from the header. + +`encodeToWithHeaders` also works on custom success types, but avoid burying stream schemas in it; wrap streams with `WithHeaders` instead so the generated client keeps the stream's error channel. + +### Untyped Response Headers + +For headers that should not appear in the API contract, call `HttpEffect.appendPreResponseHandler` inside your handler. The callback receives the request and response objects and must return the updated response. These headers bypass the schemas, the OpenAPI documentation, and the derived client. **Example** (Adding a Custom Response Header) @@ -1569,7 +1746,7 @@ const Api = HttpApi.make("api").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => @@ -1582,13 +1759,13 @@ const GroupLive = HttpApiBuilder.group( })) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // curl -v "http://localhost:3000/hello" 2>&1 | grep -i "x-custom" // < x-custom: hello @@ -1615,7 +1792,7 @@ const Api = HttpApi.make("api").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => @@ -1632,13 +1809,13 @@ const GroupLive = HttpApiBuilder.group( })) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // curl -v "http://localhost:3000/hello" 2>&1 | grep -i "set-cookie" // < set-cookie: my-cookie=my-value; Path=/; HttpOnly; Secure @@ -1666,51 +1843,59 @@ const Api = HttpApi.make("MyApi").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => - handlers - .handle("newPage", () => Effect.succeed("You are on /new")) - .handle("oldPage", () => + handlers.handleAll({ + newPage: () => Effect.succeed("You are on /new"), + oldPage: () => Effect.succeed( HttpServerResponse.redirect("/new", { status: 302 }) - )) + ) + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // curl "http://localhost:3000/old" -L ``` ## Streaming Responses -To stream data to the client over time, return an `HttpServerResponse.stream` from the handler. The stream emits chunks at whatever pace you choose. +To stream data to the client over time, declare the success schema with one of the streaming constructors: + +- `HttpApiSchema.StreamUint8Array()` streams raw bytes (default content type: `application/octet-stream`) +- `HttpApiSchema.StreamSse(options)` streams typed Server-Sent Events (default content type: `text/event-stream`) -**Example** (Implementing a Streaming Endpoint) +With a streaming success schema, the handler returns a `Stream` directly, and the derived client resolves to a typed `Stream` on the consuming side. The stream declaration is also rendered in the OpenAPI documentation. + +### Streaming Raw Bytes + +`HttpApiSchema.StreamUint8Array` declares a binary streaming response. The handler returns a `Stream` that emits chunks at whatever pace you choose. + +**Example** (Implementing a Binary Streaming Endpoint) ```ts import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" -import { Effect, Layer, Schedule, Schema, Stream } from "effect" -import { HttpRouter, HttpServerResponse } from "effect/unstable/http" +import { Effect, Layer, Schedule, Stream } from "effect" +import { HttpRouter } from "effect/unstable/http" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" import { createServer } from "node:http" const Api = HttpApi.make("myApi").add( HttpApiGroup.make("group").add( HttpApiEndpoint.get("getStream", "/stream", { - success: Schema.String.pipe( - HttpApiSchema.asText({ - contentType: "application/octet-stream" - }) - ) + // Declare a streaming success response + // (default content type: application/octet-stream) + success: HttpApiSchema.StreamUint8Array() }) ) ) @@ -1721,23 +1906,24 @@ const stream = Stream.make("a", "b", "c").pipe( Stream.map((s) => new TextEncoder().encode(s)) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => handlers.handle( "getStream", - () => Effect.succeed(HttpServerResponse.stream(stream)) + // Return the stream directly from the handler + () => Effect.succeed(stream) ) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test the streaming response using `curl` or any similar HTTP client that supports streaming: @@ -1748,6 +1934,103 @@ curl 'http://localhost:3000/stream' --no-buffer The response will stream data (`a`, `b`, `c`) with a 500ms interval between each item. +### Streaming Server-Sent Events + +`HttpApiSchema.StreamSse` declares a Server-Sent Events response. Pass a `data` schema to stream plain values: each stream element is JSON-encoded into the `data` field of an SSE event on the way out, and the derived client decodes it back to the value. + +**Example** (Implementing an SSE Endpoint) + +```ts +import { NodeHttpServer, NodeRuntime } from "@effect/platform-node" +import { Effect, Layer, Schedule, Schema, Stream } from "effect" +import { HttpRouter } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" +import { createServer } from "node:http" + +const Message = Schema.Struct({ + text: Schema.String +}) + +const Api = HttpApi.make("myApi").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("getEvents", "/events", { + // Each stream element is JSON-encoded into the `data` field of an SSE + // event (default content type: text/event-stream) + success: HttpApiSchema.StreamSse({ data: Message }) + }) + ) +) + +const GroupLayer = HttpApiBuilder.group( + Api, + "group", + (handlers) => + handlers.handle("getEvents", () => + Effect.succeed( + Stream.make({ text: "one" }, { text: "two" }, { text: "three" }).pipe( + Stream.schedule(Schedule.spaced("500 millis")) + ) + )) +) + +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + HttpRouter.serve, + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) +) + +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) + +// curl 'http://localhost:3000/events' --no-buffer +// data: {"text":"one"} +// +// data: {"text":"two"} +// +// data: {"text":"three"} +``` + +To control the full event shape instead, pass an `events` schema whose Encoded side matches the SSE wire format: a required `data` string plus optional `id` and `event` fields. The handler then emits whole events. + +### Consuming Streams with the Derived Client + +For both constructors, calling the endpoint on the derived client resolves to a typed `Stream`: + +```ts +const events = yield * client.group.getEvents() +// events: Stream<{ readonly text: string }, ...> + +yield * Stream.runForEach(events, (message) => Console.log(message.text)) +``` + +### Typed Stream Failures + +`HttpApiSchema.StreamSse` accepts an `error` schema describing typed stream failures. Since the HTTP status is already sent when the stream fails, the failure cannot travel as a normal error response. Instead, the server encodes the full `Cause` into a reserved `effect/httpapi/stream/failure` event, and the derived client decodes that event and fails the stream with the original cause. + +**Example** (Declaring a Typed Stream Failure) + +```ts +class StreamError extends Schema.TaggedError()("StreamError", { + reason: Schema.String +}) {} + +const Api = HttpApi.make("myApi").add( + HttpApiGroup.make("group").add( + HttpApiEndpoint.get("getEvents", "/events", { + // The handler may return a Stream that fails with StreamError, + // and the client's stream fails with StreamError too + success: HttpApiSchema.StreamSse({ data: Message, error: StreamError }) + }) + ) +) +``` + +Things to know: + +- Set a custom status or content type with `HttpApiSchema.status(code)` and the `contentType` option, for example `HttpApiSchema.status(206)(HttpApiSchema.StreamUint8Array({ contentType: "application/custom-bytes" }))`. +- Wrap a stream schema with `HttpApiSchema.WithHeaders` to declare typed response headers (see [Setting Response Headers](#setting-response-headers)). The handler returns `HttpApiSchema.withHeaders({ body, headers })` with the stream as `body`, and the client resolves to the same shape. +- An endpoint may declare at most one streaming success schema, `HEAD` endpoints cannot declare one, and streaming schemas are not supported in error responses. +- The `effect/httpapi/stream/failure` event name is reserved: `events` schemas may not declare it, and the client treats such an event as a stream failure only when its `data` decodes to a `Cause`. + # Error Handling ## Adding Custom Error Responses @@ -1806,35 +2089,36 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUser", (ctx) => { + handlers.handleAll({ + getUser: (ctx) => { const id = ctx.params.id if (id === 1) { return Effect.fail(UserNotFound.make({ message: "User not found" })) } return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("deleteUser", (ctx) => { + }, + deleteUser: (ctx) => { const id = ctx.params.id if (id === 1) { return Effect.fail(UserNotFound.make({ message: "User not found" })) } return Effect.succeed(void 0) - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` You can test these endpoints. For example: @@ -1890,7 +2174,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1904,14 +2188,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` | Name | Status | Description | @@ -1974,7 +2258,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -1988,14 +2272,14 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` ## Customizing Schema Error Responses @@ -2024,7 +2308,7 @@ import { import { createServer } from "node:http" // Define a custom error for validation failures -class ValidationError extends Schema.TaggedErrorClass()( +class ValidationError extends Schema.TaggedError()( "ValidationError", { message: Schema.String @@ -2040,7 +2324,7 @@ class SchemaErrorHandler extends HttpApiMiddleware.Service() ) {} // Implement the middleware layer -const SchemaErrorHandlerLive = HttpApiMiddleware.layerSchemaErrorTransform( +const SchemaErrorHandlerLayer = HttpApiMiddleware.layerSchemaErrorTransform( SchemaErrorHandler, (schemaError) => Effect.fail( @@ -2068,21 +2352,21 @@ const Api = HttpApi.make("MyApi").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => handlers.handle("getUser", (ctx) => Effect.succeed({ id: ctx.query.id, name: `User ${ctx.query.id}` })) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), - Layer.provide(SchemaErrorHandlerLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), + Layer.provide(SchemaErrorHandlerLayer), Layer.provide(HttpApiScalar.layer(Api)), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // Test: // curl "http://localhost:3000/user?id=1" # 200 OK @@ -2144,7 +2428,7 @@ const Api = HttpApi.make("api").add( // Or apply the middleware to the entire group .middleware(Logger) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => @@ -2154,7 +2438,7 @@ const GroupLive = HttpApiBuilder.group( }) ) -const LoggerLive = Layer.effect( +const LoggerLayer = Layer.effect( Logger, Effect.gen(function*() { yield* Effect.log("creating Logger middleware") @@ -2168,20 +2452,77 @@ const LoggerLive = Layer.effect( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), - Layer.provide(LoggerLive), + Layer.provide(LoggerLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // Test this with this curl command: // curl "http://localhost:3000/user/1" ``` +## Interdependent Middleware + +Middleware can depend on services provided by other middleware. Declare the dependency with `requires` and declare the produced service with `provides`. + +When you attach interdependent middleware to an endpoint, group, or API, the middleware that uses `requires` must come **BEFORE** the middleware that provides that service. The earlier middleware wraps the later middleware, so it can consume the service that the later middleware adds to the request effect. + +**Example** (Middleware that consumes another middleware's output) + +```ts +import { Context, Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/unstable/httpapi" + +class AuthInfo extends Context.Service()("AuthInfo") {} + +class LoadAuth extends HttpApiMiddleware.Service()("LoadAuth") {} + +class RequireAuth extends HttpApiMiddleware.Service()("RequireAuth") {} + +const Api = HttpApi.make("api").add( + HttpApiGroup.make("users").add( + HttpApiEndpoint.get("me", "/me", { + success: Schema.String + }) + // RequireAuth reads AuthInfo, so it must appear first. + .middleware(RequireAuth) + // LoadAuth provides AuthInfo to middleware that appears before it. + .middleware(LoadAuth) + ) +) + +const LoadAuthLayer = Layer.effect( + LoadAuth, + Effect.succeed((effect) => + Effect.provideService(effect, AuthInfo, { + userId: "user-1" + }) + ) +) + +const RequireAuthLayer = Layer.effect( + RequireAuth, + Effect.succeed( + Effect.fnUntraced(function*(effect) { + const authInfo = yield* AuthInfo + yield* Effect.log(`authenticated user ${authInfo.userId}`) + return yield* effect + }) + ) +) +``` + # Security The `HttpApiSecurity` module lets you declare how an endpoint is protected. These declarations show up in the generated OpenAPI spec and are enforced at runtime through middleware. @@ -2206,7 +2547,7 @@ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSecur class User extends Schema.Class("User")({ id: Schema.Finite }) {} // Define a schema for the "Unauthorized" error -class Unauthorized extends Schema.TaggedErrorClass()( +class Unauthorized extends Schema.TaggedError()( "Unauthorized", {}, // Specify the HTTP status code for unauthorized errors @@ -2265,7 +2606,7 @@ import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" class User extends Schema.Class("User")({ id: Schema.Finite }) {} -class Unauthorized extends Schema.TaggedErrorClass()( +class Unauthorized extends Schema.TaggedError()( "Unauthorized", {}, // Specify the HTTP status code for unauthorized errors @@ -2286,7 +2627,7 @@ class Authorization extends HttpApiMiddleware.Service("User")({ id: Schema.Finite }) {} -class Unauthorized extends Schema.TaggedErrorClass()( +class Unauthorized extends Schema.TaggedError()( "Unauthorized", {}, // Specify the HTTP status code for unauthorized errors @@ -2376,7 +2717,7 @@ const security = HttpApiSecurity.apiKey({ key: "token" }) -const UsersApiLive = HttpApiBuilder.group(Api, "Users", (handlers) => +const UsersApiLayer = HttpApiBuilder.group(Api, "Users", (handlers) => handlers.handle("login", () => // Set the security cookie with a redacted value HttpApiBuilder.securitySetCookie(security, Redacted.make("keep me secret")))) @@ -2418,7 +2759,7 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => @@ -2433,8 +2774,8 @@ const GroupLive = HttpApiBuilder.group( }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), Layer.provide( Layer.succeed(UsersRepository, { @@ -2445,7 +2786,7 @@ const ApiLive = HttpApiBuilder.layer(Api).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` # OpenAPI Documentation @@ -2504,41 +2845,42 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Users", (handlers) => - handlers - .handle("getUsers", () => + handlers.handleAll({ + getUsers: () => Effect.succeed( [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }] - )) - .handle("getUser", (ctx) => { + ), + getUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) - .handle("createUser", (ctx) => { + }, + createUser: (ctx) => { const user = ctx.payload return Effect.succeed(user) - }) - .handle("deleteUser", (ctx) => { + }, + deleteUser: (ctx) => { const id = ctx.params.id return Effect.log(`Deleting user ${id}`) - }) - .handle("updateUser", (ctx) => { + }, + updateUser: (ctx) => { const id = ctx.params.id return Effect.succeed({ id, name: `User ${id}` }) - }) + } + }) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), // "/docs" is the default path. HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) ``` After running the server, open your browser and navigate to http://localhost:3000/docs. @@ -3064,19 +3406,19 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Greetings", (handlers) => handlers.handle("hello", () => Effect.succeed("Hello, World!")) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) // Create a program that derives and uses the client const program = Effect.gen(function*() { @@ -3123,19 +3465,19 @@ const Api = HttpApi.make("MyApi") ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "Greetings", (handlers) => handlers.handle("hello", () => Effect.succeed("Hello, World!")) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), HttpRouter.serve, Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) -Layer.launch(ApiLive).pipe(NodeRuntime.runMain) +Layer.launch(ApiLayer).pipe(NodeRuntime.runMain) const program = Effect.gen(function*() { const client = yield* HttpApiClient.make(Api, { @@ -3169,21 +3511,21 @@ const Api = HttpApi.make("myApi").add( ) ) -const GroupLive = HttpApiBuilder.group( +const GroupLayer = HttpApiBuilder.group( Api, "group", (handlers) => handlers.handle("get", () => Effect.succeed("Hello, world!")) ) -const ApiLive = HttpApiBuilder.layer(Api).pipe( - Layer.provide(GroupLive), +const ApiLayer = HttpApiBuilder.layer(Api).pipe( + Layer.provide(GroupLayer), Layer.provide(HttpApiScalar.layer(Api)), Layer.provide(HttpServer.layerServices) ) // Convert the API to a web handler const { dispose, handler } = HttpRouter.toWebHandler( - Layer.mergeAll(ApiLive) + Layer.mergeAll(ApiLayer) ) // Serving the handler using a custom HTTP server diff --git a/packages/effect/MCP.md b/packages/effect/MCP.md index ff30493f8..53f77d24f 100644 --- a/packages/effect/MCP.md +++ b/packages/effect/MCP.md @@ -9,17 +9,16 @@ It's important to understand the architecture of the Effect MCP server. Here is an example of a MCP server implementation: ```typescript -import { NodeRuntime, NodeSink, NodeStream } from "@effect/platform-node" -import { Effect, Layer, Logger } from "effect" -import { Schema } from "effect/schema" -import { McpServer, Tool, Toolkit } from "effect/unstable/ai" +import { NodeRuntime, NodeStdio } from "@effect/platform-node" +import { Effect, Layer, Logger, Schema } from "effect" +import { McpProtocol, McpServer, Tool, Toolkit } from "effect/unstable/ai" // Define a simple tool const DemoTool = Tool.make("DemoTool", { description: "A demo tool that echoes back the input", - parameters: { + parameters: Schema.Struct({ message: Schema.String - }, + }), success: Schema.String }) @@ -58,11 +57,12 @@ const ServerLayer = Layer.mergeAll( McpServer.layerStdio({ name: "Demo MCP Server", version: "1.0.0", - stdin: NodeStream.stdin, - stdout: NodeSink.stdout + protocols: [McpProtocol.v2025_06_18] }) ), - Layer.provide(Logger.layer([Logger.consolePretty({ stderr: true })])) + Layer.provide(NodeStdio.layer), + Layer.provide(Logger.layer([Logger.consolePretty()])), + Layer.provideMerge(Layer.succeed(Logger.LogToStderr, true)) ) Layer.launch(ServerLayer).pipe(NodeRuntime.runMain) @@ -78,8 +78,14 @@ The server exposes three main parts: The part layers are merged into one layer that has a MCP server implementation as dependency. `McpServer.layerStdio` is used to create a standard I/O–based MCP server identified by its name and -version. Because of the layer architecture the server implementation can be easily exchanged with an -HTTP based implementation with `McpServer.layerHttp`. Finally, a logging layer is added with +version. Its ordered, non-empty `protocols` declaration names implemented protocol adapters rather +than arbitrary version strings. This release supports `McpProtocol.v2024_11_05`, +`McpProtocol.v2025_03_26`, and `McpProtocol.v2025_06_18`. The `v2024_11_05` adapter implements that +revision's RPC schemas and stdio framing, including its batch policy. It does not implement the +historical two-endpoint HTTP+SSE transport. `McpServer.layerHttp` instead offers the 2024 RPC schema +through the same single-endpoint HTTP compatibility transport used by the 2025 adapters. Because of +the layer architecture the server implementation can be easily exchanged with this HTTP-based +implementation. Finally, a logging layer is added with `Logger.layer([Logger.consolePretty({ stderr: true })])`, ensuring logs are written to `stderr`. This is essential when using stdio, as any output to `stdout` would interfere with the protocol communication. @@ -92,8 +98,7 @@ resource is defined as a template that specifies its location, behavior, and met parameters, completions, and content generation. ```typescript -import { Effect } from "effect" -import { Schema } from "effect/schema" +import { Effect, Schema } from "effect" import { McpSchema, McpServer } from "effect/unstable/ai" const SimpleResource = McpServer.resource({ @@ -134,8 +139,7 @@ structured, parameterized instructions or messages that the client can send to t generation logic in a declarative way. ```typescript -import { Effect } from "effect" -import { Schema } from "effect/schema" +import { Effect, Schema } from "effect" import { McpServer } from "effect/unstable/ai" const DemoPrompt = McpServer.prompt({ @@ -162,24 +166,23 @@ contract while the actual logic is provided separately through an implementation grouped into toolkits, which can be combined and converted into layers. ```typescript -import { Effect, Layer } from "effect" -import { Schema } from "effect/schema" +import { Effect, Layer, Schema } from "effect" import { McpServer, Tool, Toolkit } from "effect/unstable/ai" const DemoTool = Tool.make("DemoTool", { description: "This is a demo tool for the documentation", - parameters: { + parameters: Schema.Struct({ demoId: Schema.Number, demoName: Schema.String - }, + }), success: Schema.String }) const OtherDemoTool = Tool.make("OtherDemoTool", { description: "Another demo tool", - parameters: { + parameters: Schema.Struct({ value: Schema.Number - }, + }), success: Schema.String }) @@ -210,8 +213,7 @@ defines both the message shown to the user and the expected response schema, ens validated user input. ```typescript -import { Effect } from "effect" -import { Schema } from "effect/schema" +import { Effect, Schema } from "effect" import { McpServer } from "effect/unstable/ai" const DemoElicitation = McpServer.elicit({ @@ -238,7 +240,7 @@ Here's a complete, copy/pastable MCP server example that combines all the concep ```typescript import { NodeRuntime, NodeStdio } from "@effect/platform-node" import { Effect, Layer, Logger, Schema } from "effect" -import { McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai" +import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from "effect/unstable/ai" // Define tools const GreetTool = Tool.make("GreetTool", { @@ -357,11 +359,12 @@ const ServerLayer = Layer.mergeAll( Layer.provide( McpServer.layerStdio({ name: "Demo MCP Server", - version: "1.0.0" + version: "1.0.0", + protocols: [McpProtocol.v2025_06_18] }) ), Layer.provide(NodeStdio.layer), - Layer.provide(Layer.succeed(Logger.LogToStderr)(true)) + Layer.provideMerge(Layer.succeed(Logger.LogToStderr, true)) ) // Run the server diff --git a/packages/effect/README.md b/packages/effect/README.md index 513ae19cc..d702d36b6 100644 --- a/packages/effect/README.md +++ b/packages/effect/README.md @@ -1,43 +1,37 @@ -# `effect` Core Package +# effect -The `effect` package is the heart of the Effect framework, providing robust primitives for managing side effects, ensuring type safety, and supporting concurrency in your TypeScript applications. +Effect is a library for building robust, maintainable, type-safe, and production grade applications in TypeScript. -## Requirements +The `effect` package is the core of the framework. It provides primitives for managing side effects, errors, concurrency, resources, and structured data, alongside a rich standard library. + +## Installation -- **TypeScript 5.4 or Newer:** - Ensure you are using a compatible TypeScript version. +```sh +npm install effect@rc +``` + +## Requirements -- **Strict Type-Checking:** - The `strict` flag must be enabled in your `tsconfig.json`. For example: +- **TypeScript 5.9 or newer.** TypeScript 7 is recommended for the best performance and compatibility with [Effect's TypeScript tooling](https://github.com/Effect-TS/tsgo#installation). +- **Node.js 18 or newer** when running Effect on Node.js. +- **Strict type-checking:** the `strict` flag must be enabled in your `tsconfig.json`: ```json { "compilerOptions": { "strict": true - // ...other options } } ``` -## Installation - -Install the core package using your preferred package manager. For example, with npm: - -```bash -npm install effect -``` - ## Documentation -- **Website:** - For detailed information and usage examples, visit the [Effect website](https://www.effect.website/). - -- **API Reference:** - For a complete API reference of the core package `effect`, see the [Effect API documentation](https://effect-ts.github.io/effect/). +- [Effect website](https://effect.website) +- [API reference](https://effect.website/docs/v4/api/effect) -## Overview of Effect Modules +## Overview -The `effect` package provides a collection of modules designed for functional programming in TypeScript. Below is a brief overview of the core modules: +The `effect` package is a collection of modules. Some of the core ones: | Module | Description | | -------- | -------------------------------------------------------------------------------------------------------------------------- | @@ -50,4 +44,4 @@ The `effect` package provides a collection of modules designed for functional pr | Scope | Manages the lifecycle of resources, ensuring proper acquisition and release. | | Schema | A powerful library for defining, validating, and transforming structured data with type-safe encoding and decoding. | -For a comparison between `effect/Schema` and `zod`, see [Schema vs Zod](https://github.com/Effect-TS/effect/tree/main/packages/effect/schema-vs-zod.md). +In v4, functionality that previously lived in separate packages ships inside `effect` under the `effect/unstable/*` namespaces, including `http`, `httpapi`, `rpc`, `cluster`, `workflow`, `cli`, `ai`, `sql`, and `reactivity`. diff --git a/packages/effect/SCHEMA.md b/packages/effect/SCHEMA.md index 041a31d41..3f8b28edc 100644 --- a/packages/effect/SCHEMA.md +++ b/packages/effect/SCHEMA.md @@ -37,6 +37,39 @@ Use Schema to: 13. **Integrations** — working examples for TanStack Form and Elysia. 14. **Migration from v3** — API mapping from Schema v3 to v4. +## Runtime Performance + +Effect Schema is benchmarked against the public +[`schema-benchmarks`](https://github.com/open-circle/schema-benchmarks) suite. +It exercises a realistic product schema across validation, parsing, error +reporting, schema creation, and codecs. + +The table below compares Effect Schema with the Valibot and Zod cases available +in the same suite. + +Values are microseconds per operation and lower is better. Results vary between +machines, so they are most useful for understanding relative costs. A dash +means that the library does not provide that benchmark. + +| Scenario | Effect Schema | Valibot | Zod 4 | +| ------------------------------------- | ------------: | ---------: | ---------: | +| Create a schema | 118.23 | **40.24** | 318.56 | +| Create a schema and parser | **130.50** | — | — | +| Validate valid data | **5.415** | 5.63 | — | +| Validate invalid data | 1.348 | **0.2431** | — | +| Parse valid data and collect errors | 5.366 | **5.22** | 7.16 | +| Parse invalid data and collect errors | **9.100** | 15.70 | 41.58 | +| Parse valid data and stop early | **5.294** | 5.37 | — | +| Parse invalid data and stop early | 1.352 | **0.2572** | — | +| Standard Schema, valid data | 5.935 | 5.35 | **3.83** | +| Standard Schema, invalid data | **15.203** | 16.51 | 32.85 | +| Standard Schema, valid, stop early | **5.843** | — | — | +| Standard Schema, invalid, stop early | **2.244** | — | — | +| Encode with a typed codec | 0.3420 | — | **0.0405** | +| Decode with a typed codec | 0.3762 | — | **0.0463** | +| Encode unknown input | **0.3472** | — | — | +| Decode unknown input | **0.3637** | — | — | + # Defining Elementary Schemas Schema provides built-in schemas for all common TypeScript types. These schemas represent a single value — like a string or a number — and they are the building blocks you combine into more complex shapes. @@ -244,9 +277,8 @@ Schema.BigInt.check(isNonPositive) ## Dates -The `Schema.Date` schema matches `Date` objects (even invalid dates). - -If you want to validate only valid dates, use `Schema.DateValid` instead. +The `Schema.Date` schema matches valid `Date` objects and rejects invalid dates +such as `new Date(NaN)`. ## Template literals @@ -254,6 +286,10 @@ You can use `Schema.TemplateLiteral` to define structured string patterns made o Template literal matching is based on the semantics of each part rather than only a generated regular expression. Checks on string, number, and bigint schema parts are applied while matching each segment. +Parts must not contain encodings. Construction throws for transformed parts, including transformations inside unions and transformations whose decoded and encoded types are equal. Brands and supported checks without encodings remain valid. Use `Schema.TemplateLiteralParser` when the parts need to decode values, such as `BooleanFromBit` or `FiniteFromString`. + +To describe bit spellings directly, use `Schema.Literals([0, 1])` as the part. To describe finite numeric spellings, use `Schema.Finite`. Replacing `FiniteFromString` with `Finite` changes the accepted spelling rules: a finite numeric part does not accept an empty segment. Explicit `Schema.toType` and `Schema.toEncoded` projections remove transformations, but can also change the constraints a template validates. + **Example** (Constraining parts of an email-like string) ```ts @@ -282,7 +318,7 @@ Success("a@b.com") console.log(String(Schema.decodeUnknownExit(email)("@b.com"))) /* -Failure(Cause([Fail(SchemaError(Expected a string matching template literal parts, got "@b.com"))])) +Failure(Cause([Fail(SchemaError(Expected a string matching template literal parts))])) */ ``` @@ -290,6 +326,12 @@ Failure(Cause([Fail(SchemaError(Expected a string matching template literal part If you want to extract the parts of a string that match a template, you can use `Schema.TemplateLiteralParser`. This allows you to parse the input into its individual components rather than treat it as a single string. +The parser transforms a template built from the encoded sides of the parts into a tuple that retains their decoders and checks. Encoding applies the parts' encoders and joins the segments. The parser requires the decoding and encoding services of its parts in the corresponding direction. + +`Schema.toEncoded(parser)` validates that source template. Use `Schema.String` if you need to accept unrestricted strings. + +Ambiguous templates use greedy segmentation with backtracking. Encoding a tuple and decoding the resulting string can produce a different tuple when a segment contains a separator used by the template. + **Example** (Parsing a template literal into components) ```ts @@ -308,11 +350,11 @@ console.log(String(Schema.decodeUnknownExit(schema)("aa:1"))) // Success(["aa",":",1]) console.log(String(Schema.decodeUnknownExit(schema)("a:1"))) -// Failure(Cause([Fail(SchemaError(Expected a value with a length of at least 2, got "a" +// Failure(Cause([Fail(SchemaError(Expected a value with a length of at least 2 // at [0]))])) console.log(String(Schema.decodeUnknownExit(schema)("aa:1.2"))) -// Failure(Cause([Fail(SchemaError(Expected an integer, got 1.2 +// Failure(Cause([Fail(SchemaError(Expected an integer // at [2]))])) ``` @@ -920,7 +962,7 @@ type Encoded = { type Encoded = typeof schema.Encoded ``` -If you want the record part to be mutable, you can wrap it in `Schema.mutable`. +If you want the record part to be mutable, apply `Schema.mutableKey` to its value schema. **Example** (Allowing dynamic keys to be mutable) @@ -1133,7 +1175,7 @@ console.log( }) ) ) -// Failure(Cause([Fail(SchemaError: Expected a === b, got {"a":"a","b":"b","c":"c"})])) +// Failure(Cause([Fail(SchemaError: Expected a === b)])) ``` #### Mapping individual fields @@ -1598,6 +1640,14 @@ const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapE An array schema describes a variable-length list where every element shares the same type. +### Mutability + +Array and tuple schemas are readonly by default. Use `Schema.mutable` to make them mutable. + +> [!NOTE] +> `Schema.mutable` does not support an encoding attached directly to the array or tuple schema. Apply it before adding +> such an encoding. Encodings on element schemas are supported. + ### Unique Arrays You can deduplicate arrays using `Schema.UniqueArray`. @@ -1610,7 +1660,7 @@ import { Schema } from "effect" const schema = Schema.UniqueArray(Schema.String) console.log(String(Schema.decodeUnknownExit(schema)(["a", "b", "a"]))) -// Failure(Cause([Fail(SchemaError: Expected an array with unique items, got ["a","b","a"])])) +// Failure(Cause([Fail(SchemaError: Expected an array with unique items)])) ``` ## Records @@ -1638,9 +1688,12 @@ console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, c_d: 2 })) // { aB: 1, cD: 2 } ``` -By default, if a transformation results in duplicate keys, the last value wins. +When parsing sequentially, transformed keys are applied in selection order, so +the later selected property wins if a transformation produces a duplicate key. +With concurrency greater than `1`, completion order determines which value is +retained. -**Example** (Merging transformed keys by keeping the last one) +**Example** (Keeping the later selected value when parsing sequentially) ```ts import { Schema, SchemaTransformation } from "effect" @@ -1653,35 +1706,6 @@ console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, aB: 2 })) // { aB: 2 } ``` -You can customize how key conflicts are resolved by providing a `combine` function. - -**Example** (Combining values for conflicting keys) - -```ts -import { Schema, SchemaTransformation } from "effect" - -const SnakeToCamel = Schema.String.pipe(Schema.decode(SchemaTransformation.snakeToCamel())) - -const schema = Schema.Record(SnakeToCamel, Schema.Number, { - keyValueCombiner: { - decode: { - // When decoding, combine values of conflicting keys by summing them - combine: ([_, v1], [k2, v2]) => [k2, v1 + v2] // you can pass a Semigroup to combine keys - }, - encode: { - // Same logic applied when encoding - combine: ([_, v1], [k2, v2]) => [k2, v1 + v2] - } - } -}) - -console.log(Schema.decodeUnknownSync(schema)({ a_b: 1, aB: 2 })) -// { aB: 3 } - -console.log(Schema.encodeUnknownSync(schema)({ a_b: 1, aB: 2 })) -// { a_b: 3 } -``` - ### Number Keys Records with number keys are supported. @@ -1700,7 +1724,7 @@ console.log(String(Schema.decodeUnknownExit(schema)({ 1.1: "ignored" }))) // Success({}) console.log(String(Schema.decodeUnknownExit(schema)({ 1: null }))) -// Failure(Cause([Fail(SchemaError(Expected string, got null +// Failure(Cause([Fail(SchemaError(Expected string // at ["1"]))])) ``` @@ -1810,7 +1834,7 @@ import { Schema } from "effect" const schema = Schema.Union([Schema.NonEmptyString, Schema.Number]) console.log(String(Schema.decodeUnknownExit(schema)(""))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1, got "")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1)])) ``` If none of the union members match the input, the union fails with a message at the top level. @@ -1823,7 +1847,7 @@ import { Schema } from "effect" const schema = Schema.Union([Schema.NonEmptyString, Schema.Number]) console.log(String(Schema.decodeUnknownExit(schema)(null))) -// Failure(Cause([Fail(SchemaError: Expected string | number, got null)])) +// Failure(Cause([Fail(SchemaError: Expected string | number)])) ``` This behavior is especially helpful when working with literal values. Instead of producing a separate error for each literal (as in version 3), the schema reports a single, clear message. @@ -1836,7 +1860,7 @@ import { Schema } from "effect" const schema = Schema.Literals(["a", "b"]) console.log(String(Schema.decodeUnknownExit(schema)(null))) -// Failure(Cause([Fail(SchemaError: Expected "a" | "b", got null)])) +// Failure(Cause([Fail(SchemaError: Expected "a" | "b")])) ``` ### Exclusive Unions @@ -1853,7 +1877,7 @@ const schema = Schema.Union([Schema.Struct({ a: Schema.String }), Schema.Struct( }) console.log(String(Schema.decodeUnknownExit(schema)({ a: "a", b: 1 }))) -// Failure(Cause([Fail(SchemaError: Expected exactly one member to match the input {"a":"a","b":1})])) +// Failure(Cause([Fail(SchemaError: Expected exactly one member to match)])) ``` ### Deriving Unions @@ -1996,7 +2020,7 @@ The result is a tagged union schema with built-in helpers based on the tag value ### Augmenting Tagged Unions -The `asTaggedUnion` function enhances a tagged union schema by adding helper methods for working with its members. +The `toTaggedUnion` function enhances a tagged union schema by adding helper methods for working with its members. You need to specify the name of the tag field used to differentiate between variants. @@ -2022,6 +2046,8 @@ This helper has some advantages over a dedicated constructor: - You can choose among multiple possible tag fields if present. - It supports unions that include nested unions. +Each member must have a unique discriminant property key. `toTaggedUnion` throws when it encounters a duplicate. Numeric and string values that resolve to the same property key, such as `1` and `"1"`, are considered duplicates. + **Note**. If the tag is the standard `_tag` field, you can use `Schema.TaggedUnion` instead. #### Accessing Members by Tag @@ -2036,6 +2062,18 @@ const B = tagged.cases.B const C = tagged.cases.C ``` +#### Accessing Discriminant Values + +The `discriminants` property contains the decoded discriminant values in the same order as the flattened union members. Its type preserves the exact tuple of values. + +**Example** (Deriving a literal schema from discriminants) + +```ts +const Tags = Schema.Literals(tagged.discriminants) + +// Schema.Literals +``` + #### Checking Membership in a Subset of Tags The `isAnyOf` method lets you check if a value belongs to a selected subset of tags. @@ -2191,7 +2229,7 @@ console.log(String(Schema.decodeUnknownExit(URLSchema)(new URL("https://example. // Success(https://example.com/) console.log(String(Schema.decodeUnknownExit(URLSchema)(null))) -// Failure(Cause([Fail(SchemaError(Expected , got null))])) +// Failure(Cause([Fail(SchemaError(Expected ))])) ``` > **Tip**: For simple `instanceof` checks, prefer `Schema.instanceOf(URL)`, it wraps `Schema.declare` with an `instanceof` guard automatically. @@ -2211,14 +2249,14 @@ const URLSchema = Schema.declare( ) console.log(String(Schema.decodeUnknownExit(URLSchema)(null))) -// Failure(Cause([Fail(SchemaError(Expected URL, got null))])) +// Failure(Cause([Fail(SchemaError(Expected URL))])) // ^^^ // Now the error message shows "URL" instead of "" ``` ### Adding JSON support with `toCodecJson` -`Schema.toCodecJson` derives a codec that can convert your type **to and from JSON**. By default, declared schemas have no JSON representation — encoding produces `null`: +`Schema.toCodecJson` derives a codec that can convert your type **to and from JSON**. By default, declared schemas use `Schema.Json` as their encoded representation. This keeps codec derivation defined, but it does not invent a serialization for your custom type. For example, a `URL` instance is not itself a JSON value, so encoding fails: ```ts import { Schema } from "effect" @@ -2231,10 +2269,9 @@ const URLSchema = Schema.declare( // Derive a JSON codec from the schema const codec = Schema.toCodecJson(URLSchema) -// Encoding a URL produces null because Schema doesn't know -// how to serialize a URL to JSON yet +// Encoding fails because Schema doesn't know how to serialize a URL to JSON yet console.log(String(Schema.encodeUnknownExit(codec)(new URL("https://example.com")))) -// Success(null) +// Failure(Cause([Fail(SchemaError(Expected JSON value))])) ``` To fix this, provide a `toCodecJson` annotation. This annotation is a function that returns an `AST.Link`, a bridge that describes how to convert between your custom type and a JSON-friendly representation. @@ -2244,10 +2281,12 @@ You build a `Link` using `Schema.link()`, which takes two arguments: 1. **A JSON-side schema** — the shape of the JSON value (e.g. `Schema.String` for a URL string) 2. **A transformation** — how to convert back and forth between your type and the JSON value +The annotation only defines the link; the consuming parser chooses its execution and failure handling. Transformations may be asynchronous, may fail, and may use optional services, but cannot require services not declared by the input schema. + **Example** (Making `URL` JSON-serializable) ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect" +import { Effect, Schema, SchemaIssue, SchemaTransformation } from "effect" const URLSchema = Schema.declare( (u): u is URL => u instanceof URL, @@ -2261,10 +2300,10 @@ const URLSchema = Schema.declare( // How to convert between URL and string SchemaTransformation.transformOrFail({ // JSON string -> URL (may fail if the string is not a valid URL) - decode: (s) => + decode: (s, options) => Effect.try({ try: () => new URL(s), - catch: (e) => new SchemaIssue.InvalidValue(Option.some(s), { message: globalThis.String(e) }) + catch: () => new SchemaIssue.InvalidValue({ message: "Invalid URL string" }, s, options) }), // URL -> JSON string (always succeeds) encode: (url) => Effect.succeed(url.href) @@ -2319,7 +2358,7 @@ The parsing function you return from `run` is responsible for: **Example** (A generic `Box` container) ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaParser } from "effect" +import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" // 1. Define the type interface Box { @@ -2340,7 +2379,7 @@ const Box = (item: A) => (u, ast, options) => { // First, check the outer shape if (!isBox(u)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(u))) + return Effect.fail(new SchemaIssue.InvalidType(ast, u, options)) } // Then, decode the inner value using the item codec return Effect.mapBothEager( @@ -2361,7 +2400,7 @@ console.log(String(Schema.decodeUnknownExit(schema)({ value: "1" }))) // Success({ value: 1 }) console.log(String(Schema.decodeUnknownExit(schema)({ value: "a" }))) -// Failure(Cause([Fail(SchemaError(Expected a finite number, got NaN +// Failure(Cause([Fail(SchemaError(Expected a finite number // at ["value"]))])) ``` @@ -2384,7 +2423,7 @@ import { Schema } from "effect" const schema = Schema.String.check(Schema.makeFilter((s) => s.length >= 3)) console.log(String(Schema.decodeUnknownExit(schema)(""))) -// Failure(Cause([Fail(SchemaError: Expected , got "")])) +// Failure(Cause([Fail(SchemaError: Expected )])) ``` You can attach annotations and provide a custom error message when defining a filter. @@ -2429,10 +2468,10 @@ import { Schema } from "effect" const Username = Schema.NonEmptyString.annotate({ identifier: "Username" }) console.log(String(Schema.decodeUnknownExit(Username)(null))) -// Failure(Cause([Fail(SchemaError: Expected Username, got null)])) +// Failure(Cause([Fail(SchemaError: Expected Username)])) console.log(String(Schema.decodeUnknownExit(Username)(""))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1, got "")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1)])) ``` ### Filter return shapes @@ -2543,7 +2582,7 @@ const schema = Schema.String.check( ) console.log(String(Schema.decodeUnknownExit(schema)(" a"))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got " a")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` **Example** (Using `isMinLength` with an object that has `length`) @@ -2555,7 +2594,7 @@ import { Schema } from "effect" const schema = Schema.Struct({ length: Schema.Number }).check(Schema.isMinLength(3)) console.log(String(Schema.decodeUnknownExit(schema)({ length: 2 }))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got {"length":2}])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` **Example** (Validating array length) @@ -2567,7 +2606,7 @@ import { Schema } from "effect" const schema = Schema.Array(Schema.String).check(Schema.isMinLength(3)) console.log(String(Schema.decodeUnknownExit(schema)(["a", "b"]))) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got ["a","b"]])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` ## Multiple Issues Reporting @@ -2589,8 +2628,8 @@ console.log( ) ) /* -Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got " a" -Expected a string with no leading or trailing whitespace, got " a")])) +Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3 +Expected a string with no leading or trailing whitespace)])) */ ``` @@ -2615,7 +2654,7 @@ console.log( }) ) ) -// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3, got " a")])) +// Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 3)])) ``` ## Filter Groups @@ -2672,16 +2711,16 @@ const branded = Schema.String.pipe(Schema.brand("UserId")) Some filters check the structure of a value rather than its contents — for example, the number of items in an array or the number of keys in an object. These are called **structural filters**. -Structural filters are evaluated separately from item-level filters, which allows multiple issues to be reported when `{ errors: "all" }` is used. Examples include: +Examples include: - `isMinLength` or `isMaxLength` on arrays - `isMinSize` or `isMaxSize` on objects with a `size` property - `isMinProperties` or `isMaxProperties` on objects - any constraint that applies to the "shape" of a value rather than to its nested values -These filters are evaluated separately from item-level filters and allow multiple issues to be reported when `{ errors: "all" }` is used. +Structural filters run only after the base array, object, or declaration and its nested values parse successfully. If a nested value fails, its issue is reported but structural filters on the containing value are not evaluated, even with `{ errors: "all" }`. -**Example** (Validating an array with item and structural constraints) +**Example** (A nested failure prevents the structural filter from running) ```ts import { Schema } from "effect" @@ -2694,10 +2733,8 @@ const schema = Schema.Struct({ console.log(String(Schema.decodeUnknownExit(schema)({ tags: ["a", ""] }, { errors: "all" }))) /* -Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1, got "" - at ["tags"][1] -Expected a value with a length of at least 3, got ["a",""] - at ["tags"])])) +Failure(Cause([Fail(SchemaError: Expected a value with a length of at least 1 + at ["tags"][1])])) */ ``` @@ -2710,7 +2747,7 @@ Define an effectful filter with `Getter.checkEffect` as part of a transformation **Example** (Asynchronous validation of a numeric value) ```ts -import { Effect, Option, Result, Schema, SchemaGetter, SchemaIssue } from "effect" +import { Effect, Result, Schema, SchemaGetter, SchemaIssue } from "effect" // Simulated API call that fails when userId is 0 const myapi = (userId: number) => @@ -2723,13 +2760,15 @@ const myapi = (userId: number) => const schema = Schema.Finite.pipe( Schema.decode({ - decode: SchemaGetter.checkEffect((n) => + decode: SchemaGetter.checkEffect((n, options) => Effect.gen(function*() { // Call the async API and wrap the result in a Result const user = yield* Effect.result(myapi(n)) // If the result is an error, return a SchemaIssue - return Result.isFailure(user) ? new SchemaIssue.InvalidValue(Option.some(n), { title: "not found" }) : undefined // No issue, value is valid + return Result.isFailure(user) + ? new SchemaIssue.InvalidValue({ message: "not found" }, n, options) + : undefined // No issue, value is valid }) ), encode: SchemaGetter.passthrough() @@ -3192,16 +3231,16 @@ This is useful when you need to validate input or enforce rules that may not alw **Example** (Converting a string URL into a `URL` object) ```ts -import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect" +import { Effect, Schema, SchemaIssue, SchemaTransformation } from "effect" const URLFromString = Schema.String.pipe( Schema.decodeTo( Schema.instanceOf(URL), SchemaTransformation.transformOrFail({ - decode: (s) => + decode: (s, options) => Effect.try({ try: () => new URL(s), - catch: () => new Issue.InvalidValue(Option.some(s), { message: `Invalid URL string: ${s}` }) + catch: () => new SchemaIssue.InvalidValue({ message: "Invalid URL string" }, s, options) }), encode: (url) => Effect.succeed(url.href) }) @@ -3309,10 +3348,17 @@ Strict mode ensures that decoding and encoding fully match. You can disable it b ```ts import { Schema, SchemaTransformation } from "effect" -const From = Schema.String +const From = Schema.Struct({ + a: Schema.Literals(["a", "b"]), + b: Schema.Number +}) -const To = Schema.Number +const To = Schema.Struct({ + a: Schema.String, + b: Schema.Literals([1, 2]) +}) +// Neither From.Type nor To.Encoded extends the other. const schema = From.pipe(Schema.decodeTo(To, SchemaTransformation.passthrough({ strict: false }))) ``` @@ -3654,7 +3700,7 @@ class Person extends Schema.Opaque()( ) {} console.log(String(Schema.decodeUnknownExit(Person)(null))) -// Failure(Cause([Fail(SchemaError: Expected Person, got null)])) +// Failure(Cause([Fail(SchemaError: Expected Person)])) ``` When you call methods like `annotate` on an opaque struct, you get back the original struct, not a new class. @@ -3819,7 +3865,7 @@ g(A.make({ a: "a" })) // error: Argument of type 'A' is not assignable to parame ## Schema as a Class -`Schema.asClass` turns any schema into a class that can be extended with `extends`. The resulting class inherits the full schema API (e.g. `annotate`) and supports static methods that reference `this`. +Any schema can be extended directly with `extends`. The resulting class inherits the full schema API (e.g. `annotate`) and supports static methods that reference `this`. Unlike `Schema.Opaque`, it does **not** make the decoded type nominally distinct, and unlike `Schema.Class`, it does **not** create prototype-backed instances with methods or constructors. It is a lightweight way to attach custom static helpers to a schema. @@ -3828,7 +3874,7 @@ Unlike `Schema.Opaque`, it does **not** make the decoded type nominally distinct ```ts import { Schema } from "effect" -class MyString extends Schema.asClass(Schema.String) { +class MyString extends Schema.String { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -3841,9 +3887,7 @@ console.log(MyString.decodeUnknownSync("a")) ```ts import { Schema } from "effect" -class MyStruct extends Schema.asClass( - Schema.Struct({ name: Schema.String }) -) { +class MyStruct extends Schema.Struct({ name: Schema.String }) { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -3853,12 +3897,12 @@ console.log(MyStruct.decodeUnknownSync({ name: "a" })) ### Subclassing -You can extend an `asClass` class to layer on more static helpers: +You can extend a schema class to layer on more static helpers: ```ts import { Schema } from "effect" -class MyString extends Schema.asClass(Schema.FiniteFromString) { +class MyString extends Schema.FiniteFromString { static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) } @@ -3902,7 +3946,7 @@ try { } } /* -Expected a finite number, got NaN +Expected a finite number at [1] */ ``` @@ -4016,7 +4060,7 @@ class PersonWithEmail extends Person { **Example** (Extending Data.Error) ```ts -import { Data, Effect, identity, Schema, SchemaTransformation, SchemaUtils } from "effect" +import { Data, Effect, identity, Schema, SchemaTransformation } from "effect" const Props = Schema.Struct({ message: Schema.String @@ -4062,9 +4106,6 @@ const schema = Schema.instanceOf(Err, { json: () => Schema.link()(Props, transformation) } }).pipe(Schema.encodeTo(Props, transformation)) - -// built-in helper? -const builtIn = SchemaUtils.getNativeClassSchema(Err, { encoding: Props }) ``` ### Class API @@ -4111,14 +4152,14 @@ try { } catch (error: any) { console.log(error.message) } -// Expected a === b, got {"a":"a","b":"b"} +// Expected a === b try { Schema.decodeUnknownSync(A)({ a: "a", b: "b" }) } catch (error: any) { console.log(error.message) } -// Expected a === b, got {"a":"a","b":"b"} +// Expected a === b ``` #### Branded Classes @@ -4398,19 +4439,19 @@ console.log(Schema.decodeUnknownSync(Animal)({ _tag: "Cat", lives: 9 })) All features from `Class` are available: `extend`, `annotate`, `check`, branded classes, and recursive definitions. -### ErrorClass +### Error ```ts import { Schema } from "effect" -class E extends Schema.ErrorClass("E")({ +class E extends Schema.Error("E")({ id: Schema.Number }) {} ``` -### TaggedErrorClass +### TaggedError -`TaggedErrorClass` combines `ErrorClass` with an automatic `_tag` field, giving you a tagged error that can be caught with `Effect.catchTag`. +`TaggedError` combines `Error` with an automatic `_tag` field, giving you a tagged error that can be caught with `Effect.catchTag`. Like `TaggedClass`, the tag value doubles as the identifier by default, and you can pass an explicit identifier as the first argument to override it. @@ -4419,7 +4460,7 @@ Like `TaggedClass`, the tag value doubles as the identifier by default, and you ```ts import { Effect, Schema } from "effect" -class HttpError extends Schema.TaggedErrorClass()("HttpError", { +class HttpError extends Schema.TaggedError()("HttpError", { status: Schema.Number, message: Schema.String }) {} @@ -4438,11 +4479,11 @@ const recovered = program.pipe( ```ts import { Effect, Schema } from "effect" -class NotFound extends Schema.TaggedErrorClass()("NotFound", { +class NotFound extends Schema.TaggedError()("NotFound", { path: Schema.String }) {} -class Unauthorized extends Schema.TaggedErrorClass()("Unauthorized", { +class Unauthorized extends Schema.TaggedError()("Unauthorized", { reason: Schema.String }) {} @@ -4463,7 +4504,7 @@ const recovered = program.pipe( ) ``` -All features from `ErrorClass` are available: `extend`, `annotate`, and `check`. +All features from `Error` are available: `extend`, `annotate`, and `check`. # Serialization @@ -4767,6 +4808,8 @@ A canonical codec describes how values that match a schema should be converted t 2. **AST transformation**: the codec builder walks the schema AST and produces a new schema that represents the serialized form (this traversal is handled by Effect). 3. **Recursive composition**: codecs apply through nested structures (objects, arrays, unions, and so on) without you having to wire everything manually. +Canonical codec derivation does not run transformations; the consuming parser chooses execution and failure handling. Annotation links may be asynchronous, may fail, and may use optional services, but cannot add required services absent from the derived codec type. `toCodecIso` declares none, while `toCodecJson` and `toCodecStringTree` preserve the input schema's requirements. + The next example shows why a custom class needs a codec when working with JSON. **Example** (A custom class that does not round-trip through JSON) @@ -4788,30 +4831,23 @@ class Point { const PointSchema = Schema.instanceOf(Point) ``` -Even if encoding produces something JSON-looking, decoding cannot rebuild a `Point` instance (including its prototype and methods) from plain JSON data. +Without an annotation, `Schema.toCodecJson` uses `Schema.Json` as the encoded representation. A `Point` instance is not a JSON value, and a plain object parsed from JSON is not a `Point` instance (including its prototype and methods), so neither direction can bridge the two representations. ```ts -// Encode a Point instance using the schema, then stringify it. -// This produces a plain JSON object, not a class instance. -const json = JSON.stringify(Schema.encodeUnknownSync(PointSchema)(new Point(1, 2))) +const codecJson = Schema.toCodecJson(PointSchema) -console.log(json) -// '{"x":1,"y":2}' +console.log(String(Schema.encodeUnknownExit(codecJson)(new Point(1, 2)))) +// Failure(Cause([Fail(SchemaError(Expected JSON value))])) -// Decode attempts to create a Point instance from parsed JSON. -// This fails because JSON.parse returns a plain object, not `new Point(...)`. -try { - Schema.decodeUnknownSync(PointSchema)(JSON.parse(json)) -} catch (error) { - console.error(String(error)) -} +console.log(String(Schema.decodeUnknownExit(codecJson)({ x: 1, y: 2 }))) +// Failure(Cause([Fail(SchemaError(Expected ))])) ``` -The same issue shows up when generating a JSON Schema document: since the schema represents a class instance and there is no JSON representation for it, the generator falls back to a placeholder. +The same issue shows up when generating a JSON Schema document. Since the opaque declaration has no structural JSON representation, the generator emits an unconstrained JSON Schema: ```ts console.log(Schema.toJsonSchemaDocument(PointSchema)) -// { dialect: 'draft-2020-12', schema: { type: 'null' }, definitions: {} } +// { dialect: 'draft-2020-12', schema: {}, definitions: {} } ``` #### Configuring the Codec @@ -4896,6 +4932,8 @@ When you call `Schema.toCodecJson(schema)`, the library: 3. **Applies transformations**: it replaces types that are not JSON-friendly with types that are. 4. **Composes recursively**: it builds codecs for nested schemas by combining the codecs of their parts. +When this traversal adds a transformation because a node has no direct JSON representation, its checks and annotations remain on the source node; they are not copied to the artificial JSON target. The codec still applies source checks after transforming values. + #### Custom Encodings `Schema.toCodecJson` respects **explicit encodings** you add to a schema. If you choose a custom representation, that choice takes priority over the default. @@ -5136,6 +5174,11 @@ Output: To generate a draft-07 JSON Schema, use `JsonSchema.toDocumentDraft07` to convert the draft-2020-12 JSON Schema. +The dialect converters preserve unknown and custom keywords as opaque values. When a known keyword cannot be +represented by the target dialect, or an opaque keyword would become active and change meaning in that dialect, +conversion throws instead of silently changing its meaning. For example, Draft-07 cannot represent dynamic +references, unevaluated constraints, or non-default `minContains` / `maxContains` cardinality. + **Example** (Tuple to draft-7 JSON Schema) ```ts @@ -5180,6 +5223,8 @@ Use `.annotate(...)` to attach standard JSON Schema annotations: - `readOnly` - `writeOnly` +`Schema.toJsonSchemaDocument` compiles the canonical JSON target. Checks and annotations left on the source side of an artificial transformation therefore do not appear in the generated document. Put metadata on an explicit JSON target when it must appear there. + **Example** (Adding basic annotations) ```ts @@ -5347,7 +5392,7 @@ console.log(JSON.stringify(document, null, 2)) #### Defining a JSON-safe representation for custom types -This example shows how `Schema.toCodecJson` and `Schema.toJsonSchema` can describe the same JSON shape for a custom type. +This example shows how `Schema.toCodecJson` and `Schema.toJsonSchemaDocument` can describe the same JSON shape for a custom type. `Headers` is not JSON-friendly by default. `JSON.stringify(new Headers({ a: "b" }))` produces `{}` because the header data is not stored in enumerable properties. By adding a `toCodecJson` annotation, you define a JSON-safe representation and use it for both serialization and JSON Schema generation. @@ -5534,412 +5579,6 @@ console.log(JSON.stringify(document, null, 2)) */ ``` -### Generating an Arbitrary from a Schema - -Property-based tests need generators. `Schema.toArbitrary` derives a -`fast-check` `Arbitrary` that generates decoded `Type` values accepted by the -schema. - -Most schemas do not need any extra work: - -```ts -import { Schema } from "effect" -import { FastCheck } from "effect/testing" - -const Person = Schema.Struct({ - name: Schema.String, - age: Schema.Int.check(Schema.isBetween({ minimum: 18, maximum: 80 })) -}) - -const PersonArbitrary = Schema.toArbitrary(Person) - -console.log(FastCheck.sample(PersonArbitrary, 3)) -``` - -Use `Schema.toArbitraryLazy` only when you want the caller to provide -`fast-check`: - -```ts -import { Schema } from "effect" -import { FastCheck } from "effect/testing" - -const makeStringArbitrary = Schema.toArbitraryLazy(Schema.String) - -const StringArbitrary = makeStringArbitrary(FastCheck) -``` - -`Schema.Never` and declaration schemas without a `toArbitrary` annotation cannot -be derived automatically. - -#### Filters - -Generated values are always checked by the schema filters before they are -returned. The important question is whether a filter can also help choose a good -generator. - -Built-in filters already do this: - -```ts -import { Schema } from "effect" - -const Username = Schema.String.check( - Schema.isMinLength(3), - Schema.isMaxLength(20), - Schema.isPattern(/^[a-z0-9_]+$/) -) - -const PositiveInteger = Schema.Int.check( - Schema.isGreaterThanOrEqualTo(1) -) - -const Tags = Schema.Array(Schema.String).check( - Schema.isMinLength(1), - Schema.isUnique() -) -``` - -For these schemas, `toArbitrary` does not generate random unconstrained strings, -numbers, or arrays and then hope the filters pass. It uses the length, range, -pattern, and uniqueness metadata to build a better generator first. - -A custom filter without metadata is still correct, but may be inefficient: - -```ts -import { Schema } from "effect" - -const isPalindrome = (s: string) => s === Array.from(s).reverse().join("") - -const Palindrome = Schema.String.check( - Schema.makeFilter(isPalindrome, { - expected: "a palindrome" - }) -) -``` - -This works because the final predicate check rejects strings that are not -palindromes. It may need many attempts, because the base string generator has no -reason to produce mirrored strings. - -#### Reports - -Use `{ report: true }` when you want to know which filters did not guide -generation: - -```ts -import { Schema } from "effect" - -const isPalindrome = (s: string) => s === Array.from(s).reverse().join("") - -const Palindrome = Schema.String.check( - Schema.makeFilter(isPalindrome, { - expected: "a palindrome" - }) -) - -const result = Schema.toArbitrary(Palindrome, { report: true }) - -result.value -result.report.warnings -``` - -An `OpaqueFilter` warning means: "this filter is still checked, but it did not -help build the generator." - -Reports contain warnings only. Unsupported schemas, impossible constraints, -invalid candidates, and recursive schemas without a finite terminal path still -fail immediately. - -#### Custom Filters With Constraints - -If part of a custom filter can be described as a normal generation constraint, -attach `arbitrary.constraint` to the filter. The constraint does not have to -prove the whole predicate; it just makes the base generator closer to the values -the predicate accepts. - -```ts -import { Order, Schema } from "effect" - -const isPrimeNumber = (n: number) => { - if (!Number.isInteger(n) || n < 2) { - return false - } - for (let divisor = 2; divisor * divisor <= n; divisor++) { - if (n % divisor === 0) { - return false - } - } - return true -} - -const prime = Schema.makeFilter(isPrimeNumber, { - expected: "a prime number", - arbitrary: { - constraint: { - integer: true, - ordered: { - order: Order.Number, - minimum: 2 - } - } - } -}) - -const Prime = Schema.Number.check(prime) -``` - -The filter still checks primality. The constraint only tells `toArbitrary` not -to waste time on non-integers or numbers below `2`. - -Think of `constraint` as a small vocabulary that the current schema node can -understand: - -- On strings, `minLength` and `maxLength` mean string length. -- On arrays, `minLength` and `maxLength` mean array length. -- On objects, `minLength` and `maxLength` mean final own-property count. -- On sets, maps, hash collections, and chunks, `minLength` and `maxLength` mean final collection size. -- `patterns` apply to string generation. -- `integer`, `noNaN`, `noInfinity`, `valid`, and `unique` are enabled when any contributing filter sets them. -- `ordered` stores bounds for ordered values such as numbers, bigints, dates, `DateTime`, and `BigDecimal`. - -Fields that do not make sense for the current node are ignored. The final filter -check still validates every generated value. - -#### Custom Filters With Candidates - -Use a candidate when the filter cannot be expressed with the constraint -vocabulary. - -```ts -import { Schema } from "effect" - -const reverse = (s: string) => Array.from(s).reverse().join("") - -const isPalindrome = (s: string) => s === reverse(s) - -const palindrome = Schema.makeFilter( - isPalindrome, - { - expected: "a palindrome", - arbitrary: { - candidate: { - weight: 5, - make: (fc) => fc.string().map((half) => `${half}${reverse(half)}`) - } - } - } -) - -const Palindrome = Schema.String.check(palindrome) -``` - -A candidate is an extra source used together with the schema node's base -generator. The base generator has weight `1`. A candidate has weight `1` unless -you set another positive integer weight. - -With one candidate at weight `5`, fast-check tries the candidate roughly five -times as often as the base generator. Candidate values are still checked by all -filters, so a bad candidate can waste attempts but cannot produce invalid -values. - -`make` receives the arbitrary context and may return `undefined` when the -candidate should not be used for that context. - -#### Schema-Level Overrides - -Use a `toArbitrary` annotation when you want to replace the generator for a -schema node. - -The annotation is not limited to declaration schemas. You can attach it to a -normal schema with `.annotate(...)`: - -```ts -import { Schema } from "effect" - -const Name = Schema.String.annotate({ - toArbitrary: () => (fc) => fc.constantFrom("Alice", "Bob", "Carol") -}) -``` - -Put override annotations on base schemas when possible, before adding filters: - -```ts -const Name = Schema.String.annotate({ - toArbitrary: () => (fc) => fc.constantFrom("Alice", "Bob", "Carol") -}).check(Schema.isMinLength(1)) -``` - -This shape is easier to reason about. The override provides the base generator; -the filter remains a normal filter. Schema still checks generated values at the -end. - -Avoid putting an override on a schema that already has filters unless the -override intentionally handles those filters too: - -```ts -const Name = Schema.String.check(Schema.isMinLength(1)).annotate({ - toArbitrary: () => (fc) => fc.constant("") -}) -``` - -This is valid TypeScript, but it is a bad generator: it always generates a value -that the filter rejects. - -The second argument of a `toArbitrary` hook is the arbitrary context. Its -`constraint` field contains constraints collected from filters on the same -schema node as the override. If the override is placed before `.check(...)`, the -context does not include the later filters. If the override is placed after -`.check(...)`, the context includes those filters and the override must respect -them. - -`context.recursion` is present while deriving inside a recursive schema. - -#### Declaration Schemas - -Declaration schemas are opaque to Schema. If you define one, provide a -`toArbitrary` hook. - -For an atomic declaration, return a normal `fast-check` arbitrary: - -```ts -import { Schema } from "effect" - -const Url = Schema.instanceOf(globalThis.URL, { - title: "URL", - toArbitrary: () => (fc) => fc.webUrl().map((s) => new globalThis.URL(s)) -}) -``` - -Generic declarations receive one derivation per type parameter: - -- `arbitrary`: the normal generator for the type parameter. -- `terminal`: a finite generator for the type parameter, used to close recursive generation. - -For an opaque wrapper type, you usually map both sources in the same way: - -```ts -import { Effect, Option, Schema, SchemaIssue, SchemaParser } from "effect" - -class Box { - private constructor(private readonly value: A) {} - - static make(value: A): Box { - return new Box(value) - } - - static unbox(box: Box): A { - return box.value - } -} - -const isBox = (u: unknown): u is Box => u instanceof Box - -const BoxSchema = (value: A) => - Schema.declareConstructor, Box>()( - [value], - ([valueCodec]) => (input, ast, options) => { - if (!isBox(input)) { - return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))) - } - return Effect.map( - SchemaParser.decodeUnknownEffect(valueCodec)(Box.unbox(input), options), - Box.make - ) - }, - { - toArbitrary: ([value]) => () => ({ - arbitrary: value.arbitrary.map(Box.make), - terminal: value.terminal?.map(Box.make) - }) - } - ) -``` - -This looks like duplicated code, but it is not the same generator twice. It is -the same opaque constructor applied to two different sources. - -Suppose someone later builds a recursive schema like this: - -```ts -interface Tree { - readonly value: A - readonly children: ReadonlyArray> -} - -type BoxedTree = Box> -``` - -`Box` does not know whether `A` is recursive. If `A` is `Tree`, then -`value.arbitrary` may generate a recursive tree, while `value.terminal` is the -finite tree generator used when the recursion budget is exhausted. Mapping both -sources through `Box.make` preserves that information. If `Box` returned only -`arbitrary`, it would hide the finite path from outer recursive schemas. - -If the type parameter has no finite terminal generator, `value.terminal` is -`undefined`, and the wrapper cannot provide a terminal branch either. - -#### Integration with Synthetic Data Generation Tools - -Synthetic data libraries such as `@faker-js/faker` are useful when the generated -values should look realistic. Put them behind a Fast-Check arbitrary instead of -calling them directly, so Fast-Check still controls randomness and shrinking. - -```ts -import { faker } from "@faker-js/faker" -import { Schema } from "effect" -import { FastCheck } from "effect/testing" - -/** - * Make it easy to plug a Faker generator into a Schema's `toArbitrary` override. - * The seed comes from Fast-Check so data is reproducible and shrinks correctly. - */ -function fake( - gen: (f: typeof faker) => A -): Schema.Annotations.ToArbitrary.Declaration { - return () => (fc) => - fc.nat().map((seed) => { - faker.seed(seed) - return gen(faker) - }) -} - -const FirstName = Schema.String.annotate({ - toArbitrary: fake((faker) => faker.person.firstName()) -}) - -const LastName = Schema.String.annotate({ - toArbitrary: fake((faker) => faker.person.lastName()) -}) - -const JobTitle = Schema.String.annotate({ - toArbitrary: fake((faker) => faker.person.jobTitle()) -}) - -const Company = Schema.String.annotate({ - toArbitrary: fake((faker) => faker.company.name()) -}) - -const Person = Schema.Struct({ - firstName: FirstName, - lastName: LastName, - jobTitle: JobTitle, - company: Company -}) - -console.log(FastCheck.sample(Schema.toArbitrary(Person), 3)) -``` - -These overrides are useful because the values have domain shape: names look like -names, job titles look like job titles, and companies look like companies. For -plain numeric ranges, prefer Schema constraints and the default arbitrary -derivation. - -If you combine a Faker source with filters, put the override on the base schema -first and add filters afterwards. This keeps the responsibilities simple: the -override chooses a realistic source, and the filter remains the final validation -rule. If you put the override after `.check(...)`, the override must respect -those filters itself, or generation will spend time producing values that are -rejected. - ### Generating an Equivalence from a Schema An equivalence function checks whether two values are structurally equal according to the schema's definition. Schema derives this automatically, so you do not need to write manual comparison logic. @@ -6050,6 +5689,8 @@ console.log(_s.replace("b", new B({ a: new A({ s: "a" }) }))) // B { a: A { s: 'b' } } ``` +Reading through the generated `Iso` encodes the schema value, while replacing through it decodes the new focus. `Schema.toIso` consumes `Schema.toCodecIso` synchronously, so failing, asynchronous, or service-dependent transformations can throw. Schema failures use `"Schema validation failed"` with a `SchemaIssue.Issue` in `cause`; format it with `SchemaIssue.makeFormatterDefault()`, or use an effectful parser with `Schema.toCodecIso` for asynchronous execution or explicit failure handling. + ### Using the Differ Module for Type-Safe JSON Patches The `Differ` module lets you compute and apply JSON Patch (RFC 6902) changes for any value described by a `Schema`. You give it a schema once, then use the returned differ to produce a patch from an old value to a new value, and to apply that patch. @@ -6129,49 +5770,55 @@ The idea is simple: if you have a `Schema` for a type `T`, you can serialize any This approach keeps patches independent from TypeScript types and uses the schema as the guardrail when turning JSON back into `T`. +`Schema.toDifferJsonPatch` consumes `Schema.toCodecJson` synchronously, so failing, asynchronous, or service-dependent transformations can throw. Schema failures use `"Schema validation failed"` with a `SchemaIssue.Issue` in `cause`, which `SchemaIssue.makeFormatterDefault()` can format; invalid patch operations instead produce `JsonPatch` errors. + # Schema Representation -The `SchemaRepresentation` module converts a `Schema` into a portable data structure and back again. +The `SchemaRepresentation` module exposes the structural form used to inspect, persist, compile, and rebuild schemas. + +A representation is always a projection of one side of a schema. By default, `Schema.toRepresentation` and +`SchemaRepresentation.toRepresentation` project the encoded side. Apply `Schema.toType` or `SchemaAST.toType` first when +you need the decoded type side instead. Use it when you need to: -- store schemas on disk (for example in a cache) -- send schemas over the network -- rebuild runtime schemas later -- convert to JSON Schema (Draft 2020-12) -- generate TypeScript code that recreates schemas +- inspect the structural form of a schema +- store schemas on disk or send them over the network +- rebuild runtime schemas with an explicit set of revivers +- compile live representations to JSON Schema Draft 2020-12 +- generate TypeScript code from live representations At a high level: -- `fromAST` / `fromASTs` turn a schema AST into a `Document` / `MultiDocument` -- `DocumentFromJson` (schema) round-trip that document through JSON -- `toSchema` rebuilds a runtime `Schema` from the stored representation -- `toJsonSchemaDocument` produces a Draft 2020-12 JSON Schema document -- `toCodeDocument` prepares data for code generation (via `toMultiDocument`) +- `Schema.toRepresentation(schema)` converts a schema to a `Document` +- `SchemaRepresentation.toRepresentation(ast)` and `toRepresentations(asts)` convert schema ASTs to a `Document` or + `MultiDocument` +- `toJson` / `fromJson` cross the persistence boundary +- `fromRepresentation` / `fromRepresentations` rebuild runtime schemas using explicit revivers +- `toJsonSchemaDocument` compiles a live `Document` to JSON Schema Draft 2020-12 +- `toCodeDocument` compiles a live `MultiDocument` to runtime and TypeScript source fragments ```mermaid flowchart TD - S[Schema] -->|fromAST|D{"SchemaRepresentation.Document"} - S -->|fromASTs|MD{"SchemaRepresentation.MultiDocument"} - JS["JSON Schema (draft-07, draft-2020-12, openapi-3.0, openapi-3.1)"] -->JSD - JD --> JS - JD["JsonSchema.Document"] -->|fromJsonSchemaDocument|D - D <--> |"DocumentFromJson (schema)"|JSON - D --> |toJsonSchemaDocument|JD - D --> |toSchema|S - MD --> |toCodeDocument|CodeDocument["CodeDocument"] - D --> |toMultiDocument|MD - MD --> |toJsonSchemaMultiDocument|JMD[JsonSchema.MultiDocument] - MD <--> |"MultiDocumentFromJson (schema)"|JSON + S[Schema] -->|Schema.toRepresentation|LD["live Document"] + AST[SchemaAST] -->|SchemaRepresentation.toRepresentation|LD + LD -->|toJson|JSON["JSON value"] + JSON -->|fromJson|PD["persisted Document"] + PD -->|"fromRepresentation + revivers"|S + LD -->|toJsonSchemaDocument|JD["JsonSchema.Document (draft-2020-12)"] + JD -->|fromJsonSchemaDocument|S + LD -->|toMultiDocument|LMD["live MultiDocument"] + LMD -->|toCodeDocument|CodeDocument + LMD -->|toJsonSchemaMultiDocument|JMD[JsonSchema.MultiDocument] + LMD -->|toJsonMultiDocument|JSON ``` ## The data model ### `Representation` -A `Representation` is a tagged object tree (`_tag` fields like `"String"`, `"Objects"`, `"Union"`, ...). It describes the _structure_ of a schema in a JSON-friendly way. - -Only a subset of schema features can be represented. See "Limitations" below. +A `Representation` is a tagged object tree (`_tag` fields like `"String"`, `"Objects"`, `"Union"`, ...). It describes one +structural side of a schema. Named or recursive nodes use `Reference` values instead of duplicating their definitions. ### `Document` @@ -6188,131 +5835,344 @@ A `MultiDocument` stores multiple root representations that share the same `refe This is useful if you want to serialize a set of schemas together, or if you want to generate code for multiple schemas while emitting shared definitions only once. -## Limitations +## Projection and persistence boundaries -`SchemaRepresentation` is meant for schemas that can be described without user code. +### Representations use the encoded side -That has a few consequences. +`toRepresentation` follows a schema's encoding chain and represents its last encoded side. It does not serialize the +transformation functions. -### Transformations are not supported +```ts +import { Schema } from "effect" -The representation format describes the schema's _shape_ and a set of known checks. It does not store transformation logic. +const encoded = Schema.toRepresentation(Schema.NumberFromString) +console.log(encoded.representation._tag) +// "String" -Schemas that rely on transformations cannot be round-tripped, including: +const decoded = Schema.toRepresentation(Schema.toType(Schema.NumberFromString)) +console.log(decoded.representation._tag) +// "Number" +``` -- `Schema.transform(...)` -- `Schema.encodeTo(...)` -- custom codecs or any schema that changes how values are encoded/decoded +Consequently, rebuilding `encoded` produces a schema for the string representation; it does not recreate the original +string-to-number transformation. -If you serialize a transformed schema, the transformation logic will be lost. When you rebuild it with `toSchema`, you will only get the structural schema. +### Live and persisted documents -> **Aside** (Why transformations are excluded) -> -> A transformation is user code (functions). JSON cannot store functions, and serializing functions as strings would not be safe or portable. +A live `Document` can contain functions in its ordinary annotations. These callbacks allow compilers to handle custom +behavior: -### Only built-in checks can be represented +- a check can provide `toJsonSchema` +- a declaration or check can provide `toCode` -Checks are stored as `Filter` / `FilterGroup` nodes with a small `meta` object. +Functions cannot cross the JSON persistence boundary. `toJson` removes them and keeps only JSON-valued ordinary +annotations. Nested JSON arrays and objects are preserved; a complete annotation value is omitted when it contains a +function, `undefined`, `bigint`, a symbol, a cycle, or another non-JSON value. -Only checks that match the built-in meta definitions are supported, such as: +Structural values such as bigint literals and registered unique symbols have dedicated canonical encodings. That does not +make bigint or symbol values valid generic annotations. -- string checks: `isMinLength`, `isPattern`, `isUUID`, ... -- number checks: `isInt`, `isBetween`, `isMultipleOf`, ... -- bigint checks: `isGreaterThanBigInt`, ... -- array checks: `isLength`, `isUnique`, ... -- object checks: `isMinProperties`, ... -- date checks: `isBetweenDate`, ... +### Persistence identities -Custom predicates (for example `Schema.filter((x) => ...)`) are not supported, because the representation has nowhere to store the function. +Opaque declarations and checks need a stable identity before they can be persisted: -### Annotations are filtered +```ts +interface RepresentationAnnotation { + readonly id: string + readonly payload: Schema.Json +} -Annotations are stored as a record, but: +interface CheckRepresentationAnnotation extends RepresentationAnnotation { + readonly schemas?: ReadonlyArray +} +``` + +`id` selects a reviver, `payload` contains its JSON configuration, and a check can use `schemas` for schema dependencies. +This replaces the previous closed set of check metadata. Custom declarations and checks are therefore persistable when +they provide a representation identity and the consumer provides a matching reviver. + +An unannotated custom declaration or leaf filter can still exist in a live representation, but `toJson` rejects it because +there is no portable way to reconstruct its user code. + +## Creating representations + +Use `Schema.toRepresentation` when starting from a schema: + +```ts +import { Schema } from "effect" + +const document = Schema.toRepresentation( + Schema.Struct({ name: Schema.NonEmptyString }) +) +``` + +Use the lower-level functions when working directly with ASTs or several roots: + +```ts +import { Schema, SchemaRepresentation } from "effect" + +const document = SchemaRepresentation.toRepresentation(Schema.String.ast) + +const multiDocument = SchemaRepresentation.toRepresentations([ + Schema.String.ast, + Schema.Number.ast +]) +``` + +By default, only candidates with a resolved identifier are placed in `references`. Anonymous non-recursive nodes remain +inline even when the same AST occurs more than once. Recursive schemas always require a reference; when no identifier is +available, the converter assigns a synthetic name such as `Objects_` or `Suspend_`. + +The default policy uses an explicit `identifier` as the reference name. Reusing the same schema shares its reference. +Context-only copies created through `SchemaAST.replaceContext` retain the original AST as their reference owner, including +across several successive context changes. Context still belongs to each occurrence and does not, by itself, create a new +candidate. Independently constructed ASTs are not canonicalized merely because they are structurally equal. When distinct +schemas request the same name, the first schema keeps it and later schemas receive numeric suffixes in encounter order, +such as `Value_1` and `Value_2`. Internal `~identifier` annotations are fallback allocation hints; their generated names +use the `Encoded` suffix and follow the same collision rules. + +`toMultiDocument(document)` wraps a single document when a compiler requires multiple roots. -- only values that look like JSON primitives (plus `bigint` and `symbol` in the in-memory form) are kept -- some annotation keys are dropped using an internal blacklist +### Reference policies -In practice, documentation annotations like `title` and `description` are preserved, while complex values (functions, instances, nested objects) are ignored. +`Schema.toRepresentation`, `SchemaRepresentation.toRepresentation`, and `SchemaRepresentation.toRepresentations` accept +an optional `referencePolicy`. The policy runs once for every candidate, after occurrences across all roots have been +counted: -### Declarations need a reviver +```ts +interface ReferencePolicyInput { + readonly ast: SchemaAST.AST + readonly occurrences: number + readonly identifier: string | undefined +} +``` -Some runtime schemas are represented as `Declaration` nodes. Rebuilding them requires a "reviver" function. +Return a reference name to move the candidate into `references`, or return `undefined` to leave it inline. This example +extracts anonymous candidates only when the same candidate occurs more than once, while still respecting identifiers: -`toSchema` ships with a default reviver (`toSchemaDefaultReviver`) that recognizes a fixed set of constructors, including: +```ts +import { Schema, SchemaRepresentation } from "effect" -- `effect/Option`, `effect/Result`, `effect/Exit`, ... -- `ReadonlyMap`, `ReadonlySet` -- `RegExp`, `URL`, `Date` -- `FormData`, `URLSearchParams`, `Uint8Array` -- `DateTime.Utc`, `effect/Duration` +const Item = Schema.Struct({ name: Schema.String }) -If your document contains other declarations, pass a custom `reviver` to `toSchema`. +const document = SchemaRepresentation.toRepresentations([Item.ast, Item.ast], { + referencePolicy: ({ ast, identifier, occurrences }) => identifier ?? (occurrences > 1 ? `${ast._tag}_` : undefined) +}) -## JSON round-tripping +console.log(document.representations) +// [ +// { _tag: "Reference", $ref: "Objects_" }, +// { _tag: "Reference", $ref: "Objects_" } +// ] +``` + +Occurrences are counted by candidate identity, not structural equality. Two independently constructed but structurally +equal ASTs are separate candidates and each reports one occurrence. Recursive candidates cannot remain inline: if a policy +returns `undefined` for one, the converter assigns a synthetic reference name. If different candidates request the same +name, later candidates receive numeric suffixes. + +`Schema.toJsonSchemaDocument(schema, options)` forwards the policy after deriving the schema's canonical JSON codec, so +the policy receives canonical JSON-encoded ASTs. `OpenApi.fromApi(api, options)` applies the policy at the same canonical +JSON boundary when deciding which schemas become OpenAPI components. A `referencePolicy` passed to +`SchemaRepresentation.toJsonSchemaDocument` or `toJsonSchemaMultiDocument` cannot change an existing document's references; +pass it while creating the `Document` or `MultiDocument` instead. + +## JSON persistence ### `toJson` / `fromJson` -- `toJson(document)` returns JSON-compatible data (safe to `JSON.stringify`) -- `fromJson(unknown)` validates and parses JSON data back into a `Document` +`toJson(document)` projects and validates a live document, then returns a `Schema.Json` value suitable for storage or +transport. `fromJson(input)` validates persisted JSON and returns a `Document`; it does not restore runtime callbacks. -Internally, these functions use a canonical JSON codec for `Document$`. This is why values like `bigint` in annotations are encoded as strings in the JSON form and restored on decode. +The multi-root equivalents are `toJsonMultiDocument` and `fromJsonMultiDocument`. -## Rebuilding runtime schemas +```ts +import { Schema, SchemaRepresentation } from "effect" -### `toSchema` +const live = Schema.toRepresentation( + Schema.String.check(Schema.isMinLength(3)) +) -`toSchema(document)` walks the representation tree and recreates a runtime schema. +const json = SchemaRepresentation.toJson(live) +const persisted = SchemaRepresentation.fromJson(json) +``` -What it does: +Persisted `Declaration` and `Filter` nodes must contain a representation identity. `fromJson` validates the document but +does not require the corresponding revivers until reconstruction. -- rebuilds the structural schema nodes (`Struct`, `Tuple`, `Union`, ...) -- resolves references from `document.references` -- supports recursive references using `Schema.suspend` -- re-attaches stored annotations via `.annotate(...)` and `.annotateKey(...)` -- re-applies supported checks via `.check(...)` +## Rebuilding runtime schemas -If you need custom handling for declarations: +### `fromRepresentation` + +`fromRepresentation` rebuilds structural nodes, resolves references, restores recursion, reattaches annotations, and +reapplies checks. Revivers are resolved by `id`; none are installed implicitly, so the `revivers` array is required even +when it is empty. ```ts -SchemaRepresentation.toSchema(document, { - reviver: (declaration, recur) => { - // Return a runtime schema to override how a Declaration is rebuilt. - // Return undefined to fall back to the default behavior. - return undefined - } +import { Schema, SchemaRepresentation } from "effect" + +const json = SchemaRepresentation.toJson( + Schema.toRepresentation( + Schema.String.check(Schema.isMinLength(3)) + ) +) + +const document = SchemaRepresentation.fromJson(json) +const rebuilt = SchemaRepresentation.fromRepresentation(document, { + revivers: [SchemaRepresentation.isMinLengthReviver] }) + +console.log(Schema.is(rebuilt)("abc")) +// true +console.log(Schema.is(rebuilt)("a")) +// false ``` -## JSON Schema output +`SchemaRepresentation` exports individual revivers for built-in declarations and checks, such as +`OptionReviver`, `DateReviver`, and `isMinLengthReviver`. Supply every reviver required by the document; a missing or +duplicate `id`, or a payload that does not satisfy its reviver's `payloadSchema`, is an error. -### `toJsonSchemaDocument` / `toJsonSchemaMultiDocument` +`fromRepresentations` rebuilds the ordered roots of a `MultiDocument` in a shared reference environment. Only references +reachable from those roots are revived. -These functions convert a `Document` or `MultiDocument` into a Draft 2020-12 JSON Schema document. +### Custom revivers -This is useful for tooling that expects JSON Schema, or for producing OpenAPI-compatible schema pieces (depending on your pipeline). +There are separate reviver contracts for opaque declarations, leaf filters, and opaque filter groups: + +- `DeclarationReviver

` +- `FilterReviver

` +- `FilterGroupReviver

` + +Use `makeReviverDeclaration`, `makeReviverFilter`, and `makeReviverFilterGroup` to infer `P` from `payloadSchema`. + +```ts +import { Schema, SchemaRepresentation } from "effect" + +const id = "acme/schema/minLength" + +function minLength( + minimum: number, + annotations?: Schema.Annotations.Filter +) { + return Schema.makeFilter((value) => value.length >= minimum, { + ...annotations, + representation: { id, payload: { minimum } } + }) +} + +const minLengthReviver = SchemaRepresentation.makeReviverFilter( + id, + Schema.Struct({ minimum: Schema.Number }), + ({ annotations, payload }) => minLength(payload.minimum, annotations) +) +``` + +The same reviver can then be included in the `revivers` array passed to `fromRepresentation` or +`fromRepresentations`. + +## JSON Schema + +### Exporting JSON Schema + +For a runtime schema, prefer `Schema.toJsonSchemaDocument(schema)`. It first derives the schema's canonical JSON codec, +then compiles its encoded representation to JSON Schema Draft 2020-12. The default reference policy extracts candidates +with resolved identifiers and leaves anonymous non-recursive candidates inline, including repeated declarations. Recursive +candidates still receive references, using a synthetic name when necessary. Pass `referencePolicy` in the options to use a +different allocation rule. + +At the lower level, `SchemaRepresentation.toJsonSchemaDocument(document)` compiles a live `Document`, and +`toJsonSchemaMultiDocument` compiles a live `MultiDocument`. Check-level `toJsonSchema` callbacks contribute JSON Schema +constraints. Opaque declarations that have not been structurally lowered compile to an unconstrained JSON Schema. + +`toJsonSchema` callbacks must treat their input schemas as immutable and return a valid JSON Schema object graph. After a +callback returns, it must not mutate that object or anything reachable from it; returning a new graph is the supported way +to produce different output during a later compilation. The compiler may cache structural comparisons while +deduplicating completed definitions, so mutating a previously returned graph can make equality results stale. + +Definitions are compared only with definitions in the same internal fallback-identifier group. Equal definitions in +different groups and definitions with explicit identifiers remain distinct. After compilation, local `#/$defs/...` +references are rewritten to the surviving definition, including references returned directly by callbacks. External +references and other local JSON Pointers remain unchanged. + +Because compiler callbacks are not persisted, compile the live document before calling `toJson`, or rebuild and lower the +schema with revivers first. + +### Importing JSON Schema + +`SchemaRepresentation.fromJsonSchemaDocument` imports a JSON Schema Draft 2020-12 document as a runtime `Schema.Top`. +It does not return a representation document. + +Only direct local references to top-level definitions in the form `#/$defs/` are supported. Root +references, external references, and pointers below a definition throw an `Unsupported reference` error. A direct +reference to a missing definition throws an `Invalid reference` error. + +`fromJsonSchemaMultiDocument` returns the ordered root schemas. It translates only definitions reachable from those +roots. To pass the result to a representation compiler, call `toRepresentations` with the returned schemas' ASTs. + +Import translates a Draft 2020-12 subset. `$dynamicRef`, `contains`, `dependentRequired`, `dependentSchemas`, `not`, +active `if` / `then` / `else`, `unevaluatedItems`, and `unevaluatedProperties` throw an +`Unsupported JSON Schema keyword` error. Inactive conditional keywords and `minContains` / `maxContains` without +`contains` have no validation effect and are ignored. Unknown extension keywords are ignored and their semantics are not +enforced. Objects and arrays used as `const` values or `enum` members throw an +`Unsupported structured JSON Schema value` error. The optional `onEnter` callback can normalize each JSON Schema node +before it is translated. + +Intersections of overlapping unions are limited to disjoint root-type partitions and finite primitive `anyOf` literal +sets. Other union intersections, including cases that would duplicate a nested choice, throw an +`Unsupported intersection of overlapping unions` error. + +Regular expression constraints reached during translation are rejected by default because imported patterns use the +runtime's native regular expression engine and may block validation for an unbounded amount of time. Set +`patterns: "apply"` only for trusted documents. Set `patterns: "ignore"` to skip reached pattern constraints explicitly; +the resulting schema accepts values that the source document may reject. The policy includes `pattern`, the keys of +`patternProperties`, and patterns nested in `propertyNames`. Ignoring `patternProperties` also skips its value constraints +and `additionalProperties`, because matching keys cannot be determined without evaluating the patterns. ## Code generation ### `toCodeDocument` -`toCodeDocument` converts a `MultiDocument` into a structure that is convenient for generating TypeScript source. - -It: +`toCodeDocument` compiles a live `MultiDocument` into runtime and TypeScript source fragments. It: -- sorts references so non-recursive definitions can be emitted in dependency order -- keeps recursive definitions separate (they must be emitted using `Schema.suspend`) +- returns one `Code` value for each root +- sorts non-recursive references in dependency order +- keeps recursive references separate so callers can emit `Schema.suspend` - sanitizes reference names into valid JavaScript identifiers -- collects extra artifacts that must be emitted (enums, symbols, imports) - -You can customize: +- collects symbol, enum, and import artifacts -- `sanitizeReference` to control how `$ref` strings become identifiers -- `reviver` to generate custom code for `Declaration` nodes +Opaque declarations and checks provide code through their `toCode` callbacks. `toCodeDocument` does not accept a +reviver option. To generate code from persisted JSON, first reconstruct the schemas with `fromRepresentation` or +`fromRepresentations`, then create a new live representation so the revivers can restore the callbacks. # Error Handling and Formatting When validation fails, Schema produces structured error objects that describe what went wrong. Formatters turn those error objects into human-readable messages you can display to users or write to logs. +### Reporting Rejected Inputs + +By default, schema issues neither retain rejected input values nor include them in formatted messages. Pass `{ reportInput: true }` to a parser when the additional diagnostic context is worth the disclosure and retention risk: + +```ts +import { Result, Schema, SchemaIssue, SchemaParser } from "effect" + +const result = SchemaParser.decodeUnknownResult(Schema.String)(1, { reportInput: true }) +const formatIssue = SchemaIssue.makeFormatterDefault() + +if (Result.isFailure(result)) { + SchemaIssue.hasInput(result.failure) // true + if (SchemaIssue.hasInput(result.failure)) { + result.failure.input // 1 + } + formatIssue(result.failure) // "Expected string, got 1" +} +``` + +Value-bearing issues created by the parser then expose an enumerable own `input` field. The input is retained by reference, not copied. Use `SchemaIssue.hasInput(issue)` instead of checking `issue.input !== undefined`, because a present input whose value is `undefined` is distinct from an issue that does not retain input. + +Enabling this option can retain or disclose secrets, personally identifiable information, and large object graphs. Object enumeration, spread, serialization, `SchemaIssue.makeFormatterDefault()`, `SchemaError.message`, and Standard Schema messages may expose the retained value. A Standard Schema failure still contains only its standard `message` and `path` fields; the input can appear inside `message`, but no non-standard `input` field is added. + +User-created `SchemaIssue.Issue` values returned directly by declarations, checks, transformations, or middleware are not modified. To make a custom value-bearing issue honor `reportInput`, pass the callback's input and effective parse options to its constructor, for example `new SchemaIssue.InvalidValue(annotations, input, options)`. + ### Formatters #### StandardSchemaV1 formatter @@ -6390,7 +6250,7 @@ Output: { issues: [ { path: [ 'a' ], message: 'Missing key' }, - { path: [ 'b' ], message: 'Expected a value with a length of at least 1, got ""' } + { path: [ 'b' ], message: 'Expected a value with a length of at least 1' } ] } */ @@ -6586,7 +6446,7 @@ if (r._tag === "Failure") { { issues: [ { - message: 'Expected a value with a length of at least 1, got ""', + message: 'Expected a value with a length of at least 1', path: [ 'a' ] }, { message: 'Missing key', path: [ 'c', 0 ] }, diff --git a/packages/effect/benchmark/Pool.ts b/packages/effect/benchmark/Pool.ts new file mode 100644 index 000000000..094b50eb4 --- /dev/null +++ b/packages/effect/benchmark/Pool.ts @@ -0,0 +1,76 @@ +import { Effect, Exit, Pool, Scope } from "effect" +import { Bench } from "tinybench" + +const poolSize = 10 +let nextItem = 0 + +const acquire = Effect.sync(() => ++nextItem) +const getAll = (pool: Pool.Pool) => + Effect.all( + Array.from({ length: poolSize }, () => Pool.get(pool)), + { concurrency: "unbounded", discard: true } + ) + +const makeFixed = Effect.scoped( + Effect.flatMap( + Pool.make({ acquire, size: poolSize }), + getAll + ) +) + +const makeWithTTL = Effect.scoped( + Effect.flatMap( + Pool.makeWithTTL({ + acquire, + min: 0, + max: poolSize, + timeToLive: "1 minute" + }), + getAll + ) +) + +const poolScope = await Effect.runPromise(Scope.make()) +const fixedPool = await Effect.runPromise( + Pool.make({ acquire, size: poolSize }).pipe(Scope.provide(poolScope)) +) +const ttlPool = await Effect.runPromise( + Pool.makeWithTTL({ + acquire, + min: 0, + max: poolSize, + timeToLive: "1 minute" + }).pipe(Scope.provide(poolScope)) +) +const invalidationPool = await Effect.runPromise( + Pool.make({ acquire, size: 1 }).pipe(Scope.provide(poolScope)) +) + +await Effect.runPromise(Effect.scoped(Effect.all([getAll(fixedPool), getAll(ttlPool)], { discard: true }))) + +let invalidationItem = await Effect.runPromise(Effect.scoped(Pool.get(invalidationPool))) +const invalidateAndReplace = Effect.scoped( + Effect.gen(function*() { + yield* Pool.invalidate(invalidationPool, invalidationItem) + invalidationItem = yield* Pool.get(invalidationPool) + }) +) + +const bench = new Bench() + +const useItem = (item: number) => Effect.succeed(item) + +bench + .add("make fixed pool (10 items)", () => Effect.runPromise(makeFixed)) + .add("make TTL pool (10 items)", () => Effect.runPromise(makeWithTTL)) + .add("get and release (fixed pool)", () => Effect.runPromise(Effect.scoped(Pool.get(fixedPool)))) + .add("get and release (TTL pool)", () => Effect.runPromise(Effect.scoped(Pool.get(ttlPool)))) + .add("use (fixed pool)", () => Effect.runPromise(Pool.use(fixedPool, useItem))) + .add("use (TTL pool)", () => Effect.runPromise(Pool.use(ttlPool, useItem))) + .add("get and release (10 concurrent)", () => Effect.runPromise(Effect.scoped(getAll(fixedPool)))) + .add("invalidate and replace", () => Effect.runPromise(invalidateAndReplace)) + +await bench.run() +await Effect.runPromise(Scope.close(poolScope, Exit.void)) + +console.table(bench.table()) diff --git a/packages/effect/benchmark/http/multipart.ts b/packages/effect/benchmark/http/multipart.ts new file mode 100644 index 000000000..22ef22378 --- /dev/null +++ b/packages/effect/benchmark/http/multipart.ts @@ -0,0 +1,122 @@ +import { Effect, Stream } from "effect" +import * as Multipart from "effect/unstable/http/Multipart" +import * as MultipartParser from "effect/unstable/http/MultipartParser" +import { Bench } from "tinybench" + +const bench = new Bench() + +const boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" +const headers = { + "content-type": `multipart/form-data; boundary=${boundary}` +} +const encoder = new TextEncoder() + +const filePayload = (fileSize: number, chunkSize: number): Array => { + const head = encoder.encode( + `--${boundary}\r\ncontent-disposition: form-data; name="file"; filename="file.bin"\r\ncontent-type: application/octet-stream\r\n\r\n` + ) + const body = new Uint8Array(fileSize) + for (let i = 0; i < fileSize; i++) { + body[i] = i % 251 + } + const tail = encoder.encode(`\r\n--${boundary}--\r\n`) + const payload = new Uint8Array(head.length + body.length + tail.length) + payload.set(head, 0) + payload.set(body, head.length) + payload.set(tail, head.length + body.length) + const chunks: Array = [] + for (let i = 0; i < payload.length; i += chunkSize) { + chunks.push(payload.subarray(i, i + chunkSize)) + } + return chunks +} + +const fieldsPayload = (fieldCount: number): Array => { + let out = "" + for (let i = 0; i < fieldCount; i++) { + out += `--${boundary}\r\ncontent-disposition: form-data; name="field${i}"\r\n\r\nvalue of field number ${i}\r\n` + } + out += `--${boundary}--\r\n` + return [encoder.encode(out)] +} + +const file16MiB64KiB = filePayload(16 * 1024 * 1024, 64 * 1024) +const file16MiB4KiB = filePayload(16 * 1024 * 1024, 4 * 1024) +const file1MiB64KiB = filePayload(1024 * 1024, 64 * 1024) +const fields100 = fieldsPayload(100) + +const runParser = (chunks: Array) => { + const parser = MultipartParser.make({ + headers, + onField() {}, + onFile() { + return () => {} + }, + onError(error) { + throw new Error(`unexpected error: ${error._tag}`) + }, + onDone() {} + }) + for (let i = 0; i < chunks.length; i++) { + parser.write(chunks[i]) + } + parser.end() +} + +const runChannelDrain = (chunks: Array) => + Effect.runPromise( + Stream.fromArray(chunks).pipe( + Stream.pipeThroughChannel(Multipart.makeChannel(headers)), + Stream.mapEffect((part) => part._tag === "File" ? Stream.runDrain(part.content) : Effect.void), + Stream.runDrain + ) + ) + +// Stream.fromArray emits the chunks in one batch, so these are single-pull controls. +const runChannelCollect = (chunks: Array) => + Effect.runPromise( + Stream.fromArray(chunks).pipe( + Stream.pipeThroughChannel(Multipart.makeChannel(headers)), + Stream.mapEffect((part) => part._tag === "File" ? part.contentEffect : Effect.void), + Stream.runDrain + ) + ) + +// rechunk(1) forces one upstream pull per chunk, simulating network reads and +// guarding against quadratic accumulation across pulls. +const runChannelCollectStreaming = (chunks: Array) => + Effect.runPromise( + Stream.fromArray(chunks).pipe( + Stream.rechunk(1), + Stream.pipeThroughChannel(Multipart.makeChannel(headers)), + Stream.mapEffect((part) => part._tag === "File" ? part.contentEffect : Effect.void), + Stream.runDrain + ) + ) + +bench + .add("parser: 16MiB file / 64KiB chunks", function() { + runParser(file16MiB64KiB) + }) + .add("parser: 16MiB file / 4KiB chunks", function() { + runParser(file16MiB4KiB) + }) + .add("parser: 100 small fields", function() { + runParser(fields100) + }) + .add("channel drain: 16MiB file / 64KiB chunks", async function() { + await runChannelDrain(file16MiB64KiB) + }) + .add("channel collect: 16MiB file / 64KiB chunks", async function() { + await runChannelCollect(file16MiB64KiB) + }) + .add("channel collect: 1MiB file / 64KiB chunks", async function() { + await runChannelCollect(file1MiB64KiB) + }) + .add("channel collect streaming: 16MiB file / 64KiB chunks", async function() { + await runChannelCollectStreaming(file16MiB64KiB) + }) + +await bench.run() + +console.table(bench.table()) diff --git a/packages/effect/benchmark/http/serverAllocations.ts b/packages/effect/benchmark/http/serverAllocations.ts new file mode 100644 index 000000000..927a0cac5 --- /dev/null +++ b/packages/effect/benchmark/http/serverAllocations.ts @@ -0,0 +1,138 @@ +import { HttpRouter, HttpServerResponse } from "effect/unstable/http" +import * as inspector from "node:inspector/promises" + +const WARMUP = 5_000 +const REQUESTS = 50_000 +const RETAINED_LIMIT = 64 +const CHURN_LIMIT = 17_000 +const DEFERRED_REQUESTS = 20_000 +const DEFERRED_CONCURRENCY = 32 +// main deferred a per-request span-end task via setImmediate (~4k bytes/request +// held until the loop turns); the native-tracer fast path ends the span inline. +const DEFERRED_LIMIT = 512 + +const gc = (globalThis as any).gc as undefined | (() => void) +if (gc === undefined) { + console.error("run with --expose-gc") + process.exit(1) +} + +const session = new inspector.Session() +session.connect() + +const { dispose, handler } = HttpRouter.toWebHandler( + HttpRouter.add("GET", "/ping", HttpServerResponse.text("pong")), + { disableLogger: true } +) + +const run = async (count: number) => { + for (let i = 0; i < count; i++) { + const response = await handler(new Request("http://localhost/ping")) + await response.arrayBuffer() + } +} + +// a promise-chained load never yields to the event loop, so any per-request +// macrotask (setImmediate) the request path schedules accumulates unrun +const runConcurrent = async (count: number) => { + let remaining = count + const worker = async () => { + while (remaining > 0) { + remaining-- + const response = await handler(new Request("http://localhost/ping")) + await response.arrayBuffer() + } + } + await Promise.all(Array.from({ length: DEFERRED_CONCURRENCY }, worker)) +} + +interface ProfileNode { + callFrame: { functionName: string; url: string; lineNumber: number } + selfSize: number + children?: Array +} + +const topFrames = (head: ProfileNode) => { + const byFrame = new Map() + let total = 0 + const visit = (node: ProfileNode) => { + if (node.selfSize > 0) { + const f = node.callFrame + const url = f.url.replace(/^.*\/(packages|node_modules)\//, "$1/") + const key = `${f.functionName || "(anonymous)"} @ ${url}:${f.lineNumber + 1}` + byFrame.set(key, (byFrame.get(key) ?? 0) + node.selfSize) + total += node.selfSize + } + node.children?.forEach(visit) + } + visit(head) + return { + total, + frames: [...byFrame.entries()].sort((a, b) => b[1] - a[1]).slice(0, 15) + } +} + +// Allow FinalizationRegistry callbacks to run before measuring retained heap. +const heapUsed = async () => { + for (let i = 0; i < 5; i++) { + gc() + await new Promise((resolve) => setImmediate(resolve)) + } + return process.memoryUsage().heapUsed +} + +await run(WARMUP) + +const heapBefore = await heapUsed() +await run(REQUESTS) +const heapAfter = await heapUsed() + +// A promise-chained burst never yields to the event loop, so synchronous-GC +// heap deltas expose per-request work deferred into macrotask queues. +await runConcurrent(WARMUP) +gc() +gc() +const deferredBefore = process.memoryUsage().heapUsed +await runConcurrent(DEFERRED_REQUESTS) +gc() +gc() +const deferredAfter = process.memoryUsage().heapUsed + +await session.post("HeapProfiler.startSampling", { + samplingInterval: 16384, + includeObjectsCollectedByMajorGC: true, + includeObjectsCollectedByMinorGC: true +}) +await run(REQUESTS) +const { profile } = await session.post("HeapProfiler.stopSampling") + +await dispose() + +const { frames, total } = topFrames(profile.head as ProfileNode) +const churnPerRequest = Math.round(total / REQUESTS) +const retainedPerRequest = Math.round((heapAfter - heapBefore) / REQUESTS) +const deferredPerRequest = Math.round((deferredAfter - deferredBefore) / DEFERRED_REQUESTS) + +console.log(`requests: ${REQUESTS}`) +console.log(`allocated/request: ${churnPerRequest} bytes (limit ${CHURN_LIMIT})`) +console.log(`retained/request: ${retainedPerRequest} bytes (limit ${RETAINED_LIMIT})`) +console.log(`deferred/request: ${deferredPerRequest} bytes (limit ${DEFERRED_LIMIT})`) +console.log("top allocation sites:") +for (const [key, size] of frames) { + const pct = ((size / total) * 100).toFixed(1).padStart(5) + console.log(` ${pct}% ${(size / 1024 / 1024).toFixed(2).padStart(7)} MiB ${key}`) +} + +if (retainedPerRequest > RETAINED_LIMIT) { + console.error(`FAIL: retained heap grows ${retainedPerRequest} bytes/request`) + process.exit(1) +} +if (deferredPerRequest > DEFERRED_LIMIT) { + console.error(`FAIL: microtask-only burst holds ${deferredPerRequest} bytes/request exceeds ${DEFERRED_LIMIT}`) + process.exit(1) +} +if (churnPerRequest > CHURN_LIMIT) { + console.error(`FAIL: allocation churn ${churnPerRequest} bytes/request exceeds ${CHURN_LIMIT}`) + process.exit(1) +} +console.log("PASS") diff --git a/packages/effect/benchmark/http/tracer.ts b/packages/effect/benchmark/http/tracer.ts new file mode 100644 index 000000000..ef9b4bbaf --- /dev/null +++ b/packages/effect/benchmark/http/tracer.ts @@ -0,0 +1,143 @@ +import { Effect } from "effect" +import * as Tracer from "effect/Tracer" +import * as Headers from "effect/unstable/http/Headers" +import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware" +import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" +import * as HttpTraceContext from "effect/unstable/http/HttpTraceContext" +import { Bench } from "tinybench" + +const bench = new Bench() + +const noopTracer = Tracer.make({ + span: (options) => + ({ + _tag: "Span", + spanId: "spanid0123456789", + traceId: "traceid0123456789traceid01234567", + sampled: true, + name: options.name, + parent: options.parent, + annotations: options.annotations, + links: options.links, + kind: options.kind, + status: { _tag: "Started", startTime: options.startTime }, + attributes: new Map(), + attribute() {}, + event() {}, + end() {}, + addLinks() {} + }) as Tracer.Span +}) + +const request = HttpServerRequest.fromWeb( + new Request("http://localhost:3000/some/path?foo=bar", { + headers: { + "user-agent": "bench/1.0", + "accept": "application/json", + "accept-encoding": "gzip, br", + "authorization": "Bearer secret", + "cookie": "session=abc", + "x-request-id": "12345678", + "host": "localhost:3000" + } + }) +) + +const requestWithTraceparent = HttpServerRequest.fromWeb( + new Request("http://localhost:3000/some/path?foo=bar", { + headers: { + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "host": "localhost:3000" + } + }) +) + +const app = Effect.succeed(HttpServerResponse.text("ok")) + +const tracedApp = HttpMiddleware.tracer(app).pipe( + Effect.provideService(HttpServerRequest.HttpServerRequest, request), + Effect.provideService(Tracer.Tracer, noopTracer) +) + +const tracedAppNative = HttpMiddleware.tracer(app).pipe( + Effect.provideService(HttpServerRequest.HttpServerRequest, request) +) + +const tracedAppDisabled = HttpMiddleware.tracer(app).pipe( + Effect.provideService(HttpServerRequest.HttpServerRequest, request), + Effect.withTracerEnabled(false) +) + +const bareApp = app.pipe( + Effect.provideService(HttpServerRequest.HttpServerRequest, request) +) + +const withSpanApp = Effect.void.pipe( + Effect.withSpan("bench"), + Effect.provideService(Tracer.Tracer, noopTracer) +) + +const withSpanAppNoStack = Effect.void.pipe( + Effect.withSpan("bench", undefined, { captureStackTrace: false }), + Effect.provideService(Tracer.Tracer, noopTracer) +) + +const withSpanDisabled = Effect.void.pipe( + Effect.withSpan("bench"), + Effect.withTracerEnabled(false) +) + +bench + .add("bare app (no middleware)", async () => { + await Effect.runPromise(bareApp) + }) + .add("tracer middleware (noop tracer)", async () => { + await Effect.runPromise(tracedApp) + }) + .add("tracer middleware (NativeSpan)", async () => { + await Effect.runPromise(tracedAppNative) + }) + .add("tracer middleware (tracing disabled)", async () => { + await Effect.runPromise(tracedAppDisabled) + }) + .add("withSpan reused (noop tracer)", async () => { + await Effect.runPromise(withSpanApp) + }) + .add("withSpan constructed per run (noop tracer)", async () => { + await Effect.runPromise( + Effect.void.pipe( + Effect.withSpan("bench"), + Effect.provideService(Tracer.Tracer, noopTracer) + ) + ) + }) + .add("withSpan constructed per run, no stack capture", async () => { + await Effect.runPromise( + Effect.void.pipe( + Effect.withSpan("bench", undefined, { captureStackTrace: false }), + Effect.provideService(Tracer.Tracer, noopTracer) + ) + ) + }) + .add("withSpan reused, no stack capture (noop tracer)", async () => { + await Effect.runPromise(withSpanAppNoStack) + }) + .add("withSpan reused (tracing disabled)", async () => { + await Effect.runPromise(withSpanDisabled) + }) + .add("fromHeaders: no trace headers", () => { + HttpTraceContext.fromHeaders(request.headers) + }) + .add("fromHeaders: traceparent", () => { + HttpTraceContext.fromHeaders(requestWithTraceparent.headers) + }) + .add("Headers.redact (7 headers, default names)", () => { + Headers.redact(request.headers, ["authorization", "cookie", "set-cookie", "x-api-key"]) + }) + .add("Request.toURL", () => { + HttpServerRequest.toURL(request) + }) + +await bench.run() +console.table(bench.table()) diff --git a/packages/effect/benchmark/httpapi/sseClientDecoder.ts b/packages/effect/benchmark/httpapi/sseClientDecoder.ts new file mode 100644 index 000000000..0b41490a8 --- /dev/null +++ b/packages/effect/benchmark/httpapi/sseClientDecoder.ts @@ -0,0 +1,52 @@ +import { Effect, Schema, Stream } from "effect" +import { HttpClient, HttpClientResponse } from "effect/unstable/http" +import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi" +import { Bench } from "tinybench" + +const Event = Schema.Struct({ + event: Schema.Literal("tick"), + data: Schema.String +}) + +const Api = HttpApi.make("Api").add( + HttpApiGroup.make("test").add( + HttpApiEndpoint.get("events", "/events", { + success: HttpApiSchema.StreamSse({ + events: Event, + error: Schema.String + }) + }) + ) +) + +const httpClient = HttpClient.make((request) => + Effect.sync(() => + HttpClientResponse.fromWeb( + request, + new Response("event: tick\ndata: payload\n\n", { + status: 200, + headers: { "content-type": "text/event-stream" } + }) + ) + ) +) + +const client = await Effect.runPromise(HttpApiClient.makeWith(Api, { + baseUrl: "http://localhost", + httpClient +})) +const consumeResponse = Effect.flatMap(client.test.events({}), Stream.runDrain) +const responsesPerIteration = 25 +const responses = Array.from({ length: responsesPerIteration }) +const runIteration = Effect.forEach(responses, () => consumeResponse, { + concurrency: 1, + discard: true +}) + +const bench = new Bench({ time: 3000 }) + +bench.add(`${responsesPerIteration} sequential SSE responses through one client`, () => Effect.runPromise(runIteration)) + +await bench.run() + +console.table(bench.table()) diff --git a/packages/effect/benchmark/rpc/RpcSerialization.ts b/packages/effect/benchmark/rpc/RpcSerialization.ts new file mode 100644 index 000000000..e0665b14d --- /dev/null +++ b/packages/effect/benchmark/rpc/RpcSerialization.ts @@ -0,0 +1,297 @@ +import { Effect, Exit, Schema } from "effect" +import { Rpc, RpcSerialization } from "effect/unstable/rpc" +import assert from "node:assert/strict" +import { cpus } from "node:os" +import { Bench } from "tinybench" + +const User = Schema.Struct({ + id: Schema.String, + displayName: Schema.String, + email: Schema.String, + active: Schema.Boolean, + roles: Schema.Array(Schema.String) +}) + +const SearchUsers = Rpc.make("SearchUsers", { + payload: Schema.Struct({ + organizationId: Schema.String, + query: Schema.String, + page: Schema.Number, + pageSize: Schema.Number, + filters: Schema.Struct({ + active: Schema.optional(Schema.Boolean), + roles: Schema.Array(Schema.String) + }) + }), + success: Schema.Array(User), + error: Schema.Struct({ + code: Schema.String, + message: Schema.String + }) +}) + +const Event = Schema.Struct({ + sequence: Schema.Number, + timestamp: Schema.Date, + level: Schema.Literals(["info", "warning", "error"]), + message: Schema.String, + attributes: Schema.Record(Schema.String, Schema.String) +}) + +const searchPayload = { + organizationId: "org-effect", + query: "schema binary", + page: 3, + pageSize: 25, + filters: { + active: true, + roles: ["maintainer", "contributor"] + } +} + +const users = Array.from({ length: 12 }, (_, index) => ({ + id: `user-${index.toString().padStart(3, "0")}`, + displayName: `Benchmark User ${index}`, + email: `benchmark-${index}@example.com`, + active: index % 4 !== 0, + roles: index % 3 === 0 ? ["maintainer", "contributor"] : ["contributor"] +})) + +const makeEvent = (index: number) => ({ + sequence: index + 1, + timestamp: new Date(1_756_000_000_000 + index * 1_000), + level: index % 11 === 0 ? "warning" as const : "info" as const, + message: `Processed RPC event ${index + 1}`, + attributes: { + region: ["eu-west-1", "us-east-1", "ap-southeast-2"][index % 3]!, + worker: `worker-${index % 8}`, + attempt: String(index % 3 + 1) + } +}) + +const events = [ + makeEvent(0), + ...Array.from({ length: 31 }, (_, index) => makeEvent(index + 1)) +] satisfies Schema.NonEmptyArray["Type"] + +interface BenchmarkCase { + readonly name: string + readonly schema: Schema.Codec + readonly value: unknown + readonly envelope: (hole: unknown) => unknown + readonly hole: (envelope: unknown) => unknown +} + +const getHole = (key: PropertyKey) => (envelope: unknown): unknown => { + assert(typeof envelope === "object" && envelope !== null && key in envelope) + return (envelope as Record)[key] +} + +const cases: ReadonlyArray = [ + { + name: "request / nested search payload", + schema: SearchUsers.payloadSchema, + value: searchPayload, + envelope: (payload) => ({ + _tag: "Request", + id: 1, + tag: SearchUsers._tag, + payload, + headers: [ + ["authorization", "Bearer benchmark-token"], + ["x-request-id", "benchmark-request-0001"] + ], + traceId: "0123456789abcdef0123456789abcdef", + spanId: "0123456789abcdef", + sampled: true + }), + hole: getHole("payload") + }, + { + name: "exit / 12-user success", + schema: Rpc.exitSchema(SearchUsers), + value: Exit.succeed(users), + envelope: (exit) => ({ + _tag: "Exit", + requestId: 1, + exit + }), + hole: getHole("exit") + }, + { + name: "chunk / 32 events", + schema: Schema.NonEmptyArray(Event), + value: events, + envelope: (values) => ({ + _tag: "Chunk", + requestId: 1, + values + }), + hole: getHole("values") + } +] + +const schemaBinary = Effect.runSync( + RpcSerialization.RpcSerialization.pipe(Effect.provide(RpcSerialization.layerSchemaBinary())) +) + +const formats = [ + { name: "NDJSON", serialization: RpcSerialization.ndjson }, + { name: "SchemaBinary", serialization: schemaBinary } +] as const + +const warmupIterations = 500 +const iterations = 5_000 +// A serialization may carry state from one frame to the next, so the decode +// task walks a stream in order and never feeds the same frame twice. +const streamLength = warmupIterations + iterations + 1 +// Frames a connection sends before its serialization has settled. +const warmupFrames = 8 + +interface Prepared { + readonly caseName: string + readonly formatName: string + readonly firstFrameSize: number + readonly steadyFrameSize: number + readonly encode: () => unknown + readonly decode: () => unknown +} + +const toBytes = (encoded: Uint8Array | string | undefined): Uint8Array => { + assert(encoded instanceof Uint8Array) + return encoded.slice() +} + +const prepared = cases.flatMap((testCase): ReadonlyArray => + formats.map(({ name, serialization }) => { + const encodeHole = Schema.encodeUnknownSync(serialization.codecFor(testCase.schema)) + const decodeHole = Schema.decodeUnknownSync(serialization.codecFor(testCase.schema)) + // A binary serialization fills the hole with bytes; a JSON-shaped one fills + // it with the value itself. + const encodedHole = encodeHole(testCase.value) + const hole = encodedHole instanceof Uint8Array ? encodedHole.slice() : encodedHole + const encoder = serialization.makeUnsafe() + const encode = () => encoder.encode(testCase.envelope(encodeHole(testCase.value))) + const firstFrame = toBytes(encode()) + let steadyFrame = firstFrame + for (let i = 0; i < warmupFrames; i++) steadyFrame = toBytes(encode()) + + const streamWriter = serialization.makeUnsafe() + const stream = Array.from( + { length: streamLength }, + () => toBytes(streamWriter.encode(testCase.envelope(hole)) as Uint8Array) + ) + let decoder = serialization.makeUnsafe() + let index = 0 + const decode = () => { + if (index === stream.length) { + decoder = serialization.makeUnsafe() + index = 0 + } + const envelopes = decoder.decode(stream[index++]) + assert.strictEqual(envelopes.length, 1) + return decodeHole(testCase.hole(envelopes[0])) + } + + assert.deepStrictEqual(decode(), testCase.value) + + return { + caseName: testCase.name, + formatName: name, + firstFrameSize: firstFrame.length, + steadyFrameSize: steadyFrame.length, + encode, + decode + } + }) +) + +console.log(`${process.platform} ${process.arch}; ${cpus()[0]?.model ?? "unknown CPU"}; Node ${process.version}`) +console.log( + "End-to-end operations include the payload codec plus RPC envelope framing; codec construction is excluded." +) +console.log("SchemaBinary fingerprints envelopes only and keeps every frame independently decodable.") +console.log( + "First-frame sizes use a fresh serializer; steady sizes and throughput reuse one as on a long-lived connection, and decode walks a stream in frame order." +) +console.log( + `${warmupIterations.toLocaleString()} warmup operations and ${iterations.toLocaleString()} measured operations per case, format, and direction.` +) + +console.table(prepared.map((entry) => ({ + Case: entry.caseName, + Format: entry.formatName, + "First frame bytes": entry.firstFrameSize, + "Steady frame bytes": entry.steadyFrameSize +}))) + +const bench = new Bench({ + iterations, + time: 0, + warmupIterations, + warmupTime: 0, + timestampProvider: "hrtimeNow" +}) +const labels = new Map() +let sink: unknown + +for (const entry of prepared) { + for (const [direction, run] of [["encode", entry.encode], ["decode", entry.decode]] as const) { + const name = `${entry.caseName} / ${entry.formatName} / ${direction}` + labels.set(name, { + caseName: entry.caseName, + formatName: entry.formatName, + direction + }) + bench.add(name, () => { + sink = run() + }) + } +} + +await bench.run() +assert.notStrictEqual(sink, undefined) + +const ndjsonThroughput = new Map() +for (const task of bench.tasks) { + const label = labels.get(task.name)! + if (label.formatName === "NDJSON" && task.result?.state === "completed") { + ndjsonThroughput.set(`${label.caseName}/${label.direction}`, task.result.throughput.mean) + } +} + +console.table(bench.tasks.map((task) => { + const label = labels.get(task.name)! + const result = task.result + if (result?.state === "errored") { + return { + Case: label.caseName, + Format: label.formatName, + Direction: label.direction, + Error: result.error.message + } + } + if (result?.state !== "completed") { + return { + Case: label.caseName, + Format: label.formatName, + Direction: label.direction, + State: result?.state ?? "missing result" + } + } + const baseline = ndjsonThroughput.get(`${label.caseName}/${label.direction}`)! + return { + Case: label.caseName, + Format: label.formatName, + Direction: label.direction, + "Throughput avg (ops/s)": Math.round(result.throughput.mean), + "vs NDJSON": `${(result.throughput.mean / baseline).toFixed(2)}x`, + "Latency med (us/op)": (result.latency.p50 * 1_000).toFixed(2), + "Latency RME": `${result.latency.rme.toFixed(2)}%`, + Samples: result.latency.samplesCount + } +})) diff --git a/packages/effect/benchmark/schema/Enums.ts b/packages/effect/benchmark/schema/Enums.ts deleted file mode 100644 index fad7abed0..000000000 --- a/packages/effect/benchmark/schema/Enums.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Schema } from "effect" -import { Bench } from "tinybench" - -/* -┌─────────┬───────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼───────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'good' │ '79.26 ± 0.73%' │ '83.00 ± 1.00' │ '14443802 ± 0.02%' │ '12048193 ± 143431' │ 12616336 │ -│ 1 │ 'bad' │ '131.52 ± 0.92%' │ '125.00 ± 0.00' │ '7976946 ± 0.01%' │ '8000000 ± 0' │ 7603261 │ -└─────────┴───────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -enum Enum { - A = "a", - B = "b" -} - -const schema = Schema.Enum(Enum) - -const good = "b" -const bad = "c" - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(valid)) -// console.log(decodeUnknownExit(invalid)) - -bench - .add("good", function() { - decodeUnknownExit(good) - }) - .add("bad", function() { - decodeUnknownExit(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/packages/effect/benchmark/schema/Optic.ts b/packages/effect/benchmark/schema/Optic.ts index 0581d5fa3..5d4741772 100644 --- a/packages/effect/benchmark/schema/Optic.ts +++ b/packages/effect/benchmark/schema/Optic.ts @@ -1,19 +1,24 @@ import { Optic, Schema } from "effect" import { Bench } from "tinybench" -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'iso get' │ '907.53 ± 1.06%' │ '834.00 ± 1.00' │ '1159005 ± 0.02%' │ '1199041 ± 1439' │ 1101891 │ -│ 1 │ 'optic get' │ '32.79 ± 0.20%' │ '42.00 ± 1.00' │ '25353263 ± 0.00%' │ '23809524 ± 580720' │ 30500447 │ -│ 2 │ 'direct get' │ '23.12 ± 0.48%' │ '41.00 ± 1.00' │ '32734753 ± 0.01%' │ '24390244 ± 580720' │ 43255789 │ -│ 3 │ 'iso replace' │ '2693.0 ± 2.87%' │ '2459.0 ± 41.00' │ '396398 ± 0.03%' │ '406669 ± 6669' │ 371349 │ -│ 4 │ 'direct replace' │ '848.59 ± 0.45%' │ '792.00 ± 1.00' │ '1244301 ± 0.02%' │ '1262626 ± 1596' │ 1178430 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ +// Batching bounds sample storage and keeps sub-microsecond timings above timer resolution. +const batchSize = 1_000 +const bench = new Bench({ + iterations: 1_000, + time: 0, + warmupIterations: 100, + warmupTime: 0, + timestampProvider: "hrtimeNow" +}) +let sink: unknown -const bench = new Bench() +const batch = (run: () => A) => () => { + let value = run() + for (let index = 1; index < batchSize; index++) { + value = run() + } + sink = value +} // Define a class with nested properties class User extends Schema.Class("User")({ @@ -47,33 +52,53 @@ const iso = Schema.toIso(User).key("profile").key("address").key("street") const optic = Optic.id().key("profile").key("address").key("street") bench - .add("iso get", function() { - iso.get(user) - }) - .add("optic get", function() { - optic.get(user) - }) - .add("direct get", function() { - // oxlint-disable-next-line no-unused-expressions - user.profile.address.street - }) - .add("iso replace", function() { - iso.replace("Updated", user) - }) - .add("direct replace", function() { - // oxlint-disable-next-line no-new - new User({ - ...user, - profile: { - ...user.profile, - address: { - ...user.profile.address, - street: "Updated" + .add("iso get", batch(() => iso.get(user))) + .add("optic get", batch(() => optic.get(user))) + .add("direct get", batch(() => user.profile.address.street)) + .add("iso replace", batch(() => iso.replace("Updated", user))) + .add( + "direct replace", + batch(() => + new User({ + ...user, + profile: { + ...user.profile, + address: { + ...user.profile.address, + street: "Updated" + } } - } - }) - }) + }) + ) + ) await bench.run() -console.table(bench.table()) +if (sink === undefined) { + throw new Error("Benchmark did not run") +} + +console.table(bench.table((task) => { + const result = task.result + if (result?.state === "errored") { + return { + "Task name": task.name, + Error: result.error.message + } + } + if (result?.state !== "completed") { + return { + "Task name": task.name, + State: result?.state ?? "missing result" + } + } + const latencyToNs = (value: number) => value * 1_000_000 / batchSize + return { + "Task name": task.name, + "Latency avg (ns/op)": latencyToNs(result.latency.mean).toFixed(2), + "Latency med (ns/op)": latencyToNs(result.latency.p50).toFixed(2), + "Latency RME": `${result.latency.rme.toFixed(2)}%`, + "Throughput avg (ops/s)": Math.round(result.throughput.mean * batchSize), + Samples: result.latency.samplesCount + } +})) diff --git a/packages/effect/benchmark/schema/SchemaBinary.md b/packages/effect/benchmark/schema/SchemaBinary.md new file mode 100644 index 000000000..216d07468 --- /dev/null +++ b/packages/effect/benchmark/schema/SchemaBinary.md @@ -0,0 +1,13 @@ +# SchemaBinary benchmark + +Run the benchmark from the repository root: + +```sh +nix develop -c pnpm --dir packages/effect exec node benchmark/schema/SchemaBinary.ts +``` + +The benchmark compares SchemaBinary's default and fingerprint modes with JSON and Protobuf. It measures encoded, gzip, and zstd sizes together with one-shot encode/decode throughput. + +The streaming section compares reusable SchemaBinary parsers, Protobuf delimited decoding, and NDJSON channels. It covers single frames, batches, and frames fragmented after the first byte. Codec, schema, and Protobuf descriptor construction are excluded from timings. + +Compare formats within the same case and run. Absolute throughput varies with the machine and runtime. diff --git a/packages/effect/benchmark/schema/SchemaBinary.ts b/packages/effect/benchmark/schema/SchemaBinary.ts new file mode 100644 index 000000000..7077ec325 --- /dev/null +++ b/packages/effect/benchmark/schema/SchemaBinary.ts @@ -0,0 +1,688 @@ +import { Effect, Schema, Stream } from "effect" +import { Ndjson, SchemaBinary } from "effect/unstable/encoding" +import assert from "node:assert/strict" +import { gzipSync, zstdCompressSync } from "node:zlib" +import protobuf, { type Message, type Type } from "protobufjs" +import { Bench } from "tinybench" + +const streamBatchSize = 32 +const repeatedRecordStreamSize = 200 + +const SmallRecord = Schema.Struct({ + id: Schema.Number, + active: Schema.Boolean, + score: Schema.Number, + retryCount: Schema.Number, + region: Schema.String, + verified: Schema.Boolean +}) + +const LineItem = Schema.Struct({ + sku: Schema.String, + quantity: Schema.Number, + unitPrice: Schema.Number +}) + +const NestedPayload = Schema.Struct({ + orderId: Schema.String, + customer: Schema.Struct({ + id: Schema.String, + name: Schema.String, + email: Schema.String + }), + shipping: Schema.Struct({ + street: Schema.String, + city: Schema.String, + postalCode: Schema.String, + country: Schema.String + }), + lines: Schema.Array(LineItem), + metadata: Schema.Struct({ + source: Schema.String, + campaign: Schema.String, + priority: Schema.Boolean + }) +}) + +const Collections = Schema.Struct({ + tags: Schema.Array(Schema.String), + metrics: Schema.Record(Schema.String, Schema.Number), + samples: Schema.Array(Schema.Tuple([Schema.Number, Schema.Number, Schema.Boolean])), + buckets: Schema.Array(Schema.Array(Schema.Number)) +}) + +const LargeRow = Schema.Struct({ + transactionIdentifier: Schema.String, + customerIdentifier: Schema.String, + productDescription: Schema.String, + fulfillmentLocation: Schema.String, + quantityPurchased: Schema.Number, + unitPriceInCents: Schema.Number, + discountInBasisPoints: Schema.Number, + requiresManualReview: Schema.Boolean +}) + +const LargePayload = Schema.Array(LargeRow) + +const largeRows = Array.from({ length: repeatedRecordStreamSize }, (_, index) => ({ + transactionIdentifier: `transaction-${index.toString().padStart(4, "0")}`, + customerIdentifier: `customer-${index % 37}`, + productDescription: `Product ${index % 19} with a repeated descriptive field value`, + fulfillmentLocation: ["London", "New York", "Singapore", "Sydney"][index % 4]!, + quantityPurchased: index % 9 + 1, + unitPriceInCents: 500 + index % 73 * 25, + discountInBasisPoints: index % 5 * 125, + requiresManualReview: index % 17 === 0 +})) + +const metrics = (count: number) => + Object.fromEntries(Array.from({ length: count }, (_, index) => [`metric-${index}`, index * 1.25])) + +const protobufRoot = protobuf.parse(` + syntax = "proto3"; + + message SmallRecord { + double id = 1; + bool active = 2; + double score = 3; + double retryCount = 4; + string region = 5; + bool verified = 6; + } + + message LineItem { + string sku = 1; + double quantity = 2; + double unitPrice = 3; + } + + message Customer { + string id = 1; + string name = 2; + string email = 3; + } + + message Shipping { + string street = 1; + string city = 2; + string postalCode = 3; + string country = 4; + } + + message Metadata { + string source = 1; + string campaign = 2; + bool priority = 3; + } + + message NestedPayload { + string orderId = 1; + Customer customer = 2; + Shipping shipping = 3; + repeated LineItem lines = 4; + Metadata metadata = 5; + } + + message Sample { + double first = 1; + double second = 2; + bool third = 3; + } + + message Bucket { + repeated double values = 1; + } + + message Collections { + repeated string tags = 1; + map metrics = 2; + repeated Sample samples = 3; + repeated Bucket buckets = 4; + } + + message NumberMap { + map values = 1; + } + + message LargeRow { + string transactionIdentifier = 1; + string customerIdentifier = 2; + string productDescription = 3; + string fulfillmentLocation = 4; + double quantityPurchased = 5; + double unitPriceInCents = 6; + double discountInBasisPoints = 7; + bool requiresManualReview = 8; + } + + message LargePayload { + repeated LargeRow values = 1; + } +`).root + +interface ProtobufFixture { + readonly type: Type + readonly encodeInput: (value: unknown) => object + readonly decodeOutput: (message: Message) => unknown +} + +const protobufObject = (type: Type, message: Message): T => + type.toObject(message, { arrays: true, defaults: true, objects: true }) as T + +const directProtobufFixture = (name: string): ProtobufFixture => { + const type = protobufRoot.lookupType(name) + return { + type, + encodeInput: (value) => value as object, + decodeOutput: (message) => protobufObject(type, message) + } +} + +const collectionsProtobufType = protobufRoot.lookupType("Collections") +const numberMapProtobufType = protobufRoot.lookupType("NumberMap") +const largePayloadProtobufType = protobufRoot.lookupType("LargePayload") + +const collectionsProtobufFixture: ProtobufFixture = { + type: collectionsProtobufType, + encodeInput: (value) => { + const collections = value as (typeof Collections)["Type"] + return { + tags: collections.tags, + metrics: collections.metrics, + samples: collections.samples.map(([first, second, third]) => ({ first, second, third })), + buckets: collections.buckets.map((values) => ({ values })) + } + }, + decodeOutput: (message) => { + const collections = protobufObject<{ + readonly tags: ReadonlyArray + readonly metrics: Readonly> + readonly samples: ReadonlyArray<{ readonly first: number; readonly second: number; readonly third: boolean }> + readonly buckets: ReadonlyArray<{ readonly values: ReadonlyArray }> + }>(collectionsProtobufType, message) + return { + tags: collections.tags, + metrics: collections.metrics, + samples: collections.samples.map((sample) => [sample.first, sample.second, sample.third]), + buckets: collections.buckets.map((bucket) => bucket.values) + } + } +} + +const numberMapProtobufFixture: ProtobufFixture = { + type: numberMapProtobufType, + encodeInput: (value) => ({ values: value }), + decodeOutput: (message) => + protobufObject<{ readonly values: Readonly> }>(numberMapProtobufType, message).values +} + +const largePayloadProtobufFixture: ProtobufFixture = { + type: largePayloadProtobufType, + encodeInput: (value) => ({ values: value }), + decodeOutput: (message) => + protobufObject<{ readonly values: ReadonlyArray<(typeof LargeRow)["Type"]> }>(largePayloadProtobufType, message) + .values +} + +const largeRowProtobufFixture = directProtobufFixture("LargeRow") + +const cases = [ + { + name: "small record", + schema: SmallRecord, + value: { + id: 42, + active: true, + score: 98.5, + retryCount: 2, + region: "eu-west-1", + verified: false + }, + protobuf: directProtobufFixture("SmallRecord") + }, + { + name: "nested payload", + schema: NestedPayload, + value: { + orderId: "order-2026-000184", + customer: { + id: "customer-91", + name: "Ada Lovelace", + email: "ada@example.com" + }, + shipping: { + street: "12 Analytical Engine Way", + city: "London", + postalCode: "SW1A 1AA", + country: "GB" + }, + lines: [ + { sku: "widget-blue", quantity: 2, unitPrice: 12.5 }, + { sku: "adapter-pro", quantity: 1, unitPrice: 48 }, + { sku: "cable-2m", quantity: 3, unitPrice: 8.25 } + ], + metadata: { + source: "partner-api", + campaign: "summer-2026", + priority: true + } + }, + protobuf: directProtobufFixture("NestedPayload") + }, + { + name: "collections", + schema: Collections, + value: { + tags: Array.from({ length: 24 }, (_, index) => `tag-${index}`), + metrics: Object.fromEntries(Array.from({ length: 24 }, (_, index) => [`metric-${index}`, index * 1.25])), + samples: Array.from({ length: 48 }, (_, index) => [index, index / 10, index % 3 === 0] as const), + buckets: Array.from({ length: 8 }, (_, bucket) => Array.from({ length: 16 }, (_, index) => bucket * 100 + index)) + }, + protobuf: collectionsProtobufFixture + }, + { + name: "index signatures / 128 keys", + schema: Schema.Record(Schema.String, Schema.Number), + value: metrics(128), + protobuf: numberMapProtobufFixture + }, + { + name: "index signatures / 512 keys", + schema: Schema.Record(Schema.String, Schema.Number), + value: metrics(512), + protobuf: numberMapProtobufFixture + }, + { + name: "200-row array payload", + schema: LargePayload, + value: largeRows, + protobuf: largePayloadProtobufFixture + } +] as const + +interface Format { + readonly name: string + readonly encodedSize: number + readonly gzipSize: number + readonly zstdSize: number + readonly encode: () => unknown + readonly decode: () => unknown +} + +interface StreamFormat { + readonly name: string + readonly framesPerOp: number + readonly decode: () => ReadonlyArray | Promise> +} + +interface StreamSize { + readonly name: string + readonly frames: number + readonly encodedSize: number + readonly gzipSize: number + readonly zstdSize: number +} + +const textEncoder = new TextEncoder() + +const concatFrames = (frames: ReadonlyArray): Uint8Array => { + const out = new Uint8Array(frames.reduce((length, frame) => length + frame.length, 0)) + let offset = 0 + for (const frame of frames) { + out.set(frame, offset) + offset += frame.length + } + return out +} + +const sizes = (encoded: Uint8Array): Pick => ({ + encodedSize: encoded.length, + gzipSize: gzipSync(encoded).length, + zstdSize: zstdCompressSync(encoded).length +}) + +const prepare = >( + schema: S, + value: S["Type"], + protobufFixture: ProtobufFixture +): { + readonly formats: ReadonlyArray +} => { + const jsonSchema = Schema.toCodecJson(schema) + const binaryCodec = SchemaBinary.toCodec(schema) + const fingerprintCodec = SchemaBinary.toCodec(schema, { fingerprint: true }) + const jsonCodec = Schema.fromJsonString(jsonSchema) + + const binaryEncode = Schema.encodeUnknownSync(binaryCodec) + const binaryDecode = Schema.decodeUnknownSync(binaryCodec) + const fingerprintEncode = Schema.encodeUnknownSync(fingerprintCodec) + const fingerprintDecode = Schema.decodeUnknownSync(fingerprintCodec) + const jsonEncode = Schema.encodeUnknownSync(jsonCodec) + const jsonDecode = Schema.decodeUnknownSync(jsonCodec) + const protobufValue = protobufFixture.encodeInput(value) + + const binary = binaryEncode(value) + const fingerprint = fingerprintEncode(value).slice() + const json = jsonEncode(value) + const jsonBytes = textEncoder.encode(json) + const protobufBytes = protobufFixture.type.encode(protobufValue).finish() + const protobufDecode = () => protobufFixture.decodeOutput(protobufFixture.type.decode(protobufBytes)) + + assert.deepStrictEqual(binaryDecode(binary), value) + assert.deepStrictEqual(fingerprintDecode(fingerprint), value) + assert.deepStrictEqual(jsonDecode(json), value) + assert.deepStrictEqual(protobufDecode(), value) + + return { + formats: [ + { + name: "SchemaBinary", + ...sizes(binary), + encode: () => binaryEncode(value), + decode: () => binaryDecode(binary) + }, + { + name: "SchemaBinary fingerprint", + ...sizes(fingerprint), + encode: () => fingerprintEncode(value), + decode: () => fingerprintDecode(fingerprint) + }, + { + name: "JSON", + ...sizes(jsonBytes), + encode: () => jsonEncode(value), + decode: () => jsonDecode(json) + }, + { + name: "Protobuf", + ...sizes(protobufBytes), + encode: () => protobufFixture.type.encode(protobufValue).finish(), + decode: protobufDecode + } + ] + } +} + +const prepared = cases.map((testCase) => ({ + name: testCase.name, + ...prepare(testCase.schema, testCase.value, testCase.protobuf) +})) + +const prepareStream = async >( + schema: S, + values: ReadonlyArray, + protobufFixture: ProtobufFixture +): Promise<{ readonly formats: ReadonlyArray; readonly sizes: ReadonlyArray }> => { + const binaryCodec = SchemaBinary.toCodec(schema) + const binaryEncode = Schema.encodeUnknownSync(binaryCodec) + const binaryFrames = values.map((value) => binaryEncode(value).slice()) + const binaryStream = concatFrames(binaryFrames) + const binaryFragments = binaryFrames.map((frame) => { + return [frame.subarray(0, 1), frame.subarray(1)] as const + }) + + const fingerprintEncode = Schema.encodeUnknownSync(SchemaBinary.toCodec(schema, { fingerprint: true })) + const fingerprintFrames = values.map((value) => fingerprintEncode(value).slice()) + const fingerprintStream = concatFrames(fingerprintFrames) + const fingerprintFragments = fingerprintFrames.map((frame) => { + return [frame.subarray(0, 1), frame.subarray(1)] as const + }) + + const jsonSchema = Schema.toCodecJson(schema) + const encodeJsonValue = Schema.encodeUnknownSync(jsonSchema) + + const protobufFrames = values.map((value) => + protobufFixture.type.encodeDelimited(protobufFixture.encodeInput(value)).finish() + ) + const protobufStream = concatFrames(protobufFrames) + const decodeProtobufStream = () => { + const reader = protobuf.Reader.create(protobufStream) + const decoded: Array = [] + while (reader.pos < reader.len) { + decoded.push(protobufFixture.decodeOutput(protobufFixture.type.decodeDelimited(reader))) + } + return decoded + } + + const ndjsonFrames = values.map((value) => textEncoder.encode(`${JSON.stringify(encodeJsonValue(value))}\n`)) + const ndjsonStream = concatFrames(ndjsonFrames) + const ndjsonFragments = ndjsonFrames.map((frame) => { + return [frame.subarray(0, 1), frame.subarray(1)] as const + }) + const ndjsonDecoder = Ndjson.decodeSchema(jsonSchema)() + const runNdjson = (chunks: ReadonlyArray) => + Effect.runPromise( + Stream.fromIterable(chunks).pipe( + Stream.pipeThroughChannel(ndjsonDecoder), + Stream.runCollect, + Effect.map((chunk) => Array.from(chunk)) + ) + ) + let ndjsonSingleIndex = 0 + let ndjsonFragmentedIndex = 0 + const decodeNdjsonSingle = () => runNdjson([ndjsonFrames[ndjsonSingleIndex++ % ndjsonFrames.length]!]) + const decodeNdjsonBatch = () => runNdjson([ndjsonStream]) + const decodeNdjsonFragmented = () => runNdjson(ndjsonFragments[ndjsonFragmentedIndex++ % ndjsonFragments.length]!) + + const feedShapes = (options?: { readonly fingerprint: true }) => { + const frames = options === undefined ? binaryFrames : fingerprintFrames + const fragments = options === undefined ? binaryFragments : fingerprintFragments + const stream = options === undefined ? binaryStream : fingerprintStream + const singleParser = SchemaBinary.parser(schema, options) + const fragmentedParser = SchemaBinary.parser(schema, options) + const batchParser = SchemaBinary.parser(schema, options) + let singleIndex = 0 + let fragmentedIndex = 0 + return { + single: () => singleParser.feedSync(frames[singleIndex++ % frames.length]), + fragmented: () => { + const pair = fragments[fragmentedIndex++ % fragments.length] + const first = fragmentedParser.feedSync(pair[0]) + const second = fragmentedParser.feedSync(pair[1]) + return first.length === 0 ? second : [...first, ...second] + }, + batch: () => batchParser.feedSync(stream) + } + } + + const defaultFeeds = feedShapes() + const fingerprintFeeds = feedShapes({ fingerprint: true }) + const decodeSingle = defaultFeeds.single + const decodeFragmented = defaultFeeds.fragmented + const decodeBatch = defaultFeeds.batch + assert.deepStrictEqual(decodeSingle(), [values[0]]) + assert.deepStrictEqual(decodeFragmented(), [values[0]]) + assert.deepStrictEqual(decodeBatch(), values) + assert.deepStrictEqual(fingerprintFeeds.single(), [values[0]]) + assert.deepStrictEqual(fingerprintFeeds.fragmented(), [values[0]]) + assert.deepStrictEqual(fingerprintFeeds.batch(), values) + assert.deepStrictEqual(decodeProtobufStream(), values) + assert.deepStrictEqual(await decodeNdjsonSingle(), [values[0]]) + assert.deepStrictEqual(await decodeNdjsonFragmented(), [values[0]]) + assert.deepStrictEqual(await decodeNdjsonBatch(), values) + + return { + formats: [ + { name: "SchemaBinary parser / single frame", framesPerOp: 1, decode: decodeSingle }, + { name: "SchemaBinary parser / batch", framesPerOp: values.length, decode: decodeBatch }, + { name: "SchemaBinary parser / fragmented", framesPerOp: 1, decode: decodeFragmented }, + { name: "SchemaBinary fingerprint / single frame", framesPerOp: 1, decode: fingerprintFeeds.single }, + { name: "SchemaBinary fingerprint / batch", framesPerOp: values.length, decode: fingerprintFeeds.batch }, + { name: "SchemaBinary fingerprint / fragmented", framesPerOp: 1, decode: fingerprintFeeds.fragmented }, + { name: "Protobuf decodeDelimited / batch", framesPerOp: values.length, decode: decodeProtobufStream }, + { name: "NDJSON Channel / single frame", framesPerOp: 1, decode: decodeNdjsonSingle }, + { name: "NDJSON Channel / batch", framesPerOp: values.length, decode: decodeNdjsonBatch }, + { name: "NDJSON Channel / fragmented", framesPerOp: 1, decode: decodeNdjsonFragmented } + ], + sizes: [ + { name: "SchemaBinary", frames: values.length, ...sizes(binaryStream) }, + { name: "SchemaBinary fingerprint", frames: values.length, ...sizes(fingerprintStream) }, + { name: "Protobuf", frames: values.length, ...sizes(protobufStream) }, + { name: "NDJSON", frames: values.length, ...sizes(ndjsonStream) } + ] + } +} + +const preparedStreams = await Promise.all([ + ...cases.map((testCase) => ({ + name: testCase.name, + prepared: prepareStream( + testCase.schema, + Array.from({ length: streamBatchSize }, () => testCase.value), + testCase.protobuf + ) + })), + { name: "200 single-row frames", prepared: prepareStream(LargeRow, largeRows, largeRowProtobufFixture) } +].map(async ({ name, prepared }) => ({ name, ...await prepared }))) + +console.log(`Node ${process.version}; codec and schema construction excluded from timings.`) +console.log("JSON sizes are UTF-8 bytes.") +console.log("Protobuf uses prebuilt protobufjs descriptors; descriptor construction and encode adapters are excluded.") +console.log( + "Compare formats within a case and direction in the same run; absolute rates vary with the machine and runtime." +) + +console.table(prepared.flatMap((testCase) => + testCase.formats.map((format) => ({ + Case: testCase.name, + Format: format.name, + "Raw bytes": format.encodedSize, + "gzip -6 bytes": format.gzipSize, + "zstd bytes": format.zstdSize + })) +)) + +console.log( + "SchemaBinary streaming reuses one parser per feed shape; NDJSON runs its Channel per operation. Fragmented frames split after the first byte." +) +console.table(preparedStreams.flatMap((testCase) => + testCase.sizes.map((format) => ({ + Case: testCase.name, + Format: format.name, + Frames: format.frames, + "Raw bytes": format.encodedSize, + "Bytes / frame": format.encodedSize / format.frames, + "gzip -6 bytes": format.gzipSize, + "zstd bytes": format.zstdSize + })) +)) + +const bench = new Bench({ + iterations: 1_000, + time: 0, + warmupIterations: 100, + warmupTime: 0, + timestampProvider: "hrtimeNow" +}) +const tasks = new Map() +const sinkSentinel = Symbol("benchmark did not run") +let sink: unknown = sinkSentinel + +for (const testCase of prepared) { + for (const format of testCase.formats) { + for (const [direction, run] of [["encode", format.encode], ["decode", format.decode]] as const) { + const name = `${testCase.name} / ${format.name} / ${direction}` + tasks.set(name, { caseName: testCase.name, formatName: format.name, direction }) + bench.add(name, () => { + sink = run() + }) + } + } +} + +await bench.run() + +if (sink === sinkSentinel) { + throw new Error("Benchmark did not run") +} + +console.table(bench.tasks.map((task) => { + const labels = tasks.get(task.name)! + const result = task.result + if (result?.state === "errored") { + return { + Case: labels.caseName, + Format: labels.formatName, + Direction: labels.direction, + Error: result.error.message + } + } + if (result?.state !== "completed") { + return { + Case: labels.caseName, + Format: labels.formatName, + Direction: labels.direction, + State: result?.state ?? "missing result" + } + } + return { + Case: labels.caseName, + Format: labels.formatName, + Direction: labels.direction, + "Throughput avg (ops/s)": Math.round(result.throughput.mean), + "Latency med (us/op)": (result.latency.p50 * 1_000).toFixed(2), + "Latency RME": `${result.latency.rme.toFixed(2)}%`, + Samples: result.latency.samplesCount + } +})) + +const streamBench = new Bench({ + iterations: 250, + time: 0, + warmupIterations: 25, + warmupTime: 0, + timestampProvider: "hrtimeNow" +}) +const streamTasks = new Map< + string, + { readonly caseName: string; readonly formatName: string; readonly framesPerOp: number } +>() + +for (const testCase of preparedStreams) { + for (const format of testCase.formats) { + const name = `${testCase.name} / ${format.name} / stream decode` + streamTasks.set(name, { + caseName: testCase.name, + formatName: format.name, + framesPerOp: format.framesPerOp + }) + streamBench.add(name, () => { + const decoded = format.decode() + if (decoded instanceof Promise) { + return decoded.then((value) => { + sink = value + }) + } + sink = decoded + }) + } +} + +await streamBench.run() + +console.table(streamBench.tasks.map((task) => { + const labels = streamTasks.get(task.name)! + const result = task.result + if (result?.state === "errored") { + return { + Case: labels.caseName, + Format: labels.formatName, + Error: result.error.message + } + } + if (result?.state !== "completed") { + return { + Case: labels.caseName, + Format: labels.formatName, + State: result?.state ?? "missing result" + } + } + return { + Case: labels.caseName, + Format: labels.formatName, + "Throughput avg (values/s)": Math.round(result.throughput.mean * labels.framesPerOp), + "Latency med (us/value)": (result.latency.p50 * 1_000 / labels.framesPerOp).toFixed(2), + "Latency RME": `${result.latency.rme.toFixed(2)}%`, + Samples: result.latency.samplesCount + } +})) diff --git a/packages/effect/benchmark/schema/SchemaError.ts b/packages/effect/benchmark/schema/SchemaError.ts new file mode 100644 index 000000000..ba2b450c7 --- /dev/null +++ b/packages/effect/benchmark/schema/SchemaError.ts @@ -0,0 +1,46 @@ +import { Schema, SchemaIssue } from "effect" +import { Bench } from "tinybench" + +const batchSize = 1_000 +const bench = new Bench({ + iterations: 1_000, + time: 0, + warmupIterations: 100, + warmupTime: 0, + timestampProvider: "hrtimeNow" +}) +const issue = new SchemaIssue.InvalidValue({ message: "Expected string" }) +let sink: Schema.SchemaError | undefined + +bench.add("SchemaError construction", () => { + let error = new Schema.SchemaError(issue) + for (let index = 1; index < batchSize; index++) { + error = new Schema.SchemaError(issue) + } + sink = error +}) + +await bench.run() + +if (sink === undefined) { + throw new Error("Benchmark did not run") +} + +console.table(bench.table((task) => { + const result = task.result + if (result?.state !== "completed") { + return { + "Task name": task.name, + State: result?.state ?? "missing result" + } + } + const latencyToNs = (value: number) => value * 1_000_000 / batchSize + return { + "Task name": task.name, + "Latency avg (ns/op)": latencyToNs(result.latency.mean).toFixed(2), + "Latency med (ns/op)": latencyToNs(result.latency.p50).toFixed(2), + "Latency RME": `${result.latency.rme.toFixed(2)}%`, + "Throughput avg (ops/s)": Math.round(result.throughput.mean * batchSize), + Samples: result.latency.samplesCount + } +})) diff --git a/packages/effect/benchmark/schema/array.ts b/packages/effect/benchmark/schema/array.ts deleted file mode 100644 index b4596fabe..000000000 --- a/packages/effect/benchmark/schema/array.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type } from "arktype" -import { Schema } from "effect" -import { Bench } from "tinybench" -import * as v from "valibot" -import { z } from "zod/v4-mini" - -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'Schema (good)' │ '218.06 ± 1.54%' │ '208.00 ± 0.00' │ '4904782 ± 0.01%' │ '4807692 ± 0' │ 4585950 │ -│ 1 │ 'Schema (bad)' │ '362.29 ± 3.26%' │ '292.00 ± 1.00' │ '3199501 ± 0.01%' │ '3424658 ± 11769' │ 2760191 │ -│ 2 │ 'Valibot (good)' │ '67.50 ± 3.42%' │ '42.00 ± 1.00' │ '18944492 ± 0.02%' │ '23809524 ± 580720' │ 14872899 │ -│ 3 │ 'Valibot (bad)' │ '129.11 ± 0.74%' │ '125.00 ± 0.00' │ '8244804 ± 0.01%' │ '8000000 ± 0' │ 7745459 │ -│ 4 │ 'Arktype (good)' │ '25.76 ± 6.47%' │ '41.00 ± 1.00' │ '30181117 ± 0.01%' │ '24390244 ± 580720' │ 38824544 │ -│ 5 │ 'Arktype (bad)' │ '1837.1 ± 2.51%' │ '1750.0 ± 41.00' │ '567189 ± 0.02%' │ '571429 ± 13393' │ 544325 │ -│ 6 │ 'Zod (good)' │ '43.74 ± 4.91%' │ '42.00 ± 0.00' │ '23500345 ± 0.00%' │ '23809524 ± 0' │ 22863784 │ -│ 7 │ 'Zod (bad)' │ '5205.5 ± 0.76%' │ '4958.0 ± 83.00' │ '199317 ± 0.04%' │ '201694 ± 3360' │ 192106 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.Array(Schema.String) - -const valibot = v.array(v.string()) - -const arktype = type("string[]") - -const zod = z.array(z.string()) - -const good = ["a", "b"] -const bad = ["a", 1] - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(v.safeParse(valibot, good)) -// console.log(v.safeParse(valibot, bad)) -// console.log(arktype(good)) -// console.log(arktype(bad)) -// console.log(zod.safeParse(good)) -// console.log(zod.safeParse(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Valibot (good)", function() { - v.safeParse(valibot, good) - }) - .add("Valibot (bad)", function() { - v.safeParse(valibot, bad) - }) - .add("Arktype (good)", function() { - arktype(good) - }) - .add("Arktype (bad)", function() { - arktype(bad) - }) - .add("Zod (good)", function() { - zod.safeParse(good) - }) - .add("Zod (bad)", function() { - zod.safeParse(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/packages/effect/benchmark/schema/filter.ts b/packages/effect/benchmark/schema/filter.ts deleted file mode 100644 index 6ff05e83a..000000000 --- a/packages/effect/benchmark/schema/filter.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { type } from "arktype" -import { Schema } from "effect" -import { Bench } from "tinybench" -import * as v from "valibot" -import { z } from "zod/v4-mini" - -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'Schema (good)' │ '129.33 ± 0.60%' │ '125.00 ± 0.00' │ '8158156 ± 0.01%' │ '8000000 ± 0' │ 7732112 │ -│ 1 │ 'Schema (bad)' │ '239.58 ± 1.44%' │ '209.00 ± 1.00' │ '4413764 ± 0.01%' │ '4784689 ± 23003' │ 4174352 │ -│ 2 │ 'Valibot (good)' │ '49.81 ± 1.01%' │ '42.00 ± 0.00' │ '22751446 ± 0.01%' │ '23809524 ± 1' │ 20075175 │ -│ 3 │ 'Valibot (bad)' │ '70.52 ± 1.11%' │ '83.00 ± 1.00' │ '16762925 ± 0.02%' │ '12048193 ± 143431' │ 14179523 │ -│ 4 │ 'Arktype (good)' │ '23.37 ± 0.14%' │ '41.00 ± 1.00' │ '32327042 ± 0.01%' │ '24390244 ± 580720' │ 42787691 │ -│ 5 │ 'Arktype (bad)' │ '1361.2 ± 2.65%' │ '1333.0 ± 41.00' │ '757323 ± 0.01%' │ '750188 ± 23806' │ 734659 │ -│ 6 │ 'Zod (good)' │ '44.10 ± 0.72%' │ '42.00 ± 0.00' │ '23479006 ± 0.00%' │ '23809524 ± 0' │ 22674716 │ -│ 7 │ 'Zod (bad)' │ '5005.9 ± 1.50%' │ '4834.0 ± 83.00' │ '204249 ± 0.03%' │ '206868 ± 3492' │ 199767 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.String.check(Schema.isNonEmpty()) - -const valibot = v.pipe(v.string(), v.nonEmpty()) - -const arktype = type("string > 0") - -const zod = z.string().check(z.minLength(1)) - -const good = "a" -const bad = "" - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(v.safeParse(valibot, good)) -// console.log(v.safeParse(valibot, bad)) -// console.log(arktype(good)) -// console.log(arktype(bad)) -// console.log(zod.safeParse(good)) -// console.log(zod.safeParse(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Valibot (good)", function() { - v.safeParse(valibot, good) - }) - .add("Valibot (bad)", function() { - v.safeParse(valibot, bad) - }) - .add("Arktype (good)", function() { - arktype(good) - }) - .add("Arktype (bad)", function() { - arktype(bad) - }) - .add("Zod (good)", function() { - zod.safeParse(good) - }) - .add("Zod (bad)", function() { - zod.safeParse(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/packages/effect/benchmark/schema/object.ts b/packages/effect/benchmark/schema/object.ts deleted file mode 100644 index 7e164daef..000000000 --- a/packages/effect/benchmark/schema/object.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { type } from "arktype" -import { Schema, SchemaParser } from "effect" -import { Bench } from "tinybench" -import * as v from "valibot" -import { z } from "zod/v4-mini" - -/* -┌─────────┬──────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬──────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼──────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼──────────┤ -│ 0 │ 'Schema (good)' │ '124.63 ± 0.14%' │ '125.00 ± 0.00' │ '8434862 ± 0.01%' │ '8000000 ± 0' │ 8023546 │ -│ 1 │ 'Schema (bad)' │ '203.36 ± 6.27%' │ '208.00 ± 41.00' │ '5319764 ± 0.01%' │ '4807692 ± 807692' │ 4917416 │ -│ 2 │ 'Valibot (good)' │ '48.91 ± 0.14%' │ '42.00 ± 0.00' │ '22152363 ± 0.01%' │ '23809524 ± 1' │ 20444356 │ -│ 3 │ 'Valibot (bad)' │ '101.98 ± 0.88%' │ '84.00 ± 1.00' │ '10439854 ± 0.01%' │ '11904762 ± 143431' │ 9806230 │ -│ 4 │ 'Arktype (good)' │ '14.57 ± 1.46%' │ '0.00 ± 0.00' │ '53567410 ± 0.01%' │ '68616078 ± 0' │ 68616080 │ -│ 5 │ 'Arktype (bad)' │ '2001.6 ± 7.05%' │ '1750.0 ± 41.00' │ '554199 ± 0.03%' │ '571429 ± 13393' │ 499602 │ -│ 6 │ 'Zod (good)' │ '33.75 ± 6.13%' │ '41.00 ± 1.00' │ '25375235 ± 0.00%' │ '24390240 ± 580716' │ 29633666 │ -│ 7 │ 'Zod (bad)' │ '5392.2 ± 3.11%' │ '5167.0 ± 42.00' │ '190866 ± 0.03%' │ '193536 ± 1586' │ 185454 │ -└─────────┴──────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴──────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.Struct({ - a: Schema.String -}) - -const valibot = v.object({ - a: v.string() -}) - -const arktype = type({ - a: "string" -}) - -const zod = z.object({ - a: z.string() -}) - -const good = { a: "a" } -const bad = { a: 1 } - -const decodeUnknownExit = SchemaParser.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(v.safeParse(valibot, good)) -// console.log(v.safeParse(valibot, bad)) -// console.log(arktype(good)) -// console.log(arktype(bad)) -// console.log(zod.safeParse(good)) -// console.log(zod.safeParse(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Valibot (good)", function() { - v.safeParse(valibot, good) - }) - .add("Valibot (bad)", function() { - v.safeParse(valibot, bad) - }) - .add("Arktype (good)", function() { - arktype(good) - }) - .add("Arktype (bad)", function() { - arktype(bad) - }) - .add("Zod (good)", function() { - zod.safeParse(good) - }) - .add("Zod (bad)", function() { - zod.safeParse(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/packages/effect/benchmark/schema/tagged-union.ts b/packages/effect/benchmark/schema/tagged-union.ts deleted file mode 100644 index 2250ea4c0..000000000 --- a/packages/effect/benchmark/schema/tagged-union.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Array as RA, Schema } from "effect" -import { Bench } from "tinybench" - -/* -┌─────────┬────────────────────┬──────────────────┬──────────────────┬────────────────────────┬────────────────────────┬─────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼────────────────────┼──────────────────┼──────────────────┼────────────────────────┼────────────────────────┼─────────┤ -│ 0 │ 'Schema (good)' │ '488.43 ± 0.48%' │ '459.00 ± 1.00' │ '2119495 ± 0.01%' │ '2178649 ± 4757' │ 2047396 │ -│ 1 │ 'Schema (bad)' │ '629.17 ± 0.29%' │ '584.00 ± 1.00' │ '1645689 ± 0.01%' │ '1712329 ± 2937' │ 1589395 │ -│ 2 │ 'candidate (good)' │ '327.20 ± 0.27%' │ '292.00 ± 1.00' │ '3205291 ± 0.01%' │ '3424658 ± 11769' │ 3056264 │ -│ 3 │ 'candidate (bad)' │ '449.52 ± 2.41%' │ '417.00 ± 0.00' │ '2372897 ± 0.01%' │ '2398082 ± 0' │ 2224610 │ -└─────────┴────────────────────┴──────────────────┴──────────────────┴────────────────────────┴────────────────────────┴─────────┘ -*/ - -const bench = new Bench({ time: 1000 }) - -const n = 100 -const f = (i: number) => - Schema.Struct({ - kind: Schema.Literal(i), - a: Schema.String, - b: Schema.Number, - c: Schema.Boolean - }) -const members = RA.makeBy(n, f) - -const schema = Schema.Union(members) - -const candidate = f(n - 1) - -const good = { - kind: n - 1, - a: "a", - b: 1, - c: true -} - -const bad = { - kind: n - 1, - a: "a", - b: 1, - c: "c" -} - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) -const decodeUnknownExitCandidate = Schema.decodeUnknownExit(candidate) - -// console.log(decodeUnknownExit(good)) -// console.log(decodeUnknownExit(bad)) -// console.log(decodeUnknownExitCandidate(good)) -// console.log(decodeUnknownExitCandidate(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("candidate (good)", function() { - decodeUnknownExitCandidate(good) - }) - .add("candidate (bad)", function() { - decodeUnknownExitCandidate(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/packages/effect/benchmark/schema/transformation.ts b/packages/effect/benchmark/schema/transformation.ts deleted file mode 100644 index 40cae620f..000000000 --- a/packages/effect/benchmark/schema/transformation.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Schema, SchemaTransformation } from "effect" -import { Bench } from "tinybench" -import { z } from "zod" - -/* -┌─────────┬─────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐ -│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │ -├─────────┼─────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤ -│ 0 │ 'Schema (good)' │ '1097.8 ± 1.12%' │ '1042.0 ± 1.00' │ '949296 ± 0.01%' │ '959693 ± 922' │ 910953 │ -│ 1 │ 'Zod (good)' │ '267.92 ± 4.46%' │ '208.00 ± 0.00' │ '4505289 ± 0.02%' │ '4807692 ± 0' │ 3732515 │ -│ 2 │ 'Schema (bad)' │ '683.49 ± 1.54%' │ '625.00 ± 0.00' │ '1593775 ± 0.01%' │ '1600000 ± 0' │ 1463090 │ -│ 3 │ 'Zod (bad)' │ '8172.9 ± 4.43%' │ '6417.0 ± 125.00' │ '152563 ± 0.07%' │ '155836 ± 3096' │ 122357 │ -└─────────┴─────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘ -*/ - -const bench = new Bench() - -const schema = Schema.Struct({ - a: Schema.String, - id: Schema.String, - c: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)), - d: Schema.String -}).pipe(Schema.decodeTo( - Schema.Struct({ - a: Schema.String, - b: Schema.Struct({ id: Schema.String }), - c: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)), - d: Schema.String - }), - SchemaTransformation.transform({ - decode: ({ id, ...v }) => ({ ...v, b: { id } }), - encode: ({ b: { id }, ...v }) => ({ ...v, id }) - }) -)) - -const zod = z.codec( - z.object({ - a: z.string(), - id: z.string(), - c: z.number().check(z.nonnegative()), - d: z.string() - }), - z.object({ - a: z.string(), - b: z.object({ id: z.string() }), - c: z.number().check(z.nonnegative()), - d: z.string() - }), - { - decode: ({ id, ...v }) => ({ ...v, b: { id } }), - encode: ({ b: { id }, ...v }) => ({ ...v, id }) - } -) - -const good = { - a: "a", - id: "id", - c: 1, - d: "d" -} -const bad = { - a: "a", - id: "id", - c: -1, - d: "d" -} - -const decodeUnknownExit = Schema.decodeUnknownExit(schema) - -// console.log(decodeUnknownExit(good)) -// console.log(String(decodeUnknownExit(bad))) -// console.log(zod.safeDecode(good)) -// console.log(zod.safeDecode(bad)) - -bench - .add("Schema (good)", function() { - decodeUnknownExit(good) - }) - .add("Zod (good)", function() { - zod.safeDecode(good) - }) - .add("Schema (bad)", function() { - decodeUnknownExit(bad) - }) - .add("Zod (bad)", function() { - zod.safeDecode(bad) - }) - -await bench.run() - -console.table(bench.table()) diff --git a/packages/effect/docgen.json b/packages/effect/docgen.json deleted file mode 100644 index 99d82864a..000000000 --- a/packages/effect/docgen.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "../../node_modules/@effect/docgen/schema.json", - "exclude": ["src/internal/**/*.ts", "src/unstable/**/internal/**/*.ts", "src/schema/StandardSchema.ts"], - "srcLink": "https://github.com/Effect-TS/effect-smol/tree/main/packages/effect/src/", - "tscExecutable": "tsgo", - "examplesCompilerOptions": { - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "Bundler", - "module": "ES2022", - "target": "ES2022", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, - "paths": { - "effect": ["../../../effect/src/index.js"], - "effect/*": ["../../../effect/src/*.js"], - "@effect/platform-node": ["../../../platform-node/src/index.js"], - "@effect/platform-node/*": ["../../../platform-node/src/*.js"] - }, - "plugins": [ - { - "name": "@effect/language-service", - "includeSuggestionsInTsc": false, - "diagnosticSeverity": { - "unusedDirective": "off", - "floatingEffect": "off", - "multipleEffectProvide": "off", - "globalErrorInEffectFailure": "off", - "unknownInEffectCatch": "off", - "globalErrorInEffectCatch": "off", - "missingReturnYieldStar": "off" - } - } - ] - } -} diff --git a/packages/effect/package.json b/packages/effect/package.json index bce406a50..ab9475709 100644 --- a/packages/effect/package.json +++ b/packages/effect/package.json @@ -1,17 +1,17 @@ { "name": "effect", "type": "module", - "version": "4.0.0-beta.90", + "version": "4.0.0-rc.112", "license": "MIT", "description": "The missing standard library for TypeScript, for writing production-grade software.", "homepage": "https://effect.website", "repository": { "type": "git", - "url": "https://github.com/Effect-TS/effect-smol.git", + "url": "https://github.com/Effect-TS/effect.git", "directory": "packages/effect" }, "bugs": { - "url": "https://github.com/Effect-TS/effect-smol/issues" + "url": "https://github.com/Effect-TS/effect/issues" }, "tags": [ "typescript", @@ -31,6 +31,7 @@ ".": "./src/index.ts", "./testing": "./src/testing/index.ts", "./unstable/ai": "./src/unstable/ai/index.ts", + "./unstable/arbitrary": "./src/unstable/arbitrary/index.ts", "./unstable/cli": "./src/unstable/cli/index.ts", "./unstable/cluster": "./src/unstable/cluster/index.ts", "./unstable/devtools": "./src/unstable/devtools/index.ts", @@ -38,6 +39,7 @@ "./unstable/eventlog": "./src/unstable/eventlog/index.ts", "./unstable/http": "./src/unstable/http/index.ts", "./unstable/httpapi": "./src/unstable/httpapi/index.ts", + "./unstable/net": "./src/unstable/net/index.ts", "./unstable/observability": "./src/unstable/observability/index.ts", "./unstable/persistence": "./src/unstable/persistence/index.ts", "./unstable/process": "./src/unstable/process/index.ts", @@ -49,9 +51,10 @@ "./unstable/workflow": "./src/unstable/workflow/index.ts", "./unstable/workers": "./src/unstable/workers/index.ts", "./*": "./src/*.ts", - "./internal/*": null, "./unstable/cli/internal/*": null, "./unstable/cluster/internal/*": null, + "./internal/*": null, + "./index": null, "./*/index": null }, "files": [ @@ -59,7 +62,10 @@ "dist/**/*.js", "dist/**/*.js.map", "dist/**/*.d.ts", - "dist/**/*.d.ts.map" + "dist/**/*.d.ts.map", + "AGENTS.md", + "CLAUDE.md", + "ai-docs/**/*" ], "publishConfig": { "access": "public", @@ -69,6 +75,7 @@ ".": "./dist/index.js", "./testing": "./dist/testing/index.js", "./unstable/ai": "./dist/unstable/ai/index.js", + "./unstable/arbitrary": "./dist/unstable/arbitrary/index.js", "./unstable/cli": "./dist/unstable/cli/index.js", "./unstable/cluster": "./dist/unstable/cluster/index.js", "./unstable/devtools": "./dist/unstable/devtools/index.js", @@ -76,6 +83,7 @@ "./unstable/eventlog": "./dist/unstable/eventlog/index.js", "./unstable/http": "./dist/unstable/http/index.js", "./unstable/httpapi": "./dist/unstable/httpapi/index.js", + "./unstable/net": "./dist/unstable/net/index.js", "./unstable/observability": "./dist/unstable/observability/index.js", "./unstable/persistence": "./dist/unstable/persistence/index.js", "./unstable/process": "./dist/unstable/process/index.js", @@ -90,38 +98,24 @@ "./internal/*": null, "./unstable/cli/internal/*": null, "./unstable/cluster/internal/*": null, + "./index": null, "./*/index": null } }, "scripts": { "codegen": "effect-utils codegen", "build": "tsc -b tsconfig.json && pnpm babel", - "build:tsgo": "tsgo -b tsconfig.json && pnpm babel", "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", - "check": "tsc -b tsconfig.json", - "test": "vitest --sequence.concurrent=false", - "coverage": "vitest --run --coverage --sequence.concurrent=false" + "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@types/ini": "^4.1.1", - "@types/node": "^25.7.0", + "@types/node": "^26.4.1", "ajv": "^8.20.0", - "arktype": "^2.2.0", - "ast-types": "^0.14.2", - "immer": "^11.1.8", - "tinybench": "^6.0.1", - "valibot": "^1.4.0" - }, - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "fast-check": "^4.8.0", - "find-my-way-ts": "^0.1.6", - "ini": "^7.0.0", - "kubernetes-types": "^1.30.0", - "msgpackr": "^2.0.1", - "multipasta": "^0.2.7", - "toml": "^4.1.1", - "uuid": "^14.0.0", - "yaml": "^2.9.0" + "ajv-draft-04": "^1.0.0", + "ast-types": "^0.16.3", + "immer": "^11.1.18", + "protobufjs": "^8.8.0", + "tinybench": "^6.1.6", + "valibot": "^1.4.2" } } diff --git a/packages/effect/runtimeperf/README.md b/packages/effect/runtimeperf/README.md new file mode 100644 index 000000000..8265911a7 --- /dev/null +++ b/packages/effect/runtimeperf/README.md @@ -0,0 +1,165 @@ +# Effect Runtime Performance + +This harness measures focused synchronous runtime paths in fresh Node +processes. It supports: + +- focused Effect Schema diagnostics; +- native Arbitrary comparisons against equivalent fast-check v4 arbitraries; +- the upstream Effect, Valibot and Zod benchmark matrix; +- paired comparisons between Git revisions or the current working tree. + +Cross-library results are diagnostic. Effect base/head comparisons are the +authoritative measurement for source changes. + +## Commands + +Run the complete registry: + +```sh +pnpm runtimeperf +``` + +Run the cases extracted from the `effect@beta`, Valibot and Zod adapters in +[`open-circle/schema-benchmarks`](https://github.com/open-circle/schema-benchmarks): + +```sh +pnpm runtimeperf schema-benchmarks +pnpm runtimeperf-compare schema-benchmarks --base main --head HEAD +``` + +This suite covers every upstream timing case supported by those adapters: +schema and decoder initialization, validation, parsing and Standard Schema +with valid/invalid inputs and first/all error modes, plus BigInt codec +operations. The upstream bundle and stack reports are not throughput +benchmarks, and the adapters do not define the optional string-format cases. + +Select a suite, fixture, shared scenario, tier, family or implementation: + +```sh +pnpm runtimeperf schema +pnpm runtimeperf arbitrary +pnpm runtimeperf object-32-valid +pnpm runtimeperf schema/object-32-valid-effect +pnpm runtimeperf --family arrays +pnpm runtimeperf --implementation zod4 +``` + +Override measurement settings: + +```sh +pnpm runtimeperf object-32-valid --rounds 9 --time 500 --warmup-time 150 +``` + +Compare Effect `HEAD` with the working tree: + +```sh +pnpm runtimeperf-compare schema/object-32-valid-effect +``` + +Compare explicit refs: + +```sh +pnpm runtimeperf-compare schema --base main --head HEAD +``` + +Only `--fail-on-regression` turns a statistically classified regression into a +non-zero comparison exit code. Worker, fixture, configuration and Git errors +always fail. + +Reports are written under `tmp/runtimeperf/results/`. Temporary Git worktrees +are created under `tmp/runtimeperf/worktrees/` and removed in `finally`. + +## Registry + +`config.json` groups fixture cases by source file. Every case records: + +- tier and family; +- covered Effect AST tags; +- operation and path; +- input size; +- implementation; +- a shared scenario name for cross-library comparisons. + +The `schema` suite is Effect-only and retains targeted diagnostics for scaling, +template literals, unions, records, transformations, optional properties, +adapters, recursion and cold paths. The `schema-benchmarks` suite contains the +complete timing matrices exposed by the upstream Effect, Valibot and Zod +adapters. + +The `arbitrary` suite compares the native public API with direct fast-check v4 arbitraries in separate processes. It +measures derivation through the first recursive sample, steady-state recursive +sampling, optional-Struct sampling, fixed-length string generation to exercise constraint pushdown, +bounded Number generation, direct Uint8Array generation, a rare residual filter, a fixed-length unique array, and a +mixed regular expression through cold derivation, warm generation, and shrinking. It also covers literal sampling as +a runner baseline and the public `map`, `filter`, `filterMap`, and `all` combinators for tuples and records. The failure +paths include a filtered failure with rejected shrink candidates, dependent `flatMap` sampling, shrinking, replay, a +passing property, a failure that shrinks from `1000` to `1`, and replay of that failure. The +recursive distributions are implementation-defined, so the fixtures use +implementation-specific size settings and validate a comparable total node +count for the fixed seed. The bounded Number case is a throughput comparison, +not distribution parity: native selects among 64-bit IEEE-754 representations, +while the fast-check fixture uses its 64-bit `double`. Replay is an +end-to-end public API comparison, but the work is not identical: native replay +verifies the original failure and its full shrink path, while fast-check can +start directly from its recorded path. + +The regular-expression fixtures validate the same language, and both fixed-seed warm fixtures must reach multiple +lengths and every alternative family. Their exact distributions remain implementation-defined, so cross-library +timings are diagnostic while base/head comparisons protect the cost of each implementation's established behavior. + +An impossible Schema filter is deliberately not a cross-engine timing case. +The native runner reports bounded exhaustion, while a direct fast-check arbitrary +built with `Arbitrary.filter` does not return from generation +when no value can satisfy the predicate. Native exhaustion remains covered by +the Arbitrary tests instead of placing a permanently blocking fixture in the +performance harness. + +Zod parsing cases import `zod/v4` and call `safeParse` with `{ jitless: true }`; +its Standard Schema and codec cases use their native APIs. Valibot uses the +corresponding `is`, `safeParse` and Standard Schema APIs. The focused Effect +adapter family measures the overhead of public APIs that wrap parser issues. + +## Measurement model + +Each worker validates the fixture before and after measuring. Calibration finds +a batch large enough for the configured target duration. Each implementation +uses its own calibrated batch and executes in a separate process, with rotating +order within the scenario. + +Tinybench measures one synchronous batched task. The primary process result is: + +```text +nsPerOp = totalTimeMs * 1_000_000 / (task.runs * batchSize) +``` + +Tinybench latency statistics are retained as diagnostics and normalized by the +batch size. Independent Node processes, not Tinybench samples, are the +statistical observations. + +Base/head comparisons alternate execution order by round and analyze paired +log ratios with a deterministic bootstrap. The report keeps all raw worker +results so aggregates can be recalculated. + +## Fixture contract + +Each configured export is a factory: + +```ts +type RuntimePerfCase = { + readonly run: () => unknown + readonly validate: (result: unknown) => void +} + +type RuntimePerfCaseFactory = () => RuntimePerfCase +``` + +Construct schemas, steady-state adapters and deterministic inputs in the +factory. Cold fixtures deliberately construct them inside `run`. A fixture +must measure one named operation, perform no I/O and return no Promise. + +## Validation + +```sh +node --test packages/effect/runtimeperf/test/*.test.mts +pnpm exec dprint check package.json packages/effect/runtimeperf +``` diff --git a/packages/effect/runtimeperf/compare.mts b/packages/effect/runtimeperf/compare.mts new file mode 100644 index 000000000..7c2cf346e --- /dev/null +++ b/packages/effect/runtimeperf/compare.mts @@ -0,0 +1,265 @@ +import { spawnSync } from "node:child_process" +import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs" +import os from "node:os" +import { join } from "node:path" +import process from "node:process" +import { materializeFixture } from "./materialize.mts" +import { analyzePairs } from "./stats.mts" +import { + aggregateMeasurements, + calibrateFixture, + configPath, + coverageSummary, + effectDir, + formatNs, + hashFile, + libraryVersions, + loadRegistry, + makeRunId, + measureFixture, + parseArgs, + printTable, + relativeToRepo, + repoRoot, + reportPath, + resolveDefaults, + selectFixtures, + sha256, + workerPath, + writeJson +} from "./utils.mts" + +const usage = `Usage: pnpm runtimeperf-compare [suite[/fixture]|scenario] [options] + +Options: + --base Defaults to HEAD + --head Defaults to worktree + --rounds + --time + --warmup-time + --tier <0-3> + --family + --fail-on-regression +` + +const run = (command, args, options = {}) => + spawnSync(command, args, { + encoding: "utf8", + maxBuffer: 50 * 1024 * 1024, + ...options + }) + +const runGit = (args) => { + const result = run("git", args, { cwd: repoRoot }) + if (result.error) throw result.error + if (result.status !== 0) { + throw new Error(`${result.stdout}${result.stderr}`.trim()) + } + return result.stdout.trim() +} + +const resolveRef = (ref) => runGit(["rev-parse", "--verify", `${ref}^{commit}`]) + +const linkDirectory = (source, target) => { + if (!existsSync(target)) { + symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir") + } +} + +const removeWorktree = (path) => { + const result = run("git", ["worktree", "remove", "--force", path], { cwd: repoRoot }) + if (result.status !== 0) { + process.stderr.write(`${result.stdout}${result.stderr}`) + } +} + +const createWorktree = (runRoot, name, sha) => { + const path = join(runRoot, name) + const result = run("git", ["worktree", "add", "--detach", path, sha], { cwd: repoRoot }) + if (result.error) throw result.error + if (result.status !== 0) { + throw new Error(`${result.stdout}${result.stderr}`.trim()) + } + try { + linkDirectory(join(repoRoot, "node_modules"), join(path, "node_modules")) + linkDirectory(join(effectDir, "node_modules"), join(path, "packages", "effect", "node_modules")) + } catch (error) { + removeWorktree(path) + throw error + } + return path +} + +const worktreeState = () => { + const diff = runGit(["diff", "--binary", "HEAD", "--"]) + const untrackedOutput = runGit([ + "ls-files", + "--others", + "--exclude-standard", + "--", + "package.json", + "packages/effect" + ]) + const untracked = untrackedOutput === "" + ? [] + : untrackedOutput.split("\n").map((path) => ({ + path, + hash: hashFile(join(repoRoot, path)) + })) + return { + dirty: diff !== "" || untracked.length > 0, + diffHash: sha256(diff), + untracked + } +} + +const main = () => { + const options = parseArgs(process.argv.slice(2), { compare: true }) + if (options.help) { + process.stdout.write(usage) + return + } + const { config, fixtures } = loadRegistry() + const selected = selectFixtures(fixtures, options, { effectOnly: true }) + const defaults = resolveDefaults(config, options) + const baseSha = resolveRef(options.base) + const headSha = options.head === "worktree" ? resolveRef("HEAD") : resolveRef(options.head) + const state = worktreeState() + const runId = makeRunId() + const tmpRoot = join(repoRoot, "tmp", "runtimeperf", "worktrees") + mkdirSync(tmpRoot, { recursive: true }) + const runRoot = mkdtempSync(join(tmpRoot, "run-")) + const worktrees = [] + const results = [] + const executionOrder = [] + + try { + const baseRoot = createWorktree(runRoot, "base", baseSha) + worktrees.push(baseRoot) + const headRoot = options.head === "worktree" + ? repoRoot + : createWorktree(runRoot, "head", headSha) + if (headRoot !== repoRoot) worktrees.push(headRoot) + + for (const fixture of selected) { + const baseFixturePath = materializeFixture(baseRoot, fixture) + const headFixturePath = headRoot === repoRoot + ? fixture.fixturePath + : materializeFixture(headRoot, fixture) + const baseCalibration = calibrateFixture(fixture, defaults, baseFixturePath) + const headCalibration = calibrateFixture(fixture, defaults, headFixturePath) + const batchSize = Math.max(baseCalibration.batchSize, headCalibration.batchSize) + const baseMeasurements = [] + const headMeasurements = [] + + for (let round = 0; round < defaults.rounds; round++) { + const order = round % 2 === 0 ? ["base", "head"] : ["head", "base"] + for (const side of order) { + const measurement = side === "base" + ? measureFixture(fixture, defaults, batchSize, baseFixturePath) + : measureFixture(fixture, defaults, batchSize, headFixturePath) + ;(side === "base" ? baseMeasurements : headMeasurements).push(measurement) + executionOrder.push({ target: fixture.target, round: round + 1, side }) + } + } + + results.push({ + fixture, + batchSize, + calibration: { + base: baseCalibration, + head: headCalibration + }, + base: { + measurements: baseMeasurements, + aggregate: aggregateMeasurements(baseMeasurements) + }, + head: { + measurements: headMeasurements, + aggregate: aggregateMeasurements(headMeasurements) + }, + comparison: analyzePairs( + baseMeasurements.map((item) => item.nsPerOp), + headMeasurements.map((item) => item.nsPerOp), + { + iterations: defaults.bootstrapIterations, + seed: defaults.bootstrapSeed, + minImprovementPercent: defaults.minImprovementPercent, + maxRegressionPercent: defaults.maxRegressionPercent + } + ) + }) + } + } finally { + for (const worktree of worktrees.reverse()) { + removeWorktree(worktree) + } + rmSync(runRoot, { recursive: true, force: true }) + } + + const report = { + schemaVersion: 1, + kind: "comparison", + runId, + target: options.target ?? null, + filters: { + tier: options.tier ?? null, + family: options.family ?? null + }, + config: defaults, + base: { + ref: options.base, + sha: baseSha + }, + head: { + ref: options.head, + sha: headSha, + worktree: options.head === "worktree" ? state : undefined + }, + environment: { + node: process.version, + v8: process.versions.v8, + platform: process.platform, + arch: process.arch, + cpu: os.cpus()[0]?.model ?? "unknown" + }, + libraries: libraryVersions(), + artifactMode: "repository", + coverage: coverageSummary(selected), + hashes: { + config: hashFile(configPath), + worker: hashFile(workerPath), + fixtures: Object.fromEntries( + [...new Set(selected.map((fixture) => fixture.fixturePath))] + .map((path) => [relativeToRepo(path), hashFile(path)]) + ) + }, + executionOrder, + results + } + const path = reportPath(runId, options.target, "compare") + writeJson(path, report) + printTable( + ["fixture", "base", "head", "delta", "ci low", "ci high", "status"], + results.map((result) => [ + result.fixture.target, + formatNs(result.base.aggregate.median), + formatNs(result.head.aggregate.median), + `${result.comparison.deltaPercent.toFixed(2)}%`, + `${result.comparison.lowPercent.toFixed(2)}%`, + `${result.comparison.highPercent.toFixed(2)}%`, + result.comparison.status + ]) + ) + process.stdout.write(`\nReport: ${relativeToRepo(path)}\n`) + if (options.failOnRegression && results.some((result) => result.comparison.status === "regression")) { + process.exitCode = 1 + } +} + +try { + main() +} catch (error) { + process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : error}\n`) + process.exitCode = 1 +} diff --git a/packages/effect/runtimeperf/config.json b/packages/effect/runtimeperf/config.json new file mode 100644 index 000000000..261711dcd --- /dev/null +++ b/packages/effect/runtimeperf/config.json @@ -0,0 +1,1594 @@ +{ + "defaults": { + "rounds": 5, + "timeMs": 300, + "warmupTimeMs": 100, + "targetBatchTimeNs": 100000, + "maxBatchSize": 1048576, + "bootstrapIterations": 10000, + "bootstrapSeed": 1592594996, + "minImprovementPercent": 2, + "maxRegressionPercent": 5 + }, + "suites": [ + { + "name": "arbitrary", + "fixtures": [ + { + "file": "suites/arbitrary/fixtures/native.ts", + "defaults": { + "tier": 1, + "implementation": "effect", + "family": "arbitrary", + "astTags": [ + "Objects", + "Arrays", + "String", + "Number", + "Declaration", + "Suspend" + ], + "path": "valid" + }, + "cases": [ + { + "name": "cold-recursive-first-sample-native", + "export": "coldRecursiveFirstSample", + "scenario": "arbitrary-cold-recursive-first-sample", + "operation": "derive-and-sample", + "path": "cold", + "size": 1 + }, + { + "name": "recursive-sample-32-native", + "export": "recursiveSample32", + "scenario": "arbitrary-recursive-sample-32", + "operation": "sample", + "size": 32 + }, + { + "name": "optional-struct-sample-128-native", + "export": "optionalStructSample128", + "scenario": "arbitrary-optional-struct-sample-128", + "operation": "sample-optional-struct", + "astTags": [ + "Objects", + "Number" + ], + "size": 128 + }, + { + "name": "constrained-string-sample-128-native", + "export": "constrainedStringSample128", + "scenario": "arbitrary-constrained-string-sample-128", + "operation": "sample", + "astTags": [ + "String" + ], + "size": 128 + }, + { + "name": "cold-regexp-first-sample-native", + "export": "coldRegExpFirstSample", + "scenario": "arbitrary-cold-regexp-first-sample", + "operation": "derive-and-sample-pattern", + "path": "cold", + "astTags": [ + "String" + ], + "size": 1 + }, + { + "name": "regexp-sample-64-native", + "export": "regExpSample64", + "scenario": "arbitrary-regexp-sample-64", + "operation": "sample-pattern", + "astTags": [ + "String" + ], + "size": 64 + }, + { + "name": "regexp-check-falsify-shrink-native", + "export": "regExpCheckFalsifyAndShrink", + "scenario": "arbitrary-regexp-check-falsify-shrink", + "operation": "check-pattern-and-shrink", + "path": "failure", + "astTags": [ + "String" + ], + "size": 1 + }, + { + "name": "bounded-number-sample-128-native", + "export": "boundedNumberSample128", + "scenario": "arbitrary-bounded-number-sample-128", + "operation": "sample", + "astTags": [ + "Number" + ], + "size": 128 + }, + { + "name": "uint8-array-sample-128-native", + "export": "uint8ArraySample128", + "scenario": "arbitrary-uint8-array-sample-128", + "operation": "sample", + "astTags": [ + "Declaration", + "Arrays", + "Number" + ], + "size": 128 + }, + { + "name": "big-decimal-sample-128-native", + "export": "bigDecimalSample128", + "scenario": "arbitrary-big-decimal-sample-128", + "operation": "sample", + "astTags": [ + "Declaration", + "Objects", + "BigInt", + "Number" + ], + "size": 128 + }, + { + "name": "date-time-utc-sample-128-native", + "export": "dateTimeUtcSample128", + "scenario": "arbitrary-date-time-utc-sample-128", + "operation": "sample", + "astTags": [ + "Declaration", + "Number" + ], + "size": 128 + }, + { + "name": "time-zone-named-sample-128-native", + "export": "timeZoneNamedSample128", + "scenario": "arbitrary-time-zone-named-sample-128", + "operation": "sample", + "astTags": [ + "Declaration", + "Literal" + ], + "size": 128 + }, + { + "name": "time-zone-sample-128-native", + "export": "timeZoneSample128", + "scenario": "arbitrary-time-zone-sample-128", + "operation": "sample", + "astTags": [ + "Declaration", + "Union", + "Number", + "Literal" + ], + "size": 128 + }, + { + "name": "date-time-zoned-sample-128-native", + "export": "dateTimeZonedSample128", + "scenario": "arbitrary-date-time-zoned-sample-128", + "operation": "sample", + "astTags": [ + "Declaration", + "Objects", + "Union", + "Number", + "Literal" + ], + "size": 128 + }, + { + "name": "rare-filter-sample-32-native", + "export": "rareFilterSample32", + "scenario": "arbitrary-rare-filter-sample-32", + "operation": "sample", + "astTags": [ + "Number" + ], + "size": 32 + }, + { + "name": "unique-array-sample-32-native", + "export": "uniqueArraySample32", + "scenario": "arbitrary-unique-array-sample-32", + "operation": "sample", + "astTags": [ + "Arrays", + "Number" + ], + "size": 32 + }, + { + "name": "literal-sample-128-native", + "export": "literalSample128", + "scenario": "arbitrary-literal-sample-128", + "operation": "sample", + "astTags": [ + "Literal" + ], + "size": 128 + }, + { + "name": "map-sample-128-native", + "export": "mapSample128", + "scenario": "arbitrary-map-sample-128", + "operation": "sample-map", + "astTags": [ + "Number" + ], + "size": 128 + }, + { + "name": "passing-filter-sample-128-native", + "export": "passingFilterSample128", + "scenario": "arbitrary-passing-filter-sample-128", + "operation": "sample-filter", + "astTags": [ + "Number" + ], + "size": 128 + }, + { + "name": "selective-filter-sample-32-native", + "export": "selectiveFilterSample32", + "scenario": "arbitrary-selective-filter-sample-32", + "operation": "sample-filter", + "astTags": [ + "Number" + ], + "size": 32 + }, + { + "name": "filter-map-sample-128-native", + "export": "filterMapSample128", + "scenario": "arbitrary-filter-map-sample-128", + "operation": "sample-filter-map", + "astTags": [ + "Number" + ], + "size": 128 + }, + { + "name": "filter-check-falsify-shrink-native", + "export": "filterCheckFalsifyAndShrink", + "scenario": "arbitrary-filter-check-falsify-shrink", + "operation": "check-filter-and-shrink", + "path": "failure", + "astTags": [ + "Number" + ], + "size": 1 + }, + { + "name": "all-tuple-sample-128-native", + "export": "allTupleSample128", + "scenario": "arbitrary-all-tuple-sample-128", + "operation": "sample-all-tuple", + "astTags": [ + "Literal" + ], + "size": 128 + }, + { + "name": "all-record-sample-128-native", + "export": "allRecordSample128", + "scenario": "arbitrary-all-record-sample-128", + "operation": "sample-all-record", + "astTags": [ + "Union", + "Literal", + "Number" + ], + "size": 128 + }, + { + "name": "flat-map-sample-128-native", + "export": "flatMapSample128", + "scenario": "arbitrary-flat-map-sample-128", + "operation": "sample-flat-map", + "astTags": [ + "Arrays", + "Number" + ], + "size": 128 + }, + { + "name": "flat-map-check-falsify-shrink-native", + "export": "flatMapCheckFalsifyAndShrink", + "scenario": "arbitrary-flat-map-check-falsify-shrink", + "operation": "check-flat-map-and-shrink", + "path": "failure", + "astTags": [ + "Arrays", + "Number" + ], + "size": 1 + }, + { + "name": "flat-map-check-replay-native", + "export": "flatMapCheckReplay", + "scenario": "arbitrary-flat-map-check-replay", + "operation": "replay-flat-map", + "path": "failure", + "astTags": [ + "Arrays", + "Number" + ], + "size": 1 + }, + { + "name": "check-pass-100-native", + "export": "checkPass100", + "scenario": "arbitrary-check-pass-100", + "operation": "check", + "astTags": [ + "Number" + ], + "size": 100 + }, + { + "name": "test-schema-verify-generation-100-native", + "export": "testSchemaVerifyGeneration100", + "scenario": "test-schema-verify-generation-100", + "operation": "derive-and-check", + "astTags": [ + "Number" + ], + "size": 100 + }, + { + "name": "check-falsify-shrink-native", + "export": "checkFalsifyAndShrink", + "scenario": "arbitrary-check-falsify-shrink", + "operation": "check-and-shrink", + "path": "failure", + "astTags": [ + "Number" + ], + "size": 1 + }, + { + "name": "check-replay-native", + "export": "checkReplay", + "scenario": "arbitrary-check-replay", + "operation": "replay", + "path": "failure", + "astTags": [ + "Number" + ], + "size": 1 + } + ] + }, + { + "file": "suites/arbitrary/fixtures/fast-check-v4.ts", + "defaults": { + "tier": 1, + "implementation": "fast-check-v4", + "family": "arbitrary", + "astTags": [], + "path": "valid" + }, + "cases": [ + { + "name": "cold-recursive-first-sample-fast-check-v4", + "export": "coldRecursiveFirstSample", + "scenario": "arbitrary-cold-recursive-first-sample", + "operation": "derive-and-sample", + "path": "cold", + "size": 1 + }, + { + "name": "recursive-sample-32-fast-check-v4", + "export": "recursiveSample32", + "scenario": "arbitrary-recursive-sample-32", + "operation": "sample", + "size": 32 + }, + { + "name": "optional-struct-sample-128-fast-check-v4", + "export": "optionalStructSample128", + "scenario": "arbitrary-optional-struct-sample-128", + "operation": "sample-optional-struct", + "size": 128 + }, + { + "name": "constrained-string-sample-128-fast-check-v4", + "export": "constrainedStringSample128", + "scenario": "arbitrary-constrained-string-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "cold-regexp-first-sample-fast-check-v4", + "export": "coldRegExpFirstSample", + "scenario": "arbitrary-cold-regexp-first-sample", + "operation": "derive-and-sample-pattern", + "path": "cold", + "size": 1 + }, + { + "name": "regexp-sample-64-fast-check-v4", + "export": "regExpSample64", + "scenario": "arbitrary-regexp-sample-64", + "operation": "sample-pattern", + "size": 64 + }, + { + "name": "regexp-check-falsify-shrink-fast-check-v4", + "export": "regExpCheckFalsifyAndShrink", + "scenario": "arbitrary-regexp-check-falsify-shrink", + "operation": "check-pattern-and-shrink", + "path": "failure", + "size": 1 + }, + { + "name": "bounded-number-sample-128-fast-check-v4", + "export": "boundedNumberSample128", + "scenario": "arbitrary-bounded-number-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "uint8-array-sample-128-fast-check-v4", + "export": "uint8ArraySample128", + "scenario": "arbitrary-uint8-array-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "big-decimal-sample-128-fast-check-v4", + "export": "bigDecimalSample128", + "scenario": "arbitrary-big-decimal-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "date-time-utc-sample-128-fast-check-v4", + "export": "dateTimeUtcSample128", + "scenario": "arbitrary-date-time-utc-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "time-zone-named-sample-128-fast-check-v4", + "export": "timeZoneNamedSample128", + "scenario": "arbitrary-time-zone-named-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "time-zone-sample-128-fast-check-v4", + "export": "timeZoneSample128", + "scenario": "arbitrary-time-zone-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "date-time-zoned-sample-128-fast-check-v4", + "export": "dateTimeZonedSample128", + "scenario": "arbitrary-date-time-zoned-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "rare-filter-sample-32-fast-check-v4", + "export": "rareFilterSample32", + "scenario": "arbitrary-rare-filter-sample-32", + "operation": "sample", + "size": 32 + }, + { + "name": "unique-array-sample-32-fast-check-v4", + "export": "uniqueArraySample32", + "scenario": "arbitrary-unique-array-sample-32", + "operation": "sample", + "size": 32 + }, + { + "name": "literal-sample-128-fast-check-v4", + "export": "literalSample128", + "scenario": "arbitrary-literal-sample-128", + "operation": "sample", + "size": 128 + }, + { + "name": "map-sample-128-fast-check-v4", + "export": "mapSample128", + "scenario": "arbitrary-map-sample-128", + "operation": "sample-map", + "size": 128 + }, + { + "name": "passing-filter-sample-128-fast-check-v4", + "export": "passingFilterSample128", + "scenario": "arbitrary-passing-filter-sample-128", + "operation": "sample-filter", + "size": 128 + }, + { + "name": "selective-filter-sample-32-fast-check-v4", + "export": "selectiveFilterSample32", + "scenario": "arbitrary-selective-filter-sample-32", + "operation": "sample-filter", + "size": 32 + }, + { + "name": "filter-map-sample-128-fast-check-v4", + "export": "filterMapSample128", + "scenario": "arbitrary-filter-map-sample-128", + "operation": "sample-filter-map", + "size": 128 + }, + { + "name": "filter-check-falsify-shrink-fast-check-v4", + "export": "filterCheckFalsifyAndShrink", + "scenario": "arbitrary-filter-check-falsify-shrink", + "operation": "check-filter-and-shrink", + "path": "failure", + "size": 1 + }, + { + "name": "all-tuple-sample-128-fast-check-v4", + "export": "allTupleSample128", + "scenario": "arbitrary-all-tuple-sample-128", + "operation": "sample-all-tuple", + "size": 128 + }, + { + "name": "all-record-sample-128-fast-check-v4", + "export": "allRecordSample128", + "scenario": "arbitrary-all-record-sample-128", + "operation": "sample-all-record", + "size": 128 + }, + { + "name": "flat-map-sample-128-fast-check-v4", + "export": "flatMapSample128", + "scenario": "arbitrary-flat-map-sample-128", + "operation": "sample-flat-map", + "size": 128 + }, + { + "name": "flat-map-check-falsify-shrink-fast-check-v4", + "export": "flatMapCheckFalsifyAndShrink", + "scenario": "arbitrary-flat-map-check-falsify-shrink", + "operation": "check-flat-map-and-shrink", + "path": "failure", + "size": 1 + }, + { + "name": "flat-map-check-replay-fast-check-v4", + "export": "flatMapCheckReplay", + "scenario": "arbitrary-flat-map-check-replay", + "operation": "replay-flat-map", + "path": "failure", + "size": 1 + }, + { + "name": "check-pass-100-fast-check-v4", + "export": "checkPass100", + "scenario": "arbitrary-check-pass-100", + "operation": "check", + "size": 100 + }, + { + "name": "test-schema-verify-generation-100-fast-check-v4", + "export": "testSchemaVerifyGeneration100", + "scenario": "test-schema-verify-generation-100", + "operation": "derive-and-check", + "size": 100 + }, + { + "name": "check-falsify-shrink-fast-check-v4", + "export": "checkFalsifyAndShrink", + "scenario": "arbitrary-check-falsify-shrink", + "operation": "check-and-shrink", + "path": "failure", + "size": 1 + }, + { + "name": "check-replay-fast-check-v4", + "export": "checkReplay", + "scenario": "arbitrary-check-replay", + "operation": "replay", + "path": "failure", + "size": 1 + } + ] + } + ] + }, + { + "name": "schema", + "fixtures": [ + { + "file": "suites/schema/fixtures/comparison.ts", + "defaults": { + "tier": 1, + "operation": "decode" + }, + "cases": [ + { + "name": "object-32-valid-effect", + "export": "effectObject32Valid", + "scenario": "object-32-valid", + "implementation": "effect", + "family": "objects", + "astTags": [ + "Objects" + ], + "path": "valid", + "size": 32 + }, + { + "name": "object-32-invalid-last-effect", + "export": "effectObject32InvalidLast", + "scenario": "object-32-invalid-last", + "implementation": "effect", + "family": "objects", + "astTags": [ + "Objects" + ], + "path": "invalid", + "size": 32 + }, + { + "name": "array-32-valid-effect", + "export": "effectArray32Valid", + "scenario": "array-32-valid", + "implementation": "effect", + "family": "arrays", + "astTags": [ + "Arrays" + ], + "path": "valid", + "size": 32 + }, + { + "name": "array-32-invalid-last-effect", + "export": "effectArray32InvalidLast", + "scenario": "array-32-invalid-last", + "implementation": "effect", + "family": "arrays", + "astTags": [ + "Arrays" + ], + "path": "invalid", + "size": 32 + }, + { + "name": "record-32-valid-effect", + "export": "effectRecord32Valid", + "scenario": "record-32-valid", + "implementation": "effect", + "family": "records", + "astTags": [ + "Objects" + ], + "path": "valid", + "size": 32 + }, + { + "name": "record-32-invalid-last-effect", + "export": "effectRecord32InvalidLast", + "scenario": "record-32-invalid-last", + "implementation": "effect", + "family": "records", + "astTags": [ + "Objects" + ], + "path": "invalid", + "size": 32 + }, + { + "name": "literal-100-valid-last-effect", + "export": "effectLiteral100ValidLast", + "scenario": "literal-100-valid-last", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Literal" + ], + "path": "valid", + "size": 100 + }, + { + "name": "literal-100-invalid-effect", + "export": "effectLiteral100Invalid", + "scenario": "literal-100-invalid", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Literal" + ], + "path": "invalid", + "size": 100 + }, + { + "name": "tagged-100-valid-last-effect", + "export": "effectTagged100ValidLast", + "scenario": "tagged-100-valid-last", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "valid", + "size": 100 + }, + { + "name": "tagged-100-invalid-selected-effect", + "export": "effectTagged100InvalidSelected", + "scenario": "tagged-100-invalid-selected", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "invalid", + "size": 100 + }, + { + "name": "tagged-100-invalid-tag-effect", + "export": "effectTagged100InvalidTag", + "scenario": "tagged-100-invalid-tag", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "invalid", + "size": 100 + }, + { + "name": "multi-sentinel-100-valid-first-effect", + "export": "effectMultiSentinel100ValidFirst", + "scenario": "multi-sentinel-100-valid-first", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "valid", + "size": 100 + }, + { + "name": "multi-sentinel-100-invalid-variant-effect", + "export": "effectMultiSentinel100InvalidVariant", + "scenario": "multi-sentinel-100-invalid-variant", + "implementation": "effect", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "invalid", + "size": 100 + } + ] + }, + { + "file": "suites/schema/fixtures/behavior.ts", + "defaults": { + "tier": 2, + "implementation": "effect", + "operation": "decode" + }, + "cases": [ + { + "name": "checks-invalid-first", + "export": "checksInvalidFirst", + "scenario": "checks-invalid-first", + "family": "checks", + "astTags": [ + "String" + ], + "path": "invalid", + "size": 2 + }, + { + "name": "checks-invalid-last", + "export": "checksInvalidLast", + "scenario": "checks-invalid-last", + "family": "checks", + "astTags": [ + "String" + ], + "path": "invalid", + "size": 2 + }, + { + "name": "encoding-check-valid", + "export": "encodingCheckValid", + "scenario": "encoding-check-valid", + "family": "checks", + "astTags": [ + "String" + ], + "path": "valid", + "size": 1 + }, + { + "name": "template-literal-linear-valid", + "export": "templateLiteralLinearValid", + "scenario": "template-literal-linear-valid", + "family": "template-literal", + "astTags": [ + "TemplateLiteral", + "Arrays" + ], + "path": "valid", + "size": 5 + }, + { + "name": "template-literal-linear-invalid", + "export": "templateLiteralLinearInvalid", + "scenario": "template-literal-linear-invalid", + "family": "template-literal", + "astTags": [ + "TemplateLiteral", + "Arrays" + ], + "path": "invalid", + "size": 5 + }, + { + "name": "template-literal-backtracking-valid", + "export": "templateLiteralBacktrackingValid", + "scenario": "template-literal-backtracking-valid", + "family": "template-literal", + "astTags": [ + "TemplateLiteral", + "Arrays", + "String" + ], + "path": "valid", + "size": 4 + }, + { + "name": "template-literal-backtracking-invalid", + "export": "templateLiteralBacktrackingInvalid", + "scenario": "template-literal-backtracking-invalid", + "family": "template-literal", + "astTags": [ + "TemplateLiteral", + "Arrays", + "String" + ], + "path": "invalid", + "size": 4 + }, + { + "name": "template-literal-transformed-valid", + "export": "templateLiteralTransformedValid", + "scenario": "template-literal-transformed-valid", + "family": "template-literal", + "astTags": [ + "TemplateLiteral", + "Arrays", + "String", + "Number" + ], + "path": "valid", + "size": 3 + }, + { + "name": "template-literal-record-32-valid", + "export": "templateLiteralRecord32Valid", + "scenario": "template-literal-record-32-valid", + "family": "template-literal", + "astTags": [ + "TemplateLiteral", + "Objects", + "String", + "Number" + ], + "path": "valid", + "size": 32 + }, + { + "name": "transformation-decode-valid", + "export": "transformationDecodeValid", + "scenario": "transformation-decode-valid", + "family": "transformation", + "astTags": [ + "String", + "Number" + ], + "path": "valid", + "size": 1 + }, + { + "name": "transformation-decode-invalid", + "export": "transformationDecodeInvalid", + "scenario": "transformation-decode-invalid", + "family": "transformation", + "astTags": [ + "String", + "Number" + ], + "path": "invalid", + "size": 1 + }, + { + "name": "transformation-encode-valid", + "export": "transformationEncodeValid", + "scenario": "transformation-encode-valid", + "family": "transformation", + "astTags": [ + "String", + "Number" + ], + "operation": "encode", + "path": "valid", + "size": 1 + }, + { + "name": "encoding-chain-8-decode-valid", + "export": "encodingChain8DecodeValid", + "scenario": "encoding-chain-8-decode-valid", + "family": "transformation", + "astTags": [ + "String", + "Number" + ], + "path": "valid", + "size": 8 + }, + { + "name": "encoding-chain-8-decode-invalid", + "export": "encodingChain8DecodeInvalid", + "scenario": "encoding-chain-8-decode-invalid", + "family": "transformation", + "astTags": [ + "String", + "Number" + ], + "path": "invalid", + "size": 8 + }, + { + "name": "encoding-chain-8-encode-valid", + "export": "encodingChain8EncodeValid", + "scenario": "encoding-chain-8-encode-valid", + "family": "transformation", + "astTags": [ + "String", + "Number" + ], + "operation": "encode", + "path": "valid", + "size": 8 + }, + { + "name": "record-32-transformed-keys-valid", + "export": "transformedKeyRecordValid", + "scenario": "record-32-transformed-keys-valid", + "family": "records", + "astTags": [ + "Objects", + "String" + ], + "path": "valid", + "size": 32 + }, + { + "name": "optional-valid", + "export": "optionalValid", + "scenario": "optional-valid", + "family": "optional", + "astTags": [ + "Objects", + "Union", + "Undefined" + ], + "path": "valid", + "size": 3 + }, + { + "name": "optional-present-valid", + "export": "optionalPresentValid", + "scenario": "optional-present-valid", + "family": "optional", + "astTags": [ + "Objects", + "Union", + "Undefined" + ], + "path": "valid", + "size": 3 + }, + { + "name": "optional-present-invalid", + "export": "optionalPresentInvalid", + "scenario": "optional-present-invalid", + "family": "optional", + "astTags": [ + "Objects", + "Union", + "Undefined" + ], + "path": "invalid", + "size": 3 + }, + { + "name": "object-32-suspended-middle-valid", + "export": "object32SuspendedMiddleValid", + "scenario": "object-32-suspended-middle-valid", + "family": "objects", + "astTags": [ + "Objects", + "String", + "Transformation" + ], + "path": "valid", + "size": 32 + }, + { + "name": "literal-2-valid-last", + "export": "literal2ValidLast", + "scenario": "literal-2-valid-last", + "family": "union", + "astTags": [ + "Union", + "Literal" + ], + "path": "valid", + "size": 2 + }, + { + "name": "literal-100-valid-first", + "export": "literal100ValidFirst", + "scenario": "literal-100-valid-first", + "family": "union", + "astTags": [ + "Union", + "Literal" + ], + "path": "valid", + "size": 100 + }, + { + "name": "homogeneous-union-100-invalid", + "export": "homogeneousUnion100Invalid", + "scenario": "homogeneous-union-100-invalid", + "family": "union", + "astTags": [ + "Union", + "String" + ], + "path": "invalid", + "size": 100 + }, + { + "name": "tagged-2-valid-last", + "export": "tagged2ValidLast", + "scenario": "tagged-2-valid-last", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "valid", + "size": 2 + }, + { + "name": "tagged-with-fallback-valid", + "export": "taggedWithFallbackValid", + "scenario": "tagged-with-fallback-valid", + "family": "union", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "path": "valid", + "size": 2 + }, + { + "name": "property-order-original", + "export": "propertyOrderOriginal", + "scenario": "property-order-original", + "family": "parse-options", + "astTags": [ + "Objects" + ], + "path": "valid", + "size": 3 + }, + { + "name": "recursive-tree-depth-16-valid", + "export": "recursiveTreeDepth16Valid", + "scenario": "recursive-tree-depth-16-valid", + "family": "recursion", + "astTags": [ + "Objects", + "Arrays", + "Suspend" + ], + "path": "valid", + "size": 16 + } + ] + }, + { + "file": "suites/schema/fixtures/adapters.ts", + "defaults": { + "tier": 2, + "implementation": "effect", + "family": "adapters", + "astTags": [ + "Objects" + ], + "path": "valid", + "size": 2 + }, + "cases": [ + { + "name": "adapter-parser-decode-exit-invalid", + "export": "parserExitInvalid", + "scenario": "adapter-parser-decode-exit-invalid", + "operation": "decode", + "path": "invalid", + "adapter": "SchemaParser Exit" + }, + { + "name": "adapter-decode-exit-valid", + "export": "exitValid", + "scenario": "adapter-decode-exit-valid", + "operation": "decode", + "adapter": "Exit" + }, + { + "name": "adapter-decode-exit-invalid", + "export": "exitInvalid", + "scenario": "adapter-decode-exit-invalid", + "operation": "decode", + "path": "invalid", + "adapter": "Exit" + }, + { + "name": "adapter-decode-option-valid", + "export": "optionValid", + "scenario": "adapter-decode-option-valid", + "operation": "decode", + "adapter": "Option" + }, + { + "name": "adapter-decode-option-invalid", + "export": "optionInvalid", + "scenario": "adapter-decode-option-invalid", + "operation": "decode", + "path": "invalid", + "adapter": "Option" + }, + { + "name": "adapter-decode-result-valid", + "export": "resultValid", + "scenario": "adapter-decode-result-valid", + "operation": "decode", + "adapter": "Result" + }, + { + "name": "adapter-decode-result-invalid", + "export": "resultInvalid", + "scenario": "adapter-decode-result-invalid", + "operation": "decode", + "path": "invalid", + "adapter": "Result" + }, + { + "name": "adapter-decode-sync-invalid", + "export": "syncInvalid", + "scenario": "adapter-decode-sync-invalid", + "operation": "decode", + "path": "invalid", + "adapter": "Sync" + } + ] + }, + { + "file": "suites/schema/fixtures/cold.ts", + "defaults": { + "tier": 2, + "family": "cold", + "astTags": [ + "Objects" + ], + "path": "valid", + "size": 32 + }, + "cases": [ + { + "name": "schema-creation-template-literal-effect", + "export": "effectSchemaCreationTemplateLiteral", + "scenario": "schema-creation-template-literal", + "implementation": "effect", + "operation": "schema", + "astTags": [ + "TemplateLiteral" + ], + "size": 5 + }, + { + "name": "first-decode-checked-object-32-effect", + "export": "effectFirstDecodeCheckedObject32", + "scenario": "first-decode-checked-object-32", + "implementation": "effect", + "operation": "first-decode" + }, + { + "name": "first-decode-template-literal-effect", + "export": "effectFirstDecodeTemplateLiteral", + "scenario": "first-decode-template-literal", + "implementation": "effect", + "operation": "first-decode", + "astTags": [ + "TemplateLiteral" + ], + "size": 5 + }, + { + "name": "first-decode-record-32-effect", + "export": "effectFirstDecodeRecord32", + "scenario": "first-decode-record-32", + "implementation": "effect", + "operation": "first-decode" + }, + { + "name": "first-decode-literal-100-effect", + "export": "effectFirstDecodeLiteral100", + "scenario": "first-decode-literal-100", + "implementation": "effect", + "operation": "first-decode", + "astTags": [ + "Union", + "Literal" + ], + "size": 100 + }, + { + "name": "first-decode-tagged-100-effect", + "export": "effectFirstDecodeTagged100", + "scenario": "first-decode-tagged-100", + "implementation": "effect", + "operation": "first-decode", + "astTags": [ + "Union", + "Objects", + "Literal" + ], + "size": 100 + }, + { + "name": "first-decode-encoding-chain-8-effect", + "export": "effectFirstDecodeEncodingChain8", + "scenario": "first-decode-encoding-chain-8", + "implementation": "effect", + "operation": "first-decode", + "astTags": [ + "String", + "Number" + ], + "size": 8 + } + ] + } + ] + }, + { + "name": "schema-benchmarks", + "fixtures": [ + { + "file": "suites/schema-benchmarks/fixtures/valibot.ts", + "defaults": { + "tier": 3, + "implementation": "valibot", + "family": "schema-benchmarks", + "astTags": [], + "size": "product" + }, + "cases": [ + { + "name": "initialization-schema-valibot", + "export": "initializationSchema", + "scenario": "schema-benchmarks-initialization-schema", + "operation": "schema", + "path": "cold" + }, + { + "name": "validation-valid-valibot", + "export": "validationValid", + "scenario": "schema-benchmarks-validation-valid", + "operation": "is", + "path": "valid" + }, + { + "name": "validation-invalid-valibot", + "export": "validationInvalid", + "scenario": "schema-benchmarks-validation-invalid", + "operation": "is", + "path": "invalid" + }, + { + "name": "parsing-all-valid-valibot", + "export": "parsingAllValid", + "scenario": "schema-benchmarks-parsing-all-valid", + "operation": "safe-parse-all", + "path": "valid" + }, + { + "name": "parsing-all-invalid-valibot", + "export": "parsingAllInvalid", + "scenario": "schema-benchmarks-parsing-all-invalid", + "operation": "safe-parse-all", + "path": "invalid" + }, + { + "name": "parsing-first-valid-valibot", + "export": "parsingFirstValid", + "scenario": "schema-benchmarks-parsing-first-valid", + "operation": "safe-parse-first", + "path": "valid" + }, + { + "name": "parsing-first-invalid-valibot", + "export": "parsingFirstInvalid", + "scenario": "schema-benchmarks-parsing-first-invalid", + "operation": "safe-parse-first", + "path": "invalid" + }, + { + "name": "standard-all-valid-valibot", + "export": "standardAllValid", + "scenario": "schema-benchmarks-standard-all-valid", + "operation": "standard-schema-all", + "path": "valid" + }, + { + "name": "standard-all-invalid-valibot", + "export": "standardAllInvalid", + "scenario": "schema-benchmarks-standard-all-invalid", + "operation": "standard-schema-all", + "path": "invalid" + } + ] + }, + { + "file": "suites/schema-benchmarks/fixtures/zod.ts", + "defaults": { + "tier": 3, + "implementation": "zod4", + "family": "schema-benchmarks", + "astTags": [], + "size": "product" + }, + "cases": [ + { + "name": "initialization-schema-zod4", + "export": "initializationSchema", + "scenario": "schema-benchmarks-initialization-schema", + "operation": "schema", + "path": "cold" + }, + { + "name": "parsing-all-valid-zod4", + "export": "parsingAllValid", + "scenario": "schema-benchmarks-parsing-all-valid", + "operation": "safe-parse-all", + "path": "valid" + }, + { + "name": "parsing-all-invalid-zod4", + "export": "parsingAllInvalid", + "scenario": "schema-benchmarks-parsing-all-invalid", + "operation": "safe-parse-all", + "path": "invalid" + }, + { + "name": "standard-all-valid-zod4", + "export": "standardAllValid", + "scenario": "schema-benchmarks-standard-all-valid", + "operation": "standard-schema-all", + "path": "valid" + }, + { + "name": "standard-all-invalid-zod4", + "export": "standardAllInvalid", + "scenario": "schema-benchmarks-standard-all-invalid", + "operation": "standard-schema-all", + "path": "invalid" + }, + { + "name": "codec-typed-encode-zod4", + "export": "codecTypedEncode", + "scenario": "schema-benchmarks-codec-typed-encode", + "operation": "encode", + "path": "valid", + "size": 1 + }, + { + "name": "codec-typed-decode-zod4", + "export": "codecTypedDecode", + "scenario": "schema-benchmarks-codec-typed-decode", + "operation": "decode", + "path": "valid", + "size": 1 + } + ] + }, + { + "file": "suites/schema-benchmarks/fixtures/effect-beta.ts", + "defaults": { + "tier": 3, + "implementation": "effect", + "family": "schema-benchmarks", + "astTags": [ + "Objects", + "Arrays", + "Literal", + "String", + "Number", + "Null", + "Declaration" + ], + "size": "product" + }, + "cases": [ + { + "name": "initialization-schema", + "export": "initializationSchema", + "scenario": "schema-benchmarks-initialization-schema", + "operation": "schema", + "path": "cold" + }, + { + "name": "initialization-decoder", + "export": "initializationDecoder", + "scenario": "schema-benchmarks-initialization-decoder", + "operation": "schema-and-decoder", + "path": "cold" + }, + { + "name": "validation-valid", + "export": "validationValid", + "scenario": "schema-benchmarks-validation-valid", + "operation": "is", + "path": "valid" + }, + { + "name": "validation-invalid", + "export": "validationInvalid", + "scenario": "schema-benchmarks-validation-invalid", + "operation": "is", + "path": "invalid" + }, + { + "name": "parsing-all-valid", + "export": "parsingAllValid", + "scenario": "schema-benchmarks-parsing-all-valid", + "operation": "decode-option-all", + "path": "valid" + }, + { + "name": "parsing-all-invalid", + "export": "parsingAllInvalid", + "scenario": "schema-benchmarks-parsing-all-invalid", + "operation": "decode-option-all", + "path": "invalid" + }, + { + "name": "parsing-first-valid", + "export": "parsingFirstValid", + "scenario": "schema-benchmarks-parsing-first-valid", + "operation": "decode-option-first", + "path": "valid" + }, + { + "name": "parsing-first-invalid", + "export": "parsingFirstInvalid", + "scenario": "schema-benchmarks-parsing-first-invalid", + "operation": "decode-option-first", + "path": "invalid" + }, + { + "name": "standard-all-valid", + "export": "standardAllValid", + "scenario": "schema-benchmarks-standard-all-valid", + "operation": "standard-schema-all", + "path": "valid" + }, + { + "name": "standard-all-invalid", + "export": "standardAllInvalid", + "scenario": "schema-benchmarks-standard-all-invalid", + "operation": "standard-schema-all", + "path": "invalid" + }, + { + "name": "standard-first-valid", + "export": "standardFirstValid", + "scenario": "schema-benchmarks-standard-first-valid", + "operation": "standard-schema-first", + "path": "valid" + }, + { + "name": "standard-first-invalid", + "export": "standardFirstInvalid", + "scenario": "schema-benchmarks-standard-first-invalid", + "operation": "standard-schema-first", + "path": "invalid" + }, + { + "name": "codec-typed-encode", + "export": "codecTypedEncode", + "scenario": "schema-benchmarks-codec-typed-encode", + "operation": "encode-sync", + "path": "valid", + "astTags": [ + "String", + "BigInt" + ], + "size": 1 + }, + { + "name": "codec-typed-decode", + "export": "codecTypedDecode", + "scenario": "schema-benchmarks-codec-typed-decode", + "operation": "decode-sync", + "path": "valid", + "astTags": [ + "String", + "BigInt" + ], + "size": 1 + }, + { + "name": "codec-unknown-encode", + "export": "codecUnknownEncode", + "scenario": "schema-benchmarks-codec-unknown-encode", + "operation": "encode-unknown-sync", + "path": "valid", + "astTags": [ + "String", + "BigInt" + ], + "size": 1 + }, + { + "name": "codec-unknown-decode", + "export": "codecUnknownDecode", + "scenario": "schema-benchmarks-codec-unknown-decode", + "operation": "decode-unknown-sync", + "path": "valid", + "astTags": [ + "String", + "BigInt" + ], + "size": 1 + } + ] + } + ] + } + ] +} diff --git a/packages/effect/runtimeperf/materialize.mts b/packages/effect/runtimeperf/materialize.mts new file mode 100644 index 000000000..5635ebe8d --- /dev/null +++ b/packages/effect/runtimeperf/materialize.mts @@ -0,0 +1,14 @@ +import { cpSync, existsSync, mkdirSync } from "node:fs" +import { basename, dirname, join, relative } from "node:path" +import { runtimeperfDir, sanitize } from "./utils.mts" + +export const materializeFixture = (targetRoot, fixture) => { + const sourceDir = dirname(fixture.fixturePath) + const name = sanitize(relative(runtimeperfDir, sourceDir)) + const targetDir = join(targetRoot, "packages", "effect", ".runtimeperf-compare", name) + mkdirSync(dirname(targetDir), { recursive: true }) + if (!existsSync(targetDir)) { + cpSync(sourceDir, targetDir, { recursive: true }) + } + return join(targetDir, basename(fixture.fixturePath)) +} diff --git a/packages/effect/runtimeperf/run.mts b/packages/effect/runtimeperf/run.mts new file mode 100644 index 000000000..b97665f60 --- /dev/null +++ b/packages/effect/runtimeperf/run.mts @@ -0,0 +1,163 @@ +import os from "node:os" +import process from "node:process" +import { analyzePairs } from "./stats.mts" +import { + aggregateMeasurements, + calibrateFixture, + configPath, + coverageSummary, + currentGitState, + formatNs, + hashFile, + libraryVersions, + loadRegistry, + makeRunId, + measureFixture, + parseArgs, + printTable, + relativeToRepo, + reportPath, + resolveDefaults, + selectFixtures, + workerPath, + writeJson +} from "./utils.mts" + +const usage = `Usage: pnpm runtimeperf [suite[/fixture]|scenario] [options] + +Options: + --rounds + --time + --warmup-time + --tier <0-3> + --family + --implementation +` + +const rotate = (items, offset) => items.map((_, index) => items[(index + offset) % items.length]) + +const main = () => { + const options = parseArgs(process.argv.slice(2)) + if (options.help) { + process.stdout.write(usage) + return + } + const { config, fixtures } = loadRegistry() + const selected = selectFixtures(fixtures, options) + const defaults = resolveDefaults(config, options) + const runId = makeRunId() + const groups = Map.groupBy(selected, (fixture) => fixture.scenario) + const results = [] + const executionOrder = [] + + for (const [scenario, group] of groups) { + const calibrations = new Map(group.map((fixture) => [fixture, calibrateFixture(fixture, defaults)])) + const byTarget = new Map(group.map((fixture) => [fixture.target, []])) + + for (let round = 0; round < defaults.rounds; round++) { + for (const fixture of rotate(group, round % group.length)) { + const calibration = calibrations.get(fixture) + const measurement = measureFixture(fixture, defaults, calibration.batchSize) + byTarget.get(fixture.target).push(measurement) + executionOrder.push({ scenario, round: round + 1, target: fixture.target }) + } + } + + for (const fixture of group) { + const calibration = calibrations.get(fixture) + const measurements = byTarget.get(fixture.target) + results.push({ + fixture, + batchSize: calibration.batchSize, + calibration, + measurements, + aggregate: aggregateMeasurements(measurements) + }) + } + } + + const crossLibrary = [] + for (const [scenario, group] of Map.groupBy(results, (result) => result.fixture.scenario)) { + const effect = group.find((result) => result.fixture.implementation === "effect") + if (!effect) continue + for (const candidate of group) { + if (candidate === effect) continue + crossLibrary.push({ + scenario, + implementation: candidate.fixture.implementation, + comparison: analyzePairs( + effect.measurements.map((item) => item.nsPerOp), + candidate.measurements.map((item) => item.nsPerOp), + { + iterations: defaults.bootstrapIterations, + seed: defaults.bootstrapSeed + } + ) + }) + } + } + + const report = { + schemaVersion: 1, + kind: "single", + runId, + target: options.target ?? null, + filters: { + tier: options.tier ?? null, + family: options.family ?? null, + implementation: options.implementation ?? null + }, + config: defaults, + environment: { + node: process.version, + v8: process.versions.v8, + platform: process.platform, + arch: process.arch, + cpu: os.cpus()[0]?.model ?? "unknown" + }, + libraries: libraryVersions(), + crossLibraryDecodeApis: { + effect: "SchemaParser.decodeUnknownExit (SchemaIssue)", + valibot: "safeParser", + zod4: "safeParse ({ jitless: true })" + }, + artifactMode: "repository", + git: currentGitState(), + coverage: coverageSummary(selected), + hashes: { + config: hashFile(configPath), + worker: hashFile(workerPath), + fixtures: Object.fromEntries( + [...new Set(selected.map((fixture) => fixture.fixturePath))] + .map((path) => [relativeToRepo(path), hashFile(path)]) + ) + }, + executionOrder, + results, + crossLibrary + } + const path = reportPath(runId, options.target, "single") + writeJson(path, report) + const comparisons = new Map(crossLibrary.map((item) => [`${item.scenario}/${item.implementation}`, item])) + printTable( + ["scenario", "implementation", "ns/op", "mad", "vs Effect"], + results.map((result) => { + const comparison = comparisons.get(`${result.fixture.scenario}/${result.fixture.implementation}`) + return [ + result.fixture.scenario, + result.fixture.implementation, + formatNs(result.aggregate.median), + formatNs(result.aggregate.mad), + comparison ? `${comparison.comparison.ratio.toFixed(3)}x` : "-" + ] + }) + ) + process.stdout.write(`\nReport: ${relativeToRepo(path)}\n`) +} + +try { + main() +} catch (error) { + process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : error}\n`) + process.exitCode = 1 +} diff --git a/packages/effect/runtimeperf/stats.mts b/packages/effect/runtimeperf/stats.mts new file mode 100644 index 000000000..4f4be2068 --- /dev/null +++ b/packages/effect/runtimeperf/stats.mts @@ -0,0 +1,131 @@ +const assertFiniteNumbers = (values, label) => { + if (!Array.isArray(values) || values.length === 0) { + throw new Error(`${label} must be a non-empty array`) + } + for (const value of values) { + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`${label} must contain finite positive numbers`) + } + } +} + +export const median = (values) => { + if (!Array.isArray(values) || values.length === 0) { + throw new Error("values must be a non-empty array") + } + const sorted = values.slice().sort((a, b) => a - b) + const middle = Math.floor(sorted.length / 2) + return sorted.length % 2 === 0 + ? (sorted[middle - 1] + sorted[middle]) / 2 + : sorted[middle] +} + +export const percentile = (values, probability) => { + if (!Array.isArray(values) || values.length === 0) { + throw new Error("values must be a non-empty array") + } + if (!Number.isFinite(probability) || probability < 0 || probability > 1) { + throw new Error("probability must be between 0 and 1") + } + const sorted = values.slice().sort((a, b) => a - b) + const index = (sorted.length - 1) * probability + const lower = Math.floor(index) + const upper = Math.ceil(index) + if (lower === upper) return sorted[lower] + const weight = index - lower + return sorted[lower] * (1 - weight) + sorted[upper] * weight +} + +const makeRandom = (seed) => { + let state = seed >>> 0 + return () => { + state += 0x6d2b79f5 + let value = state + value = Math.imul(value ^ value >>> 15, value | 1) + value ^= value + Math.imul(value ^ value >>> 7, value | 61) + return ((value ^ value >>> 14) >>> 0) / 4294967296 + } +} + +export const aggregate = (values) => { + assertFiniteNumbers(values, "values") + const center = median(values) + return { + median: center, + min: Math.min(...values), + max: Math.max(...values), + mad: median(values.map((value) => Math.abs(value - center))) + } +} + +export const bootstrapMedianLogRatio = ( + ratios, + { confidence = 0.95, iterations = 10_000, seed = 0x5eed1234 } = {} +) => { + assertFiniteNumbers(ratios, "ratios") + if (!Number.isInteger(iterations) || iterations <= 0) { + throw new Error("iterations must be a positive integer") + } + if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) { + throw new Error("confidence must be between 0 and 1") + } + if (!Number.isInteger(seed)) { + throw new Error("seed must be an integer") + } + + const logRatios = ratios.map(Math.log) + const random = makeRandom(seed) + const samples = new Array(iterations) + const resample = new Array(logRatios.length) + for (let iteration = 0; iteration < iterations; iteration++) { + for (let index = 0; index < logRatios.length; index++) { + resample[index] = logRatios[Math.floor(random() * logRatios.length)] + } + samples[iteration] = median(resample) + } + const tail = (1 - confidence) / 2 + return { + ratio: Math.exp(median(logRatios)), + lowRatio: Math.exp(percentile(samples, tail)), + highRatio: Math.exp(percentile(samples, 1 - tail)), + confidence, + iterations, + seed + } +} + +export const analyzePairs = ( + base, + head, + { + confidence = 0.95, + iterations = 10_000, + seed = 0x5eed1234, + minImprovementPercent = 2, + maxRegressionPercent = 5 + } = {} +) => { + assertFiniteNumbers(base, "base") + assertFiniteNumbers(head, "head") + if (base.length !== head.length) { + throw new Error("base and head must contain the same number of observations") + } + const ratios = base.map((value, index) => head[index] / value) + const interval = bootstrapMedianLogRatio(ratios, { confidence, iterations, seed }) + const deltaPercent = (interval.ratio - 1) * 100 + const lowPercent = (interval.lowRatio - 1) * 100 + const highPercent = (interval.highRatio - 1) * 100 + const status = interval.highRatio < 1 - minImprovementPercent / 100 + ? "improvement" + : interval.lowRatio > 1 + maxRegressionPercent / 100 + ? "regression" + : "inconclusive" + return { + ratios, + deltaPercent, + lowPercent, + highPercent, + status, + ...interval + } +} diff --git a/packages/effect/runtimeperf/suites/arbitrary/fixtures/fast-check-v4.ts b/packages/effect/runtimeperf/suites/arbitrary/fixtures/fast-check-v4.ts new file mode 100644 index 000000000..374da26c4 --- /dev/null +++ b/packages/effect/runtimeperf/suites/arbitrary/fixtures/fast-check-v4.ts @@ -0,0 +1,414 @@ +import * as BigDecimal from "effect/BigDecimal" +import * as DateTime from "effect/DateTime" +import * as Option from "effect/Option" +import * as Schema from "effect/Schema" +import * as FastCheck from "fast-check" +import assert from "node:assert/strict" +import type { Tree } from "./schema.ts" +import { + makeBigDecimalSchema, + makeDateTimeUtcSchema, + makeDateTimeZonedSchema, + makeOptionalStructSchema, + regularExpression, + validateNumbers, + validateRegExpCoverage, + validateRegExpValues, + validateSchemaValues, + validateStrings, + validateTrees, + validateUint8Arrays +} from "./schema.ts" + +const seed = 42 +const recursiveSeed = 188 +const regExpShrinkSeed = 0 +const namedTimeZones = ["UTC", "Europe/London", "America/New_York", "Asia/Tokyo", "Australia/Sydney"] as const + +const makeRegExpArbitrary = () => FastCheck.stringMatching(regularExpression) + +interface FlatMapValue { + readonly length: number + readonly values: ReadonlyArray +} + +const flatMapTargets = globalThis.Array.from({ length: 8 }, (_, index) => { + const length = index + 1 + return FastCheck.array(FastCheck.integer({ min: -1_000, max: 1_000 }), { minLength: length, maxLength: length }) + .map((values): FlatMapValue => ({ length, values })) +}) +const flatMapArbitrary = FastCheck.integer({ min: 1, max: 8 }).chain((length) => flatMapTargets[length - 1]) + +const scoreArbitrary = FastCheck.oneof( + FastCheck.constant(Option.none()), + FastCheck.integer({ min: 0, max: 100 }).map(Option.some) +) + +const treeFields = ( + score: FastCheck.Arbitrary>, + children: FastCheck.Arbitrary> +) => ({ + label: FastCheck.string({ minLength: 2, maxLength: 12 }), + score, + children +}) + +const treeArbitrary = (maxDepth = 1) => { + const depthIdentifier = FastCheck.createDepthIdentifier() + const recursion = { maxDepth, depthIdentifier } + const recursive = FastCheck.letrec<{ readonly Tree: Tree }>((tie) => ({ + Tree: FastCheck.oneof( + recursion, + FastCheck.record(treeFields(FastCheck.constant(Option.none()), FastCheck.constant([]))), + FastCheck.constant(null).chain(() => + FastCheck.record( + treeFields( + FastCheck.oneof(recursion, FastCheck.constant(Option.none()), scoreArbitrary), + FastCheck.array(tie("Tree"), { maxLength: 3 }) + ) + ) + ) + ) + })).Tree + return FastCheck.record( + treeFields(scoreArbitrary, FastCheck.array(recursive, { maxLength: 3 })) + ) +} + +const timeZoneArbitrary = () => + FastCheck.oneof( + FastCheck.integer({ min: -12 * 60 * 60 * 1_000, max: 14 * 60 * 60 * 1_000 }).map(DateTime.zoneMakeOffset), + FastCheck.constantFrom(...namedTimeZones).map(DateTime.zoneMakeNamedUnsafe) + ) + +export const coldRecursiveFirstSample = () => ({ + run: () => FastCheck.sample(treeArbitrary(0), { numRuns: 1, seed }), + validate: validateTrees(1, 2, 2) +}) + +export const recursiveSample32 = () => { + const arbitrary = treeArbitrary() + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 32, seed: recursiveSeed }), + validate: validateTrees(32, 90, 110) + } +} + +export const optionalStructSample128 = () => { + const schema = makeOptionalStructSchema() + const item = FastCheck.integer({ min: 0, max: 1_000 }) + const arbitrary = FastCheck.record({ a: item, b: item, c: item, d: item, e: item, f: item, g: item, h: item }, { + requiredKeys: [] + }) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateSchemaValues(schema, 128) + } +} + +export const constrainedStringSample128 = () => { + const arbitrary = FastCheck.string({ minLength: 32, maxLength: 32 }) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateStrings(128) + } +} + +export const coldRegExpFirstSample = () => ({ + run: () => FastCheck.sample(makeRegExpArbitrary(), { numRuns: 1, seed }), + validate: validateRegExpValues(1) +}) + +export const regExpSample64 = () => { + const arbitrary = makeRegExpArbitrary() + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 64, seed }), + validate: validateRegExpCoverage + } +} + +export const regExpCheckFalsifyAndShrink = () => { + const property = FastCheck.property(makeRegExpArbitrary(), () => false) + return { + run: () => FastCheck.check(property, { numRuns: 1, seed: regExpShrinkSeed }), + validate: (result: FastCheck.RunDetails<[string]>) => { + assert.equal(result.failed, true) + if (!result.failed) return + validateRegExpValues(1)(result.counterexample) + assert.equal(result.numShrinks > 0, true) + } + } +} + +export const boundedNumberSample128 = () => { + const arbitrary = FastCheck.double({ min: 2, max: 4, noNaN: true }) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateNumbers(128) + } +} + +export const uint8ArraySample128 = () => { + const arbitrary = FastCheck.uint8Array({ maxLength: 10 }) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateUint8Arrays(128) + } +} + +export const bigDecimalSample128 = () => { + const schema = makeBigDecimalSchema() + const scale = 20 + const factor = BigInt(10) ** BigInt(17) + const arbitrary = FastCheck.bigInt({ + min: BigInt(1234) * factor + BigInt(1), + max: BigInt(1236) * factor - BigInt(1) + }).map((value) => BigDecimal.make(value, scale)) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateSchemaValues(schema, 128) + } +} + +export const dateTimeUtcSample128 = () => { + const schema = makeDateTimeUtcSchema() + const arbitrary = FastCheck.integer({ min: -1_000_000_000, max: 1_000_000_000 }).map(DateTime.makeUnsafe) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateSchemaValues(schema, 128) + } +} + +export const timeZoneNamedSample128 = () => { + const arbitrary = FastCheck.constantFrom(...namedTimeZones).map(DateTime.zoneMakeNamedUnsafe) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateSchemaValues(Schema.TimeZoneNamed, 128) + } +} + +export const timeZoneSample128 = () => { + const arbitrary = timeZoneArbitrary() + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateSchemaValues(Schema.TimeZone, 128) + } +} + +export const dateTimeZonedSample128 = () => { + const schema = makeDateTimeZonedSchema() + const arbitrary = FastCheck.tuple( + FastCheck.integer({ min: -1_000_000_000, max: 1_000_000_000 }), + timeZoneArbitrary() + ).map(([epochMilliseconds, timeZone]) => DateTime.makeZonedUnsafe(epochMilliseconds, { timeZone })) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: validateSchemaValues(schema, 128) + } +} + +export const rareFilterSample32 = () => { + const arbitrary = FastCheck.integer({ min: 0, max: 255 }).filter((value) => value % 16 === 0) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 32, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 32) + assert.equal(values.every((value) => value % 16 === 0), true) + } + } +} + +export const uniqueArraySample32 = () => { + const arbitrary = FastCheck.uniqueArray(FastCheck.integer({ min: 0, max: 1_023 }), { + minLength: 32, + maxLength: 32 + }) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 32, seed }), + validate: (values: ReadonlyArray>) => { + assert.equal(values.length, 32) + assert.equal(values.every((value) => value.length === 32 && new Set(value).size === 32), true) + } + } +} + +export const literalSample128 = () => { + const arbitrary = FastCheck.constant("value") + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value === "value"), true) + } + } +} + +export const mapSample128 = () => { + const arbitrary = FastCheck.integer({ min: 0, max: 1_000 }).map((value) => value + 1) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value >= 1 && value <= 1_001), true) + } + } +} + +export const passingFilterSample128 = () => { + const arbitrary = FastCheck.integer({ min: 0, max: 1_000 }).filter((value) => value >= 0) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value >= 0 && value <= 1_000), true) + } + } +} + +export const selectiveFilterSample32 = () => { + const arbitrary = FastCheck.integer({ min: 0, max: 255 }).filter((value) => value % 16 === 0) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 32, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 32) + assert.equal(values.every((value) => value % 16 === 0), true) + } + } +} + +export const filterMapSample128 = () => { + const arbitrary = FastCheck.integer({ min: 0, max: 255 }) + .filter((value) => value % 2 === 0) + .map((value) => value / 2) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => Number.isInteger(value) && value >= 0 && value <= 127), true) + } + } +} + +export const filterCheckFalsifyAndShrink = () => { + const arbitrary = FastCheck.integer({ min: 1, max: 8 }).filter( + (value) => value === 8 || value === 5 || value === 4 + ) + const property = FastCheck.property(arbitrary, () => false) + return { + run: () => FastCheck.check(property, { examples: [[8]], numRuns: 1, seed }), + validate: (result: FastCheck.RunDetails<[number]>) => { + assert.equal(result.failed, true) + assert.deepEqual(result.counterexample, [4]) + assert.equal(result.numShrinks, 2) + } + } +} + +export const allTupleSample128 = () => { + const arbitrary = FastCheck.tuple(FastCheck.constant("left"), FastCheck.constant(1)) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray<["left", 1]>) => { + assert.equal(values.length, 128) + assert.equal(values.every(([left, right]) => left === "left" && right === 1), true) + } + } +} + +export const allRecordSample128 = () => { + const arbitrary = FastCheck.record({ + name: FastCheck.constantFrom("Ada", "Grace"), + age: FastCheck.integer() + }) + return { + run: () => FastCheck.sample(arbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray<{ readonly name: string; readonly age: number }>) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value.name === "Ada" || value.name === "Grace"), true) + } + } +} + +export const flatMapSample128 = () => ({ + run: () => FastCheck.sample(flatMapArbitrary, { numRuns: 128, seed }), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value.values.length === value.length), true) + } +}) + +export const flatMapCheckFalsifyAndShrink = () => { + const property = FastCheck.property(flatMapArbitrary, () => false) + return { + run: () => FastCheck.check(property, { numRuns: 1, seed }), + validate: (result: FastCheck.RunDetails<[FlatMapValue]>) => { + assert.equal(result.failed, true) + assert.equal(result.counterexample?.[0].length, 1) + assert.equal(result.counterexample?.[0].values.length, 1) + } + } +} + +export const flatMapCheckReplay = () => { + const property = FastCheck.property(flatMapArbitrary, () => false) + const initial = FastCheck.check(property, { numRuns: 1, seed }) + assert.equal(initial.failed, true) + return { + run: () => FastCheck.check(property, { numRuns: 1, seed: initial.seed, path: initial.counterexamplePath }), + validate: (result: FastCheck.RunDetails<[FlatMapValue]>) => { + assert.equal(result.failed, true) + assert.equal(result.counterexample?.[0].length, 1) + assert.equal(result.counterexample?.[0].values.length, 1) + } + } +} + +export const checkPass100 = () => { + const arbitrary = FastCheck.integer() + const property = FastCheck.property(arbitrary, () => true) + return { + run: () => FastCheck.check(property, { numRuns: 100, seed }), + validate: (result: FastCheck.RunDetails<[number]>) => { + assert.equal(result.failed, false) + assert.equal(result.numRuns, 100) + assert.equal(result.numSkips, 0) + } + } +} + +export const testSchemaVerifyGeneration100 = () => ({ + run: () => { + const schema = Schema.Int + const arbitrary = FastCheck.integer() + FastCheck.assert(FastCheck.property(arbitrary, Schema.is(schema)), { numRuns: 100, seed }) + }, + validate: (result: void) => assert.equal(result, undefined) +}) + +export const checkFalsifyAndShrink = () => { + const arbitrary = FastCheck.integer({ min: 1, max: 1_000 }) + const property = FastCheck.property(arbitrary, (value) => value < 0) + return { + run: () => FastCheck.check(property, { examples: [[1_000]], numRuns: 1, seed }), + validate: (result: FastCheck.RunDetails<[number]>) => { + assert.equal(result.failed, true) + assert.deepEqual(result.counterexample, [1]) + assert.equal(result.numShrinks, 1) + } + } +} + +export const checkReplay = () => { + const arbitrary = FastCheck.integer({ min: 1, max: 1_000 }) + const property = FastCheck.property(arbitrary, (value) => value < 0) + const initial = FastCheck.check(property, { examples: [[1_000]], numRuns: 1, seed }) + assert.equal(initial.failed, true) + return { + run: () => FastCheck.check(property, { numRuns: 1, seed: initial.seed, path: initial.counterexamplePath }), + validate: (result: FastCheck.RunDetails<[number]>) => { + assert.equal(result.failed, true) + assert.deepEqual(result.counterexample, [1]) + assert.equal(result.numShrinks, 0) + } + } +} diff --git a/packages/effect/runtimeperf/suites/arbitrary/fixtures/native.ts b/packages/effect/runtimeperf/suites/arbitrary/fixtures/native.ts new file mode 100644 index 000000000..79d46504b --- /dev/null +++ b/packages/effect/runtimeperf/suites/arbitrary/fixtures/native.ts @@ -0,0 +1,438 @@ +import * as Effect from "effect/Effect" +import * as Result from "effect/Result" +import * as Schema from "effect/Schema" +import * as TestSchema from "effect/testing/TestSchema" +import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary" +import assert from "node:assert/strict" +import { + makeBigDecimalSchema, + makeConstrainedStringSchema, + makeDateTimeUtcSchema, + makeDateTimeZonedSchema, + makeOptionalStructSchema, + makeRareFilterSchema, + makeRegExpSchema, + makeTreeSchema, + makeUniqueArraySchema, + regularExpression, + validateNumbers, + validateRegExpCoverage, + validateRegExpValues, + validateSchemaValues, + validateStrings, + validateTrees, + validateUint8Arrays +} from "./schema.ts" + +const seed = 42 +const recursiveSeed = 188 +const regExpShrinkSeed = 0 +const size = 10 + +interface FlatMapValue { + readonly length: number + readonly values: ReadonlyArray +} + +const flatMapSource = Arbitrary.schema(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 8 }))) +const flatMapItem = Schema.Int.check(Schema.isBetween({ minimum: -1_000, maximum: 1_000 })) +const flatMapTargets = globalThis.Array.from({ length: 8 }, (_, index) => { + const length = index + 1 + return Arbitrary.schema( + Schema.Array(flatMapItem).check(Schema.isMinLength(length), Schema.isMaxLength(length)) + ).pipe(Arbitrary.map((values): FlatMapValue => ({ length, values }))) +}) +const makeFlatMapArbitrary = () => flatMapSource.pipe(Arbitrary.flatMap((length) => flatMapTargets[length - 1])) + +export const coldRecursiveFirstSample = () => ({ + run: () => + Effect.runSync( + Arbitrary.sampleEffect(Arbitrary.schema(makeTreeSchema()), { count: 1, seed, size: 1 }) + ), + validate: validateTrees(1, 2, 2) +}) + +export const recursiveSample32 = () => { + const arbitrary = Arbitrary.schema(makeTreeSchema()) + const program = Arbitrary.sampleEffect(arbitrary, { count: 32, seed: recursiveSeed, size: 3 }) + return { + run: () => Effect.runSync(program), + validate: validateTrees(32, 90, 110) + } +} + +export const optionalStructSample128 = () => { + const schema = makeOptionalStructSchema() + const arbitrary = Arbitrary.schema(schema) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size: 8 }) + return { + run: () => Effect.runSync(program), + validate: validateSchemaValues(schema, 128) + } +} + +export const constrainedStringSample128 = () => { + const arbitrary = Arbitrary.schema(makeConstrainedStringSchema()) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateStrings(128) + } +} + +export const coldRegExpFirstSample = () => ({ + run: () => + Effect.runSync( + Arbitrary.sampleEffect(Arbitrary.schema(makeRegExpSchema()), { + count: 1, + maxDiscards: 0, + seed, + size: 48 + }) + ), + validate: validateRegExpValues(1) +}) + +export const regExpSample64 = () => { + const arbitrary = Arbitrary.schema(makeRegExpSchema()) + const program = Arbitrary.sampleEffect(arbitrary, { count: 64, maxDiscards: 0, seed, size: 48 }) + return { + run: () => Effect.runSync(program), + validate: validateRegExpCoverage + } +} + +export const regExpCheckFalsifyAndShrink = () => { + const arbitrary = Arbitrary.schema(makeRegExpSchema()) + const program = Arbitrary.checkEffect(arbitrary, () => false, { + runs: 1, + maxDiscards: 0, + seed: regExpShrinkSeed, + size: 48 + }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.equal(result._tag, "Falsified") + if (result._tag !== "Falsified") return + validateRegExpValues(2)([result.initialInput, result.shrunkInput]) + assert.equal(result.shrinks > 0, true) + } + } +} + +export const boundedNumberSample128 = () => { + const arbitrary = Arbitrary.schema(Schema.Number.check(Schema.isBetween({ minimum: 2, maximum: 4 }))) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateNumbers(128) + } +} + +export const uint8ArraySample128 = () => { + const arbitrary = Arbitrary.schema(Schema.Uint8Array) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateUint8Arrays(128) + } +} + +export const bigDecimalSample128 = () => { + const schema = makeBigDecimalSchema() + const arbitrary = Arbitrary.schema(schema) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateSchemaValues(schema, 128) + } +} + +export const dateTimeUtcSample128 = () => { + const schema = makeDateTimeUtcSchema() + const arbitrary = Arbitrary.schema(schema) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateSchemaValues(schema, 128) + } +} + +export const timeZoneNamedSample128 = () => { + const arbitrary = Arbitrary.schema(Schema.TimeZoneNamed) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateSchemaValues(Schema.TimeZoneNamed, 128) + } +} + +export const timeZoneSample128 = () => { + const arbitrary = Arbitrary.schema(Schema.TimeZone) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateSchemaValues(Schema.TimeZone, 128) + } +} + +export const dateTimeZonedSample128 = () => { + const schema = makeDateTimeZonedSchema() + const arbitrary = Arbitrary.schema(schema) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: validateSchemaValues(schema, 128) + } +} + +export const rareFilterSample32 = () => { + const arbitrary = Arbitrary.schema(makeRareFilterSchema()) + const program = Arbitrary.sampleEffect(arbitrary, { count: 32, maxDiscards: 2_048, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 32) + assert.equal(values.every((value) => value % 16 === 0), true) + } + } +} + +export const uniqueArraySample32 = () => { + const arbitrary = Arbitrary.schema(makeUniqueArraySchema()) + const program = Arbitrary.sampleEffect(arbitrary, { count: 32, maxDiscards: 2_048, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray>) => { + assert.equal(values.length, 32) + assert.equal(values.every((value) => value.length === 32 && new Set(value).size === 32), true) + } + } +} + +export const literalSample128 = () => { + const arbitrary = Arbitrary.schema(Schema.Literal("value")) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value === "value"), true) + } + } +} + +export const mapSample128 = () => { + const arbitrary = Arbitrary.map( + Arbitrary.schema(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_000 }))), + (value) => value + 1 + ) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value >= 1 && value <= 1_001), true) + } + } +} + +export const passingFilterSample128 = () => { + const arbitrary = Arbitrary.filter( + Arbitrary.schema(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_000 }))), + (value) => value >= 0 + ) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value >= 0 && value <= 1_000), true) + } + } +} + +export const selectiveFilterSample32 = () => { + const arbitrary = Arbitrary.filter( + Arbitrary.schema(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 255 }))), + (value) => value % 16 === 0 + ) + const program = Arbitrary.sampleEffect(arbitrary, { count: 32, maxDiscards: 2_048, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 32) + assert.equal(values.every((value) => value % 16 === 0), true) + } + } +} + +export const filterMapSample128 = () => { + const arbitrary = Arbitrary.filterMap( + Arbitrary.schema(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 255 }))), + (value) => value % 2 === 0 ? Result.succeed(value / 2) : Result.fail(value) + ) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 2_048, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => Number.isInteger(value) && value >= 0 && value <= 127), true) + } + } +} + +export const filterCheckFalsifyAndShrink = () => { + const arbitrary = Arbitrary.filter( + Arbitrary.schema(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 8 }))), + (value) => value === 8 || value === 5 || value === 4 + ) + const program = Arbitrary.checkEffect(arbitrary, () => false, { + runs: 1, + seed: 47, + size, + maxShrinks: 100 + }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.equal(result._tag, "Falsified") + if (result._tag !== "Falsified") return + assert.equal(result.initialInput, 8) + assert.equal(result.shrunkInput, 4) + assert.equal(result.shrinks, 2) + } + } +} + +export const allTupleSample128 = () => { + const arbitrary = Arbitrary.all([ + Arbitrary.schema(Schema.Literal("left")), + Arbitrary.schema(Schema.Literal(1)) + ]) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray<["left", 1]>) => { + assert.equal(values.length, 128) + assert.equal(values.every(([left, right]) => left === "left" && right === 1), true) + } + } +} + +export const allRecordSample128 = () => { + const arbitrary = Arbitrary.all({ + name: Arbitrary.schema(Schema.Literals(["Ada", "Grace"])), + age: Arbitrary.schema(Schema.Int) + }) + const program = Arbitrary.sampleEffect(arbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray<{ readonly name: string; readonly age: number }>) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value.name === "Ada" || value.name === "Grace"), true) + } + } +} + +export const flatMapSample128 = () => { + const flatMapArbitrary = makeFlatMapArbitrary() + const program = Arbitrary.sampleEffect(flatMapArbitrary, { count: 128, maxDiscards: 0, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (values: ReadonlyArray) => { + assert.equal(values.length, 128) + assert.equal(values.every((value) => value.values.length === value.length), true) + } + } +} + +export const flatMapCheckFalsifyAndShrink = () => { + const flatMapArbitrary = makeFlatMapArbitrary() + const program = Arbitrary.checkEffect(flatMapArbitrary, () => false, { runs: 1, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.equal(result._tag, "Falsified") + if (result._tag !== "Falsified") return + assert.equal(result.shrunkInput.length, 1) + assert.equal(result.shrunkInput.values.length, 1) + } + } +} + +export const flatMapCheckReplay = () => { + const flatMapArbitrary = makeFlatMapArbitrary() + const property = () => false + const initial = Effect.runSync(Arbitrary.checkEffect(flatMapArbitrary, property, { runs: 1, seed, size })) + assert.equal(initial._tag, "Falsified") + if (initial._tag !== "Falsified") throw new Error("Expected the flatMap replay setup to falsify") + const program = Arbitrary.checkEffect(flatMapArbitrary, property, { replay: initial.replay }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.equal(result._tag, "Falsified") + if (result._tag !== "Falsified") return + assert.equal(result.shrunkInput.length, 1) + assert.equal(result.shrunkInput.values.length, 1) + } + } +} + +export const checkPass100 = () => { + const arbitrary = Arbitrary.schema(Schema.Int) + const program = Arbitrary.checkEffect(arbitrary, () => true, { runs: 100, seed, size }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.deepEqual(result, { _tag: "Passed", runs: 100, discards: 0 }) + } + } +} + +export const testSchemaVerifyGeneration100 = () => { + const asserts = new TestSchema.Asserts(Schema.Int) + return { + run: () => asserts.arbitrary().verifyGeneration({ runs: 100, seed }), + validate: (result: void) => assert.equal(result, undefined) + } +} + +export const checkFalsifyAndShrink = () => { + const arbitrary = Arbitrary.schema( + Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1_000 })) + ) + const program = Arbitrary.checkEffect(arbitrary, (value) => value < 0, { runs: 1, seed: 47, size }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.equal(result._tag, "Falsified") + if (result._tag !== "Falsified") return + assert.equal(result.initialInput, 1_000) + assert.equal(result.shrunkInput, 1) + assert.equal(result.shrinks, 1) + } + } +} + +export const checkReplay = () => { + const arbitrary = Arbitrary.schema( + Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1_000 })) + ) + const property = (value: number) => value < 0 + const initial = Effect.runSync(Arbitrary.checkEffect(arbitrary, property, { runs: 1, seed: 47, size })) + assert.equal(initial._tag, "Falsified") + if (initial._tag !== "Falsified") throw new Error("Expected the replay setup to falsify") + const program = Arbitrary.checkEffect(arbitrary, property, { replay: initial.replay }) + return { + run: () => Effect.runSync(program), + validate: (result: Arbitrary.CheckResult) => { + assert.equal(result._tag, "Falsified") + if (result._tag !== "Falsified") return + assert.equal(result.initialInput, 1_000) + assert.equal(result.shrunkInput, 1) + assert.equal(result.shrinks, 1) + } + } +} diff --git a/packages/effect/runtimeperf/suites/arbitrary/fixtures/schema.ts b/packages/effect/runtimeperf/suites/arbitrary/fixtures/schema.ts new file mode 100644 index 000000000..b71ad8a56 --- /dev/null +++ b/packages/effect/runtimeperf/suites/arbitrary/fixtures/schema.ts @@ -0,0 +1,134 @@ +import * as BigDecimal from "effect/BigDecimal" +import * as DateTime from "effect/DateTime" +import type * as Option from "effect/Option" +import * as Schema from "effect/Schema" +import assert from "node:assert/strict" + +export interface Tree { + readonly label: string + readonly score: Option.Option + readonly children: ReadonlyArray +} + +export const makeTreeSchema = (): Schema.Codec => { + const Tree: Schema.Codec = Schema.Struct({ + label: Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(12)), + score: Schema.Option(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))), + children: Schema.Array(Schema.suspend(() => Tree)).check(Schema.isMaxLength(3)) + }) + return Tree +} + +export const makeConstrainedStringSchema = () => Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(32)) + +const optionalInt = Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_000 }))) + +export const makeOptionalStructSchema = () => + Schema.Struct({ + a: optionalInt, + b: optionalInt, + c: optionalInt, + d: optionalInt, + e: optionalInt, + f: optionalInt, + g: optionalInt, + h: optionalInt + }) + +export const regularExpression = /^(?:a|[B-D]{2}|[0-9]{3}){16}$/ + +export const makeRegExpSchema = () => Schema.String.check(Schema.isPattern(regularExpression)) + +export const makeRareFilterSchema = () => + Schema.Int.check( + Schema.isBetween({ minimum: 0, maximum: 255 }), + Schema.makeFilter((value: number) => value % 16 === 0) + ) + +export const makeUniqueArraySchema = () => + Schema.UniqueArray(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_023 }))).check( + Schema.isMinLength(32), + Schema.isMaxLength(32) + ) + +export const makeBigDecimalSchema = () => + Schema.BigDecimal.check(Schema.isBetweenBigDecimal({ + minimum: BigDecimal.make(BigInt(1234), 3), + maximum: BigDecimal.make(BigInt(1236), 3), + exclusiveMinimum: true, + exclusiveMaximum: true + })) + +const isBetweenDateTime = Schema.makeIsBetween({ order: DateTime.Order }) + +export const makeDateTimeUtcSchema = () => + Schema.DateTimeUtc.check(isBetweenDateTime({ + minimum: DateTime.makeUnsafe(-1_000_000_000), + maximum: DateTime.makeUnsafe(1_000_000_000) + })) + +export const makeDateTimeZonedSchema = () => + Schema.DateTimeZoned.check(isBetweenDateTime({ + minimum: DateTime.makeZonedUnsafe(-1_000_000_000, { timeZone: "UTC" }), + maximum: DateTime.makeZonedUnsafe(1_000_000_000, { timeZone: "UTC" }) + })) + +const validateTree = Schema.is(makeTreeSchema()) + +const countTreeNodes = (tree: Tree): number => + 1 + tree.children.reduce((total, child) => total + countTreeNodes(child), 0) + +export const validateTrees = (count: number, minimumNodes: number, maximumNodes: number) => (values: unknown) => { + assert.ok(Array.isArray(values)) + assert.equal(values.length, count) + assert.equal(values.every(validateTree), true) + const nodes = values.reduce((total, tree) => total + countTreeNodes(tree), 0) + assert.ok(nodes >= minimumNodes && nodes <= maximumNodes) +} + +export const validateStrings = (count: number) => (values: unknown) => { + assert.ok(Array.isArray(values)) + assert.equal(values.length, count) + assert.equal(values.every((value) => typeof value === "string" && value.length === 32), true) +} + +export const validateRegExpValues = (count: number) => (values: unknown) => { + assert.ok(Array.isArray(values)) + assert.equal(values.length, count) + assert.equal( + values.every((value) => typeof value === "string" && regularExpression.test(value)), + true + ) +} + +export const validateRegExpCoverage = (values: unknown) => { + validateRegExpValues(64)(values) + assert.ok(Array.isArray(values)) + assert.ok(new Set(values.map((value) => value.length)).size > 1) + assert.equal(values.some((value) => value.includes("a")), true) + assert.equal(values.some((value) => /[B-D]{2}/.test(value)), true) + assert.equal(values.some((value) => /[0-9]{3}/.test(value)), true) +} + +export const validateNumbers = (count: number) => (values: unknown) => { + assert.ok(Array.isArray(values)) + assert.equal(values.length, count) + assert.equal(values.every((value) => typeof value === "number" && value >= 2 && value <= 4), true) +} + +export const validateUint8Arrays = (count: number) => (values: unknown) => { + assert.ok(Array.isArray(values)) + assert.equal(values.length, count) + assert.equal(values.every((value) => value instanceof Uint8Array && value.length <= 10), true) + const bytes = values.reduce((total, value) => total + value.length, 0) + assert.ok(bytes >= 500 && bytes <= 700) +} + +export const validateSchemaValues = (schema: Schema.Top, count: number) => { + const is = Schema.is(schema) + return (values: unknown) => { + assert.ok(Array.isArray(values)) + assert.equal(values.length, count) + assert.equal(values.every(is), true) + } +} diff --git a/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/data.ts b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/data.ts new file mode 100644 index 000000000..af9d40583 --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/data.ts @@ -0,0 +1,159 @@ +// Schema Benchmarks requires real Date instances in the shared input. +const date = new Date(0) + +export const validData = { + id: 252, + created: date, + title: "Apple", + brand: "Sunny Backyard", + description: "Red apple from Lake Constance", + price: 89, + discount: null, + quantity: 5, + tags: ["fruit", "red", "round", "sweet", "juicy", "healthy"], + images: [ + { + id: 248, + created: date, + title: "Close up of an apple on a tree", + type: "jpg", + size: 92357232, + url: "https://www.example.com/images/248" + }, + { + id: 295, + created: date, + title: "Our apples in the final packaging", + type: "jpg", + size: 83247232, + url: "https://www.example.com/images/295" + }, + { + id: 723, + created: date, + title: "Our fruit fields at Lake Constance", + type: "jpg", + size: 72356345, + url: "https://www.example.com/images/723" + } + ], + ratings: [ + { + id: 315, + stars: 4.5, + title: "Tastes super delicious", + text: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.", + images: [ + { + id: 835, + created: date, + title: "The result of our apple pie", + type: "jpg", + size: 8247493, + url: "https://www.example.com/images/835" + } + ] + }, + { + id: 642, + stars: 5, + title: "Very tasty! I will buy them again!", + text: + "In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt.", + images: [ + { + id: 352, + created: date, + title: "The fruit salad in a bowl", + type: "jpg", + size: 3582543, + url: "https://www.example.com/images/352" + }, + { + id: 465, + created: date, + title: "The fruit salad on a plate", + type: "jpg", + size: 9824742, + url: "https://www.example.com/images/465" + } + ] + } + ] +} + +export const invalidData = { + id: 252, + created: date, + title: "", + brand: "Sunny Backyard", + description: "Red apple from Lake Constance", + price: 0, + discount: null, + quantity: 1000, + tags: ["fruit", null, "round", undefined, "juicy", "healthy"], + images: [ + { + created: null, + title: "Close up of an apple on a tree", + type: "mp4", + size: 92357232, + url: "https://www.example.com/images/248" + }, + { + id: 295, + created: date, + title: "Our apples in the final packaging", + type: "jpg", + size: 83247232 + }, + { + id: 723, + created: date, + title: "Our fruit fields at Lake Constance", + type: "jpg", + size: 72356345, + url: "https://www.example.com/images/723" + } + ], + ratings: [ + { + id: 315, + stars: 4.5, + title: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.", + text: "Tastes super delicious", + images: [ + { + id: 835, + created: date, + title: "The result of our apple pie", + type: "jpg", + size: 8247493, + url: "https://www.example.com/images/835" + } + ] + }, + { + id: 642, + stars: 5, + title: "Very tasty! I will buy them again!", + text: + "In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt.", + images: [ + { + id: "352", + created: undefined, + title: "The fruit salad in a bowl", + type: "jpg", + size: 3582543, + url: "INVALID_URL" + }, + { + id: 465, + created: date, + url: "https://www.example.com/images/465" + } + ] + } + ] +} diff --git a/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/effect-beta.ts b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/effect-beta.ts new file mode 100644 index 000000000..b21c0dd2d --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/effect-beta.ts @@ -0,0 +1,133 @@ +import * as Option from "effect/Option" +import * as Schema from "effect/Schema" +import assert from "node:assert/strict" +import { invalidData, validData } from "./data.ts" + +// Extracted from open-circle/schema-benchmarks at +// 11fab2a741cef95a1374910276023c51218c0683. + +const makeSchema = () => { + const Image = Schema.Struct({ + id: Schema.Number, + created: Schema.instanceOf(Date), + title: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(100)), + type: Schema.Literals(["jpg", "png"]), + size: Schema.Number, + url: Schema.String.check(Schema.makeFilter((value) => URL.canParse(value))) + }) + const Rating = Schema.Struct({ + id: Schema.Number, + stars: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(5)), + title: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(100)), + text: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(1000)), + images: Schema.mutable(Schema.Array(Image)) + }) + return Schema.Struct({ + id: Schema.Number, + created: Schema.instanceOf(Date), + title: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(100)), + brand: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(30)), + description: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(500)), + price: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(10000)), + discount: Schema.NullOr( + Schema.Number.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100)) + ), + quantity: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(10)), + tags: Schema.mutable( + Schema.Array(Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(30))) + ), + images: Schema.mutable(Schema.Array(Image)), + ratings: Schema.mutable(Schema.Array(Rating)) + }) +} + +export const initializationSchema = () => ({ + run: makeSchema, + validate: (schema) => assert.equal(schema.ast._tag, "Objects") +}) + +export const initializationDecoder = () => ({ + run: () => Schema.decodeUnknownOption(makeSchema()), + validate: (decode) => assert.equal(typeof decode, "function") +}) + +const validationCase = (input, expected) => () => { + const run = Schema.is(makeSchema()) + return { + run: () => run(input), + validate: (result) => assert.equal(result, expected) + } +} + +export const validationValid = validationCase(validData, true) +export const validationInvalid = validationCase(invalidData, false) + +const parsingCase = (input, errors, success) => () => { + const run = Schema.decodeUnknownOption(makeSchema()) + return { + run: () => run(input, { errors }), + validate: (result) => assert.equal(Option.isSome(result), success) + } +} + +export const parsingAllValid = parsingCase(validData, "all", true) +export const parsingAllInvalid = parsingCase(invalidData, "all", false) +export const parsingFirstValid = parsingCase(validData, "first", true) +export const parsingFirstInvalid = parsingCase(invalidData, "first", false) + +const standardCase = (input, errors, success) => () => { + const schema = Schema.toStandardSchemaV1(makeSchema(), { parseOptions: { errors } }) + return { + run: () => schema["~standard"].validate(input), + validate: (result) => { + assert.equal(typeof result?.then, "undefined") + if (success) { + assert.equal(result.issues, undefined) + assert.deepEqual(result.value, validData) + } else { + assert.ok(result.issues) + assert.ok(result.issues.length > 0) + } + } + } +} + +export const standardAllValid = standardCase(validData, "all", true) +export const standardAllInvalid = standardCase(invalidData, "all", false) +export const standardFirstValid = standardCase(validData, "first", true) +export const standardFirstInvalid = standardCase(invalidData, "first", false) + +const bigint = BigInt("1234567890123456789") +const bigintString = bigint.toString() + +export const codecTypedEncode = () => { + const run = Schema.encodeSync(Schema.BigIntFromString) + return { + run: () => run(bigint), + validate: (result) => assert.equal(result, bigintString) + } +} + +export const codecTypedDecode = () => { + const run = Schema.decodeSync(Schema.BigIntFromString) + return { + run: () => run(bigintString), + validate: (result) => assert.equal(result, bigint) + } +} + +export const codecUnknownEncode = () => { + const run = Schema.encodeUnknownSync(Schema.BigIntFromString) + return { + run: () => run(bigint), + validate: (result) => assert.equal(result, bigintString) + } +} + +export const codecUnknownDecode = () => { + const run = Schema.decodeUnknownSync(Schema.BigIntFromString) + return { + run: () => run(bigintString), + validate: (result) => assert.equal(result, bigint) + } +} diff --git a/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/valibot.ts b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/valibot.ts new file mode 100644 index 000000000..fdff6a1b5 --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/valibot.ts @@ -0,0 +1,80 @@ +import assert from "node:assert/strict" +import * as v from "valibot" +import { invalidData, validData } from "./data.ts" + +// Extracted from open-circle/schema-benchmarks at +// 11fab2a741cef95a1374910276023c51218c0683. + +const makeSchema = () => { + const image = v.object({ + id: v.number(), + created: v.date(), + title: v.pipe(v.string(), v.minLength(1), v.maxLength(100)), + type: v.picklist(["jpg", "png"]), + size: v.number(), + url: v.pipe(v.string(), v.url()) + }) + const rating = v.object({ + id: v.number(), + stars: v.pipe(v.number(), v.minValue(1), v.maxValue(5)), + title: v.pipe(v.string(), v.minLength(1), v.maxLength(100)), + text: v.pipe(v.string(), v.minLength(1), v.maxLength(1000)), + images: v.array(image) + }) + return v.object({ + id: v.number(), + created: v.date(), + title: v.pipe(v.string(), v.minLength(1), v.maxLength(100)), + brand: v.pipe(v.string(), v.minLength(1), v.maxLength(30)), + description: v.pipe(v.string(), v.minLength(1), v.maxLength(500)), + price: v.pipe(v.number(), v.minValue(1), v.maxValue(10000)), + discount: v.nullable(v.pipe(v.number(), v.minValue(1), v.maxValue(100))), + quantity: v.pipe(v.number(), v.minValue(1), v.maxValue(10)), + tags: v.array(v.pipe(v.string(), v.minLength(1), v.maxLength(30))), + images: v.array(image), + ratings: v.array(rating) + }) +} + +export const initializationSchema = () => ({ + run: makeSchema, + validate: (schema) => assert.equal(schema.type, "object") +}) + +const validationCase = (input, expected) => () => { + const schema = makeSchema() + return { + run: () => v.is(schema, input), + validate: (result) => assert.equal(result, expected) + } +} + +export const validationValid = validationCase(validData, true) +export const validationInvalid = validationCase(invalidData, false) + +const parsingCase = (input, options, success) => () => { + const schema = makeSchema() + return { + run: () => v.safeParse(schema, input, options), + validate: (result) => assert.equal(result.success, success) + } +} + +export const parsingAllValid = parsingCase(validData, undefined, true) +export const parsingAllInvalid = parsingCase(invalidData, undefined, false) +export const parsingFirstValid = parsingCase(validData, { abortEarly: true }, true) +export const parsingFirstInvalid = parsingCase(invalidData, { abortEarly: true }, false) + +const standardCase = (input, success) => () => { + const schema = makeSchema() + return { + run: () => schema["~standard"].validate(input), + validate: (result) => { + assert.equal(typeof result?.then, "undefined") + assert.equal(result.issues === undefined, success) + } + } +} + +export const standardAllValid = standardCase(validData, true) +export const standardAllInvalid = standardCase(invalidData, false) diff --git a/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/zod.ts b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/zod.ts new file mode 100644 index 000000000..81e56f482 --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema-benchmarks/fixtures/zod.ts @@ -0,0 +1,85 @@ +import assert from "node:assert/strict" +import { z } from "zod/v4" +import { invalidData, validData } from "./data.ts" + +// Extracted from open-circle/schema-benchmarks at +// 11fab2a741cef95a1374910276023c51218c0683. + +const makeSchema = () => { + const image = z.object({ + id: z.number(), + created: z.date(), + title: z.string().min(1).max(100), + type: z.enum(["jpg", "png"]), + size: z.number(), + url: z.url() + }) + const rating = z.object({ + id: z.number(), + stars: z.number().min(0).max(5), + title: z.string().min(1).max(100), + text: z.string().min(1).max(1000), + images: z.array(image) + }) + return z.object({ + id: z.number(), + created: z.date(), + title: z.string().min(1).max(100), + brand: z.string().min(1).max(30), + description: z.string().min(1).max(500), + price: z.number().min(1).max(10000), + discount: z.number().min(1).max(100).nullable(), + quantity: z.number().min(0).max(10), + tags: z.array(z.string().min(1).max(30)), + images: z.array(image), + ratings: z.array(rating) + }) +} + +export const initializationSchema = () => ({ + run: makeSchema, + validate: (schema) => assert.equal(schema.type, "object") +}) + +const parsingCase = (input, success) => () => { + const schema = makeSchema() + const options = { jitless: true } + return { + run: () => schema.safeParse(input, options), + validate: (result) => assert.equal(result.success, success) + } +} + +export const parsingAllValid = parsingCase(validData, true) +export const parsingAllInvalid = parsingCase(invalidData, false) + +const standardCase = (input, success) => () => { + const schema = makeSchema() + return { + run: () => schema["~standard"].validate(input), + validate: (result) => { + assert.equal(typeof result?.then, "undefined") + assert.equal(result.issues === undefined, success) + } + } +} + +export const standardAllValid = standardCase(validData, true) +export const standardAllInvalid = standardCase(invalidData, false) + +const codec = z.codec(z.string(), z.bigint(), { + decode: (value) => BigInt(value), + encode: (value) => value.toString() +}) +const bigint = BigInt("1234567890123456789") +const bigintString = bigint.toString() + +export const codecTypedEncode = () => ({ + run: () => codec.encode(bigint), + validate: (result) => assert.equal(result, bigintString) +}) + +export const codecTypedDecode = () => ({ + run: () => codec.decode(bigintString), + validate: (result) => assert.equal(result, bigint) +}) diff --git a/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts b/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts new file mode 100644 index 000000000..2a84cab07 --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema/fixtures/adapters.ts @@ -0,0 +1,83 @@ +import * as Option from "effect/Option" +import * as Result from "effect/Result" +import * as Schema from "effect/Schema" +import * as SchemaParser from "effect/SchemaParser" +import assert from "node:assert/strict" + +const schema = Schema.Struct({ + a: Schema.String, + b: Schema.Number +}) +const input = { a: "a", b: 1 } +const invalidInput = { a: "a", b: "invalid" } + +export const parserExitInvalid = () => { + const run = SchemaParser.decodeUnknownExit(schema) + return { + run: () => run(invalidInput), + validate: (result) => assert.equal(result._tag, "Failure") + } +} + +export const exitValid = () => { + const run = Schema.decodeUnknownExit(schema) + return { + run: () => run(input), + validate: (result) => assert.equal(result._tag, "Success") + } +} + +export const exitInvalid = () => { + const run = Schema.decodeUnknownExit(schema) + return { + run: () => run(invalidInput), + validate: (result) => assert.equal(result._tag, "Failure") + } +} + +export const optionValid = () => { + const run = Schema.decodeUnknownOption(schema) + return { + run: () => run(input), + validate: (result) => assert.equal(Option.isSome(result), true) + } +} + +export const optionInvalid = () => { + const run = Schema.decodeUnknownOption(schema) + return { + run: () => run(invalidInput), + validate: (result) => assert.equal(Option.isNone(result), true) + } +} + +export const resultValid = () => { + const run = Schema.decodeUnknownResult(schema) + return { + run: () => run(input), + validate: (result) => assert.equal(Result.isSuccess(result), true) + } +} + +export const resultInvalid = () => { + const run = Schema.decodeUnknownResult(schema) + return { + run: () => run(invalidInput), + validate: (result) => assert.equal(Result.isFailure(result), true) + } +} + +export const syncInvalid = () => { + const run = Schema.decodeUnknownSync(schema) + return { + run: () => { + try { + run(invalidInput) + } catch (error) { + return error + } + return undefined + }, + validate: (result) => assert.equal(result instanceof Error, true) + } +} diff --git a/packages/effect/runtimeperf/suites/schema/fixtures/behavior.ts b/packages/effect/runtimeperf/suites/schema/fixtures/behavior.ts new file mode 100644 index 000000000..8ed02deaf --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema/fixtures/behavior.ts @@ -0,0 +1,215 @@ +import * as Effect from "effect/Effect" +import * as Schema from "effect/Schema" +import * as SchemaGetter from "effect/SchemaGetter" +import * as SchemaParser from "effect/SchemaParser" +import * as SchemaTransformation from "effect/SchemaTransformation" +import assert from "node:assert/strict" + +const decodeCase = (schema, input, success, options) => () => { + const run = Schema.decodeUnknownExit(schema, options) + return { + run: () => run(input), + validate: (result) => assert.equal(result._tag, success ? "Success" : "Failure") + } +} + +const decodeParserCase = (schema, input, success, options) => () => { + const run = SchemaParser.decodeUnknownExit(schema, options) + return { + run: () => run(input), + validate: (result) => assert.equal(result._tag, success ? "Success" : "Failure") + } +} + +const encodeParserCase = (schema, input, success, options) => () => { + const run = SchemaParser.encodeUnknownExit(schema, options) + return { + run: () => run(input), + validate: (result) => assert.equal(result._tag, success ? "Success" : "Failure") + } +} + +const checkedString = Schema.String + .check(Schema.isMinLength(2)) + .check(Schema.isPattern(/^[a-z]+$/)) + +export const checksInvalidFirst = decodeParserCase(checkedString, "", false) +export const checksInvalidLast = decodeParserCase(checkedString, "runtime-perf", false) + +const encodingCheckedString = Schema.String.pipe( + Schema.flip, + Schema.check(Schema.isMinLength(2)), + Schema.flip +) + +export const encodingCheckValid = decodeParserCase(encodingCheckedString, "runtimeperf", true) + +const templateLiteralLinear = Schema.TemplateLiteralParser([ + "prefix-", + Schema.String, + "-middle-", + Schema.Number, + "-suffix" +]) + +export const templateLiteralLinearValid = decodeParserCase( + templateLiteralLinear, + "prefix-value-middle-123-suffix", + true +) +export const templateLiteralLinearInvalid = decodeParserCase( + templateLiteralLinear, + "prefix-value-middle-invalid", + false +) + +const templateLiteralBacktracking = Schema.TemplateLiteralParser([ + Schema.String, + ":", + Schema.NonEmptyString, + "x" +]) + +export const templateLiteralBacktrackingValid = decodeParserCase( + templateLiteralBacktracking, + "a:b:x", + true +) +export const templateLiteralBacktrackingInvalid = decodeParserCase( + templateLiteralBacktracking, + "a:x", + false +) + +export const templateLiteralTransformedValid = decodeParserCase( + Schema.TemplateLiteralParser([Schema.FiniteFromString, "a", Schema.NonEmptyString]), + "100ab23a", + true +) + +const templateLiteralRecordInput = Object.fromEntries( + Array.from({ length: 32 }, (_, index) => [`field-${index}`, `value${index}`]) +) + +export const templateLiteralRecord32Valid = decodeParserCase( + Schema.Record(Schema.TemplateLiteral(["field-", Schema.Number]), Schema.String), + templateLiteralRecordInput, + true +) + +export const transformationDecodeValid = decodeParserCase(Schema.FiniteFromString, "123", true) +export const transformationDecodeInvalid = decodeParserCase(Schema.FiniteFromString, "invalid", false) +export const transformationEncodeValid = encodeParserCase(Schema.FiniteFromString, 123, true) + +const makeEncodingChain = (size) => { + let schema = Schema.FiniteFromString + for (let i = 1; i < size; i++) { + schema = Schema.String.pipe( + Schema.decodeTo(schema, SchemaTransformation.passthrough()) + ) + } + return schema +} + +const encodingChain8 = makeEncodingChain(8) + +export const encodingChain8DecodeValid = decodeParserCase(encodingChain8, "123", true) +export const encodingChain8DecodeInvalid = decodeParserCase(encodingChain8, "invalid", false) +export const encodingChain8EncodeValid = encodeParserCase(encodingChain8, 123, true) + +const transformedKeyRecord = Schema.Record( + Schema.String.pipe(Schema.decode(SchemaTransformation.snakeToCamel())), + Schema.String +) +const transformedKeyRecordInput = Object.fromEntries( + Array.from({ length: 32 }, (_, index) => [`field_${index}_value`, `value${index}`]) +) + +export const transformedKeyRecordValid = decodeParserCase( + transformedKeyRecord, + transformedKeyRecordInput, + true +) + +const optionalStruct = Schema.Struct({ + required: Schema.String, + optionalKey: Schema.optionalKey(Schema.String), + optionalValue: Schema.optional(Schema.String) +}) + +export const optionalValid = decodeCase(optionalStruct, { required: "value" }, true) +export const optionalPresentValid = decodeCase( + optionalStruct, + { required: "value", optionalKey: "key", optionalValue: "value" }, + true +) +export const optionalPresentInvalid = decodeCase(optionalStruct, { required: "value", optionalKey: 1 }, false) + +const suspendedString = Schema.String.pipe(Schema.decode({ + decode: new SchemaGetter.Getter((input) => Effect.suspend(() => Effect.succeed(input))), + encode: SchemaGetter.passthrough() +})) +const suspendedObjectFields = Object.fromEntries( + Array.from({ length: 32 }, (_, index) => [`field${index}`, index === 16 ? suspendedString : Schema.String]) +) +const suspendedObjectInput = Object.fromEntries( + Array.from({ length: 32 }, (_, index) => [`field${index}`, `value${index}`]) +) + +export const object32SuspendedMiddleValid = decodeParserCase( + Schema.Struct(suspendedObjectFields), + suspendedObjectInput, + true +) + +const literal2 = Schema.Literals(["value0", "value1"]) +const literal100 = Schema.Literals(Array.from({ length: 100 }, (_, index) => `value${index}`)) +const homogeneousUnion100 = Schema.Union( + Array.from( + { length: 100 }, + (_, index) => Schema.String.check(Schema.makeFilter((value) => value === `value${index}`)) + ) +) +const tagged2 = Schema.Union([ + Schema.Struct({ kind: Schema.Literal("a"), value: Schema.String }), + Schema.Struct({ kind: Schema.Literal("b"), value: Schema.String }) +]) +const taggedWithFallback = Schema.Union([ + Schema.Struct({ kind: Schema.Literal("a"), value: Schema.String }), + Schema.Struct({ value: Schema.String }) +]) + +export const literal2ValidLast = decodeParserCase(literal2, "value1", true) +export const literal100ValidFirst = decodeParserCase(literal100, "value0", true) +export const homogeneousUnion100Invalid = decodeParserCase(homogeneousUnion100, "missing", false) +export const tagged2ValidLast = decodeParserCase(tagged2, { kind: "b", value: "value" }, true) +export const taggedWithFallbackValid = decodeParserCase( + taggedWithFallback, + { kind: "a", value: "value" }, + true +) + +const propertyOrderSchema = Schema.Struct({ + a: Schema.String, + b: Schema.String +}) +const propertyOrderInput = { extra: "extra", b: "b", a: "a" } + +export const propertyOrderOriginal = decodeCase( + propertyOrderSchema, + propertyOrderInput, + true, + { onExcessProperty: "preserve", propertyOrder: "original" } +) + +const recursiveTree = Schema.Struct({ + value: Schema.String, + children: Schema.Array(Schema.suspend(() => recursiveTree)) +}) + +const makeTree = (depth) => + depth === 0 + ? { value: "leaf", children: [] } + : { value: `node${depth}`, children: [makeTree(depth - 1)] } + +export const recursiveTreeDepth16Valid = decodeCase(recursiveTree, makeTree(16), true) diff --git a/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts b/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts new file mode 100644 index 000000000..2f62d5ac0 --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema/fixtures/cold.ts @@ -0,0 +1,87 @@ +import * as Schema from "effect/Schema" +import * as SchemaParser from "effect/SchemaParser" +import * as SchemaTransformation from "effect/SchemaTransformation" +import assert from "node:assert/strict" + +const size = 32 +const input = Object.fromEntries(Array.from({ length: size }, (_, index) => [`field${index}`, `value${index}`])) + +const makeEffectCheckedSchema = () => + Schema.Struct( + Object.fromEntries(Array.from({ length: size }, (_, index) => [`field${index}`, Schema.NonEmptyString])) + ) + +const makeEffectTemplateLiteralSchema = () => + Schema.TemplateLiteral(["prefix-", Schema.String, "-middle-", Schema.Number, "-suffix"]) + +const makeEffectRecordSchema = () => Schema.Record(Schema.String, Schema.String) + +const literalValues100 = Array.from({ length: 100 }, (_, index) => `value${index}`) + +const makeEffectLiteral100Schema = () => Schema.Literals(literalValues100) + +const makeEffectTaggedMember = (index) => + Schema.Struct({ + kind: Schema.Literal(`kind${index}`), + a: Schema.String, + b: Schema.Number, + c: Schema.Boolean + }) + +const makeEffectTagged100Schema = () => + Schema.Union(Array.from({ length: 100 }, (_, index) => makeEffectTaggedMember(index))) + +const taggedInput = { + kind: "kind99", + a: "a", + b: 1, + c: true +} + +const makeEffectEncodingChain = (size) => { + let schema = Schema.FiniteFromString + for (let i = 1; i < size; i++) { + schema = Schema.String.pipe( + Schema.decodeTo(schema, SchemaTransformation.passthrough()) + ) + } + return schema +} + +export const effectSchemaCreationTemplateLiteral = () => ({ + run: makeEffectTemplateLiteralSchema, + validate: (schema) => assert.equal(schema.ast._tag, "TemplateLiteral") +}) + +export const effectFirstDecodeCheckedObject32 = () => ({ + run: () => SchemaParser.decodeUnknownExit(makeEffectCheckedSchema())(input), + validate: (result) => assert.equal(result._tag, "Success") +}) + +export const effectFirstDecodeTemplateLiteral = () => ({ + run: () => SchemaParser.decodeUnknownExit(makeEffectTemplateLiteralSchema())("prefix-value-middle-123-suffix"), + validate: (result) => assert.equal(result._tag, "Success") +}) + +export const effectFirstDecodeRecord32 = () => ({ + run: () => SchemaParser.decodeUnknownExit(makeEffectRecordSchema())(input), + validate: (result) => assert.equal(result._tag, "Success") +}) + +export const effectFirstDecodeLiteral100 = () => ({ + run: () => SchemaParser.decodeUnknownExit(makeEffectLiteral100Schema())("value99"), + validate: (result) => assert.equal(result._tag, "Success") +}) + +export const effectFirstDecodeTagged100 = () => ({ + run: () => SchemaParser.decodeUnknownExit(makeEffectTagged100Schema())(taggedInput), + validate: (result) => assert.equal(result._tag, "Success") +}) + +export const effectFirstDecodeEncodingChain8 = () => ({ + run: () => SchemaParser.decodeUnknownExit(makeEffectEncodingChain(8))("123"), + validate: (result) => { + assert.equal(result._tag, "Success") + assert.equal(result.value, 123) + } +}) diff --git a/packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts b/packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts new file mode 100644 index 000000000..252635985 --- /dev/null +++ b/packages/effect/runtimeperf/suites/schema/fixtures/comparison.ts @@ -0,0 +1,91 @@ +import * as Schema from "effect/Schema" +import * as SchemaParser from "effect/SchemaParser" +import assert from "node:assert/strict" + +const effectCase = (schema, input, success) => () => { + const run = SchemaParser.decodeUnknownExit(schema) + return { + run: () => run(input), + validate: (result) => { + assert.equal(result._tag, success ? "Success" : "Failure") + if (success) assert.deepEqual(result.value, input) + } + } +} + +const makeObjectInput = (size) => + Object.fromEntries(Array.from({ length: size }, (_, index) => [`field${index}`, `value${index}`])) + +const makeInvalidLastObjectInput = (size) => ({ + ...makeObjectInput(size), + [`field${size - 1}`]: 1 +}) + +const object32 = makeObjectInput(32) +const object32InvalidLast = makeInvalidLastObjectInput(32) +const effectObject32 = Schema.Struct( + Object.fromEntries(Array.from({ length: 32 }, (_, index) => [`field${index}`, Schema.String])) +) + +export const effectObject32Valid = effectCase(effectObject32, object32, true) +export const effectObject32InvalidLast = effectCase(effectObject32, object32InvalidLast, false) + +const array32 = Array.from({ length: 32 }, (_, index) => `value${index}`) +const array32InvalidLast = array32.slice() +array32InvalidLast[array32InvalidLast.length - 1] = 1 +const effectArray32 = Schema.Array(Schema.String) + +export const effectArray32Valid = effectCase(effectArray32, array32, true) +export const effectArray32InvalidLast = effectCase(effectArray32, array32InvalidLast, false) + +const record32 = makeObjectInput(32) +const record32InvalidLast = makeInvalidLastObjectInput(32) +const effectRecord32 = Schema.Record(Schema.String, Schema.String) + +export const effectRecord32Valid = effectCase(effectRecord32, record32, true) +export const effectRecord32InvalidLast = effectCase(effectRecord32, record32InvalidLast, false) + +const literalValues = Array.from({ length: 100 }, (_, index) => `value${index}`) +const effectLiteral100 = Schema.Literals(literalValues) + +export const effectLiteral100ValidLast = effectCase(effectLiteral100, "value99", true) +export const effectLiteral100Invalid = effectCase(effectLiteral100, "missing", false) + +const makeEffectTaggedMember = (index) => + Schema.Struct({ + kind: Schema.Literal(`kind${index}`), + a: Schema.String, + b: Schema.Number, + c: Schema.Boolean + }) + +const taggedInput = { kind: "kind99", a: "a", b: 1, c: true } +const taggedInvalidSelected = { kind: "kind99", a: "a", b: 1, c: "invalid" } +const taggedInvalidTag = { kind: "missing", a: "a", b: 1, c: true } +const effectTagged100 = Schema.Union( + Array.from({ length: 100 }, (_, index) => makeEffectTaggedMember(index)) +) + +export const effectTagged100ValidLast = effectCase(effectTagged100, taggedInput, true) +export const effectTagged100InvalidSelected = effectCase(effectTagged100, taggedInvalidSelected, false) +export const effectTagged100InvalidTag = effectCase(effectTagged100, taggedInvalidTag, false) + +const makeEffectMultiSentinelMember = (index) => + Schema.Struct({ + kind: Schema.Literal("shared"), + variant: Schema.Literal(`variant${index}`), + value: Schema.String + }) + +const effectMultiSentinel100 = Schema.Union( + Array.from({ length: 100 }, (_, index) => makeEffectMultiSentinelMember(index)) +) +const multiSentinelValidFirst = { kind: "shared", variant: "variant0", value: "value" } +const multiSentinelInvalidVariant = { kind: "shared", variant: "missing", value: "value" } + +export const effectMultiSentinel100ValidFirst = effectCase(effectMultiSentinel100, multiSentinelValidFirst, true) +export const effectMultiSentinel100InvalidVariant = effectCase( + effectMultiSentinel100, + multiSentinelInvalidVariant, + false +) diff --git a/packages/effect/runtimeperf/test/materialize.test.mts b/packages/effect/runtimeperf/test/materialize.test.mts new file mode 100644 index 000000000..e4331c510 --- /dev/null +++ b/packages/effect/runtimeperf/test/materialize.test.mts @@ -0,0 +1,24 @@ +import assert from "node:assert/strict" +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { dirname, join } from "node:path" +import { describe, it } from "node:test" +import { materializeFixture } from "../materialize.mts" + +describe("runtimeperf fixture materialization", () => { + it("preserves relative fixture dependencies", () => { + const root = mkdtempSync(join(tmpdir(), "effect-runtimeperf-materialize-")) + try { + const fixturePath = join(root, "fixtures", "fixture.mts") + mkdirSync(dirname(fixturePath), { recursive: true }) + writeFileSync(join(dirname(fixturePath), "data.mts"), "export const value = 1\n") + writeFileSync(fixturePath, 'import { value } from "./data.mts"\nexport { value }\n') + + const materialized = materializeFixture(join(root, "target"), { fixturePath }) + + assert.equal(existsSync(join(dirname(materialized), "data.mts")), true) + } finally { + rmSync(root, { recursive: true, force: true }) + } + }) +}) diff --git a/packages/effect/runtimeperf/test/registry.test.mts b/packages/effect/runtimeperf/test/registry.test.mts new file mode 100644 index 000000000..1598b6344 --- /dev/null +++ b/packages/effect/runtimeperf/test/registry.test.mts @@ -0,0 +1,134 @@ +import assert from "node:assert/strict" +import { readFile } from "node:fs/promises" +import { describe, it } from "node:test" +import { pathToFileURL } from "node:url" +import { loadRegistry } from "../utils.mts" + +describe("runtimeperf registry", () => { + it("uses unique fixture targets and valid implementations", () => { + const { fixtures } = loadRegistry() + assert.equal(new Set(fixtures.map((fixture) => fixture.target)).size, fixtures.length) + for (const fixture of fixtures) { + assert.ok(["effect", "fast-check-v4", "valibot", "zod4"].includes(fixture.implementation)) + } + }) + + it("pairs every Arbitrary scenario across the native and fast-check implementations", () => { + const { fixtures } = loadRegistry() + const scenarios = Map.groupBy( + fixtures.filter((fixture) => fixture.suite === "arbitrary"), + (fixture) => fixture.scenario + ) + assert.equal(scenarios.size, 31) + for (const fixtures of scenarios.values()) { + assert.deepEqual(fixtures.map((fixture) => fixture.implementation).sort(), ["effect", "fast-check-v4"]) + const metadata = (fixture) => ({ + export: fixture.export, + family: fixture.family, + operation: fixture.operation, + path: fixture.path, + scenario: fixture.scenario, + size: fixture.size, + tier: fixture.tier + }) + assert.deepEqual(metadata(fixtures[0]), metadata(fixtures[1])) + } + }) + + it("keeps the focused Schema diagnostics Effect-only", () => { + const { fixtures } = loadRegistry() + const diagnostics = fixtures.filter((fixture) => fixture.suite === "schema") + assert.ok(diagnostics.length > 0) + assert.equal(diagnostics.every((fixture) => fixture.implementation === "effect"), true) + }) + + it("includes the complete effect@beta Schema Benchmarks matrix", () => { + const { fixtures } = loadRegistry() + assert.deepEqual( + fixtures + .filter((fixture) => fixture.suite === "schema-benchmarks" && fixture.implementation === "effect") + .map((fixture) => fixture.name) + .sort(), + [ + "codec-typed-decode", + "codec-typed-encode", + "codec-unknown-decode", + "codec-unknown-encode", + "initialization-decoder", + "initialization-schema", + "parsing-all-invalid", + "parsing-all-valid", + "parsing-first-invalid", + "parsing-first-valid", + "standard-all-invalid", + "standard-all-valid", + "standard-first-invalid", + "standard-first-valid", + "validation-invalid", + "validation-valid" + ] + ) + }) + + it("includes the complete Valibot and Zod Schema Benchmarks matrices", () => { + const { fixtures } = loadRegistry() + const names = (implementation) => + fixtures + .filter((fixture) => fixture.suite === "schema-benchmarks" && fixture.implementation === implementation) + .map((fixture) => fixture.name) + .sort() + assert.deepEqual(names("valibot"), [ + "initialization-schema-valibot", + "parsing-all-invalid-valibot", + "parsing-all-valid-valibot", + "parsing-first-invalid-valibot", + "parsing-first-valid-valibot", + "standard-all-invalid-valibot", + "standard-all-valid-valibot", + "validation-invalid-valibot", + "validation-valid-valibot" + ]) + assert.deepEqual(names("zod4"), [ + "codec-typed-decode-zod4", + "codec-typed-encode-zod4", + "initialization-schema-zod4", + "parsing-all-invalid-zod4", + "parsing-all-valid-zod4", + "standard-all-invalid-zod4", + "standard-all-valid-zod4" + ]) + }) + + it("uses Zod 4 standard and jitless safeParse for the zod4 fixtures", async () => { + const { fixtures } = loadRegistry() + const zodFiles = new Set( + fixtures + .filter((fixture) => fixture.implementation === "zod4") + .map((fixture) => fixture.fixturePath) + ) + assert.ok(zodFiles.size > 0) + for (const path of zodFiles) { + const source = await readFile(path, "utf8") + assert.match(source, /from "zod\/v4"/) + assert.doesNotMatch(source, /from "zod\/v4-mini"/) + assert.match(source, /jitless:\s*true/) + } + }) + + it("loads, runs and validates every fixture export", async () => { + const { fixtures } = loadRegistry() + const modules = new Map() + for (const fixture of fixtures) { + let module = modules.get(fixture.fixturePath) + if (module === undefined) { + module = await import(pathToFileURL(fixture.fixturePath)) + modules.set(fixture.fixturePath, module) + } + assert.equal(typeof module[fixture.export], "function", fixture.target) + const runtimeCase = module[fixture.export]() + const result = runtimeCase.run() + assert.equal(typeof result?.then, "undefined", fixture.target) + runtimeCase.validate(result) + } + }) +}) diff --git a/packages/effect/runtimeperf/test/stats.test.mts b/packages/effect/runtimeperf/test/stats.test.mts new file mode 100644 index 000000000..4114ab6f1 --- /dev/null +++ b/packages/effect/runtimeperf/test/stats.test.mts @@ -0,0 +1,46 @@ +import assert from "node:assert/strict" +import { describe, it } from "node:test" +import { aggregate, analyzePairs, bootstrapMedianLogRatio, median, percentile } from "../stats.mts" + +describe("runtimeperf stats", () => { + it("calculates medians and percentiles", () => { + assert.equal(median([3, 1, 2]), 2) + assert.equal(median([4, 1, 3, 2]), 2.5) + assert.equal(percentile([1, 2, 3, 4, 5], 0.5), 3) + }) + + it("aggregates raw observations", () => { + assert.deepEqual(aggregate([10, 12, 14]), { + median: 12, + min: 10, + max: 14, + mad: 2 + }) + }) + + it("classifies improvements, regressions and parity", () => { + const options = { iterations: 1_000, seed: 1 } + assert.equal(analyzePairs([100, 101, 99], [80, 81, 79], options).status, "improvement") + assert.equal(analyzePairs([100, 101, 99], [120, 121, 119], options).status, "regression") + assert.equal(analyzePairs([100, 101, 99], [100, 101, 99], options).status, "inconclusive") + }) + + it("keeps exact percentage thresholds inconclusive", () => { + const options = { iterations: 100, seed: 1 } + assert.equal(analyzePairs([100, 100, 100], [98, 98, 98], options).status, "inconclusive") + assert.equal(analyzePairs([100, 100, 100], [105, 105, 105], options).status, "inconclusive") + }) + + it("is deterministic for a fixed seed", () => { + const first = bootstrapMedianLogRatio([0.8, 0.9, 1], { iterations: 1_000, seed: 42 }) + const second = bootstrapMedianLogRatio([0.8, 0.9, 1], { iterations: 1_000, seed: 42 }) + assert.deepEqual(first, second) + }) + + it("rejects invalid input", () => { + assert.throws(() => median([]), /non-empty/) + assert.throws(() => percentile([1], 2), /between 0 and 1/) + assert.throws(() => bootstrapMedianLogRatio([0]), /finite positive/) + assert.throws(() => analyzePairs([1], [1, 2]), /same number/) + }) +}) diff --git a/packages/effect/runtimeperf/test/worker.test.mts b/packages/effect/runtimeperf/test/worker.test.mts new file mode 100644 index 000000000..72cc79f52 --- /dev/null +++ b/packages/effect/runtimeperf/test/worker.test.mts @@ -0,0 +1,60 @@ +import assert from "node:assert/strict" +import { spawnSync } from "node:child_process" +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { describe, it } from "node:test" +import { effectDir, workerPath } from "../utils.mts" + +describe("runtimeperf worker", () => { + it("measures one calibrated batch per minimum iteration and validates the final sink", () => { + const root = mkdtempSync(join(tmpdir(), "effect-runtimeperf-worker-")) + try { + const fixturePath = join(root, "fixture.mjs") + const validationsPath = join(root, "validations.txt") + writeFileSync( + fixturePath, + `import { appendFileSync } from "node:fs" +let count = 0 +export const runtimeCase = () => ({ + run: () => { + count++ + const end = process.hrtime.bigint() + 2_000_000n + while (process.hrtime.bigint() < end) {} + return count + }, + validate: (value) => appendFileSync(${JSON.stringify(validationsPath)}, \`\${value}\\n\`) +}) +` + ) + const result = spawnSync(process.execPath, [ + workerPath, + "--mode", + "measure", + "--fixture", + fixturePath, + "--export", + "runtimeCase", + "--batch-size", + "3", + "--time-ms", + "1", + "--warmup-time-ms", + "1" + ], { + cwd: effectDir, + encoding: "utf8" + }) + assert.equal(result.status, 0, result.stderr) + const output = JSON.parse(result.stdout) + assert.equal(output.runs, 1) + assert.equal(output.batchSize, 3) + assert.deepEqual( + readFileSync(validationsPath, "utf8").trim().split("\n").map(Number), + [1, 7] + ) + } finally { + rmSync(root, { recursive: true, force: true }) + } + }) +}) diff --git a/packages/effect/runtimeperf/utils.mts b/packages/effect/runtimeperf/utils.mts new file mode 100644 index 000000000..2dc6210b0 --- /dev/null +++ b/packages/effect/runtimeperf/utils.mts @@ -0,0 +1,253 @@ +import { spawnSync } from "node:child_process" +import { createHash, randomBytes } from "node:crypto" +import { mkdirSync, readFileSync, writeFileSync } from "node:fs" +import { dirname, join, relative, resolve } from "node:path" +import { fileURLToPath } from "node:url" +import { aggregate } from "./stats.mts" + +export const runtimeperfDir = dirname(fileURLToPath(import.meta.url)) +export const effectDir = resolve(runtimeperfDir, "..") +export const repoRoot = resolve(effectDir, "../..") +export const workerPath = join(runtimeperfDir, "worker.mts") +export const configPath = join(runtimeperfDir, "config.json") +export const resultsRoot = join(repoRoot, "tmp", "runtimeperf", "results") + +export const readJson = (path) => JSON.parse(readFileSync(path, "utf8")) + +export const writeJson = (path, value) => { + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`) +} + +export const sha256 = (value) => createHash("sha256").update(value).digest("hex") + +export const hashFile = (path) => sha256(readFileSync(path)) + +export const libraryVersions = () => ({ + effect: readJson(join(effectDir, "package.json")).version, + fastCheck: readJson(join(repoRoot, "node_modules", "fast-check", "package.json")).version, + tinybench: readJson(join(effectDir, "node_modules", "tinybench", "package.json")).version, + valibot: readJson(join(effectDir, "node_modules", "valibot", "package.json")).version, + zod: readJson(join(repoRoot, "node_modules", "zod", "package.json")).version, + zodExport: "zod/v4" +}) + +export const currentGitState = () => { + const git = (args) => { + const result = spawnSync("git", args, { cwd: repoRoot, encoding: "utf8" }) + if (result.error) throw result.error + if (result.status !== 0) { + throw new Error(`${result.stdout}${result.stderr}`.trim()) + } + return result.stdout.trim() + } + const status = git(["status", "--short", "--untracked-files=all"]) + const diff = git(["diff", "--binary", "HEAD", "--"]) + return { + sha: git(["rev-parse", "HEAD"]), + dirty: status !== "", + status: status === "" ? [] : status.split("\n"), + diffHash: sha256(diff) + } +} + +export const makeRunId = () => + `${new Date().toISOString().replace(/[:.]/g, "-")}-${process.pid}-${randomBytes(3).toString("hex")}` + +export const sanitize = (name) => name.replace(/[^a-zA-Z0-9._-]/g, "-") + +export const parseArgs = (args, { compare = false } = {}) => { + const options = { + target: undefined, + rounds: undefined, + timeMs: undefined, + warmupTimeMs: undefined, + tier: undefined, + family: undefined, + implementation: undefined, + base: "HEAD", + head: "worktree", + failOnRegression: false, + help: false + } + const valueOptions = new Map([ + ["--rounds", "rounds"], + ["--time", "timeMs"], + ["--warmup-time", "warmupTimeMs"], + ["--tier", "tier"], + ["--family", "family"], + ["--implementation", "implementation"], + ["--base", "base"], + ["--head", "head"] + ]) + for (let index = 0; index < args.length; index++) { + const arg = args[index] + if (arg === "--help" || arg === "-h") { + options.help = true + } else if (arg === "--fail-on-regression" && compare) { + options.failOnRegression = true + } else if (valueOptions.has(arg)) { + const value = args[++index] + if (value === undefined || value.startsWith("-")) { + throw new Error(`Missing value for ${arg}`) + } + options[valueOptions.get(arg)] = value + } else if (arg.startsWith("-")) { + throw new Error(`Unknown option: ${arg}`) + } else if (options.target === undefined) { + options.target = arg + } else { + throw new Error(`Expected at most one target, got ${options.target} and ${arg}`) + } + } + for (const key of ["rounds", "timeMs", "warmupTimeMs", "tier"]) { + if (options[key] !== undefined) { + const value = Number(options[key]) + if (!Number.isInteger(value) || value < 0 || (key !== "tier" && value === 0)) { + throw new Error(`--${key} must be ${key === "tier" ? "a non-negative" : "a positive"} integer`) + } + options[key] = value + } + } + return options +} + +export const loadRegistry = () => { + const config = readJson(configPath) + const fixtures = config.suites.flatMap((suite) => + suite.fixtures.flatMap((fixtureGroup) => + fixtureGroup.cases.map((runtimeCase) => ({ + ...fixtureGroup.defaults, + ...runtimeCase, + suite: suite.name, + target: `${suite.name}/${runtimeCase.name}`, + fixturePath: resolve(runtimeperfDir, fixtureGroup.file) + })) + ) + ) + return { config, fixtures } +} + +export const selectFixtures = (fixtures, options, { effectOnly = false } = {}) => { + let selected = fixtures + if (options.target !== undefined) { + selected = selected.filter((fixture) => + fixture.suite === options.target || + fixture.target === options.target || + fixture.scenario === options.target + ) + } + if (options.tier !== undefined) { + selected = selected.filter((fixture) => fixture.tier === options.tier) + } + if (options.family !== undefined) { + selected = selected.filter((fixture) => fixture.family === options.family) + } + if (options.implementation !== undefined) { + selected = selected.filter((fixture) => fixture.implementation === options.implementation) + } + if (effectOnly) { + selected = selected.filter((fixture) => fixture.implementation === "effect") + } + if (selected.length === 0) { + throw new Error("No runtimeperf fixtures matched the selection") + } + return selected +} + +export const resolveDefaults = (config, options) => ({ + rounds: options.rounds ?? config.defaults.rounds, + timeMs: options.timeMs ?? config.defaults.timeMs, + warmupTimeMs: options.warmupTimeMs ?? config.defaults.warmupTimeMs, + targetBatchTimeNs: config.defaults.targetBatchTimeNs, + maxBatchSize: config.defaults.maxBatchSize, + bootstrapIterations: config.defaults.bootstrapIterations, + bootstrapSeed: config.defaults.bootstrapSeed, + minImprovementPercent: config.defaults.minImprovementPercent, + maxRegressionPercent: config.defaults.maxRegressionPercent +}) + +export const runWorker = (workerArgs) => { + const result = spawnSync(process.execPath, [workerPath, ...workerArgs], { + cwd: effectDir, + encoding: "utf8", + maxBuffer: 10 * 1024 * 1024 + }) + if (result.error) throw result.error + if (result.status !== 0) { + throw new Error(`${result.stderr || result.stdout}`.trim()) + } + try { + return JSON.parse(result.stdout) + } catch { + throw new Error(`Worker returned invalid JSON: ${result.stdout}`) + } +} + +export const calibrateFixture = (fixture, defaults, fixturePath = fixture.fixturePath) => + runWorker([ + "--mode", + "calibrate", + "--fixture", + fixturePath, + "--export", + fixture.export, + "--target-batch-time-ns", + String(defaults.targetBatchTimeNs), + "--max-batch-size", + String(defaults.maxBatchSize) + ]) + +export const measureFixture = (fixture, defaults, batchSize, fixturePath = fixture.fixturePath) => + runWorker([ + "--mode", + "measure", + "--fixture", + fixturePath, + "--export", + fixture.export, + "--batch-size", + String(batchSize), + "--time-ms", + String(defaults.timeMs), + "--warmup-time-ms", + String(defaults.warmupTimeMs) + ]) + +export const aggregateMeasurements = (measurements) => aggregate(measurements.map((item) => item.nsPerOp)) + +export const coverageSummary = (fixtures) => ({ + tiers: [...new Set(fixtures.map((fixture) => fixture.tier))].sort(), + families: [...new Set(fixtures.map((fixture) => fixture.family))].sort(), + implementations: [...new Set(fixtures.map((fixture) => fixture.implementation))].sort(), + effectAstTags: [ + ...new Set( + fixtures + .filter((fixture) => fixture.implementation === "effect") + .flatMap((fixture) => fixture.astTags) + ) + ].sort() +}) + +export const formatNs = (value) => value < 1_000 + ? value.toFixed(1) + : value < 1_000_000 + ? `${(value / 1_000).toFixed(2)}µs` + : `${(value / 1_000_000).toFixed(2)}ms` + +export const printTable = (headers, rows) => { + const textRows = rows.map((row) => row.map(String)) + const table = [headers, ...textRows] + const widths = headers.map((_, index) => Math.max(...table.map((row) => row[index].length))) + table.forEach((row, index) => { + process.stdout.write(`${row.map((cell, cellIndex) => cell.padEnd(widths[cellIndex])).join(" ")}\n`) + if (index === 0) { + process.stdout.write(`${widths.map((width) => "-".repeat(width)).join(" ")}\n`) + } + }) +} + +export const reportPath = (runId, target, kind) => + join(resultsRoot, `${runId}-${kind}-${sanitize(target ?? "all")}.json`) + +export const relativeToRepo = (path) => relative(repoRoot, path) diff --git a/packages/effect/runtimeperf/worker.mts b/packages/effect/runtimeperf/worker.mts new file mode 100644 index 000000000..19bc6d1ae --- /dev/null +++ b/packages/effect/runtimeperf/worker.mts @@ -0,0 +1,154 @@ +import { pathToFileURL } from "node:url" +import { Bench } from "tinybench" +import { median } from "./stats.mts" + +let sink + +const args = process.argv.slice(2) +const readOption = (name) => { + const index = args.indexOf(name) + if (index === -1) return undefined + const value = args[index + 1] + if (value === undefined || value.startsWith("--")) { + throw new Error(`Missing value for ${name}`) + } + return value +} + +const readPositiveNumber = (name, fallback) => { + const raw = readOption(name) + if (raw === undefined) return fallback + const value = Number(raw) + if (!Number.isFinite(value) || value <= 0) { + throw new Error(`${name} must be a positive number`) + } + return value +} + +const isPromiseLike = (value) => + value !== null && + (typeof value === "object" || typeof value === "function") && + typeof value.then === "function" + +const loadCase = async (fixturePath, exportName) => { + const fixture = await import(`${pathToFileURL(fixturePath).href}?runtimeperf=${process.pid}`) + const factory = fixture[exportName] + if (typeof factory !== "function") { + throw new Error(`Fixture export ${exportName} is not a function`) + } + const runtimeCase = factory() + if ( + runtimeCase === null || + typeof runtimeCase !== "object" || + typeof runtimeCase.run !== "function" || + typeof runtimeCase.validate !== "function" + ) { + throw new Error(`Fixture export ${exportName} did not return a RuntimePerfCase`) + } + const validationResult = runtimeCase.run() + if (isPromiseLike(validationResult)) { + throw new Error(`Fixture export ${exportName} returned a Promise`) + } + runtimeCase.validate(validationResult) + sink = validationResult + return runtimeCase +} + +const runBatch = (run, batchSize) => { + let value + for (let index = 0; index < batchSize; index++) { + value = run() + } + if (isPromiseLike(value)) { + throw new Error("Synchronous runtimeperf task returned a Promise") + } + sink = value +} + +const calibrate = (runtimeCase, targetBatchTimeNs, maxBatchSize) => { + const warnings = [] + let batchSize = 1 + while (true) { + const samples = new Array(5) + for (let attempt = 0; attempt < samples.length; attempt++) { + const start = process.hrtime.bigint() + runBatch(runtimeCase.run, batchSize) + samples[attempt] = Number(process.hrtime.bigint() - start) + } + if (median(samples) >= targetBatchTimeNs || batchSize >= maxBatchSize) { + if (batchSize >= maxBatchSize && median(samples) < targetBatchTimeNs) { + warnings.push(`Maximum batch size ${maxBatchSize} did not reach ${targetBatchTimeNs} ns`) + } + return { batchSize, warnings } + } + batchSize = Math.min(batchSize * 2, maxBatchSize) + } +} + +const measure = (runtimeCase, batchSize, timeMs, warmupTimeMs) => { + const bench = new Bench({ + iterations: 1, + time: timeMs, + warmup: true, + warmupIterations: 1, + warmupTime: warmupTimeMs, + timestampProvider: "hrtimeNow" + }) + bench.add("runtimeperf", () => runBatch(runtimeCase.run, batchSize), { async: false }) + bench.runSync() + const task = bench.tasks[0] + const result = task.result + if (result?.state !== "completed") { + throw new Error(`Tinybench task did not complete: ${result?.state ?? "missing result"}`) + } + const latencyToNs = (value) => value * 1_000_000 / batchSize + return { + nsPerOp: result.totalTime * 1_000_000 / (task.runs * batchSize), + runs: task.runs, + batchSize, + totalTimeMs: result.totalTime, + latency: { + p50Ns: latencyToNs(result.latency.p50), + p99Ns: latencyToNs(result.latency.p99), + minNs: latencyToNs(result.latency.min), + maxNs: latencyToNs(result.latency.max), + rme: result.latency.rme, + samplesCount: result.latency.samplesCount + }, + runtime: result.runtime, + runtimeVersion: result.runtimeVersion, + timestampProviderName: result.timestampProviderName + } +} + +const main = async () => { + const mode = readOption("--mode") + const fixturePath = readOption("--fixture") + const exportName = readOption("--export") + if (mode !== "calibrate" && mode !== "measure") { + throw new Error("--mode must be calibrate or measure") + } + if (fixturePath === undefined || exportName === undefined) { + throw new Error("--fixture and --export are required") + } + const runtimeCase = await loadCase(fixturePath, exportName) + const output = mode === "calibrate" + ? calibrate( + runtimeCase, + readPositiveNumber("--target-batch-time-ns", 100_000), + readPositiveNumber("--max-batch-size", 1_048_576) + ) + : measure( + runtimeCase, + readPositiveNumber("--batch-size", 1), + readPositiveNumber("--time-ms", 500), + readPositiveNumber("--warmup-time-ms", 150) + ) + runtimeCase.validate(sink) + process.stdout.write(`${JSON.stringify({ ok: true, mode, ...output })}\n`) +} + +main().catch((error) => { + process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`) + process.exitCode = 1 +}) diff --git a/packages/effect/src/Array.ts b/packages/effect/src/Array.ts index 32f6a566e..8813ded8c 100644 --- a/packages/effect/src/Array.ts +++ b/packages/effect/src/Array.ts @@ -13,9 +13,12 @@ import * as Equal from "./Equal.ts" import * as Equivalence from "./Equivalence.ts" import type { LazyArg } from "./Function.ts" import { dual, identity } from "./Function.ts" +import * as Hash from "./Hash.ts" import type { TypeLambda } from "./HKT.ts" import * as internalArray from "./internal/array.ts" +import * as Count from "./internal/count.ts" import * as internalDoNotation from "./internal/doNotation.ts" +import * as InternalRecord from "./internal/record.ts" import * as moduleIterable from "./Iterable.ts" import * as Option from "./Option.ts" import * as Order from "./Order.ts" @@ -36,11 +39,10 @@ import type { NoInfer, TupleOf } from "./Types.ts" * * **Example** (Accessing the Array constructor) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const arr = new Array.Array(3) - * console.log(arr) // [undefined, undefined, undefined] + * Array.Array === globalThis.Array // => true * ``` * * @category constructors @@ -51,7 +53,7 @@ export const Array = globalThis.Array /** * Type lambda for `ReadonlyArray`, used for higher-kinded type operations. * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface ReadonlyArrayTypeLambda extends TypeLambda { @@ -68,11 +70,13 @@ export interface ReadonlyArrayTypeLambda extends TypeLambda { * * **Example** (Typing a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * const nonEmpty: Array.NonEmptyReadonlyArray = [1, 2, 3] * const head: number = nonEmpty[0] // guaranteed to exist + * + * head // => 1 * ``` * * @see {@link NonEmptyArray} — mutable counterpart @@ -99,11 +103,13 @@ export type NonEmptyReadonlyArray = readonly [A, ...Array] * * **Example** (Typing a mutable non-empty array) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * const nonEmpty: Array.NonEmptyArray = [1, 2, 3] * nonEmpty.push(4) + * + * nonEmpty // => [1, 2, 3, 4] * ``` * * @see {@link NonEmptyReadonlyArray} — readonly counterpart @@ -128,11 +134,10 @@ export type NonEmptyArray = [A, ...Array] * * **Example** (Creating an array from values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.make(1, 2, 3) - * console.log(result) // [1, 2, 3] + * Array.make(1, 2, 3) // => [1, 2, 3] * ``` * * @see {@link of} — create a single-element array @@ -154,15 +159,15 @@ export const make = >( * * **Details** * + * `n` is rounded down. `NaN` and non-positive values are treated as `0`. * Elements are typed as `A | undefined` because the slots are empty. * * **Example** (Allocating a fixed-size array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.allocate(3) - * console.log(result.length) // 3 + * Array.allocate(3).length // => 3 * ``` * * @see {@link makeBy} — create an array by computing each element @@ -170,7 +175,7 @@ export const make = >( * @category constructors * @since 2.0.0 */ -export const allocate = (n: number): Array => new Array(n) +export const allocate = (n: number): Array => new Array(Count.normalize(n)) /** * Creates a `NonEmptyArray` of length `n` where element `i` is computed by `f(i)`. @@ -181,17 +186,16 @@ export const allocate = (n: number): Array => new Arra * * **Details** * - * `n` is normalized to an integer greater than or equal to 1, so this function - * always returns at least one element. Supports both data-first and data-last - * usage. + * `n` is rounded down and normalized to an integer greater than or equal to 1. + * `NaN` is treated as `1`, so this function always returns at least one + * element. Supports both data-first and data-last usage. * * **Example** (Generating values from indices) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.makeBy(5, (n) => n * 2) - * console.log(result) // [0, 2, 4, 6, 8] + * Array.makeBy(5, (n) => n * 2) // => [0, 2, 4, 6, 8] * ``` * * @see {@link range} — create a range of integers @@ -204,7 +208,7 @@ export const makeBy: { (f: (i: number) => A): (n: number) => NonEmptyArray (n: number, f: (i: number) => A): NonEmptyArray } = dual(2, (n: number, f: (i: number) => A) => { - const max = Math.max(1, Math.floor(n)) + const max = Count.normalizeNonEmpty(n) const out = new Array(max) for (let i = 0; i < max; i++) { out[i] = f(i) @@ -226,11 +230,10 @@ export const makeBy: { * * **Example** (Creating a range) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.range(1, 3) - * console.log(result) // [1, 2, 3] + * Array.range(1, 3) // => [1, 2, 3] * ``` * * @see {@link makeBy} — generate values from a function @@ -256,11 +259,10 @@ export const range = (start: number, end: number): NonEmptyArray => * * **Example** (Repeating a value) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.replicate("a", 3) - * console.log(result) // ["a", "a", "a"] + * Array.replicate("a", 3) // => ["a", "a", "a"] * ``` * * @see {@link makeBy} — vary values based on index @@ -288,11 +290,10 @@ export const replicate: { * * **Example** (Converting a Set to an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.fromIterable(new Set([1, 2, 3])) - * console.log(result) // [1, 2, 3] + * Array.fromIterable(new Set([1, 2, 3])) // => [1, 2, 3] * ``` * * @see {@link ensure} — wrap a single value or return an existing array @@ -320,11 +321,11 @@ export const fromIterable = (collection: Iterable): Array => * * **Example** (Normalizing input) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.ensure("a")) // ["a"] - * console.log(Array.ensure(["a", "b", "c"])) // ["a", "b", "c"] + * Array.ensure("a") // => ["a"] + * Array.ensure(["a", "b", "c"]) // => ["a", "b", "c"] * ``` * * @see {@link of} — always wrap in a single-element array @@ -350,11 +351,10 @@ export const ensure = (self: ReadonlyArray | A): Array => Array.isArray * * **Example** (Converting a record to entries) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.fromRecord({ a: 1, b: 2, c: 3 }) - * console.log(result) // [["a", 1], ["b", 2], ["c", 3]] + * Array.fromRecord({ a: 1, b: 2, c: 3 }) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @see {@link Record.toEntries} the equivalent function from the Record module @@ -374,11 +374,11 @@ export const fromRecord: (self: Readonly>) => * * **Example** (Converting an Option to an array) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * - * console.log(Array.fromOption(Option.some(1))) // [1] - * console.log(Array.fromOption(Option.none())) // [] + * Array.fromOption(Option.some(1)) // => [1] + * Array.fromOption(Option.none()) // => [] * ``` * * @see {@link getSomes} — extract `Some` values from an array of Options @@ -402,15 +402,16 @@ export const fromOption: (self: Option.Option) => Array = Option.toArra * * **Example** (Branching on emptiness) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const describe = Array.match({ * onEmpty: () => "empty", * onNonEmpty: ([head, ...tail]) => `head: ${head}, tail: ${tail.length}` * }) - * console.log(describe([])) // "empty" - * console.log(describe([1, 2, 3])) // "head: 1, tail: 2" + * + * describe([]) // => "empty" + * describe([1, 2, 3]) // => "head: 1, tail: 2" * ``` * * @see {@link matchLeft} — destructures into head + tail @@ -456,15 +457,16 @@ export const match: { * * **Example** (Destructuring head and tail) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const matchLeft = Array.matchLeft({ * onEmpty: () => "empty", * onNonEmpty: (head, tail) => `head: ${head}, tail: ${tail.length}` * }) - * console.log(matchLeft([])) // "empty" - * console.log(matchLeft([1, 2, 3])) // "head: 1, tail: 2" + * + * matchLeft([]) // => "empty" + * matchLeft([1, 2, 3]) // => "head: 1, tail: 2" * ``` * * @see {@link match} — receives the full non-empty array @@ -510,15 +512,16 @@ export const matchLeft: { * * **Example** (Destructuring init and last) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const matchRight = Array.matchRight({ * onEmpty: () => "empty", * onNonEmpty: (init, last) => `init: ${init.length}, last: ${last}` * }) - * console.log(matchRight([])) // "empty" - * console.log(matchRight([1, 2, 3])) // "init: 2, last: 3" + * + * matchRight([]) // => "empty" + * matchRight([1, 2, 3]) // => "init: 2, last: 3" * ``` * * @see {@link match} — receives the full non-empty array @@ -562,11 +565,10 @@ export const matchRight: { * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.prepend([2, 3, 4], 1) - * console.log(result) // [1, 2, 3, 4] + * Array.prepend([2, 3, 4], 1) // => [1, 2, 3, 4] * ``` * * @see {@link append} — add to the end @@ -593,11 +595,10 @@ export const prepend: { * * **Example** (Prepending multiple elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.prependAll([2, 3], [0, 1]) - * console.log(result) // [0, 1, 2, 3] + * Array.prependAll([2, 3], [0, 1]) // => [0, 1, 2, 3] * ``` * * @see {@link prepend} — add a single element to the front @@ -628,11 +629,10 @@ export const prependAll: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.append([1, 2, 3], 4) - * console.log(result) // [1, 2, 3, 4] + * Array.append([1, 2, 3], 4) // => [1, 2, 3, 4] * ``` * * @see {@link prepend} — add to the front @@ -660,11 +660,10 @@ export const append: { * * **Example** (Concatenating arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.appendAll([1, 2], [3, 4]) - * console.log(result) // [1, 2, 3, 4] + * Array.appendAll([1, 2], [3, 4]) // => [1, 2, 3, 4] * ``` * * @see {@link append} — add a single element to the end @@ -700,11 +699,10 @@ export const appendAll: { * * **Example** (Running totals) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.scan([1, 2, 3, 4], 0, (acc, value) => acc + value) - * console.log(result) // [0, 1, 3, 6, 10] + * Array.scan([1, 2, 3, 4], 0, (acc, value) => acc + value) // => [0, 1, 3, 6, 10] * ``` * * @see {@link scanRight} — right-to-left scan @@ -741,11 +739,10 @@ export const scan: { * * **Example** (Scanning running totals in reverse) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.scanRight([1, 2, 3, 4], 0, (acc, value) => acc + value) - * console.log(result) // [10, 9, 7, 4, 0] + * Array.scanRight([1, 2, 3, 4], 0, (acc, value) => acc + value) // => [10, 9, 7, 4, 0] * ``` * * @see {@link scan} — left-to-right scan @@ -781,11 +778,11 @@ export const scanRight: { * * **Example** (Type-guarding an unknown value) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isArray(null)) // false - * console.log(Array.isArray([1, 2, 3])) // true + * Array.isArray(null) // => false + * Array.isArray([1, 2, 3]) // => true * ``` * * @see {@link isArrayEmpty} — check for an empty array @@ -804,11 +801,11 @@ export const isArray: { * * **Example** (Checking for an empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isArrayEmpty([])) // true - * console.log(Array.isArrayEmpty([1, 2, 3])) // false + * Array.isArrayEmpty([]) // => true + * Array.isArrayEmpty([1, 2, 3]) // => false * ``` * * @see {@link isReadonlyArrayEmpty} — readonly variant @@ -824,11 +821,11 @@ export const isArrayEmpty = (self: Array): self is [] => self.length === 0 * * **Example** (Checking for an empty readonly array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isReadonlyArrayEmpty([])) // true - * console.log(Array.isReadonlyArrayEmpty([1, 2, 3])) // false + * Array.isReadonlyArrayEmpty([]) // => true + * Array.isReadonlyArrayEmpty([1, 2, 3]) // => false * ``` * * @see {@link isArrayEmpty} — mutable variant @@ -850,11 +847,11 @@ export const isReadonlyArrayEmpty: (self: ReadonlyArray) => self is readon * * **Example** (Checking for a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isArrayNonEmpty([])) // false - * console.log(Array.isArrayNonEmpty([1, 2, 3])) // true + * Array.isArrayNonEmpty([]) // => false + * Array.isArrayNonEmpty([1, 2, 3]) // => true * ``` * * @see {@link isReadonlyArrayNonEmpty} — readonly variant @@ -876,11 +873,11 @@ export const isArrayNonEmpty: (self: Array) => self is NonEmptyArray = * * **Example** (Checking for a non-empty readonly array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.isReadonlyArrayNonEmpty([])) // false - * console.log(Array.isReadonlyArrayNonEmpty([1, 2, 3])) // true + * Array.isReadonlyArrayNonEmpty([]) // => false + * Array.isReadonlyArrayNonEmpty([1, 2, 3]) // => true * ``` * * @see {@link isArrayNonEmpty} — mutable variant @@ -901,10 +898,10 @@ export const isReadonlyArrayNonEmpty: (self: ReadonlyArray) => self is Non * * **Example** (Getting the length) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.length([1, 2, 3])) // 3 + * Array.length([1, 2, 3]) // => 3 * ``` * * @category getters @@ -912,13 +909,23 @@ export const isReadonlyArrayNonEmpty: (self: ReadonlyArray) => self is Non */ export const length = (self: ReadonlyArray): number => self.length +/** + * Checks whether a string represents a JavaScript array index: a non-negative + * integer below `2 ** 32 - 1`, written without leading zeroes, a sign, or + * exponent notation. + * + * @internal + */ +export function isCanonicalArrayIndex(key: string): boolean { + const index = Number(key) + return String(index) === key && Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 +} + /** @internal */ export function isOutOfBounds(i: number, as: ReadonlyArray): boolean { - return i < 0 || i >= as.length + return !Number.isFinite(i) || i < 0 || i >= as.length } -const clamp = (i: number, as: ReadonlyArray): number => Math.floor(Math.min(Math.max(0, i), as.length)) - /** * Reads an element at the given index safely, returning `Option.some` or * `Option.none` if the index is out of bounds. @@ -934,11 +941,11 @@ const clamp = (i: number, as: ReadonlyArray): number => Math.floor(Math.mi * * **Example** (Accessing indexes safely) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.get([1, 2, 3], 1)) // Some(2) - * console.log(Array.get([1, 2, 3], 10)) // None + * Array.get([1, 2, 3], 1) // => Option.some(2) + * Array.get([1, 2, 3], 10) // => Option.none() * ``` * * @see {@link getUnsafe} for indexed access that throws when the index is out of bounds @@ -971,10 +978,10 @@ export const get: { * * **Example** (Accessing indexes unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.getUnsafe([1, 2, 3], 1)) // 2 + * Array.getUnsafe([1, 2, 3], 1) // => 2 * // Array.getUnsafe([1, 2, 3], 10) // throws Error * ``` * @@ -1008,11 +1015,10 @@ export const getUnsafe: { * * **Example** (Destructuring head and tail) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.unprepend([1, 2, 3, 4]) - * console.log(result) // [1, [2, 3, 4]] + * Array.unprepend([1, 2, 3, 4]) // => [1, [2, 3, 4]] * ``` * * @see {@link unappend} for splitting a non-empty array into init and last @@ -1041,11 +1047,10 @@ export const unprepend = ( * * **Example** (Destructuring init and last) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.unappend([1, 2, 3, 4]) - * console.log(result) // [[1, 2, 3], 4] + * Array.unappend([1, 2, 3, 4]) // => [[1, 2, 3], 4] * ``` * * @see {@link unprepend} for splitting a non-empty array into head and tail @@ -1069,11 +1074,11 @@ export const unappend = ( * * **Example** (Getting the first element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.head([1, 2, 3])) // Some(1) - * console.log(Array.head([])) // None + * Array.head([1, 2, 3]) // => Option.some(1) + * Array.head([]) // => Option.none() * ``` * * @see {@link headNonEmpty} — direct access when array is known non-empty @@ -1095,10 +1100,10 @@ export const head: (self: ReadonlyArray) => Option.Option = get(0) * * **Example** (Getting the head of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.headNonEmpty([1, 2, 3, 4])) // 1 + * Array.headNonEmpty([1, 2, 3, 4]) // => 1 * ``` * * @see {@link head} — safe version for possibly-empty arrays @@ -1118,11 +1123,11 @@ export const headNonEmpty: (self: NonEmptyReadonlyArray) => A = getUnsafe( * * **Example** (Getting the last element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.last([1, 2, 3])) // Some(3) - * console.log(Array.last([])) // None + * Array.last([1, 2, 3]) // => Option.some(3) + * Array.last([]) // => Option.none() * ``` * * @see {@link lastNonEmpty} — direct access when array is known non-empty @@ -1145,10 +1150,10 @@ export const last = (self: ReadonlyArray): Option.Option => * * **Example** (Getting the last of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.lastNonEmpty([1, 2, 3, 4])) // 4 + * Array.lastNonEmpty([1, 2, 3, 4]) // => 4 * ``` * * @see {@link last} — safe version for possibly-empty arrays @@ -1171,11 +1176,11 @@ export const lastNonEmpty = (self: NonEmptyReadonlyArray): A => self[self. * * **Example** (Getting the tail) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.tail([1, 2, 3, 4])) // Option.some([2, 3, 4]) - * console.log(Array.tail([])) // Option.none() + * Array.tail([1, 2, 3, 4]) // => Option.some([2, 3, 4]) + * Array.tail([]) // => Option.none() * ``` * * @see {@link tailNonEmpty} — when the array is known non-empty @@ -1198,10 +1203,10 @@ export function tail(self: Iterable): Option.Option> { * * **Example** (Getting the tail of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.tailNonEmpty([1, 2, 3, 4])) // [2, 3, 4] + * Array.tailNonEmpty([1, 2, 3, 4]) // => [2, 3, 4] * ``` * * @see {@link tail} — safe version for possibly-empty arrays @@ -1226,11 +1231,11 @@ export const tailNonEmpty = (self: NonEmptyReadonlyArray): Array => sel * * **Example** (Getting init) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.init([1, 2, 3, 4])) // Option.some([1, 2, 3]) - * console.log(Array.init([])) // Option.none() + * Array.init([1, 2, 3, 4]) // => Option.some([1, 2, 3]) + * Array.init([]) // => Option.none() * ``` * * @see {@link initNonEmpty} — when the array is known non-empty @@ -1253,10 +1258,10 @@ export function init(self: Iterable): Option.Option> { * * **Example** (Getting init of a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.initNonEmpty([1, 2, 3, 4])) // [1, 2, 3] + * Array.initNonEmpty([1, 2, 3, 4]) // => [1, 2, 3] * ``` * * @see {@link init} — safe version for possibly-empty arrays @@ -1267,6 +1272,8 @@ export function init(self: Iterable): Option.Option> { */ export const initNonEmpty = (self: NonEmptyReadonlyArray): Array => self.slice(0, -1) +const clampCount = (n: number, length: number): number => Math.min(Count.normalize(n), length) + /** * Keeps the first `n` elements, creating a new array. * @@ -1276,14 +1283,15 @@ export const initNonEmpty = (self: NonEmptyReadonlyArray): Array => sel * * **Details** * - * `n` is clamped to `[0, length]`. Returns an empty array when `n <= 0`. + * `n` is rounded down and clamped to `[0, length]`. `NaN` is treated as `0`. + * Returns an empty array when `n <= 0`. * * **Example** (Taking from the start) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.take([1, 2, 3, 4, 5], 3)) // [1, 2, 3] + * Array.take([1, 2, 3, 4, 5], 3) // => [1, 2, 3] * ``` * * @see {@link takeRight} for keeping elements from the end @@ -1298,7 +1306,7 @@ export const take: { (self: Iterable, n: number): Array } = dual(2, (self: Iterable, n: number): Array => { const input = fromIterable(self) - return input.slice(0, clamp(n, input)) + return input.slice(0, clampCount(n, input.length)) }) /** @@ -1310,14 +1318,15 @@ export const take: { * * **Details** * - * `n` is clamped to `[0, length]`. Returns an empty array when `n <= 0`. + * `n` is rounded down and clamped to `[0, length]`. `NaN` is treated as `0`. + * Returns an empty array when `n <= 0`. * * **Example** (Taking from the end) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.takeRight([1, 2, 3, 4, 5], 3)) // [3, 4, 5] + * Array.takeRight([1, 2, 3, 4, 5], 3) // => [3, 4, 5] * ``` * * @see {@link take} — keep from the start @@ -1331,7 +1340,7 @@ export const takeRight: { (self: Iterable, n: number): Array } = dual(2, (self: Iterable, n: number): Array => { const input = fromIterable(self) - const i = clamp(n, input) + const i = clampCount(n, input.length) return i === 0 ? [] : input.slice(-i) }) @@ -1351,10 +1360,10 @@ export const takeRight: { * * **Example** (Taking while condition holds) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.takeWhile([1, 3, 2, 4, 1, 2], (x) => x < 4)) // [1, 3, 2] + * Array.takeWhile([1, 3, 2, 4, 1, 2], (x) => x < 4) // => [1, 3, 2] * ``` * * @see {@link take} for keeping a fixed number of leading elements @@ -1446,10 +1455,10 @@ const spanIndex = (self: Iterable, predicate: (a: A, i: number) => boolean * * **Example** (Splitting at predicate boundary) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.span([1, 3, 2, 4, 5], (x) => x % 2 === 1)) // [[1, 3], [2, 4, 5]] + * Array.span([1, 3, 2, 4, 5], (x) => x % 2 === 1) // => [[1, 3], [2, 4, 5]] * ``` * * @see {@link takeWhile} for keeping only the matching prefix @@ -1487,15 +1496,15 @@ export const span: { * * **Details** * - * `n` is clamped to `[0, length]`. When `n <= 0`, this returns a copy of the - * full array. + * `n` is rounded down and clamped to `[0, length]`. `NaN` is treated as `0`. + * When `n <= 0`, this returns a copy of the full array. * * **Example** (Dropping from the start) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.drop([1, 2, 3, 4, 5], 2)) // [3, 4, 5] + * Array.drop([1, 2, 3, 4, 5], 2) // => [3, 4, 5] * ``` * * @see {@link dropRight} for removing a fixed number of elements from the end @@ -1510,7 +1519,7 @@ export const drop: { (self: Iterable, n: number): Array } = dual(2, (self: Iterable, n: number): Array => { const input = fromIterable(self) - return input.slice(clamp(n, input), input.length) + return input.slice(clampCount(n, input.length), input.length) }) /** @@ -1522,14 +1531,14 @@ export const drop: { * * **Details** * - * `n` is clamped to `[0, length]`. + * `n` is rounded down and clamped to `[0, length]`. `NaN` is treated as `0`. * * **Example** (Dropping from the end) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dropRight([1, 2, 3, 4, 5], 2)) // [1, 2, 3] + * Array.dropRight([1, 2, 3, 4, 5], 2) // => [1, 2, 3] * ``` * * @see {@link drop} — remove from the start @@ -1543,7 +1552,7 @@ export const dropRight: { (self: Iterable, n: number): Array } = dual(2, (self: Iterable, n: number): Array => { const input = fromIterable(self) - return input.slice(0, input.length - clamp(n, input)) + return input.slice(0, input.length - clampCount(n, input.length)) }) /** @@ -1559,10 +1568,10 @@ export const dropRight: { * * **Example** (Dropping while condition holds) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dropWhile([1, 2, 3, 4, 5], (x) => x < 4)) // [4, 5] + * Array.dropWhile([1, 2, 3, 4, 5], (x) => x < 4) // => [4, 5] * ``` * * @see {@link takeWhile} — keep the matching prefix instead @@ -1634,16 +1643,16 @@ export const dropWhileFilter: { * * **Example** (Finding an index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findFirstIndex([5, 3, 8, 9], (x) => x > 5)) // Option.some(2) + * Array.findFirstIndex([5, 3, 8, 9], (x) => x > 5) // => Option.some(2) * ``` * * @see {@link findLastIndex} — search from the end * @see {@link findFirst} — get the element itself * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirstIndex: { @@ -1670,16 +1679,16 @@ export const findFirstIndex: { * * **Example** (Finding the last matching index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findLastIndex([1, 3, 8, 9], (x) => x < 5)) // Option.some(1) + * Array.findLastIndex([1, 3, 8, 9], (x) => x < 5) // => Option.some(1) * ``` * * @see {@link findFirstIndex} — search from the start * @see {@link findLast} — get the element itself * - * @category elements + * @category searching * @since 2.0.0 */ export const findLastIndex: { @@ -1712,17 +1721,17 @@ export const findLastIndex: { * * **Example** (Finding the first match) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findFirst([1, 2, 3, 4, 5], (x) => x > 3)) // Option.some(4) + * Array.findFirst([1, 2, 3, 4, 5], (x) => x > 3) // => Option.some(4) * ``` * * @see {@link findLast} — search from the end * @see {@link findFirstIndex} — get the index instead * @see {@link findFirstWithIndex} — get both element and index * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -1750,16 +1759,16 @@ export const findFirst: { * * **Example** (Finding element with its index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findFirstWithIndex([1, 2, 3, 4, 5], (x) => x > 3)) // Option.some([4, 3]) + * Array.findFirstWithIndex([1, 2, 3, 4, 5], (x) => x > 3) // => Option.some([4, 3]) * ``` * * @see {@link findFirst} — get only the element * @see {@link findFirstIndex} — get only the index * - * @category elements + * @category searching * @since 3.17.0 */ export const findFirstWithIndex: { @@ -1808,16 +1817,16 @@ export const findFirstWithIndex: { * * **Example** (Finding the last match) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.findLast([1, 2, 3, 4, 5], (n) => n % 2 === 0)) // Option.some(4) + * Array.findLast([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => Option.some(4) * ``` * * @see {@link findFirst} — search from the start * @see {@link findLastIndex} — get the index instead * - * @category elements + * @category searching * @since 2.0.0 */ export const findLast: { @@ -1865,16 +1874,16 @@ export const findLast: { * * **Example** (Inserting at an index) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.insertAt(["a", "b", "c", "e"], 3, "d")) // Option.some(["a", "b", "c", "d", "e"]) + * Array.insertAt(["a", "b", "c", "e"], 3, "d") // => Option.some(["a", "b", "c", "d", "e"]) * ``` * * @see {@link replace} — replace an existing element * @see {@link modify} — transform an element at an index * - * @category elements + * @category transforming * @since 2.0.0 */ export const insertAt: { @@ -1882,10 +1891,11 @@ export const insertAt: { (self: Iterable, i: number, b: B): Option.Option> } = dual(3, (self: Iterable, i: number, b: B): Option.Option> => { const out: Array = Array.from(self) // copy because `splice` mutates the array - if (i < 0 || i > out.length) { + const index = Math.floor(i) + if (index !== out.length && isOutOfBounds(index, out)) { return Option.none() } - out.splice(i, 0, b) + out.splice(index, 0, b) return Option.some(out as any) }) @@ -1903,16 +1913,16 @@ export const insertAt: { * * **Example** (Replacing an element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" * - * console.log(Array.replace([1, 2, 3], 1, 4)) // Option.some([1, 4, 3]) + * Array.replace([1, 2, 3], 1, 4) // => Option.some([1, 4, 3]) * ``` * * @see {@link modify} — transform an element with a function * @see {@link insertAt} — insert without removing * - * @category elements + * @category transforming * @since 2.0.0 */ export const replace: { @@ -1944,18 +1954,21 @@ export const replace: { * * **Example** (Modifying an element) * - * ```ts - * import { Array } from "effect" + * ```ts import.meta.vitest + * import { Array, Option } from "effect" + * + * const values = [1, 2, 3, 4] + * const double = (n: number) => n * 2 * - * console.log(Array.modify([1, 2, 3, 4], 2, (n) => n * 2)) // Option.some([1, 2, 6, 4]) - * console.log(Array.modify([1, 2, 3, 4], 5, (n) => n * 2)) // Option.none() + * Array.modify(values, 2, double) // => Option.some([1, 2, 6, 4]) + * Array.modify(values, 5, double) // => Option.none() * ``` * * @see {@link replace} — set a fixed value at an index * @see {@link modifyHeadNonEmpty} — modify the first element * @see {@link modifyLastNonEmpty} — modify the last element * - * @category elements + * @category transforming * @since 2.0.0 */ export const modify: { @@ -1970,12 +1983,13 @@ export const modify: { ): Option.Option | B>> } = dual(3, (self: Iterable, i: number, f: (a: A) => B): Option.Option> => { const arr = Array.from(self) - if (isOutOfBounds(i, arr)) { + const index = Math.floor(i) + if (isOutOfBounds(index, arr)) { return Option.none() } const out: Array = arr - const b = f(arr[i]) - out[i] = b + const b = f(arr[index]) + out[index] = b return Option.some(out) }) @@ -1990,17 +2004,17 @@ export const modify: { * * **Example** (Removing an element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.remove([1, 2, 3, 4], 2)) // [1, 2, 4] - * console.log(Array.remove([1, 2, 3, 4], 5)) // [1, 2, 3, 4] + * Array.remove([1, 2, 3, 4], 2) // => [1, 2, 4] + * Array.remove([1, 2, 3, 4], 5) // => [1, 2, 3, 4] * ``` * * @see {@link insertAt} — insert an element * @see {@link filter} — remove elements by predicate * - * @category elements + * @category transforming * @since 2.0.0 */ export const remove: { @@ -2008,10 +2022,11 @@ export const remove: { (self: Iterable, i: number): Array } = dual(2, (self: Iterable, i: number): Array => { const out = Array.from(self) - if (isOutOfBounds(i, out)) { + const index = Math.floor(i) + if (isOutOfBounds(index, out)) { return out } - out.splice(i, 1) + out.splice(index, 1) return out }) @@ -2029,13 +2044,13 @@ export const remove: { * * **Example** (Reversing an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.reverse([1, 2, 3, 4])) // [4, 3, 2, 1] + * Array.reverse([1, 2, 3, 4]) // => [4, 3, 2, 1] * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const reverse = >( @@ -2057,10 +2072,10 @@ export const reverse = >( * * **Example** (Sorting numbers) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.sort([3, 1, 4, 1, 5], Order.Number)) // [1, 1, 3, 4, 5] + * Array.sort([3, 1, 4, 1, 5], Order.Number) // => [1, 1, 3, 4, 5] * ``` * * @see {@link sortWith} — sort by a mapping function @@ -2096,17 +2111,16 @@ export const sort: { * * **Example** (Sorting strings by length) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.sortWith(["aaa", "b", "cc"], (s) => s.length, Order.Number)) - * // ["b", "cc", "aaa"] + * Array.sortWith(["aaa", "b", "cc"], (s) => s.length, Order.Number) // => ["b", "cc", "aaa"] * ``` * * @see {@link sort} for sorting with an `Order` that compares the elements directly * @see {@link sortBy} for sorting with multiple `Order`s applied in sequence * - * @category elements + * @category sorting * @since 2.0.0 */ export const sortWith: { @@ -2138,7 +2152,7 @@ export const sortWith: { * * **Example** (Sorting by multiple keys) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order, pipe } from "effect" * * const users = [ @@ -2147,15 +2161,15 @@ export const sortWith: { * { name: "Charlie", age: 30 } * ] * - * const result = pipe( + * const sortedUsers = pipe( * users, * Array.sortBy( * Order.mapInput(Order.Number, (user: (typeof users)[number]) => user.age), * Order.mapInput(Order.String, (user: (typeof users)[number]) => user.name) * ) * ) - * console.log(result) - * // [{ name: "Bob", age: 25 }, { name: "Alice", age: 30 }, { name: "Charlie", age: 30 }] + * + * sortedUsers.map((user) => user.name).join(",") // => "Bob,Alice,Charlie" * ``` * * @see {@link sort} — sort by a single `Order` @@ -2193,10 +2207,10 @@ export const sortBy = >( * * **Example** (Zipping two arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.zip([1, 2, 3], ["a", "b"])) // [[1, "a"], [2, "b"]] + * Array.zip([1, 2, 3], ["a", "b"]) // => [[1, "a"], [2, "b"]] * ``` * * @see {@link zipWith} — zip with a combiner function @@ -2226,10 +2240,10 @@ export const zip: { * * **Example** (Zipping with addition) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b)) // [5, 7, 9] + * Array.zipWith([1, 2, 3], [4, 5, 6], (a, b) => a + b) // => [5, 7, 9] * ``` * * @see {@link zip} — zip into tuples @@ -2261,10 +2275,10 @@ export const zipWith: { * * **Example** (Unzipping pairs) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.unzip([[1, "a"], [2, "b"], [3, "c"]])) // [[1, 2, 3], ["a", "b", "c"]] + * Array.unzip([[1, "a"], [2, "b"], [3, "c"]]) // => [[1, 2, 3], ["a", "b", "c"]] * ``` * * @see {@link zip} — combine two arrays into pairs @@ -2304,15 +2318,15 @@ export const unzip: >( * * **Example** (Interspersing a separator) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.intersperse([1, 2, 3], 0)) // [1, 0, 2, 0, 3] + * Array.intersperse([1, 2, 3], 0) // => [1, 0, 2, 0, 3] * ``` * * @see {@link join} — intersperse and join into a string * - * @category elements + * @category transforming * @since 2.0.0 */ export const intersperse: { @@ -2347,16 +2361,16 @@ export const intersperse: { * * **Example** (Modifying the head) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.modifyHeadNonEmpty([1, 2, 3], (n) => n * 10)) // [10, 2, 3] + * Array.modifyHeadNonEmpty([1, 2, 3], (n) => n * 10) // => [10, 2, 3] * ``` * * @see {@link setHeadNonEmpty} — replace with a fixed value * @see {@link modifyLastNonEmpty} — modify the last element * - * @category elements + * @category transforming * @since 4.0.0 */ export const modifyHeadNonEmpty: { @@ -2380,16 +2394,16 @@ export const modifyHeadNonEmpty: { * * **Example** (Setting the head) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.setHeadNonEmpty([1, 2, 3], 10)) // [10, 2, 3] + * Array.setHeadNonEmpty([1, 2, 3], 10) // => [10, 2, 3] * ``` * * @see {@link modifyHeadNonEmpty} — transform the head with a function * @see {@link setLastNonEmpty} — replace the last element * - * @category elements + * @category transforming * @since 4.0.0 */ export const setHeadNonEmpty: { @@ -2411,16 +2425,16 @@ export const setHeadNonEmpty: { * * **Example** (Modifying the last element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.modifyLastNonEmpty([1, 2, 3], (n) => n * 2)) // [1, 2, 6] + * Array.modifyLastNonEmpty([1, 2, 3], (n) => n * 2) // => [1, 2, 6] * ``` * * @see {@link setLastNonEmpty} — replace with a fixed value * @see {@link modifyHeadNonEmpty} — modify the first element * - * @category elements + * @category transforming * @since 4.0.0 */ export const modifyLastNonEmpty: { @@ -2442,16 +2456,16 @@ export const modifyLastNonEmpty: { * * **Example** (Setting the last element) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.setLastNonEmpty([1, 2, 3], 4)) // [1, 2, 4] + * Array.setLastNonEmpty([1, 2, 3], 4) // => [1, 2, 4] * ``` * * @see {@link modifyLastNonEmpty} — transform the last element with a function * @see {@link setHeadNonEmpty} — replace the first element * - * @category elements + * @category transforming * @since 4.0.0 */ export const setLastNonEmpty: { @@ -2479,16 +2493,16 @@ export const setLastNonEmpty: { * * **Example** (Rotating elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.rotate(["a", "b", "c", "d"], 2)) // ["c", "d", "a", "b"] + * Array.rotate(["a", "b", "c", "d"], 2) // => ["c", "d", "a", "b"] * ``` * * @see {@link take} for taking a fixed number of elements from the start * @see {@link drop} for dropping a fixed number of elements from the start * - * @category elements + * @category transforming * @since 2.0.0 */ export const rotate: { @@ -2507,7 +2521,7 @@ export const rotate: { const [f, s] = splitAtNonEmpty(input, -m) return appendAll(s, f) } else { - return rotate(self, m - len) + return rotate(input, m - len) } } return [] @@ -2523,16 +2537,17 @@ export const rotate: { * * **Example** (Checking with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * * const containsNumber = Array.containsWith((a: number, b: number) => a === b) - * console.log(pipe([1, 2, 3, 4], containsNumber(3))) // true + * + * pipe([1, 2, 3, 4], containsNumber(3)) // => true * ``` * * @see {@link contains} for the `Equal.equivalence()` variant * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -2559,15 +2574,15 @@ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * console.log(pipe(["a", "b", "c", "d"], Array.contains("c"))) // true + * pipe(["a", "b", "c", "d"], Array.contains("c")) // => true * ``` * * @see {@link containsWith} — use custom equality * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -2591,20 +2606,16 @@ export const contains: { * * **Example** (Chopping an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.chop( - * [1, 2, 3, 4, 5], - * (as): [number, Array] => [as[0] * 2, as.slice(1)] - * ) - * console.log(result) // [2, 4, 6, 8, 10] + * Array.chop([1, 2, 3, 4, 5], (as): [number, Array] => [as[0] * 2, as.slice(1)]) // => [2, 4, 6, 8, 10] * ``` * * @see {@link chunksOf} — split into fixed-size chunks * @see {@link splitAt} — split at an index * - * @category elements + * @category splitting * @since 2.0.0 */ export const chop: { @@ -2647,15 +2658,15 @@ export const chop: { * * **Details** * - * `n` can be `0`, in which case all elements are placed in the second array. - * The index is floored to an integer. + * `n` is rounded down and clamped to `[0, length]`. `NaN` is treated as `0`, + * which places all elements in the second array. * * **Example** (Splitting at an index) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.splitAt([1, 2, 3, 4, 5], 3)) // [[1, 2, 3], [4, 5]] + * Array.splitAt([1, 2, 3, 4, 5], 3) // => [[1, 2, 3], [4, 5]] * ``` * * @see {@link splitAtNonEmpty} — for non-empty arrays @@ -2669,7 +2680,7 @@ export const splitAt: { (self: Iterable, n: number): [beforeIndex: Array, fromIndex: Array] } = dual(2, (self: Iterable, n: number): [Array, Array] => { const input = Array.from(self) - const _n = Math.floor(n) + const _n = Count.normalize(n) if (isReadonlyArrayNonEmpty(input)) { if (_n >= 1) { return splitAtNonEmpty(input, _n) @@ -2688,13 +2699,16 @@ export const splitAt: { * Use when downstream code requires the left side of the split to contain at * least one element. * + * **Details** + * + * `n` is rounded down and clamped to `[1, length]`. `NaN` is treated as `1`. + * * **Example** (Splitting a non-empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.splitAtNonEmpty(["a", "b", "c", "d", "e"], 3)) - * // [["a", "b", "c"], ["d", "e"]] + * Array.splitAtNonEmpty(["a", "b", "c", "d", "e"], 3) // => [["a", "b", "c"], ["d", "e"]] * ``` * * @see {@link splitAt} — for possibly-empty arrays @@ -2706,7 +2720,7 @@ export const splitAtNonEmpty: { (n: number): (self: NonEmptyReadonlyArray) => [beforeIndex: NonEmptyArray, fromIndex: Array] (self: NonEmptyReadonlyArray, n: number): [beforeIndex: NonEmptyArray, fromIndex: Array] } = dual(2, (self: NonEmptyReadonlyArray, n: number): [NonEmptyArray, Array] => { - const _n = Math.max(1, Math.floor(n)) + const _n = Count.normalizeNonEmpty(n) return _n >= self.length ? [copy(self), []] : [prepend(self.slice(1, _n), headNonEmpty(self)), self.slice(_n)] @@ -2721,14 +2735,15 @@ export const splitAtNonEmpty: { * * **Details** * - * Uses `chunksOf(ceil(length / n))` internally. The last chunk may be shorter. + * `n` is rounded down and normalized to at least `1`, with `NaN` treated as + * `1`. The last chunk may be shorter. * * **Example** (Splitting into groups) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.split([1, 2, 3, 4, 5, 6, 7, 8], 3)) // [[1, 2, 3], [4, 5, 6], [7, 8]] + * Array.split([1, 2, 3, 4, 5, 6, 7, 8], 3) // => [[1, 2, 3], [4, 5, 6], [7, 8]] * ``` * * @see {@link chunksOf} — split into fixed-size chunks @@ -2741,7 +2756,7 @@ export const split: { (self: Iterable, n: number): Array> } = dual(2, (self: Iterable, n: number) => { const input = fromIterable(self) - return chunksOf(input, Math.ceil(input.length / Math.floor(n))) + return chunksOf(input, Math.ceil(input.length / Count.normalizeNonEmpty(n))) }) /** @@ -2755,10 +2770,10 @@ export const split: { * * **Example** (Splitting at a condition) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.splitWhere([1, 2, 3, 4, 5], (n) => n > 3)) // [[1, 2, 3], [4, 5]] + * Array.splitWhere([1, 2, 3, 4, 5], (n) => n > 3) // => [[1, 2, 3], [4, 5]] * ``` * * @see {@link span} — splits at the first element that fails the predicate @@ -2793,18 +2808,19 @@ export const splitWhere: { * * **Example** (Copying an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const original = [1, 2, 3] * const copied = Array.copy(original) - * console.log(copied) // [1, 2, 3] - * console.log(original === copied) // false + * + * copied // => [1, 2, 3] + * original === copied // => false * ``` * * @see {@link fromIterable} — returns the same reference for arrays * - * @category elements + * @category transforming * @since 2.0.0 */ export const copy: { @@ -2822,20 +2838,21 @@ export const copy: { * * **Details** * - * Returns an empty array when `n <= 0`. + * `n` is rounded down. `NaN` and non-positive values are treated as `0`, which + * returns an empty array. * * **Example** (Padding an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.pad([1, 2, 3], 6, 0)) // [1, 2, 3, 0, 0, 0] + * Array.pad([1, 2, 3], 6, 0) // => [1, 2, 3, 0, 0, 0] * ``` * * @see {@link take} — truncate without padding * @see {@link replicate} — create an array of a single repeated value * - * @category elements + * @category transforming * @since 3.8.4 */ export const pad: { @@ -2847,12 +2864,13 @@ export const pad: { ) => Array (self: Array, n: number, fill: T): Array } = dual(3, (self: Array, n: number, fill: T): Array => { - if (self.length >= n) { - return take(self, n) + const length = Count.normalize(n) + if (self.length >= length) { + return take(self, length) } return appendAll( self, - makeBy(n - self.length, () => fill) + makeBy(length - self.length, () => fill) ) }) @@ -2867,15 +2885,17 @@ export const pad: { * * **Details** * - * `chunksOf(n)([])` is `[]`, not `[[]]`. Each chunk is a `NonEmptyArray`, and - * the outer return type preserves `NonEmptyArray`. + * `n` is rounded down and normalized to at least `1`; `NaN` and non-positive + * values therefore produce singleton chunks. `chunksOf(n)([])` is `[]`, not + * `[[]]`. Each chunk is a `NonEmptyArray`, and the outer return type preserves + * `NonEmptyArray`. * * **Example** (Chunking an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.chunksOf([1, 2, 3, 4, 5], 2)) // [[1, 2], [3, 4], [5]] + * Array.chunksOf([1, 2, 3, 4, 5], 2) // => [[1, 2], [3, 4], [5]] * ``` * * @see {@link split} — split into a given number of groups @@ -2909,16 +2929,19 @@ export const chunksOf: { * * **Details** * - * Returns an empty array if `n <= 0` or the array has fewer than `n` elements. - * Each window is a tuple of exactly `n` elements. + * `n` is rounded down, with `NaN` and non-positive values treated as `0`. + * Returns an empty array if the normalized size is `0` or exceeds the array + * length. Each window is a tuple of exactly the normalized size. * * **Example** (Creating sliding windows) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.window([1, 2, 3, 4, 5], 3)) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]] - * console.log(Array.window([1, 2, 3, 4, 5], 6)) // [] + * const values = [1, 2, 3, 4, 5] + * + * Array.window(values, 3) // => [[1, 2, 3], [2, 3, 4], [3, 4, 5]] + * Array.window(values, 6) // => [] * ``` * * @see {@link chunksOf} — non-overlapping chunks @@ -2931,10 +2954,11 @@ export const window: { (self: Iterable, n: N): Array> } = dual(2, (self: Iterable, n: number): Array> => { const input = fromIterable(self) - if (n > 0 && isReadonlyArrayNonEmpty(input)) { + const size = Count.normalize(n) + if (size > 0 && size <= input.length && isReadonlyArrayNonEmpty(input)) { return Array.from( - { length: input.length - (n - 1) }, - (_, index) => input.slice(index, index + n) + { length: input.length - (size - 1) }, + (_, index) => input.slice(index, index + size) ) } return [] @@ -2955,11 +2979,13 @@ export const window: { * * **Example** (Grouping consecutive equal elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.groupWith(["a", "a", "b", "b", "b", "c", "a"], (x, y) => x === y)) - * // [["a", "a"], ["b", "b", "b"], ["c"], ["a"]] + * Array.groupWith( + * ["a", "a", "b", "b", "b", "c", "a"], + * (x, y) => x === y + * ) // => [["a", "a"], ["b", "b", "b"], ["c"], ["a"]] * ``` * * @see {@link group} for grouping adjacent elements with `Equal.equivalence()` @@ -3004,10 +3030,10 @@ export const groupWith: { * * **Example** (Grouping adjacent equal elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.group([1, 1, 2, 2, 2, 3, 1])) // [[1, 1], [2, 2, 2], [3], [1]] + * Array.group([1, 1, 2, 2, 2, 3, 1]) // => [[1, 1], [2, 2, 2], [3], [1]] * ``` * * @see {@link groupWith} — use custom equality @@ -3033,9 +3059,16 @@ export const group: (self: NonEmptyReadonlyArray) => NonEmptyArray(self: NonEmptyReadonlyArray) => NonEmptyArray person.group) - * console.log(result) - * // { A: [{ name: "Alice", group: "A" }, { name: "Charlie", group: "A" }], B: [{ name: "Bob", group: "B" }] } + * Object.keys(Array.groupBy(people, (person) => person.group)).join(",") // => "A,B" * ``` * * @see {@link group} — group adjacent equal elements @@ -3058,27 +3089,67 @@ export const group: (self: NonEmptyReadonlyArray) => NonEmptyArray( f: (a: A) => K - ): (self: Iterable) => Record, NonEmptyArray> + ): (self: Iterable) => Record.ReadonlyRecord.GroupByResult> ( self: Iterable, f: (a: A) => K - ): Record, NonEmptyArray> + ): Record.ReadonlyRecord.GroupByResult> } = dual(2, ( self: Iterable, f: (a: A) => K -): Record, NonEmptyArray> => { +): Record.ReadonlyRecord.GroupByResult> => { const out: Record> = {} for (const a of self) { const k = f(a) if (Object.hasOwn(out, k)) { out[k].push(a) } else { - out[k] = [a] + InternalRecord.assignProperty(out, k, [a]) } } return out }) +type HashBuckets = Map> + +const hashBucketsAdd = (buckets: HashBuckets, value: unknown): boolean => { + const hash = Hash.hash(value) + const bucket = buckets.get(hash) + if (bucket === undefined) { + buckets.set(hash, [value]) + return true + } + // Hash collisions still require an Effect equality check. + for (const previous of bucket) { + if (Equal.equals(previous, value)) { + return false + } + } + bucket.push(value) + return true +} + +const makeHashBuckets = (values: Iterable): HashBuckets => { + const buckets: HashBuckets = new Map() + for (const value of values) { + hashBucketsAdd(buckets, value) + } + return buckets +} + +const hashBucketsHas = (buckets: HashBuckets, value: unknown): boolean => { + const bucket = buckets.get(Hash.hash(value)) + if (bucket === undefined) { + return false + } + for (const candidate of bucket) { + if (Equal.equals(candidate, value)) { + return true + } + } + return false +} + /** * Computes the union of two arrays using a custom equivalence, removing * duplicates. @@ -3090,17 +3161,17 @@ export const groupBy: { * * **Example** (Computing unions with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.unionWith([1, 2], [2, 3], (a, b) => a === b)) // [1, 2, 3] + * Array.unionWith([1, 2], [2, 3], (a, b) => a === b) // => [1, 2, 3] * ``` * * @see {@link union} for the `Equal.equivalence()` variant * @see {@link intersectionWith} for keeping elements present in both arrays * @see {@link differenceWith} for keeping elements present only in the first array * - * @category elements + * @category set operations * @since 2.0.0 */ export const unionWith: { @@ -3138,17 +3209,17 @@ export const unionWith: { * * **Example** (Computing array unions) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.union([1, 2], [2, 3])) // [1, 2, 3] + * Array.union([1, 2], [2, 3]) // => [1, 2, 3] * ``` * * @see {@link unionWith} — use custom equality * @see {@link intersection} — elements in both arrays * @see {@link difference} — elements only in the first array * - * @category elements + * @category set operations * @since 2.0.0 */ export const union: { @@ -3162,7 +3233,14 @@ export const union: { (self: Iterable, that: Iterable): Array } = dual( 2, - (self: Iterable, that: Iterable): Array => unionWith(self, that, Equal.asEquivalence()) + (self: Iterable, that: Iterable): Array => { + const a = fromIterable(self) + const b = fromIterable(that) + if (isReadonlyArrayNonEmpty(a)) { + return isReadonlyArrayNonEmpty(b) ? dedupe(appendAll(a, b)) : a + } + return b + } ) /** @@ -3176,20 +3254,21 @@ export const union: { * * **Example** (Computing intersections with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const array1 = [{ id: 1 }, { id: 2 }, { id: 3 }] * const array2 = [{ id: 3 }, { id: 4 }, { id: 1 }] * const isEquivalent = (a: { id: number }, b: { id: number }) => a.id === b.id - * console.log(Array.intersectionWith(isEquivalent)(array2)(array1)) // [{ id: 1 }, { id: 3 }] + * + * Array.intersectionWith(isEquivalent)(array2)(array1) // => [{ id: 1 }, { id: 3 }] * ``` * * @see {@link intersection} for the `Equal.equivalence()` variant * @see {@link unionWith} for keeping values from either array with custom equality * @see {@link differenceWith} for keeping values only from the first array with custom equality * - * @category elements + * @category set operations * @since 2.0.0 */ export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -3217,23 +3296,31 @@ export const intersectionWith = (isEquivalent: (self: A, that: A) => boolean) * * **Example** (Computing array intersections) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.intersection([1, 2, 3], [3, 4, 1])) // [1, 3] + * Array.intersection([1, 2, 3], [3, 4, 1]) // => [1, 3] * ``` * * @see {@link intersectionWith} — use custom equality * @see {@link union} — elements in either array * @see {@link difference} — elements only in the first array * - * @category elements + * @category set operations * @since 2.0.0 */ export const intersection: { (that: Iterable): (self: Iterable) => Array (self: Iterable, that: Iterable): Array -} = intersectionWith(Equal.asEquivalence()) +} = dual(2, (self: Iterable, that: Iterable): Array => { + const thatArray = fromIterable(that) + const selfArray = fromIterable(self) + if (selfArray.length === 0 || thatArray.length === 0) { + return [] + } + const buckets = makeHashBuckets(thatArray) + return selfArray.filter((value): value is A & B => hashBucketsHas(buckets, value)) +}) /** * Computes elements in the first array that are not in the second, using a @@ -3246,18 +3333,17 @@ export const intersection: { * * **Example** (Computing differences with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const diff = Array.differenceWith((a, b) => a === b)([1, 2, 3], [2, 3, 4]) - * console.log(diff) // [1] + * Array.differenceWith((a, b) => a === b)([1, 2, 3], [2, 3, 4]) // => [1] * ``` * * @see {@link difference} for the `Equal.equivalence()` variant * @see {@link unionWith} for keeping values from either array with custom equality * @see {@link intersectionWith} for keeping values present in both arrays with custom equality * - * @category elements + * @category set operations * @since 2.0.0 */ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -3285,23 +3371,34 @@ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): * * **Example** (Computing array differences) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.difference([1, 2, 3], [2, 3, 4])) // [1] + * Array.difference([1, 2, 3], [2, 3, 4]) // => [1] * ``` * * @see {@link differenceWith} — use custom equality * @see {@link union} — elements in either array * @see {@link intersection} — elements in both arrays * - * @category elements + * @category set operations * @since 2.0.0 */ export const difference: { (that: Iterable): (self: Iterable) => Array (self: Iterable, that: Iterable): Array -} = differenceWith(Equal.asEquivalence()) +} = dual(2, (self: Iterable, that: Iterable): Array => { + const thatArray = fromIterable(that) + const selfArray = fromIterable(self) + if (selfArray.length === 0) { + return [] + } + if (thatArray.length === 0) { + return selfArray.filter(() => true) + } + const buckets = makeHashBuckets(thatArray) + return selfArray.filter((value) => !hashBucketsHas(buckets, value)) +}) /** * Creates an empty array. @@ -3312,11 +3409,10 @@ export const difference: { * * **Example** (Creating an empty array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.empty() - * console.log(result) // [] + * Array.empty() // => [] * ``` * * @see {@link of} — create a single-element array @@ -3332,10 +3428,10 @@ export const empty: () => Array = () => [] * * **Example** (Creating a single-element array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.of(1)) // [1] + * Array.of(1) // => [1] * ``` * * @see {@link make} — create from multiple values @@ -3358,14 +3454,14 @@ export declare namespace ReadonlyArray { * * **Example** (Inferring an element type) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type StringArrayType = Array.ReadonlyArray.Infer> * // StringArrayType is string * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Infer> = S extends ReadonlyArray ? A @@ -3377,14 +3473,14 @@ export declare namespace ReadonlyArray { * * **Example** (Preserving non-emptiness) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Result = Array.ReadonlyArray.With * // Result is NonEmptyArray * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type With, A> = S extends NonEmptyReadonlyArray ? NonEmptyArray @@ -3395,7 +3491,7 @@ export declare namespace ReadonlyArray { * * **Example** (Preserving non-emptiness from either input) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Result = Array.ReadonlyArray.OrNonEmpty< @@ -3406,7 +3502,7 @@ export declare namespace ReadonlyArray { * // Result is NonEmptyArray * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type OrNonEmpty< @@ -3422,7 +3518,7 @@ export declare namespace ReadonlyArray { * * **Example** (Preserving non-emptiness from both inputs) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Result = Array.ReadonlyArray.AndNonEmpty< @@ -3433,7 +3529,7 @@ export declare namespace ReadonlyArray { * // Result is NonEmptyArray * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type AndNonEmpty< @@ -3449,7 +3545,7 @@ export declare namespace ReadonlyArray { * * **Example** (Flattening nested array types) * - * ```ts + * ```ts import.meta.vitest * import type { Array } from "effect" * * type Nested = ReadonlyArray> @@ -3457,7 +3553,7 @@ export declare namespace ReadonlyArray { * // Flattened is Array * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Flatten>> = T extends @@ -3479,10 +3575,10 @@ export declare namespace ReadonlyArray { * * **Example** (Doubling values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.map([1, 2, 3], (x) => x * 2)) // [2, 4, 6] + * Array.map([1, 2, 3], (x) => x * 2) // => [2, 4, 6] * ``` * * @see {@link flatMap} — map and flatten @@ -3512,10 +3608,10 @@ export const map: { * * **Example** (Flat mapping an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.flatMap([1, 2, 3], (x) => [x, x * 2])) // [1, 2, 2, 4, 3, 6] + * Array.flatMap([1, 2, 3], (x) => [x, x * 2]) // => [1, 2, 2, 4, 3, 6] * ``` * * @see {@link map} — transform without flattening @@ -3557,10 +3653,10 @@ export const flatMap: { * * **Example** (Flattening nested arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.flatten([[1, 2], [], [3, 4], [], [5, 6]])) // [1, 2, 3, 4, 5, 6] + * Array.flatten([[1, 2], [], [3, 4], [], [5, 6]]) // => [1, 2, 3, 4, 5, 6] * ``` * * @see {@link flatMap} — map then flatten in one step @@ -3581,10 +3677,10 @@ export const flatten: >>(self: * * **Example** (Extracting Some values) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * - * console.log(Array.getSomes([Option.some(1), Option.none(), Option.some(2)])) // [1, 2] + * Array.getSomes([Option.some(1), Option.none(), Option.some(2)]) // => [1, 2] * ``` * * @see {@link fromOption} — convert a single Option @@ -3617,11 +3713,10 @@ export const getSomes: >, X = any>( * * **Example** (Extracting failures) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.getFailures([Result.succeed(1), Result.fail("err"), Result.succeed(2)])) - * // ["err"] + * Array.getFailures([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) // => ["err"] * ``` * * @see {@link getSuccesses} — extract success values @@ -3654,11 +3749,10 @@ export const getFailures = >>( * * **Example** (Extracting successes) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.getSuccesses([Result.succeed(1), Result.fail("err"), Result.succeed(2)])) - * // [1, 2] + * Array.getSuccesses([Result.succeed(1), Result.fail("err"), Result.succeed(2)]) // => [1, 2] * ``` * * @see {@link getFailures} — extract failure values @@ -3694,11 +3788,10 @@ export const getSuccesses = >>( * * **Example** (Filtering and transforming) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.filterMap([1, 2, 3, 4], (n) => n % 2 === 0 ? Result.succeed(n * 10) : Result.failVoid)) - * // [20, 40] + * Array.filterMap([1, 2, 3, 4], (n) => n % 2 === 0 ? Result.succeed(n * 10) : Result.failVoid) // => [20, 40] * ``` * * @see {@link filter} — keep original elements matching a predicate @@ -3737,10 +3830,10 @@ export const filterMap: { * * **Example** (Filtering even numbers) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.filter([1, 2, 3, 4], (x) => x % 2 === 0)) // [2, 4] + * Array.filter([1, 2, 3, 4], (x) => x % 2 === 0) // => [2, 4] * ``` * * @see {@link partition} — split into matching and non-matching @@ -3782,13 +3875,12 @@ export const filter: { * * **Example** (Partitioning with a filter) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * console.log(Array.partition([1, -2, 3], (n, i) => + * Array.partition([1, -2, 3], (n, i) => * n > 0 ? Result.succeed(n + i) : Result.fail(`negative:${n}`) - * )) - * // [["negative:-2"], [1, 5]] + * ) // => [["negative:-2"], [1, 5]] * ``` * * @see {@link filter} — keep only matching elements @@ -3841,14 +3933,10 @@ export const partition: { * * **Example** (Separating Results) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * - * const [failures, successes] = Array.separate([ - * Result.succeed(1), Result.fail("error"), Result.succeed(2) - * ]) - * console.log(failures) // ["error"] - * console.log(successes) // [1, 2] + * Array.separate([Result.succeed(1), Result.fail("error"), Result.succeed(2)]) // => [["error"], [1, 2]] * ``` * * @see {@link getFailures} — extract only failures @@ -3878,10 +3966,10 @@ export const separate: >>( * * **Example** (Summing an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.reduce([1, 2, 3], 0, (acc, n) => acc + n)) // 6 + * Array.reduce([1, 2, 3], 0, (acc, n) => acc + n) // => 6 * ``` * * @see {@link reduceRight} — fold from right to left @@ -3912,10 +4000,10 @@ export const reduce: { * * **Example** (Folding from right to left) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.reduceRight([1, 2, 3], 0, (acc, n) => acc + n)) // 6 + * Array.reduceRight([1, 2, 3], 0, (acc, n) => acc + n) // => 6 * ``` * * @see {@link reduce} — fold from left to right @@ -3939,13 +4027,13 @@ export const reduceRight: { * * **Example** (Wrapping values conditionally) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const isEven = (n: number) => n % 2 === 0 - * const to = Array.liftPredicate(isEven) - * console.log(to(1)) // [] - * console.log(to(2)) // [2] + * const fromEven = Array.liftPredicate((n: number) => n % 2 === 0) + * + * fromEven(1) // => [] + * fromEven(2) // => [2] * ``` * * @see {@link liftOption} — lift an Option-returning function @@ -3969,15 +4057,16 @@ export const liftPredicate: { // Note: I intentionally avoid using the NoInfer p * * **Example** (Lifting an Option function) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * * const parseNumber = Array.liftOption((s: string) => { * const n = Number(s) * return isNaN(n) ? Option.none() : Option.some(n) * }) - * console.log(parseNumber("123")) // [123] - * console.log(parseNumber("abc")) // [] + * + * parseNumber("123") // => [123] + * parseNumber("abc") // => [] * ``` * * @see {@link liftPredicate} — lift a boolean predicate @@ -4001,12 +4090,12 @@ export const liftOption = , B>( * * **Example** (Converting nullable values to an array) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.fromNullishOr(1)) // [1] - * console.log(Array.fromNullishOr(null)) // [] - * console.log(Array.fromNullishOr(undefined)) // [] + * Array.fromNullishOr(1) // => [1] + * Array.fromNullishOr(null) // => [] + * Array.fromNullishOr(undefined) // => [] * ``` * * @see {@link liftNullishOr} — lift a nullable-returning function @@ -4023,15 +4112,16 @@ export const fromNullishOr = (a: A): Array> => a == null ? emp * * **Example** (Lifting a nullable function) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const parseNumber = Array.liftNullishOr((s: string) => { * const n = Number(s) * return isNaN(n) ? null : n * }) - * console.log(parseNumber("123")) // [123] - * console.log(parseNumber("abc")) // [] + * + * parseNumber("123") // => [123] + * parseNumber("abc") // => [] * ``` * * @see {@link fromNullishOr} — convert a single nullable value @@ -4056,11 +4146,10 @@ export const liftNullishOr = , B>( * * **Example** (Flat mapping with nullable values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.flatMapNullishOr([1, 2, 3], (n) => (n % 2 === 0 ? null : n))) - * // [1, 3] + * Array.flatMapNullishOr([1, 2, 3], (n) => (n % 2 === 0 ? null : n)) // => [1, 3] * ``` * * @see {@link flatMap} for mapping each element to an array and flattening @@ -4088,7 +4177,7 @@ export const flatMapNullishOr: { * * **Example** (Lifting a Result function) * - * ```ts + * ```ts import.meta.vitest * import { Array, Result } from "effect" * * const parseNumber = (s: string): Result.Result => @@ -4097,8 +4186,9 @@ export const flatMapNullishOr: { * : Result.succeed(Number(s)) * * const liftedParseNumber = Array.liftResult(parseNumber) - * console.log(liftedParseNumber("42")) // [42] - * console.log(liftedParseNumber("not a number")) // [] + * + * liftedParseNumber("42") // => [42] + * liftedParseNumber("not a number") // => [] * ``` * * @see {@link liftOption} — lift an Option-returning function @@ -4126,16 +4216,16 @@ export const liftResult = , E, B>( * * **Example** (Testing all elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.every([2, 4, 6], (x) => x % 2 === 0)) // true - * console.log(Array.every([2, 3, 6], (x) => x % 2 === 0)) // false + * Array.every([2, 4, 6], (x) => x % 2 === 0) // => true + * Array.every([2, 3, 6], (x) => x % 2 === 0) // => false * ``` * * @see {@link some} — test if any element matches * - * @category elements + * @category guards * @since 2.0.0 */ export const every: { @@ -4157,17 +4247,17 @@ export const every: { * * **Example** (Testing for any match) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.some([1, 3, 4], (x) => x % 2 === 0)) // true - * console.log(Array.some([1, 3, 5], (x) => x % 2 === 0)) // false + * Array.some([1, 3, 4], (x) => x % 2 === 0) // => true + * Array.some([1, 3, 5], (x) => x % 2 === 0) // => false * ``` * * @see {@link every} — test if all elements match * @see {@link contains} — test for a specific value * - * @category elements + * @category guards * @since 2.0.0 */ export const some: { @@ -4196,10 +4286,10 @@ export const some: { * * **Example** (Computing suffix lengths) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.extend([1, 2, 3], (as) => as.length)) // [3, 2, 1] + * Array.extend([1, 2, 3], (as) => as.length) // => [3, 2, 1] * ``` * * @see {@link scan} for keeping intermediate accumulator values during a fold @@ -4221,16 +4311,16 @@ export const extend: { * * **Example** (Finding the minimum) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.min([3, 1, 2], Order.Number)) // 1 + * Array.min([3, 1, 2], Order.Number) // => 1 * ``` * * @see {@link max} — find the maximum * @see {@link sort} — sort the entire array * - * @category elements + * @category getters * @since 2.0.0 */ export const min: { @@ -4244,16 +4334,16 @@ export const min: { * * **Example** (Finding the maximum) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * - * console.log(Array.max([3, 1, 2], Order.Number)) // 3 + * Array.max([3, 1, 2], Order.Number) // => 3 * ``` * * @see {@link min} — find the minimum * @see {@link sort} — sort the entire array * - * @category elements + * @category getters * @since 2.0.0 */ export const max: { @@ -4268,11 +4358,10 @@ export const max: { * * **Example** (Generating a sequence) * - * ```ts + * ```ts import.meta.vitest * import { Array, Option } from "effect" * - * console.log(Array.unfold(1, (n) => n <= 5 ? Option.some([n, n + 1]) : Option.none())) - * // [1, 2, 3, 4, 5] + * Array.unfold(1, (n) => n <= 5 ? Option.some([n, n + 1]) : Option.none()) // => [1, 2, 3, 4, 5] * ``` * * @see {@link makeBy} — generate from index @@ -4303,11 +4392,12 @@ export const unfold = (b: B, f: (b: B) => Option.Option): * * **Example** (Comparing arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array, Order } from "effect" * * const arrayOrder = Array.makeOrder(Order.Number) - * console.log(arrayOrder([1, 2], [1, 3])) // -1 + * + * arrayOrder([1, 2], [1, 3]) // => -1 * ``` * * @see {@link makeEquivalence} — create an equivalence for arrays @@ -4324,11 +4414,12 @@ export const makeOrder: (O: Order.Order) => Order.Order> * * **Example** (Comparing arrays for equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * * const eq = Array.makeEquivalence((a, b) => a === b) - * console.log(eq([1, 2, 3], [1, 2, 3])) // true + * + * eq([1, 2, 3], [1, 2, 3]) // => true * ``` * * @see {@link makeOrder} — create an ordering for arrays @@ -4350,15 +4441,18 @@ export const makeEquivalence: ( * * **Example** (Iterating with side-effects) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * Array.forEach([1, 2, 3], (n) => console.log(n)) // 1, 2, 3 + * const visited: Array = [] + * Array.forEach([1, 2, 3], (n) => visited.push(n)) + * + * visited // => [1, 2, 3] * ``` * * @see {@link map} for transforming each element into a new array * - * @category elements + * @category traversing * @since 2.0.0 */ export const forEach: { @@ -4377,16 +4471,16 @@ export const forEach: { * * **Example** (Deduplicating with custom equality) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupeWith([1, 2, 2, 3, 3, 3], (a, b) => a === b)) // [1, 2, 3] + * Array.dedupeWith([1, 2, 2, 3, 3, 3], (a, b) => a === b) // => [1, 2, 3] * ``` * * @see {@link dedupe} — uses default equality * @see {@link dedupeAdjacentWith} — only dedupes consecutive elements * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupeWith: { @@ -4424,22 +4518,34 @@ export const dedupeWith: { * * **Example** (Removing duplicates) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupe([1, 2, 1, 3, 2, 4])) // [1, 2, 3, 4] + * Array.dedupe([1, 2, 1, 3, 2, 4]) // => [1, 2, 3, 4] * ``` * * @see {@link dedupeWith} — use custom equality * @see {@link dedupeAdjacent} — only dedupes consecutive elements * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupe = >( self: S -): S extends NonEmptyReadonlyArray ? NonEmptyArray : S extends Iterable ? Array : never => - dedupeWith(self, Equal.asEquivalence()) as any +): S extends NonEmptyReadonlyArray ? NonEmptyArray : S extends Iterable ? Array : never => { + const input = fromIterable(self) + if (input.length < 2) { + return [...input] as any + } + const buckets: HashBuckets = new Map() + const out: Array = [] + for (const value of input) { + if (hashBucketsAdd(buckets, value)) { + out.push(value) + } + } + return out as any +} /** * Removes consecutive duplicate elements using a custom equivalence. @@ -4456,17 +4562,16 @@ export const dedupe = >( * * **Example** (Deduplicating adjacent elements) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupeAdjacentWith([1, 1, 2, 2, 3, 3], (a, b) => a === b)) - * // [1, 2, 3] + * Array.dedupeAdjacentWith([1, 1, 2, 2, 3, 3], (a, b) => a === b) // => [1, 2, 3] * ``` * * @see {@link dedupeAdjacent} — uses default equality * @see {@link dedupeWith} — dedupes all duplicates, not just adjacent * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupeAdjacentWith: { @@ -4494,16 +4599,16 @@ export const dedupeAdjacentWith: { * * **Example** (Removing adjacent duplicates) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.dedupeAdjacent([1, 1, 2, 2, 3, 3])) // [1, 2, 3] + * Array.dedupeAdjacent([1, 1, 2, 2, 3, 3]) // => [1, 2, 3] * ``` * * @see {@link dedupeAdjacentWith} — use custom equality * @see {@link dedupe} — remove all duplicates * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacentWith(Equal.asEquivalence()) @@ -4513,10 +4618,10 @@ export const dedupeAdjacent: (self: Iterable) => Array = dedupeAdjacent * * **Example** (Joining strings) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * console.log(Array.join(["a", "b", "c"], "-")) // "a-b-c" + * Array.join(["a", "b", "c"], "-") // => "a-b-c" * ``` * * @see {@link intersperse} — insert separator elements without joining @@ -4546,11 +4651,10 @@ export const join: { * * **Example** (Running sum alongside mapped values) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.mapAccum([1, 2, 3], 0, (acc, n) => [acc + n, acc + n]) - * console.log(result) // [6, [1, 3, 6]] + * Array.mapAccum([1, 2, 3], 0, (acc, n) => [acc + n, acc + n]) // => [6, [1, 3, 6]] * ``` * * @see {@link scan} — when you only need the accumulated results (not the final state) @@ -4601,16 +4705,15 @@ export const mapAccum: { * * **Example** (Combining numbers and letters) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.cartesianWith([1, 2], ["a", "b"], (a, b) => `${a}-${b}`) - * console.log(result) // ["1-a", "1-b", "2-a", "2-b"] + * Array.cartesianWith([1, 2], ["a", "b"], (a, b) => `${a}-${b}`) // => ["1-a", "1-b", "2-a", "2-b"] * ``` * * @see {@link cartesian} for returning tuples instead of applying a combiner * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesianWith: { @@ -4636,16 +4739,15 @@ export const cartesianWith: { * * **Example** (Generating all pairs from two arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.cartesian([1, 2], ["a", "b"]) - * console.log(result) // [[1, "a"], [1, "b"], [2, "a"], [2, "b"]] + * Array.cartesian([1, 2], ["a", "b"]) // => [[1, "a"], [1, "b"], [2, "a"], [2, "b"]] * ``` * * @see {@link cartesianWith} — apply a combiner to each pair * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesian: { @@ -4676,24 +4778,23 @@ export const cartesian: { * * **Example** (Building array comprehensions with do notation) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * const result = pipe( + * pipe( * Array.Do, * Array.bind("x", () => [1, 3, 5]), * Array.bind("y", () => [2, 4, 6]), * Array.filter(({ x, y }) => x < y), * Array.map(({ x, y }) => [x, y] as const) - * ) - * console.log(result) // [[1, 2], [1, 4], [1, 6], [3, 4], [3, 6], [5, 6]] + * ) // => [[1, 2], [1, 4], [1, 6], [3, 4], [3, 6], [5, 6]] * ``` * * @see {@link bind} — introduce an array variable into the scope * @see {@link bindTo} — start a pipeline by naming the first array * @see {@link let_ let} — introduce a plain computed value * - * @category do notation + * @category constructors * @since 3.2.0 */ export const Do: ReadonlyArray<{}> = of({}) @@ -4714,23 +4815,21 @@ export const Do: ReadonlyArray<{}> = of({}) * * **Example** (Binding two arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * const result = pipe( + * pipe( * Array.Do, * Array.bind("x", () => [1, 2]), * Array.bind("y", () => ["a", "b"]) - * ) - * console.log(result) - * // [{ x: 1, y: "a" }, { x: 1, y: "b" }, { x: 2, y: "a" }, { x: 2, y: "b" }] + * ) // => [{ x: 1, y: "a" }, { x: 1, y: "b" }, { x: 2, y: "a" }, { x: 2, y: "b" }] * ``` * * @see {@link Do} — start a do-notation pipeline * @see {@link bindTo} — name the first array in a pipeline * @see {@link let_ let} — add a plain computed value * - * @category do notation + * @category sequencing * @since 3.2.0 */ export const bind: { @@ -4762,20 +4861,16 @@ export const bind: { * * **Example** (Naming an existing array) * - * ```ts - * import { Array, pipe } from "effect" + * ```ts import.meta.vitest + * import { Array } from "effect" * - * const result = pipe( - * [1, 2, 3], - * Array.bindTo("x") - * ) - * console.log(result) // [{ x: 1 }, { x: 2 }, { x: 3 }] + * Array.bindTo([1, 2, 3], "x") // => [{ x: 1 }, { x: 2 }, { x: 3 }] * ``` * * @see {@link Do} — start with an empty scope * @see {@link bind} — add another array variable to the scope * - * @category do notation + * @category mapping * @since 3.2.0 */ export const bindTo: { @@ -4812,22 +4907,20 @@ export { * * **Example** (Adding a computed value) * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * - * const result = pipe( + * pipe( * Array.Do, * Array.bind("x", () => [1, 2, 3]), * Array.let("doubled", ({ x }) => x * 2) - * ) - * console.log(result) - * // [{ x: 1, doubled: 2 }, { x: 2, doubled: 4 }, { x: 3, doubled: 6 }] + * ) // => [{ x: 1, doubled: 2 }, { x: 2, doubled: 4 }, { x: 3, doubled: 6 }] * ``` * * @see {@link Do} — start a do-notation pipeline * @see {@link bind} — introduce an array variable (produces cartesian product) * - * @category do notation + * @category mapping * @since 3.2.0 */ let_ as let @@ -4874,11 +4967,10 @@ export function makeReducerConcat(): Reducer.Reducer> { * * **Example** (Counting even numbers) * - * ```ts + * ```ts import.meta.vitest * import { Array } from "effect" * - * const result = Array.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) - * console.log(result) // 2 + * Array.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => 2 * ``` * * @see {@link filter} — when you need the matching elements, not just the count diff --git a/packages/effect/src/BigDecimal.ts b/packages/effect/src/BigDecimal.ts index 8ca0678d9..f7d660475 100644 --- a/packages/effect/src/BigDecimal.ts +++ b/packages/effect/src/BigDecimal.ts @@ -34,13 +34,13 @@ const TypeId = "~effect/BigDecimal" * * **Example** (Inspecting BigDecimal storage) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const d = BigDecimal.fromStringUnsafe("123.45") * - * console.log(d.value) // 12345n - * console.log(d.scale) // 2 + * d.value // => 12345n + * d.scale // => 2 * ``` * * @category models @@ -90,13 +90,14 @@ const BigDecimalProto: Omit = { * * **Example** (Checking BigDecimal values) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const decimal = BigDecimal.fromNumber(123.45) - * console.log(BigDecimal.isBigDecimal(decimal)) // true - * console.log(BigDecimal.isBigDecimal(123.45)) // false - * console.log(BigDecimal.isBigDecimal("123.45")) // false + * BigDecimal.isBigDecimal(decimal) // => false + * BigDecimal.isBigDecimal(BigDecimal.fromStringUnsafe("123.45")) // => true + * BigDecimal.isBigDecimal(123.45) // => false + * BigDecimal.isBigDecimal("123.45") // => false * ``` * * @category guards @@ -105,25 +106,29 @@ const BigDecimalProto: Omit = { export const isBigDecimal = (u: unknown): u is BigDecimal => hasProperty(u, TypeId) /** - * Creates a `BigDecimal` from a `bigint` value and a scale. + * Creates a `BigDecimal` from a `bigint` value and a safe integer scale. * * **When to use** * * Use to construct a decimal directly from its unscaled integer value and * decimal scale. * + * **Gotchas** + * + * Throws a `RangeError` if `scale` is not a safe integer. + * * **Example** (Creating decimals from bigint and scale) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * // Create 123.45 (12345 with scale 2) * const decimal = BigDecimal.make(12345n, 2) - * console.log(BigDecimal.format(decimal)) // "123.45" + * decimal // => BigDecimal.fromStringUnsafe("123.45") * * // Create 42 (42 with scale 0) * const integer = BigDecimal.make(42n, 0) - * console.log(BigDecimal.format(integer)) // "42" + * integer // => BigDecimal.fromBigInt(42n) * ``` * * @see {@link fromBigInt} for constructing an integer decimal from a `bigint` @@ -132,6 +137,9 @@ export const isBigDecimal = (u: unknown): u is BigDecimal => hasProperty(u, Type * @since 2.0.0 */ export const make = (value: bigint, scale: number): BigDecimal => { + if (!Number.isSafeInteger(scale)) { + throw new RangeError(`Scale must be a safe integer, got ${scale}`) + } const o = Object.create(BigDecimalProto) o.value = value o.scale = scale @@ -173,18 +181,14 @@ const one = makeNormalizedUnsafe(bigint1, 0) * * **Example** (Normalizing trailing zeros) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.normalize(BigDecimal.fromStringUnsafe("123.00000")), - * BigDecimal.normalize(BigDecimal.make(123n, 0)) - * ) - * assert.deepStrictEqual( - * BigDecimal.normalize(BigDecimal.fromStringUnsafe("12300000")), - * BigDecimal.normalize(BigDecimal.make(123n, -5)) - * ) + * + * const decimal = BigDecimal.normalize(BigDecimal.fromStringUnsafe("123.00000")) + * const decimalStorage = [decimal.value, decimal.scale] // => [123n, 0] + * + * const largeDecimal = BigDecimal.normalize(BigDecimal.fromStringUnsafe("12300000")) + * const largeDecimalStorage = [largeDecimal.value, largeDecimal.scale] // => [123n, -5] * ``` * * @see {@link format} for rendering normalized decimals as strings @@ -236,18 +240,18 @@ export const normalize = (self: BigDecimal): BigDecimal => { * * **Example** (Scaling decimal precision) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const decimal = BigDecimal.fromNumberUnsafe(123.45) * * // Increase scale (add more precision) * const scaled = BigDecimal.scale(decimal, 4) - * console.log(BigDecimal.format(scaled)) // "123.4500" + * const scaledStorage = [scaled.value, scaled.scale] // => [1234500n, 4] * - * // Decrease scale (reduce precision, rounds down) + * // Decrease scale (reduce precision, truncating toward zero) * const reduced = BigDecimal.scale(decimal, 1) - * console.log(BigDecimal.format(reduced)) // "123.4" + * reduced // => BigDecimal.fromStringUnsafe("123.4") * ``` * * @see {@link round} for changing scale with configurable rounding @@ -280,14 +284,13 @@ export const scale: { * * **Example** (Adding decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.sum(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("5") - * ) + * const result = BigDecimal.sum( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(5n) * ``` * * @see {@link sumAll} for summing an iterable of `BigDecimal` values @@ -328,14 +331,14 @@ export const sum: { * * **Example** (Adding multiple decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.sumAll([BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("4")]), - * BigDecimal.fromStringUnsafe("9") - * ) + * const result = BigDecimal.sumAll([ + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("4") + * ]) // => BigDecimal.fromBigInt(9n) * ``` * * @see {@link sum} for adding two `BigDecimal` values @@ -360,14 +363,13 @@ export const sumAll = (collection: Iterable): BigDecimal => { * * **Example** (Multiplying decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.multiply(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("6") - * ) + * const result = BigDecimal.multiply( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(6n) * ``` * * @see {@link multiplyAll} for multiplying an iterable of `BigDecimal` values @@ -395,14 +397,14 @@ export const multiply: { * * **Example** (Multiplying multiple decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.multiplyAll([BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("4")]), - * BigDecimal.fromStringUnsafe("24") - * ) + * const result = BigDecimal.multiplyAll([ + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("4") + * ]) // => BigDecimal.fromBigInt(24n) * ``` * * @see {@link multiply} for multiplying two `BigDecimal` values @@ -430,14 +432,13 @@ export const multiplyAll = (collection: Iterable): BigDecimal => { * * **Example** (Subtracting decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.subtract(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("-1") - * ) + * const result = BigDecimal.subtract( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(-1n) * ``` * * @category math @@ -550,33 +551,14 @@ export const roundTerminal = (n: bigint): bigint => { * * **Example** (Dividing decimals safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" * - * console.log( - * Option.getOrThrow( - * BigDecimal.divide( - * BigDecimal.fromStringUnsafe("6"), - * BigDecimal.fromStringUnsafe("3") - * ) - * ) - * ) // BigDecimal(2) - * console.log( - * Option.getOrThrow( - * BigDecimal.divide( - * BigDecimal.fromStringUnsafe("6"), - * BigDecimal.fromStringUnsafe("4") - * ) - * ) - * ) // BigDecimal(1.5) - * console.log( - * Option.isNone( - * BigDecimal.divide( - * BigDecimal.fromStringUnsafe("6"), - * BigDecimal.fromStringUnsafe("0") - * ) - * ) - * ) // true + * const six = BigDecimal.fromBigInt(6n) + * + * BigDecimal.divide(six, BigDecimal.fromBigInt(3n)) // => Option.some(BigDecimal.fromBigInt(2n)) + * BigDecimal.divide(six, BigDecimal.fromBigInt(4n)) // => Option.some(BigDecimal.fromStringUnsafe("1.5")) + * BigDecimal.divide(six, BigDecimal.fromBigInt(0n)) // => Option.none() * ``` * * @see {@link divideUnsafe} for division that throws when the divisor is zero @@ -624,11 +606,11 @@ export const divide: { * * **Example** (Dividing decimals unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * - * console.log(BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("3"))) // BigDecimal(2) - * console.log(BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("4"))) // BigDecimal(1.5) + * BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(2n) + * BigDecimal.divideUnsafe(BigDecimal.fromStringUnsafe("6"), BigDecimal.fromStringUnsafe("4")) // => BigDecimal.fromStringUnsafe("1.5") * ``` * * @see {@link divide} for division that returns `Option.none` when the divisor is zero @@ -655,6 +637,46 @@ export const divideUnsafe: { return divideWithPrecision(self.value, that.value, scale, DEFAULT_PRECISION) }) +const MAX_COMPARISON_SCALE_ALIGNMENT = 100 +const comparisonPowersOfTen: Array = [bigint1] + +const compareBigInt = (self: bigint, that: bigint): Ordering => self === that ? 0 : self < that ? -1 : 1 + +const compareMagnitude = (self: BigDecimal, that: BigDecimal): Ordering => { + const selfDigits = `${self.value < bigint0 ? -self.value : self.value}` + const thatDigits = `${that.value < bigint0 ? -that.value : that.value}` + const exponentDifference = BigInt(selfDigits.length - thatDigits.length) - BigInt(self.scale) + BigInt(that.scale) + if (exponentDifference !== bigint0) return exponentDifference < bigint0 ? -1 : 1 + + const length = Math.max(selfDigits.length, thatDigits.length) + for (let i = 0; i < length; i++) { + const selfDigit = i < selfDigits.length ? selfDigits.charCodeAt(i) : 48 + const thatDigit = i < thatDigits.length ? thatDigits.charCodeAt(i) : 48 + if (selfDigit !== thatDigit) return selfDigit < thatDigit ? -1 : 1 + } + return 0 +} + +const compare = (self: BigDecimal, that: BigDecimal): Ordering => { + if (self.scale === that.scale) return compareBigInt(self.value, that.value) + + const selfSign = sign(self) + const thatSign = sign(that) + if (selfSign !== thatSign) return selfSign < thatSign ? -1 : 1 + if (selfSign === 0) return 0 + + const scaleDifference = self.scale - that.scale + const absoluteScaleDifference = Math.abs(scaleDifference) + if (absoluteScaleDifference > MAX_COMPARISON_SCALE_ALIGNMENT) { + return selfSign === -1 ? compareMagnitude(that, self) : compareMagnitude(self, that) + } + + const powerOfTen = comparisonPowersOfTen[absoluteScaleDifference] ??= bigint10 ** BigInt(absoluteScaleDifference) + return scaleDifference > 0 + ? compareBigInt(self.value, that.value * powerOfTen) + : compareBigInt(self.value * powerOfTen, that.value) +} + /** * Provides an `Order` instance for `BigDecimal` that allows comparing and sorting BigDecimal values. * @@ -665,37 +687,22 @@ export const divideUnsafe: { * * **Example** (Comparing decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const a = BigDecimal.fromNumberUnsafe(1.5) * const b = BigDecimal.fromNumberUnsafe(2.3) * const c = BigDecimal.fromNumberUnsafe(1.5) * - * console.log(BigDecimal.Order(a, b)) // -1 (a < b) - * console.log(BigDecimal.Order(b, a)) // 1 (b > a) - * console.log(BigDecimal.Order(a, c)) // 0 (a === c) + * BigDecimal.Order(a, b) // => -1 + * BigDecimal.Order(b, a) // => 1 + * BigDecimal.Order(a, c) // => 0 * ``` * * @category instances * @since 2.0.0 */ -export const Order: order.Order = order.make((self, that) => { - const scmp = order.Number(sign(self), sign(that)) - if (scmp !== 0) { - return scmp - } - - if (self.scale > that.scale) { - return order.BigInt(self.value, scale(that, self.scale).value) - } - - if (self.scale < that.scale) { - return order.BigInt(scale(self, that.scale).value, that.value) - } - - return order.BigInt(self.value, that.value) -}) +export const Order: order.Order = order.make(compare) /** * Returns `true` if the first argument is less than the second, otherwise `false`. @@ -706,22 +713,16 @@ export const Order: order.Order = order.make((self, that) => { * * **Example** (Checking less-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isLessThan(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThan(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThan(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * false - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isLessThan(two, three) // => true + * BigDecimal.isLessThan(three, three) // => false + * BigDecimal.isLessThan(four, three) // => false * ``` * * @category predicates @@ -741,22 +742,16 @@ export const isLessThan: { * * **Example** (Checking less-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isLessThanOrEqualTo(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThanOrEqualTo(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isLessThanOrEqualTo(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * false - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isLessThanOrEqualTo(two, three) // => true + * BigDecimal.isLessThanOrEqualTo(three, three) // => true + * BigDecimal.isLessThanOrEqualTo(four, three) // => false * ``` * * @category predicates @@ -776,22 +771,16 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking greater-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isGreaterThan(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThan(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThan(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * true - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isGreaterThan(two, three) // => false + * BigDecimal.isGreaterThan(three, three) // => false + * BigDecimal.isGreaterThan(four, three) // => true * ``` * * @category predicates @@ -811,22 +800,16 @@ export const isGreaterThan: { * * **Example** (Checking greater-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.isGreaterThanOrEqualTo(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * false - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThanOrEqualTo(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("3")), - * true - * ) - * assert.deepStrictEqual( - * BigDecimal.isGreaterThanOrEqualTo(BigDecimal.fromStringUnsafe("4"), BigDecimal.fromStringUnsafe("3")), - * true - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const four = BigDecimal.fromStringUnsafe("4") + * + * BigDecimal.isGreaterThanOrEqualTo(two, three) // => false + * BigDecimal.isGreaterThanOrEqualTo(three, three) // => true + * BigDecimal.isGreaterThanOrEqualTo(four, three) // => true * ``` * * @category predicates @@ -846,18 +829,17 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking decimal ranges) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * * const between = BigDecimal.between({ * minimum: BigDecimal.fromStringUnsafe("1"), * maximum: BigDecimal.fromStringUnsafe("5") * }) * - * assert.deepStrictEqual(between(BigDecimal.fromStringUnsafe("3")), true) - * assert.deepStrictEqual(between(BigDecimal.fromStringUnsafe("0")), false) - * assert.deepStrictEqual(between(BigDecimal.fromStringUnsafe("6")), false) + * between(BigDecimal.fromStringUnsafe("3")) // => true + * between(BigDecimal.fromStringUnsafe("0")) // => false + * between(BigDecimal.fromStringUnsafe("6")) // => false * ``` * * @see {@link clamp} for forcing a `BigDecimal` into an inclusive range @@ -891,27 +873,17 @@ export const between: { * * **Example** (Clamping decimals to a range) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * * const clamp = BigDecimal.clamp({ * minimum: BigDecimal.fromStringUnsafe("1"), * maximum: BigDecimal.fromStringUnsafe("5") * }) * - * assert.deepStrictEqual( - * clamp(BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("3") - * ) - * assert.deepStrictEqual( - * clamp(BigDecimal.fromStringUnsafe("0")), - * BigDecimal.fromStringUnsafe("1") - * ) - * assert.deepStrictEqual( - * clamp(BigDecimal.fromStringUnsafe("6")), - * BigDecimal.fromStringUnsafe("5") - * ) + * clamp(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(3n) + * clamp(BigDecimal.fromStringUnsafe("0")) // => BigDecimal.fromBigInt(1n) + * clamp(BigDecimal.fromStringUnsafe("6")) // => BigDecimal.fromBigInt(5n) * ``` * * @see {@link between} for checking whether a `BigDecimal` is already inside a range @@ -939,14 +911,13 @@ export const clamp: { * * **Example** (Selecting the smaller decimal) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.min(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), - * BigDecimal.fromStringUnsafe("2") - * ) + * const result = BigDecimal.min( + * BigDecimal.fromStringUnsafe("2"), + * BigDecimal.fromStringUnsafe("3") + * ) // => BigDecimal.fromBigInt(2n) * ``` * * @see {@link max} for selecting the larger value @@ -968,14 +939,13 @@ export const min: { * * **Example** (Selecting the larger decimal) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.max(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("3")), + * const result = BigDecimal.max( + * BigDecimal.fromStringUnsafe("2"), * BigDecimal.fromStringUnsafe("3") - * ) + * ) // => BigDecimal.fromBigInt(3n) * ``` * * @see {@link min} for selecting the smaller value @@ -997,13 +967,12 @@ export const max: { * * **Example** (Reading decimal signs) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.sign(BigDecimal.fromStringUnsafe("-5")), -1) - * assert.deepStrictEqual(BigDecimal.sign(BigDecimal.fromStringUnsafe("0")), 0) - * assert.deepStrictEqual(BigDecimal.sign(BigDecimal.fromStringUnsafe("5")), 1) + * BigDecimal.sign(BigDecimal.fromStringUnsafe("-5")) // => -1 + * BigDecimal.sign(BigDecimal.fromStringUnsafe("0")) // => 0 + * BigDecimal.sign(BigDecimal.fromStringUnsafe("5")) // => 1 * ``` * * @category math @@ -1020,13 +989,12 @@ export const sign = (n: BigDecimal): Ordering => n.value === bigint0 ? 0 : n.val * * **Example** (Calculating absolute values) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.abs(BigDecimal.fromStringUnsafe("-5")), BigDecimal.fromStringUnsafe("5")) - * assert.deepStrictEqual(BigDecimal.abs(BigDecimal.fromStringUnsafe("0")), BigDecimal.fromStringUnsafe("0")) - * assert.deepStrictEqual(BigDecimal.abs(BigDecimal.fromStringUnsafe("5")), BigDecimal.fromStringUnsafe("5")) + * BigDecimal.abs(BigDecimal.fromStringUnsafe("-5")) // => BigDecimal.fromBigInt(5n) + * BigDecimal.abs(BigDecimal.fromStringUnsafe("0")) // => BigDecimal.fromBigInt(0n) + * BigDecimal.abs(BigDecimal.fromStringUnsafe("5")) // => BigDecimal.fromBigInt(5n) * ``` * * @category math @@ -1043,12 +1011,11 @@ export const abs = (n: BigDecimal): BigDecimal => n.value < bigint0 ? make(-n.va * * **Example** (Negating decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.negate(BigDecimal.fromStringUnsafe("3")), BigDecimal.fromStringUnsafe("-3")) - * assert.deepStrictEqual(BigDecimal.negate(BigDecimal.fromStringUnsafe("-6")), BigDecimal.fromStringUnsafe("6")) + * BigDecimal.negate(BigDecimal.fromStringUnsafe("3")) // => BigDecimal.fromBigInt(-3n) + * BigDecimal.negate(BigDecimal.fromStringUnsafe("-6")) // => BigDecimal.fromBigInt(6n) * ``` * * @category math @@ -1071,31 +1038,15 @@ export const negate = (n: BigDecimal): BigDecimal => make(-n.value, n.scale) * * **Example** (Computing remainders safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.remainder( - * BigDecimal.fromStringUnsafe("2"), - * BigDecimal.fromStringUnsafe("2") - * ), - * Option.some(BigDecimal.fromStringUnsafe("0")) - * ) - * assert.deepStrictEqual( - * BigDecimal.remainder( - * BigDecimal.fromStringUnsafe("3"), - * BigDecimal.fromStringUnsafe("2") - * ), - * Option.some(BigDecimal.fromStringUnsafe("1")) - * ) - * assert.deepStrictEqual( - * BigDecimal.remainder( - * BigDecimal.fromStringUnsafe("-4"), - * BigDecimal.fromStringUnsafe("2") - * ), - * Option.some(BigDecimal.fromStringUnsafe("0")) - * ) + * + * const two = BigDecimal.fromStringUnsafe("2") + * const three = BigDecimal.fromStringUnsafe("3") + * const zero = BigDecimal.fromStringUnsafe("0") + * + * BigDecimal.remainder(three, two) // => Option.some(BigDecimal.fromBigInt(1n)) + * BigDecimal.remainder(two, zero) // => Option.none() * ``` * * @see {@link remainderUnsafe} for remainder calculation that throws when the divisor is zero @@ -1131,22 +1082,13 @@ export const remainder: { * * **Example** (Computing remainders unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.remainderUnsafe(BigDecimal.fromStringUnsafe("2"), BigDecimal.fromStringUnsafe("2")), - * BigDecimal.fromStringUnsafe("0") - * ) - * assert.deepStrictEqual( - * BigDecimal.remainderUnsafe(BigDecimal.fromStringUnsafe("3"), BigDecimal.fromStringUnsafe("2")), - * BigDecimal.fromStringUnsafe("1") - * ) - * assert.deepStrictEqual( - * BigDecimal.remainderUnsafe(BigDecimal.fromStringUnsafe("-4"), BigDecimal.fromStringUnsafe("2")), - * BigDecimal.fromStringUnsafe("0") - * ) + * + * BigDecimal.remainderUnsafe( + * BigDecimal.fromStringUnsafe("3"), + * BigDecimal.fromStringUnsafe("2") + * ) // => BigDecimal.fromBigInt(1n) * ``` * * @see {@link remainder} for returning `Option.none` when the divisor is zero @@ -1176,31 +1118,21 @@ export const remainderUnsafe: { * * **Example** (Checking decimal equivalence) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const a = BigDecimal.fromStringUnsafe("1.50") * const b = BigDecimal.fromStringUnsafe("1.5") * const c = BigDecimal.fromStringUnsafe("2.0") * - * console.log(BigDecimal.Equivalence(a, b)) // true (1.50 === 1.5) - * console.log(BigDecimal.Equivalence(a, c)) // false (1.50 !== 2.0) + * BigDecimal.Equivalence(a, b) // => true + * BigDecimal.Equivalence(a, c) // => false * ``` * * @category instances * @since 2.0.0 */ -export const Equivalence: Equ.Equivalence = Equ.make((self, that) => { - if (self.scale > that.scale) { - return scale(that, self.scale).value === self.value - } - - if (self.scale < that.scale) { - return scale(self, that.scale).value === that.value - } - - return self.value === that.value -}) +export const Equivalence: Equ.Equivalence = Equ.make((self, that) => compare(self, that) === 0) /** * Checks whether two `BigDecimal`s are equal. @@ -1211,15 +1143,15 @@ export const Equivalence: Equ.Equivalence = Equ.make((self, that) => * * **Example** (Checking decimal equality) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const a = BigDecimal.fromStringUnsafe("1.5") * const b = BigDecimal.fromStringUnsafe("1.50") * const c = BigDecimal.fromStringUnsafe("2.0") * - * console.log(BigDecimal.equals(a, b)) // true - * console.log(BigDecimal.equals(a, c)) // false + * BigDecimal.equals(a, b) // => true + * BigDecimal.equals(a, c) // => false * ``` * * @see {@link Equivalence} for passing decimal equality to APIs that require an `Equivalence` @@ -1241,14 +1173,14 @@ export const equals: { * * **Example** (Creating decimals from bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * * const decimal = BigDecimal.fromBigInt(123n) - * console.log(BigDecimal.format(decimal)) // "123" + * decimal // => BigDecimal.fromStringUnsafe("123") * * const largeBigInt = BigDecimal.fromBigInt(9007199254740991n) - * console.log(BigDecimal.format(largeBigInt)) // "9007199254740991" + * largeBigInt // => BigDecimal.fromStringUnsafe("9007199254740991") * ``` * * @see {@link make} for constructing a decimal with an explicit scale @@ -1274,12 +1206,11 @@ export const fromBigInt = (n: bigint): BigDecimal => make(n, 0) * * **Example** (Creating decimals from finite numbers) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.fromNumberUnsafe(123), BigDecimal.make(123n, 0)) - * assert.deepStrictEqual(BigDecimal.fromNumberUnsafe(123.456), BigDecimal.make(123456n, 3)) + * BigDecimal.fromNumberUnsafe(123) // => BigDecimal.fromBigInt(123n) + * BigDecimal.fromNumberUnsafe(123.456) // => BigDecimal.fromStringUnsafe("123.456") * ``` * * @see {@link fromNumber} for returning `Option.none` when the number is not finite @@ -1310,16 +1241,11 @@ export const fromNumberUnsafe = (n: number): BigDecimal => { * * **Example** (Creating decimals from numbers safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual(BigDecimal.fromNumber(123), Option.some(BigDecimal.make(123n, 0))) - * assert.deepStrictEqual( - * BigDecimal.fromNumber(123.456), - * Option.some(BigDecimal.make(123456n, 3)) - * ) - * assert.deepStrictEqual(BigDecimal.fromNumber(Infinity), Option.none()) + * + * BigDecimal.fromNumber(123.456) // => Option.some(BigDecimal.fromStringUnsafe("123.456")) + * BigDecimal.fromNumber(Infinity) // => Option.none() * ``` * * @see {@link fromNumberUnsafe} for throwing when the number is not finite @@ -1357,16 +1283,11 @@ export const fromNumber = (n: number): Option.Option => { * * **Example** (Parsing decimal strings safely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal, Option } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual(BigDecimal.fromString("123"), Option.some(BigDecimal.make(123n, 0))) - * assert.deepStrictEqual( - * BigDecimal.fromString("123.456"), - * Option.some(BigDecimal.make(123456n, 3)) - * ) - * assert.deepStrictEqual(BigDecimal.fromString("123.abc"), Option.none()) + * + * BigDecimal.fromString("123.456") // => Option.some(BigDecimal.make(123456n, 3)) + * BigDecimal.fromString("123.abc") // => Option.none() * ``` * * @see {@link fromStringUnsafe} for parsing that throws on invalid input @@ -1435,13 +1356,11 @@ export const fromString = (s: string): Option.Option => { * * **Example** (Parsing decimal strings unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.fromStringUnsafe("123"), BigDecimal.make(123n, 0)) - * assert.deepStrictEqual(BigDecimal.fromStringUnsafe("123.456"), BigDecimal.make(123456n, 3)) - * assert.throws(() => BigDecimal.fromStringUnsafe("123.abc")) + * BigDecimal.fromStringUnsafe("123") // => BigDecimal.fromBigInt(123n) + * BigDecimal.fromStringUnsafe("123.456") // => BigDecimal.make(123456n, 3) * ``` * * @see {@link fromString} for returning `Option.none` on invalid input @@ -1468,13 +1387,12 @@ export const fromStringUnsafe = (s: string): BigDecimal => { * * **Example** (Formatting decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.format(BigDecimal.fromStringUnsafe("-5")), "-5") - * assert.deepStrictEqual(BigDecimal.format(BigDecimal.fromStringUnsafe("123.456")), "123.456") - * assert.deepStrictEqual(BigDecimal.format(BigDecimal.fromStringUnsafe("-0.00000123")), "-0.00000123") + * BigDecimal.format(BigDecimal.fromStringUnsafe("-5")) // => "-5" + * BigDecimal.format(BigDecimal.fromStringUnsafe("123.456")) // => "123.456" + * BigDecimal.format(BigDecimal.fromStringUnsafe("-0.00000123")) // => "-0.00000123" * ``` * * @see {@link toExponential} for always rendering scientific notation @@ -1522,11 +1440,10 @@ export const format = (n: BigDecimal): string => { * * **Example** (Formatting decimals exponentially) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.toExponential(BigDecimal.make(123456n, -5)), "1.23456e+10") + * BigDecimal.toExponential(BigDecimal.make(123456n, -5)) // => "1.23456e+10" * ``` * * @see {@link format} for plain decimal formatting when possible @@ -1569,11 +1486,10 @@ export const toExponential = (n: BigDecimal): string => { * * **Example** (Converting decimals to numbers) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.toNumberUnsafe(BigDecimal.fromStringUnsafe("123.456")), 123.456) + * BigDecimal.toNumberUnsafe(BigDecimal.fromStringUnsafe("123.456")) // => 123.456 * ``` * * @see {@link format} for preserving decimal precision as text @@ -1592,13 +1508,12 @@ export const toNumberUnsafe = (n: BigDecimal): number => Number(format(n)) * * **Example** (Checking integer decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isInteger(BigDecimal.fromStringUnsafe("0")), true) - * assert.deepStrictEqual(BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1")), true) - * assert.deepStrictEqual(BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1.1")), false) + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("0")) // => true + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1")) // => true + * BigDecimal.isInteger(BigDecimal.fromStringUnsafe("1.1")) // => false * ``` * * @category predicates @@ -1615,12 +1530,11 @@ export const isInteger = (n: BigDecimal): boolean => normalize(n).scale <= 0 * * **Example** (Checking zero decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isZero(BigDecimal.fromStringUnsafe("0")), true) - * assert.deepStrictEqual(BigDecimal.isZero(BigDecimal.fromStringUnsafe("1")), false) + * BigDecimal.isZero(BigDecimal.fromStringUnsafe("0")) // => true + * BigDecimal.isZero(BigDecimal.fromStringUnsafe("1")) // => false * ``` * * @category predicates @@ -1637,13 +1551,12 @@ export const isZero = (n: BigDecimal): boolean => n.value === bigint0 * * **Example** (Checking negative decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isNegative(BigDecimal.fromStringUnsafe("-1")), true) - * assert.deepStrictEqual(BigDecimal.isNegative(BigDecimal.fromStringUnsafe("0")), false) - * assert.deepStrictEqual(BigDecimal.isNegative(BigDecimal.fromStringUnsafe("1")), false) + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("-1")) // => true + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("0")) // => false + * BigDecimal.isNegative(BigDecimal.fromStringUnsafe("1")) // => false * ``` * * @category predicates @@ -1660,13 +1573,12 @@ export const isNegative = (n: BigDecimal): boolean => n.value < bigint0 * * **Example** (Checking positive decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigDecimal.isPositive(BigDecimal.fromStringUnsafe("-1")), false) - * assert.deepStrictEqual(BigDecimal.isPositive(BigDecimal.fromStringUnsafe("0")), false) - * assert.deepStrictEqual(BigDecimal.isPositive(BigDecimal.fromStringUnsafe("1")), true) + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("-1")) // => false + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("0")) // => false + * BigDecimal.isPositive(BigDecimal.fromStringUnsafe("1")) // => true * ``` * * @category predicates @@ -1726,18 +1638,14 @@ export type RoundingMode = * * **Example** (Rounding decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.round(BigDecimal.fromStringUnsafe("145"), { mode: "from-zero", scale: -1 }), - * BigDecimal.fromStringUnsafe("150") - * ) - * assert.deepStrictEqual( - * BigDecimal.round(BigDecimal.fromStringUnsafe("-14.5")), - * BigDecimal.fromStringUnsafe("-15") - * ) + * + * const positive = BigDecimal.round(BigDecimal.fromStringUnsafe("145"), { mode: "from-zero", scale: -1 }) + * positive // => BigDecimal.fromBigInt(150n) + * + * const negative = BigDecimal.round(BigDecimal.fromStringUnsafe("-14.5")) + * negative // => BigDecimal.fromBigInt(-15n) * ``` * * @see {@link ceil} for fixed rounding toward positive infinity @@ -1808,11 +1716,11 @@ export const round: { * * **Example** (Truncating decimals) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" * - * console.log(BigDecimal.truncate(BigDecimal.fromStringUnsafe("145"), -1)) // BigDecimal(140) - * console.log(BigDecimal.truncate(BigDecimal.fromStringUnsafe("-14.5"))) // BigDecimal(-14) + * BigDecimal.truncate(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(140n) + * BigDecimal.truncate(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n) * ``` * * @see {@link round} for configurable rounding modes @@ -1853,15 +1761,11 @@ export const truncate: { * * **Example** (Rounding decimals up) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * BigDecimal.ceil(BigDecimal.fromStringUnsafe("145"), -1), - * BigDecimal.fromStringUnsafe("150") - * ) - * assert.deepStrictEqual(BigDecimal.ceil(BigDecimal.fromStringUnsafe("-14.5")), BigDecimal.fromStringUnsafe("-14")) + * BigDecimal.ceil(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(150n) + * BigDecimal.ceil(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-14n) * ``` * * @category math @@ -1908,18 +1812,11 @@ export const digitAt: { * * **Example** (Rounding decimals down) * - * ```ts + * ```ts import.meta.vitest * import { BigDecimal } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * BigDecimal.floor(BigDecimal.fromStringUnsafe("145"), -1), - * BigDecimal.fromStringUnsafe("140") - * ) - * assert.deepStrictEqual( - * BigDecimal.floor(BigDecimal.fromStringUnsafe("-14.5")), - * BigDecimal.fromStringUnsafe("-15") - * ) + * + * BigDecimal.floor(BigDecimal.fromStringUnsafe("145"), -1) // => BigDecimal.fromBigInt(140n) + * BigDecimal.floor(BigDecimal.fromStringUnsafe("-14.5")) // => BigDecimal.fromBigInt(-15n) * ``` * * @see {@link ceil} for rounding toward positive infinity diff --git a/packages/effect/src/BigInt.ts b/packages/effect/src/BigInt.ts index 34a973647..18ac6ba68 100644 --- a/packages/effect/src/BigInt.ts +++ b/packages/effect/src/BigInt.ts @@ -36,14 +36,11 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Constructing bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * - * const bigInt = BigInt.BigInt(123) - * console.log(bigInt) // 123n - * - * const fromString = BigInt.BigInt("456") - * console.log(fromString) // 456n + * BigInt.BigInt(123) // => 123n + * BigInt.BigInt("456") // => 456n * ``` * * @category constructors @@ -64,12 +61,11 @@ const bigint2 = BigInt(2) * * **Example** (Checking for bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isBigInt(1n), true) - * assert.deepStrictEqual(BigInt.isBigInt(1), false) + * BigInt.isBigInt(1n) // => true + * BigInt.isBigInt(1) // => false * ``` * * @category guards @@ -87,11 +83,10 @@ export const isBigInt: (u: unknown) => u is bigint = predicate.isBigInt * * **Example** (Adding bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sum(2n, 3n), 5n) + * BigInt.sum(2n, 3n) // => 5n * ``` * * @see {@link sumAll} for summing an iterable of `bigint` values @@ -113,11 +108,10 @@ export const sum: { * * **Example** (Multiplying bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.multiply(2n, 3n), 6n) + * BigInt.multiply(2n, 3n) // => 6n * ``` * * @see {@link multiplyAll} for multiplying an iterable of `bigint` values @@ -139,11 +133,10 @@ export const multiply: { * * **Example** (Subtracting bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.subtract(2n, 3n), -1n) + * BigInt.subtract(2n, 3n) // => -1n * ``` * * @category math @@ -169,12 +162,11 @@ export const subtract: { * * **Example** (Dividing bigints safely) * - * ```ts + * ```ts import.meta.vitest * import { BigInt, Option } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.divide(6n, 3n), Option.some(2n)) - * assert.deepStrictEqual(BigInt.divide(6n, 0n), Option.none()) + * BigInt.divide(6n, 3n) // => Option.some(2n) + * BigInt.divide(6n, 0n) // => Option.none() * ``` * * @see {@link divideUnsafe} for division that throws when the divisor is `0n` @@ -210,12 +202,11 @@ export const divide: { * * **Example** (Dividing bigints unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.divideUnsafe(6n, 3n), 2n) - * assert.deepStrictEqual(BigInt.divideUnsafe(6n, 4n), 1n) + * BigInt.divideUnsafe(6n, 3n) // => 2n + * BigInt.divideUnsafe(6n, 4n) // => 1n * ``` * * @see {@link divide} for division that returns `Option.none` when the divisor is `0n` @@ -237,11 +228,10 @@ export const divideUnsafe: { * * **Example** (Incrementing a bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.increment(2n), 3n) + * BigInt.increment(2n) // => 3n * ``` * * @category math @@ -258,11 +248,10 @@ export const increment = (n: bigint): bigint => n + bigint1 * * **Example** (Decrementing a bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.decrement(3n), 2n) + * BigInt.decrement(3n) // => 2n * ``` * * @category math @@ -280,16 +269,16 @@ export const decrement = (n: bigint): bigint => n - bigint1 * * **Example** (Comparing bigints with Order) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * * const a = 123n * const b = 456n * const c = 123n * - * console.log(BigInt.Order(a, b)) // -1 (a < b) - * console.log(BigInt.Order(b, a)) // 1 (b > a) - * console.log(BigInt.Order(a, c)) // 0 (a === c) + * BigInt.Order(a, b) // => -1 + * BigInt.Order(b, a) // => 1 + * BigInt.Order(a, c) // => 0 * ``` * * @category instances @@ -307,11 +296,11 @@ export const Order: order.Order = order.BigInt * * **Example** (Comparing bigints for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * - * console.log(BigInt.Equivalence(1n, 1n)) // true - * console.log(BigInt.Equivalence(1n, 2n)) // false + * BigInt.Equivalence(1n, 1n) // => true + * BigInt.Equivalence(1n, 2n) // => false * ``` * * @category instances @@ -328,13 +317,12 @@ export const Equivalence: Equ.Equivalence = Equ.BigInt * * **Example** (Checking less-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isLessThan(2n, 3n), true) - * assert.deepStrictEqual(BigInt.isLessThan(3n, 3n), false) - * assert.deepStrictEqual(BigInt.isLessThan(4n, 3n), false) + * BigInt.isLessThan(2n, 3n) // => true + * BigInt.isLessThan(3n, 3n) // => false + * BigInt.isLessThan(4n, 3n) // => false * ``` * * @category predicates @@ -354,13 +342,12 @@ export const isLessThan: { * * **Example** (Checking less-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isLessThanOrEqualTo(2n, 3n), true) - * assert.deepStrictEqual(BigInt.isLessThanOrEqualTo(3n, 3n), true) - * assert.deepStrictEqual(BigInt.isLessThanOrEqualTo(4n, 3n), false) + * BigInt.isLessThanOrEqualTo(2n, 3n) // => true + * BigInt.isLessThanOrEqualTo(3n, 3n) // => true + * BigInt.isLessThanOrEqualTo(4n, 3n) // => false * ``` * * @category predicates @@ -380,13 +367,12 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking greater-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isGreaterThan(2n, 3n), false) - * assert.deepStrictEqual(BigInt.isGreaterThan(3n, 3n), false) - * assert.deepStrictEqual(BigInt.isGreaterThan(4n, 3n), true) + * BigInt.isGreaterThan(2n, 3n) // => false + * BigInt.isGreaterThan(3n, 3n) // => false + * BigInt.isGreaterThan(4n, 3n) // => true * ``` * * @category predicates @@ -406,13 +392,12 @@ export const isGreaterThan: { * * **Example** (Checking greater-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.isGreaterThanOrEqualTo(2n, 3n), false) - * assert.deepStrictEqual(BigInt.isGreaterThanOrEqualTo(3n, 3n), true) - * assert.deepStrictEqual(BigInt.isGreaterThanOrEqualTo(4n, 3n), true) + * BigInt.isGreaterThanOrEqualTo(2n, 3n) // => false + * BigInt.isGreaterThanOrEqualTo(3n, 3n) // => true + * BigInt.isGreaterThanOrEqualTo(4n, 3n) // => true * ``` * * @category predicates @@ -432,15 +417,14 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking whether a bigint is within bounds) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * * const between = BigInt.between({ minimum: 0n, maximum: 5n }) * - * assert.deepStrictEqual(between(3n), true) - * assert.deepStrictEqual(between(-1n), false) - * assert.deepStrictEqual(between(6n), false) + * between(3n) // => true + * between(-1n) // => false + * between(6n) // => false * ``` * * @see {@link clamp} for forcing a `bigint` into an inclusive range @@ -474,15 +458,14 @@ export const between: { * * **Example** (Clamping a bigint to bounds) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * * const clamp = BigInt.clamp({ minimum: 1n, maximum: 5n }) * - * assert.equal(clamp(3n), 3n) - * assert.equal(clamp(0n), 1n) - * assert.equal(clamp(6n), 5n) + * clamp(3n) // => 3n + * clamp(0n) // => 1n + * clamp(6n) // => 5n * ``` * * @see {@link between} for checking whether a `bigint` is already inside a range @@ -510,11 +493,10 @@ export const clamp: { * * **Example** (Finding the minimum bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.min(2n, 3n), 2n) + * BigInt.min(2n, 3n) // => 2n * ``` * * @see {@link max} for selecting the larger value @@ -536,11 +518,10 @@ export const min: { * * **Example** (Finding the maximum bigint) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.max(2n, 3n), 3n) + * BigInt.max(2n, 3n) // => 3n * ``` * * @see {@link min} for selecting the smaller value @@ -562,13 +543,12 @@ export const max: { * * **Example** (Determining bigint signs) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sign(-5n), -1) - * assert.deepStrictEqual(BigInt.sign(0n), 0) - * assert.deepStrictEqual(BigInt.sign(5n), 1) + * BigInt.sign(-5n) // => -1 + * BigInt.sign(0n) // => 0 + * BigInt.sign(5n) // => 1 * ``` * * @category math @@ -585,13 +565,12 @@ export const sign = (n: bigint): Ordering => order.BigInt(n, bigint0) * * **Example** (Calculating absolute values) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.abs(-5n), 5n) - * assert.deepStrictEqual(BigInt.abs(0n), 0n) - * assert.deepStrictEqual(BigInt.abs(5n), 5n) + * BigInt.abs(-5n) // => 5n + * BigInt.abs(0n) // => 0n + * BigInt.abs(5n) // => 5n * ``` * * @category math @@ -608,13 +587,12 @@ export const abs = (n: bigint): bigint => (n < bigint0 ? -n : n) * * **Example** (Calculating greatest common divisors) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.gcd(2n, 3n), 1n) - * assert.deepStrictEqual(BigInt.gcd(2n, 4n), 2n) - * assert.deepStrictEqual(BigInt.gcd(16n, 24n), 8n) + * BigInt.gcd(2n, 3n) // => 1n + * BigInt.gcd(2n, 4n) // => 2n + * BigInt.gcd(16n, 24n) // => 8n * ``` * * @see {@link lcm} for computing the least common multiple @@ -631,7 +609,7 @@ export const gcd: { that = self % that self = t } - return self + return abs(self) }) /** @@ -643,13 +621,12 @@ export const gcd: { * * **Example** (Calculating least common multiples) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.lcm(2n, 3n), 6n) - * assert.deepStrictEqual(BigInt.lcm(2n, 4n), 4n) - * assert.deepStrictEqual(BigInt.lcm(16n, 24n), 48n) + * BigInt.lcm(2n, 3n) // => 6n + * BigInt.lcm(2n, 4n) // => 4n + * BigInt.lcm(16n, 24n) // => 48n * ``` * * @see {@link gcd} for computing the greatest common divisor @@ -660,7 +637,11 @@ export const gcd: { export const lcm: { (that: bigint): (self: bigint) => bigint (self: bigint, that: bigint): bigint -} = dual(2, (self: bigint, that: bigint): bigint => (self * that) / gcd(self, that)) +} = dual( + 2, + (self: bigint, that: bigint): bigint => + self === bigint0 || that === bigint0 ? bigint0 : abs((self * that) / gcd(self, that)) +) /** * Returns the integer square root of a non-negative `bigint`. @@ -682,13 +663,12 @@ export const lcm: { * * **Example** (Calculating square roots unsafely) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sqrtUnsafe(4n), 2n) - * assert.deepStrictEqual(BigInt.sqrtUnsafe(9n), 3n) - * assert.deepStrictEqual(BigInt.sqrtUnsafe(16n), 4n) + * BigInt.sqrtUnsafe(4n) // => 2n + * BigInt.sqrtUnsafe(9n) // => 3n + * BigInt.sqrtUnsafe(16n) // => 4n * ``` * * @see {@link sqrt} for returning `Option.none` when the input is negative @@ -726,13 +706,13 @@ export const sqrtUnsafe = (n: bigint): bigint => { * * **Example** (Calculating square roots safely) * - * ```ts - * import { BigInt } from "effect" + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" * - * BigInt.sqrt(4n) // Option.some(2n) - * BigInt.sqrt(9n) // Option.some(3n) - * BigInt.sqrt(16n) // Option.some(4n) - * BigInt.sqrt(-1n) // Option.none() + * BigInt.sqrt(4n) // => Option.some(2n) + * BigInt.sqrt(9n) // => Option.some(3n) + * BigInt.sqrt(16n) // => Option.some(4n) + * BigInt.sqrt(-1n) // => Option.none() * ``` * * @see {@link sqrtUnsafe} for square root computation that throws on negative input @@ -753,11 +733,10 @@ export const sqrt = (n: bigint): Option.Option => * * **Example** (Summing iterable bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.sumAll([2n, 3n, 4n]), 9n) + * BigInt.sumAll([2n, 3n, 4n]) // => 9n * ``` * * @see {@link sum} for adding two `bigint` values @@ -783,11 +762,10 @@ export const sumAll = (collection: Iterable): bigint => { * * **Example** (Multiplying iterable bigints) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(BigInt.multiplyAll([2n, 3n, 4n]), 24n) + * BigInt.multiplyAll([2n, 3n, 4n]) // => 24n * ``` * * @see {@link multiply} for multiplying two `bigint` values @@ -822,12 +800,12 @@ export const multiplyAll = (collection: Iterable): bigint => { * * **Example** (Converting bigints to numbers) * - * ```ts - * import { BigInt as BI } from "effect" + * ```ts import.meta.vitest + * import { BigInt as BI, Option } from "effect" * - * BI.toNumber(42n) // Option.some(42) - * BI.toNumber(BigInt(Number.MAX_SAFE_INTEGER) + 1n) // Option.none() - * BI.toNumber(BigInt(Number.MIN_SAFE_INTEGER) - 1n) // Option.none() + * BI.toNumber(42n) // => Option.some(42) + * BI.toNumber(9007199254740992n) // => Option.none() + * BI.toNumber(-9007199254740992n) // => Option.none() * ``` * * @see {@link fromNumber} for converting a safe integer number to `bigint` @@ -856,12 +834,12 @@ export const toNumber = (b: bigint): Option.Option => { * * **Example** (Parsing strings as bigints) * - * ```ts - * import { BigInt } from "effect" + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" * - * BigInt.fromString("42") // Option.some(42n) - * BigInt.fromString(" ") // Option.none() - * BigInt.fromString("a") // Option.none() + * BigInt.fromString("42") // => Option.some(42n) + * BigInt.fromString(" ") // => Option.none() + * BigInt.fromString("a") // => Option.none() * ``` * * @see {@link BigInt} for native constructor coercion that throws on invalid input @@ -894,13 +872,12 @@ export const fromString = (s: string): Option.Option => { * * **Example** (Converting numbers to bigints) * - * ```ts - * import { BigInt } from "effect" - * - * BigInt.fromNumber(42) // Option.some(42n) + * ```ts import.meta.vitest + * import { BigInt, Option } from "effect" * - * BigInt.fromNumber(Number.MAX_SAFE_INTEGER + 1) // Option.none() - * BigInt.fromNumber(Number.MIN_SAFE_INTEGER - 1) // Option.none() + * BigInt.fromNumber(42) // => Option.some(42n) + * BigInt.fromNumber(Number.MAX_SAFE_INTEGER + 1) // => Option.none() + * BigInt.fromNumber(Number.MIN_SAFE_INTEGER - 1) // => Option.none() * ``` * * @see {@link toNumber} for converting `bigint` values back to safe integer numbers @@ -935,12 +912,11 @@ export function fromNumber(n: number): Option.Option { * * **Example** (Calculating remainders) * - * ```ts + * ```ts import.meta.vitest * import { BigInt } from "effect" * - * BigInt.remainder(10n, 3n) // 1n - * - * BigInt.remainder(15n, 4n) // 3n + * BigInt.remainder(10n, 3n) // => 1n + * BigInt.remainder(15n, 4n) // => 3n * ``` * * @see {@link divide} for quotient calculation with division-by-zero represented as `Option.none` diff --git a/packages/effect/src/Boolean.ts b/packages/effect/src/Boolean.ts index d69e52a27..405e7b35a 100644 --- a/packages/effect/src/Boolean.ts +++ b/packages/effect/src/Boolean.ts @@ -31,17 +31,12 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Coercing values to booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" * - * const bool = Boolean.Boolean(1) - * console.log(bool) // true - * - * const fromString = Boolean.Boolean("false") - * console.log(fromString) // true (non-empty string) - * - * const fromZero = Boolean.Boolean(0) - * console.log(fromZero) // false + * Boolean.Boolean(1) // => true + * Boolean.Boolean("false") // => true + * Boolean.Boolean(0) // => false * ``` * * @category constructors @@ -58,12 +53,11 @@ export const Boolean = globalThis.Boolean * * **Example** (Checking for booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.isBoolean(true), true) - * assert.deepStrictEqual(Boolean.isBoolean("true"), false) + * Boolean.isBoolean(true) // => true + * Boolean.isBoolean("true") // => false * ``` * * @category guards @@ -80,17 +74,13 @@ export const isBoolean: (input: unknown) => input is boolean = predicate.isBoole * * **Example** (Pattern matching on booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * Boolean.match(true, { - * onFalse: () => "It's false!", - * onTrue: () => "It's true!" - * }), - * "It's true!" - * ) + * + * Boolean.match(true, { + * onFalse: () => "It's false!", + * onTrue: () => "It's true!" + * }) // => "It's true!" * ``` * * @category pattern matching @@ -121,12 +111,12 @@ export const match: { * * **Example** (Comparing booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" * - * console.log(Boolean.Order(false, true)) // -1 (false < true) - * console.log(Boolean.Order(true, false)) // 1 (true > false) - * console.log(Boolean.Order(true, true)) // 0 (true === true) + * Boolean.Order(false, true) // => -1 + * Boolean.Order(true, false) // => 1 + * Boolean.Order(true, true) // => 0 * ``` * * @category instances @@ -144,11 +134,11 @@ export const Order: order.Order = order.Boolean * * **Example** (Comparing booleans for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" * - * console.log(Boolean.Equivalence(true, true)) // true - * console.log(Boolean.Equivalence(true, false)) // false + * Boolean.Equivalence(true, true) // => true + * Boolean.Equivalence(true, false) // => false * ``` * * @category instances @@ -165,12 +155,11 @@ export const Equivalence: Equ.Equivalence = Equ.Boolean * * **Example** (Negating booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.not(true), false) - * assert.deepStrictEqual(Boolean.not(false), true) + * Boolean.not(true) // => false + * Boolean.not(false) // => true * ``` * * @category combinators @@ -191,14 +180,13 @@ export const not = (self: boolean): boolean => !self * * **Example** (Combining booleans with AND) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.and(true, true), true) - * assert.deepStrictEqual(Boolean.and(true, false), false) - * assert.deepStrictEqual(Boolean.and(false, true), false) - * assert.deepStrictEqual(Boolean.and(false, false), false) + * Boolean.and(true, true) // => true + * Boolean.and(true, false) // => false + * Boolean.and(false, true) // => false + * Boolean.and(false, false) // => false * ``` * * @category combinators @@ -218,14 +206,13 @@ export const and: { * * **Example** (Combining booleans with NAND) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.nand(true, true), false) - * assert.deepStrictEqual(Boolean.nand(true, false), true) - * assert.deepStrictEqual(Boolean.nand(false, true), true) - * assert.deepStrictEqual(Boolean.nand(false, false), true) + * Boolean.nand(true, true) // => false + * Boolean.nand(true, false) // => true + * Boolean.nand(false, true) // => true + * Boolean.nand(false, false) // => true * ``` * * @category combinators @@ -245,14 +232,13 @@ export const nand: { * * **Example** (Combining booleans with OR) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.or(true, true), true) - * assert.deepStrictEqual(Boolean.or(true, false), true) - * assert.deepStrictEqual(Boolean.or(false, true), true) - * assert.deepStrictEqual(Boolean.or(false, false), false) + * Boolean.or(true, true) // => true + * Boolean.or(true, false) // => true + * Boolean.or(false, true) // => true + * Boolean.or(false, false) // => false * ``` * * @category combinators @@ -272,14 +258,13 @@ export const or: { * * **Example** (Combining booleans with NOR) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.nor(true, true), false) - * assert.deepStrictEqual(Boolean.nor(true, false), false) - * assert.deepStrictEqual(Boolean.nor(false, true), false) - * assert.deepStrictEqual(Boolean.nor(false, false), true) + * Boolean.nor(true, true) // => false + * Boolean.nor(true, false) // => false + * Boolean.nor(false, true) // => false + * Boolean.nor(false, false) // => true * ``` * * @category combinators @@ -299,14 +284,13 @@ export const nor: { * * **Example** (Combining booleans with XOR) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.xor(true, true), false) - * assert.deepStrictEqual(Boolean.xor(true, false), true) - * assert.deepStrictEqual(Boolean.xor(false, true), true) - * assert.deepStrictEqual(Boolean.xor(false, false), false) + * Boolean.xor(true, true) // => false + * Boolean.xor(true, false) // => true + * Boolean.xor(false, true) // => true + * Boolean.xor(false, false) // => false * ``` * * @category combinators @@ -326,14 +310,13 @@ export const xor: { * * **Example** (Checking boolean equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.eqv(true, true), true) - * assert.deepStrictEqual(Boolean.eqv(true, false), false) - * assert.deepStrictEqual(Boolean.eqv(false, true), false) - * assert.deepStrictEqual(Boolean.eqv(false, false), true) + * Boolean.eqv(true, true) // => true + * Boolean.eqv(true, false) // => false + * Boolean.eqv(false, true) // => false + * Boolean.eqv(false, false) // => true * ``` * * @category combinators @@ -353,14 +336,13 @@ export const eqv: { * * **Example** (Checking boolean implication) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.implies(true, true), true) - * assert.deepStrictEqual(Boolean.implies(true, false), false) - * assert.deepStrictEqual(Boolean.implies(false, true), true) - * assert.deepStrictEqual(Boolean.implies(false, false), true) + * Boolean.implies(true, true) // => true + * Boolean.implies(true, false) // => false + * Boolean.implies(false, true) // => true + * Boolean.implies(false, false) // => true * ``` * * @category combinators @@ -380,12 +362,11 @@ export const implies: { * * **Example** (Checking every boolean) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.every([true, true, true]), true) - * assert.deepStrictEqual(Boolean.every([true, false, true]), false) + * Boolean.every([true, true, true]) // => true + * Boolean.every([true, false, true]) // => false * ``` * * @see {@link some} for checking whether at least one value is `true` @@ -412,12 +393,11 @@ export const every = (collection: Iterable): boolean => { * * **Example** (Checking some booleans) * - * ```ts + * ```ts import.meta.vitest * import { Boolean } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Boolean.some([true, false, true]), true) - * assert.deepStrictEqual(Boolean.some([false, false, false]), false) + * Boolean.some([true, false, true]) // => true + * Boolean.some([false, false, false]) // => false * ``` * * @see {@link every} for checking whether all values are `true` diff --git a/packages/effect/src/Brand.ts b/packages/effect/src/Brand.ts index 39789b23a..1cbba59fd 100644 --- a/packages/effect/src/Brand.ts +++ b/packages/effect/src/Brand.ts @@ -12,7 +12,7 @@ import * as Option from "./Option.ts" import * as Result from "./Result.ts" import type * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" -import type * as SchemaIssue from "./SchemaIssue.ts" +import * as SchemaIssue from "./SchemaIssue.ts" import type * as Types from "./Types.ts" const TypeId = "~effect/Brand" @@ -92,8 +92,8 @@ export interface Constructor> { * * **Details** * - * The error wraps a `SchemaIssue.Issue`, exposes `message` through - * `issue.toString()`, and formats as `BrandError()`. + * The error wraps a `SchemaIssue.Issue`, renders `message` with the default + * schema issue formatter, and formats as `BrandError()`. * * **Gotchas** * @@ -131,7 +131,7 @@ export class BrandError { * @since 4.0.0 */ get message() { - return this.issue.toString() + return SchemaIssue.defaultFormatter(this.issue) } /** * Formats the brand error together with its validation message. diff --git a/packages/effect/src/ByteSize.ts b/packages/effect/src/ByteSize.ts new file mode 100644 index 000000000..ae7dfc112 --- /dev/null +++ b/packages/effect/src/ByteSize.ts @@ -0,0 +1,658 @@ +/** + * Represents exact, non-negative, integral byte counts. + * + * Decimal units use powers of 1,000 and binary units use powers of 1,024. + * + * @since 4.0.0 + */ +import * as BI from "./BigInt.ts" +import type * as Brand from "./Brand.ts" +import type * as Combiner from "./Combiner.ts" +import type * as Equ from "./Equivalence.ts" +import { dual } from "./Function.ts" +import * as Option from "./Option.ts" +import type * as order from "./Order.ts" +import type * as Reducer from "./Reducer.ts" + +const TypeId = "~effect/ByteSize" + +const bigint0 = BigInt(0) +const bigint1 = BigInt(1) +const decimalBase = BigInt(1000) +const binaryBase = BigInt(1024) + +/** + * Represents an exact, non-negative number of bytes. + * + * @category models + * @since 4.0.0 + */ +export type ByteSize = Brand.Branded + +/** + * Values accepted by byte-size decoding operations. + * + * @category models + * @since 4.0.0 + */ +export type Input = ByteSize | bigint | number | string + +/** + * Canonical decimal byte unit symbols. + * + * @category models + * @since 4.0.0 + */ +export type DecimalUnit = + | "B" + | "kB" + | "MB" + | "GB" + | "TB" + | "PB" + | "EB" + | "ZB" + | "YB" + | "RB" + | "QB" + +/** + * Canonical binary byte unit symbols. + * + * @category models + * @since 4.0.0 + */ +export type BinaryUnit = + | "B" + | "KiB" + | "MiB" + | "GiB" + | "TiB" + | "PiB" + | "EiB" + | "ZiB" + | "YiB" + +/** + * Canonical decimal and binary byte unit symbols. + * + * @category models + * @since 4.0.0 + */ +export type Unit = DecimalUnit | BinaryUnit + +/** + * Options controlling compact byte-size formatting. + * + * @category models + * @since 4.0.0 + */ +export type FormatOptions = + & { + readonly precision?: number | undefined + readonly trailingZeros?: boolean | undefined + } + & ( + | { + readonly system?: "decimal" | undefined + readonly unit?: DecimalUnit | undefined + } + | { + readonly system?: "binary" | undefined + readonly unit?: BinaryUnit | undefined + } + ) + +interface UnitInfo { + readonly symbol: Unit + readonly factor: bigint + readonly names: ReadonlyArray +} + +const decimalUnits: ReadonlyArray = [ + { symbol: "B", factor: bigint1, names: ["B", "byte", "bytes"] }, + { symbol: "kB", factor: decimalBase, names: ["kB", "kilobyte", "kilobytes"] }, + { symbol: "MB", factor: decimalBase ** BigInt(2), names: ["MB", "megabyte", "megabytes"] }, + { symbol: "GB", factor: decimalBase ** BigInt(3), names: ["GB", "gigabyte", "gigabytes"] }, + { symbol: "TB", factor: decimalBase ** BigInt(4), names: ["TB", "terabyte", "terabytes"] }, + { symbol: "PB", factor: decimalBase ** BigInt(5), names: ["PB", "petabyte", "petabytes"] }, + { symbol: "EB", factor: decimalBase ** BigInt(6), names: ["EB", "exabyte", "exabytes"] }, + { symbol: "ZB", factor: decimalBase ** BigInt(7), names: ["ZB", "zettabyte", "zettabytes"] }, + { symbol: "YB", factor: decimalBase ** BigInt(8), names: ["YB", "yottabyte", "yottabytes"] }, + { symbol: "RB", factor: decimalBase ** BigInt(9), names: ["RB", "ronnabyte", "ronnabytes"] }, + { symbol: "QB", factor: decimalBase ** BigInt(10), names: ["QB", "quettabyte", "quettabytes"] } +] + +const binaryUnits: ReadonlyArray = [ + decimalUnits[0], + { symbol: "KiB", factor: binaryBase, names: ["KiB", "kibibyte", "kibibytes"] }, + { symbol: "MiB", factor: binaryBase ** BigInt(2), names: ["MiB", "mebibyte", "mebibytes"] }, + { symbol: "GiB", factor: binaryBase ** BigInt(3), names: ["GiB", "gibibyte", "gibibytes"] }, + { symbol: "TiB", factor: binaryBase ** BigInt(4), names: ["TiB", "tebibyte", "tebibytes"] }, + { symbol: "PiB", factor: binaryBase ** BigInt(5), names: ["PiB", "pebibyte", "pebibytes"] }, + { symbol: "EiB", factor: binaryBase ** BigInt(6), names: ["EiB", "exbibyte", "exbibytes"] }, + { symbol: "ZiB", factor: binaryBase ** BigInt(7), names: ["ZiB", "zebibyte", "zebibytes"] }, + { symbol: "YiB", factor: binaryBase ** BigInt(8), names: ["YiB", "yobibyte", "yobibytes"] } +] + +const allUnits = [...decimalUnits, ...binaryUnits.slice(1)] +const unitsByName = new Map(allUnits.flatMap((unit) => unit.names.map((name) => [name, unit] as const))) +const unitsBySymbol = new Map(allUnits.map((unit) => [unit.symbol, unit] as const)) + +const make = (value: bigint): ByteSize => value as ByteSize + +/** + * The byte size containing zero bytes. + * + * @category constants + * @since 4.0.0 + */ +export const zero: ByteSize = make(bigint0) + +const invalid = (message: string): never => { + throw new Error(`Invalid ByteSize: ${message}`) +} + +const fromNumber = (input: number): ByteSize => { + if (!Number.isSafeInteger(input) || input < 0) { + return invalid(`expected a non-negative safe integer, received ${input}`) + } + return make(BigInt(input)) +} + +const fromQuantity = (quantity: number | bigint, unit: UnitInfo): ByteSize => { + if (typeof quantity === "bigint") { + if (quantity < bigint0) return invalid(`expected a non-negative quantity, received ${quantity}`) + return make(quantity * unit.factor) + } + const value = quantity * Number(unit.factor) + if (!Number.isSafeInteger(value) || value < 0) { + return invalid(`expected an exact non-negative safe-integer byte result, received ${quantity} ${unit.symbol}`) + } + return make(BigInt(value)) +} + +const parse = (input: string): ByteSize => { + const match = /^\s*(\d+)(?:\.(\d+))?\s*([A-Za-z]+)\s*$/.exec(input) + if (match === null) return invalid(`unsupported syntax ${JSON.stringify(input)}`) + const unit = unitsByName.get(match[3]) + if (unit === undefined) return invalid(`unsupported unit ${JSON.stringify(match[3])}`) + const fraction = match[2] ?? "" + const scale = BigInt(10) ** BigInt(fraction.length) + const numerator = BigInt(match[1] + fraction) * unit.factor + if (numerator % scale !== bigint0) { + return invalid(`${JSON.stringify(input)} does not represent an integral number of bytes`) + } + return make(numerator / scale) +} + +/** + * Decodes a trusted input into a byte size and throws for invalid input. + * + * @category constructors + * @since 4.0.0 + */ +export const fromInputUnsafe = (input: Input): ByteSize => { + switch (typeof input) { + case "bigint": + if (input < bigint0) return invalid(`expected a non-negative bigint, received ${input}`) + return make(input) + case "number": + return fromNumber(input) + case "string": + return parse(input) + } + return invalid(`unsupported input ${input}`) +} + +/** + * Decodes an input into a byte size, returning `None` for invalid input. + * + * @category constructors + * @since 4.0.0 + */ +export const fromInput: (input: Input) => Option.Option = Option.liftThrowable(fromInputUnsafe) + +/** + * Creates a byte size from a non-negative byte count. + * + * @category constructors + * @since 4.0.0 + */ +export const bytes = (value: number | bigint): ByteSize => + typeof value === "bigint" ? fromInputUnsafe(value) : fromNumber(value) + +const unitConstructor = (symbol: Unit) => (value: number | bigint): ByteSize => + fromQuantity(value, unitsBySymbol.get(symbol)!) + +/** + * Creates a decimal kilobyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const kilobytes: (value: number | bigint) => ByteSize = unitConstructor("kB") +/** + * Creates a decimal megabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const megabytes: (value: number | bigint) => ByteSize = unitConstructor("MB") +/** + * Creates a decimal gigabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const gigabytes: (value: number | bigint) => ByteSize = unitConstructor("GB") +/** + * Creates a decimal terabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const terabytes: (value: number | bigint) => ByteSize = unitConstructor("TB") +/** + * Creates a decimal petabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const petabytes: (value: number | bigint) => ByteSize = unitConstructor("PB") +/** + * Creates a decimal exabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const exabytes: (value: number | bigint) => ByteSize = unitConstructor("EB") +/** + * Creates a decimal zettabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const zettabytes: (value: number | bigint) => ByteSize = unitConstructor("ZB") +/** + * Creates a decimal yottabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const yottabytes: (value: number | bigint) => ByteSize = unitConstructor("YB") +/** + * Creates a decimal ronnabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const ronnabytes: (value: number | bigint) => ByteSize = unitConstructor("RB") +/** + * Creates a decimal quettabyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const quettabytes: (value: number | bigint) => ByteSize = unitConstructor("QB") +/** + * Creates a binary kibibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const kibibytes: (value: number | bigint) => ByteSize = unitConstructor("KiB") +/** + * Creates a binary mebibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const mebibytes: (value: number | bigint) => ByteSize = unitConstructor("MiB") +/** + * Creates a binary gibibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const gibibytes: (value: number | bigint) => ByteSize = unitConstructor("GiB") +/** + * Creates a binary tebibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const tebibytes: (value: number | bigint) => ByteSize = unitConstructor("TiB") +/** + * Creates a binary pebibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const pebibytes: (value: number | bigint) => ByteSize = unitConstructor("PiB") +/** + * Creates a binary exbibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const exbibytes: (value: number | bigint) => ByteSize = unitConstructor("EiB") +/** + * Creates a binary zebibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const zebibytes: (value: number | bigint) => ByteSize = unitConstructor("ZiB") +/** + * Creates a binary yobibyte value. + * + * @category constructors + * @since 4.0.0 + */ +export const yobibytes: (value: number | bigint) => ByteSize = unitConstructor("YiB") + +/** + * Checks whether a value is a byte size. + * + * @category guards + * @since 4.0.0 + */ +export const isByteSize = (input: unknown): input is ByteSize => typeof input === "bigint" && input >= bigint0 + +/** + * Checks whether a byte size is zero. + * + * @category predicates + * @since 4.0.0 + */ +export const isZero = (self: ByteSize): boolean => self === bigint0 + +/** + * Returns the exact byte count as a bigint. + * + * @category getters + * @since 4.0.0 + */ +export const toBigInt = (self: ByteSize): bigint => self + +/** + * Converts a byte size to a safe integer, returning `None` when it is too large. + * + * @category converting + * @since 4.0.0 + */ +export const toNumber: (self: ByteSize) => Option.Option = BI.toNumber + +/** + * Converts a byte size to a safe integer and throws when it is too large. + * + * @category unsafe + * @since 4.0.0 + */ +export const toNumberUnsafe = (self: ByteSize): number => + Option.getOrThrowWith(toNumber(self), () => new Error(`ByteSize exceeds Number.MAX_SAFE_INTEGER: ${self}`)) + +/** + * Converts a byte size to an approximate number of the specified unit. + * + * @category converting + * @since 4.0.0 + */ +export const toUnit: { + (unit: Unit): (self: ByteSize) => number + (self: ByteSize, unit: Unit): number +} = dual(2, (self: ByteSize, unit: Unit) => Number(self) / Number(unitsBySymbol.get(unit)!.factor)) + +/** + * Provides an order for byte sizes. + * + * @category instances + * @since 4.0.0 + */ +export const Order: order.Order = BI.Order + +/** + * Provides an equivalence for byte sizes. + * + * @category instances + * @since 4.0.0 + */ +export const Equivalence: Equ.Equivalence = BI.Equivalence + +/** + * Returns whether a byte size is in an inclusive range. + * + * @category predicates + * @since 4.0.0 + */ +export const between: { + (options: { minimum: ByteSize; maximum: ByteSize }): (self: ByteSize) => boolean + (self: ByteSize, options: { minimum: ByteSize; maximum: ByteSize }): boolean +} = BI.between + +/** + * Returns the smaller byte size. + * + * @category ordering + * @since 4.0.0 + */ +export const min: { + (that: ByteSize): (self: ByteSize) => ByteSize + (self: ByteSize, that: ByteSize): ByteSize +} = BI.min as any + +/** + * Returns the larger byte size. + * + * @category ordering + * @since 4.0.0 + */ +export const max: { + (that: ByteSize): (self: ByteSize) => ByteSize + (self: ByteSize, that: ByteSize): ByteSize +} = BI.max as any + +/** + * Constrains a byte size to an inclusive range. + * + * @category ordering + * @since 4.0.0 + */ +export const clamp: { + (options: { minimum: ByteSize; maximum: ByteSize }): (self: ByteSize) => ByteSize + (self: ByteSize, options: { minimum: ByteSize; maximum: ByteSize }): ByteSize +} = BI.clamp as any + +/** + * Checks whether the first byte size is less than the second. + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThan: { + (that: ByteSize): (self: ByteSize) => boolean + (self: ByteSize, that: ByteSize): boolean +} = BI.isLessThan + +/** + * Checks whether the first byte size is at most the second. + * + * @category predicates + * @since 4.0.0 + */ +export const isLessThanOrEqualTo: { + (that: ByteSize): (self: ByteSize) => boolean + (self: ByteSize, that: ByteSize): boolean +} = BI.isLessThanOrEqualTo + +/** + * Checks whether the first byte size is greater than the second. + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThan: { + (that: ByteSize): (self: ByteSize) => boolean + (self: ByteSize, that: ByteSize): boolean +} = BI.isGreaterThan + +/** + * Checks whether the first byte size is at least the second. + * + * @category predicates + * @since 4.0.0 + */ +export const isGreaterThanOrEqualTo: { + (that: ByteSize): (self: ByteSize) => boolean + (self: ByteSize, that: ByteSize): boolean +} = BI.isGreaterThanOrEqualTo + +/** + * Checks whether two byte sizes contain the same count. + * + * @category predicates + * @since 4.0.0 + */ +export const equals: { + (that: ByteSize): (self: ByteSize) => boolean + (self: ByteSize, that: ByteSize): boolean +} = dual(2, Equivalence) + +/** + * Adds two byte sizes exactly. + * + * @category math + * @since 4.0.0 + */ +export const sum: { + (that: ByteSize): (self: ByteSize) => ByteSize + (self: ByteSize, that: ByteSize): ByteSize +} = BI.sum as any + +/** + * Subtracts byte sizes, returning `None` on underflow. + * + * @category math + * @since 4.0.0 + */ +export const subtract: { + (that: ByteSize): (self: ByteSize) => Option.Option + (self: ByteSize, that: ByteSize): Option.Option +} = dual( + 2, + (self: ByteSize, that: ByteSize) => self < that ? Option.none() : Option.some(make(self - that)) +) + +/** + * Subtracts byte sizes and throws on underflow. + * + * @category unsafe + * @since 4.0.0 + */ +export const subtractUnsafe: { + (that: ByteSize): (self: ByteSize) => ByteSize + (self: ByteSize, that: ByteSize): ByteSize +} = dual(2, (self: ByteSize, that: ByteSize) => { + if (self < that) throw new Error(`ByteSize subtraction underflow: ${self} - ${that}`) + return make(self - that) +}) + +const scalar = (input: number | bigint, positive: boolean): bigint | undefined => { + if (typeof input === "bigint") return input >= (positive ? bigint1 : bigint0) ? input : undefined + return Number.isSafeInteger(input) && input >= (positive ? 1 : 0) ? BigInt(input) : undefined +} + +/** + * Multiplies a byte size by a non-negative integer scalar. + * + * @category math + * @since 4.0.0 + */ +export const times: { + (multiplier: number | bigint): (self: ByteSize) => Option.Option + (self: ByteSize, multiplier: number | bigint): Option.Option +} = dual(2, (self: ByteSize, multiplier: number | bigint) => { + const value = scalar(multiplier, false) + return value === undefined ? Option.none() : Option.some(make(self * value)) +}) + +/** + * Divides a byte size by a positive integer, discarding any remainder. + * + * @category math + * @since 4.0.0 + */ +export const divide: { + (divisor: number | bigint): (self: ByteSize) => Option.Option + (self: ByteSize, divisor: number | bigint): Option.Option +} = dual(2, (self: ByteSize, divisor: number | bigint) => { + const value = scalar(divisor, true) + return value === undefined ? Option.none() : Option.some(make(self / value)) +}) + +const validatePrecision = (precision: number): number => { + if (!Number.isSafeInteger(precision) || precision < 0 || precision > 20) { + throw new Error(`ByteSize format precision must be an integer from 0 to 20, received ${precision}`) + } + return precision +} + +const formatWithUnit = (value: bigint, unit: UnitInfo, precision: number, trailingZeros: boolean): string => { + const scale = BigInt(10) ** BigInt(precision) + const rounded = (value * scale * BigInt(2) + unit.factor) / (unit.factor * BigInt(2)) + const whole = rounded / scale + if (precision === 0) return `${whole} ${unit.symbol}` + let fraction = `${rounded % scale}`.padStart(precision, "0") + if (!trailingZeros) fraction = fraction.replace(/0+$/, "") + return `${whole}${fraction.length === 0 ? "" : `.${fraction}`} ${unit.symbol}` +} + +/** + * Formats a byte size with canonical decimal or binary unit symbols. + * + * @category converting + * @since 4.0.0 + */ +export const format = (self: ByteSize, options: FormatOptions = {}): string => { + const precision = validatePrecision(options.precision ?? 2) + const trailingZeros = options.trailingZeros ?? false + if (options.unit !== undefined) { + return formatWithUnit(self, unitsBySymbol.get(options.unit)!, precision, trailingZeros) + } + const units = options.system === "decimal" ? decimalUnits : binaryUnits + if (self === bigint0) return "0 B" + let index = units.length - 1 + while (index > 0 && self < units[index].factor) index-- + if (index < units.length - 1) { + const scale = BigInt(10) ** BigInt(precision) + const rounded = (self * scale * BigInt(2) + units[index].factor) / (units[index].factor * BigInt(2)) + const base = options.system === "decimal" ? decimalBase : binaryBase + if (rounded >= base * scale) index++ + } + return formatWithUnit(self, units[index], precision, trailingZeros) +} + +/** + * Reducer that sums byte sizes from zero. + * + * @category math + * @since 4.0.0 + */ +export const ReducerSum: Reducer.Reducer = BI.ReducerSum as any + +/** + * Combiner that keeps the largest byte size. + * + * @category math + * @since 4.0.0 + */ +export const CombinerMax: Combiner.Combiner = BI.CombinerMax as any + +/** + * Combiner that keeps the smallest byte size. + * + * @category math + * @since 4.0.0 + */ +export const CombinerMin: Combiner.Combiner = BI.CombinerMin as any diff --git a/packages/effect/src/Cache.ts b/packages/effect/src/Cache.ts index 21fb2328b..0bc335802 100644 --- a/packages/effect/src/Cache.ts +++ b/packages/effect/src/Cache.ts @@ -9,7 +9,6 @@ * @since 4.0.0 */ import * as Context from "./Context.ts" -import * as Deferred from "./Deferred.ts" import * as Duration from "./Duration.ts" import type * as Effect from "./Effect.ts" import type * as Exit from "./Exit.ts" @@ -33,7 +32,7 @@ const TypeId = "~effect/Cache" * * **Example** (Creating a basic cache) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Basic cache with string keys and number values @@ -50,12 +49,15 @@ const TypeId = "~effect/Cache" * * return [value1, value2, value3] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [5, 5, 5] * ``` * * **Example** (Handling lookup failures) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" * * // Cache with error handling * const program = Effect.gen(function*() { @@ -68,16 +70,19 @@ const TypeId = "~effect/Cache" * }) * * // Handle successful and failed lookups - * const success = yield* Cache.get(cache, "test") // 4 - * const failure = yield* Effect.exit(Cache.get(cache, "error")) // Exit.fail + * const success = yield* Cache.get(cache, "test") + * const failure = yield* Effect.exit(Cache.get(cache, "error")) * - * return { success, failure } + * return [success, failure] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [4, Exit.fail("Lookup failed")] * ``` * * **Example** (Using complex keys with TTL) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Data, Duration, Effect } from "effect" * * // Cache with complex key types and TTL @@ -93,8 +98,11 @@ const TypeId = "~effect/Cache" * const userId = new UserId({ id: 123 }) * const userName = yield* Cache.get(userCache, userId) * - * return userName // "User-123" + * return userName * }) + * + * const actual = await Effect.runPromise(program) + * actual // => "User-123" * ``` * * @category models @@ -129,7 +137,9 @@ export interface Cache ex */ export interface Entry { expiresAt: number | undefined - readonly deferred: Deferred.Deferred + awaiters: number + readonly fiber: Fiber.Fiber + await(this: Entry): Effect.Effect } /** @@ -147,11 +157,11 @@ export interface Entry { * * **Example** (Configuring dynamic time to live) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect, Exit } from "effect" * * // Cache with TTL based on computed value - * const userCache = Effect.gen(function*() { + * const program = Effect.gen(function*() { * const cache = yield* Cache.makeWith( * (id: number) => Effect.succeed({ id, active: id % 2 === 0 }), * { @@ -166,8 +176,11 @@ export interface Entry { * } * ) * - * return cache + * return cache.capacity * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 1000 * ``` * * @see {@link make} for a simpler cache constructor with a fixed time-to-live for all entries @@ -214,7 +227,7 @@ export const makeWith = < * * **Example** (Creating a basic cache) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Basic cache with string keys @@ -226,13 +239,16 @@ export const makeWith = < * * const result1 = yield* Cache.get(cache, "hello") * const result2 = yield* Cache.get(cache, "world") - * console.log({ result1, result2 }) // { result1: 5, result2: 5 } + * return { result1, result2 } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { result1: 5, result2: 5 } * ``` * * **Example** (Creating a cache with TTL) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -258,11 +274,12 @@ export const makeWith = < * }) * * const user1 = yield* Cache.get(cache, 123) - * console.log(user1) // { name: "Ada", email: "ada@example.com" } - * * const user2 = yield* Cache.get(cache, 123) - * console.log(user2) // { name: "Ada", email: "ada@example.com" } + * return [user1, user2, user1 === user2] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [{ name: "Ada", email: "ada@example.com" }, { name: "Ada", email: "ada@example.com" }, true] * ``` * * @category constructors @@ -288,7 +305,7 @@ export const make = < > => makeWith(options.lookup, { ...options, - timeToLive: options.timeToLive ? () => options.timeToLive! : defaultTimeToLive + timeToLive: options.timeToLive !== undefined ? () => options.timeToLive! : defaultTimeToLive }) const Proto = { @@ -317,7 +334,7 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * * **Example** (Getting cached values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -328,20 +345,21 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * * // Cache miss - triggers lookup function * const result1 = yield* Cache.get(cache, "hello") - * console.log(result1) // 5 * * // Cache hit - returns cached value without lookup * const result2 = yield* Cache.get(cache, "hello") - * console.log(result2) // 5 (from cache) * * return { result1, result2 } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { result1: 5, result2: 5 } * ``` * * **Example** (Handling lookup failures) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Exit } from "effect" * * // Error handling when lookup fails * const program = Effect.gen(function*() { @@ -355,17 +373,19 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * * // Successful lookup * const success = yield* Cache.get(cache, "hello") - * console.log(success) // 5 * * // Failed lookup - returns error * const failure = yield* Effect.exit(Cache.get(cache, "error")) - * console.log(failure) // Exit.fail("Lookup failed") + * return [success, failure] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [5, Exit.fail("Lookup failed")] * ``` * * **Example** (Sharing concurrent lookups) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Concurrent access - multiple gets of same key only invoke lookup once @@ -387,9 +407,11 @@ const defaultTimeToLive = (_: Exit.Exit, _key: unknown): Duration.Du * Cache.get(cache, "hello") * ], { concurrency: "unbounded" }) * - * console.log(results) // [5, 5, 5] - * console.log(lookupCount) // 1 (lookup called only once) + * return { results, lookupCount } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { results: [5, 5, 5], lookupCount: 1 } * ``` * * @category combinators @@ -407,30 +429,61 @@ export const get: { // Move the entry to the end of the map to keep it fresh MutableHashMap.remove(self.map, key) MutableHashMap.set(self.map, key, oentry.value) - return Deferred.await(oentry.value.deferred) - } - const deferred = Deferred.makeUnsafe() - const entry: Entry = { - expiresAt: undefined, - deferred + return oentry.value.await() } - MutableHashMap.set(self.map, key, entry) - if (Number.isFinite(self.capacity)) { - checkCapacity(self) - } - return effect.onExit(self.lookup(key), (exit) => { - Deferred.doneUnsafe(deferred, exit) + const entry = new EntryImpl(fiber, self.lookup(key)) + entry.fiber.addObserver((exit) => { + if (effect.exitHasInterrupts(exit)) { + const current = MutableHashMap.get(self.map, key) + if (Option.isSome(current) && current.value === entry) { + MutableHashMap.remove(self.map, key) + } + return + } const ttl = self.timeToLive(exit, key) if (Duration.isFinite(ttl)) { entry.expiresAt = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) } else if (Duration.isZero(ttl)) { MutableHashMap.remove(self.map, key) } - return effect.void }) + const exit = entry.fiber.pollUnsafe() + if (exit === undefined || !effect.exitHasInterrupts(exit)) { + MutableHashMap.set(self.map, key, entry) + } + if (Number.isFinite(self.capacity)) { + checkCapacity(self) + } + return entry.await() }) ) +class EntryImpl implements Entry { + expiresAt: number | undefined + awaiters: number + fiber: Fiber.Fiber + + constructor( + parent: Fiber.Fiber, + valueEffect: Effect.Effect + ) { + this.fiber = effect.forkUnsafe(parent, valueEffect, true, true) + this.awaiters = 0 + this.expiresAt = undefined + } + + await(): Effect.Effect { + const exit = this.fiber.pollUnsafe() + if (exit) return exit + this.awaiters++ + return effect.onExit(effect.fiberJoin(this.fiber), () => { + this.awaiters-- + if (this.awaiters > 0 || this.fiber.pollUnsafe()) return effect.void + return effect.fiberInterrupt(this.fiber) + }) + } +} + const hasExpired = (entry: Entry, fiber: Fiber.Fiber): boolean => { if (entry.expiresAt === undefined) { return false @@ -461,8 +514,8 @@ const checkCapacity = (self: Cache) => { * * **Example** (Reading cached values without lookup) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Option } from "effect" * * const program = Effect.gen(function*() { * const cache = yield* Cache.make({ @@ -472,23 +525,23 @@ const checkCapacity = (self: Cache) => { * * // No value in cache yet - returns None without lookup * const empty = yield* Cache.getOption(cache, "hello") - * console.log(empty) // Option.none() * * // Populate cache using get * yield* Cache.get(cache, "hello") * * // Now getOption returns the cached value * const cached = yield* Cache.getOption(cache, "hello") - * console.log(cached) // Option.some(5) - * - * return { empty, cached } + * return [empty, cached] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Option.none(), Option.some(5)] * ``` * * **Example** (Skipping expired entries) * - * ```ts - * import { Cache, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cache, Effect, Option } from "effect" * import { TestClock } from "effect/testing" * * // Expired entries return None @@ -504,21 +557,23 @@ const checkCapacity = (self: Cache) => { * * // Value exists before expiration * const beforeExpiry = yield* Cache.getOption(cache, "hello") - * console.log(beforeExpiry) // Option.some(5) * * // Simulate time passing * yield* TestClock.adjust("2 hours") * * // Value expired - returns None * const afterExpiry = yield* Cache.getOption(cache, "hello") - * console.log(afterExpiry) // Option.none() + * return [beforeExpiry, afterExpiry] as const * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [Option.some(5), Option.none()] * ``` * * **Example** (Waiting for pending lookups) * - * ```ts - * import { Cache, Deferred, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cache, Deferred, Effect, Fiber, Option } from "effect" * * // Waits for ongoing computation to complete * const program = Effect.gen(function*() { @@ -538,11 +593,12 @@ const checkCapacity = (self: Cache) => { * yield* Deferred.succeed(deferred, void 0) * * const result = yield* Fiber.join(optionFiber) - * console.log(result) // Option.some(42) - * * const value = yield* Fiber.join(getFiber) - * console.log(value) // 42 + * return [result, value] as const * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Option.some(42), 42] * ``` * * @category combinators @@ -556,7 +612,7 @@ export const getOption: { (self: Cache, key: Key): Effect.Effect, E> => core.withFiber((fiber) => { const entry = getImpl(self, key, fiber) - return entry ? effect.asSome(Deferred.await(entry.deferred)) : effect.succeedNone + return entry ? effect.asSome(entry.await()) : effect.succeedNone }) ) @@ -602,7 +658,7 @@ export const getSuccess: { 2, (self: Cache, key: Key): Effect.Effect> => core.withFiber((fiber) => { - const exit = getImpl(self, key, fiber)?.deferred.effect as Exit.Exit | undefined + const exit = getImpl(self, key, fiber)?.fiber.pollUnsafe() if (exit && effect.exitIsSuccess(exit)) { return effect.succeedSome(exit.value) } @@ -616,7 +672,7 @@ export const getSuccess: { * * **Example** (Setting values directly) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -627,14 +683,16 @@ export const getSuccess: { * * // Set a value directly without invoking lookup * yield* Cache.set(cache, "hello", 42) - * const result = yield* Cache.get(cache, "hello") - * console.log(result) // 42 (not 5 from lookup) + * return yield* Cache.get(cache, "hello") * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 * ``` * * **Example** (Overwriting cached values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Overwriting existing cached values @@ -651,13 +709,16 @@ export const getSuccess: { * yield* Cache.set(cache, "test", 999) * const updated = yield* Cache.get(cache, "test") // 999 * - * console.log({ original, updated }) + * return { original, updated } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { original: 4, updated: 999 } * ``` * * **Example** (Applying TTL to set values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -671,17 +732,21 @@ export const getSuccess: { * * // Set value with TTL applied * yield* Cache.set(cache, "temporary", 123) - * console.log(yield* Cache.has(cache, "temporary")) // true + * const beforeExpiry = yield* Cache.has(cache, "temporary") * * // Advance time past TTL * yield* TestClock.adjust("2 hours") - * console.log(yield* Cache.has(cache, "temporary")) // false + * const afterExpiry = yield* Cache.has(cache, "temporary") + * return [beforeExpiry, afterExpiry] * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [true, false] * ``` * * **Example** (Enforcing capacity when setting values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Capacity enforcement with set operations @@ -694,14 +759,18 @@ export const getSuccess: { * // Fill cache to capacity * yield* Cache.set(cache, "a", 1) * yield* Cache.set(cache, "b", 2) - * console.log(yield* Cache.size(cache)) // 2 + * const sizeBeforeEviction = yield* Cache.size(cache) * * // Adding another entry evicts oldest * yield* Cache.set(cache, "c", 3) - * console.log(yield* Cache.size(cache)) // 2 - * console.log(yield* Cache.has(cache, "a")) // false (evicted) - * console.log(yield* Cache.has(cache, "c")) // true + * const sizeAfterEviction = yield* Cache.size(cache) + * const hasOldest = yield* Cache.has(cache, "a") + * const hasNewest = yield* Cache.has(cache, "c") + * return [sizeBeforeEviction, sizeAfterEviction, hasOldest, hasNewest] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [2, 2, false, true] * ``` * * @category combinators @@ -715,19 +784,16 @@ export const set: { (self: Cache, key: Key, value: A): Effect.Effect => core.withFiber((fiber) => { const exit = core.exitSucceed(value) - const deferred = Deferred.makeUnsafe() - Deferred.doneUnsafe(deferred, exit) + const entry = new EntryImpl(fiber, exit) const ttl = self.timeToLive(exit, key) if (Duration.isZero(ttl)) { MutableHashMap.remove(self.map, key) return effect.void } - MutableHashMap.set(self.map, key, { - deferred, - expiresAt: Duration.isFinite(ttl) - ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) - : undefined - }) + entry.expiresAt = Duration.isFinite(ttl) + ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl) + : undefined + MutableHashMap.set(self.map, key, entry) checkCapacity(self) return effect.void }) @@ -743,7 +809,7 @@ export const set: { * * **Example** (Checking for cached keys) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -753,17 +819,21 @@ export const set: { * }) * * // Check non-existent key - * console.log(yield* Cache.has(cache, "missing")) // false + * const missing = yield* Cache.has(cache, "missing") * * // Add entry and check existence * yield* Cache.get(cache, "hello") - * console.log(yield* Cache.has(cache, "hello")) // true + * const present = yield* Cache.has(cache, "hello") + * return [missing, present] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [false, true] * ``` * * **Example** (Checking TTL expiration) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -777,21 +847,25 @@ export const set: { * * // Add entry with TTL * yield* Cache.get(cache, "expires") - * console.log(yield* Cache.has(cache, "expires")) // true + * const initial = yield* Cache.has(cache, "expires") * * // Still valid before expiration * yield* TestClock.adjust("30 minutes") - * console.log(yield* Cache.has(cache, "expires")) // true + * const beforeExpiry = yield* Cache.has(cache, "expires") * * // Expired after TTL * yield* TestClock.adjust("31 minutes") - * console.log(yield* Cache.has(cache, "expires")) // false + * const afterExpiry = yield* Cache.has(cache, "expires") + * return [initial, beforeExpiry, afterExpiry] * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [true, true, false] * ``` * * **Example** (Checking multiple keys) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Checking multiple keys efficiently @@ -807,16 +881,16 @@ export const set: { * * // Check multiple keys * const keys = ["apple", "banana", "cherry", "date"] + * const results: Array = [] * for (const key of keys) { * const exists = yield* Cache.has(cache, key) - * console.log(`${key}: ${exists}`) + * results.push(`${key}: ${exists}`) * } - * // Output: - * // apple: true - * // banana: true - * // cherry: false - * // date: false + * return results * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["apple: true", "banana: true", "cherry: false", "date: false"] * ``` * * @category combinators @@ -839,7 +913,7 @@ export const has: { * * **Example** (Invalidating cached entries) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -850,11 +924,11 @@ export const has: { * * // Add a value to the cache * yield* Cache.get(cache, "hello") - * console.log(yield* Cache.has(cache, "hello")) // true + * const beforeInvalidation = yield* Cache.has(cache, "hello") * * // Invalidate the entry * yield* Cache.invalidate(cache, "hello") - * console.log(yield* Cache.has(cache, "hello")) // false + * const afterInvalidation = yield* Cache.has(cache, "hello") * * // Invalidating non-existent keys doesn't error * yield* Cache.invalidate(cache, "nonexistent") @@ -873,7 +947,11 @@ export const has: { * yield* Cache.get(cache2, "test") // lookupCount = 1 * yield* Cache.invalidate(cache2, "test") * yield* Cache.get(cache2, "test") // lookupCount = 2 (lookup called again) + * return { beforeInvalidation, afterInvalidation, lookupCount } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { beforeInvalidation: true, afterInvalidation: false, lookupCount: 2 } * ``` * * @category combinators @@ -893,7 +971,7 @@ export const invalidate: { * * **Example** (Invalidating entries conditionally) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -912,8 +990,7 @@ export const invalidate: { * "hello", * (value) => value === 5 * ) - * console.log(invalidated1) // true - * console.log(yield* Cache.has(cache, "hello")) // false + * const hasHello = yield* Cache.has(cache, "hello") * * // Don't invalidate when predicate doesn't match * const invalidated2 = yield* Cache.invalidateWhen( @@ -921,8 +998,7 @@ export const invalidate: { * "hi", * (value) => value === 5 * ) - * console.log(invalidated2) // false - * console.log(yield* Cache.has(cache, "hi")) // true (still present) + * const hasHi = yield* Cache.has(cache, "hi") * * // Returns false for non-existent keys * const invalidated3 = yield* Cache.invalidateWhen( @@ -930,7 +1006,6 @@ export const invalidate: { * "nonexistent", * () => true * ) - * console.log(invalidated3) // false * * // Returns false for failed cached values * const cacheWithErrors = yield* Cache.make({ @@ -945,8 +1020,11 @@ export const invalidate: { * "fail", * () => true * ) - * console.log(invalidated4) // false (can't invalidate failed values) + * return [invalidated1, hasHello, invalidated2, hasHi, invalidated3, invalidated4] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [true, false, false, true, false, false] * ``` * * @category combinators @@ -963,9 +1041,13 @@ export const invalidateWhen: { if (oentry === undefined) { return effect.succeed(false) } - return Deferred.await(oentry.deferred).pipe( + return oentry.await().pipe( effect.map((value) => { if (f(value)) { + const current = MutableHashMap.get(self.map, key) + if (Option.isNone(current) || current.value !== oentry) { + return false + } MutableHashMap.remove(self.map, key) return true } @@ -986,7 +1068,7 @@ export const invalidateWhen: { * * **Example** (Refreshing cached values) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Force refresh of existing cached values @@ -999,25 +1081,25 @@ export const invalidateWhen: { * * // Initial cache population * const value1 = yield* Cache.get(cache, "user") - * console.log(value1) // "user-1" * * // Get from cache (no lookup) * const value2 = yield* Cache.get(cache, "user") - * console.log(value2) // "user-1" (same value) * * // Force refresh - always calls lookup * const refreshed = yield* Cache.refresh(cache, "user") - * console.log(refreshed) // "user-2" (new value) * * // Subsequent gets return refreshed value * const value3 = yield* Cache.get(cache, "user") - * console.log(value3) // "user-2" + * return [value1, value2, refreshed, value3, counter] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["user-1", "user-1", "user-2", "user-2", 2] * ``` * * **Example** (Resetting TTL on refresh) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * import { TestClock } from "effect/testing" * @@ -1033,20 +1115,24 @@ export const invalidateWhen: { * yield* TestClock.adjust("45 minutes") * * // Entry would normally expire in 15 minutes - * console.log(yield* Cache.has(cache, "test")) // true + * const beforeRefresh = yield* Cache.has(cache, "test") * * // Refresh resets the TTL to full 1 hour * yield* Cache.refresh(cache, "test") * yield* TestClock.adjust("30 minutes") * * // Still valid because TTL was reset - * console.log(yield* Cache.has(cache, "test")) // true + * const afterRefresh = yield* Cache.has(cache, "test") + * return [beforeRefresh, afterRefresh] * }) + * + * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer())) + * actual // => [true, true] * ``` * * **Example** (Refreshing missing keys) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Refresh non-existent keys @@ -1058,11 +1144,14 @@ export const invalidateWhen: { * * // Refresh non-existent key creates new entry * const result = yield* Cache.refresh(cache, "newKey") - * console.log(result) // "value-for-newKey" * * // Verify it's now cached - * console.log(yield* Cache.has(cache, "newKey")) // true + * const cached = yield* Cache.has(cache, "newKey") + * return [result, cached] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => ["value-for-newKey", true] * ``` * * @category combinators @@ -1075,21 +1164,26 @@ export const refresh: { 2, (self: Cache, key: Key): Effect.Effect => core.withFiber((fiber) => { - const deferred = Deferred.makeUnsafe() - const entry: Entry = { - expiresAt: undefined, - deferred - } + const entry = new EntryImpl(fiber, self.lookup(key)) const existing = getImpl(self, key, fiber, false) !== undefined if (!existing) { MutableHashMap.set(self.map, key, entry) checkCapacity(self) } - return effect.onExit(self.lookup(key), (exit) => { - Deferred.doneUnsafe(deferred, exit) + entry.fiber.addObserver((exit) => { + if (effect.exitHasInterrupts(exit)) { + const current = MutableHashMap.get(self.map, key) + if (Option.isSome(current) && current.value === entry) { + MutableHashMap.remove(self.map, key) + } + return + } const ttl = self.timeToLive(exit, key) if (Duration.isZero(ttl)) { - MutableHashMap.remove(self.map, key) + const current = MutableHashMap.get(self.map, key) + if (existing || (Option.isSome(current) && current.value === entry)) { + MutableHashMap.remove(self.map, key) + } return effect.void } entry.expiresAt = Duration.isFinite(ttl) @@ -1097,9 +1191,10 @@ export const refresh: { : undefined if (existing) { MutableHashMap.set(self.map, key, entry) + checkCapacity(self) } - return effect.void }) + return entry.await() }) ) @@ -1108,7 +1203,7 @@ export const refresh: { * * **Example** (Invalidating all entries) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * // Clear all cached entries at once @@ -1123,18 +1218,29 @@ export const refresh: { * yield* Cache.get(cache, "banana") * yield* Cache.get(cache, "cherry") * - * console.log(yield* Cache.size(cache)) // 3 - * console.log(yield* Cache.has(cache, "apple")) // true + * const sizeBeforeInvalidation = yield* Cache.size(cache) + * const hasAppleBeforeInvalidation = yield* Cache.has(cache, "apple") * * // Clear all entries * yield* Cache.invalidateAll(cache) * * // Verify cache is empty - * console.log(yield* Cache.size(cache)) // 0 - * console.log(yield* Cache.has(cache, "apple")) // false - * console.log(yield* Cache.has(cache, "banana")) // false - * console.log(yield* Cache.has(cache, "cherry")) // false + * const sizeAfterInvalidation = yield* Cache.size(cache) + * const hasAppleAfterInvalidation = yield* Cache.has(cache, "apple") + * const hasBananaAfterInvalidation = yield* Cache.has(cache, "banana") + * const hasCherryAfterInvalidation = yield* Cache.has(cache, "cherry") + * return [ + * sizeBeforeInvalidation, + * hasAppleBeforeInvalidation, + * sizeAfterInvalidation, + * hasAppleAfterInvalidation, + * hasBananaAfterInvalidation, + * hasCherryAfterInvalidation + * ] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [3, true, 0, false, false, false] * ``` * * @category combinators @@ -1156,7 +1262,7 @@ export const invalidateAll = (self: Cache): Effect.E * * **Example** (Reading cache size) * - * ```ts + * ```ts import.meta.vitest * import { Cache, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -1167,19 +1273,20 @@ export const invalidateAll = (self: Cache): Effect.E * * // Empty cache has size 0 * const emptySize = yield* Cache.size(cache) - * console.log(emptySize) // 0 * * // Add entries and check size * yield* Cache.get(cache, "hello") * yield* Cache.get(cache, "world") * const sizeAfterAdding = yield* Cache.size(cache) - * console.log(sizeAfterAdding) // 2 * * // Size decreases after invalidation * yield* Cache.invalidate(cache, "hello") * const sizeAfterInvalidation = yield* Cache.size(cache) - * console.log(sizeAfterInvalidation) // 1 + * return [emptySize, sizeAfterAdding, sizeAfterInvalidation] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [0, 2, 1] * ``` * * @category combinators @@ -1193,7 +1300,7 @@ export const size = (self: Cache): Effect.Effect(self: Cache): Effect.Effect ["cache", "hello", "world"] * ``` * * @category combinators @@ -1236,7 +1345,7 @@ export const keys = (self: Cache): Effect.Effect(self: Cache): Effect.Effect [1, 2, 3] * ``` * * @category combinators @@ -1284,10 +1394,10 @@ export const entries = (self: Cache): Effect.Effect< const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() return effect.succeed(Iterable.filterMap(self.map, ([key, entry]) => { if (entry.expiresAt === undefined || entry.expiresAt > now) { - const exit = entry.deferred.effect - return !core.isExit(exit) || effect.exitIsFailure(exit) - ? Result.failVoid - : Result.succeed([key, exit.value as A]) + const exit = entry.fiber.pollUnsafe() + return exit && exit._tag === "Success" + ? Result.succeed([key, exit.value]) + : Result.failVoid } MutableHashMap.remove(self.map, key) return Result.failVoid diff --git a/packages/effect/src/Cause.ts b/packages/effect/src/Cause.ts index 5cc96c473..9cda574ab 100644 --- a/packages/effect/src/Cause.ts +++ b/packages/effect/src/Cause.ts @@ -63,12 +63,10 @@ export const ReasonTypeId: "~effect/Cause/Reason" = core.CauseReasonTypeId * * **Example** (Creating and inspecting a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.fail("Something went wrong") - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isFailReason(cause.reasons[0])) // true + * Cause.fail("Something went wrong") // => Cause.fail("Something went wrong") * ``` * * @category models @@ -84,11 +82,11 @@ export interface Cause extends Pipeable, Inspectable, Equal { * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isCause(Cause.fail("error"))) // true - * console.log(Cause.isCause("not a cause")) // false + * Cause.isCause(Cause.fail("error")) // => true + * Cause.isCause("not a cause") // => false * ``` * * @category guards @@ -101,12 +99,12 @@ export const isCause: (self: unknown) => self is Cause = core.isCause * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const reason = Cause.fail("error").reasons[0] - * console.log(Cause.isReason(reason)) // true - * console.log(Cause.isReason("not a reason")) // false + * Cause.isReason(reason) // => true + * Cause.isReason("not a reason") // => false * ``` * * @category guards @@ -131,12 +129,12 @@ export const isReason: (self: unknown) => self is Reason = core.isCause * * **Example** (Narrowing a reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const reason = Cause.fail("error").reasons[0] * if (Cause.isFailReason(reason)) { - * console.log(reason.error) // "error" + * reason.error // => "error" * } * ``` * @@ -155,12 +153,12 @@ export type Reason = Fail | Die | Interrupt * * **Example** (Filtering fail reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.fail("error") * const fails = cause.reasons.filter(Cause.isFailReason) - * console.log(fails[0].error) // "error" + * fails[0].error // => "error" * ``` * * @see {@link isDieReason} — narrow to `Die` @@ -181,12 +179,12 @@ export const isFailReason: (self: Reason) => self is Fail = core.isFail * * **Example** (Filtering die reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.die("defect") * const dies = cause.reasons.filter(Cause.isDieReason) - * console.log(dies[0].defect) // "defect" + * dies[0].defect // => "defect" * ``` * * @see {@link isFailReason} — narrow to `Fail` @@ -207,12 +205,12 @@ export const isDieReason: (self: Reason) => self is Die = core.isDieReason * * **Example** (Filtering interrupt reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.interrupt(123) * const interrupts = cause.reasons.filter(Cause.isInterruptReason) - * console.log(interrupts[0].fiberId) // 123 + * interrupts[0].fiberId // => 123 * ``` * * @see {@link isFailReason} — narrow to `Fail` @@ -234,14 +232,14 @@ export declare namespace Cause { * * **Example** (Extracting the error type) * - * ```ts + * ```ts import.meta.vitest * import type { Cause } from "effect" * * // string * type E = Cause.Cause.Error> * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Error = T extends Cause ? E : never @@ -263,7 +261,7 @@ export declare namespace Cause { readonly [ReasonTypeId]: typeof ReasonTypeId readonly _tag: Tag readonly annotations: ReadonlyMap - annotate(annotations: Context.Context | ReadonlyMap, options?: { + annotate(annotations: Context.Context, options?: { readonly overwrite?: boolean | undefined }): this } @@ -280,14 +278,14 @@ export declare namespace Reason { * * **Example** (Extracting the error type) * - * ```ts + * ```ts import.meta.vitest * import type { Cause } from "effect" * * // string * type E = Cause.Reason.Error> * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Error = T extends Reason ? E : never @@ -309,13 +307,13 @@ export declare namespace Reason { * * **Example** (Accessing the defect) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.die("Unexpected") * const reason = cause.reasons[0] * if (Cause.isDieReason(reason)) { - * console.log(reason.defect) // "Unexpected" + * reason.defect // => "Unexpected" * } * ``` * @@ -344,13 +342,13 @@ export interface Die extends Cause.ReasonProto<"Die"> { * * **Example** (Accessing the error) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.fail("Something went wrong") * const reason = cause.reasons[0] * if (Cause.isFailReason(reason)) { - * console.log(reason.error) // "Something went wrong" + * reason.error // => "Something went wrong" * } * ``` * @@ -374,13 +372,13 @@ export interface Fail extends Cause.ReasonProto<"Fail"> { * * **Example** (Accessing the fiber ID) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.interrupt(123) * const reason = cause.reasons[0] * if (Cause.isInterruptReason(reason)) { - * console.log(reason.fiberId) // 123 + * reason.fiberId // => 123 * } * ``` * @@ -412,15 +410,14 @@ export interface Interrupt extends Cause.ReasonProto<"Interrupt"> { * * **Example** (Building a cause from reasons) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const reasons = [ * Cause.makeFailReason("err1"), * Cause.makeFailReason("err2") * ] - * const cause = Cause.fromReasons(reasons) - * console.log(cause.reasons.length) // 2 + * Cause.fromReasons(reasons) // => Cause.combine(Cause.fail("err1"), Cause.fail("err2")) * ``` * * @see {@link combine} — merge two existing causes @@ -447,13 +444,10 @@ export const fromReasons: ( * * **Example** (Combining with the empty cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.combine(Cause.empty, Cause.fail("boom")) - * - * console.log(cause.reasons.length) // 1 - * console.log(Cause.hasFails(cause)) // true + * Cause.combine(Cause.empty, Cause.fail("boom")) // => Cause.fail("boom") * ``` * * @see {@link combine} for merging causes where `empty` acts as the identity @@ -473,12 +467,10 @@ export const empty: Cause = core.causeEmpty * * **Example** (Creating a fail cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.fail("Something went wrong") - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isFailReason(cause.reasons[0])) // true + * Cause.fail("Something went wrong") // => Cause.fromReasons([Cause.makeFailReason("Something went wrong")]) * ``` * * @see {@link die} — for untyped defects @@ -499,12 +491,10 @@ export const fail: (error: E) => Cause = core.causeFail * * **Example** (Creating a die cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.die("Unexpected") - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isDieReason(cause.reasons[0])) // true + * Cause.die("Unexpected") // => Cause.fromReasons([Cause.makeDieReason("Unexpected")]) * ``` * * @see {@link fail} — for typed errors @@ -521,12 +511,10 @@ export const die: (defect: unknown) => Cause = core.causeDie * * **Example** (Creating an interrupt cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause = Cause.interrupt(123) - * console.log(cause.reasons.length) // 1 - * console.log(Cause.isInterruptReason(cause.reasons[0])) // true + * Cause.interrupt(123) // => Cause.fromReasons([Cause.makeInterruptReason(123)]) * ``` * * @see {@link fail} — for typed errors @@ -547,12 +535,10 @@ export const interrupt: (fiberId?: number | undefined) => Cause = effect. * * **Example** (Creating a Fail reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const reason = Cause.makeFailReason("error") - * console.log(reason._tag) // "Fail" - * console.log(reason.error) // "error" + * Cause.makeFailReason("error") // => Cause.fail("error").reasons[0] * ``` * * @see {@link makeDieReason} — create a `Die` reason @@ -573,12 +559,10 @@ export const makeFailReason = (error: E): Fail => new core.Fail(error) * * **Example** (Creating a Die reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const reason = Cause.makeDieReason("bug") - * console.log(reason._tag) // "Die" - * console.log(reason.defect) // "bug" + * Cause.makeDieReason("bug") // => Cause.die("bug").reasons[0] * ``` * * @see {@link makeFailReason} — create a `Fail` reason @@ -600,12 +584,10 @@ export const makeDieReason = (defect: unknown): Die => new core.Die(defect) * * **Example** (Creating an Interrupt reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const reason = Cause.makeInterruptReason(42) - * console.log(reason._tag) // "Interrupt" - * console.log(reason.fiberId) // 42 + * Cause.makeInterruptReason(42) // => Cause.interrupt(42).reasons[0] * ``` * * @see {@link makeFailReason} — create a `Fail` reason @@ -626,12 +608,12 @@ export const makeInterruptReason: (fiberId?: number | undefined) => Interrupt = * * **Example** (Checking interrupt-only causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasInterruptsOnly(Cause.interrupt(123))) // true - * console.log(Cause.hasInterruptsOnly(Cause.fail("error"))) // false - * console.log(Cause.hasInterruptsOnly(Cause.empty)) // false + * Cause.hasInterruptsOnly(Cause.interrupt(123)) // => true + * Cause.hasInterruptsOnly(Cause.fail("error")) // => false + * Cause.hasInterruptsOnly(Cause.empty) // => false * ``` * * @see {@link hasInterrupts} — `true` if the cause contains *any* interrupts @@ -659,14 +641,14 @@ export const hasInterruptsOnly: (self: Cause) => boolean = effect.hasInter * * **Example** (Mapping errors to uppercase) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * * const cause = Cause.fail("error") * const mapped = Cause.map(cause, (e) => e.toUpperCase()) * const reason = mapped.reasons[0] * if (Cause.isFailReason(reason)) { - * console.log(reason.error) // "ERROR" + * reason.error // => "ERROR" * } * ``` * @@ -694,13 +676,11 @@ export const map: { * * **Example** (Combining two causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const cause1 = Cause.fail("error1") - * const cause2 = Cause.fail("error2") - * const combined = Cause.combine(cause1, cause2) - * console.log(combined.reasons.length) // 2 + * const combined = Cause.combine(Cause.fail("error1"), Cause.fail("error2")) + * combined // => Cause.fromReasons([Cause.makeFailReason("error1"), Cause.makeFailReason("error2")]) * ``` * * @see {@link fromReasons} — build a cause from an array of reasons @@ -740,11 +720,11 @@ export const combine: { * * **Example** (Squashing a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.squash(Cause.fail("error"))) // "error" - * console.log(Cause.squash(Cause.die("defect"))) // "defect" + * Cause.squash(Cause.fail("error")) // => "error" + * Cause.squash(Cause.die("defect")) // => "defect" * ``` * * @see {@link prettyErrors} — non-lossy conversion to `Array` @@ -765,11 +745,11 @@ export const squash: (self: Cause) => unknown = effect.causeSquash * * **Example** (Checking for typed errors) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasFails(Cause.fail("error"))) // true - * console.log(Cause.hasFails(Cause.die("defect"))) // false + * Cause.hasFails(Cause.fail("error")) // => true + * Cause.hasFails(Cause.die("defect")) // => false * ``` * * @see {@link hasDies} — check for defects @@ -793,13 +773,10 @@ export const hasFails: (self: Cause) => boolean = effect.hasFails * * **Example** (Extracting the first Fail reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findFail(Cause.fail("error")) - * if (!Result.isFailure(result)) { - * console.log(result.success.error) // "error" - * } + * Cause.findFail(Cause.fail("error")) // => Result.succeed(Cause.makeFailReason("error")) * ``` * * @see {@link findError} — extract the unwrapped `E` value @@ -823,13 +800,10 @@ export const findFail: (self: Cause) => Result.Result, Cause Result.succeed("error") * ``` * * @see {@link findFail} — extract the full `Fail` reason @@ -851,14 +825,11 @@ export const findError: (self: Cause) => Result.Result> = * * **Example** (Extracting an error as Option) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Option } from "effect" * - * const some = Cause.findErrorOption(Cause.fail("error")) - * console.log(Option.isSome(some)) // true - * - * const none = Cause.findErrorOption(Cause.die("defect")) - * console.log(Option.isNone(none)) // true + * Cause.findErrorOption(Cause.fail("error")) // => Option.some("error") + * Cause.findErrorOption(Cause.die("defect")) // => Option.none() * ``` * * @see {@link findError} — `Result`-based variant @@ -878,11 +849,11 @@ export const findErrorOption: (input: Cause) => Option = effect.findErr * * **Example** (Checking for defects) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasDies(Cause.die("defect"))) // true - * console.log(Cause.hasDies(Cause.fail("error"))) // false + * Cause.hasDies(Cause.die("defect")) // => true + * Cause.hasDies(Cause.fail("error")) // => false * ``` * * @see {@link hasFails} — check for typed errors @@ -905,13 +876,10 @@ export const hasDies: (self: Cause) => boolean = effect.hasDies * * **Example** (Extracting the first Die reason) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findDie(Cause.die("defect")) - * if (!Result.isFailure(result)) { - * console.log(result.success.defect) // "defect" - * } + * Cause.findDie(Cause.die("defect")) // => Result.succeed(Cause.makeDieReason("defect")) * ``` * * @see {@link findDefect} — extract the unwrapped defect value @@ -934,13 +902,10 @@ export const findDie: (self: Cause) => Result.Result> = effe * * **Example** (Extracting the first defect) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findDefect(Cause.die("defect")) - * if (!Result.isFailure(result)) { - * console.log(result.success) // "defect" - * } + * Cause.findDefect(Cause.die("defect")) // => Result.succeed("defect") * ``` * * @see {@link findDie} — extract the full `Die` reason @@ -956,11 +921,11 @@ export const findDefect: (self: Cause) => Result.Result> * * **Example** (Checking for interruptions) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.hasInterrupts(Cause.interrupt(123))) // true - * console.log(Cause.hasInterrupts(Cause.fail("error"))) // false + * Cause.hasInterrupts(Cause.interrupt(123)) // => true + * Cause.hasInterrupts(Cause.fail("error")) // => false * ``` * * @see {@link hasInterruptsOnly} — `true` only when *all* reasons are interrupts @@ -984,13 +949,10 @@ export const hasInterrupts: (self: Cause) => boolean = effect.hasInterrupt * * **Example** (Extracting the first interrupt) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.findInterrupt(Cause.interrupt(42)) - * if (!Result.isFailure(result)) { - * console.log(result.success.fiberId) // 42 - * } + * Cause.findInterrupt(Cause.interrupt(42)) // => Result.succeed(Cause.makeInterruptReason(42)) * ``` * * @see {@link interruptors} — collect all interrupting fiber IDs as a `Set` @@ -1012,7 +974,7 @@ export const findInterrupt: (self: Cause) => Result.Result(self: Cause) => Result.Result new Set([1, 2]) * ``` * * @see {@link filterInterruptors} — `Result`-based variant * - * @category accessors + * @category getters * @since 2.0.0 */ export const interruptors: (self: Cause) => ReadonlySet = effect.causeInterruptors @@ -1048,13 +1010,10 @@ export const interruptors: (self: Cause) => ReadonlySet = effect.c * * **Example** (Extracting interruptors with Result) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Result } from "effect" * - * const result = Cause.filterInterruptors(Cause.interrupt(1)) - * if (!Result.isFailure(result)) { - * console.log(result.success) // Set(1) { 1 } - * } + * Cause.filterInterruptors(Cause.interrupt(1)) // => Result.succeed(new Set([1])) * ``` * * @see {@link interruptors} — always-succeeding variant @@ -1094,21 +1053,21 @@ export const filterInterruptors: (self: Cause) => Result.Result "boom" * ``` * * @see {@link pretty} — renders the cause as a single string * @see {@link squash} — lossy collapse to a single thrown value * - * @category rendering + * @category formatting * @since 3.2.0 */ -export const prettyErrors: (self: Cause) => Array = effect.causePrettyErrors +export const prettyErrors: (self: Cause, options?: { + readonly includeCauseInStack?: boolean | undefined +}) => Array = effect.causePrettyErrors /** * Formats a `Cause` as a human-readable string for logging or debugging. @@ -1142,16 +1101,15 @@ export const prettyErrors: (self: Cause) => Array = effect.causePre * * **Example** (Rendering a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const rendered = Cause.pretty(Cause.fail("something went wrong")) - * console.log(rendered.includes("something went wrong")) // true + * Cause.pretty(Cause.fail("something went wrong")).includes("something went wrong") // => true * ``` * * @see {@link prettyErrors} — get the individual `Error` instances * - * @category rendering + * @category formatting * @since 2.0.0 */ export const pretty: (cause: Cause) => string = effect.causePretty @@ -1169,14 +1127,16 @@ export const pretty: (cause: Cause) => string = effect.causePretty * * **Example** (Yielding an error in Effect.gen) * - * ```ts - * import { Cause, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" * * const error = new Cause.NoSuchElementError("not found") * * const program = Effect.gen(function*() { * return yield* error // fails the effect with NoSuchElementError * }) + * + * await Effect.runPromiseExit(program) // => Exit.fail(error) * ``` * * @category errors @@ -1192,11 +1152,11 @@ export interface YieldableError extends Error, Pipeable, Inspectable { * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isNoSuchElementError(new Cause.NoSuchElementError())) // true - * console.log(Cause.isNoSuchElementError("nope")) // false + * Cause.isNoSuchElementError(new Cause.NoSuchElementError()) // => true + * Cause.isNoSuchElementError("nope") // => false * ``` * * @category guards @@ -1231,16 +1191,6 @@ export const NoSuchElementErrorTypeId: "~effect/Cause/NoSuchElementError" = core * expected case. This error is mainly for APIs that intentionally turn absence * into a thrown value or failed effect. * - * **Example** (Creating and checking a NoSuchElementError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.NoSuchElementError("Element not found") - * console.log(error._tag) // "NoSuchElementError" - * console.log(error.message) // "Element not found" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1259,11 +1209,10 @@ export interface NoSuchElementError extends YieldableError { * * **Example** (Creating a NoSuchElementError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.NoSuchElementError("Element not found") - * console.log(error.message) // "Element not found" + * new Cause.NoSuchElementError("Element not found").message // => "Element not found" * ``` * * @see {@link isNoSuchElementError} for checking unknown values @@ -1278,11 +1227,11 @@ export const NoSuchElementError: new(message?: string) => NoSuchElementError = c * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isDone(Cause.Done())) // true - * console.log(Cause.isDone("not done")) // false + * Cause.isDone(Cause.Done()) // => true + * Cause.isDone("not done") // => false * ``` * * @category guards @@ -1315,7 +1264,7 @@ export const DoneTypeId: "~effect/Cause/Done" = core.DoneTypeId * * **Example** (Signaling queue completion) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1323,9 +1272,12 @@ export const DoneTypeId: "~effect/Cause/Done" = core.DoneTypeId * yield* Queue.offer(queue, 1) * yield* Queue.end(queue) * + * yield* Queue.take(queue) * const result = yield* Effect.flip(Queue.take(queue)) - * console.log(Cause.isDone(result)) // true + * return Cause.isDone(result) * }) + * + * await Effect.runPromise(program) // => true * ``` * * @category errors @@ -1385,14 +1337,12 @@ export const Done: (value?: A) => Done = core.Done * * **Example** (Failing with Done) * - * ```ts - * import { Cause, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" * * const program = Cause.done("finished") * - * Effect.runPromiseExit(program).then((exit) => { - * console.log(exit._tag) // "Failure" - * }) + * await Effect.runPromiseExit(program) // => Exit.fail(Cause.Done("finished")) * ``` * * @see {@link Done} — create the signal value without an Effect @@ -1415,11 +1365,11 @@ export const TimeoutErrorTypeId: "~effect/Cause/TimeoutError" = effect.TimeoutEr * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isTimeoutError(new Cause.TimeoutError())) // true - * console.log(Cause.isTimeoutError("nope")) // false + * Cause.isTimeoutError(new Cause.TimeoutError()) // => true + * Cause.isTimeoutError("nope") // => false * ``` * * @category guards @@ -1435,16 +1385,6 @@ export const isTimeoutError: (u: unknown) => u is TimeoutError = effect.isTimeou * Produced by `Effect.timeout` and related APIs. Implements * `YieldableError`. * - * **Example** (Creating and checking a TimeoutError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.TimeoutError("Operation timed out") - * console.log(error._tag) // "TimeoutError" - * console.log(error.message) // "Operation timed out" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1458,11 +1398,10 @@ export interface TimeoutError extends YieldableError { * * **Example** (Creating a TimeoutError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.TimeoutError("Operation timed out") - * console.log(error.message) // "Operation timed out" + * new Cause.TimeoutError("Operation timed out").message // => "Operation timed out" * ``` * * @category constructors @@ -1483,11 +1422,11 @@ export const IllegalArgumentErrorTypeId: "~effect/Cause/IllegalArgumentError" = * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isIllegalArgumentError(new Cause.IllegalArgumentError())) // true - * console.log(Cause.isIllegalArgumentError("nope")) // false + * Cause.isIllegalArgumentError(new Cause.IllegalArgumentError()) // => true + * Cause.isIllegalArgumentError("nope") // => false * ``` * * @category guards @@ -1503,16 +1442,6 @@ export const isIllegalArgumentError: (u: unknown) => u is IllegalArgumentError = * * Implements `YieldableError`. * - * **Example** (Creating and checking an IllegalArgumentError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.IllegalArgumentError("Expected positive number") - * console.log(error._tag) // "IllegalArgumentError" - * console.log(error.message) // "Expected positive number" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1526,11 +1455,10 @@ export interface IllegalArgumentError extends YieldableError { * * **Example** (Creating an IllegalArgumentError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.IllegalArgumentError("Invalid argument") - * console.log(error.message) // "Invalid argument" + * new Cause.IllegalArgumentError("Invalid argument").message // => "Invalid argument" * ``` * * @category constructors @@ -1543,11 +1471,11 @@ export const IllegalArgumentError: new(message?: string) => IllegalArgumentError * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isExceededCapacityError(new Cause.ExceededCapacityError())) // true - * console.log(Cause.isExceededCapacityError("nope")) // false + * Cause.isExceededCapacityError(new Cause.ExceededCapacityError()) // => true + * Cause.isExceededCapacityError("nope") // => false * ``` * * @category guards @@ -1576,16 +1504,6 @@ export const ExceededCapacityErrorTypeId: "~effect/Cause/ExceededCapacityError" * * Implements `YieldableError`. * - * **Example** (Creating and checking an ExceededCapacityError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.ExceededCapacityError("Queue full") - * console.log(error._tag) // "ExceededCapacityError" - * console.log(error.message) // "Queue full" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1603,11 +1521,10 @@ export interface ExceededCapacityError extends YieldableError { * * **Example** (Creating an ExceededCapacityError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.ExceededCapacityError("Queue full") - * console.log(error.message) // "Queue full" + * new Cause.ExceededCapacityError("Queue full").message // => "Queue full" * ``` * * @see {@link isExceededCapacityError} for checking unknown values @@ -1631,15 +1548,14 @@ export const AsyncFiberErrorTypeId: "~effect/Cause/AsyncFiberError" = effect.Asy * * **Example** (Checking the runtime type) * - * ```ts - * import { Cause } from "effect" - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" * - * declare const fiber: Fiber.Fiber + * const fiber = Effect.runFork(Effect.void) * * const error = new Cause.AsyncFiberError(fiber) - * console.log(Cause.isAsyncFiberError(error)) // true - * console.log(Cause.isAsyncFiberError("nope")) // false + * Cause.isAsyncFiberError(error) // => true + * Cause.isAsyncFiberError("nope") // => false * ``` * * @category guards @@ -1662,15 +1578,14 @@ export const isAsyncFiberError: (u: unknown) => u is AsyncFiberError = effect.is * * **Example** (Accessing the fiber) * - * ```ts - * import { Cause } from "effect" - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" * - * declare const fiber: Fiber.Fiber + * const fiber = Effect.runFork(Effect.void) * - * const error = new Cause.AsyncFiberError(fiber) - * console.log(error._tag) // "AsyncFiberError" - * console.log(error.fiber === fiber) // true + * const value = new Cause.AsyncFiberError(fiber) + * const isSameFiber = value.fiber === fiber + * isSameFiber // => true * ``` * * @category errors @@ -1693,14 +1608,12 @@ export interface AsyncFiberError extends YieldableError { * * **Example** (Creating an AsyncFiberError) * - * ```ts - * import { Cause } from "effect" - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" * - * declare const fiber: Fiber.Fiber + * const fiber = Effect.runFork(Effect.void) * - * const error = new Cause.AsyncFiberError(fiber) - * console.log(error.message) // "An asynchronous Effect was executed with Effect.runSync" + * new Cause.AsyncFiberError(fiber).message // => "An asynchronous Effect was executed with Effect.runSync" * ``` * * @see {@link isAsyncFiberError} for checking unknown values @@ -1723,11 +1636,11 @@ export const UnknownErrorTypeId: "~effect/Cause/UnknownError" = effect.UnknownEr * * **Example** (Checking the runtime type) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * console.log(Cause.isUnknownError(new Cause.UnknownError("x"))) // true - * console.log(Cause.isUnknownError("nope")) // false + * Cause.isUnknownError(new Cause.UnknownError("x")) // => true + * Cause.isUnknownError("nope") // => false * ``` * * @category guards @@ -1744,16 +1657,6 @@ export const isUnknownError: (u: unknown) => u is UnknownError = effect.isUnknow * typed error. The original value is stored in the `cause` property inherited * from `Error`. Implements `YieldableError`. * - * **Example** (Creating and checking an UnknownError) - * - * ```ts - * import { Cause } from "effect" - * - * const error = new Cause.UnknownError("original", "Something unknown") - * console.log(error._tag) // "UnknownError" - * console.log(error.message) // "Something unknown" - * ``` - * * @category errors * @since 4.0.0 */ @@ -1769,11 +1672,10 @@ export interface UnknownError extends YieldableError { * * **Example** (Creating an UnknownError) * - * ```ts + * ```ts import.meta.vitest * import { Cause } from "effect" * - * const error = new Cause.UnknownError({ raw: true }, "Unexpected value") - * console.log(error.message) // "Unexpected value" + * new Cause.UnknownError({ raw: true }, "Unexpected value").message // => "Unexpected value" * ``` * * @category constructors @@ -1800,15 +1702,13 @@ export const UnknownError: new(cause: unknown, message?: string) => UnknownError * * **Example** (Annotating a cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Context } from "effect" * * class RequestId extends Context.Service()("RequestId") {} * - * const cause = Cause.fail("error") - * const annotated = Cause.annotate(cause, Context.make(RequestId, "req-1")) - * - * console.log(Context.getOrUndefined(Cause.annotations(annotated), RequestId)) // "req-1" + * const annotated = Cause.annotate(Cause.fail("error"), Context.make(RequestId, "req-1")) + * Context.getOrUndefined(Cause.annotations(annotated), RequestId) // => "req-1" * ``` * * @see {@link annotations} for reading merged annotations from a cause @@ -1839,7 +1739,7 @@ export const annotate: { * * **Example** (Reading reason annotations) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Context } from "effect" * * class RequestId extends Context.Service()("RequestId") {} @@ -1847,7 +1747,7 @@ export const annotate: { * const reason = Cause.makeFailReason("error") * const annotated = reason.annotate(Context.make(RequestId, "req-1")) * - * console.log(Context.getOrUndefined(Cause.reasonAnnotations(annotated), RequestId)) // "req-1" + * Context.getOrUndefined(Cause.reasonAnnotations(annotated), RequestId) // => "req-1" * ``` * * @see {@link annotations} — merged annotations from all reasons in a cause @@ -1871,7 +1771,7 @@ export const reasonAnnotations: (self: Reason) => Context.Context = * * **Example** (Reading merged annotations) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Context } from "effect" * * class RequestId extends Context.Service()("RequestId") {} @@ -1881,7 +1781,7 @@ export const reasonAnnotations: (self: Reason) => Context.Context = * Context.make(RequestId, "req-1") * ) * - * console.log(Context.getOrUndefined(Cause.annotations(cause), RequestId)) // "req-1" + * Context.getOrUndefined(Cause.annotations(cause), RequestId) // => "req-1" * ``` * * @see {@link reasonAnnotations} — annotations from a single reason @@ -1909,7 +1809,7 @@ export const annotations: (self: Cause) => Context.Context = effect * @see {@link annotations} for reading merged annotations from a cause * @see {@link InterruptorStackTrace} for the interrupt-specific stack-frame annotation * - * @category annotations + * @category services * @since 4.0.0 */ export class StackTrace extends Context.Service()("effect/Cause/StackTrace") {} @@ -1931,7 +1831,7 @@ export class StackTrace extends Context.Service()("effec * @see {@link reasonAnnotations} for reading annotations from a single reason * @see {@link annotate} for attaching annotations to a cause * - * @category annotations + * @category services * @since 4.0.0 */ export class InterruptorStackTrace diff --git a/packages/effect/src/Channel.ts b/packages/effect/src/Channel.ts index f094d9bad..930672fb3 100644 --- a/packages/effect/src/Channel.ts +++ b/packages/effect/src/Channel.ts @@ -20,12 +20,14 @@ import * as Fiber from "./Fiber.ts" import type * as Filter from "./Filter.ts" import type { LazyArg } from "./Function.ts" import { constant, constTrue, constVoid, dual, identity as identity_ } from "./Function.ts" -import { ClockRef, endSpan } from "./internal/effect.ts" +import * as Count from "./internal/count.ts" +import { ClockRef, endSpan, scopeFinalizerCountUnsafe } from "./internal/effect.ts" import { addSpanStackTrace } from "./internal/tracer.ts" import * as Iterable from "./Iterable.ts" import * as Latch from "./Latch.ts" import * as Layer from "./Layer.ts" import type { Severity } from "./LogLevel.ts" +import * as MutableRef from "./MutableRef.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" @@ -67,12 +69,12 @@ export const TypeId: TypeId = "~effect/Channel" * * **Example** (Checking for channels) * - * ```ts + * ```ts import.meta.vitest * import { Channel } from "effect" * * const channel = Channel.succeed(42) - * console.log(Channel.isChannel(channel)) // true - * console.log(Channel.isChannel("not a channel")) // false + * Channel.isChannel(channel) // => true + * Channel.isChannel("not a channel") // => false * ``` * * @category guards @@ -109,8 +111,8 @@ export const isChannel = ( * * **Example** (Typing channels) * - * ```ts - * import type { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // A channel that outputs numbers and requires no environment * type NumberChannel = Channel.Channel @@ -128,6 +130,9 @@ export const isChannel = ( * boolean, // InDone - input completion * { db: string } // Env - required environment * > + * + * const channel: NumberChannel = Channel.succeed(1) + * Effect.runSync(Channel.runCollect(channel)) // => [1] * ``` * * @category models @@ -264,12 +269,13 @@ const ChannelProto = { * * **Example** (Creating channels from transforms) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * * const channel = Channel.fromTransform((upstream, scope) => * Effect.succeed(upstream) * ) + * await Effect.runPromise(Channel.runCollect(channel)) // => [] * ``` * * @category constructors @@ -300,7 +306,7 @@ export const fromTransform = value * 2) * ) * ) - * // Outputs: 2, 4, 6 + * await Effect.runPromise(Channel.runCollect(transformedChannel)) // => [2, 4, 6] * ``` * * @category constructors @@ -354,12 +360,18 @@ export const transformPull = < * * **Example** (Creating channels from pulls) * - * ```ts - * import { Channel, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Effect } from "effect" * - * const channel = Channel.fromPull( - * Effect.succeed(Effect.succeed(42)) - * ) + * const channel = Channel.fromPull(Effect.sync(() => { + * let emitted = false + * return Effect.suspend(() => { + * if (emitted) return Cause.done() + * emitted = true + * return Effect.succeed(42) + * }) + * })) + * await Effect.runPromise(Channel.runCollect(channel)) // => [42] * ``` * * @category constructors @@ -410,12 +422,13 @@ export const fromTransformBracket = "function" + * Effect.runSync(Channel.runCollect(channel)) // => [42] * ``` * * @category destructors @@ -433,10 +446,10 @@ export const toTransform = 4096 * ``` * * @category constants @@ -465,7 +478,7 @@ const asyncQueue = ( * * **Example** (Creating channels from callbacks) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect, Queue } from "effect" * * const channel = Channel.callback((queue) => @@ -473,8 +486,10 @@ const asyncQueue = ( * yield* Queue.offer(queue, 1) * yield* Queue.offer(queue, 2) * yield* Queue.offer(queue, 3) + * yield* Queue.end(queue) * }) * ) + * await Effect.runPromise(Channel.runCollect(channel)) // => [1, 2, 3] * ``` * * @category constructors @@ -494,14 +509,15 @@ export const callback = ( * * **Example** (Creating array channels from callbacks) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect, Queue } from "effect" * * const channel = Channel.callbackArray(Effect.fn(function*(queue) { * yield* Queue.offer(queue, 1) * yield* Queue.offer(queue, 2) + * yield* Queue.end(queue) * })) - * // Emits arrays of numbers instead of individual numbers + * await Effect.runPromise(Channel.runCollect(channel)) // => [[1, 2]] * ``` * * @category constructors @@ -521,11 +537,11 @@ export const callbackArray = ( * * **Example** (Suspending channel creation) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const channel = Channel.suspend(() => Channel.succeed(42)) - * // The inner channel is not created until the suspended channel is run + * Effect.runSync(Channel.runCollect(channel)) // => [42] * ``` * * @category constructors @@ -548,14 +564,16 @@ export const suspend = ( * * **Example** (Managing resources with acquire-use-release) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * + * const released: Array = [] * const channel = Channel.acquireUseRelease( * Effect.succeed("resource"), * (resource) => Channel.succeed(resource.toUpperCase()), - * (resource, exit) => Effect.log(`Released: ${resource}`) + * (resource, exit) => Effect.sync(() => released.push(resource)) * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channel)), released] // => [["RESOURCE"], ["resource"]] * ``` * * @category constructors @@ -591,13 +609,15 @@ export const acquireUseRelease = = [] * const channel = Channel.acquireRelease( * Effect.succeed("resource"), - * (resource, exit) => Effect.log(`Released: ${resource}`) + * (resource, exit) => Effect.sync(() => released.push(resource)) * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channel)), released] // => [["resource"], ["resource"]] * ``` * * @category constructors @@ -625,12 +645,12 @@ export const acquireRelease: { * * **Example** (Creating channels from iterators) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const channel = Channel.fromIterator(() => numbers[Symbol.iterator]()) - * // Emits: 1, 2, 3, 4, 5 + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -652,11 +672,11 @@ export const fromIterator = (iterator: LazyArg>): Channel [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -673,12 +693,12 @@ export const fromArray = (array: ReadonlyArray): Channel => * * **Example** (Creating channels from chunks) * - * ```ts - * import { Channel, Chunk } from "effect" + * ```ts import.meta.vitest + * import { Channel, Chunk, Effect } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const channel = Channel.fromChunk(chunk) - * // Emits: 1, 2, 3 + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3] * ``` * * @category constructors @@ -689,10 +709,15 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu /** * Creates a `Channel` from an iterator that emits arrays of elements. * + * **Details** + * + * Finite fractional `chunkSize` values are rounded down. `NaN` and non-positive + * values are treated as `1` so every successful pull emits a non-empty array. + * * **Example** (Batching iterator output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel from a simple iterator * const numberIterator = (): Iterator => { @@ -708,13 +733,13 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu * } * * const channel = Channel.fromIteratorArray(() => numberIterator(), 2) - * // This will emit arrays: [0, 1], [2], then complete with "finished" + * Effect.runSync(Channel.runCollect(channel)) // => [[0, 1], [2]] * ``` * * **Example** (Batching generator output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create channel from a generator function * function* fibonacci(): Generator { @@ -726,7 +751,7 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu * } * * const fibChannel = Channel.fromIteratorArray(() => fibonacci(), 3) - * // Emits: [0, 1, 1], [2, 3], then completes + * Effect.runSync(Channel.runCollect(fibChannel)) // => [[0, 1, 1], [2, 3]] * ``` * * @category constructors @@ -735,15 +760,16 @@ export const fromChunk = (chunk: Chunk.Chunk): Channel => fromArray(Chu export const fromIteratorArray = ( iterator: LazyArg>, chunkSize = DefaultChunkSize -): Channel, never, L> => - fromPull( +): Channel, never, L> => { + const size = Count.normalizeNonEmpty(chunkSize) + return fromPull( Effect.sync(() => { const iter = iterator() let done = Option.none() return Effect.suspend(() => { if (done._tag === "Some") return Cause.done(done.value) const buffer: Array = [] - while (buffer.length < chunkSize) { + while (buffer.length < size) { const state = iter.next() if (state.done) { if (buffer.length === 0) { @@ -758,18 +784,19 @@ export const fromIteratorArray = ( }) }) ) +} /** * Creates a `Channel` that emits all elements from an iterable. * * **Example** (Creating channels from iterables) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const set = new Set([1, 2, 3]) * const channel = Channel.fromIterable(set) - * // Emits: 1, 2, 3 + * Effect.runSync(Channel.runCollect(channel)) // => [1, 2, 3] * ``` * * @category constructors @@ -781,14 +808,19 @@ export const fromIterable = (iterable: Iterable): Channel [[1, 2, 3, 4], [5]] * ``` * * @category constructors @@ -804,11 +836,11 @@ export const fromIterableArray = ( * * **Example** (Creating channels that succeed) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const channel = Channel.succeed(42) - * // Emits: 42 + * Effect.runSync(Channel.runCollect(channel)) // => [42] * ``` * * @category constructors @@ -821,11 +853,11 @@ export const succeed = (value: A): Channel => fromEffect(Effect.succeed(va * * **Example** (Ending with a value) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * const channel = Channel.end("done") - * // Ends immediately with "done", emits nothing + * Effect.runSync(Channel.runCollect(channel)) // => [] * ``` * * @category constructors @@ -847,8 +879,8 @@ export const endSync = (evaluate: LazyArg): Channel => * * **Example** (Computing values lazily) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * let requests = 0 * @@ -856,7 +888,7 @@ export const endSync = (evaluate: LazyArg): Channel => * requests += 1 * return `request-${requests}` * }) - * // Emits "request-1" when the channel runs for the first time + * Effect.runSync(Channel.runCollect(channel)) // => ["request-1"] * ``` * * @category constructors @@ -869,8 +901,8 @@ export const sync = (evaluate: LazyArg): Channel => fromEffect(Effect.s * * **Example** (Creating empty channels) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create an empty channel * const emptyChannel = Channel.empty @@ -882,6 +914,8 @@ export const sync = (evaluate: LazyArg): Channel => fromEffect(Effect.s * // Empty channel can be used as a no-op in conditional logic * const conditionalChannel = (shouldEmit: boolean) => * shouldEmit ? Channel.succeed("data") : Channel.empty + * + * Effect.runSync(Channel.runCollect(conditionalChannel(true))) // => ["data"] * ``` * * @category constructors @@ -894,7 +928,7 @@ export const empty: Channel = fromPull(Effect.succeed(Cause.done())) * * **Example** (Creating non-terminating channels) * - * ```ts + * ```ts import.meta.vitest * import { Channel } from "effect" * * // Create a channel that never completes @@ -909,6 +943,8 @@ export const empty: Channel = fromPull(Effect.succeed(Cause.done())) * // Never channel is useful for testing or as a placeholder * const conditionalChannel = (shouldComplete: boolean) => * shouldComplete ? Channel.succeed("done") : Channel.never + * + * Channel.isChannel(conditionalChannel(false)) // => true * ``` * * @category constructors @@ -921,26 +957,11 @@ export const never: Channel = fromPull(Effect.succeed(Effec * * **Example** (Failing with an error) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Exit } from "effect" * - * // Create a channel that fails with a string error * const failedChannel = Channel.fail("Something went wrong") - * - * // Create a channel that fails with a custom error - * class CustomError extends Error { - * constructor(message: string) { - * super(message) - * this.name = "CustomError" - * } - * } - * const customErrorChannel = Channel.fail(new CustomError("Custom error")) - * - * // Use in error handling by piping to another channel - * const channelWithFallback = Channel.concatWith( - * failedChannel, - * () => Channel.succeed("fallback value") - * ) + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))) // => Exit.fail("Something went wrong") * ``` * * @category constructors @@ -959,27 +980,18 @@ export const fail = (error: E): Channel => fromPull(Effect.s * * **Example** (Failing with a lazy error) * - * ```ts - * import { Channel } from "effect" - * - * // Create a channel that fails with a lazily computed error - * const failedChannel = Channel.failSync(() => { - * console.log("Computing error...") - * return new Error("Computed at runtime") - * }) + * ```ts import.meta.vitest + * import { Channel, Effect, Exit } from "effect" * - * // The error computation is deferred until the channel runs * let attempts = 0 * const conditionalError = Channel.failSync(() => { * attempts += 1 * return `Error after attempt ${attempts}` * }) - * - * // Use with expensive error construction - * const expensiveError = Channel.failSync(() => { - * const requestId = "request-123" - * return new Error(`Failed while processing ${requestId}`) - * }) + * const observed = [ + * Effect.runSync(Effect.exit(Channel.runCollect(conditionalError))), + * attempts + * ] // => [Exit.fail("Error after attempt 1"), 1] * ``` * * @category constructors @@ -997,20 +1009,12 @@ export const failSync = (evaluate: LazyArg): Channel => f * * **Example** (Failing with causes) * - * ```ts - * import { Cause, Channel } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" * - * // Create a channel that fails with a simple cause * const simpleCause = Cause.fail("Simple error") * const failedChannel = Channel.failCause(simpleCause) - * - * // Create a channel with a die cause - * const dieCause = Cause.die(new Error("System error")) - * const dieFailure = Channel.failCause(dieCause) - * - * // Create a channel with a simple fail cause - * const failCause = Cause.fail("Simple error") - * const simpleFail = Channel.failCause(failCause) + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))) // => Exit.failCause(simpleCause) * ``` * * @category constructors @@ -1024,8 +1028,8 @@ export const failCause = (cause: Cause.Cause): Channel => * * **Example** (Failing with lazy causes) * - * ```ts - * import { Cause, Channel } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" * * // Create a channel that fails with a lazily computed cause * let attempts = 0 @@ -1034,11 +1038,10 @@ export const failCause = (cause: Cause.Cause): Channel => * return Cause.fail(`Runtime error after attempt ${attempts}`) * }) * - * // Create a channel with die cause computation - * const dieCauseChannel = Channel.failCauseSync(() => { - * const operation = "load-profile" - * return Cause.die(`Unexpected defect during ${operation}`) - * }) + * const observed = [ + * Effect.runSync(Effect.exit(Channel.runCollect(failedChannel))), + * attempts + * ] // => [Exit.fail("Runtime error after attempt 1"), 1] * ``` * * @category constructors @@ -1053,20 +1056,12 @@ export const failCauseSync = ( * * **Example** (Dying with defects) * - * ```ts - * import { Channel } from "effect" - * - * // Create a channel that dies with a string defect - * const diedChannel = Channel.die("Unrecoverable error") - * - * // Create a channel that dies with an Error object - * const errorDefect = Channel.die(new Error("System failure")) + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Exit } from "effect" * - * // Die with any value as a defect - * const objectDefect = Channel.die({ - * code: "SYSTEM_FAILURE", - * details: "Critical system component failed" - * }) + * const defect = "Unrecoverable error" + * const diedChannel = Channel.die(defect) + * Effect.runSync(Effect.exit(Channel.runCollect(diedChannel))) // => Exit.failCause(Cause.die(defect)) * ``` * * @category constructors @@ -1079,33 +1074,13 @@ export const die = (defect: unknown): Channel => failCause( * * **Example** (Creating channels from effects) * - * ```ts - * import { Channel, Data, Effect } from "effect" - * - * class DatabaseError extends Data.TaggedError("DatabaseError")<{ - * readonly message: string - * }> {} + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * - * // Create a channel from a successful effect * const successChannel = Channel.fromEffect( * Effect.succeed("Hello from effect!") * ) - * - * // Create a channel from an effect that might fail - * const fetchUserChannel = Channel.fromEffect( - * Effect.tryPromise({ - * try: () => fetch("/api/user").then((res) => res.json()), - * catch: (error) => new DatabaseError({ message: String(error) }) - * }) - * ) - * - * // Channel from effect with async computation - * const asyncChannel = Channel.fromEffect( - * Effect.gen(function*() { - * yield* Effect.sleep("100 millis") - * return "Async result" - * }) - * ) + * Effect.runSync(Channel.runCollect(successChannel)) // => ["Hello from effect!"] * ``` * * @category constructors @@ -1173,35 +1148,17 @@ export const fromEffectTake = ( * * **Example** (Creating channels from queues) * - * ```ts - * import { Channel, Data, Effect, Queue } from "effect" - * - * class QueueError extends Data.TaggedError("QueueError")<{ - * readonly reason: string - * }> {} + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { - * // Create a bounded queue - * const queue = yield* Queue.bounded(10) - * - * // Add some items to the queue - * yield* Queue.offer(queue, "item1") - * yield* Queue.offer(queue, "item2") - * yield* Queue.offer(queue, "item3") - * - * // Create a channel from the queue + * const queue = yield* Queue.bounded(3) + * yield* Queue.offerAll(queue, ["item1", "item2", "item3"]) + * yield* Queue.end(queue) * const channel = Channel.fromQueue(queue) - * - * // The channel will read items from the queue one by one - * return channel - * }) - * - * // Sliding queue example - * const slidingProgram = Effect.gen(function*() { - * const slidingQueue = yield* Queue.sliding(5) - * yield* Queue.offerAll(slidingQueue, [1, 2, 3, 4, 5, 6]) - * return Channel.fromQueue(slidingQueue) + * return yield* Channel.runCollect(channel) * }) + * await Effect.runPromise(program) // => ["item1", "item2", "item3"] * ``` * * @category constructors @@ -1216,39 +1173,17 @@ export const fromQueue = ( * * **Example** (Creating batched channels from queues) * - * ```ts - * import { Channel, Data, Effect, Queue } from "effect" - * - * class ProcessingError extends Data.TaggedError("ProcessingError")<{ - * readonly stage: string - * }> {} + * ```ts import.meta.vitest + * import { Cause, Channel, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { - * // Create a queue for batch processing - * const queue = yield* Queue.bounded(100) - * - * // Fill queue with data - * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - * - * // Create a channel that reads arrays from the queue + * const queue = yield* Queue.bounded(4) + * yield* Queue.offerAll(queue, [1, 2, 3, 4]) + * yield* Queue.end(queue) * const arrayChannel = Channel.fromQueueArray(queue) - * - * // This will emit non-empty arrays of elements instead of individual items - * // Useful for batch processing scenarios - * return arrayChannel - * }) - * - * // High-throughput processing example - * const batchProcessor = Effect.gen(function*() { - * const dataQueue = yield* Queue.dropping(1000) - * const batchChannel = Channel.fromQueueArray(dataQueue) - * - * // Process data in batches for better performance - * return Channel.map( - * batchChannel, - * (batch) => batch.map((item) => item.toUpperCase()) - * ) + * return yield* Channel.runCollect(arrayChannel) * }) + * await Effect.runPromise(program) // => [[1, 2, 3, 4]] * ``` * * @category constructors @@ -1273,8 +1208,8 @@ export const identity = (): Channel(): Channel Option.some("Hello") * * // Real-time notifications example * const notificationChannel = Effect.gen(function*() { @@ -1327,8 +1264,8 @@ export const fromSubscription = ( * * **Example** (Batching subscription values) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ * readonly message: string @@ -1350,12 +1287,14 @@ export const fromSubscription = ( * // The channel will output arrays like [1, 2, 3] and [4] * return channel * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some([1, 2, 3, 4]) * ``` * * **Example** (Processing subscription values in batches) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class BatchProcessingError extends Data.TaggedError("BatchProcessingError")<{ * readonly reason: string @@ -1369,19 +1308,21 @@ export const fromSubscription = ( * const batchChannel = Channel.fromSubscriptionArray(subscription) * * // Transform to process each batch - * const processedChannel = Channel.map(batchChannel, (batch) => { - * console.log(`Processing batch of ${batch.length} items:`, batch) - * return batch.map((item) => item.toUpperCase()) - * }) + * const processedChannel = Channel.map(batchChannel, (batch) => + * batch.map((item) => item.toUpperCase()) + * ) * + * yield* PubSub.publishAll(pubsub, ["one", "two"]) * return processedChannel * }) + * const batch = Effect.scoped(Effect.flatMap(batchProcessor, Channel.runHead)) + * await Effect.runPromise(batch) // => Option.some(["ONE", "TWO"]) * ``` * * **Example** (Aggregating subscription metrics) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * const metricsAggregator = Effect.gen(function*() { * const metricsPubSub = yield* PubSub.bounded< @@ -1411,8 +1352,12 @@ export const fromSubscription = ( * } * }) * + * yield* PubSub.publish(metricsPubSub, { timestamp: 1, value: 10 }) * return aggregatedChannel * }) + * const metric = Effect.scoped(Effect.flatMap(metricsAggregator, Channel.runHead)) + * const result = await Effect.runPromise(metric) + * Option.map(result, ({ count, sum, average, min, max }) => ({ count, sum, average, min, max })) // => Option.some({ count: 1, sum: 10, average: 10, min: 10, max: 10 }) * ``` * * @category constructors @@ -1434,15 +1379,15 @@ export const fromSubscriptionArray = ( * * **Example** (Creating channels from PubSubs) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class StreamError extends Data.TaggedError("StreamError")<{ * readonly message: string * }> {} * * const program = Effect.gen(function*() { - * const pubsub = yield* PubSub.bounded(16) + * const pubsub = yield* PubSub.unbounded({ replay: 3 }) * * // Create a channel that reads individual values * const channel = Channel.fromPubSub(pubsub) @@ -1455,15 +1400,17 @@ export const fromSubscriptionArray = ( * // The channel will output: 1, 2, 3 (individual values) * return channel * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some(1) * ``` * * **Example** (Streaming PubSub notifications) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * const notificationService = Effect.gen(function*() { - * const notificationPubSub = yield* PubSub.bounded(50) + * const notificationPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel for real-time notifications * const notificationChannel = Channel.fromPubSub(notificationPubSub) @@ -1476,14 +1423,17 @@ export const fromSubscriptionArray = ( * id: `notification:${message}` * })) * + * yield* PubSub.publish(notificationPubSub, "ready") * return timestampedChannel * }) + * const notification = Effect.scoped(Effect.flatMap(notificationService, Channel.runHead)) + * await Effect.runPromise(notification) // => Option.some({ message: "ready", receivedAt: "2024-01-01T00:00:00.000Z", id: "notification:ready" }) * ``` * * **Example** (Processing PubSub events) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * interface DomainEvent { * readonly type: string @@ -1492,7 +1442,7 @@ export const fromSubscriptionArray = ( * } * * const eventProcessor = Effect.gen(function*() { - * const eventPubSub = yield* PubSub.bounded(100) + * const eventPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel for processing domain events * const eventChannel = Channel.fromPubSub(eventPubSub) @@ -1509,8 +1459,11 @@ export const fromSubscriptionArray = ( * return event * }) * + * yield* PubSub.publish(eventPubSub, { type: "user.created", payload: {}, timestamp: 1 }) * return processedChannel * }) + * const event = Effect.scoped(Effect.flatMap(eventProcessor, Channel.runHead)) + * const result = await Effect.runPromise(event) // => Option.some({ type: "user.created", payload: {}, timestamp: 1, processed: true, processedAt: 2 }) * ``` * * @category constructors @@ -1531,15 +1484,15 @@ export const fromPubSub = ( * * **Example** (Batching PubSub values) * - * ```ts - * import { Channel, Data, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Option, PubSub } from "effect" * * class BatchError extends Data.TaggedError("BatchError")<{ * readonly message: string * }> {} * * const program = Effect.gen(function*() { - * const pubsub = yield* PubSub.bounded(16) + * const pubsub = yield* PubSub.unbounded({ replay: 4 }) * * // Create a channel that reads arrays of values * const channel = Channel.fromPubSubArray(pubsub) @@ -1553,12 +1506,14 @@ export const fromPubSub = ( * // The channel will output arrays like [1, 2, 3] and [4] * return channel * }) + * const result = Effect.scoped(Effect.flatMap(program, Channel.runHead)) + * await Effect.runPromise(result) // => Option.some([1, 2, 3, 4]) * ``` * * **Example** (Processing PubSub orders in batches) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * interface Order { * readonly id: string @@ -1569,7 +1524,7 @@ export const fromPubSub = ( * } * * const orderBatchProcessor = Effect.gen(function*() { - * const orderPubSub = yield* PubSub.bounded(100) + * const orderPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel that processes orders in batches * const orderChannel = Channel.fromPubSubArray(orderPubSub) @@ -1590,14 +1545,20 @@ export const fromPubSub = ( * } * }) * + * yield* PubSub.publish(orderPubSub, { + * id: "1", customerId: "a", items: ["book"], total: 10, submittedAt: 1 + * }) * return processedChannel * }) + * const order = Effect.scoped(Effect.flatMap(orderBatchProcessor, Channel.runHead)) + * const result = await Effect.runPromise(order) + * Option.map(result, (batch) => [batch.batchSize, batch.totalRevenue, batch.uniqueCustomers]) // => Option.some([1, 10, 1]) * ``` * * **Example** (Processing PubSub logs in batches) * - * ```ts - * import { Channel, Effect, PubSub } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect, Option, PubSub } from "effect" * * interface LogEntry { * readonly timestamp: number @@ -1607,7 +1568,7 @@ export const fromPubSub = ( * } * * const logAggregator = Effect.gen(function*() { - * const logPubSub = yield* PubSub.bounded(500) + * const logPubSub = yield* PubSub.unbounded({ replay: 1 }) * * // Create a channel that collects logs in batches * const logChannel = Channel.fromPubSubArray(logPubSub) @@ -1634,8 +1595,17 @@ export const fromPubSub = ( * } * }) * + * yield* PubSub.publish(logPubSub, { + * timestamp: 1, + * level: "info", + * message: "ready", + * source: "app" + * } satisfies LogEntry) * return analysisChannel * }) + * const log = Effect.scoped(Effect.flatMap(logAggregator, Channel.runHead)) + * const result = await Effect.runPromise(log) + * Option.map(result, (batch) => [batch.batchId, batch.totalEntries, batch.infoCount]) // => Option.some(["1-1", 1, 1]) * ``` * * @category constructors @@ -1671,6 +1641,229 @@ export const fromSchedule = ( ): Channel => fromPull(Effect.map(Schedule.toStepWithSleep(schedule), (step) => step(void 0))) +/** + * Creates a channel from a lazily supplied Web `ReadableStream`. + * + * **Example** (Reading from a Web stream) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.fromReadableStream({ + * evaluate: () => new ReadableStream({ + * start(controller) { + * controller.enqueue(1) + * controller.close() + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * await Effect.runPromise(Channel.runCollect(channel)) // => [[1]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromReadableStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly releaseLockOnEnd?: boolean | undefined +}): Channel, E> => + fromTransform((_, scope) => + readableStreamToPullUnsafe({ + scope, + readable: options.evaluate(), + onError: options.onError, + releaseLockOnEnd: options.releaseLockOnEnd + }) + ) + +/** @internal */ +export const pullIntoWritableStream = (options: { + readonly pull: Pull.Pull, IE, unknown> + readonly writable: WritableStream + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined +}): Pull.Pull => + Effect.acquireUseRelease( + Effect.sync(() => options.writable.getWriter()), + (writer) => { + const loop = options.pull.pipe( + Effect.flatMap((chunk) => + Effect.forEach( + chunk, + (value) => + Effect.tryPromise({ + try: () => writer.ready.then(() => writer.write(value)), + catch: options.onError + }), + { discard: true } + ) + ), + Effect.forever({ disableYield: true }) + ) + const withClose = options.closeOnDone !== false + ? Pull.catchDone(loop, (done) => + Effect.andThen( + Effect.tryPromise({ + try: () => writer.close(), + catch: options.onError + }), + Cause.done(done) + )) + : loop + return Effect.onError( + withClose, + (cause) => + Pull.isDoneCause(cause) + ? Effect.void + : Effect.promise(() => writer.abort(cause).catch(constVoid)) + ) + }, + (writer) => Effect.sync(() => writer.releaseLock()) + ) + +/** + * Creates a channel that writes upstream values to a lazily supplied Web + * `WritableStream`. + * + * **Example** (Writing channel input) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const written: Array = [] + * const sink = Channel.fromWritableStream({ + * evaluate: () => new WritableStream({ + * write(value) { + * written.push(value) + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * const program = Channel.fromArray([[1, 2] as [number, number]]).pipe( + * Channel.pipeTo(sink), + * Channel.runDrain + * ) + * + * await Effect.runPromise(program) + * written // => [1, 2] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromWritableStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined +}): Channel, IE> => + fromTransform((pull: Pull.Pull, IE, unknown>) => { + const writable = options.evaluate() + return Effect.succeed(pullIntoWritableStream({ ...options, writable, pull })) + }) + +/** + * Creates a channel backed by a Web `TransformStream`, writing upstream values + * while emitting transformed values from its readable side. + * + * **Example** (Transforming channel input) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const transform = Channel.fromTransformStream({ + * evaluate: () => new TransformStream({ + * transform(value, controller) { + * controller.enqueue(value * 2) + * } + * }), + * onError: (cause) => new Error(String(cause)) + * }) + * + * const program = Channel.fromArray([[1, 2] as [number, number]]).pipe( + * Channel.pipeTo(transform), + * Channel.runCollect + * ) + * + * await Effect.runPromise(program) // => [[2], [4]] + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromTransformStream = (options: { + readonly evaluate: LazyArg> + readonly onError: (error: unknown) => E + readonly closeOnDone?: boolean | undefined + readonly releaseLockOnEnd?: boolean | undefined +}): Channel, IE | E, void, Arr.NonEmptyReadonlyArray, IE> => + fromTransform((upstream, scope) => { + const transform = options.evaluate() + const exit = MutableRef.make | undefined>(undefined) + return pullIntoWritableStream({ + pull: upstream, + writable: transform.writable, + onError: options.onError, + closeOnDone: options.closeOnDone + }).pipe( + Effect.catchCause((cause) => { + if (!Pull.isDoneCause(cause)) { + exit.current = Exit.failCause(cause as Cause.Cause) + } + return Effect.void + }), + Effect.forkIn(scope), + Effect.flatMap(() => + readableStreamToPullUnsafe({ + scope, + exit, + readable: transform.readable, + onError: options.onError, + releaseLockOnEnd: options.releaseLockOnEnd + }) + ) + ) + }) + +const readableStreamToPullUnsafe = (options: { + readonly scope: Scope.Scope + readonly exit?: MutableRef.MutableRef | undefined> | undefined + readonly readable: ReadableStream + readonly onError: (error: unknown) => E + readonly releaseLockOnEnd?: boolean | undefined +}): Effect.Effect, E | E2>, never> => { + const reader = options.readable.getReader() + const exit = options.exit ?? MutableRef.make(undefined) + const pull = Effect.suspend(() => { + if (exit.current) return exit.current + return Effect.matchCauseEffect( + Effect.tryPromise({ + try: () => reader.read(), + catch: options.onError + }), + { + onFailure: (cause) => exit.current ?? Effect.failCause(cause), + onSuccess: ({ done, value }) => { + if (exit.current) return exit.current + return done ? Cause.done() : Effect.succeed(Arr.of(value)) + } + } + ) + }) + return Effect.as( + Scope.addFinalizer( + options.scope, + options.releaseLockOnEnd + ? Effect.sync(() => reader.releaseLock()) + : Effect.promise(() => reader.cancel().catch(constVoid)) + ), + pull + ) +} + /** * Creates a channel that pulls values from an `AsyncIterable`. * @@ -1725,8 +1918,8 @@ export const fromAsyncIterableArray = ( * * **Example** (Mapping channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class TransformError extends Data.TaggedError("TransformError")<{ * readonly reason: string @@ -1735,12 +1928,12 @@ export const fromAsyncIterableArray = ( * // Basic mapping of channel values * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * const doubledChannel = Channel.map(numbersChannel, (n) => n * 2) - * // Outputs: 2, 4, 6, 8, 10 + * Effect.runSync(Channel.runCollect(doubledChannel)) // => [2, 4, 6, 8, 10] * * // Transform string data * const wordsChannel = Channel.fromIterable(["hello", "world", "effect"]) * const upperCaseChannel = Channel.map(wordsChannel, (word) => word.toUpperCase()) - * // Outputs: "HELLO", "WORLD", "EFFECT" + * Effect.runSync(Channel.runCollect(upperCaseChannel)) // => ["HELLO", "WORLD", "EFFECT"] * * // Complex object transformation * type User = { id: number; name: string } @@ -1754,6 +1947,7 @@ export const fromAsyncIterableArray = ( * displayName: `User: ${user.name}`, * isActive: true * })) + * Effect.runSync(Channel.runCollect(displayChannel)) // => [{ displayName: "User: Alice", isActive: true }, { displayName: "User: Bob", isActive: true }] * ``` * * @category sequencing @@ -1862,40 +2056,15 @@ const concurrencyIsSequential = ( * * **Example** (Mapping channel output with effects) * - * ```ts - * import { Channel, Data, Effect } from "effect" - * - * class NetworkError extends Data.TaggedError("NetworkError")<{ - * readonly url: string - * }> {} - * - * // Transform values using effectful operations - * const urlsChannel = Channel.fromIterable([ - * "/api/users/1", - * "/api/users/2", - * "/api/users/3" - * ]) - * - * const fetchDataChannel = Channel.mapEffect( - * urlsChannel, - * (url) => - * Effect.tryPromise({ - * try: () => fetch(url).then((res) => res.json()), - * catch: () => new NetworkError({ url }) - * }) - * ) + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * - * // Concurrent processing with options * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * const processedChannel = Channel.mapEffect( * numbersChannel, - * (n) => - * Effect.gen(function*() { - * yield* Effect.sleep("100 millis") // Simulate async work - * return n * n - * }), - * { concurrency: 3, unordered: true } + * (n) => Effect.succeed(n * n) * ) + * await Effect.runPromise(Channel.runCollect(processedChannel)) // => [1, 4, 9, 16, 25] * ``` * * @category sequencing @@ -2015,7 +2184,7 @@ const mapEffectConcurrent = < Effect.Effect>, OutErr | EX | Cause.Done >(concurrencyN - 2) - yield* Scope.addFinalizer(forkedScope, Queue.shutdown(queue)) + yield* Scope.addFinalizer(forkedScope, Queue.shutdown(effects)) yield* Queue.take(effects).pipe( Effect.flatten, @@ -2124,8 +2293,8 @@ export const mapInputError: { * * **Example** (Tapping channel output) * - * ```ts - * import { Channel, Console, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class LogError extends Data.TaggedError("LogError")<{ * readonly message: string @@ -2135,13 +2304,13 @@ export const mapInputError: { * const numberChannel = Channel.fromIterable([1, 2, 3]) * * // Tap into each output element to perform side effects + * const processed: Array = [] * const tappedChannel = Channel.tap( * numberChannel, - * (n) => Console.log(`Processing number: ${n}`) + * (n) => Effect.sync(() => processed.push(n)) * ) * - * // The channel still outputs the same elements but logs each one - * // Outputs: 1, 2, 3 (while logging each) + * const observed = [await Effect.runPromise(Channel.runCollect(tappedChannel)), processed] // => [[1, 2, 3], [1, 2, 3]] * ``` * * @category sequencing @@ -2188,8 +2357,8 @@ export const tap: { * * **Example** (Flat mapping channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ProcessError extends Data.TaggedError("ProcessError")<{ * readonly cause: string @@ -2205,8 +2374,7 @@ export const tap: { * Channel.fromIterable(Array.from({ length: n }, (_, i) => `item-${n}-${i}`)) * ) * - * // Flattens nested channels into a single stream - * // Outputs: "item-1-0", "item-2-0", "item-2-1", "item-3-0", "item-3-1", "item-3-2" + * Effect.runSync(Channel.runCollect(flatMappedChannel)) // => ["item-1-0", "item-2-0", "item-2-1", "item-3-0", "item-3-1", "item-3-2"] * ``` * * @category sequencing @@ -2345,7 +2513,7 @@ const flatMapSequential = < const catchHalt = Pull.catchDone((_) => { childPull = undefined // we can reuse the scope if the only finalizer is the "fork" one - if (childScope!.state._tag === "Open" && childScope!.state.finalizers.size === 1) { + if (childScope!.state._tag === "Open" && scopeFinalizerCountUnsafe(childScope!) === 1) { return makePull } const close = Scope.close(childScope!, Exit.void) @@ -2394,8 +2562,8 @@ const flatMapConcurrent = < * * **Example** (Concatenating with completion values) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ConcatError extends Data.TaggedError("ConcatError")<{ * readonly reason: string @@ -2406,8 +2574,7 @@ const flatMapConcurrent = < * Channel.concatWith((sum: void) => Channel.succeed(`Completed processing`)) * ) * - * // Concatenates additional channel based on completion value - * // Outputs: 1, 2, 3, then "Completed processing" + * Effect.runSync(Channel.runCollect(numberChannel)) // => [1, 2, 3, "Completed processing"] * ``` * * @category sequencing @@ -2507,8 +2674,8 @@ export const concatWith: { * * **Example** (Concatenating channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ConcatError extends Data.TaggedError("ConcatError")<{ * readonly reason: string @@ -2521,7 +2688,7 @@ export const concatWith: { * // Concatenate them * const concatenatedChannel = Channel.concat(firstChannel, secondChannel) * - * // Outputs: 1, 2, 3, "a", "b", "c" + * Effect.runSync(Channel.runCollect(concatenatedChannel)) // => [1, 2, 3, "a", "b", "c"] * ``` * * @category sequencing @@ -2831,8 +2998,8 @@ export const orElseIfEmpty: { * * **Example** (Flattening nested channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class FlattenError extends Data.TaggedError("FlattenError")<{ * readonly cause: string @@ -2848,7 +3015,7 @@ export const orElseIfEmpty: { * // Flatten the nested channels * const flattenedChannel = Channel.flatten(nestedChannels) * - * // Outputs: 1, 2, 3, 4, 5, 6 + * Effect.runSync(Channel.runCollect(flattenedChannel)) // => [1, 2, 3, 4, 5, 6] * ``` * * @category constructors @@ -2886,8 +3053,8 @@ export const flatten = < * * **Example** (Flattening arrays of channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class FlattenError extends Data.TaggedError("FlattenError")<{ * readonly message: string @@ -2903,7 +3070,7 @@ export const flatten = < * // Flatten the arrays into individual elements * const flattenedChannel = Channel.flattenArray(arrayChannel) * - * // Outputs: 1, 2, 3, 4, 5, 6, 7, 8, 9 + * Effect.runSync(Channel.runCollect(flattenedChannel)) // => [1, 2, 3, 4, 5, 6, 7, 8, 9] * ``` * * @category transforming @@ -2981,8 +3148,8 @@ export const flattenTake = < * * **Example** (Draining channel output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel that outputs values * const sourceChannel = Channel.fromIterable([1, 2, 3, 4, 5]) @@ -2990,8 +3157,7 @@ export const flattenTake = < * // Drain all output, keeping only the completion * const drainedChannel = Channel.drain(sourceChannel) * - * // The channel completes but emits no values - * // Useful for consuming side effects without collecting output + * Effect.runSync(Channel.runCollect(drainedChannel)) // => [] * ``` * * @category constructors @@ -3139,15 +3305,15 @@ export const schedule: { * * **Example** (Filtering channel output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel with mixed numbers * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5, 6, 7, 8]) * * // Filter to keep only even numbers * const evenChannel = Channel.filter(numbersChannel, (n) => n % 2 === 0) - * // Outputs: 2, 4, 6, 8 + * Effect.runSync(Channel.runCollect(evenChannel)) // => [2, 4, 6, 8] * * // Filter with type refinement * const mixedChannel = Channel.fromIterable([1, "hello", 2, "world", 3]) @@ -3155,7 +3321,7 @@ export const schedule: { * mixedChannel, * (value): value is number => typeof value === "number" * ) - * // Outputs: 1, 2, 3 (all typed as numbers) + * Effect.runSync(Channel.runCollect(numbersOnlyChannel)) // => [1, 2, 3] * ``` * * @category filtering @@ -3349,8 +3515,8 @@ export const filterMapEffect: { * * **Example** (Filtering array output) * - * ```ts - * import { Array, Channel } from "effect" + * ```ts import.meta.vitest + * import { Array, Channel, Effect } from "effect" * * const nonEmptyArrayPredicate = Array.isReadonlyArrayNonEmpty * @@ -3363,7 +3529,7 @@ export const filterMapEffect: { * * // Filter arrays to keep only even numbers * const evenArraysChannel = Channel.filterArray(arrayChannel, (n) => n % 2 === 0) - * // Outputs: [2, 4], [6, 8, 10], [12, 14] + * Effect.runSync(Channel.runCollect(evenArraysChannel)) // => [[2, 4], [6, 8, 10], [12, 14]] * // Note: Only non-empty filtered arrays are emitted * * // Arrays that would become empty after filtering are discarded entirely @@ -3373,7 +3539,7 @@ export const filterMapEffect: { * Array.make(7, 9) * ]).pipe(Channel.filter(nonEmptyArrayPredicate)) * const filteredOddChannel = Channel.filterArray(oddChannel, (n) => n % 2 === 0) - * // Outputs: [2, 4] (the arrays [1,3,5] and [7,9] are discarded) + * Effect.runSync(Channel.runCollect(filteredOddChannel)) // => [[2, 4]] * ``` * * @category filtering @@ -3554,7 +3720,7 @@ export const filterMapArrayEffect: { * * **Example** (Mapping with accumulated state) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * * // Create a channel with numbers @@ -3570,8 +3736,6 @@ export const filterMapArrayEffect: { * return [newSum, [current, newSum]] as const * } * ) - * // Outputs: 1, 1, 2, 3, 3, 6, 4, 10 - * * // Using with Effect for async processing * const asyncMapAccum = Channel.mapAccum( * numbersChannel, @@ -3582,6 +3746,8 @@ export const filterMapArrayEffect: { * return [newAcc, [`${value}-processed`, newAcc]] as const * }) * ) + * Effect.runSync(Channel.runCollect(runningSum)) // => [1, 1, 2, 3, 3, 6, 4, 10] + * Effect.runSync(Channel.runCollect(asyncMapAccum)) // => ["1-processed", "1", "2-processed", "12", "3-processed", "123", "4-processed", "1234"] * ``` * * @category sequencing @@ -3696,15 +3862,15 @@ export const mapAccum: { * * **Example** (Scanning channel output) * - * ```ts - * import { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create a channel with numbers * const numbersChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * * // Scan to create running sum * const runningSumChannel = Channel.scan(numbersChannel, 0, (sum, n) => sum + n) - * // Outputs: 0, 1, 3, 6, 10, 15 + * Effect.runSync(Channel.runCollect(runningSumChannel)) // => [0, 1, 3, 6, 10, 15] * // Note: emits the initial value and each intermediate result * * // Scan with string concatenation @@ -3714,7 +3880,7 @@ export const mapAccum: { * "", * (sentence, word) => sentence === "" ? word : `${sentence} ${word}` * ) - * // Outputs: "", "hello", "hello world", "hello world from", "hello world from effect" + * Effect.runSync(Channel.runCollect(sentenceChannel)) // => ["", "hello", "hello world", "hello world from", "hello world from effect"] * ``` * * @category sequencing @@ -3760,7 +3926,7 @@ export const scan: { * * **Example** (Scanning channel output with effects) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect } from "effect" * * class ScanError extends Data.TaggedError("ScanError")<{ @@ -3776,12 +3942,10 @@ export const scan: { * "", * (acc, value) => * Effect.gen(function*() { - * // Simulate async work - * yield* Effect.sleep("10 millis") * return acc + value.toString() * }) * ) - * // Outputs: "", "1", "12", "123", "1234" + * await Effect.runPromise(Channel.runCollect(asyncScanChannel)) // => ["", "1", "12", "123", "1234"] * * // Scan with error handling * const errorHandlingScan = Channel.scanEffect( @@ -3794,6 +3958,7 @@ export const scan: { * return Effect.succeed(sum + n) * } * ) + * await Effect.runPromise(Channel.runCollect(errorHandlingScan)) // => [0, 1, 3, 6, 10] * ``` * * @category sequencing @@ -3852,8 +4017,8 @@ export const scanEffect: { * * **Example** (Recovering from failure causes) * - * ```ts - * import { Cause, Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Data, Effect } from "effect" * * class ProcessError extends Data.TaggedError("ProcessError")<{ * readonly reason: string @@ -3876,7 +4041,7 @@ export const scanEffect: { * return Channel.succeed("Recovered from interruption") * }) * - * // The channel recovers gracefully from errors + * Effect.runSync(Channel.runCollect(recoveredChannel)) // => ["Recovered from failure"] * ``` * * @category error handling @@ -3978,6 +4143,97 @@ export const catchCause: { }) })) +/** + * Recovers from defects using the provided function. + * + * **Details** + * + * Typed failures and interruptions are not caught. + * + * **Example** (Recovering from a defect) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.fromEffect(Effect.die("boom")).pipe( + * Channel.catchDefect((defect) => Channel.succeed(`recovered: ${defect}`)) + * ) + * + * Effect.runSync(Channel.runCollect(channel)) // => ["recovered: boom"] + * ``` + * + * @category error handling + * @since 4.0.0 + */ +export const catchDefect: { + ( + f: (defect: unknown) => Channel + ): ( + self: Channel + ) => Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > + < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 + >( + self: Channel, + f: (defect: unknown) => Channel + ): Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 + > +} = dual(2, < + OutElem, + OutErr, + OutDone, + InElem, + InErr, + InDone, + Env, + OutElem1, + OutErr1, + OutDone1, + InElem1, + InErr1, + InDone1, + Env1 +>( + self: Channel, + f: (defect: unknown) => Channel +): Channel< + OutElem | OutElem1, + OutErr | OutErr1, + OutDone | OutDone1, + InElem & InElem1, + InErr & InErr1, + InDone & InDone1, + Env | Env1 +> => catchCauseFilter(self, Cause.findDefect, f)) + /** * Runs an effect with the full failure `Cause` when the channel fails, then * fails the returned channel with the original cause. @@ -5072,8 +5328,8 @@ export const catchTag: { * * **Example** (Recovering from nested reasons) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number @@ -5087,15 +5343,15 @@ export const catchTag: { * reason: RateLimitError | QuotaExceededError * }> {} * - * const channel = Channel.fail( - * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) - * ) + * const reason = new RateLimitError({ retryAfter: 60 }) + * const channel = Channel.fail(new AiError({ reason })) * * const recovered = channel.pipe( * Channel.catchReason("AiError", "RateLimitError", (reason) => * Channel.succeed(`retry: ${reason.retryAfter}`) * ) * ) + * Effect.runSync(Channel.runCollect(recovered)) // => ["retry: 60"] * ``` * * @category error handling @@ -5457,8 +5713,8 @@ export const catchReasons: { * * **Example** (Promoting nested reasons) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ * retryAfter: number @@ -5472,11 +5728,11 @@ export const catchReasons: { * reason: RateLimitError | QuotaExceededError * }> {} * - * const channel = Channel.fail( - * new AiError({ reason: new RateLimitError({ retryAfter: 60 }) }) - * ) + * const reason = new RateLimitError({ retryAfter: 60 }) + * const channel = Channel.fail(new AiError({ reason })) * * const unwrapped = channel.pipe(Channel.unwrapReason("AiError")) + * Effect.runSync(Effect.exit(Channel.runCollect(unwrapped))) // => Exit.fail(reason) * ``` * * @category error handling @@ -5577,20 +5833,21 @@ export const mapError: { * * **Example** (Converting failures to defects) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Cause, Channel, Data, Effect, Exit } from "effect" * * class ValidationError extends Data.TaggedError("ValidationError")<{ * readonly field: string * }> {} * * // Create a channel that might fail - * const failingChannel = Channel.fail(new ValidationError({ field: "email" })) + * const error = new ValidationError({ field: "email" }) + * const failingChannel = Channel.fail(error) * * // Convert failures to defects * const fatalChannel = Channel.orDie(failingChannel) * - * // Any failure will now become a defect (uncaught exception) + * Effect.runSync(Effect.exit(Channel.runCollect(fatalChannel))) // => Exit.failCause(Cause.die(error)) * ``` * * @category error handling @@ -5791,8 +6048,8 @@ export const retry: { * * **Example** (Switching mapped channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class SwitchError extends Data.TaggedError("SwitchError")<{ * readonly reason: string @@ -5807,7 +6064,7 @@ export const retry: { * (n) => Channel.fromIterable([`value-${n}`]) * ) * - * // Outputs: "value-1", "value-2", "value-3" + * await Effect.runPromise(Channel.runCollect(switchedChannel)) // => ["value-3"] * ``` * * @category sequencing @@ -5915,8 +6172,8 @@ export const switchMap: { * * **Example** (Merging nested channels) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class MergeAllError extends Data.TaggedError("MergeAllError")<{ * readonly reason: string @@ -5931,11 +6188,11 @@ export const switchMap: { * * // Merge all channels with bounded concurrency * const mergedChannel = Channel.mergeAll({ - * concurrency: 2, + * concurrency: 1, * bufferSize: 16 * })(nestedChannels) * - * // Outputs: 1, 2, 3, 4, 5, 6 (order may vary due to concurrency) + * await Effect.runPromise(Channel.runCollect(mergedChannel)) // => [1, 2, 3, 4, 5, 6] * ``` * * @category combining @@ -6033,8 +6290,21 @@ export const mergeAll: { yield* Effect.gen(function*() { while (true) { - if (semaphore) yield* semaphore.take(1) - const channel = yield* pull + let pullFiber: Fiber.Fiber, any> | undefined + if (semaphore) { + if (fibers.size < concurrencyN) { + yield* semaphore.take(1) + } else { + pullFiber = yield* Effect.forkChild(pull) + yield* Effect.raceFirst( + semaphore.take(1), + Effect.andThen(Fiber.join(pullFiber), Effect.never) + ) + } + } + const channel = pullFiber === undefined + ? yield* pull + : yield* Fiber.join(pullFiber) const childScope = Scope.forkUnsafe(forkedScope) const childPull = yield* toTransform(channel)(upstream, childScope) @@ -6069,7 +6339,13 @@ export const mergeAll: { fibers.add(fiber) } }).pipe( - Effect.catchCause((cause) => doneLatch.whenOpen(Queue.failCause(queue, cause))), + Effect.catchCause((cause) => { + const halt = Pull.filterDone(cause) + if (Result.isSuccess(halt)) { + return doneLatch.whenOpen(Queue.failCause(queue, cause)) + } + return Queue.failCause(queue, cause) + }), Effect.forkIn(forkedScope) ) @@ -6083,14 +6359,11 @@ export const mergeAll: { * * **Example** (Choosing merge halt strategies) * - * ```ts - * import type { Channel } from "effect" + * ```ts import.meta.vitest + * import { Channel } from "effect" * * // Different halt strategies for channel merging - * const leftFirst: Channel.HaltStrategy = "left" // Stop when left channel halts - * const rightFirst: Channel.HaltStrategy = "right" // Stop when right channel halts - * const both: Channel.HaltStrategy = "both" // Stop when both channels halt - * const either: Channel.HaltStrategy = "either" // Stop when either channel halts + * const strategies: Array = ["left", "right", "both", "either"] // => ["left", "right", "both", "either"] * ``` * * @category models @@ -6104,24 +6377,18 @@ export type HaltStrategy = "left" | "right" | "both" | "either" * * **Example** (Merging channels) * - * ```ts - * import { Channel, Data } from "effect" - * - * class MergeError extends Data.TaggedError("MergeError")<{ - * readonly source: string - * }> {} + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" * * // Create two channels * const leftChannel = Channel.fromIterable([1, 2, 3]) * const rightChannel = Channel.fromIterable(["a", "b", "c"]) * - * // Merge them with "either" halt strategy - * const mergedChannel = Channel.merge(leftChannel, rightChannel, { - * haltStrategy: "either" - * }) + * // The default "both" strategy waits for both channels to complete + * const mergedChannel = Channel.merge(leftChannel, rightChannel) * - * // Outputs elements from both channels concurrently - * // Order may vary: 1, "a", 2, "b", 3, "c" + * const values = await Effect.runPromise(Channel.runCollect(mergedChannel)) + * values.map(String).sort() // => ["1", "2", "3", "a", "b", "c"] * ``` * * @category combining @@ -6314,19 +6581,16 @@ export const mergeEffect: { * * **Example** (Splitting string chunks into lines) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Stream } from "effect" * - * Effect.runPromise(Effect.gen(function*() { - * const result = yield* Stream.runCollect( - * Stream.splitLines(Stream.make("hel", "lo\r\nwor", "ld\n")) - * ) - * console.log(result) - * // [ 'hello', 'world' ] - * })) + * const result = await Effect.runPromise(Stream.runCollect( + * Stream.splitLines(Stream.make("hel", "lo\r\nwor", "ld\n")) + * )) + * result // => ["hello", "world"] * ``` * - * @category String manipulation + * @category splitting * @since 2.0.0 */ export const splitLines = (): Channel< @@ -6342,9 +6606,8 @@ export const splitLines = (): Channel< // Accumulates text that has not yet been terminated by a line break. // Content is carried across chunks until a terminator is found. let stringBuilder = "" - // Set when a chunk ends with \r so the next chunk can check whether - // the following character is \n (completing a \r\n pair) or not - // (standalone \r, which is itself a line terminator). + // A trailing \r completes the line immediately. Remember it only to + // suppress a leading \n in the next nonempty string. let midCRLF = false // Remembers the upstream Done value after the first time the upstream // signals completion, so subsequent pulls return Done immediately @@ -6371,11 +6634,8 @@ export const splitLines = (): Channel< let indexOfLF = str.indexOf("\n") if (midCRLF) { if (indexOfLF === 0) { - pushLine("") from = 1 indexOfLF = str.indexOf("\n", from) - } else { - pushLine("") } midCRLF = false } @@ -6385,18 +6645,19 @@ export const splitLines = (): Channel< from = indexOfLF + 1 indexOfLF = str.indexOf("\n", from) } else { + pushLine(str.substring(from, indexOfCR)) if (str.length === indexOfCR + 1) { midCRLF = true + from = str.length indexOfCR = -1 } else { - pushLine(str.substring(from, indexOfCR)) from = indexOfCR + (indexOfLF === indexOfCR + 1 ? 2 : 1) indexOfCR = str.indexOf("\r", from) indexOfLF = str.indexOf("\n", from) } } } - stringBuilder = stringBuilder + str.substring(from, str.length - (midCRLF ? 1 : 0)) + stringBuilder = stringBuilder + str.substring(from) } } return Arr.isReadonlyArrayNonEmpty(chunkBuilder) ? chunkBuilder : null @@ -6411,7 +6672,7 @@ export const splitLines = (): Channel< onFailure: Effect.failCause, onDone: (leftover) => { done = Option.some(leftover) - if (stringBuilder.length > 0 || midCRLF) { + if (stringBuilder.length > 0) { const last = stringBuilder stringBuilder = "" midCRLF = false @@ -6440,7 +6701,7 @@ export const splitLines = (): Channel< * span `Uint8Array` boundaries. The optional `encoding` and `options` are * passed to `TextDecoder`. * - * @category String manipulation + * @category decoding * @since 4.0.0 */ export const decodeText = (encoding?: string, options?: TextDecoderOptions): Channel< @@ -6466,7 +6727,7 @@ export const decodeText = (encoding?: string, options?: TextDecoderOp * * Each string inside an emitted array is encoded independently. * - * @category String manipulation + * @category encoding * @since 4.0.0 */ export const encodeText = (): Channel< @@ -6492,8 +6753,8 @@ export const encodeText = (): Channel< * * **Example** (Piping one channel into another) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class PipeError extends Data.TaggedError("PipeError")<{ * readonly stage: string @@ -6506,7 +6767,7 @@ export const encodeText = (): Channel< * // Pipe the source into the transform * const pipedChannel = Channel.pipeTo(sourceChannel, transformChannel) * - * // Outputs: 2, 4, 6 + * Effect.runSync(Channel.runCollect(pipedChannel)) // => [2, 4, 6] * ``` * * @category sequencing @@ -6540,21 +6801,22 @@ export const pipeTo: { * * **Example** (Piping while preserving failures) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" * * class SourceError extends Data.TaggedError("SourceError")<{ * readonly code: number * }> {} * * // Create a failing source channel - * const failingSource = Channel.fail(new SourceError({ code: 404 })) - * const safeTransform = Channel.succeed("transformed") + * const error = new SourceError({ code: 404 }) + * const failingSource = Channel.fail(error) + * const safeTransform = Channel.identity() * * // Pipe while preserving source failures * const safePipedChannel = Channel.pipeToOrFail(failingSource, safeTransform) * - * // Source errors are preserved and not sent to transform channel + * Effect.runSync(Effect.exit(Channel.runCollect(safePipedChannel))) // => Exit.fail(error) * ``` * * @category sequencing @@ -6602,7 +6864,7 @@ export const pipeToOrFail: { * * **Example** (Unwrapping channel effects) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect } from "effect" * * class UnwrapError extends Data.TaggedError("UnwrapError")<{ @@ -6617,7 +6879,7 @@ export const pipeToOrFail: { * // Unwrap the effect to get the channel * const unwrappedChannel = Channel.unwrap(channelEffect) * - * // The resulting channel outputs: 1, 2, 3 + * Effect.runSync(Channel.runCollect(unwrappedChannel)) // => [1, 2, 3] * ``` * * @category constructors @@ -6666,7 +6928,7 @@ export const scoped = ( * * **Example** (Embedding custom input handling) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Effect } from "effect" * * // Create a base channel @@ -6677,13 +6939,11 @@ export const scoped = ( * baseChannel, * (upstream) => * upstream.pipe( - * Effect.tap((message) => - * Effect.sync(() => console.log(message)) - * ), * Effect.forever, * Effect.ignore * ) * ) + * await Effect.runPromise(Channel.runCollect(embeddedChannel)) // => [1, 2, 3] * ``` * * @category sequencing @@ -6741,7 +7001,7 @@ export const embedInput: { * * @see {@link bufferArray} for buffering elements from array outputs * - * @category Buffering + * @category buffering * @since 2.0.0 */ export const buffer: { @@ -6807,7 +7067,7 @@ export const buffer: { * * @see {@link buffer} for buffering output elements without flattening arrays * - * @category Buffering + * @category buffering * @since 4.0.0 */ export const bufferArray: { @@ -6915,17 +7175,16 @@ export const haltWhen: { ): Channel => fromTransformBracket(Effect.fnUntraced(function*(upstream, scope, forkedScope) { const pull = yield* toTransform(self)(upstream, scope) - let haltCause: Cause.Cause> | undefined = undefined - yield* effect.pipe( - Effect.catchCause((cause) => { - haltCause = cause - return Effect.void - }), - Effect.forkIn(forkedScope) - ) - return Effect.suspend((): Pull.Pull => - haltCause ? Effect.failCause(haltCause) : pull - ) + const fiber = yield* Effect.forkIn(effect, forkedScope, { startImmediately: true }) + return Effect.suspend((): Pull.Pull => { + const exit = fiber.pollUnsafe() + return exit === undefined + ? pull + : Exit.match(exit, { + onFailure: Effect.failCause, + onSuccess: Cause.done + }) + }) }))) /** @@ -6961,8 +7220,8 @@ export const onError: { * * **Example** (Running exit finalizers) * - * ```ts - * import { Channel, Console, Data, Exit } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Exit } from "effect" * * class ExitError extends Data.TaggedError("ExitError")<{ * readonly stage: string @@ -6972,13 +7231,12 @@ export const onError: { * const dataChannel = Channel.fromIterable([1, 2, 3]) * * // Attach exit handler + * const exits: Array> = [] * const channelWithExit = Channel.onExit(dataChannel, (exit) => { - * if (Exit.isSuccess(exit)) { - * return Console.log(`Channel completed successfully with: ${exit.value}`) - * } else { - * return Console.log(`Channel failed with: ${exit.cause}`) - * } + * exits.push(exit) + * return Effect.void * }) + * const observed = [await Effect.runPromise(Channel.runCollect(channelWithExit)), exits] // => [[1, 2, 3], [Exit.void]] * ``` * * @category resource management @@ -7109,8 +7367,8 @@ export const onEnd: { * * **Example** (Ensuring cleanup runs) * - * ```ts - * import { Channel, Console, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class EnsureError extends Data.TaggedError("EnsureError")<{ * readonly operation: string @@ -7120,10 +7378,12 @@ export const onEnd: { * const dataChannel = Channel.fromIterable([1, 2, 3]) * * // Ensure cleanup always runs + * const events: Array = [] * const channelWithCleanup = Channel.ensuring( * dataChannel, - * Console.log("Cleanup executed regardless of success or failure") + * Effect.sync(() => events.push("cleanup")) * ) + * const observed = [await Effect.runPromise(Channel.runCollect(channelWithCleanup)), events] // => [[1, 2, 3], ["cleanup"]] * ``` * * @category resource management @@ -7170,7 +7430,7 @@ const runWith = < /** * Creates a channel from the specified services. * - * @category services + * @category accessors * @since 2.0.0 */ export const contextWith = ( @@ -7184,7 +7444,7 @@ export const contextWith = ( /** * The starting channel for Do notation, emitting an empty object. * - * @category do notation + * @category constructors * @since 4.0.0 */ export const Do: Channel<{}> = succeed({}) @@ -7503,7 +7763,7 @@ export { /** * Adds a computed field to each object emitted by a channel. * - * @category do notation + * @category mapping * @since 4.0.0 */ let_ as let @@ -7519,7 +7779,7 @@ export { * channel's output becomes the value of the new field. `options.concurrency` * and `options.bufferSize` control how derived channels are flattened. * - * @category do notation + * @category sequencing * @since 4.0.0 */ export const bind: { @@ -7625,7 +7885,7 @@ export const bind: { * @see {@link bind} for adding a field produced by another channel * @see {@link let_ let} for adding a computed field * - * @category do notation + * @category mapping * @since 4.0.0 */ export const bindTo: { @@ -7670,8 +7930,8 @@ export const bindTo: { * * **Example** (Counting channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class CountError extends Data.TaggedError("CountError")<{ * readonly reason: string @@ -7683,23 +7943,23 @@ export const bindTo: { * // Count the elements * const countEffect = Channel.runCount(numbersChannel) * - * // Effect.runSync(countEffect) // Returns: 5 + * Effect.runSync(countEffect) // => 5 * ``` * - * @category execution + * @category running * @since 4.0.0 */ export const runCount = ( self: Channel -): Effect.Effect => runFold(self, () => 0, (acc) => acc + 1) +): Effect.Effect => runFold(self, () => 0, (acc) => acc + 1) /** * Runs a channel and discards all output elements, returning only the final result. * * **Example** (Draining channel output at runtime) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class DrainError extends Data.TaggedError("DrainError")<{ * readonly stage: string @@ -7707,18 +7967,15 @@ export const runCount = ( * * // Create a channel that outputs elements and completes with a result * const resultChannel = Channel.fromIterable([1, 2, 3]) - * const completedChannel = Channel.concatWith( - * resultChannel, - * () => Channel.succeed("completed") - * ) + * const completedChannel = Channel.concat(resultChannel, Channel.end("completed")) * * // Drain all elements and get only the final result * const drainEffect = Channel.runDrain(completedChannel) * - * // Effect.runSync(drainEffect) // Returns: "completed" + * Effect.runSync(drainEffect) // => "completed" * ``` * - * @category execution + * @category running * @since 2.0.0 */ export const runDrain = ( @@ -7730,8 +7987,8 @@ export const runDrain = ( * * **Example** (Running effects for each output) * - * ```ts - * import { Channel, Console, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class ForEachError extends Data.TaggedError("ForEachError")<{ * readonly element: unknown @@ -7740,16 +7997,18 @@ export const runDrain = ( * // Create a channel with numbers * const numbersChannel = Channel.fromIterable([1, 2, 3]) * - * // Run forEach to log each element + * // Run forEach to process each element + * const processed: Array = [] * const forEachEffect = Channel.runForEach( * numbersChannel, - * (n) => Console.log(`Processing: ${n}`) + * (n) => Effect.sync(() => processed.push(n)) * ) * - * // Logs: "Processing: 1", "Processing: 2", "Processing: 3" + * await Effect.runPromise(forEachEffect) + * processed // => [1, 2, 3] * ``` * - * @category execution + * @category running * @since 4.0.0 */ export const runForEach: { @@ -7780,7 +8039,7 @@ export const runForEach: { * Returning `true` continues consuming the channel. Returning `false` stops * consumption early. The returned effect completes with `void`. * - * @category execution + * @category running * @since 4.0.0 */ export const runForEachWhile: { @@ -7807,13 +8066,71 @@ export const runForEachWhile: { )) ) +/** + * Concatenates a channel's `Uint8Array` chunks into a single `Uint8Array`. + * + * **Gotchas** + * + * This materializes the full content in memory. The source channel must not + * reuse or mutate emitted buffers, which are retained until collection completes. + * + * **Example** (Joining channel byte chunks) + * + * ```ts import.meta.vitest + * import { Channel, Effect } from "effect" + * + * const channel = Channel.fromArray([ + * [new Uint8Array([1, 2])], + * [new Uint8Array([3, 4])] + * ] as const) + * + * const bytes = Effect.runSync(Channel.mkUint8Array(channel)) + * Array.from(bytes) // => [1, 2, 3, 4] + * ``` + * + * @category running + * @since 4.0.0 + */ +export const mkUint8Array = ( + self: Channel, OutErr, OutDone, unknown, unknown, unknown, Env> +): Effect.Effect, OutErr, Env> => + Effect.map( + runFold( + self, + (): { + bytes: number + readonly arrays: Array + } => ({ + bytes: 0, + arrays: [] + }), + (acc, chunk) => { + for (let i = 0; i < chunk.length; i++) { + acc.bytes += chunk[i].length + acc.arrays.push(chunk[i]) + } + return acc + } + ), + ({ arrays, bytes }) => { + const result = new Uint8Array(bytes) + let offset = 0 + for (let i = 0; i < arrays.length; i++) { + const array = arrays[i] + result.set(array, offset) + offset += array.length + } + return result + } + ) + /** * Runs a channel and collects all output elements into an array. * * **Example** (Collecting channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class CollectError extends Data.TaggedError("CollectError")<{ * readonly reason: string @@ -7825,10 +8142,10 @@ export const runForEachWhile: { * // Collect all elements into an array * const collectEffect = Channel.runCollect(numbersChannel) * - * // Effect.runSync(collectEffect) // Returns: [1, 2, 3, 4, 5] + * Effect.runSync(collectEffect) // => [1, 2, 3, 4, 5] * ``` * - * @category execution + * @category running * @since 2.0.0 */ export const runCollect = ( @@ -7839,16 +8156,6 @@ export const runCollect = ( return acc }) -/** - * Runs a channel and outputs the done value. - * - * @category execution - * @since 4.0.0 - */ -export const runDone = ( - self: Channel -): Effect.Effect => runWith(self, identity_, Effect.succeed) - /** * Runs a channel until the first output element is available, returning it in * an `Option`. @@ -7858,7 +8165,7 @@ export const runDone = ( * Returns `Option.some` with the first output element, or `Option.none` if the * channel completes without emitting output. * - * @category execution + * @category running * @since 4.0.0 */ export const runHead = ( @@ -7885,7 +8192,7 @@ export const runHead = ( * Returns `Option.some` with the last emitted element, or `Option.none` if the * channel completes without emitting output. * - * @category execution + * @category running * @since 4.0.0 */ export const runLast = ( @@ -7913,8 +8220,8 @@ export const runLast = ( * * **Example** (Folding channel output) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect } from "effect" * * class FoldError extends Data.TaggedError("FoldError")<{ * readonly operation: string @@ -7926,10 +8233,10 @@ export const runLast = ( * // Fold to calculate sum * const sumEffect = Channel.runFold(numbersChannel, () => 0, (acc, n) => acc + n) * - * // Effect.runSync(sumEffect) // Returns: 15 + * Effect.runSync(sumEffect) // => 15 * ``` * - * @category execution + * @category running * @since 4.0.0 */ export const runFold: { @@ -7979,7 +8286,7 @@ export const runFold: { * the effectful accumulator function. The returned effect succeeds with the * final accumulator value. * - * @category execution + * @category running * @since 4.0.0 */ export const runFoldEffect: { @@ -8029,7 +8336,7 @@ export const runFoldEffect: { * * **Example** (Converting channels to pulls) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect } from "effect" * * class PullError extends Data.TaggedError("PullError")<{ @@ -8039,12 +8346,11 @@ export const runFoldEffect: { * // Create a channel * const numbersChannel = Channel.fromIterable([1, 2, 3]) * - * // Convert to Pull within a scope - * const pullEffect = Effect.scoped( - * Channel.toPull(numbersChannel) - * ) - * - * // Use the Pull to manually consume elements + * const program = Effect.scoped(Effect.gen(function*() { + * const pull = yield* Channel.toPull(numbersChannel) + * return [yield* pull, yield* pull, yield* pull] + * })) + * await Effect.runPromise(program) // => [1, 2, 3] * ``` * * @category destructors @@ -8078,7 +8384,7 @@ export const toPull: ( * * **Example** (Converting channels to scoped pulls) * - * ```ts + * ```ts import.meta.vitest * import { Channel, Data, Effect, Scope } from "effect" * * class ScopedPullError extends Data.TaggedError("ScopedPullError")<{ @@ -8090,10 +8396,11 @@ export const toPull: ( * * // Convert to Pull with explicit scope * const scopedPullEffect = Effect.gen(function*() { - * const scope = yield* Scope.make() + * const scope = yield* Effect.scope * const pull = yield* Channel.toPullScoped(numbersChannel, scope) - * return pull + * return [yield* pull, yield* pull, yield* pull] * }) + * await Effect.runPromise(Effect.scoped(scopedPullEffect)) // => [1, 2, 3] * ``` * * @category destructors @@ -8201,8 +8508,8 @@ export const runIntoQueueArray: { * * **Example** (Converting channels to queues) * - * ```ts - * import { Channel, Data } from "effect" + * ```ts import.meta.vitest + * import { Channel, Data, Effect, Queue } from "effect" * * class QueueError extends Data.TaggedError("QueueError")<{ * readonly operation: string @@ -8212,10 +8519,11 @@ export const runIntoQueueArray: { * const dataChannel = Channel.fromIterable([1, 2, 3, 4, 5]) * * // Convert to queue for concurrent processing - * const queueEffect = Channel.toQueue(dataChannel, { capacity: 32 }) - * - * // The queue can be used for concurrent consumption - * // Multiple consumers can read from the queue + * const program = Effect.scoped(Effect.gen(function*() { + * const queue = yield* Channel.toQueue(dataChannel, { capacity: 32 }) + * return yield* Queue.takeBetween(queue, 5, 5) + * })) + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category destructors diff --git a/packages/effect/src/ChannelSchema.ts b/packages/effect/src/ChannelSchema.ts index fb2b4864c..6f91859ec 100644 --- a/packages/effect/src/ChannelSchema.ts +++ b/packages/effect/src/ChannelSchema.ts @@ -136,7 +136,7 @@ export const decodeUnknown: ( Arr.NonEmptyReadonlyArray, IE | Schema.SchemaError, Done, - Arr.NonEmptyReadonlyArray, + Arr.NonEmptyReadonlyArray, IE, Done, S["DecodingServices"] diff --git a/packages/effect/src/Chunk.ts b/packages/effect/src/Chunk.ts index 67b2f997a..1e6a6d94a 100644 --- a/packages/effect/src/Chunk.ts +++ b/packages/effect/src/Chunk.ts @@ -19,6 +19,7 @@ import { dual, identity, pipe } from "./Function.ts" import * as Hash from "./Hash.ts" import type { TypeLambda } from "./HKT.ts" import { type Inspectable, NodeInspectSymbol, toJson } from "./Inspectable.ts" +import * as Count from "./internal/count.ts" import type { NonEmptyIterable } from "./NonEmptyIterable.ts" import type { Option } from "./Option.ts" import * as O from "./Option.ts" @@ -30,19 +31,19 @@ import * as R from "./Result.ts" import type { Result } from "./Result.ts" import type { Covariant, NoInfer } from "./Types.ts" -const TypeId = "~effect/collections/Chunk" +const TypeId = "~effect/Chunk" /** * A Chunk is an immutable, ordered collection optimized for efficient concatenation and access patterns. * * **Example** (Inspecting chunk values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk: Chunk.Chunk = Chunk.make(1, 2, 3) - * console.log(chunk.length) // 3 - * console.log(Chunk.toArray(chunk)) // [1, 2, 3] + * chunk.length // => 3 + * Chunk.toArray(chunk) // => [1, 2, 3] * ``` * * @category models @@ -64,12 +65,12 @@ export interface Chunk extends Iterable, Equal.Equal, Pipeable, Inspec * * **Example** (Working with non-empty chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk: Chunk.NonEmptyChunk = Chunk.make(1, 2, 3) - * console.log(Chunk.headNonEmpty(nonEmptyChunk)) // 1 - * console.log(Chunk.lastNonEmpty(nonEmptyChunk)) // 3 + * Chunk.headNonEmpty(nonEmptyChunk) // => 1 + * Chunk.lastNonEmpty(nonEmptyChunk) // => 3 * ``` * * @category models @@ -82,7 +83,7 @@ export interface NonEmptyChunk extends Chunk, NonEmptyIterable {} * * **Example** (Applying the Chunk type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk, HKT } from "effect" * * // Create a Chunk type using the type lambda @@ -90,7 +91,7 @@ export interface NonEmptyChunk extends Chunk, NonEmptyIterable {} * // Equivalent to: Chunk * ``` * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface ChunkTypeLambda extends TypeLambda { @@ -152,7 +153,7 @@ const emptyArray: ReadonlyArray = [] * * **Example** (Comparing chunks for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Equivalence } from "effect" * * const chunk1 = Chunk.make(1, 2, 3) @@ -160,8 +161,8 @@ const emptyArray: ReadonlyArray = [] * const chunk3 = Chunk.make(1, 2, 4) * * const eq = Chunk.makeEquivalence(Equivalence.strictEqual()) - * console.log(eq(chunk1, chunk2)) // true - * console.log(eq(chunk1, chunk3)) // false + * eq(chunk1, chunk2) // => true + * eq(chunk1, chunk3) // => false * ``` * * @category instances @@ -248,7 +249,7 @@ const makeChunk = (backing: Backing): Chunk => { } case "ISlice": { chunk.length = backing.length - chunk.depth = backing.chunk.depth + 1 + chunk.depth = 0 chunk.left = _empty chunk.right = _empty break @@ -262,18 +263,18 @@ const makeChunk = (backing: Backing): Chunk => { * * **Example** (Checking for chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const array = [1, 2, 3] * - * console.log(Chunk.isChunk(chunk)) // true - * console.log(Chunk.isChunk(array)) // false - * console.log(Chunk.isChunk("string")) // false + * Chunk.isChunk(chunk) // => true + * Chunk.isChunk(array) // => false + * Chunk.isChunk("string") // => false * ``` * - * @category constructors + * @category guards * @since 2.0.0 */ export const isChunk: { @@ -288,11 +289,10 @@ const _empty = makeChunk({ _tag: "IEmpty" }) * * **Example** (Creating an empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const emptyChunk = Chunk.empty() - * console.log(Chunk.size(emptyChunk)) // 0 + * Chunk.size(Chunk.empty()) // => 0 * ``` * * @category constructors @@ -305,11 +305,10 @@ export const empty: () => Chunk = () => _empty * * **Example** (Creating a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.make(1, 2, 3, 4)) // => [1, 2, 3, 4] * ``` * * @category constructors @@ -323,11 +322,10 @@ export const make = ]>(...as: As): NonEm * * **Example** (Creating a single-element chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.of("hello") - * console.log(Chunk.toArray(chunk)) // ["hello"] + * Chunk.toArray(Chunk.of("hello")) // => ["hello"] * ``` * * @category constructors @@ -340,11 +338,10 @@ export const of = (a: A): NonEmptyChunk => makeChunk({ _tag: "ISingleton", * * **Example** (Creating chunks from iterables) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.fromIterable([1, 2, 3]) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3] + * Chunk.toArray(Chunk.fromIterable([1, 2, 3])) // => [1, 2, 3] * ``` * * @category constructors @@ -390,17 +387,16 @@ const toArray_ = (self: Chunk): Array => toReadonlyArray(self).slice() * * **Example** (Converting chunks to mutable arrays) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const array = Chunk.toArray(chunk) - * console.log(array) // [1, 2, 3] - * console.log(Array.isArray(array)) // true + * array // => [1, 2, 3] + * Array.isArray(array) // => true * * // With empty chunk - * const emptyChunk = Chunk.empty() - * console.log(Chunk.toArray(emptyChunk)) // [] + * Chunk.toArray(Chunk.empty()) // => [] * ``` * * @category converting @@ -440,19 +436,18 @@ const toReadonlyArray_ = (self: Chunk): ReadonlyArray => { * * **Example** (Converting chunks to readonly arrays) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const readonlyArray = Chunk.toReadonlyArray(chunk) - * console.log(readonlyArray) // [1, 2, 3] + * readonlyArray // => [1, 2, 3] * * // The result is read-only, modifications would cause TypeScript errors * // readonlyArray[0] = 10 // TypeScript error * * // With empty chunk - * const emptyChunk = Chunk.empty() - * console.log(Chunk.toReadonlyArray(emptyChunk)) // [] + * Chunk.toReadonlyArray(Chunk.empty()) // => [] * ``` * * @category converting @@ -493,16 +488,14 @@ const reverseChunk = (self: Chunk): Chunk => { * * **Example** (Reversing chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) - * const result = Chunk.reverse(chunk) - * - * console.log(Chunk.toArray(result)) // [3, 2, 1] + * Chunk.toArray(Chunk.reverse(chunk)) // => [3, 2, 1] * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const reverse: >(self: S) => Chunk.With> = reverseChunk as any @@ -513,21 +506,20 @@ export const reverse: >(self: S) => Chunk.With Option.some("b") + * Chunk.get(chunk, 10) // => Option.none() + * Chunk.get(chunk, -1) // => Option.none() * * // Using pipe syntax - * const result = chunk.pipe(Chunk.get(2)) - * console.log(result) // Option.some("c") + * chunk.pipe(Chunk.get(2)) // => Option.some("c") * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const get: { @@ -553,16 +545,16 @@ export const get: { * * **Example** (Creating chunks without copying arrays) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const array = [1, 2, 3, 4, 5] * const chunk = Chunk.fromArrayUnsafe(array) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4, 5] + * Chunk.toArray(chunk) // => [1, 2, 3, 4, 5] * * // Warning: Since this doesn't copy the array, mutations affect the chunk * array[0] = 999 - * console.log(Chunk.toArray(chunk)) // [999, 2, 3, 4, 5] + * Chunk.toArray(chunk) // => [999, 2, 3, 4, 5] * ``` * * @category unsafe @@ -585,15 +577,15 @@ export const fromArrayUnsafe = (self: ReadonlyArray): Chunk => * * **Example** (Creating non-empty chunks without copying arrays) * - * ```ts + * ```ts import.meta.vitest * import { Array, Chunk } from "effect" * * const nonEmptyArray = Array.make(1, 2, 3, 4, 5) * const chunk = Chunk.fromNonEmptyArrayUnsafe(nonEmptyArray) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4, 5] + * Chunk.toArray(chunk) // => [1, 2, 3, 4, 5] * * // The result is guaranteed to be non-empty - * console.log(Chunk.isNonEmpty(chunk)) // true + * Chunk.isNonEmpty(chunk) // => true * ``` * * @category unsafe @@ -616,16 +608,16 @@ export const fromNonEmptyArrayUnsafe = (self: NonEmptyReadonlyArray): NonE * * **Example** (Accessing elements unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make("a", "b", "c", "d") * - * console.log(Chunk.getUnsafe(chunk, 1)) // "b" - * console.log(Chunk.getUnsafe(chunk, 3)) // "d" + * Chunk.getUnsafe(chunk, 1) // => "b" + * Chunk.getUnsafe(chunk, 3) // => "d" * * // Use Chunk.get when the index may be out of bounds - * console.log(Option.isNone(Chunk.get(chunk, 10))) // true + * Option.isNone(Chunk.get(chunk, 10)) // => true * ``` * * @category unsafe @@ -673,17 +665,15 @@ export const getUnsafe: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) - * const newChunk = Chunk.append(chunk, 4) - * console.log(Chunk.toArray(newChunk)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.append(chunk, 4)) // => [1, 2, 3, 4] * * // Appending to empty chunk * const emptyChunk = Chunk.empty() - * const singleElement = Chunk.append(emptyChunk, 42) - * console.log(Chunk.toArray(singleElement)) // [42] + * Chunk.toArray(Chunk.append(emptyChunk, 42)) // => [42] * ``` * * @see {@link prepend} for adding one element before the existing elements @@ -702,17 +692,15 @@ export const append: { * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(2, 3, 4) - * const newChunk = Chunk.prepend(chunk, 1) - * console.log(Chunk.toArray(newChunk)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.prepend(chunk, 1)) // => [1, 2, 3, 4] * * // Prepending to empty chunk * const emptyChunk = Chunk.empty() - * const singleElement = Chunk.prepend(emptyChunk, "first") - * console.log(Chunk.toArray(singleElement)) // ["first"] + * Chunk.toArray(Chunk.prepend(emptyChunk, "first")) // => ["first"] * ``` * * @category combining @@ -725,24 +713,25 @@ export const prepend: { /** * Takes the first up to `n` elements from the chunk. + * `n` is rounded down, with `NaN` and non-positive values treated as `0`. * * **Example** (Taking elements from the start) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.take(chunk, 3) - * console.log(Chunk.toArray(result)) // [1, 2, 3] + * Chunk.toArray(Chunk.take(chunk, 3)) // => [1, 2, 3] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const take: { (n: number): (self: Chunk) => Chunk (self: Chunk, n: number): Chunk -} = dual(2, (self: Chunk, n: number): Chunk => { +} = dual(2, (self: Chunk, _n: number): Chunk => { + const n = Count.normalize(_n) if (n <= 0) { return _empty } else if (n >= self.length) { @@ -782,24 +771,25 @@ export const take: { /** * Drops the first up to `n` elements from the chunk. + * `n` is rounded down, with `NaN` and non-positive values treated as `0`. * * **Example** (Dropping elements from the start) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.drop(chunk, 2) - * console.log(Chunk.toArray(result)) // [3, 4, 5] + * Chunk.toArray(Chunk.drop(chunk, 2)) // => [3, 4, 5] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const drop: { (n: number): (self: Chunk) => Chunk (self: Chunk, n: number): Chunk -} = dual(2, (self: Chunk, n: number): Chunk => { +} = dual(2, (self: Chunk, _n: number): Chunk => { + const n = Count.normalize(_n) if (n <= 0) { return self } else if (n >= self.length) { @@ -838,39 +828,41 @@ export const drop: { /** * Drops the last `n` elements. + * `n` is rounded down, with `NaN` and non-positive values treated as `0`. * * **Example** (Dropping elements from the end) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.dropRight(chunk, 2) - * console.log(Chunk.toArray(result)) // [1, 2, 3] + * Chunk.toArray(Chunk.dropRight(chunk, 2)) // => [1, 2, 3] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const dropRight: { (n: number): (self: Chunk) => Chunk (self: Chunk, n: number): Chunk -} = dual(2, (self: Chunk, n: number): Chunk => take(self, Math.max(0, self.length - n))) +} = dual( + 2, + (self: Chunk, n: number): Chunk => take(self, self.length - Math.min(Count.normalize(n), self.length)) +) /** * Drops all elements so long as the predicate returns true. * * **Example** (Dropping elements while a predicate matches) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.dropWhile(chunk, (n) => n < 3) - * console.log(Chunk.toArray(result)) // [3, 4, 5] + * Chunk.toArray(Chunk.dropWhile(chunk, (n) => n < 3)) // => [3, 4, 5] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const dropWhile: { @@ -892,16 +884,13 @@ export const dropWhile: { * * **Example** (Prepending all elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const result = Chunk.make(1, 2).pipe( + * Chunk.make(1, 2).pipe( * Chunk.prependAll(Chunk.make("a", "b")), * Chunk.toArray - * ) - * - * console.log(result) - * // [ "a", "b", 1, 2 ] + * ) // => ["a", "b", 1, 2] * ``` * * @category combining @@ -927,16 +916,13 @@ export const prependAll: { * * **Example** (Appending all elements) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const result = Chunk.make(1, 2).pipe( + * Chunk.make(1, 2).pipe( * Chunk.appendAll(Chunk.make("a", "b")), * Chunk.toArray - * ) - * - * console.log(result) - * // [ 1, 2, "a", "b" ] + * ) // => [1, 2, "a", "b"] * ``` * * @see {@link prependAll} for concatenating chunks in the opposite order @@ -998,7 +984,7 @@ export const appendAll: { * * **Example** (Filtering and mapping values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const chunk = Chunk.make("1", "2", "hello", "3", "world") @@ -1006,14 +992,14 @@ export const appendAll: { * const num = parseInt(str) * return isNaN(num) ? Result.failVoid : Result.succeed(num) * }) - * console.log(Chunk.toArray(numbers)) // [1, 2, 3] + * Chunk.toArray(numbers) // => [1, 2, 3] * * // With index parameter * const evenIndexNumbers = Chunk.filterMap(chunk, (str, i) => { * const num = parseInt(str) * return isNaN(num) || i % 2 !== 0 ? Result.failVoid : Result.succeed(num) * }) - * console.log(Chunk.toArray(evenIndexNumbers)) // [1] + * Chunk.toArray(evenIndexNumbers) // => [1] * ``` * * @category filtering @@ -1042,17 +1028,17 @@ export const filterMap: { * * **Example** (Filtering values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) * const evenNumbers = Chunk.filter(chunk, (n) => n % 2 === 0) - * console.log(Chunk.toArray(evenNumbers)) // [2, 4, 6] + * Chunk.toArray(evenNumbers) // => [2, 4, 6] * * // With refinement * const mixed = Chunk.make("hello", 42, "world", 100) * const numbers = Chunk.filter(mixed, (x): x is number => typeof x === "number") - * console.log(Chunk.toArray(numbers)) // [42, 100] + * Chunk.toArray(numbers) // => [42, 100] * ``` * * @category filtering @@ -1073,23 +1059,19 @@ export const filter: { * * **Example** (Filtering and mapping while values match) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const chunk = Chunk.make("1", "2", "hello", "3", "4") - * const result = Chunk.filterMapWhile(chunk, (s) => { - * const num = parseInt(s) - * return isNaN(num) ? Result.failVoid : Result.succeed(num) - * }) - * console.log(Chunk.toArray(result)) // [1, 2] - * // Stops at "hello" and doesn't process "3", "4" + * Chunk.toArray(Chunk.filterMapWhile(chunk, (s) => { + * const n = Number(s) + * return Number.isNaN(n) ? Result.failVoid : Result.succeed(n) + * })) // => [1, 2] * - * // Compare with regular filterMap - * const allNumbers = Chunk.filterMap(chunk, (s) => { - * const num = parseInt(s) - * return isNaN(num) ? Result.failVoid : Result.succeed(num) - * }) - * console.log(Chunk.toArray(allNumbers)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.filterMap(chunk, (s) => { + * const n = Number(s) + * return Number.isNaN(n) ? Result.failVoid : Result.succeed(n) + * })) // => [1, 2, 3, 4] * ``` * * @category filtering @@ -1116,12 +1098,11 @@ export const filterMapWhile: { * * **Example** (Compacting optional values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(Option.some(1), Option.none(), Option.some(3)) - * const result = Chunk.compact(chunk) - * console.log(Chunk.toArray(result)) // [1, 3] + * Chunk.toArray(Chunk.compact(chunk)) // => [1, 3] * ``` * * @category filtering @@ -1142,12 +1123,12 @@ export const compact = (self: Chunk>): Chunk => { * * **Example** (Flat mapping chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3) * const duplicated = Chunk.flatMap(chunk, (n) => Chunk.make(n, n)) - * console.log(Chunk.toArray(duplicated)) // [1, 1, 2, 2, 3, 3] + * Chunk.toArray(duplicated) // => [1, 1, 2, 2, 3, 3] * * // Flattening nested arrays * const words = Chunk.make("hello", "world") @@ -1155,11 +1136,11 @@ export const compact = (self: Chunk>): Chunk => { * words, * (word) => Chunk.fromIterable(word.split("")) * ) - * console.log(Chunk.toArray(letters)) // ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"] + * Chunk.toArray(letters).join("") // => "helloworld" * * // With index parameter * const indexed = Chunk.flatMap(chunk, (n, i) => Chunk.make(n + i)) - * console.log(Chunk.toArray(indexed)) // [1, 3, 5] + * Chunk.toArray(indexed) // => [1, 3, 5] * ``` * * @category sequencing @@ -1195,26 +1176,19 @@ export const flatMap: { * * **Example** (Iterating over chunk values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) * - * // Log each element - * Chunk.forEach(chunk, (n) => console.log(`Value: ${n}`)) - * // Output: - * // Value: 1 - * // Value: 2 - * // Value: 3 - * // Value: 4 + * const values: Array = [] + * Chunk.forEach(chunk, (n) => values.push(`Value: ${n}`)) + * values // => ["Value: 1", "Value: 2", "Value: 3", "Value: 4"] * * // With index parameter - * Chunk.forEach(chunk, (n, i) => console.log(`Index ${i}: ${n}`)) - * // Output: - * // Index 0: 1 - * // Index 1: 2 - * // Index 2: 3 - * // Index 3: 4 + * const indexed: Array = [] + * Chunk.forEach(chunk, (n, i) => indexed.push(`Index ${i}: ${n}`)) + * indexed // => ["Index 0: 1", "Index 1: 2", "Index 2: 3", "Index 3: 4"] * ``` * * @category combinators @@ -1230,7 +1204,7 @@ export const forEach: { * * **Example** (Flattening nested chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nested = Chunk.make( @@ -1238,8 +1212,7 @@ export const forEach: { * Chunk.make(3, 4, 5), * Chunk.make(6) * ) - * const flattened = Chunk.flatten(nested) - * console.log(Chunk.toArray(flattened)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(Chunk.flatten(nested)) // => [1, 2, 3, 4, 5, 6] * * // With empty chunks * const withEmpty = Chunk.make( @@ -1247,7 +1220,7 @@ export const forEach: { * Chunk.empty(), * Chunk.make(3, 4) * ) - * console.log(Chunk.toArray(Chunk.flatten(withEmpty))) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.flatten(withEmpty)) // => [1, 2, 3, 4] * ``` * * @category sequencing @@ -1265,45 +1238,45 @@ export const flatten: >>(self: S) => Chunk.Flatten * * **Details** * - * The final chunk may contain fewer than `n` elements. Empty input produces an - * empty chunk of chunks. + * `n` is rounded down and normalized to at least `1`. The final chunk may + * contain fewer than `n` elements. Empty input produces an empty chunk of + * chunks. * * **Gotchas** * - * Values of `n` less than or equal to zero produce singleton chunks. + * `NaN` and values of `n` less than or equal to zero produce singleton chunks. * * **Example** (Splitting into fixed-size chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6, 7, 8, 9) * const chunked = Chunk.chunksOf(chunk, 3) * - * console.log(Chunk.toArray(chunked).map(Chunk.toArray)) - * // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + * Chunk.toArray(chunked).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] * * // When length is not evenly divisible * const chunk2 = Chunk.make(1, 2, 3, 4, 5) * const chunked2 = Chunk.chunksOf(chunk2, 2) - * console.log(Chunk.toArray(chunked2).map(Chunk.toArray)) - * // [[1, 2], [3, 4], [5]] + * Chunk.toArray(chunked2).map(Chunk.toArray) // => [[1, 2], [3, 4], [5]] * ``` * * @see {@link split} for splitting into a target number of chunks instead of a fixed chunk size * - * @category elements + * @category splitting * @since 2.0.0 */ export const chunksOf: { (n: number): (self: Chunk) => Chunk> (self: Chunk, n: number): Chunk> } = dual(2, (self: Chunk, n: number) => { + const size = Count.normalizeNonEmpty(n) const gr: Array> = [] let current: Array = [] toReadonlyArray(self).forEach((a) => { current.push(a) - if (current.length >= n) { + if (current.length >= size) { gr.push(fromArrayUnsafe(current)) current = [] } @@ -1323,26 +1296,25 @@ export const chunksOf: { * * **Example** (Intersecting chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make(1, 2, 3, 4) * const chunk2 = Chunk.make(3, 4, 5, 6) - * const result = Chunk.intersection(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [3, 4] + * Chunk.toArray(Chunk.intersection(chunk1, chunk2)) // => [3, 4] * * // With strings * const words1 = Chunk.make("hello", "world", "foo") * const words2 = Chunk.make("world", "bar", "foo") - * console.log(Chunk.toArray(Chunk.intersection(words1, words2))) // ["world", "foo"] + * Chunk.toArray(Chunk.intersection(words1, words2)) // => ["world", "foo"] * * // No intersection * const chunk3 = Chunk.make(1, 2) * const chunk4 = Chunk.make(3, 4) - * console.log(Chunk.toArray(Chunk.intersection(chunk3, chunk4))) // [] + * Chunk.toArray(Chunk.intersection(chunk3, chunk4)) // => [] * ``` * - * @category elements + * @category set operations * @since 2.0.0 */ export const intersection: { @@ -1359,14 +1331,14 @@ export const intersection: { * * **Example** (Checking for empty chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * console.log(Chunk.isEmpty(Chunk.empty())) // true - * console.log(Chunk.isEmpty(Chunk.make(1, 2, 3))) // false + * Chunk.isEmpty(Chunk.empty()) // => true + * Chunk.isEmpty(Chunk.make(1, 2, 3)) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isEmpty = (self: Chunk): boolean => self.length === 0 @@ -1376,14 +1348,14 @@ export const isEmpty = (self: Chunk): boolean => self.length === 0 * * **Example** (Checking for non-empty chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * console.log(Chunk.isNonEmpty(Chunk.empty())) // false - * console.log(Chunk.isNonEmpty(Chunk.make(1, 2, 3))) // true + * Chunk.isNonEmpty(Chunk.empty()) // => false + * Chunk.isNonEmpty(Chunk.make(1, 2, 3)) // => true * ``` * - * @category elements + * @category guards * @since 2.0.0 */ export const isNonEmpty = (self: Chunk): self is NonEmptyChunk => self.length > 0 @@ -1393,14 +1365,14 @@ export const isNonEmpty = (self: Chunk): self is NonEmptyChunk => self. * * **Example** (Getting the first element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * - * console.log(Chunk.head(Chunk.empty())) // { _tag: "None" } - * console.log(Chunk.head(Chunk.make(1, 2, 3))) // { _tag: "Some", value: 1 } + * Chunk.head(Chunk.empty()) // => Option.none() + * Chunk.head(Chunk.make(1, 2, 3)) // => Option.some(1) * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const head: (self: Chunk) => Option = get(0) @@ -1419,17 +1391,17 @@ export const head: (self: Chunk) => Option = get(0) * * **Example** (Getting the first element unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.headUnsafe(chunk)) // 1 + * Chunk.headUnsafe(chunk) // => 1 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.headUnsafe(singleElement)) // "hello" + * Chunk.headUnsafe(singleElement) // => "hello" * * // Use Chunk.head when the chunk may be empty - * console.log(Option.isNone(Chunk.head(Chunk.empty()))) // true + * Option.isNone(Chunk.head(Chunk.empty())) // => true * ``` * * @category unsafe @@ -1442,20 +1414,20 @@ export const headUnsafe = (self: Chunk): A => getUnsafe(self, 0) * * **Example** (Getting the first element of a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.headNonEmpty(nonEmptyChunk)) // 1 + * Chunk.headNonEmpty(nonEmptyChunk) // => 1 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.headNonEmpty(singleElement)) // "hello" + * Chunk.headNonEmpty(singleElement) // => "hello" * * // Type safety: this function only accepts NonEmptyChunk * // Chunk.headNonEmpty(Chunk.empty()) // TypeScript error * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const headNonEmpty: (self: NonEmptyChunk) => A = headUnsafe @@ -1465,14 +1437,14 @@ export const headNonEmpty: (self: NonEmptyChunk) => A = headUnsafe * * **Example** (Getting the last element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * - * console.log(Chunk.last(Chunk.empty())) // { _tag: "None" } - * console.log(Chunk.last(Chunk.make(1, 2, 3))) // { _tag: "Some", value: 3 } + * Chunk.last(Chunk.empty()) // => Option.none() + * Chunk.last(Chunk.make(1, 2, 3)) // => Option.some(3) * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const last = (self: Chunk): Option => get(self, self.length - 1) @@ -1491,17 +1463,17 @@ export const last = (self: Chunk): Option => get(self, self.length - 1) * * **Example** (Getting the last element unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.lastUnsafe(chunk)) // 4 + * Chunk.lastUnsafe(chunk) // => 4 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.lastUnsafe(singleElement)) // "hello" + * Chunk.lastUnsafe(singleElement) // => "hello" * * // Use Chunk.last when the chunk may be empty - * console.log(Option.isNone(Chunk.last(Chunk.empty()))) // true + * Option.isNone(Chunk.last(Chunk.empty())) // => true * ``` * * @category unsafe @@ -1514,20 +1486,20 @@ export const lastUnsafe = (self: Chunk): A => getUnsafe(self, self.length * * **Example** (Getting the last element of a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.lastNonEmpty(nonEmptyChunk)) // 4 + * Chunk.lastNonEmpty(nonEmptyChunk) // => 4 * * const singleElement = Chunk.make("hello") - * console.log(Chunk.lastNonEmpty(singleElement)) // "hello" + * Chunk.lastNonEmpty(singleElement) // => "hello" * * // Type safety: this function only accepts NonEmptyChunk * // Chunk.lastNonEmpty(Chunk.empty()) // TypeScript error * ``` * - * @category elements + * @category getters * @since 3.4.0 */ export const lastNonEmpty: (self: NonEmptyChunk) => A = lastUnsafe @@ -1537,7 +1509,7 @@ export const lastNonEmpty: (self: NonEmptyChunk) => A = lastUnsafe * * **Example** (Working with Chunk utility types) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * // Extract the element type from a Chunk @@ -1557,7 +1529,7 @@ export declare namespace Chunk { * * **Example** (Inferring element types) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const numberChunk: Chunk.Chunk @@ -1567,7 +1539,7 @@ export declare namespace Chunk { * type StringType = Chunk.Chunk.Infer // string * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Infer> = S extends Chunk ? A : never @@ -1577,7 +1549,7 @@ export declare namespace Chunk { * * **Example** (Preserving non-emptiness) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const regularChunk: Chunk.Chunk @@ -1587,7 +1559,7 @@ export declare namespace Chunk { * type WithString2 = Chunk.Chunk.With // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type With, A> = S extends NonEmptyChunk ? NonEmptyChunk : Chunk @@ -1597,7 +1569,7 @@ export declare namespace Chunk { * * **Example** (Preserving non-emptiness from either input) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const emptyChunk: Chunk.Chunk @@ -1620,7 +1592,7 @@ export declare namespace Chunk { * > // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type OrNonEmpty, T extends Chunk, A> = S extends NonEmptyChunk ? @@ -1633,7 +1605,7 @@ export declare namespace Chunk { * * **Example** (Requiring non-emptiness from both inputs) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const emptyChunk: Chunk.Chunk @@ -1656,7 +1628,7 @@ export declare namespace Chunk { * > // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type AndNonEmpty, T extends Chunk, A> = S extends NonEmptyChunk ? @@ -1669,7 +1641,7 @@ export declare namespace Chunk { * * **Example** (Flattening nested chunk types) * - * ```ts + * ```ts import.meta.vitest * import type { Chunk } from "effect" * * declare const nestedChunk: Chunk.Chunk> @@ -1679,7 +1651,7 @@ export declare namespace Chunk { * type Flattened2 = Chunk.Chunk.Flatten // Chunk.NonEmptyChunk * ``` * - * @category types + * @category utility types * @since 2.0.0 */ export type Flatten>> = T extends NonEmptyChunk> ? NonEmptyChunk @@ -1693,12 +1665,10 @@ export declare namespace Chunk { * * **Example** (Mapping values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const result = Chunk.map(Chunk.make(1, 2), (n) => n + 1) - * - * console.log(Chunk.toArray(result)) // [2, 3] + * Chunk.toArray(Chunk.map(Chunk.make(1, 2), (n) => n + 1)) // => [2, 3] * ``` * * @category mapping @@ -1718,7 +1688,7 @@ export const map: { * * **Example** (Mapping with accumulated state) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) @@ -1727,8 +1697,8 @@ export const map: { * state + current // output running sum * ]) * - * console.log(finalState) // 15 (final accumulated sum) - * console.log(Chunk.toArray(mapped)) // [1, 3, 6, 10, 15] (running sums) + * finalState // => 15 + * Chunk.toArray(mapped) // => [1, 3, 6, 10, 15] * * // Building a string with indices * const words = Chunk.make("hello", "world", "effect") @@ -1736,8 +1706,8 @@ export const map: { * index + 1, * `${index}: ${word}` * ]) - * console.log(count) // 3 - * console.log(Chunk.toArray(indexed)) // ["0: hello", "1: world", "2: effect"] + * count // => 3 + * Chunk.toArray(indexed) // => ["0: hello", "1: world", "2: effect"] * ``` * * @category folding @@ -1760,15 +1730,15 @@ export const mapAccum: { * * **Example** (Partitioning with a Result) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const [excluded, satisfying] = Chunk.partition(Chunk.make(1, -2, 3), (n, i) => * n > 0 ? Result.succeed(n + i) : Result.fail(`negative:${n}`) * ) * - * console.log(Chunk.toArray(excluded)) // ["negative:-2"] - * console.log(Chunk.toArray(satisfying)) // [1, 5] + * Chunk.toArray(excluded) // => ["negative:-2"] + * Chunk.toArray(satisfying) // => [1, 5] * ``` * * @category filtering @@ -1804,7 +1774,7 @@ export const partition: { * * **Example** (Separating failures and successes) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Result } from "effect" * * const chunk = Chunk.make( @@ -1816,14 +1786,14 @@ export const partition: { * ) * * const [errors, values] = Chunk.separate(chunk) - * console.log(Chunk.toArray(errors)) // ["error1", "error2"] - * console.log(Chunk.toArray(values)) // [1, 2, 3] + * Chunk.toArray(errors) // => ["error1", "error2"] + * Chunk.toArray(values) // => [1, 2, 3] * * // All successes * const allSuccesses = Chunk.make(Result.succeed(1), Result.succeed(2)) * const [noErrors, allValues] = Chunk.separate(allSuccesses) - * console.log(Chunk.toArray(noErrors)) // [] - * console.log(Chunk.toArray(allValues)) // [1, 2] + * Chunk.toArray(noErrors) // => [] + * Chunk.toArray(allValues) // => [1, 2] * ``` * * @category filtering @@ -1840,14 +1810,13 @@ export const separate = (self: Chunk>): [Chunk, Chunk] * * **Example** (Getting chunk size) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.make(1, 2, 3) - * console.log(Chunk.size(chunk)) // 3 + * Chunk.size(Chunk.make(1, 2, 3)) // => 3 * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const size = (self: Chunk): number => self.length @@ -1857,21 +1826,18 @@ export const size = (self: Chunk): number => self.length * * **Example** (Sorting chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Order } from "effect" * * const numbers = Chunk.make(3, 1, 4, 1, 5, 9, 2, 6) - * const sorted = Chunk.sort(numbers, Order.Number) - * console.log(Chunk.toArray(sorted)) // [1, 1, 2, 3, 4, 5, 6, 9] + * Chunk.toArray(Chunk.sort(numbers, Order.Number)) // => [1, 1, 2, 3, 4, 5, 6, 9] * * // Reverse order - * const reverseSorted = Chunk.sort(numbers, Order.flip(Order.Number)) - * console.log(Chunk.toArray(reverseSorted)) // [9, 6, 5, 4, 3, 2, 1, 1] + * Chunk.toArray(Chunk.sort(numbers, Order.flip(Order.Number))) // => [9, 6, 5, 4, 3, 2, 1, 1] * * // String sorting * const words = Chunk.make("banana", "apple", "cherry") - * const sortedWords = Chunk.sort(words, Order.String) - * console.log(Chunk.toArray(sortedWords)) // ["apple", "banana", "cherry"] + * Chunk.toArray(Chunk.sort(words, Order.String)) // => ["apple", "banana", "cherry"] * ``` * * @category sorting @@ -1890,7 +1856,7 @@ export const sort: { * * **Example** (Sorting chunks by a derived value) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Order } from "effect" * * const people = Chunk.make( @@ -1901,18 +1867,15 @@ export const sort: { * * // Sort by age * const byAge = Chunk.sortWith(people, (person) => person.age, Order.Number) - * console.log(Chunk.toArray(byAge)) - * // [{ name: "Bob", age: 25 }, { name: "Alice", age: 30 }, { name: "Charlie", age: 35 }] + * Chunk.toArray(byAge).map((person) => person.name) // => ["Bob", "Alice", "Charlie"] * * // Sort by name * const byName = Chunk.sortWith(people, (person) => person.name, Order.String) - * console.log(Chunk.toArray(byName)) - * // [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }, { name: "Charlie", age: 35 }] + * Chunk.toArray(byName).map((person) => person.name) // => ["Alice", "Bob", "Charlie"] * * // Sort by string length * const words = Chunk.make("a", "abc", "ab") - * const byLength = Chunk.sortWith(words, (word) => word.length, Order.Number) - * console.log(Chunk.toArray(byLength)) // ["a", "ab", "abc"] + * Chunk.toArray(Chunk.sortWith(words, (word) => word.length, Order.Number)) // => ["a", "ab", "abc"] * ``` * * @category sorting @@ -1931,23 +1894,23 @@ export const sortWith: { * * **Example** (Splitting at an index) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) * const [before, after] = Chunk.splitAt(chunk, 3) - * console.log(Chunk.toArray(before)) // [1, 2, 3] - * console.log(Chunk.toArray(after)) // [4, 5, 6] + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(after) // => [4, 5, 6] * * // Split at index 0 * const [empty, all] = Chunk.splitAt(chunk, 0) - * console.log(Chunk.toArray(empty)) // [] - * console.log(Chunk.toArray(all)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty) // => [] + * Chunk.toArray(all) // => [1, 2, 3, 4, 5, 6] * * // Split beyond length * const [allElements, empty2] = Chunk.splitAt(chunk, 10) - * console.log(Chunk.toArray(allElements)) // [1, 2, 3, 4, 5, 6] - * console.log(Chunk.toArray(empty2)) // [] + * Chunk.toArray(allElements) // => [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty2) // => [] * ``` * * @category splitting @@ -1964,24 +1927,24 @@ export const splitAt: { * * **Details** * - * `n` is floored and normalized to at least `1`. If `n` is greater than or - * equal to the chunk length, the first result is the original chunk and the - * second result is empty. + * `n` is rounded down and normalized to at least `1`, with `NaN` treated as + * `1`. If `n` is greater than or equal to the chunk length, the first result is + * the original chunk and the second result is empty. * * **Example** (Splitting non-empty chunks at an index) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4, 5, 6) * const [before, after] = Chunk.splitNonEmptyAt(nonEmptyChunk, 3) - * console.log(Chunk.toArray(before)) // [1, 2, 3] - * console.log(Chunk.toArray(after)) // [4, 5, 6] + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(after) // => [4, 5, 6] * * // Split at 1 (minimum) * const [first, rest] = Chunk.splitNonEmptyAt(nonEmptyChunk, 1) - * console.log(Chunk.toArray(first)) // [1] - * console.log(Chunk.toArray(rest)) // [2, 3, 4, 5, 6] + * Chunk.toArray(first) // => [1] + * Chunk.toArray(rest) // => [2, 3, 4, 5, 6] * * // The first part is guaranteed to be NonEmptyChunk * // while the second part may be empty @@ -1994,7 +1957,7 @@ export const splitNonEmptyAt: { (n: number): (self: NonEmptyChunk) => [beforeIndex: NonEmptyChunk, fromIndex: Chunk] (self: NonEmptyChunk, n: number): [beforeIndex: NonEmptyChunk, fromIndex: Chunk] } = dual(2, (self: NonEmptyChunk, n: number): [Chunk, Chunk] => { - const _n = Math.max(1, Math.floor(n)) + const _n = Count.normalizeNonEmpty(n) return _n >= self.length ? [self, empty()] : [take(self, _n), drop(self, _n)] @@ -2005,29 +1968,27 @@ export const splitNonEmptyAt: { * * **Details** * - * The chunk size is derived from the input length and `n`; the final chunk may - * contain fewer elements than the others. + * `n` is rounded down and normalized to at least `1`, with `NaN` treated as + * `1`. The chunk size is derived from the input length and normalized count; + * the final chunk may contain fewer elements than the others. * * **Example** (Splitting chunks into groups) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6, 7, 8, 9) * const chunks = Chunk.split(chunk, 3) - * console.log(Chunk.toArray(chunks).map(Chunk.toArray)) - * // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + * Chunk.toArray(chunks).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] * * // Uneven split * const chunk2 = Chunk.make(1, 2, 3, 4, 5, 6, 7, 8) * const chunks2 = Chunk.split(chunk2, 3) - * console.log(Chunk.toArray(chunks2).map(Chunk.toArray)) - * // [[1, 2, 3], [4, 5, 6], [7, 8]] + * Chunk.toArray(chunks2).map(Chunk.toArray) // => [[1, 2, 3], [4, 5, 6], [7, 8]] * * // Split into 1 chunk * const chunks3 = Chunk.split(chunk, 1) - * console.log(Chunk.toArray(chunks3).map(Chunk.toArray)) - * // [[1, 2, 3, 4, 5, 6, 7, 8, 9]] + * Chunk.toArray(chunks3).map(Chunk.toArray) // => [[1, 2, 3, 4, 5, 6, 7, 8, 9]] * ``` * * @category splitting @@ -2036,7 +1997,7 @@ export const splitNonEmptyAt: { export const split: { (n: number): (self: Chunk) => Chunk> (self: Chunk, n: number): Chunk> -} = dual(2, (self: Chunk, n: number) => chunksOf(self, Math.ceil(self.length / Math.floor(n)))) +} = dual(2, (self: Chunk, n: number) => chunksOf(self, Math.ceil(self.length / Count.normalizeNonEmpty(n)))) /** * Splits this chunk on the first element that matches this predicate. @@ -2044,23 +2005,23 @@ export const split: { * * **Example** (Splitting at a matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) * const [before, fromMatch] = Chunk.splitWhere(chunk, (n) => n > 3) - * console.log(Chunk.toArray(before)) // [1, 2, 3] - * console.log(Chunk.toArray(fromMatch)) // [4, 5, 6] + * Chunk.toArray(before) // => [1, 2, 3] + * Chunk.toArray(fromMatch) // => [4, 5, 6] * * // No match found * const [all, empty] = Chunk.splitWhere(chunk, (n) => n > 10) - * console.log(Chunk.toArray(all)) // [1, 2, 3, 4, 5, 6] - * console.log(Chunk.toArray(empty)) // [] + * Chunk.toArray(all) // => [1, 2, 3, 4, 5, 6] + * Chunk.toArray(empty) // => [] * * // Match on first element * const [emptyBefore, allFromFirst] = Chunk.splitWhere(chunk, (n) => n === 1) - * console.log(Chunk.toArray(emptyBefore)) // [] - * console.log(Chunk.toArray(allFromFirst)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(emptyBefore) // => [] + * Chunk.toArray(allFromFirst) // => [1, 2, 3, 4, 5, 6] * ``` * * @category splitting @@ -2086,20 +2047,19 @@ export const splitWhere: { * * **Example** (Getting the tail safely) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * console.log(Chunk.tail(chunk)) // Option.some(Chunk.make(2, 3, 4)) + * Chunk.tail(chunk) // => Option.some(Chunk.make(2, 3, 4)) * * const singleElement = Chunk.make(1) - * console.log(Chunk.tail(singleElement)) // Option.some(Chunk.empty()) + * Chunk.tail(singleElement) // => Option.some(Chunk.empty()) * - * const empty = Chunk.empty() - * console.log(Chunk.tail(empty)) // Option.none() + * Chunk.tail(Chunk.empty()) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const tail = (self: Chunk): O.Option> => self.length > 0 ? O.some(drop(self, 1)) : O.none() @@ -2109,78 +2069,74 @@ export const tail = (self: Chunk): O.Option> => self.length > 0 ? * * **Example** (Getting the tail of a non-empty chunk) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const nonEmptyChunk = Chunk.make(1, 2, 3, 4) - * const result = Chunk.tailNonEmpty(nonEmptyChunk) - * console.log(Chunk.toArray(result)) // [2, 3, 4] + * Chunk.toArray(Chunk.tailNonEmpty(nonEmptyChunk)) // => [2, 3, 4] * * const singleElement = Chunk.make(1) - * const resultSingle = Chunk.tailNonEmpty(singleElement) - * console.log(Chunk.toArray(resultSingle)) // [] + * Chunk.toArray(Chunk.tailNonEmpty(singleElement)) // => [] * * // Type safety: this function only accepts NonEmptyChunk * // Chunk.tailNonEmpty(Chunk.empty()) // TypeScript error * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const tailNonEmpty = (self: NonEmptyChunk): Chunk => drop(self, 1) /** * Takes the last `n` elements. + * `n` is rounded down, with `NaN` and non-positive values treated as `0`. * * **Example** (Taking elements from the end) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5, 6) - * const lastThree = Chunk.takeRight(chunk, 3) - * console.log(Chunk.toArray(lastThree)) // [4, 5, 6] + * Chunk.toArray(Chunk.takeRight(chunk, 3)) // => [4, 5, 6] * * // Take more than available - * const all = Chunk.takeRight(chunk, 10) - * console.log(Chunk.toArray(all)) // [1, 2, 3, 4, 5, 6] + * Chunk.toArray(Chunk.takeRight(chunk, 10)) // => [1, 2, 3, 4, 5, 6] * * // Take zero - * const none = Chunk.takeRight(chunk, 0) - * console.log(Chunk.toArray(none)) // [] + * Chunk.toArray(Chunk.takeRight(chunk, 0)) // => [] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const takeRight: { (n: number): (self: Chunk) => Chunk (self: Chunk, n: number): Chunk -} = dual(2, (self: Chunk, n: number): Chunk => drop(self, self.length - n)) +} = dual( + 2, + (self: Chunk, n: number): Chunk => drop(self, self.length - Math.min(Count.normalize(n), self.length)) +) /** * Takes all elements so long as the predicate returns true. * * **Example** (Taking elements while a predicate matches) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 3, 2, 1) - * const result = Chunk.takeWhile(chunk, (n) => n < 4) - * console.log(Chunk.toArray(result)) // [1, 2, 3] + * Chunk.toArray(Chunk.takeWhile(chunk, (n) => n < 4)) // => [1, 2, 3] * * // Empty if first element doesn't match - * const none = Chunk.takeWhile(chunk, (n) => n > 5) - * console.log(Chunk.toArray(none)) // [] + * Chunk.toArray(Chunk.takeWhile(chunk, (n) => n > 5)) // => [] * * // Takes all if all match * const small = Chunk.make(1, 2, 3) - * const all = Chunk.takeWhile(small, (n) => n < 10) - * console.log(Chunk.toArray(all)) // [1, 2, 3] + * Chunk.toArray(Chunk.takeWhile(small, (n) => n < 10)) // => [1, 2, 3] * ``` * - * @category elements + * @category filtering * @since 2.0.0 */ export const takeWhile: { @@ -2205,22 +2161,20 @@ export const takeWhile: { * * **Example** (Unioning chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make(1, 2, 3) * const chunk2 = Chunk.make(3, 4, 5) - * const result = Chunk.union(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [1, 2, 3, 4, 5] + * Chunk.toArray(Chunk.union(chunk1, chunk2)) // => [1, 2, 3, 4, 5] * * // Handles duplicates within the same chunk * const withDupes1 = Chunk.make(1, 1, 2) * const withDupes2 = Chunk.make(2, 3, 3) - * const unified = Chunk.union(withDupes1, withDupes2) - * console.log(Chunk.toArray(unified)) // [1, 2, 3] + * Chunk.toArray(Chunk.union(withDupes1, withDupes2)) // => [1, 2, 3] * ``` * - * @category elements + * @category set operations * @since 2.0.0 */ export const union: { @@ -2237,25 +2191,22 @@ export const union: { * * **Example** (Removing duplicate values) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 2, 3, 1, 4, 3) - * const result = Chunk.dedupe(chunk) - * console.log(Chunk.toArray(result)) // [1, 2, 3, 4] + * Chunk.toArray(Chunk.dedupe(chunk)) // => [1, 2, 3, 4] * * // Empty chunk * const empty = Chunk.empty() - * const emptyDeduped = Chunk.dedupe(empty) - * console.log(Chunk.toArray(emptyDeduped)) // [] + * Chunk.toArray(Chunk.dedupe(empty)) // => [] * * // No duplicates * const unique = Chunk.make(1, 2, 3) - * const uniqueDeduped = Chunk.dedupe(unique) - * console.log(Chunk.toArray(uniqueDeduped)) // [1, 2, 3] + * Chunk.toArray(Chunk.dedupe(unique)) // => [1, 2, 3] * ``` * - * @category elements + * @category deduplication * @since 2.0.0 */ export const dedupe = (self: Chunk): Chunk => fromArrayUnsafe(RA.dedupe(toReadonlyArray(self))) @@ -2265,17 +2216,15 @@ export const dedupe = (self: Chunk): Chunk => fromArrayUnsafe(RA.dedupe * * **Example** (Removing adjacent duplicates) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 1, 2, 2, 2, 3, 1, 1) - * const result = Chunk.dedupeAdjacent(chunk) - * console.log(Chunk.toArray(result)) // [1, 2, 3, 1] + * Chunk.toArray(Chunk.dedupeAdjacent(chunk)) // => [1, 2, 3, 1] * * // Only removes adjacent duplicates, not all duplicates * const mixed = Chunk.make("a", "a", "b", "a", "a") - * const mixedResult = Chunk.dedupeAdjacent(mixed) - * console.log(Chunk.toArray(mixedResult)) // ["a", "b", "a"] + * Chunk.toArray(Chunk.dedupeAdjacent(mixed)) // => ["a", "b", "a"] * ``` * * @category filtering @@ -2292,7 +2241,7 @@ export const dedupeAdjacent = (self: Chunk): Chunk => fromArrayUnsafe(R * * **Example** (Unzipping pairs) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const pairs = Chunk.make( @@ -2301,17 +2250,17 @@ export const dedupeAdjacent = (self: Chunk): Chunk => fromArrayUnsafe(R * [3, "c"] as const * ) * const [numbers, letters] = Chunk.unzip(pairs) - * console.log(Chunk.toArray(numbers)) // [1, 2, 3] - * console.log(Chunk.toArray(letters)) // ["a", "b", "c"] + * Chunk.toArray(numbers) // => [1, 2, 3] + * Chunk.toArray(letters) // => ["a", "b", "c"] * * // Empty chunk * const empty = Chunk.empty<[number, string]>() * const [emptyNums, emptyStrs] = Chunk.unzip(empty) - * console.log(Chunk.toArray(emptyNums)) // [] - * console.log(Chunk.toArray(emptyStrs)) // [] + * Chunk.toArray(emptyNums) // => [] + * Chunk.toArray(emptyStrs) // => [] * ``` * - * @category elements + * @category splitting * @since 2.0.0 */ export const unzip = (self: Chunk): [Chunk, Chunk] => { @@ -2324,19 +2273,17 @@ export const unzip = (self: Chunk): [Chunk, Chunk] * * **Example** (Zipping chunks with a function) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const numbers = Chunk.make(1, 2, 3) * const letters = Chunk.make("a", "b", "c") - * const result = Chunk.zipWith(numbers, letters, (n, l) => `${n}-${l}`) - * console.log(Chunk.toArray(result)) // ["1-a", "2-b", "3-c"] + * Chunk.toArray(Chunk.zipWith(numbers, letters, (n, l) => `${n}-${l}`)) // => ["1-a", "2-b", "3-c"] * * // Different lengths - takes minimum * const short = Chunk.make(1, 2) * const long = Chunk.make("a", "b", "c", "d") - * const mixed = Chunk.zipWith(short, long, (n, l) => [n, l]) - * console.log(Chunk.toArray(mixed)) // [[1, "a"], [2, "b"]] + * Chunk.toArray(Chunk.zipWith(short, long, (n, l) => [n, l])) // => [[1, "a"], [2, "b"]] * ``` * * @category zipping @@ -2356,19 +2303,17 @@ export const zipWith: { * * **Example** (Zipping chunks) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const numbers = Chunk.make(1, 2, 3) * const letters = Chunk.make("a", "b", "c") - * const result = Chunk.zip(numbers, letters) - * console.log(Chunk.toArray(result)) // [[1, "a"], [2, "b"], [3, "c"]] + * Chunk.toArray(Chunk.zip(numbers, letters)) // => [[1, "a"], [2, "b"], [3, "c"]] * * // Different lengths - takes minimum length * const short = Chunk.make(1, 2) * const long = Chunk.make("a", "b", "c", "d") - * const zipped = Chunk.zip(short, long) - * console.log(Chunk.toArray(zipped)) // [[1, "a"], [2, "b"]] + * Chunk.toArray(Chunk.zip(short, long)) // => [[1, "a"], [2, "b"]] * ``` * * @category zipping @@ -2387,23 +2332,20 @@ export const zip: { * * **Example** (Removing an element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make("a", "b", "c", "d") - * const result = Chunk.remove(chunk, 1) - * console.log(Chunk.toArray(result)) // ["a", "c", "d"] + * Chunk.toArray(Chunk.remove(chunk, 1)) // => ["a", "c", "d"] * * // Remove first element - * const removeFirst = Chunk.remove(chunk, 0) - * console.log(Chunk.toArray(removeFirst)) // ["b", "c", "d"] + * Chunk.toArray(Chunk.remove(chunk, 0)) // => ["b", "c", "d"] * * // Index out of bounds returns same chunk - * const outOfBounds = Chunk.remove(chunk, 10) - * console.log(Chunk.toArray(outOfBounds)) // ["a", "b", "c", "d"] + * Chunk.toArray(Chunk.remove(chunk, 10)) // => ["a", "b", "c", "d"] * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const remove: { @@ -2420,23 +2362,20 @@ export const remove: { * * **Example** (Modifying an element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * const result = Chunk.modify(chunk, 1, (n) => n * 10) - * console.log(result) // Option.some(Chunk.make(1, 20, 3, 4)) + * Chunk.modify(chunk, 1, (n) => n * 10) // => Option.some(Chunk.make(1, 20, 3, 4)) * * // Index out of bounds returns None - * const outOfBounds = chunk.pipe(Chunk.modify(10, (n) => n * 10)) - * console.log(outOfBounds) // Option.none() + * chunk.pipe(Chunk.modify(10, (n) => n * 10)) // => Option.none() * * // Negative index returns None - * const negative = chunk.pipe(Chunk.modify(-1, (n) => n * 10)) - * console.log(negative) // Option.none() + * chunk.pipe(Chunk.modify(-1, (n) => n * 10)) // => Option.none() * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const modify: { @@ -2454,23 +2393,20 @@ export const modify: { * * **Example** (Replacing an element) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make("a", "b", "c", "d") - * const result = Chunk.replace(chunk, 1, "X") - * console.log(result) // Option.some(Chunk.make("a", "X", "c", "d")) + * Chunk.replace(chunk, 1, "X") // => Option.some(Chunk.make("a", "X", "c", "d")) * * // Index out of bounds returns None - * const outOfBounds = chunk.pipe(Chunk.replace(10, "Y")) - * console.log(outOfBounds) // Option.none() + * chunk.pipe(Chunk.replace(10, "Y")) // => Option.none() * * // Negative index returns None - * const negative = chunk.pipe(Chunk.replace(-1, "Z")) - * console.log(negative) // Option.none() + * chunk.pipe(Chunk.replace(-1, "Z")) // => Option.none() * ``` * - * @category elements + * @category transforming * @since 2.0.0 */ export const replace: { @@ -2483,15 +2419,15 @@ export const replace: { * * **Details** * - * `n` is normalized to an integer greater than or equal to `1`. + * `n` is rounded down and normalized to an integer greater than or equal to + * `1`. `NaN` is treated as `1`. * * **Example** (Generating chunks from indices) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.makeBy(5, (i) => i * 2) - * console.log(Chunk.toArray(chunk)) // [0, 2, 4, 6, 8] + * Chunk.toArray(Chunk.makeBy(5, (i) => i * 2)) // => [0, 2, 4, 6, 8] * ``` * * @category constructors @@ -2513,11 +2449,10 @@ export const makeBy: { * * **Example** (Creating a range) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * - * const chunk = Chunk.range(1, 5) - * console.log(Chunk.toArray(chunk)) // [1, 2, 3, 4, 5] + * Chunk.toArray(Chunk.range(1, 5)) // => [1, 2, 3, 4, 5] * ``` * * @category constructors @@ -2535,24 +2470,23 @@ export const range = (start: number, end: number): NonEmptyChunk => * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * console.log(Chunk.contains(chunk, 3)) // true - * console.log(Chunk.contains(chunk, 6)) // false + * Chunk.contains(chunk, 3) // => true + * Chunk.contains(chunk, 6) // => false * * // Works with strings * const words = Chunk.make("apple", "banana", "cherry") - * console.log(Chunk.contains(words, "banana")) // true - * console.log(Chunk.contains(words, "grape")) // false + * Chunk.contains(words, "banana") // => true + * Chunk.contains(words, "grape") // => false * * // Empty chunk - * const empty = Chunk.empty() - * console.log(Chunk.contains(empty, 1)) // false + * Chunk.contains(Chunk.empty(), 1) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -2565,7 +2499,7 @@ export const contains: { * * **Example** (Checking membership with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make({ id: 1, name: "Alice" }, { id: 2, name: "Bob" }) @@ -2574,19 +2508,19 @@ export const contains: { * const containsById = Chunk.containsWith<{ id: number; name: string }>((a, b) => * a.id === b.id * ) - * console.log(containsById(chunk, { id: 1, name: "Different" })) // true - * console.log(containsById(chunk, { id: 3, name: "Charlie" })) // false + * containsById(chunk, { id: 1, name: "Different" }) // => true + * containsById(chunk, { id: 3, name: "Charlie" }) // => false * * // Case-insensitive string comparison * const words = Chunk.make("Apple", "Banana", "Cherry") * const containsCaseInsensitive = Chunk.containsWith((a, b) => * a.toLowerCase() === b.toLowerCase() * ) - * console.log(containsCaseInsensitive(words, "apple")) // true - * console.log(containsCaseInsensitive(words, "grape")) // false + * containsCaseInsensitive(words, "apple") // => true + * containsCaseInsensitive(words, "grape") // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith: ( @@ -2602,17 +2536,14 @@ export const containsWith: ( * * **Example** (Finding the first matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findFirst(chunk, (n) => n > 3) - * console.log(Option.isSome(result)) // true - * console.log(Option.getOrElse(result, () => 0)) // 4 + * Chunk.findFirst(chunk, (n) => n > 3) // => Option.some(4) * * // No match found - * const notFound = Chunk.findFirst(chunk, (n) => n > 10) - * console.log(Option.isNone(notFound)) // true + * Chunk.findFirst(chunk, (n) => n > 10) // => Option.none() * * // With type refinement * const mixed = Chunk.make(1, "hello", 2, "world", 3) @@ -2620,10 +2551,10 @@ export const containsWith: ( * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(Option.getOrElse(firstString, () => "")) // "hello" + * firstString // => Option.some("hello") * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -2638,23 +2569,20 @@ export const findFirst: { * * **Example** (Finding the first matching index) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findFirstIndex(chunk, (n) => n > 3) - * console.log(result) // Option.some(3) + * Chunk.findFirstIndex(chunk, (n) => n > 3) // => Option.some(3) * * // No match found - * const notFound = Chunk.findFirstIndex(chunk, (n) => n > 10) - * console.log(notFound) // Option.none() + * Chunk.findFirstIndex(chunk, (n) => n > 10) // => Option.none() * * // Find first even number - * const firstEven = Chunk.findFirstIndex(chunk, (n) => n % 2 === 0) - * console.log(firstEven) // Option.some(1) + * Chunk.findFirstIndex(chunk, (n) => n % 2 === 0) // => Option.some(1) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirstIndex: { @@ -2670,24 +2598,20 @@ export const findFirstIndex: { * * **Example** (Finding the last matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findLast(chunk, (n) => n < 4) - * console.log(Option.isSome(result)) // true - * console.log(Option.getOrElse(result, () => 0)) // 3 + * Chunk.findLast(chunk, (n) => n < 4) // => Option.some(3) * * // No match found - * const notFound = Chunk.findLast(chunk, (n) => n > 10) - * console.log(Option.isNone(notFound)) // true + * Chunk.findLast(chunk, (n) => n > 10) // => Option.none() * * // Find last even number - * const lastEven = Chunk.findLast(chunk, (n) => n % 2 === 0) - * console.log(Option.getOrElse(lastEven, () => 0)) // 4 + * Chunk.findLast(chunk, (n) => n % 2 === 0) // => Option.some(4) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findLast: { @@ -2702,23 +2626,20 @@ export const findLast: { * * **Example** (Finding the last matching index) * - * ```ts - * import { Chunk } from "effect" + * ```ts import.meta.vitest + * import { Chunk, Option } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const result = Chunk.findLastIndex(chunk, (n) => n < 4) - * console.log(result) // Option.some(2) + * Chunk.findLastIndex(chunk, (n) => n < 4) // => Option.some(2) * * // No match found - * const notFound = Chunk.findLastIndex(chunk, (n) => n > 10) - * console.log(notFound) // Option.none() + * Chunk.findLastIndex(chunk, (n) => n > 10) // => Option.none() * * // Find last even number index - * const lastEven = Chunk.findLastIndex(chunk, (n) => n % 2 === 0) - * console.log(lastEven) // Option.some(3) + * Chunk.findLastIndex(chunk, (n) => n % 2 === 0) // => Option.some(3) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findLastIndex: { @@ -2734,26 +2655,24 @@ export const findLastIndex: { * * **Example** (Checking every element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const allPositive = Chunk.make(1, 2, 3, 4, 5) - * console.log(Chunk.every(allPositive, (n) => n > 0)) // true - * console.log(Chunk.every(allPositive, (n) => n > 3)) // false + * Chunk.every(allPositive, (n) => n > 0) // => true + * Chunk.every(allPositive, (n) => n > 3) // => false * * // Empty chunk returns true - * const empty = Chunk.empty() - * console.log(Chunk.every(empty, (n) => n > 0)) // true + * Chunk.every(Chunk.empty(), (n) => n > 0) // => true * * // Type refinement * const mixed = Chunk.make(1, 2, 3) * if (Chunk.every(mixed, (x): x is number => typeof x === "number")) { * // mixed is now typed as Chunk - * console.log("All elements are numbers") * } * ``` * - * @category elements + * @category guards * @since 2.0.0 */ export const every: { @@ -2772,23 +2691,22 @@ export const every: { * * **Example** (Checking for some matching element) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * console.log(Chunk.some(chunk, (n) => n > 4)) // true - * console.log(Chunk.some(chunk, (n) => n > 10)) // false + * Chunk.some(chunk, (n) => n > 4) // => true + * Chunk.some(chunk, (n) => n > 10) // => false * * // Empty chunk returns false - * const empty = Chunk.empty() - * console.log(Chunk.some(empty, (n) => n > 0)) // false + * Chunk.some(Chunk.empty(), (n) => n > 0) // => false * * // Check for specific value * const words = Chunk.make("apple", "banana", "cherry") - * console.log(Chunk.some(words, (word) => word.includes("ban"))) // true + * Chunk.some(words, (word) => word.includes("ban")) // => true * ``` * - * @category elements + * @category guards * @since 2.0.0 */ export const some: { @@ -2804,24 +2722,20 @@ export const some: { * * **Example** (Joining chunks into a string) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make("apple", "banana", "cherry") - * const result = Chunk.join(chunk, ", ") - * console.log(result) // "apple, banana, cherry" + * Chunk.join(chunk, ", ") // => "apple, banana, cherry" * * // With different separator - * const withPipe = Chunk.join(chunk, " | ") - * console.log(withPipe) // "apple | banana | cherry" + * Chunk.join(chunk, " | ") // => "apple | banana | cherry" * * // Empty chunk - * const empty = Chunk.empty() - * console.log(Chunk.join(empty, ", ")) // "" + * Chunk.join(Chunk.empty(), ", ") // => "" * * // Single element - * const single = Chunk.make("hello") - * console.log(Chunk.join(single, ", ")) // "hello" + * Chunk.join(Chunk.make("hello"), ", ") // => "hello" * ``` * * @category folding @@ -2837,21 +2751,18 @@ export const join: { * * **Example** (Reducing from the left) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4, 5) - * const sum = Chunk.reduce(chunk, 0, (acc, n) => acc + n) - * console.log(sum) // 15 + * Chunk.reduce(chunk, 0, (acc, n) => acc + n) // => 15 * * // String concatenation with index * const words = Chunk.make("a", "b", "c") - * const result = Chunk.reduce(words, "", (acc, word, i) => acc + `${i}:${word} `) - * console.log(result) // "0:a 1:b 2:c " + * Chunk.reduce(words, "", (acc, word, i) => acc + `${i}:${word} `).trimEnd() // => "0:a 1:b 2:c" * * // Find maximum - * const max = Chunk.reduce(chunk, -Infinity, (acc, n) => Math.max(acc, n)) - * console.log(max) // 5 + * Chunk.reduce(chunk, -Infinity, (acc, n) => Math.max(acc, n)) // => 5 * ``` * * @category folding @@ -2867,25 +2778,22 @@ export const reduce: { * * **Example** (Reducing from the right) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk = Chunk.make(1, 2, 3, 4) - * const result = Chunk.reduceRight(chunk, 0, (acc, n) => acc + n) - * console.log(result) // 10 + * Chunk.reduceRight(chunk, 0, (acc, n) => acc + n) // => 10 * * // String building (right to left) * const words = Chunk.make("a", "b", "c") - * const reversed = Chunk.reduceRight( + * Chunk.reduceRight( * words, * "", * (acc, word, i) => acc + `${i}:${word} ` - * ) - * console.log(reversed) // "2:c 1:b 0:a " + * ).trim() // => "2:c 1:b 0:a" * * // Subtract from right to left - * const subtraction = Chunk.reduceRight(chunk, 0, (acc, n) => n - acc) - * console.log(subtraction) // -2 (4 - (3 - (2 - (1 - 0)))) + * Chunk.reduceRight(chunk, 0, (acc, n) => n - acc) // => -2 * ``` * * @category folding @@ -2902,7 +2810,7 @@ export const reduceRight: { * * **Example** (Computing difference with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make({ id: 1, name: "Alice" }, { id: 2, name: "Bob" }) @@ -2912,8 +2820,7 @@ export const reduceRight: { * const byId = Chunk.differenceWith<{ id: number; name: string }>((a, b) => * a.id === b.id * ) - * const result = byId(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [{ id: 2, name: "Bob" }] + * Chunk.toArray(byId(chunk1, chunk2)) // => [{ id: 2, name: "Bob" }] * * // String comparison case-insensitive * const words1 = Chunk.make("Apple", "Banana", "Cherry") @@ -2921,8 +2828,7 @@ export const reduceRight: { * const caseInsensitive = Chunk.differenceWith((a, b) => * a.toLowerCase() === b.toLowerCase() * ) - * const wordDiff = caseInsensitive(words1, words2) - * console.log(Chunk.toArray(wordDiff)) // ["Banana", "Cherry"] + * Chunk.toArray(caseInsensitive(words1, words2)) // => ["Banana", "Cherry"] * ``` * * @category filtering @@ -2944,24 +2850,20 @@ export const differenceWith = (isEquivalent: (self: A, that: A) => boolean): * * **Example** (Computing chunk difference) * - * ```ts + * ```ts import.meta.vitest * import { Chunk } from "effect" * * const chunk1 = Chunk.make(1, 2, 3, 4, 5) * const chunk2 = Chunk.make(3, 4, 6, 7) - * const result = Chunk.difference(chunk1, chunk2) - * console.log(Chunk.toArray(result)) // [1, 2, 5] + * Chunk.toArray(Chunk.difference(chunk1, chunk2)) // => [1, 2, 5] * * // String difference * const words1 = Chunk.make("apple", "banana", "cherry") * const words2 = Chunk.make("banana", "grape") - * const wordDiff = Chunk.difference(words1, words2) - * console.log(Chunk.toArray(wordDiff)) // ["apple", "cherry"] + * Chunk.toArray(Chunk.difference(words1, words2)) // => ["apple", "cherry"] * * // Empty second chunk returns original - * const empty = Chunk.empty() - * const unchanged = Chunk.difference(chunk1, empty) - * console.log(Chunk.toArray(unchanged)) // [1, 2, 3, 4, 5] + * Chunk.toArray(Chunk.difference(chunk1, Chunk.empty())) // => [1, 2, 3, 4, 5] * ``` * * @category filtering diff --git a/packages/effect/src/Clock.ts b/packages/effect/src/Clock.ts index 03a8b210e..5434197be 100644 --- a/packages/effect/src/Clock.ts +++ b/packages/effect/src/Clock.ts @@ -1,8 +1,9 @@ /** * Service and helpers for reading time and sleeping inside Effect programs. - * The active `Clock` provides current time in milliseconds or nanoseconds and a - * `sleep` operation for delaying work. Because time is accessed through a - * service, tests can replace the clock with a controlled implementation. + * The active `Clock` provides Unix time, monotonic time for measuring elapsed + * durations, and a `sleep` operation for delaying work. Because time is + * accessed through a service, tests can replace the clock with a controlled + * implementation. * * @since 2.0.0 */ @@ -22,56 +23,121 @@ import * as effect from "./internal/effect.ts" * * **Example** (Reading current time) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * * const clockOperations = Effect.gen(function*() { * const currentTime = yield* Clock.currentTimeMillis * const currentTimeNanos = yield* Clock.currentTimeNanos - * - * console.log(`Current time (ms): ${currentTime}`) - * console.log(`Current time (ns): ${currentTimeNanos}`) + * return [currentTime, currentTimeNanos] as const * }) + * + * await Effect.runPromise(Effect.provideService(clockOperations, Clock.Clock, testClock)) // => [1_000, 1_000_000_000n] * ``` * - * @category models + * @category services * @since 2.0.0 */ export interface Clock { /** - * Returns the current time in milliseconds unsafely. + * Returns the current Unix time in milliseconds unsafely. * * **When to use** * - * Use to read millisecond time synchronously when you already have a `Clock` - * service and can accept non-effectful access. + * Use to read a wall-clock timestamp synchronously when you already have a + * `Clock` service and can accept non-effectful access. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ currentTimeMillisUnsafe(): number /** - * Returns the current time in milliseconds. + * Returns the current Unix time in milliseconds. * * **When to use** * - * Use to read millisecond time through this `Clock` service in `Effect`. + * Use to read a wall-clock timestamp through this `Clock` service in + * `Effect`. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ readonly currentTimeMillis: Effect /** - * Returns the current time in nanoseconds unsafely. + * Returns the current Unix time in nanoseconds unsafely. * * **When to use** * - * Use to read nanosecond time synchronously when you already have a `Clock` - * service and can accept non-effectful access. + * Use to read a wall-clock timestamp synchronously when you already have a + * `Clock` service and can accept non-effectful access. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ currentTimeNanosUnsafe(): bigint /** - * Returns the current time in nanoseconds. + * Returns the current Unix time in nanoseconds. * * **When to use** * - * Use to read nanosecond time through this `Clock` service in `Effect`. + * Use to read a wall-clock timestamp through this `Clock` service in + * `Effect`. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. */ readonly currentTimeNanos: Effect + /** + * Returns the current monotonic time in nanoseconds unsafely. + * + * **When to use** + * + * Use to measure elapsed time synchronously when you already have a `Clock` + * service and can accept non-effectful access. + * + * **Gotchas** + * + * The value has an arbitrary origin and is unsuitable for serialization. Use + * it only to subtract readings produced by the same clock. Whether it + * advances while the host is suspended depends on the runtime. + * + * @since 4.0.0 + */ + monotonicTimeNanosUnsafe(): bigint + /** + * Returns the current monotonic time in nanoseconds. + * + * **When to use** + * + * Use to measure elapsed time through this `Clock` service in `Effect`. + * + * **Gotchas** + * + * The value has an arbitrary origin and is unsuitable for serialization. Use + * it only to subtract readings produced by the same clock. Whether it + * advances while the host is suspended depends on the runtime. + * + * @since 4.0.0 + */ + readonly monotonicTimeNanos: Effect /** * Asynchronously sleeps for the specified duration. * @@ -92,20 +158,32 @@ export interface Clock { * * **Example** (Accessing the Clock service) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * * const program = Effect.gen(function*() { * const clock = yield* Clock.Clock * return clock.currentTimeMillisUnsafe() * }) + * + * await Effect.runPromise(Effect.provideService(program, Clock.Clock, testClock)) // => 1_000 * ``` * * @see {@link clockWith} for using the current Clock service inside an effect * @see {@link currentTimeMillis} for reading the current time in milliseconds * @see {@link currentTimeNanos} for reading the current time in nanoseconds * - * @category references + * @category services * @since 2.0.0 */ export const Clock: Context.Reference = effect.ClockRef @@ -120,75 +198,129 @@ export const Clock: Context.Reference = effect.ClockRef * * **Example** (Accessing the current Clock service) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * - * const program = Clock.clockWith((clock) => - * Effect.sync(() => { - * const currentTime = clock.currentTimeMillisUnsafe() - * console.log(`Current time: ${currentTime}`) - * return currentTime - * }) - * ) + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * + * const program = Clock.clockWith((clock) => Effect.sync(() => clock.currentTimeMillisUnsafe())) + * + * await Effect.runPromise(Effect.provideService(program, Clock.Clock, testClock)) // => 1_000 * ``` * * @see {@link Clock} for the service reference * @see {@link currentTimeMillis} for convenience accessor that returns milliseconds * @see {@link currentTimeNanos} for convenience accessor that returns nanoseconds - * @category constructors + * @category accessors * @since 2.0.0 */ export const clockWith: (f: (clock: Clock) => Effect) => Effect = effect.clockWith /** - * Returns an Effect that succeeds with the current time in milliseconds. + * Returns an Effect that succeeds with the current Unix time in milliseconds. * * **When to use** * - * Use to read wall-clock time from the active Clock service with millisecond - * precision. + * Use to create wall-clock timestamps from the active `Clock` service with + * millisecond precision. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. * * **Example** (Reading milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const currentTime = yield* Clock.currentTimeMillis - * console.log(`Current time: ${currentTime}ms`) - * return currentTime - * }) + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * + * await Effect.runPromise(Effect.provideService(Clock.currentTimeMillis, Clock.Clock, testClock)) // => 1_000 * ``` * * @see {@link currentTimeNanos} for nanosecond precision + * @see {@link monotonicTimeNanos} for measuring elapsed time * @see {@link clockWith} for accessing the full Clock service * - * @category constructors + * @category accessors * @since 2.0.0 */ export const currentTimeMillis: Effect = effect.currentTimeMillis /** - * Returns an Effect that succeeds with the current time in nanoseconds. + * Returns an Effect that succeeds with the current Unix time in nanoseconds. * * **When to use** * - * Use to read wall-clock time from the active `Clock` service with nanosecond - * precision. + * Use to create wall-clock timestamps from the active `Clock` service with + * nanosecond precision. + * + * **Gotchas** + * + * The value can move backward or forward when the system wall clock is + * corrected, so it is not suitable for measuring elapsed time. + * The live clock allows up to one second of drift from `Date.now()` before + * re-anchoring. * * **Example** (Reading nanoseconds) * - * ```ts + * ```ts import.meta.vitest * import { Clock, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const currentTime = yield* Clock.currentTimeNanos - * console.log(`Current time: ${currentTime}ns`) - * return currentTime - * }) + * const testClock: Clock.Clock = { + * currentTimeMillisUnsafe: () => 1_000, + * currentTimeMillis: Effect.succeed(1_000), + * monotonicTimeNanosUnsafe: () => 1_000_000_000n, + * monotonicTimeNanos: Effect.succeed(1_000_000_000n), + * currentTimeNanosUnsafe: () => 1_000_000_000n, + * currentTimeNanos: Effect.succeed(1_000_000_000n), + * sleep: () => Effect.void + * } + * + * await Effect.runPromise(Effect.provideService(Clock.currentTimeNanos, Clock.Clock, testClock)) // => 1_000_000_000n * ``` * - * @category constructors + * @see {@link monotonicTimeNanos} for measuring elapsed time + * + * @category accessors * @since 2.0.0 */ export const currentTimeNanos: Effect = effect.currentTimeNanos + +/** + * Returns an Effect that succeeds with the current monotonic time in + * nanoseconds. + * + * **When to use** + * + * Use to measure elapsed time by subtracting two readings. + * + * **Gotchas** + * + * The value has an arbitrary origin and is unsuitable for serialization. Use + * it only to subtract readings produced by the same clock. Whether it advances + * while the host is suspended depends on the runtime. + * + * @see {@link currentTimeNanos} for Unix wall-clock timestamps + * + * @category accessors + * @since 4.0.0 + */ +export const monotonicTimeNanos: Effect = effect.monotonicTimeNanos diff --git a/packages/effect/src/Combiner.ts b/packages/effect/src/Combiner.ts index 1e9ac7c78..75ab2d492 100644 --- a/packages/effect/src/Combiner.ts +++ b/packages/effect/src/Combiner.ts @@ -26,13 +26,12 @@ import type * as Order from "./Order.ts" * * **Example** (Combining numbers with addition) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Sum = Combiner.make((self, that) => self + that) * - * console.log(Sum.combine(3, 4)) - * // Output: 7 + * Sum.combine(3, 4) // => 7 * ``` * * @see {@link make} – create a `Combiner` from a function @@ -65,13 +64,12 @@ export interface Combiner { * * **Example** (Multiplying numbers) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Product = Combiner.make((self, that) => self * that) * - * console.log(Product.combine(3, 5)) - * // Output: 15 + * Product.combine(3, 5) // => 15 * ``` * * @see {@link Combiner} – the interface this creates @@ -97,13 +95,12 @@ export function make(combine: (self: A, that: A) => A): Combiner { * * **Example** (Reversing string concatenation) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, String } from "effect" * * const Prepend = Combiner.flip(String.ReducerConcat) * - * console.log(Prepend.combine("a", "b")) - * // Output: "ba" + * Prepend.combine("a", "b") // => "ba" * ``` * * @see {@link make} @@ -130,16 +127,13 @@ export function flip(combiner: Combiner): Combiner { * * **Example** (Selecting the minimum of two numbers) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, Number } from "effect" * * const Min = Combiner.min(Number.Order) * - * console.log(Min.combine(3, 1)) - * // Output: 1 - * - * console.log(Min.combine(1, 3)) - * // Output: 1 + * Min.combine(3, 1) // => 1 + * Min.combine(1, 3) // => 1 * ``` * * @see {@link max} @@ -166,16 +160,13 @@ export function min(order: Order.Order): Combiner { * * **Example** (Selecting the maximum of two numbers) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, Number } from "effect" * * const Max = Combiner.max(Number.Order) * - * console.log(Max.combine(3, 1)) - * // Output: 3 - * - * console.log(Max.combine(1, 3)) - * // Output: 3 + * Max.combine(3, 1) // => 3 + * Max.combine(1, 3) // => 3 * ``` * * @see {@link min} @@ -199,13 +190,12 @@ export function max(order: Order.Order): Combiner { * * **Example** (Keeping the first value) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const First = Combiner.first() * - * console.log(First.combine(1, 2)) - * // Output: 1 + * First.combine(1, 2) // => 1 * ``` * * @see {@link last} @@ -229,13 +219,12 @@ export function first(): Combiner { * * **Example** (Keeping the last value) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Last = Combiner.last() * - * console.log(Last.combine(1, 2)) - * // Output: 2 + * Last.combine(1, 2) // => 2 * ``` * * @see {@link first} @@ -261,13 +250,12 @@ export function last(): Combiner { * * **Example** (Always returning zero) * - * ```ts + * ```ts import.meta.vitest * import { Combiner } from "effect" * * const Zero = Combiner.constant(0) * - * console.log(Zero.combine(42, 99)) - * // Output: 0 + * Zero.combine(42, 99) // => 0 * ``` * * @see {@link first} @@ -297,13 +285,12 @@ export function constant(a: A): Combiner { * * **Example** (Joining strings with a separator) * - * ```ts + * ```ts import.meta.vitest * import { Combiner, String } from "effect" * * const commaSep = Combiner.intercalate(",")(String.ReducerConcat) * - * console.log(commaSep.combine("a", "b")) - * // Output: "a,b" + * commaSep.combine("a", "b") // => "a,b" * ``` * * @see {@link make} diff --git a/packages/effect/src/Config.ts b/packages/effect/src/Config.ts index e22371cfb..7f6cde183 100644 --- a/packages/effect/src/Config.ts +++ b/packages/effect/src/Config.ts @@ -11,11 +11,13 @@ import type { Path, SourceError } from "./ConfigProvider.ts" import * as ConfigProvider from "./ConfigProvider.ts" import * as Effect from "./Effect.ts" import * as Effectable from "./Effectable.ts" -import { dual } from "./Function.ts" +import { dual, memoize } from "./Function.ts" +import * as InternalRecord from "./internal/record.ts" import * as LogLevel_ from "./LogLevel.ts" import * as Option from "./Option.ts" import * as Predicate from "./Predicate.ts" import * as Rec from "./Record.ts" +import * as Result from "./Result.ts" import * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" import * as SchemaGetter from "./SchemaGetter.ts" @@ -35,11 +37,11 @@ const TypeId = "~effect/Config" * * **Example** (Checking Config values) * - * ```ts + * ```ts import.meta.vitest * import { Config } from "effect" * - * console.log(Config.isConfig(Config.string("HOST"))) // true - * console.log(Config.isConfig("not a config")) // false + * Config.isConfig(Config.String("HOST")) // => true + * Config.isConfig("not a config") // => false * ``` * * @category guards @@ -62,7 +64,7 @@ export const isConfig = (u: unknown): u is Config => Predicate.hasPrope * (wrong type, out of range, missing key, etc.). * * @see {@link orElse} – recover from a ConfigError - * @see {@link withDefault} – provide a fallback for missing-data errors + * @see {@link withDefault} – provide a fallback when relevant input is absent * * @category errors * @since 4.0.0 @@ -93,9 +95,7 @@ export class ConfigError { * **Details** * * Key members: - * - `parse(provider, pathPrefix?)` – runs the config against a specific provider. - * The optional path prefix is the logical scope accumulated from outer - * `Config.nested` calls. + * - `parse(provider)` – runs the config against a specific provider. * - Yieldable – can be yielded inside `Effect.gen`, which automatically * resolves the current `ConfigProvider` from the context. * - Pipeable – supports `.pipe(Config.map(...))` etc. @@ -107,10 +107,39 @@ export class ConfigError { */ export interface Config extends Effect.Effect { readonly [TypeId]: typeof TypeId - readonly parse: ( - provider: ConfigProvider.ConfigProvider, - pathPrefix?: Path - ) => Effect.Effect + readonly parse: (provider: ConfigProvider.ConfigProvider) => Effect.Effect +} + +// Config composition needs to distinguish an absent recipe from a hard failure +// before the public Effect error channel is finalized. `hasInput` records +// provider evidence separately from the value, because successful values such +// as `undefined` and values supplied by defaults are not evidence of input. +// Hard failures carry the same evidence so recovery cannot erase it. +interface Resolved { + readonly _tag: "Resolved" + readonly value: T + readonly hasInput: boolean +} + +interface Absent { + readonly _tag: "Absent" + readonly error: ConfigError +} + +type Resolution = Resolved | Absent + +interface EvaluationFailure { + readonly error: ConfigError + readonly hasInput: boolean +} + +type Evaluator = ( + provider: ConfigProvider.ConfigProvider, + pathPrefix: Path +) => Effect.Effect, EvaluationFailure> + +interface ConfigImpl extends Config { + readonly evaluator: Evaluator } const Proto = { @@ -129,13 +158,71 @@ const Proto = { } function make( - parse: (provider: ConfigProvider.ConfigProvider, pathPrefix: Path) => Effect.Effect + evaluator: Evaluator ): Config { const self = Object.create(Proto) - self.parse = (provider: ConfigProvider.ConfigProvider, pathPrefix: Path = []) => parse(provider, pathPrefix) + self.evaluator = evaluator + self.parse = (provider: ConfigProvider.ConfigProvider) => + evaluator(provider, []).pipe( + Effect.mapErrorEager((failure) => failure.error), + Effect.flatMapEager((resolution) => + resolution._tag === "Resolved" ? Effect.succeed(resolution.value) : Effect.fail(resolution.error) + ) + ) return self } +const evaluateAt = ( + self: Config, + provider: ConfigProvider.ConfigProvider, + pathPrefix: Path +): Effect.Effect, EvaluationFailure> => (self as ConfigImpl).evaluator(provider, pathPrefix) + +const resolved = (value: T, hasInput: boolean): Resolution => ({ + _tag: "Resolved", + value, + hasInput +}) + +const absent = (error: ConfigError): Absent => ({ + _tag: "Absent", + error +}) + +const evaluationFailure = (error: ConfigError, hasInput: boolean): EvaluationFailure => ({ + error, + hasInput +}) + +const isSourceError = (u: unknown): u is ConfigProvider.SourceError => Predicate.isTagged(u, "SourceError") + +const catchSourceError = ( + self: Effect.Effect, + hasInput: boolean +): Effect.Effect => + self.pipe( + Effect.catchDefect((defect) => + isSourceError(defect) + ? Effect.fail(evaluationFailure(new ConfigError(defect), hasInput)) + : Effect.die(defect) + ) + ) + +const preserveInputEvidence = ( + self: Effect.Effect, EvaluationFailure>, + hasInput: boolean +): Effect.Effect, EvaluationFailure> => { + if (!hasInput) return self + return self.pipe( + Effect.mapErrorEager((failure) => evaluationFailure(failure.error, true)), + Effect.flatMapEager((resolution) => + resolution._tag === "Resolved" + ? Effect.succeed(resolved(resolution.value, true)) + : Effect.fail(evaluationFailure(resolution.error, true)) + ) + ) +} + /** * Transforms the parsed value of a config with a pure function. * @@ -146,18 +233,18 @@ function make( * * **Example** (Uppercasing a string config) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const upper = Config.string("name").pipe( + * const upper = Config.String("name").pipe( * Config.map((s) => s.toUpperCase()) * ) * * const provider = ConfigProvider.fromUnknown({ name: "alice" }) - * // Effect.runSync(upper.parse(provider)) // "ALICE" + * Effect.runSync(upper.parse(provider)) // => "ALICE" * ``` * - * @see {@link mapOrFail} – when the transformation can fail + * @see {@link mapEffect} – when the transformation can fail * * @category mapping * @since 2.0.0 @@ -166,7 +253,12 @@ export const map: { (f: (a: A) => B): (self: Config) => Config (self: Config, f: (a: A) => B): Config } = dual(2, (self: Config, f: (a: A) => B): Config => { - return make((provider, pathPrefix) => Effect.map(self.parse(provider, pathPrefix), f)) + return make((provider, pathPrefix) => + Effect.map(evaluateAt(self, provider, pathPrefix), (resolution) => + resolution._tag === "Resolved" + ? resolved(f(resolution.value), resolution.hasInput) + : resolution) + ) }) /** @@ -179,12 +271,14 @@ export const map: { * * **Example** (Wrapping a value in an effectful transformation) * - * ```ts - * import { Config, Effect } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * - * const trimmed = Config.string("name").pipe( - * Config.mapOrFail((s) => Effect.succeed(s.trim())) + * const trimmed = Config.String("name").pipe( + * Config.mapEffect((s) => Effect.succeed(s.trim())) * ) + * const provider = ConfigProvider.fromUnknown({ name: " Alice " }) + * Effect.runSync(trimmed.parse(provider)) // => "Alice" * ``` * * @see {@link map} – when the transformation cannot fail @@ -192,11 +286,19 @@ export const map: { * @category mapping * @since 2.0.0 */ -export const mapOrFail: { +export const mapEffect: { (f: (a: A) => Effect.Effect): (self: Config) => Config (self: Config, f: (a: A) => Effect.Effect): Config } = dual(2, (self: Config, f: (a: A) => Effect.Effect): Config => { - return make((provider, pathPrefix) => Effect.flatMap(self.parse(provider, pathPrefix), f)) + return make((provider, pathPrefix) => + Effect.flatMap(evaluateAt(self, provider, pathPrefix), (resolution) => + resolution._tag === "Resolved" + ? f(resolution.value).pipe( + Effect.mapEager((value) => resolved(value, resolution.hasInput)), + Effect.mapErrorEager((error) => evaluationFailure(error, resolution.hasInput)) + ) + : Effect.succeed(resolution)) + ) }) /** @@ -209,21 +311,30 @@ export const mapOrFail: { * * **Details** * - * Unlike {@link withDefault}, this catches **all** `ConfigError`s (not just - * missing data). The fallback function receives the error and returns a new + * Unlike {@link withDefault}, this handles both semantic absence and **all** + * `ConfigError`s. The fallback function receives the error and returns a new * `Config`. * + * **Gotchas** + * + * Recovery preserves whether the primary config read provider input. When the + * recovered config is composed with {@link all}, invalid input in the primary + * branch still makes the enclosing group partially supplied, so an outer + * {@link withDefault} or {@link option} does not replace the whole group. + * * **Example** (Falling back to a literal) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * - * const hostConfig = Config.string("HOST").pipe( + * const hostConfig = Config.String("HOST").pipe( * Config.orElse(() => Config.succeed("localhost")) * ) + * const provider = ConfigProvider.fromUnknown({}) + * Effect.runSync(hostConfig.parse(provider)) // => "localhost" * ``` * - * @see {@link withDefault} – fallback only on missing data + * @see {@link withDefault} – fallback only on semantic absence * * @category combinators * @since 2.0.0 @@ -232,8 +343,18 @@ export const orElse: { (that: (error: ConfigError) => Config): (self: Config) => Config (self: Config, that: (error: ConfigError) => Config): Config } = dual(2, (self: Config, that: (error: ConfigError) => Config): Config => { - return make((provider, pathPrefix) => - Effect.catch(self.parse(provider, pathPrefix), (error) => that(error).parse(provider, pathPrefix)) + return make((provider, pathPrefix) => + Effect.matchEffect(evaluateAt(self, provider, pathPrefix), { + onFailure: (failure) => + preserveInputEvidence( + evaluateAt(that(failure.error), provider, pathPrefix), + failure.hasInput + ), + onSuccess: (resolution): Effect.Effect, EvaluationFailure> => + resolution._tag === "Absent" + ? evaluateAt(that(resolution.error), provider, pathPrefix) + : Effect.succeed(resolution) + }) ) }) @@ -249,19 +370,28 @@ export const orElse: { * Accepts a tuple (preserves positions), an iterable, or a record of configs. * Returns a config whose parsed value mirrors the input shape. * + * A combined config is absent when at least one child cannot resolve and none + * of the other children read provider input. This lets {@link withDefault} and + * {@link option} handle a wholly absent group. Once any child reads input, a + * missing sibling makes the group incomplete and parsing fails. Values supplied + * by child defaults do not count as provider input. + * + * Unlike a `Schema.Struct` passed to {@link schema}, `all` only considers input + * read by its children. An explicitly present but empty parent container does + * not by itself make the group present. + * * **Example** (Combining configs as a struct) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ - * host: Config.string("host"), - * port: Config.number("port") + * host: Config.String("host"), + * port: Config.Number("port") * }) * * const provider = ConfigProvider.fromUnknown({ host: "localhost", port: 5432 }) - * // Effect.runSync(dbConfig.parse(provider)) - * // { host: "localhost", port: 5432 } + * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * @category combinators @@ -279,53 +409,92 @@ export function all> | Record { - const configs: Array> | Record> = Array.isArray(arg) + const configs: Array> | Record> = globalThis.Array.isArray(arg) ? arg : Symbol.iterator in arg ? [...arg as any] : arg - if (Array.isArray(configs)) { + if (globalThis.Array.isArray(configs)) { return make((provider, pathPrefix) => - Effect.all(configs.map((config) => config.parse(provider, pathPrefix))) + Effect.flatMapEager( + Effect.all(configs.map((config) => Effect.result(evaluateAt(config, provider, pathPrefix)))), + resolveArray + ) ) as any } else { return make((provider, pathPrefix) => - Effect.all(Rec.map(configs, (config) => config.parse(provider, pathPrefix))) + Effect.flatMapEager( + Effect.all(Rec.map(configs, (config) => Effect.result(evaluateAt(config, provider, pathPrefix)))), + resolveRecord + ) ) as any } } -function isMissingDataOnly(issue: SchemaIssue.Issue): boolean { - switch (issue._tag) { - case "MissingKey": - return true - case "InvalidType": - case "InvalidValue": - return Option.isNone(issue.actual) || (Option.isSome(issue.actual) && issue.actual.value === undefined) - case "OneOf": - return issue.actual === undefined - case "Encoding": - return Option.isNone(issue.actual) || (Option.isSome(issue.actual) && issue.actual.value === undefined) - ? true - : isMissingDataOnly(issue.issue) - case "Pointer": - return isMissingDataOnly(issue.issue) - case "Filter": - case "UnexpectedKey": - case "Forbidden": - return false - case "Composite": - return issue.issues.every(isMissingDataOnly) - case "AnyOf": - if (issue.issues.length === 0) { - return issue.actual === undefined - } - return issue.issues.every(isMissingDataOnly) +const resolveArray = ( + results: ReadonlyArray, EvaluationFailure>> +): Effect.Effect>, EvaluationFailure> => { + const values: Array = [] + let firstFailure: EvaluationFailure | undefined + let firstAbsent: Absent | undefined + let hasInput = false + for (const result of results) { + if (Result.isFailure(result)) { + firstFailure ??= result.failure + hasInput = hasInput || result.failure.hasInput + continue + } + const resolution = result.success + if (resolution._tag === "Absent") { + firstAbsent ??= resolution + } else { + values.push(resolution.value) + hasInput = hasInput || resolution.hasInput + } + } + if (firstFailure !== undefined) { + return Effect.fail(evaluationFailure(firstFailure.error, hasInput)) } + if (firstAbsent !== undefined) { + return hasInput ? Effect.fail(evaluationFailure(firstAbsent.error, true)) : Effect.succeed(firstAbsent) + } + return Effect.succeed(resolved(values, hasInput)) +} + +const resolveRecord = ( + results: Record, EvaluationFailure>> +): Effect.Effect>, EvaluationFailure> => { + const values: Record = {} + let firstFailure: EvaluationFailure | undefined + let firstAbsent: Absent | undefined + let hasInput = false + for (const key in results) { + const result = results[key] + if (Result.isFailure(result)) { + firstFailure ??= result.failure + hasInput = hasInput || result.failure.hasInput + continue + } + const resolution = result.success + if (resolution._tag === "Absent") { + firstAbsent ??= resolution + } else { + InternalRecord.assignProperty(values, key, resolution.value) + hasInput = hasInput || resolution.hasInput + } + } + if (firstFailure !== undefined) { + return Effect.fail(evaluationFailure(firstFailure.error, hasInput)) + } + if (firstAbsent !== undefined) { + return hasInput ? Effect.fail(evaluationFailure(firstAbsent.error, true)) : Effect.succeed(firstAbsent) + } + return Effect.succeed(resolved(values, hasInput)) } /** - * Provides a fallback value when the config fails due to missing data. + * Provides a fallback value when the config cannot resolve because none of its + * relevant input is present. * * **When to use** * @@ -333,23 +502,25 @@ function isMissingDataOnly(issue: SchemaIssue.Issue): boolean { * * **Gotchas** * - * Only applies when the error is a `SchemaError` caused exclusively by - * missing data (missing keys, undefined values). Validation errors (wrong - * type, out of range) still propagate. + * Validation errors and partially supplied groups still propagate. A schema + * that successfully decodes absent input also keeps its decoded value instead + * of using the default. Schema configs first represent a missing or + * incompatible provider shape as `undefined`; the default is used only when + * the schema rejects that value and no relevant input was found. * * **Example** (Defaulting a missing port) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const port = Config.number("port").pipe(Config.withDefault(3000)) + * const port = Config.Number("port").pipe(Config.withDefault(3000)) * * const provider = ConfigProvider.fromUnknown({}) - * // Effect.runSync(port.parse(provider)) // 3000 + * Effect.runSync(port.parse(provider)) // => 3000 * ``` * * @see {@link option} – returns `Option` instead of a default value - * @see {@link orElse} – catches all errors, not just missing data + * @see {@link orElse} – catches all errors, not just absent input * * @category combinators * @since 2.0.0 @@ -358,20 +529,17 @@ export const withDefault: { (defaultValue: A2): (self: Config) => Config (self: Config, defaultValue: A2): Config } = dual(2, (self: Config, defaultValue: A2): Config => { - return orElse(self, (err) => { - if (Schema.isSchemaError(err.cause)) { - const issue = err.cause.issue - if (isMissingDataOnly(issue)) { - return succeed(defaultValue) - } - } - return fail(err.cause) - }) + return make((provider, pathPrefix) => + Effect.mapEager( + evaluateAt(self, provider, pathPrefix), + (resolution) => resolution._tag === "Absent" ? resolved(defaultValue, false) : resolution + ) + ) }) /** - * Makes a config optional: returns `Some(value)` on success and `None` when - * data is missing. + * Makes a config optional: returns `Some(value)` on success and `None` when the + * config cannot resolve because none of its relevant input is present. * * **When to use** * @@ -379,18 +547,21 @@ export const withDefault: { * * **Gotchas** * - * Like {@link withDefault}, only missing-data errors produce `None`. - * Validation errors still propagate. + * Validation errors and partially supplied groups still propagate. Successful + * values are always wrapped in `Some`, including `undefined` when the schema + * explicitly accepts it. Schema configs first represent a missing or + * incompatible provider shape as `undefined`; `None` is returned only when the + * schema rejects that value and no relevant input was found. * * **Example** (Reading optional config) * - * ```ts - * import { Config, ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Option } from "effect" * - * const maybePort = Config.option(Config.number("port")) + * const maybePort = Config.option(Config.Number("port")) * * const provider = ConfigProvider.fromUnknown({}) - * // Effect.runSync(maybePort.parse(provider)) // { _tag: "None" } + * Effect.runSync(maybePort.parse(provider)) // => Option.none() * ``` * * @see {@link withDefault} – provide a concrete fallback value instead @@ -432,7 +603,7 @@ export type Success = [T] extends [Config] ? A : never * * @see {@link unwrap} – construct a `Config` from a `Wrap` * - * @category Wrap + * @category utility types * @since 2.0.0 */ export type Wrap = [NonNullable] extends [infer T] ? [IsPlainObject] extends [true] ? @@ -460,8 +631,8 @@ type IsPlainObject = [A] extends [Record] * * **Example** (Unwrapping a record of configs) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * * interface Options { * key: string @@ -469,128 +640,158 @@ type IsPlainObject = [A] extends [Record] * * const makeConfig = (config: Config.Wrap): Config.Config => * Config.unwrap(config) + * + * const config = makeConfig({ key: Config.String("key") }) + * const provider = ConfigProvider.fromUnknown({ key: "value" }) + * Effect.runSync(config.parse(provider)) // => { key: "value" } * ``` * * @see {@link Wrap} – the utility type accepted by this function * - * @category Wrap + * @category converting * @since 2.0.0 */ export const unwrap = (wrapped: Wrap): Config => { if (isConfig(wrapped)) return wrapped - return make((provider, pathPrefix) => { - const entries = Object.entries(wrapped) - const configs = entries.map(([key, config]) => - unwrap(config as any).parse(provider, pathPrefix).pipe(Effect.map((value) => [key, value] as const)) - ) - return Effect.all(configs).pipe(Effect.map(Object.fromEntries)) - }) + return all(Rec.map(wrapped as Record>, (config) => unwrap(config))) as Config } // ----------------------------------------------------------------------------- // schema // ----------------------------------------------------------------------------- -const dump: ( +interface ConfigCursor { + readonly provider: ConfigProvider.ConfigProvider + readonly path: Path + readonly node: ConfigProvider.Node | undefined + readonly toString: () => string +} + +const cursorToString = (): string => "" + +const loadCursor: ( provider: ConfigProvider.ConfigProvider, path: Path -) => Effect.Effect = Effect.fnUntraced(function*( - provider, - path -) { - const stat = yield* provider.load(path) - if (stat === undefined) return undefined - switch (stat._tag) { - case "Value": - return stat.value - case "Record": { - if (stat.value !== undefined) return stat.value - const out: Record = {} - for (const key of stat.keys) { - const child = yield* dump(provider, [...path, key]) - if (child !== undefined) out[key] = child - } - return out - } - case "Array": { - if (stat.value !== undefined) return stat.value - const out: Array = [] - for (let i = 0; i < stat.length; i++) { - out.push(yield* dump(provider, [...path, i])) - } - return out - } +) => Effect.Effect = (provider, path) => + provider.load(path).pipe( + Effect.orDie, + Effect.mapEager((node) => ({ provider, path, node, toString: cursorToString })) + ) + +const loadChildCursor = (cursor: ConfigCursor, segment: string | number): Effect.Effect => + loadCursor(cursor.provider, [...cursor.path, segment]) + +const getScalar = (node: ConfigProvider.Node | undefined): string | undefined => node?.value + +const decodeFromCursor = ( + ast: SchemaAST.AST, + decode: (cursor: ConfigCursor) => Effect.Effect +): SchemaAST.AST => + SchemaAST.decodeTo( + SchemaAST.unknown, + ast, + new SchemaTransformation.Transformation( + SchemaGetter.transformOrFail((input: unknown) => decode(input as ConfigCursor)), + SchemaGetter.passthrough() + ) + ) + +const isScalarInput = (ast: SchemaAST.AST): boolean => { + switch (ast._tag) { + case "Union": + return ast.types.every(isScalarInput) + case "Objects": + case "Arrays": + case "Suspend": + return false + default: + return true } -}) +} -const recur: ( +const hasProviderInput = ( ast: SchemaAST.AST, - provider: ConfigProvider.ConfigProvider, - path: Path -) => Effect.Effect = Effect.fnUntraced( - function*(ast, provider, path) { + node: ConfigProvider.Node | undefined +): boolean => { + switch (ast._tag) { + case "Objects": + return node?._tag === "Record" + case "Arrays": + return node?._tag === "Array" + case "Union": + return ast.types.some((ast) => hasProviderInput(ast, node)) + case "Suspend": + return hasProviderInput(ast.thunk(), node) + default: + return getScalar(node) !== undefined + } +} + +const toConfigCursorAST = memoize((root: SchemaAST.AST): SchemaAST.AST => { + const seen = new WeakSet() + const recur = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => { + seen.add(ast) switch (ast._tag) { case "Objects": { - const stat = yield* provider.load(path) - if (stat === undefined && path.length > 0) return undefined - const out: Record = {} - for (const ps of ast.propertySignatures) { - const name = ps.name - if (typeof name === "string") { - const value = yield* recur(ps.type, provider, [...path, name]) - if (value !== undefined) out[name] = value + const matchesIndex = ast.indexSignatures.map((is) => SchemaParser._is(is.parameter)) + const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { + if (cursor.node?._tag !== "Record") { + return undefined } - } - if (ast.indexSignatures.length > 0) { - if (stat && stat._tag === "Record") { - for (const is of ast.indexSignatures) { - const matches = SchemaParser._is(is.parameter) - for (const key of stat.keys) { - if (!Object.hasOwn(out, key) && matches(key)) { - const value = yield* recur(is.type, provider, [...path, key]) - if (value !== undefined) out[key] = value - } - } + const node = cursor.node + const keys = new Set() + for (const property of ast.propertySignatures) { + if (typeof property.name === "string") keys.add(property.name) + } + if (matchesIndex.length > 0) { + for (const key of node.keys) { + if (matchesIndex.some((matches) => matches(key))) keys.add(key) } } - } - return out + const out: Record = {} + for (const key of keys) { + const child = yield* loadChildCursor(cursor, key) + if (child.node !== undefined) InternalRecord.assignProperty(out, key, child) + } + return out + }) + return decodeFromCursor(ast.recur(recur, (ast) => ast), materialize) } case "Arrays": { - const stat = yield* provider.load(path) - if (stat === undefined) return undefined - if (stat && stat._tag === "Value") return stat.value === "" ? [] : stat.value.split(",") - if (stat && stat._tag === "Array" && stat.value !== undefined) { - return stat.value === "" ? [] : stat.value.split(",") - } - const out: Array = [] - const length = stat && stat._tag === "Array" ? stat.length : ast.elements.length - for (let i = 0; i < length; i++) { - const element = ast.elements[i] ?? ast.rest[0] - if (element !== undefined) { - out.push(yield* recur(element, provider, [...path, i])) + const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { + if (cursor.node?._tag !== "Array") { + return undefined } - } - return out + const out: Array = [] + for (let i = 0; i < cursor.node.length; i++) { + out.push(yield* loadChildCursor(cursor, i)) + } + return out + }) + return decodeFromCursor(ast.recur(recur), materialize) } case "Union": - // Let downstream decoding decide; dump can return a string, object, or array. - return yield* dump(provider, path) - case "Suspend": - return yield* recur(ast.thunk(), provider, path) - default: { - // Base primitives / string-like encoded nodes. - const stat = yield* provider.load(path) - if (stat === undefined) return undefined - if (stat._tag === "Value") return stat.value - if (stat._tag === "Record" && stat.value !== undefined) return stat.value - if (stat._tag === "Array" && stat.value !== undefined) return stat.value - // Container without a co-located value cannot satisfy a scalar request. - return undefined + for (const member of ast.types) { + recur(member) + } + return isScalarInput(ast) + ? decodeFromCursor(ast, (cursor) => Effect.succeed(getScalar(cursor.node))) + : ast.recur(recur) + case "Suspend": { + const target = ast.thunk() + // Force new branches so opaque encodings fail when the Config is constructed. + if (!seen.has(target)) recur(target) + return ast.recur(recur) } + case "Declaration": + case "Any": + throw new globalThis.Error("Config.schema does not support opaque StringTree encodings", { cause: ast }) + default: + return decodeFromCursor(ast, (cursor) => Effect.succeed(getScalar(cursor.node))) } - } -) + }) + return recur(root) +}) /** * Creates a `Config` from a `Schema.Codec`. @@ -606,15 +807,50 @@ const recur: ( * {@link nested} calls. Pass a single string for a flat key or an array for * nested paths. * - * Convenience constructors such as `string`, `number`, and `boolean` delegate + * Convenience constructors such as `String`, `Number`, and `Boolean` delegate * to this API. * - * The codec is used to decode the raw `StringTree` produced by the provider - * into `T`. Schema validation errors are wrapped in `ConfigError`. + * The codec is converted to its canonical `StringTree` form. Its encoded shape + * determines how provider data is loaded: scalar schemas read a co-located + * scalar value, object schemas read declared properties and matching record + * keys, and array schemas read indexed children. A mixed-shape union loads each + * member according to that member's shape before applying the union's mode and + * checks. + * + * At the config's lookup path, a missing node or a node that cannot provide the + * representation required by the schema is decoded as `undefined`. Missing + * object properties remain omitted so the schema's property semantics still + * apply. Decoding success always wins, even when no provider input was found. + * For example, + * `Schema.UndefinedOr(Schema.String)` decodes to `undefined` and is not replaced + * by {@link withDefault}. If decoding fails and no relevant representation was + * found, the config is absent. Invalid data in a relevant representation is a + * validation failure. Provider `SourceError`s are always failures. + * + * **Gotchas** + * + * Plain `Schema.Array` and `Schema.Record` schemas use structural provider + * input. Use {@link Array} or {@link Record} when a flat separated string must + * also be accepted. + * + * `Schema.Struct` and {@link all} describe different lookup models. An + * explicitly present empty object is relevant input for a struct and required + * fields are validated. The same empty parent container does not make an + * `all` group present when all of its child configs are absent. + * + * The canonical `StringTree` encoding must expose a concrete scalar, object, + * array, or union shape. Opaque encodings such as `Schema.Any`, + * `Schema.Unknown`, `Schema.ObjectKeyword`, `Schema.Json`, and + * `Schema.MutableJson` are rejected synchronously when this config is + * constructed, including when they are nested in another schema. Suspended + * recursive schemas remain supported when their eventual shape is concrete. + * Declarations such as `Schema.URL` also remain supported when their canonical + * encoding has a concrete shape. To read arbitrary JSON from one scalar value, + * use `Schema.fromJsonString(Schema.Json)`. * * **Example** (Reading a structured config) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Schema } from "effect" * * const DbConfig = Config.schema( @@ -629,204 +865,60 @@ const recur: ( * db: { host: "localhost", port: 5432 } * }) * - * // Effect.runSync(DbConfig.parse(provider)) - * // { host: "localhost", port: 5432 } + * Effect.runSync(DbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * - * @see {@link string} / {@link number} / {@link boolean} – shortcuts for + * @see {@link String} / {@link Number} / {@link Boolean} – shortcuts for * single-value configs * * @category schemas * @since 4.0.0 */ -export function schema(codec: Schema.Codec, path?: string | ConfigProvider.Path): Config { +export function schema(codec: Schema.ConstraintCodec, path?: string | ConfigProvider.Path): Config { const codecStringTree = Schema.toCodecStringTree(codec) - const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(codecStringTree) - const codecStringTreeEncoded = SchemaAST.toEncoded(codecStringTree.ast) + const encodedAst = SchemaAST.toEncoded(codecStringTree.ast) + const decodeCursor = SchemaParser.decodeUnknownEffect( + Schema.make>(toConfigCursorAST(codecStringTree.ast)) + ) const localPath = typeof path === "string" ? [path] : path ?? [] return make((provider, pathPrefix) => { const fullPath = [...pathPrefix, ...localPath] - return recur(codecStringTreeEncoded, provider, fullPath).pipe( - Effect.flatMapEager((tree) => - decodeUnknownEffect(tree).pipe( - Effect.mapErrorEager((issue) => - new Schema.SchemaError(fullPath.length > 0 ? new SchemaIssue.Pointer(fullPath, issue) : issue) - ) + return catchSourceError(loadCursor(provider, fullPath), false).pipe( + Effect.flatMapEager((cursor) => { + const hasInput = hasProviderInput(encodedAst, cursor.node) + return catchSourceError( + decodeCursor(cursor).pipe( + Effect.mapEager((value) => resolved(value, hasInput)), + Effect.catchEager((issue) => { + const error = new ConfigError( + new Schema.SchemaError(fullPath.length > 0 ? new SchemaIssue.Pointer(fullPath, issue) : issue) + ) + return hasInput + ? Effect.fail(evaluationFailure(error, true)) + : Effect.succeed(absent(error)) + }) + ), + hasInput ) - ), - Effect.mapErrorEager((cause) => new ConfigError(cause)) + }) ) }) } -/** @internal */ -export const TrueValues = Schema.Literals(["true", "yes", "on", "1", "y"]) +const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })) -/** @internal */ -export const FalseValues = Schema.Literals(["false", "no", "off", "0", "n"]) +const LogLevelSchema = Schema.Literals(LogLevel_.values) -/** - * Schema for boolean values encoded as strings. - * - * **When to use** - * - * Use when you need the reusable boolean schema value for `Config.schema` with - * custom paths. - * - * **Details** - * - * Accepted string values: `true`, `false`, `yes`, `no`, `on`, `off`, `1`, - * `0`, `y`, `n` (case-sensitive). - * - * @see {@link boolean} – convenience constructor - * - * @category schemas - * @since 4.0.0 - */ -export const Boolean = Schema.Literals([...TrueValues.literals, ...FalseValues.literals]).pipe( - Schema.decodeTo( - Schema.Boolean, - SchemaTransformation.transform({ - decode: (value) => value === "true" || value === "yes" || value === "on" || value === "1" || value === "y", - encode: (value) => value ? "true" : "false" - }) - ) -) - -/** - * Schema for port numbers (integers in 1–65535). - * - * **When to use** - * - * Use when you need the reusable port schema value for `Config.schema` with - * custom paths. - * - * @see {@link port} – convenience constructor - * - * @category schemas - * @since 4.0.0 - */ -export const Port = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })) - -/** - * Schema for `LogLevel` string literals. - * - * **When to use** - * - * Use when you need the reusable log-level schema value for `Config.schema` - * with custom paths. - * - * **Details** - * - * Accepted values: `"All"`, `"Fatal"`, `"Error"`, `"Warn"`, `"Info"`, - * `"Debug"`, `"Trace"`, `"None"`. - * - * @see {@link logLevel} – convenience constructor - * - * @category schemas - * @since 4.0.0 - */ -export const LogLevel = Schema.Literals(LogLevel_.values) - -/** - * Schema for key-value record types that can also be parsed from - * a flat comma-separated string. - * - * **When to use** - * - * Use when reading key-value maps from a single env var (e.g. OpenTelemetry - * resource attributes). - * - * **Details** - * - * Accepts either a JSON-like record from the provider or a flat string like - * `"key1=val1,key2=val2"`. The `separator` (default `","`) and - * `keyValueSeparator` (default `"="`) can be customized. - * - * **Example** (Parsing a comma-separated record) - * - * ```ts - * import { Config, ConfigProvider, Effect, Schema } from "effect" - * - * const schema = Config.Record(Schema.String, Schema.String) - * const config = Config.schema(schema, "OTEL_RESOURCE_ATTRIBUTES") - * - * const provider = ConfigProvider.fromEnv({ - * env: { - * OTEL_RESOURCE_ATTRIBUTES: - * "service.name=my-service,service.version=1.0.0,custom.attribute=value" - * } - * }) - * - * console.dir(Effect.runSync(config.parse(provider))) - * // { - * // 'service.name': 'my-service', - * // 'service.version': '1.0.0', - * // 'custom.attribute': 'value' - * // } - * ``` - * - * @category schemas - * @since 4.0.0 - */ -export const Record = (key: K, value: V, options?: { +interface ArrayOptions { readonly separator?: string | undefined - readonly keyValueSeparator?: string | undefined -}) => { - const record = Schema.Record(key, value) - const recordString = Schema.String.pipe( - Schema.decodeTo( - Schema.Record(Schema.String, Schema.String), - SchemaTransformation.splitKeyValue(options) - ), - Schema.decodeTo(record) - ) - - return Schema.Union([record, recordString]) } -/** - * @category schemas - * @since 4.0.0 - */ -const ArrayConfig = (value: V, options?: { +interface RecordOptions { readonly separator?: string | undefined -}) => { - const array = Schema.Array(value) - const separator = options?.separator ?? "," - const arrayString = Schema.String.pipe( - Schema.decodeTo( - Schema.Array(Schema.String), - { - decode: SchemaGetter.split(options), - encode: SchemaGetter.transform((input: ReadonlyArray) => input.join(separator)) - } - ), - Schema.decodeTo(array) - ) - - return Schema.Union([arrayString, array]) + readonly keyValueSeparator?: string | undefined } -export { - /** - * Schema for array types that can also be parsed from a flat separated string. - * - * **When to use** - * - * Use when reading array values from a single env var, such as comma-separated - * exporter names. - * - * **Details** - * - * Accepts either a JSON-like array from the provider or a flat string like - * `"a,b,c"`. The `separator` defaults to `","` and can be customized. - * - * @category schemas - * @since 4.0.0 - */ - ArrayConfig as Array -} +const isPath = (u: unknown): u is string | Path => Predicate.isString(u) || globalThis.Array.isArray(u) // ----------------------------------------------------------------------------- // constructors @@ -844,7 +936,7 @@ export { * @since 2.0.0 */ export function fail(err: SourceError | Schema.SchemaError) { - return make(() => Effect.fail(new ConfigError(err))) + return make(() => Effect.fail(evaluationFailure(new ConfigError(err), false))) } /** @@ -858,19 +950,21 @@ export function fail(err: SourceError | Schema.SchemaError) { * * **Example** (Returning a constant fallback) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * - * const host = Config.string("HOST").pipe( + * const host = Config.String("HOST").pipe( * Config.orElse(() => Config.succeed("localhost")) * ) + * const provider = ConfigProvider.fromUnknown({}) + * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @category constructors * @since 2.0.0 */ export function succeed(value: T) { - return make(() => Effect.succeed(value)) + return make(() => Effect.succeed(resolved(value, false))) } /** @@ -886,22 +980,22 @@ export function succeed(value: T) { * * **Example** (Reading a string config) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const host = Config.string("HOST") + * const host = Config.String("HOST") * * const provider = ConfigProvider.fromUnknown({ HOST: "localhost" }) - * // Effect.runSync(host.parse(provider)) // "localhost" + * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * - * @see {@link nonEmptyString} – rejects empty strings + * @see {@link NonEmptyString} – rejects empty strings * @see {@link schema} – for more complex types * * @category constructors * @since 2.0.0 */ -export function string(name?: string) { +export function String(name?: string) { return schema(Schema.String, name) } @@ -917,12 +1011,12 @@ export function string(name?: string) { * * Shortcut for `Config.schema(Schema.NonEmptyString, name)`. * - * @see {@link string} for allowing empty strings + * @see {@link String} for allowing empty strings * * @category constructors * @since 3.7.0 */ -export function nonEmptyString(name?: string) { +export function NonEmptyString(name?: string) { return schema(Schema.NonEmptyString, name) } @@ -938,13 +1032,13 @@ export function nonEmptyString(name?: string) { * * Shortcut for `Config.schema(Schema.Number, name)`. * - * @see {@link finite} for rejecting `NaN` and `Infinity` - * @see {@link int} for accepting only integers + * @see {@link Finite} for rejecting `NaN` and `Infinity` + * @see {@link Int} for accepting only integers * * @category constructors * @since 2.0.0 */ -export function number(name?: string) { +export function Number(name?: string) { return schema(Schema.Number, name) } @@ -959,13 +1053,13 @@ export function number(name?: string) { * * Shortcut for `Config.schema(Schema.Finite, name)`. * - * @see {@link number} for accepting `NaN` and `Infinity` - * @see {@link int} for accepting only integers + * @see {@link Number} for accepting `NaN` and `Infinity` + * @see {@link Int} for accepting only integers * * @category constructors * @since 4.0.0 */ -export function finite(name?: string) { +export function Finite(name?: string) { return schema(Schema.Finite, name) } @@ -980,13 +1074,13 @@ export function finite(name?: string) { * * Shortcut for `Config.schema(Schema.Int, name)`. * - * @see {@link number} for accepting any number - * @see {@link port} for accepting only integers in `1` through `65535` + * @see {@link Number} for accepting any number + * @see {@link Port} for accepting only integers in `1` through `65535` * * @category constructors * @since 4.0.0 */ -export function int(name?: string) { +export function Int(name?: string) { return schema(Schema.Int, name) } @@ -1003,17 +1097,19 @@ export function int(name?: string) { * * **Example** (Restricting to a literal) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * - * const env = Config.literal("production", "ENV") + * const env = Config.Literal("production", "ENV") + * const provider = ConfigProvider.fromUnknown({ ENV: "production" }) + * Effect.runSync(env.parse(provider)) // => "production" * ``` * - * @see {@link literals} – accepts multiple literal values + * @see {@link Literals} – accepts multiple literal values * @category constructors * @since 2.0.0 */ -export function literal(literal: L, name?: string) { +export function Literal(literal: L, name?: string) { return schema(Schema.Literal(literal), name) } @@ -1030,21 +1126,158 @@ export function literal(literal: L, name?: str * * **Example** (Restricting to a set of literals) * - * ```ts - * import { Config } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect } from "effect" * - * const env = Config.literals(["development", "production"], "ENV") + * const env = Config.Literals(["development", "production"], "ENV") + * const provider = ConfigProvider.fromUnknown({ ENV: "development" }) + * Effect.runSync(env.parse(provider)) // => "development" * ``` * - * @see {@link literal} for accepting one specific literal value + * @see {@link Literal} for accepting one specific literal value * * @category constructors * @since 4.0.0 */ -export function literals>(literals: L, name?: string) { +export function Literals>(literals: L, name?: string) { return schema(Schema.Literals(literals), name) } +/** + * Creates a config for array values that may also be read from a separated string. + * + * **When to use** + * + * Use when you need to read either structural array input or a flat value such as + * `"otlp,console"` from an environment variable. + * + * **Details** + * + * Pass a string or `ConfigProvider.Path` as the second argument to set the lookup + * path. When no path is needed, pass the options object directly. The `separator` + * defaults to `","`. + * + * **Example** (Reading a comma-separated array) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Schema } from "effect" + * + * const config = Config.Array(Schema.String, "EXPORTERS") + * const provider = ConfigProvider.fromEnv({ env: { EXPORTERS: "otlp,console" } }) + * + * Effect.runSync(config.parse(provider)) // => ["otlp", "console"] + * ``` + * + * @see {@link Record} for key-value input from structural records or separated strings. + * @category constructors + * @since 4.0.0 + */ +export function Array>( + value: V, + options?: ArrayOptions +): Config> +export function Array>( + value: V, + path: string | Path, + options?: ArrayOptions +): Config> +export function Array>( + value: V, + pathOrOptions?: string | Path | ArrayOptions, + options?: ArrayOptions +) { + const hasPath = isPath(pathOrOptions) + const resolvedOptions = hasPath ? options : pathOrOptions + const array = Schema.Array(value) + const separator = resolvedOptions?.separator ?? "," + const arrayString = Schema.String.pipe( + Schema.decodeTo(Schema.toCodecStringTree(array), { + decode: SchemaGetter.split(resolvedOptions), + encode: SchemaGetter.passthrough, Schema.StringTree>({ strict: false }).compose( + SchemaGetter.transform((input) => input.join(separator)) + ) + }) + ) + return schema(Schema.Union([arrayString, array]), hasPath ? pathOrOptions : undefined) +} + +/** + * Creates a config for record values that may also be read from a separated key-value string. + * + * **When to use** + * + * Use when you need to read either structural record input or a flat value such as + * `"service.name=my-service,service.version=1.0.0"` from an environment variable. + * + * **Details** + * + * Pass a string or `ConfigProvider.Path` as the third argument to set the lookup + * path. When no path is needed, pass the options object directly. The `separator` + * defaults to `","` and `keyValueSeparator` defaults to `"="`. + * + * **Example** (Reading a comma-separated record) + * + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Schema } from "effect" + * + * const config = Config.Record(Schema.String, Schema.String, "OTEL_RESOURCE_ATTRIBUTES") + * const provider = ConfigProvider.fromEnv({ + * env: { + * OTEL_RESOURCE_ATTRIBUTES: + * "service.name=my-service,service.version=1.0.0,custom.attribute=value" + * } + * }) + * + * const result = Effect.runSync(config.parse(provider)) + * result["service.name"] // => "my-service" + * result["service.version"] // => "1.0.0" + * result["custom.attribute"] // => "value" + * ``` + * + * @see {@link Array} for array input from structural arrays or separated strings. + * @category constructors + * @since 4.0.0 + */ +export function Record< + K extends Schema.Record.Key & Schema.ConstraintCodec, + V extends Schema.ConstraintCodec +>( + key: K, + value: V, + options?: RecordOptions +): Config> +export function Record< + K extends Schema.Record.Key & Schema.ConstraintCodec, + V extends Schema.ConstraintCodec +>( + key: K, + value: V, + path: string | Path, + options?: RecordOptions +): Config> +export function Record< + K extends Schema.Record.Key & Schema.ConstraintCodec, + V extends Schema.ConstraintCodec +>( + key: K, + value: V, + pathOrOptions?: string | Path | RecordOptions, + options?: RecordOptions +) { + const hasPath = isPath(pathOrOptions) + const record = Schema.Record(key, value) + const split = SchemaTransformation.splitKeyValue(hasPath ? options : pathOrOptions) + const recordString = Schema.String.pipe( + Schema.decodeTo(Schema.toCodecStringTree(record), { + decode: split.decode, + encode: SchemaGetter.passthrough, Schema.StringTree>({ strict: false }).compose( + split.encode + ) + }) + ) + return schema(Schema.Union([record, recordString]), hasPath ? pathOrOptions : undefined) +} + /** * Creates a config for a boolean value parsed from common string * representations. @@ -1055,20 +1288,15 @@ export function literals>( * * **Details** * - * Shortcut for `Config.schema(Config.Boolean, name)`. - * * Accepted values: `true`, `false`, `yes`, `no`, `on`, `off`, `1`, `0`, * `y`, `n`. * * **Example** (Reading a boolean flag) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const flag = yield* Config.boolean("FEATURE_FLAG") - * console.log(flag) - * }) + * const program = Config.Boolean("FEATURE_FLAG") * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1078,17 +1306,14 @@ export function literals>( * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: true + * ) // => true * ``` * - * @see {@link Boolean} for the underlying boolean codec - * * @category constructors * @since 2.0.0 */ -export function boolean(name?: string) { - return schema(Boolean, name) +export function Boolean(name?: string) { + return schema(Schema.BooleanLiterals, name) } /** @@ -1108,13 +1333,10 @@ export function boolean(name?: string) { * * **Example** (Reading a duration) * - * ```ts - * import { Config, ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Duration, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const duration = yield* Config.duration("DURATION") - * console.log(duration) - * }) + * const program = Config.Duration("DURATION").pipe(Effect.map(Duration.toMillis)) * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1124,8 +1346,7 @@ export function boolean(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: Duration { _tag: "millis", value: 10000 } + * ) // => 10000 * ``` * * @see {@link schema} for decoding configuration values with a custom codec @@ -1133,10 +1354,25 @@ export function boolean(name?: string) { * @category constructors * @since 2.5.0 */ -export function duration(name?: string) { +export function Duration(name?: string) { return schema(Schema.DurationFromString, name) } +/** + * Creates a config for an exact, human-readable byte-size value. + * + * **Details** + * + * Decimal symbols such as `kB` use powers of 1,000, while binary symbols such + * as `KiB` use powers of 1,024. + * + * @category constructors + * @since 4.0.0 + */ +export function ByteSize(name?: string) { + return schema(Schema.ByteSize, name) +} + /** * Creates a config for a port number (integer in 1–65535). * @@ -1146,17 +1382,14 @@ export function duration(name?: string) { * * **Details** * - * Shortcut for `Config.schema(Config.Port, name)`. + * Accepts integers from `1` through `65535`. * * **Example** (Reading a port) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const port = yield* Config.port("PORT") - * console.log(port) - * }) + * const program = Config.Port("PORT") * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1166,18 +1399,16 @@ export function duration(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: 8080 + * ) // => 8080 * ``` * - * @see {@link int} for integer config values outside the port range - * @see {@link Port} for the underlying port codec + * @see {@link Int} for integer config values outside the port range * * @category constructors * @since 3.16.0 */ -export function port(name?: string) { - return schema(Port, name) +export function Port(name?: string) { + return schema(PortSchema, name) } /** @@ -1189,20 +1420,15 @@ export function port(name?: string) { * * **Details** * - * Shortcut for `Config.schema(Config.LogLevel, name)`. - * * Accepted values: `"All"`, `"Fatal"`, `"Error"`, `"Warn"`, `"Info"`, * `"Debug"`, `"Trace"`, `"None"`. * * **Example** (Reading a log level) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const logLevel = yield* Config.logLevel("LOG_LEVEL") - * console.log(logLevel) - * }) + * const program = Config.LogLevel("LOG_LEVEL") * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1212,17 +1438,14 @@ export function port(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: "Info" + * ) // => "Info" * ``` * - * @see {@link LogLevel} for the underlying log-level codec - * * @category constructors * @since 2.0.0 */ -export function logLevel(name?: string) { - return schema(LogLevel, name) +export function LogLevel(name?: string) { + return schema(LogLevelSchema, name) } /** @@ -1240,13 +1463,10 @@ export function logLevel(name?: string) { * * **Example** (Reading a secret) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const apiKey = yield* Config.redacted("API_KEY") - * console.log(apiKey) - * }) + * const program = Config.Redacted("API_KEY").pipe(Effect.map(String)) * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1256,16 +1476,15 @@ export function logLevel(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: + * ) // => "" * ``` * - * @see {@link string} for non-secret string settings + * @see {@link String} for non-secret string settings * * @category constructors * @since 2.0.0 */ -export function redacted(name?: string) { +export function Redacted(name?: string) { return schema(Schema.Redacted(Schema.String), name) } @@ -1286,13 +1505,10 @@ export function redacted(name?: string) { * * **Example** (Reading a URL) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const program = Effect.gen(function*() { - * const url = yield* Config.url("URL") - * console.log(url) - * }) + * const program = Config.URL("URL").pipe(Effect.map((url) => url.href)) * * const provider = ConfigProvider.fromEnv({ * env: { @@ -1302,22 +1518,7 @@ export function redacted(name?: string) { * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) - * ) - * // Output: - * // URL { - * // href: 'https://example.com/', - * // origin: 'https://example.com', - * // protocol: 'https:', - * // username: '', - * // password: '', - * // host: 'example.com', - * // hostname: 'example.com', - * // port: '', - * // pathname: '/', - * // search: '', - * // searchParams: URLSearchParams {}, - * // hash: '' - * // } + * ) // => "https://example.com/" * ``` * * @see {@link schema} for decoding configuration values with a custom codec @@ -1325,7 +1526,7 @@ export function redacted(name?: string) { * @category constructors * @since 3.11.0 */ -export function url(name?: string) { +export function URL(name?: string) { return schema(Schema.URL, name) } @@ -1338,7 +1539,7 @@ export function url(name?: string) { * * **Details** * - * Shortcut for `Config.schema(Schema.DateValid, name)`. + * Shortcut for `Config.schema(Schema.Date, name)`. * * **Gotchas** * @@ -1346,21 +1547,20 @@ export function url(name?: string) { * * **Example** (Reading a date) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const createdAt = Config.date("CREATED_AT") + * const createdAt = Config.Date("CREATED_AT") * * const provider = ConfigProvider.fromUnknown({ CREATED_AT: "2024-01-15" }) - * // Effect.runSync(createdAt.parse(provider)) - * // Date("2024-01-15T00:00:00.000Z") + * Effect.runSync(createdAt.parse(provider)).toISOString() // => "2024-01-15T00:00:00.000Z" * ``` * * @category constructors * @since 2.0.0 */ -export function date(name?: string) { - return schema(Schema.DateValid, name) +export function Date(name?: string) { + return schema(Schema.Date, name) } /** @@ -1381,32 +1581,31 @@ export function date(name?: string) { * * **Example** (Nesting a struct config under `"database"`) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ - * host: Config.string("host"), - * port: Config.number("port") + * host: Config.String("host"), + * port: Config.Number("port") * }).pipe(Config.nested("database")) * * const provider = ConfigProvider.fromUnknown({ * database: { host: "localhost", port: "5432" } * }) - * // Effect.runSync(dbConfig.parse(provider)) - * // { host: "localhost", port: 5432 } + * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * **Example** (Reading env vars with a nested prefix) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * - * const host = Config.string("host").pipe(Config.nested("database")) + * const host = Config.String("host").pipe(Config.nested("database")) * * const provider = ConfigProvider.fromEnv({ * env: { database_host: "localhost" } * }) - * // Effect.runSync(host.parse(provider)) // "localhost" + * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link all} – combine multiple configs into a struct @@ -1421,5 +1620,5 @@ export const nested: { } = dual( 2, (self: Config, name: string): Config => - make((provider, pathPrefix) => self.parse(provider, [...pathPrefix, name])) + make((provider, pathPrefix) => evaluateAt(self, provider, [...pathPrefix, name])) ) diff --git a/packages/effect/src/ConfigProvider.ts b/packages/effect/src/ConfigProvider.ts index ec718c946..8dd6622c3 100644 --- a/packages/effect/src/ConfigProvider.ts +++ b/packages/effect/src/ConfigProvider.ts @@ -9,6 +9,7 @@ * @since 4.0.0 */ +import * as Arr from "./Array.ts" import * as Context from "./Context.ts" import * as Data from "./Data.ts" import * as Effect from "./Effect.ts" @@ -41,6 +42,10 @@ import * as Str from "./String.ts" * `value`. `Array` is an indexed container with a known `length` and may also * carry an optional co-located `value`. * + * Provider lookups return `undefined` when no node exists at the requested + * path. Within a node that was found, `value: undefined` has a narrower + * structural meaning: the container exists but has no co-located scalar value. + * * @see {@link makeValue} – construct a `Value` node * @see {@link makeRecord} – construct a `Record` node * @see {@link makeArray} – construct an `Array` node @@ -81,11 +86,10 @@ export type Node = * * **Example** (Creating a value node) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider } from "effect" * - * const node = ConfigProvider.makeValue("3000") - * // { _tag: "Value", value: "3000" } + * ConfigProvider.makeValue("3000") // => { _tag: "Value", value: "3000" } * ``` * * @see {@link makeRecord} – for object-like containers @@ -115,11 +119,15 @@ export function makeValue(value: string): Node { * * **Example** (Creating a record node) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider } from "effect" * * const node = ConfigProvider.makeRecord(new Set(["host", "port"])) - * // { _tag: "Record", keys: Set(["host", "port"]), value: undefined } + * node._tag // => "Record" + * if (node._tag === "Record") { + * node.keys // => new Set(["host", "port"]) + * node.value // => undefined + * } * ``` * * @see {@link makeValue} – for terminal leaves @@ -148,11 +156,10 @@ export function makeRecord(keys: ReadonlySet, value?: string): Node { * * **Example** (Creating an array node) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider } from "effect" * - * const node = ConfigProvider.makeArray(3) - * // { _tag: "Array", length: 3, value: undefined } + * ConfigProvider.makeArray(3) // => { _tag: "Array", length: 3, value: undefined } * ``` * * @see {@link makeValue} – for terminal leaves @@ -180,7 +187,7 @@ export function makeArray(length: number, value?: string): Node { * * **Example** (Failing with a SourceError) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.make((_path) => @@ -188,12 +195,14 @@ export function makeArray(length: number, value?: string): Node { * new ConfigProvider.SourceError({ message: "connection refused" }) * ) * ) + * + * Effect.runSync(Effect.flip(provider.load(["host"]))).message // => "connection refused" * ``` * * @see {@link ConfigProvider} – the interface whose `load` may fail with this * error * - * @category models + * @category errors * @since 4.0.0 */ export class SourceError extends Data.TaggedError("SourceError")<{ @@ -213,10 +222,11 @@ export class SourceError extends Data.TaggedError("SourceError")<{ * * **Example** (A typical config path) * - * ```ts + * ```ts import.meta.vitest * import type { ConfigProvider } from "effect" * * const path: ConfigProvider.Path = ["database", "replicas", 0, "host"] + * path.join(".") // => "database.replicas.0.host" * ``` * * @category models @@ -236,29 +246,63 @@ export type Path = ReadonlyArray * * `load(path)` is the semantic lookup operation used by the `Config` module. * It applies provider transformations and composition before consulting the - * underlying source. `undefined` means "not found" and `SourceError` means the - * source itself failed. + * underlying source. `undefined` means "not found", a `Node` means the path + * exists, and `SourceError` means the source itself failed. + * + * `mapInput(f)` is the provider's path-transformation capability. Keeping this + * capability on the provider allows source and composite providers to preserve + * their own lookup behavior without exposing an internal representation. + * Transformations compose in application order: `f` receives the path produced + * by earlier transformations. + * + * `load` deliberately accepts only a `Path`. Path transformation is modeled by + * returning another provider through `mapInput`, rather than by adding a + * transformation callback to every lookup. Custom implementations therefore + * expose lookup and transformation behavior, but no source or composition + * state. * * @see {@link make} – construct a provider from a lookup function * @see {@link orElse} – compose providers with fallback * - * @category models - * @since 2.0.0 + * @category services + * @since 4.0.0 */ export interface ConfigProvider extends Pipeable { /** - * Returns the node found at `path`, or `undefined` if it does not exist. - * Fails with `SourceError` when the underlying source cannot be read. + * Returns a `Node` when `path` exists or `undefined` when it does not. Fails + * with `SourceError` when the underlying source cannot be read. * * **When to use** * * Use to resolve a path through this provider's path transformations before * reading the backing source. + * + * **Details** + * + * Lookup absence controls provider composition, such as whether + * {@link orElse} consults its fallback. An optional `value` inside a found + * `Record` or `Array` node remains `undefined` because it describes the shape + * of that node rather than the outcome of the lookup. */ readonly load: (path: Path) => Effect.Effect - /** @internal */ - readonly state: ProviderState + /** + * Returns a provider that applies `f` to lookup paths after any existing path + * transformations. + * + * **When to use** + * + * Use to implement provider-specific path transformation behavior. Most + * callers should use the pipeable {@link mapInput} combinator. + * + * **Details** + * + * This capability is part of the provider interface so composite providers + * can distribute transformations to their operands while preserving each + * operand's behavior. Providers created with {@link make} implement it + * automatically. + */ + readonly mapInput: (f: (path: Path) => Path) => ConfigProvider } /** @@ -274,7 +318,7 @@ export interface ConfigProvider extends Pipeable { * * **Example** (Providing a custom provider) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromUnknown({ port: 8080 }) @@ -285,13 +329,15 @@ export interface ConfigProvider extends Pipeable { * }).pipe( * Effect.provideService(ConfigProvider.ConfigProvider, provider) * ) + * + * Effect.runSync(program) === provider // => true * ``` * * @see {@link layer} – install a provider as a Layer * @see {@link layerAdd} – add a fallback provider as a Layer * * @category services - * @since 2.0.0 + * @since 4.0.0 */ export const ConfigProvider: Context.Reference = Context.Reference( "effect/ConfigProvider", @@ -307,29 +353,15 @@ const Proto = { } } -type SourceState = { - readonly _tag: "Source" - readonly get: (path: Path) => Effect.Effect - readonly transform: (path: Path) => Path -} - -type OrElseState = { - readonly _tag: "OrElse" - readonly first: ConfigProvider - readonly second: ConfigProvider -} - -type ProviderState = SourceState | OrElseState - const identityPath = (path: Path): Path => path function makeProvider( - state: ProviderState, - load: (path: Path) => Effect.Effect + load: (path: Path) => Effect.Effect, + mapInput: (f: (path: Path) => Path) => ConfigProvider ): ConfigProvider { const self = Object.create(Proto) - self.state = state self.load = load + self.mapInput = mapInput return self } @@ -337,25 +369,21 @@ function makeSource( get: (path: Path) => Effect.Effect, transform: (path: Path) => Path ): ConfigProvider { - const state: SourceState = { - _tag: "Source", - get, - transform - } - return makeProvider(state, (path) => state.get(state.transform(path))) + return makeProvider( + (path) => get(transform(path)), + (f) => makeSource(get, flow(transform, f)) + ) } function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvider { - const state: OrElseState = { - _tag: "OrElse", - first, - second - } - return makeProvider(state, (path) => - Effect.flatMap( - state.first.load(path), - (node) => node ? Effect.succeed(node) : state.second.load(path) - )) + return makeProvider( + (path) => + Effect.flatMap( + first.load(path), + (node) => node !== undefined ? Effect.succeed(node) : second.load(path) + ), + (f) => makeOrElse(first.mapInput(f), second.mapInput(f)) + ) } /** @@ -369,12 +397,17 @@ function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvid * **Details** * * The `get` callback receives a `Path` and must return - * `Effect`. Return `undefined` when the path - * does not exist; fail with `SourceError` only for actual I/O errors. + * `Effect`. Return `undefined` when the path does + * not exist, a `Node` when it does, and fail with `SourceError` only when the + * source cannot be read. + * + * Providers created by `make` also implement the path-transformation + * capability used by {@link mapInput}, {@link constantCase}, and + * {@link nested}. * * **Example** (Creating a simple in-memory provider) * - * ```ts + * ```ts import.meta.vitest * import { ConfigProvider, Effect } from "effect" * * const data: Record = { @@ -389,13 +422,15 @@ function makeOrElse(first: ConfigProvider, second: ConfigProvider): ConfigProvid * value !== undefined ? ConfigProvider.makeValue(value) : undefined * ) * }) + * + * Effect.runSync(provider.load(["host"])) // => ConfigProvider.makeValue("localhost") * ``` * * @see {@link fromEnv} – pre-built provider for environment variables * @see {@link fromUnknown} – pre-built provider for JSON objects * * @category constructors - * @since 2.0.0 + * @since 4.0.0 */ export function make(get: (path: Path) => Effect.Effect): ConfigProvider { return makeSource(get, identityPath) @@ -423,8 +458,8 @@ export function make(get: (path: Path) => Effect.Effect Effect.Effect ["prod.example.com", "3000"] * ``` * * @see {@link layerAdd} – install a fallback provider via a Layer @@ -463,10 +502,14 @@ export const orElse: { * `f` runs. For providers composed with {@link orElse}, the transformation is * applied to each operand. * + * The combinator delegates transformation to the provider itself. Use + * {@link make} for custom sources so this capability is implemented + * automatically. + * * **Example** (Uppercasing path segments) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ * env: { APP_HOST: "localhost" } @@ -477,6 +520,9 @@ export const orElse: { * typeof seg === "string" ? seg.toUpperCase() : seg * ) * ) + * + * const node = Effect.runSync(upper.load(["app_host"])) + * node?.value // => "localhost" * ``` * * @see {@link constantCase} – a preset that converts to `CONSTANT_CASE` @@ -490,15 +536,7 @@ export const mapInput: { (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider } = dual( 2, - (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider => { - const state = self.state - switch (state._tag) { - case "Source": - return makeSource(state.get, flow(state.transform, f)) - case "OrElse": - return makeOrElse(mapInput(state.first, f), mapInput(state.second, f)) - } - } + (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider => self.mapInput(f) ) /** @@ -511,19 +549,22 @@ export const mapInput: { * * **Details** * - * Numeric segments are left unchanged. This is a specialization of - * {@link mapInput}. + * Numeric segments are left unchanged. String segments use `String.configCase` + * so numeric word groups such as `v2` are preserved for environment variable + * names. This is a specialization of {@link mapInput}. * * **Example** (Resolving camelCase keys to env vars) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ * env: { DATABASE_HOST: "localhost" } * }).pipe(ConfigProvider.constantCase) * * // path ["databaseHost"] now resolves to env var DATABASE_HOST + * const node = Effect.runSync(provider.load(["databaseHost"])) + * node?.value // => "localhost" * ``` * * @see {@link mapInput} – for arbitrary path transformations @@ -532,7 +573,7 @@ export const mapInput: { * @since 2.0.0 */ export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput((path) => - path.map((seg) => typeof seg === "number" ? seg : Str.constantCase(seg)) + path.map((seg) => typeof seg === "number" ? seg : Str.configCase(seg)) ) /** @@ -559,8 +600,8 @@ export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput(( * * **Example** (Nesting under a prefix) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ * env: { APP_HOST: "localhost", APP_PORT: "3000" } @@ -568,6 +609,8 @@ export const constantCase: (self: ConfigProvider) => ConfigProvider = mapInput(( * * // Lookups for ["HOST"] now resolve to ["APP", "HOST"] * const scoped = ConfigProvider.nested(provider, "APP") + * const node = Effect.runSync(scoped.load(["HOST"])) + * node?.value // => "localhost" * ``` * * @see {@link mapInput} – for arbitrary path transformations @@ -582,13 +625,7 @@ export const nested: { 2, (self: ConfigProvider, prefix: string | Path): ConfigProvider => { const path = typeof prefix === "string" ? [prefix] : prefix - const state = self.state - switch (state._tag) { - case "Source": - return makeSource(state.get, flow(state.transform, (input) => [...path, ...input])) - case "OrElse": - return makeOrElse(nested(state.first, path), nested(state.second, path)) - } + return mapInput(self, (input) => [...path, ...input]) } ) @@ -607,7 +644,7 @@ export const nested: { * * **Example** (Reading config from a JSON object) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Layer } from "effect" * * const TestLayer = ConfigProvider.layer( @@ -615,11 +652,11 @@ export const nested: { * ) * * const program = Effect.gen(function*() { - * const port = yield* Config.number("port") + * const port = yield* Config.Number("port") * return port * }) * - * // Effect.runSync(Effect.provide(program, TestLayer)) // 8080 + * Effect.runSync(Effect.provide(program, TestLayer)) // => 8080 * ``` * * @see {@link layerAdd} – add a provider without replacing the existing one @@ -645,13 +682,13 @@ export const layer = ( * **Details** * * By default, the new provider acts as a fallback and is consulted only when - * the current provider returns `undefined`. Set `asPrimary: true` to make the - * new provider the primary source, with the existing one as fallback. + * the current provider returns `undefined`. Set `asPrimary: true` to make + * the new provider the primary source, with the existing one as fallback. * * **Example** (Adding default values) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { Config, ConfigProvider, Effect, Layer } from "effect" * * const defaults = ConfigProvider.fromUnknown({ * HOST: "localhost", @@ -660,6 +697,11 @@ export const layer = ( * * // The current env provider is tried first; `defaults` is the fallback * const DefaultsLayer = ConfigProvider.layerAdd(defaults) + * const BaseLayer = ConfigProvider.layer(ConfigProvider.fromUnknown({})) + * const program = Config.String("HOST") + * + * const layer = Layer.provide(DefaultsLayer, BaseLayer) + * Effect.runSync(Effect.provide(program, layer)) // => "localhost" * ``` * * @see {@link layer} – replace the provider entirely @@ -694,15 +736,25 @@ export const layerAdd = ( * **Details** * * Path traversal follows standard JS rules: string segments index into object - * keys, numeric segments index into arrays. Returns `undefined` for any path - * that cannot be resolved. Never fails with `SourceError`. + * keys, numeric segments index into arrays. Returns `undefined` for any + * path that cannot be resolved. Never fails with `SourceError`. * * Primitive values (`number`, `boolean`, `bigint`) are stringified via * `String(...)`. * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. + * + * **Gotchas** + * + * Object keys and array lengths reflect the original input shape. A leaf value + * of `""` is treated as missing when that leaf is loaded, but the parent + * container still reports its original keys or length. + * * **Example** (Providing config from a plain object) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromUnknown({ @@ -712,24 +764,27 @@ export const layerAdd = ( * } * }) * - * const host = Config.string("host").parse( + * const host = Config.String("host").parse( * provider.pipe(ConfigProvider.nested("database")) * ) * - * // Effect.runSync(host) // "localhost" + * Effect.runSync(host) // => "localhost" * ``` * * @see {@link fromEnv} – for environment variables * @see {@link make} – for custom backing stores * - * @category ConfigProviders + * @category constructors * @since 4.0.0 */ -export function fromUnknown(root: unknown): ConfigProvider { - return make((path) => Effect.succeed(nodeAtJson(root, path))) +export function fromUnknown(root: unknown, options?: { + readonly preserveEmptyStrings?: boolean | undefined +}): ConfigProvider { + const preserveEmptyStrings = options?.preserveEmptyStrings === true + return make((path) => Effect.succeed(nodeAtJson(root, path, preserveEmptyStrings))) } -function nodeAtJson(root: unknown, path: Path): Node | undefined { +function nodeAtJson(root: unknown, path: Path, preserveEmptyStrings: boolean): Node | undefined { let cur: unknown = root for (const seg of path) { @@ -752,12 +807,12 @@ function nodeAtJson(root: unknown, path: Path): Node | undefined { return undefined } - return describeUnknown(cur) + return describeUnknown(cur, preserveEmptyStrings) } -function describeUnknown(u: unknown): Node | undefined { +function describeUnknown(u: unknown, preserveEmptyStrings: boolean): Node | undefined { if (u === undefined || u === null) return undefined - if (typeof u === "string") return makeValue(u) + if (typeof u === "string") return stringNode(u, preserveEmptyStrings) if (typeof u === "number" || typeof u === "boolean" || typeof u === "bigint") { return makeValue(String(u)) } @@ -769,14 +824,59 @@ function describeUnknown(u: unknown): Node | undefined { return makeValue(format(u)) } +function stringNode(value: string, preserveEmptyStrings: boolean): Node | undefined { + const normalized = emptyStringAsMissing(value, preserveEmptyStrings) + return normalized === undefined ? undefined : makeValue(normalized) +} + +function emptyStringAsMissing(value: string | undefined, preserveEmptyStrings: boolean): string | undefined { + return value === "" && !preserveEmptyStrings ? undefined : value +} + +/** + * Creates a `ConfigProvider` backed by an explicit environment record. + * + * **When to use** + * + * Use when a restricted runtime cannot evaluate the automatic environment + * detection performed by {@link fromEnv}, or whenever the environment record + * must be supplied explicitly. + * + * **Details** + * + * `undefined` values are ignored. Path lookup and child discovery otherwise + * use the same environment-variable semantics as {@link fromEnv}. + * + * Environment variable names are captured at construction time to establish + * record keys and array lengths. The supplied record remains live for value + * lookups, so updates to known paths are observed by later loads. Keys added + * after construction can be loaded directly, but do not appear in captured + * parent record keys or array lengths. + * + * Literal empty strings are treated as missing values by default. Pass + * `{ preserveEmptyStrings: true }` to keep empty strings as explicit values. + * + * @see {@link fromEnv} – automatically reads the runtime environment + * + * @category constructors + * @since 4.0.0 + */ +export function fromEnvRecord( + env: Record, + options?: { readonly preserveEmptyStrings?: boolean | undefined } +): ConfigProvider { + const preserveEmptyStrings = options?.preserveEmptyStrings === true + const trie = buildEnvTrie(env) + return make((path) => Effect.succeed(nodeAtEnv(trie, env, path, preserveEmptyStrings))) +} + /** * Creates a `ConfigProvider` backed by environment variables. * * **When to use** * * Use to read configuration from `process.env`, which is the default when no - * provider is explicitly set, or pass a custom env record for testing or - * non-Node runtimes. + * provider is explicitly set, or pass a custom env record for testing. * * **Details** * @@ -790,11 +890,16 @@ function describeUnknown(u: unknown): Node | undefined { * The default environment merges `process.env` and `import.meta.env` (when * available). Override by passing `{ env: { ... } }`. * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. Child discovery still reflects the environment variable + * names present in the source. + * * Never fails with `SourceError` — all lookups are synchronous. * * **Example** (Reading from a custom env record) * - * ```ts + * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const provider = ConfigProvider.fromEnv({ @@ -804,37 +909,39 @@ function describeUnknown(u: unknown): Node | undefined { * } * }) * - * const host = Config.string("HOST").parse( + * const host = Config.String("HOST").parse( * provider.pipe(ConfigProvider.nested("DATABASE")) * ) * - * // Effect.runSync(host) // "localhost" + * Effect.runSync(host) // => "localhost" * ``` * * @see {@link fromUnknown} – for JSON objects + * @see {@link fromEnvRecord} – for explicit records in restricted runtimes * @see {@link constantCase} – bridge camelCase keys to SCREAMING_SNAKE_CASE * - * @category ConfigProviders + * @category constructors * @since 2.0.0 */ -export function fromEnv(options?: { readonly env?: Record | undefined }): ConfigProvider { - const env = options?.env ?? { - ...globalThis?.process?.env, +export function fromEnv(options?: { + readonly env?: Record | undefined + readonly preserveEmptyStrings?: boolean | undefined +}): ConfigProvider { + const env: Record = options?.env ?? { + ...(globalThis as { + readonly process?: { readonly env?: Record } + }).process?.env, ...(import.meta as any)?.env } - - const trie = buildEnvTrie(env) - - return make((path) => Effect.succeed(nodeAtEnv(trie, env, path))) + return fromEnvRecord(env, { preserveEmptyStrings: options?.preserveEmptyStrings }) } type EnvTrieNode = { - value?: string children?: Record } function buildEnvTrie(env: Record): EnvTrieNode { - const root: EnvTrieNode = {} + const trie: EnvTrieNode = {} for (const [name, value] of Object.entries(env)) { if (value === undefined) continue @@ -842,24 +949,24 @@ function buildEnvTrie(env: Record): EnvTrieNode { // Split on "_" and keep empty segments (no special handling for "__") const segments = name.split("_") - let node = root + let node = trie for (const seg of segments) { - node.children ??= {} - node = node.children[seg] ??= {} + const children = node.children ??= Object.create(null) + node = children[seg] ??= {} } - - // co-located value at this node - node.value = value } - return root + return trie } -const NUMERIC_INDEX = /^(0|[1-9][0-9]*)$/ - -function nodeAtEnv(trie: EnvTrieNode, env: Record, path: Path): Node | undefined { +function nodeAtEnv( + trie: EnvTrieNode, + env: Record, + path: Path, + preserveEmptyStrings: boolean +): Node | undefined { const key = path.map(String).join("_") - const leafValue = env[key] + const leafValue = emptyStringAsMissing(Object.hasOwn(env, key) ? env[key] : undefined, preserveEmptyStrings) const trieNode = trieNodeAt(trie, path) const children = trieNode?.children ? Object.keys(trieNode.children) : [] @@ -868,7 +975,7 @@ function nodeAtEnv(trie: EnvTrieNode, env: Record, p return leafValue === undefined ? undefined : makeValue(leafValue) } - const allNumeric = children.every((k) => NUMERIC_INDEX.test(k)) + const allNumeric = children.every(Arr.isCanonicalArrayIndex) if (allNumeric) { const length = Math.max(...children.map((k) => parseInt(k, 10))) + 1 return makeArray(length, leafValue) @@ -903,14 +1010,19 @@ function trieNodeAt(root: EnvTrieNode, path: Path): EnvTrieNode | undefined { * and escaped newlines. Variable expansion (for example, `${VAR}`) is disabled * by default; enable with `{ expandVariables: true }`. * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. Child discovery still reflects the keys present in the + * parsed `.env` source. + * * Parsing is based on the `dotenv` / `dotenv-expand` algorithm. * - * Internally delegates to {@link fromEnv} with the parsed key-value pairs. + * Internally delegates to {@link fromEnvRecord} with the parsed key-value pairs. * * **Example** (Parsing .env contents) * - * ```ts - * import { ConfigProvider } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect } from "effect" * * const contents = ` * HOST=localhost @@ -919,29 +1031,33 @@ function trieNodeAt(root: EnvTrieNode, path: Path): EnvTrieNode | undefined { * ` * * const provider = ConfigProvider.fromDotEnvContents(contents) + * const port = Effect.runSync(provider.load(["PORT"])) + * port?.value // => "3000" * ``` * * @see {@link fromDotEnv} – loads a `.env` file from disk + * @see {@link fromEnvRecord} – for explicit environment records * @see {@link fromEnv} – for raw environment variable access * - * @category ConfigProviders + * @category constructors * @since 4.0.0 */ export function fromDotEnvContents(lines: string, options?: { readonly expandVariables?: boolean | undefined + readonly preserveEmptyStrings?: boolean | undefined }): ConfigProvider { let env = parseDotEnvContents(lines) if (options?.expandVariables) { env = dotEnvExpand(env) } - return fromEnv({ env }) + return fromEnvRecord(env, { preserveEmptyStrings: options?.preserveEmptyStrings }) } const DOT_ENV_LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg function parseDotEnvContents(lines: string): Record { - const obj: Record = {} + const obj: Record = Object.create(null) // Convert line breaks to same format lines = lines.replace(/\r\n?/gm, "\n") @@ -976,9 +1092,9 @@ function parseDotEnvContents(lines: string): Record { } function dotEnvExpand(parsed: Record): Record { - const newParsed: Record = {} + const newParsed: Record = Object.create(null) - for (const configKey in parsed) { + for (const configKey of Object.keys(parsed)) { // resolve escape sequences newParsed[configKey] = interpolate(parsed[configKey], parsed).replace(/\\\$/g, "$") } @@ -1014,9 +1130,12 @@ function interpolate(envValue: string, parsed: Record): string { if (match !== null) { const [_, group, variableName, defaultValue] = match + const value = Object.hasOwn(parsed, variableName) && parsed[variableName] !== "" + ? parsed[variableName] + : defaultValue ?? "" return interpolate( - envValue.replace(group, defaultValue || parsed[variableName] || ""), + envValue.replace(group, () => value), parsed ) } @@ -1041,19 +1160,35 @@ function searchLast(str: string, rgx: RegExp): number { * * Requires `FileSystem` in the Effect context. Defaults to reading `".env"` in * the current directory; override with `{ path: "/custom/.env" }`. + * Variable expansion (for example, `${VAR}`) is disabled by default; enable + * with `{ expandVariables: true }`. + * + * Literal empty strings are treated as missing values when loaded as values by + * default. Pass `{ preserveEmptyStrings: true }` to keep empty strings as + * explicit values. Child discovery still reflects the keys present in the + * parsed `.env` source. * * Returns an `Effect` that resolves to a `ConfigProvider`. Fails with a * `PlatformError` if the file cannot be read. * * **Example** (Loading a .env file) * - * ```ts - * import { ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect, FileSystem } from "effect" + * + * const fileSystem = FileSystem.makeNoop({ + * readFileString: () => Effect.succeed("HOST=localhost") + * }) * * const program = Effect.gen(function*() { * const provider = yield* ConfigProvider.fromDotEnv() - * return provider + * return yield* provider.load(["HOST"]) * }) + * + * const node = await Effect.runPromise( + * Effect.provideService(program, FileSystem.FileSystem, fileSystem) + * ) + * node?.value // => "localhost" * ``` * * @see {@link fromDotEnvContents} – parse a `.env` string directly @@ -1065,11 +1200,12 @@ function searchLast(str: string, rgx: RegExp): number { export const fromDotEnv: (options?: { readonly path?: string | undefined readonly expandVariables?: boolean | undefined + readonly preserveEmptyStrings?: boolean | undefined }) => Effect.Effect = Effect.fnUntraced( function*(options) { const fs = yield* FileSystem.FileSystem const content = yield* fs.readFileString(options?.path ?? ".env") - return fromEnv({ env: parseDotEnvContents(content) }) + return fromDotEnvContents(content, options) } ) @@ -1084,36 +1220,57 @@ export const fromDotEnv: (options?: { * * **Details** * - * Resolution tries a regular file first and returns a `Value` node with - * trimmed file contents. If the file read fails, it tries a directory and - * returns a `Record` node with immediate child names as keys. If both fail with - * `NotFound`, it returns `undefined`. Other platform failures return + * Resolution tries a regular file first and returns a `Value` node for + * non-empty trimmed file contents. If the file read fails, it tries a directory + * and returns a `Record` node with immediate child names as keys. If both fail + * with `NotFound`, it returns `undefined`. Other platform failures return * `SourceError`. * * Requires `Path` and `FileSystem` in the Effect context. Defaults to root * path `/`; override with `{ rootPath: "/etc/config" }`. * + * Literal empty strings are treated as missing values by default after file + * contents are trimmed. Pass `{ preserveEmptyStrings: true }` to keep empty + * strings as explicit values. Directory listings still reflect the file names + * present on disk. + * * **Example** (Reading config from a directory) * - * ```ts - * import { ConfigProvider, Effect } from "effect" + * ```ts import.meta.vitest + * import { ConfigProvider, Effect, FileSystem, Path } from "effect" + * + * const fileSystem = FileSystem.makeNoop({ + * readFileString: (path) => + * path === "/etc/myapp/host" + * ? Effect.succeed("localhost") + * : Effect.die("unexpected path") + * }) * * const program = Effect.gen(function*() { * const provider = yield* ConfigProvider.fromDir({ * rootPath: "/etc/myapp" * }) - * return provider + * return yield* provider.load(["host"]) * }) + * + * const node = await Effect.runPromise( + * program.pipe( + * Effect.provide(Path.layer), + * Effect.provideService(FileSystem.FileSystem, fileSystem) + * ) + * ) + * node?.value // => "localhost" * ``` * * @see {@link fromEnv} – for environment variables * @see {@link fromDotEnv} – for `.env` files * - * @category ConfigProviders + * @category constructors * @since 4.0.0 */ export const fromDir: (options?: { readonly rootPath?: string | undefined + readonly preserveEmptyStrings?: boolean | undefined }) => Effect.Effect< ConfigProvider, never, @@ -1122,13 +1279,14 @@ export const fromDir: (options?: { const platformPath = yield* Path_.Path const fs = yield* FileSystem.FileSystem const rootPath = options?.rootPath ?? "/" + const preserveEmptyStrings = options?.preserveEmptyStrings === true return make((path) => { const fullPath = platformPath.join(rootPath, ...path.map(String)) // Try reading as a *file* const asFile = fs.readFileString(fullPath).pipe( - Effect.map((content) => makeValue(content.trim())) + Effect.map((content) => stringNode(content.trim(), preserveEmptyStrings)) ) // If not a file, try reading as a *directory* diff --git a/packages/effect/src/Console.ts b/packages/effect/src/Console.ts index 72f8bcf7f..83264a74b 100644 --- a/packages/effect/src/Console.ts +++ b/packages/effect/src/Console.ts @@ -19,7 +19,7 @@ import type { Scope } from "./Scope.ts" /** * Represents a console interface for logging, debugging, timing, and grouping output. * - * @category models + * @category services * @since 2.0.0 */ export interface Console { @@ -58,19 +58,26 @@ export interface Console { * * **Example** (Accessing the current console) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (...args: ReadonlyArray) => messages.push(...args) + * }) * const program = Console.consoleWith((console) => * Effect.sync(() => { * console.log("Hello from current console!") * }) * ) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Hello from current console!"] * ``` * * @see {@link consoleWith} for using the current console service inside an effect * - * @category references + * @category services * @since 2.0.0 */ export const Console: Context.Reference = effect.ConsoleRef @@ -80,15 +87,23 @@ export const Console: Context.Reference = effect.ConsoleRef * * **Example** (Accessing the current console service) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (...args: ReadonlyArray) => messages.push(...args), + * error: (...args: ReadonlyArray) => messages.push(...args) + * }) * const program = Console.consoleWith((console) => * Effect.sync(() => { * console.log("Hello, world!") * console.error("This is an error message") * }) * ) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Hello, world!", "This is an error message"] * ``` * * @category constructors @@ -102,13 +117,22 @@ export const consoleWith = (f: (console: Console) => Effect.Effect = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * assert: (condition: boolean, ...args: ReadonlyArray) => { + * if (!condition) errors.push(...args) + * } + * }) * const program = Effect.gen(function*() { * yield* Console.assert(2 + 2 === 4, "Math is working correctly") * yield* Console.assert(2 + 2 === 5, "This will be logged as an error") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * errors // => ["This will be logged as an error"] * ``` * * @category accessors @@ -136,14 +160,22 @@ export const assert = (condition: boolean, ...args: ReadonlyArray): Effect. * * **Example** (Clearing console output) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (message: string) => operations.push(`log:${message}`), + * clear: () => operations.push("clear") + * }) * const program = Effect.gen(function*() { * yield* Console.log("This will be cleared") * yield* Console.clear * yield* Console.log("This appears after clearing") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => ["log:This will be cleared", "clear", "log:This appears after clearing"] * ``` * * @category accessors @@ -160,14 +192,26 @@ export const clear: Effect.Effect = consoleWith((console) => * * **Example** (Counting repeated calls) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const counters = new Map() + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * count: (label = "default") => { + * const count = (counters.get(label) ?? 0) + 1 + * counters.set(label, count) + * messages.push(`${label}: ${count}`) + * } + * }) * const program = Effect.gen(function*() { * yield* Console.count("my-counter") - * yield* Console.count("my-counter") // Will show: my-counter: 2 - * yield* Console.count() // Default counter + * yield* Console.count("my-counter") + * yield* Console.count() * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["my-counter: 1", "my-counter: 2", "default: 1"] * ``` * * @category accessors @@ -185,15 +229,28 @@ export const count = (label?: string): Effect.Effect => * * **Example** (Resetting a counter) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const counters = new Map() + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * count: (label = "default") => { + * const count = (counters.get(label) ?? 0) + 1 + * counters.set(label, count) + * messages.push(`${label}: ${count}`) + * }, + * countReset: (label = "default") => counters.set(label, 0) + * }) * const program = Effect.gen(function*() { * yield* Console.count("my-counter") - * yield* Console.count("my-counter") // Will show: my-counter: 2 + * yield* Console.count("my-counter") * yield* Console.countReset("my-counter") - * yield* Console.count("my-counter") // Will show: my-counter: 1 + * yield* Console.count("my-counter") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["my-counter: 1", "my-counter: 2", "my-counter: 1"] * ``` * * @category accessors @@ -217,13 +274,20 @@ export const countReset = (label?: string): Effect.Effect => * * **Example** (Writing debug messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * debug: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.debug("Debug info:", { userId: 123, action: "login" }) * yield* Console.debug("Processing step", 1, "of", 5) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => [["Debug info:", { userId: 123, action: "login" }], ["Processing step", 1, "of", 5]] * ``` * * @category accessors @@ -241,14 +305,25 @@ export const debug = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Inspecting an object) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const inspected: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * dir: (item: unknown, options?: unknown) => inspected.push([item, options]) + * }) * const program = Effect.gen(function*() { * const obj = { name: "John", age: 30, nested: { city: "New York" } } * yield* Console.dir(obj) - * yield* Console.dir(obj, { depth: 2, colors: true }) + * yield* Console.dir(obj, { depth: 2 }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * [{ name: "John", age: 30, nested: { city: "New York" } }, undefined], + * [{ name: "John", age: 30, nested: { city: "New York" } }, { depth: 2 }] + * ] + * inspected // => expected * ``` * * @category accessors @@ -266,15 +341,19 @@ export const dir = (item: any, options?: any): Effect.Effect => * * **Example** (Inspecting XML-like data) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * dirxml: (...args: ReadonlyArray) => messages.push(...args) + * }) * const program = Effect.gen(function*() { * yield* Console.dirxml("Ada") * }) * - * Effect.runSync(program) - * // Ada + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * messages // => ["Ada"] * ``` * * @category accessors @@ -293,9 +372,13 @@ export const dirxml = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Writing error messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * error: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.error("Something went wrong!") * yield* Console.error("Error details:", { @@ -303,6 +386,13 @@ export const dirxml = (...args: ReadonlyArray): Effect.Effect => * message: "Internal Server Error" * }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["Something went wrong!"], + * ["Error details:", { code: 500, message: "Internal Server Error" }] + * ] + * messages // => expected * ``` * * @category accessors @@ -320,9 +410,15 @@ export const error = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Grouping scoped output) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * group: (label?: string) => operations.push(`group:${label}`), + * groupEnd: () => operations.push("groupEnd"), + * log: (message: string) => operations.push(`log:${message}`) + * }) * const program = Effect.gen(function*() { * yield* Effect.scoped( * Effect.gen(function*() { @@ -333,6 +429,16 @@ export const error = (...args: ReadonlyArray): Effect.Effect => * }) * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * "group:User Processing", + * "log:Loading user data...", + * "log:Validating user...", + * "log:User processed successfully", + * "groupEnd" + * ] + * operations // => expected * ``` * * @category accessors @@ -363,9 +469,13 @@ export const group = ( * * **Example** (Writing informational messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * info: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.info("Application started successfully") * yield* Console.info("Server configuration:", { @@ -373,6 +483,13 @@ export const group = ( * env: "development" * }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["Application started successfully"], + * ["Server configuration:", { port: 3000, env: "development" }] + * ] + * messages // => expected * ``` * * @category accessors @@ -390,14 +507,26 @@ export const info = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Writing log messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * log: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.log("Hello, world!") * yield* Console.log("User data:", { name: "John", age: 30 }) * yield* Console.log("Processing", 42, "items") * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["Hello, world!"], + * ["User data:", { name: "John", age: 30 }], + * ["Processing", 42, "items"] + * ] + * messages // => expected * ``` * * @category accessors @@ -415,9 +544,16 @@ export const log = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Displaying tabular data) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const calls: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * table: (data: ReadonlyArray, properties?: ReadonlyArray) => { + * calls.push({ rows: data.length, properties }) + * } + * }) + * * const program = Effect.gen(function*() { * const users = [ * { name: "John", age: 30, city: "New York" }, @@ -427,6 +563,9 @@ export const log = (...args: ReadonlyArray): Effect.Effect => * yield* Console.table(users) * yield* Console.table(users, ["name", "age"]) // Only show specific columns * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * calls // => [{ rows: 3, properties: undefined }, { rows: 3, properties: ["name", "age"] }] * ``` * * @category accessors @@ -444,19 +583,28 @@ export const table = (tabularData: any, properties?: ReadonlyArray): Eff * * **Example** (Timing scoped work) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * time: (label?: string) => operations.push(`start:${label}`), + * timeEnd: (label?: string) => operations.push(`end:${label}`), + * log: (message: string) => operations.push(`log:${message}`) + * }) + * * const program = Effect.gen(function*() { * yield* Effect.scoped( * Effect.gen(function*() { * yield* Console.time("operation-timer") - * yield* Effect.sleep("1 second") * yield* Console.log("Operation completed") * // Timer ends automatically when scope closes * }) * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => ["start:operation-timer", "log:Operation completed", "end:operation-timer"] * ``` * * @category accessors @@ -480,20 +628,28 @@ export const time = (label?: string | undefined): Effect.Effect = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * time: (label?: string) => operations.push(["start", label]), + * timeLog: (label?: string, ...args: ReadonlyArray) => operations.push(["log", label, ...args]), + * timeEnd: (label?: string) => operations.push(["end", label]) + * }) + * * const program = Effect.gen(function*() { * yield* Effect.scoped( * Effect.gen(function*() { * yield* Console.time("long-operation") - * yield* Effect.sleep("500 millis") * yield* Console.timeLog("long-operation", "Halfway done") - * yield* Effect.sleep("500 millis") * // Timer ends when scope closes * }) * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => [["start", "long-operation"], ["log", "long-operation", "Halfway done"], ["end", "long-operation"]] * ``` * * @category accessors @@ -512,13 +668,21 @@ export const timeLog = (label?: string, ...args: ReadonlyArray): Effect.Eff * * **Example** (Writing stack traces) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const traces: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * trace: (...args: ReadonlyArray) => traces.push(args) + * }) + * * const program = Effect.gen(function*() { * yield* Console.trace("Debug trace point") * yield* Console.trace("Function call:", { functionName: "processData" }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * traces // => [["Debug trace point"], ["Function call:", { functionName: "processData" }]] * ``` * * @category accessors @@ -537,15 +701,26 @@ export const trace = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Writing warning messages) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * warn: (...args: ReadonlyArray) => messages.push(args) + * }) * const program = Effect.gen(function*() { * yield* Console.warn("This feature is deprecated") * yield* Console.warn("Performance warning:", { * slowQuery: "SELECT * FROM large_table" * }) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["This feature is deprecated"], + * ["Performance warning:", { slowQuery: "SELECT * FROM large_table" }] + * ] + * messages // => expected * ``` * * @category accessors @@ -563,9 +738,15 @@ export const warn = (...args: ReadonlyArray): Effect.Effect => * * **Example** (Wrapping an effect in a group) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * group: (label?: string) => operations.push(`group:${label}`), + * groupEnd: () => operations.push("groupEnd"), + * log: (message: string) => operations.push(`log:${message}`) + * }) * const program = Effect.gen(function*() { * yield* Console.withGroup( * Effect.gen(function*() { @@ -576,6 +757,16 @@ export const warn = (...args: ReadonlyArray): Effect.Effect => * { label: "Processing Steps", collapsed: false } * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * "group:Processing Steps", + * "log:Step 1: Initialize", + * "log:Step 2: Process", + * "log:Step 3: Complete", + * "groupEnd" + * ] + * operations // => expected * ``` * * @category accessors @@ -618,18 +809,27 @@ export const withGroup = dual< * * **Example** (Timing an effect) * - * ```ts + * ```ts import.meta.vitest * import { Console, Effect } from "effect" * + * const operations: Array = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * time: (label?: string) => operations.push(`start:${label}`), + * timeEnd: (label?: string) => operations.push(`end:${label}`), + * log: (message: string) => operations.push(`log:${message}`) + * }) + * * const program = Effect.gen(function*() { * yield* Console.withTime( * Effect.gen(function*() { - * yield* Effect.sleep("1 second") * yield* Console.log("Operation completed") * }), * "my-operation" * ) * }) + * + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * operations // => ["start:my-operation", "log:Operation completed", "end:my-operation"] * ``` * * @category accessors diff --git a/packages/effect/src/Context.ts b/packages/effect/src/Context.ts index cfc8cd983..52ee4d14c 100644 --- a/packages/effect/src/Context.ts +++ b/packages/effect/src/Context.ts @@ -17,7 +17,6 @@ import { dual, type LazyArg } from "./Function.ts" import * as Hash from "./Hash.ts" import type { Inspectable } from "./Inspectable.ts" import { exitSucceed, PipeInspectableProto, withFiber } from "./internal/core.ts" -import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" @@ -67,7 +66,6 @@ export interface Key extends Effect extends Effect extends Effect `Result: ${sql}` }) + * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1" * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface Service extends Key { @@ -118,7 +117,7 @@ export interface Service extends Key @@ -139,7 +138,7 @@ export declare namespace ServiceClass { * Runtime and type-level metadata carried by a class-style service key, * including its service type identifier, string key, and service shape. * - * @category models + * @category services * @since 4.0.0 */ export interface Shape { @@ -172,7 +171,7 @@ export declare namespace ServiceClass { * * **Example** (Creating service keys) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Create a simple service @@ -190,15 +189,23 @@ export declare namespace ServiceClass { * query: (sql) => `Result: ${sql}` * }) * const config = Context.make(Config, { port: 8080 }) + * Context.get(db, Database).query("SELECT 1") // => "Result: SELECT 1" + * Context.get(config, Config).port // => 8080 * ``` * * @see {@link Reference} for service keys with default values * - * @category constructors + * @category services * @since 4.0.0 */ export const Service: { - (key: string): Service + ( + key: string, + options?: { + /** @internal */ + readonly fiberCached?: boolean | undefined + } | undefined + ): Service (): < const Identifier extends string, E, @@ -207,7 +214,9 @@ export const Service: { >( id: Identifier, options?: { - readonly make: ((...args: Args) => Effect) | Effect | undefined + readonly make?: ((...args: Args) => Effect) | Effect | undefined + /** @internal */ + readonly fiberCached?: boolean | undefined } | undefined ) => & ServiceClass @@ -220,6 +229,8 @@ export const Service: { id: Identifier, options: { readonly make: Make + /** @internal */ + readonly fiberCached?: boolean | undefined } ) => & ServiceClass< @@ -231,36 +242,28 @@ export const Service: { > & { readonly make: Make } } = function() { - const prevLimit = getStackTraceLimit() - setStackTraceLimit(2) - const err = new Error() - setStackTraceLimit(prevLimit) function KeyClass() {} const self = KeyClass as any as Types.Mutable> Object.setPrototypeOf(self, ServiceProto) - // @effect-diagnostics-next-line floatingEffect:off - Object.defineProperty(self, "stack", { - get() { - return err.stack - } - }) - if (arguments.length > 0) { - self.key = arguments[0] - if (arguments[1]?.defaultValue) { - self[ReferenceTypeId] = ReferenceTypeId - self.defaultValue = arguments[1].defaultValue - } - return self - } - return function(key: string, options?: { + const init = (key: string, options?: { + readonly defaultValue?: any readonly make?: any - }) { + readonly fiberCached?: boolean + }) => { self.key = key + if (options?.defaultValue) { + self[ReferenceTypeId] = ReferenceTypeId + self.defaultValue = options.defaultValue + } if (options?.make) { ;(self as any).make = options.make } + if (options?.fiberCached) { + cacheKeys.add(key) + } return self } + return arguments.length > 0 ? init(arguments[0], arguments[1]) : init } as any const ServiceProto: any = { @@ -274,8 +277,7 @@ const ServiceProto: any = { toJSON(this: Service) { return { _id: "Service", - key: this.key, - stack: this.stack + key: this.key } }, of(this: void, self: Service): Service { @@ -295,6 +297,8 @@ const ServiceProto: any = { } } +const cacheKeys = new Set() + const ReferenceTypeId = "~effect/Context/Reference" as const /** @@ -308,21 +312,24 @@ const ReferenceTypeId = "~effect/Context/Reference" as const * * **Example** (Defining a reference with a default value) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Define a reference with a default value + * const messages: Array = [] * const LoggerRef: Context.Reference<{ log: (msg: string) => void }> = * Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) + * defaultValue: () => ({ log: (msg) => { messages.push(msg) } }) * }) * * // The reference can be used without explicit provision * const context = Context.empty() * const logger = Context.get(context, LoggerRef) // Uses default value + * logger.log("default logger") + * messages // => ["default logger"] * ``` * - * @category models + * @category services * @since 3.11.0 */ export interface Reference extends Service { @@ -337,7 +344,7 @@ export interface Reference extends Service { * * **Example** (Extracting service types) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Database = Context.Service<{ @@ -349,6 +356,8 @@ export interface Reference extends Service { * * // Extract identifier type from a key * type DatabaseId = Context.Service.Identifier + * + * Database.key // => "Database" * ``` * * @since 2.0.0 @@ -360,7 +369,7 @@ export declare namespace Service { * * **Example** (Typing any service key) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Any represents any possible service type @@ -368,9 +377,10 @@ export declare namespace Service { * Context.Service<{ log: (msg: string) => void }>("Logger"), * Context.Service<{ query: (sql: string) => string }>("Database") * ] + * services.map((service) => service.key) // => ["Logger", "Database"] * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Any = Key | Key @@ -381,7 +391,7 @@ export declare namespace Service { * * **Example** (Extracting a service shape) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Database = Context.Service<{ query: (sql: string) => string }>( @@ -391,9 +401,10 @@ export declare namespace Service { * // Extract the service shape from the service * type DatabaseService = Context.Service.Shape * // DatabaseService is { query: (sql: string) => string } + * Database.key // => "Database" * ``` * - * @category models + * @category utility types * @since 4.0.0 */ export type Shape = T extends Key ? S : never @@ -404,7 +415,7 @@ export declare namespace Service { * * **Example** (Extracting a service identifier) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Database = Context.Service<{ query: (sql: string) => string }>( @@ -414,9 +425,10 @@ export declare namespace Service { * // Extract the identifier type from a key * type DatabaseId = Context.Service.Identifier * // DatabaseId is the identifier type + * Database.key // => "Database" * ``` * - * @category models + * @category utility types * @since 2.0.0 */ export type Identifier = T extends Key ? I : never @@ -435,7 +447,7 @@ const TypeId = "~effect/Context" as const * * **Example** (Creating a context with multiple services) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Create a context with multiple services @@ -444,10 +456,9 @@ const TypeId = "~effect/Context" as const * "Database" * ) * - * const context = Context.make(Logger, { - * log: (msg: string) => console.log(msg) - * }) + * const context = Context.make(Logger, { log: (_msg: string) => {} }) * .pipe(Context.add(Database, { query: (sql) => `Result: ${sql}` })) + * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1" * ``` * * @category models @@ -458,7 +469,81 @@ export interface Context extends Equal.Equal, Pipeable, Inspectable readonly _Services: Types.Contravariant } readonly mapUnsafe: ReadonlyMap - mutable: boolean +} + +interface ContextImpl extends Context { + cacheRoot: ContextImpl | undefined + base: ReadonlyMap + baseHits: number + overlay: Overlay | undefined + depth: number + _flat: ReadonlyMap | undefined +} + +interface Overlay { + readonly key: string + readonly value: unknown + readonly parent: Overlay | undefined +} + +const MaxDepth = 8 +const FlattenAfterBaseHits = 8 + +const makeImpl = ( + cacheRoot: ContextImpl | undefined, + base: ReadonlyMap, + overlay: Overlay | undefined, + depth: number +): ContextImpl => { + const self: ContextImpl = Object.create(Proto) + self.cacheRoot = cacheRoot ?? self + self.base = base + self.overlay = overlay + self.depth = depth + self._flat = undefined + self.baseHits = 0 + return self +} + +const applyOverlays = (map: Map, overlay: Overlay | undefined): void => { + if (!overlay) return + applyOverlays(map, overlay.parent) + map.set(overlay.key, overlay.value) +} + +const flatten = (self: ContextImpl): ReadonlyMap => { + if (self._flat) return self._flat + if (!self.overlay) return self._flat = self.base + const map = new Map(self.base) + applyOverlays(map, self.overlay) + return self._flat = map +} + +const withFlat = (self: Context, f: (map: Map) => void): Context => { + const map = new Map(self.mapUnsafe) + f(map) + return makeUnsafe(map) +} + +// A private symbol so user code cannot forge a value that reads as absent +const notFound = Symbol() + +const lookup = (self: Context, key: string): unknown => { + const impl = self as ContextImpl + for (let overlay = impl.overlay; overlay; overlay = overlay.parent) { + if (overlay.key === key) return overlay.value + } + const value = impl.base.get(key) + // Misses must not advance the counter: reference-default lookups miss the + // base on every fiber cache refresh, which would flatten every short-lived + // request context and reintroduce the O(services) per-request cost + if (value === undefined && !impl.base.has(key)) return notFound + if (impl.overlay && ++impl.baseHits >= FlattenAfterBaseHits) { + impl.base = flatten(impl) + impl.overlay = undefined + impl.depth = 0 + } + return value } /** @@ -471,34 +556,37 @@ export interface Context extends Equal.Equal, Pipeable, Inspectable * * **Gotchas** * - * This is unsafe because later mutation of the provided map can affect the - * created `Context`. Prefer `empty`, `make`, `add`, or `merge` for normal - * Context construction. + * The provided map is retained without copying and must not be mutated after + * construction. Prefer `empty`, `make`, `add`, or `merge` for normal Context + * construction. * * **Example** (Creating a context from a map) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * // Create a context from a Map (unsafe) * const map = new Map([ - * ["Logger", { log: (msg: string) => console.log(msg) }] + * ["Logger", { log: (_msg: string) => {} }] * ]) * * const context = Context.makeUnsafe(map) + * context.mapUnsafe.size // => 1 * ``` * * @category constructors * @since 4.0.0 */ -export const makeUnsafe = (mapUnsafe: ReadonlyMap): Context => { - const self = Object.create(Proto) - self.mapUnsafe = mapUnsafe - self.mutable = false - return self -} +export const makeUnsafe = (mapUnsafe: ReadonlyMap): Context => + makeImpl(undefined, mapUnsafe, undefined, 0) -const Proto: Omit, "mapUnsafe" | "mutable"> = { +const Proto: Omit< + ContextImpl, + "cacheRoot" | "base" | "overlay" | "depth" | "_flat" | "baseHits" +> = { + get mapUnsafe() { + return flatten(this as any as ContextImpl) + }, ...PipeInspectableProto, [TypeId]: { _Services: (_: never) => _ @@ -510,17 +598,12 @@ const Proto: Omit, "mapUnsafe" | "mutable"> = { } }, [Equal.symbol](this: Context, that: unknown): boolean { - if ( - !isContext(that) - || this.mapUnsafe.size !== that.mapUnsafe.size - ) return false - for (const k of this.mapUnsafe.keys()) { - if ( - !that.mapUnsafe.has(k) || - !Equal.equals(this.mapUnsafe.get(k), that.mapUnsafe.get(k)) - ) { - return false - } + if (!isContext(that)) return false + const self = this.mapUnsafe + const other = that.mapUnsafe + if (self.size !== other.size) return false + for (const [key, value] of self) { + if (!other.has(key) || !Equal.equals(value, other.get(key))) return false } return true }, @@ -529,6 +612,12 @@ const Proto: Omit, "mapUnsafe" | "mutable"> = { } } +/** @internal */ +export const hasSameCache = ( + self: Context, + that: Context +): boolean => (self as ContextImpl).cacheRoot === (that as ContextImpl).cacheRoot + /** * Checks whether the provided argument is a `Context`. * @@ -549,11 +638,9 @@ const Proto: Omit, "mapUnsafe" | "mutable"> = { * * **Example** (Checking for contexts) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" - * - * assert.strictEqual(Context.isContext(Context.empty()), true) + * Context.isContext(Context.empty()) // => true * ``` * * @see {@link isKey} for checking service keys @@ -569,11 +656,9 @@ export const isContext = (u: unknown): u is Context => hasProperty(u, Typ * * **Example** (Checking for keys) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" - * - * assert.strictEqual(Context.isKey(Context.Service("Service")), true) + * Context.isKey(Context.Service("Service")) // => true * ``` * * @category guards @@ -586,33 +671,30 @@ export const isKey = (u: unknown): u is Key => hasProperty(u, ServiceT * * **Example** (Checking for references) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const LoggerRef = Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) + * defaultValue: () => ({ log: (_msg: string) => {} }) * }) * - * assert.strictEqual(Context.isReference(LoggerRef), true) - * assert.strictEqual(Context.isReference(Context.Service("Key")), false) + * Context.isReference(LoggerRef) // => true + * Context.isReference(Context.Service("Key")) // => false * ``` * * @category guards * @since 3.11.0 */ -export const isReference = (u: unknown): u is Reference => hasProperty(u, ReferenceTypeId) +export const isReference = (u: Key): u is Reference => !!(u as Reference)[ReferenceTypeId] /** * Returns an empty `Context`. * * **Example** (Creating an empty context) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" - * - * assert.strictEqual(Context.isContext(Context.empty()), true) + * Context.empty().mapUnsafe.size // => 0 * ``` * * @category constructors @@ -626,15 +708,14 @@ const emptyContext = makeUnsafe(new Map()) * * **Example** (Creating a context with one service) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * * const context = Context.make(Port, { PORT: 8080 }) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) + * Context.get(context, Port).PORT // => 8080 * ``` * * @category constructors @@ -659,9 +740,8 @@ export const make = ( * * **Example** (Adding a service to a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -673,13 +753,13 @@ export const make = ( * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) + * const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT] + * values // => [8080, 5000] * ``` * * @see {@link addOrOmit} for adding or removing a service from an `Option` * - * @category adders + * @category combining * @since 2.0.0 */ export const add: { @@ -696,10 +776,36 @@ export const add: { self: Context, key: Key, service: Types.NoInfer -): Context => - withMapUnsafe(self, (map) => { - map.set(key.key, service) - })) +): Context => addUnsafe(self, key.key, service)) + +/** + * Adds a service by key to a given `Context` using a string key. + * + * @category combining + * @since 4.0.0 + */ +export const addUnsafe = ( + self: Context, + key: string, + service: Types.NoInfer +): Context => { + const impl = self as ContextImpl + const cacheRoot = cacheKeys.has(key) ? undefined : impl.cacheRoot + if (impl.depth >= MaxDepth) { + // Rebase the overlay chain into a flat map, keeping the cacheRoot so a + // rebase on an ordinary key does not invalidate fiber caches + const map = new Map(impl.mapUnsafe) + map.set(key, service) + return makeImpl(cacheRoot, map, undefined, 0) + } + + return makeImpl( + cacheRoot, + impl.base, + { key, value: service, parent: impl.overlay }, + impl.depth + 1 + ) +} /** * Adds or removes a service depending on an `Option`. @@ -715,7 +821,7 @@ export const add: { * * **Example** (Adding optional services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option } from "effect" * * const Port = Context.Service<{ PORT: number }>("Port") @@ -727,35 +833,33 @@ export const add: { * const withoutPort = withPort.pipe( * Context.addOrOmit(Port, Option.none()) * ) + * Context.getOption(withPort, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(withoutPort, Port) // => Option.none() * ``` * * @see {@link add} for always storing a service value * - * @category adders + * @category combining * @since 4.0.0 */ export const addOrOmit: { ( key: Key, service: Option.Option> - ): (self: Context) => Context + ): (self: Context) => Context> ( self: Context, key: Key, service: Option.Option> - ): Context + ): Context> } = dual(3, ( self: Context, key: Key, service: Option.Option> -): Context => - withMapUnsafe(self, (map) => { - if (service._tag === "None") { - map.delete(key.key) - } else { - map.set(key.key, service.value) - } - })) +): Context> => + service._tag === "None" + ? omit(key)(self) + : add(self, key, service.value) as any) /** * Gets the service for a key, or evaluates the fallback when a non-reference @@ -778,7 +882,7 @@ export const addOrOmit: { * * **Example** (Falling back for missing services) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" * * const Logger = Context.Service<{ log: (msg: string) => void }>("Logger") @@ -786,9 +890,7 @@ export const addOrOmit: { * "Database" * ) * - * const context = Context.make(Logger, { - * log: (msg: string) => console.log(msg) - * }) + * const context = Context.make(Logger, { log: (_msg: string) => {} }) * * const logger = Context.getOrElse(context, Logger, () => ({ log: () => {} })) * const database = Context.getOrElse( @@ -797,8 +899,8 @@ export const addOrOmit: { * () => ({ query: () => "fallback" }) * ) * - * console.log(logger === Context.get(context, Logger)) // true - * console.log(database.query("SELECT 1")) // "fallback" + * logger === Context.get(context, Logger) // => true + * database.query("SELECT 1") // => "fallback" * ``` * * @see {@link getOption} for returning `Option.none` when a non-reference key is missing @@ -810,9 +912,8 @@ export const getOrElse: { (key: Key, orElse: LazyArg): (self: Context) => S | B (self: Context, key: Key, orElse: LazyArg): S | B } = dual(3, (self: Context, key: Key, orElse: LazyArg): S | B => { - if (self.mapUnsafe.has(key.key)) { - return self.mapUnsafe.get(key.key)! as any - } + const value = lookup(self, key.key) + if (value !== notFound) return value as any return isReference(key) ? getDefaultValue(key) : orElse() }) @@ -840,9 +941,15 @@ export const getOrUndefined: { (self: Context, key: Key): S | undefined } = dual( 2, - (self: Context, key: Key): S | undefined => self.mapUnsafe.get(key.key) + (self: Context, key: Key): S | undefined => getOrUndefinedUnsafe(self, key.key) ) +/** @internal */ +export const getOrUndefinedUnsafe = (self: Context, key: string): A | undefined => { + const value = lookup(self, key) + return value === notFound ? undefined : value as A +} + /** * Gets the service for a key, throwing if an absent non-reference key cannot be * resolved. @@ -860,17 +967,16 @@ export const getOrUndefined: { * * **Example** (Getting services unsafely) * - * ```ts - * import { Context } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Context, Option } from "effect" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") * * const context = Context.make(Port, { PORT: 8080 }) * - * assert.deepStrictEqual(Context.getUnsafe(context, Port), { PORT: 8080 }) - * assert.throws(() => Context.getUnsafe(context, Timeout)) + * Context.getUnsafe(context, Port).PORT // => 8080 + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link get} for type-checked service access @@ -885,11 +991,12 @@ export const getUnsafe: { } = dual( 2, (self: Context, service: Key): S => { - if (!self.mapUnsafe.has(service.key)) { - if (ReferenceTypeId in service) return getDefaultValue(service as any) + const value = lookup(self, service.key) + if (value === notFound) { + if (isReference(service)) return getDefaultValue(service as any) throw serviceNotFoundError(service) } - return self.mapUnsafe.get(service.key)! as any + return value as any } ) @@ -903,9 +1010,8 @@ export const getUnsafe: { * * **Example** (Getting a service from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -915,7 +1021,7 @@ export const getUnsafe: { * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) + * Context.get(context, Timeout).TIMEOUT // => 5000 * ``` * * @see {@link getOption} for optional service access @@ -925,58 +1031,11 @@ export const getUnsafe: { * @since 2.0.0 */ export const get: { + (service: Key): (self: Context) => S (service: Key): (self: Context) => S (self: Context, service: Key): S } = getUnsafe -/** - * Gets the value for a `Context.Reference`, returning its cached default when - * the context does not contain an override. - * - * **When to use** - * - * Use when you need a `Context.Reference` value resolved from either a stored - * override or the reference's default value. - * - * **Details** - * - * Stored overrides take precedence. If no override is present, the reference's - * default value is computed lazily and cached on the reference itself. - * - * **Gotchas** - * - * Mutable default values can be shared across contexts unless an override is - * provided, because the default is cached on the `Context.Reference`. - * - * **Example** (Getting reference defaults unsafely) - * - * ```ts - * import { Context } from "effect" - * - * const LoggerRef = Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) - * }) - * - * const context = Context.empty() - * const logger = Context.getReferenceUnsafe(context, LoggerRef) - * - * console.log(typeof logger.log) // "function" - * ``` - * - * @see {@link getUnsafe} for unsafe access with any service key - * @see {@link get} for type-checked reference-aware access - * @see {@link getOption} for optional access to non-reference keys - * - * @category unsafe - * @since 4.0.0 - */ -export const getReferenceUnsafe = (self: Context, service: Reference): S => { - if (!self.mapUnsafe.has(service.key)) { - return getDefaultValue(service as any) - } - return self.mapUnsafe.get(service.key)! as any -} - const defaultValueCacheKey = "~effect/Context/defaultValue" as const const getDefaultValue = (ref: Reference) => { @@ -990,15 +1049,6 @@ const serviceNotFoundError = (service: Key) => { const error = new Error( `Service not found${service.key ? `: ${String(service.key)}` : ""}` ) - if (service.stack) { - const lines = service.stack.split("\n") - if (lines.length > 2) { - const afterAt = lines[2].match(/at (.*)/) - if (afterAt) { - error.message = error.message + ` (defined at ${afterAt[1]})` - } - } - } if (error.stack) { const lines = error.stack.split("\n") lines.splice(1, 3) @@ -1023,20 +1073,16 @@ const serviceNotFoundError = (service: Key) => { * * **Example** (Getting optional services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") * * const context = Context.make(Port, { PORT: 8080 }) * - * assert.deepStrictEqual( - * Context.getOption(context, Port), - * Option.some({ PORT: 8080 }) - * ) - * assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none()) + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link getOrElse} for returning a fallback value directly @@ -1048,9 +1094,8 @@ export const getOption: { (service: Key): (self: Context) => Option.Option (self: Context, service: Key): Option.Option } = dual(2, (self: Context, service: Key): Option.Option => { - if (self.mapUnsafe.has(service.key)) { - return Option.some(self.mapUnsafe.get(service.key)! as any) - } + const value = lookup(self, service.key) + if (value !== notFound) return Option.some(value as any) return isReference(service) ? Option.some(getDefaultValue(service as any)) : Option.none() }) @@ -1068,9 +1113,8 @@ export const getOption: { * * **Example** (Merging two contexts) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1080,8 +1124,8 @@ export const getOption: { * * const context = Context.merge(firstContext, secondContext) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) + * const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT] + * values // => [8080, 5000] * ``` * * @see {@link mergeAll} for merging more than two contexts at once @@ -1095,9 +1139,7 @@ export const merge: { } = dual(2, (self: Context, that: Context): Context => { if (self.mapUnsafe.size === 0) return that as any if (that.mapUnsafe.size === 0) return self as any - return withMapUnsafe(self, (map) => { - that.mapUnsafe.forEach((value, key) => map.set(key, value)) - }) + return withFlat(self, (map) => that.mapUnsafe.forEach((value, key) => map.set(key, value))) }) /** @@ -1114,9 +1156,8 @@ export const merge: { * * **Example** (Merging multiple contexts) * - * ```ts + * ```ts import.meta.vitest * import { Context } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1132,9 +1173,7 @@ export const merge: { * thirdContext * ) * - * assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 }) - * assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 }) - * assert.deepStrictEqual(Context.get(context, Host), { HOST: "localhost" }) + * context.mapUnsafe.size // => 3 * ``` * * @see {@link merge} for merging two contexts @@ -1163,9 +1202,8 @@ export const mergeAll = >( * * **Example** (Picking services from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1177,11 +1215,8 @@ export const mergeAll = >( * * const context = pipe(someContext, Context.pick(Port)) * - * assert.deepStrictEqual( - * Context.getOption(context, Port), - * Option.some({ PORT: 8080 }) - * ) - * assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none()) + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link omit} for removing selected services @@ -1192,14 +1227,13 @@ export const mergeAll = >( export const pick = >>( ...services: S ) => -(self: Context): Context> => - withMapUnsafe(self, (map) => { - const keySet = new Set(services.map((key) => key.key)) +(self: Context): Context> => { + const keep = new Set(services.map((key) => key.key)) + return withFlat(self, (map) => map.forEach((_, key) => { - if (keySet.has(key)) return - map.delete(key) - }) - }) + if (!keep.has(key)) map.delete(key) + })) +} /** * Returns a new `Context` with the specified service keys removed. @@ -1210,9 +1244,8 @@ export const pick = >>( * * **Example** (Omitting services from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Option, pipe } from "effect" - * import * as assert from "node:assert" * * const Port = Context.Service<{ PORT: number }>("Port") * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout") @@ -1224,11 +1257,8 @@ export const pick = >>( * * const context = pipe(someContext, Context.omit(Timeout)) * - * assert.deepStrictEqual( - * Context.getOption(context, Port), - * Option.some({ PORT: 8080 }) - * ) - * assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none()) + * Context.getOption(context, Port) // => Option.some({ PORT: 8080 }) + * Context.getOption(context, Timeout) // => Option.none() * ``` * * @see {@link pick} for keeping selected services @@ -1240,56 +1270,12 @@ export const omit = >>( ...keys: S ) => (self: Context): Context>> => - withMapUnsafe(self, (map) => { + withFlat(self, (map) => { for (let i = 0; i < keys.length; i++) { map.delete(keys[i].key) } }) -/** - * Performs a series of mutations on a `Context`. Prevents unnecessary copying - * of the underlying map when multiple mutations are needed. - * - * **When to use** - * - * Use to apply several `Context` transformations in one callback while copying - * the underlying service map only once. - * - * @see {@link add} for adding or replacing a service - * @see {@link addOrOmit} for adding or removing a service from an `Option` - * @see {@link merge} for combining two contexts - * @see {@link pick} for keeping selected services - * @see {@link omit} for removing selected services - * - * @category mutations - * @since 4.0.0 - */ -export const mutate: { - ( - f: (context: Context) => Context - ): (self: Context) => Context - (self: Context, f: (context: Context) => Context): Context -} = dual( - 2, - (self: Context, f: (context: Context) => Context): Context => { - const next = makeUnsafe(new Map(self.mapUnsafe)) - next.mutable = true - const result = f(next) - result.mutable = false - return result - } -) - -const withMapUnsafe = (self: Context, f: (map: Map) => void): Context => { - if (self.mutable) { - f(self.mapUnsafe as any) - return self as any - } - const map = new Map(self.mapUnsafe) - f(map) - return makeUnsafe(map) -} - /** * Creates a context key with a default value. * @@ -1308,12 +1294,13 @@ const withMapUnsafe = (self: Context, f: (map: Map = [] * const LoggerRef = Context.Reference("Logger", { - * defaultValue: () => ({ log: (msg: string) => console.log(msg) }) + * defaultValue: () => ({ log: (msg: string) => messages.push(`Default: ${msg}`) }) * }) * * // The reference provides the default value when accessed from an empty context @@ -1322,17 +1309,24 @@ const withMapUnsafe = (self: Context, f: (map: Map `Custom: ${msg}` + * log: (msg: string) => messages.push(`Custom: ${msg}`) * }) * const customLogger = Context.get(customContext, LoggerRef) + * logger.log("default") + * customLogger.log("message") + * messages // => ["Default: default", "Custom: message"] * ``` * * @see {@link Service} for required services without default values * - * @category references + * @category services * @since 3.11.0 */ export const Reference: ( key: string, - options: { readonly defaultValue: () => Service } + options: { + readonly defaultValue: () => Service + /** @internal */ + readonly fiberCached?: boolean | undefined + } ) => Reference = Service as any diff --git a/packages/effect/src/Cron.ts b/packages/effect/src/Cron.ts index 4c6b6dc6f..9d921ce56 100644 --- a/packages/effect/src/Cron.ts +++ b/packages/effect/src/Cron.ts @@ -12,7 +12,7 @@ import * as Data from "./Data.ts" import type * as DateTime from "./DateTime.ts" import * as Equal from "./Equal.ts" import * as Equ from "./Equivalence.ts" -import { format } from "./Formatter.ts" +import { format as formatValue } from "./Formatter.ts" import { constVoid, dual, pipe } from "./Function.ts" import * as Hash from "./Hash.ts" import { type Inspectable, NodeInspectSymbol } from "./Inspectable.ts" @@ -25,7 +25,7 @@ import * as Result from "./Result.ts" import * as String from "./String.ts" import type { Mutable } from "./Types.ts" -const TypeId = "~effect/time/Cron" +const TypeId = "~effect/Cron" /** * Represents a cron schedule with time constraints and timezone information. @@ -43,53 +43,21 @@ const TypeId = "~effect/time/Cron" * * **Example** (Creating a cron schedule) * - * ```ts - * import { Cron } from "effect" + * ```ts import.meta.vitest + * import { Cron, DateTime } from "effect" * * // Create a cron that runs at 9 AM on weekdays * const weekdayMorning = Cron.make({ * minutes: [0], * hours: [9], - * days: [ - * 1, - * 2, - * 3, - * 4, - * 5, - * 6, - * 7, - * 8, - * 9, - * 10, - * 11, - * 12, - * 13, - * 14, - * 15, - * 16, - * 17, - * 18, - * 19, - * 20, - * 21, - * 22, - * 23, - * 24, - * 25, - * 26, - * 27, - * 28, - * 29, - * 30, - * 31 - * ], + * days: [], * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - * weekdays: [1, 2, 3, 4, 5] // Monday to Friday + * weekdays: [1, 2, 3, 4, 5], // Monday to Friday + * tz: DateTime.zoneMakeNamedUnsafe("UTC") * }) * * // Check if a date matches the schedule - * const matches = Cron.match(weekdayMorning, new Date("2023-06-05T09:00:00")) - * console.log(matches) // true if it's 9 AM on a weekday + * Cron.match(weekdayMorning, "2023-06-05T09:00:00Z") // => true * ``` * * @see {@link make} for creating a schedule from explicit field constraints @@ -110,6 +78,8 @@ export interface Cron extends Pipeable, Equal.Equal, Inspectable { readonly months: ReadonlySet readonly weekdays: ReadonlySet /** @internal */ + readonly and: boolean + /** @internal */ readonly first: { readonly second: number readonly minute: number @@ -148,8 +118,9 @@ export interface Cron extends Pipeable, Equal.Equal, Inspectable { } function toPojo(cron: Cron): Record { - return { + const out: Record = { tz: cron.tz, + and: cron.and, seconds: Arr.fromIterable(cron.seconds), minutes: Arr.fromIterable(cron.minutes), hours: Arr.fromIterable(cron.hours), @@ -157,6 +128,7 @@ function toPojo(cron: Cron): Record { months: Arr.fromIterable(cron.months), weekdays: Arr.fromIterable(cron.weekdays) } + return out } const CronProto = { @@ -167,6 +139,7 @@ const CronProto = { [Hash.symbol](this: Cron): number { return pipe( Hash.hash(this.tz), + Hash.combine(Hash.hash(this.and)), Hash.combine(Hash.array(Arr.fromIterable(this.seconds))), Hash.combine(Hash.array(Arr.fromIterable(this.minutes))), Hash.combine(Hash.array(Arr.fromIterable(this.hours))), @@ -176,18 +149,10 @@ const CronProto = { ) }, toObject(this: Cron) { - return { - tz: this.tz, - seconds: Arr.fromIterable(this.seconds), - minutes: Arr.fromIterable(this.minutes), - hours: Arr.fromIterable(this.hours), - days: Arr.fromIterable(this.days), - months: Arr.fromIterable(this.months), - weekdays: Arr.fromIterable(this.weekdays) - } + return toPojo(this) }, toString(this: Cron) { - return `Cron(${format(toPojo(this))})` + return `Cron(${formatValue(toPojo(this))})` }, toJSON(this: Cron) { const out = toPojo(this) @@ -217,7 +182,7 @@ const CronProto = { * * **Example** (Checking cron values) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * const cron = Cron.make({ @@ -228,9 +193,9 @@ const CronProto = { * weekdays: [1, 2, 3, 4, 5] * }) * - * console.log(Cron.isCron(cron)) // true - * console.log(Cron.isCron({})) // false - * console.log(Cron.isCron("not a cron")) // false + * Cron.isCron(cron) // => true + * Cron.isCron({}) // => false + * Cron.isCron("not a cron") // => false * ``` * * @see {@link make} for constructing a `Cron` value directly @@ -251,13 +216,19 @@ export const isCron = (u: unknown): u is Cron => hasProperty(u, TypeId) * **Details** * * Constructs a cron schedule by specifying which seconds, minutes, hours, - * days, months, and weekdays the schedule should match. Empty arrays mean - * "match all" for that time unit. + * days, months, and weekdays the schedule should match. Empty arrays leave a + * time unit unrestricted. If only days or weekdays are restricted, that field + * must match. When both are restricted, the default matches either field; set + * `and: true` to require both fields to match. Weekdays range from `0` (Sunday) + * to `7` (also Sunday). The constructor throws a `RangeError` when a field + * contains a non-integer or out-of-range value. * * **Example** (Creating schedules from constraints) * - * ```ts - * import { Cron } from "effect" + * ```ts import.meta.vitest + * import { Cron, DateTime } from "effect" + * + * const utc = DateTime.zoneMakeNamedUnsafe("UTC") * * // Every day at midnight * const midnight = Cron.make({ @@ -297,49 +268,22 @@ export const isCron = (u: unknown): u is Cron => hasProperty(u, TypeId) * 31 * ], * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - * weekdays: [0, 1, 2, 3, 4, 5, 6] + * weekdays: [0, 1, 2, 3, 4, 5, 6], + * tz: utc * }) * * // Every 15 minutes during business hours on weekdays * const businessHours = Cron.make({ * minutes: [0, 15, 30, 45], * hours: [9, 10, 11, 12, 13, 14, 15, 16, 17], - * days: [ - * 1, - * 2, - * 3, - * 4, - * 5, - * 6, - * 7, - * 8, - * 9, - * 10, - * 11, - * 12, - * 13, - * 14, - * 15, - * 16, - * 17, - * 18, - * 19, - * 20, - * 21, - * 22, - * 23, - * 24, - * 25, - * 26, - * 27, - * 28, - * 29, - * 30, - * 31 - * ], + * days: [], * months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - * weekdays: [1, 2, 3, 4, 5] // Monday to Friday + * weekdays: [1, 2, 3, 4, 5], // Monday to Friday + * tz: utc * }) + * + * Cron.match(midnight, "2024-01-01T00:00:00Z") // => true + * Cron.match(businessHours, "2024-01-01T09:15:00Z") // => true * ``` * * @see {@link parse} for building a schedule from a cron expression string @@ -354,15 +298,17 @@ export const make = (values: { readonly days: Iterable readonly months: Iterable readonly weekdays: Iterable + readonly and?: boolean | undefined readonly tz?: DateTime.TimeZone | undefined }): Cron => { const o: Mutable = Object.create(CronProto) - o.seconds = new Set(Arr.sort(values.seconds ?? [0], N.Order)) - o.minutes = new Set(Arr.sort(values.minutes, N.Order)) - o.hours = new Set(Arr.sort(values.hours, N.Order)) - o.days = new Set(Arr.sort(values.days, N.Order)) - o.months = new Set(Arr.sort(values.months, N.Order)) - o.weekdays = new Set(Arr.sort(values.weekdays, N.Order)) + o.seconds = restrictions.seconds(values.seconds ?? [0]) + o.minutes = restrictions.minutes(values.minutes) + o.hours = restrictions.hours(values.hours) + o.days = restrictions.days(values.days) + o.months = restrictions.months(values.months) + o.weekdays = restrictions.weekdays(values.weekdays) + o.and = values.and === true o.tz = Option.fromUndefinedOr(values.tz) const seconds = Array.from(o.seconds) @@ -391,31 +337,57 @@ export const make = (values: { } o.next = { - second: lookupTable(seconds, 60, "next"), - minute: lookupTable(minutes, 60, "next"), - hour: lookupTable(hours, 24, "next"), - day: lookupTable(days, 32, "next"), - month: lookupTable(months, 13, "next"), - weekday: lookupTable(weekdays, 7, "next") + second: lookup.next.second(seconds), + minute: lookup.next.minute(minutes), + hour: lookup.next.hour(hours), + day: lookup.next.day(days), + month: lookup.next.month(months), + weekday: lookup.next.weekday(weekdays) } o.prev = { - second: lookupTable(seconds, 60, "prev"), - minute: lookupTable(minutes, 60, "prev"), - hour: lookupTable(hours, 24, "prev"), - day: lookupTable(days, 32, "prev"), - month: lookupTable(months, 13, "prev"), - weekday: lookupTable(weekdays, 7, "prev") + second: lookup.prev.second(seconds), + minute: lookup.prev.minute(minutes), + hour: lookup.prev.hour(hours), + day: lookup.prev.day(days), + month: lookup.prev.month(months), + weekday: lookup.prev.weekday(weekdays) } return o } -const lookupTable = ( - values: ReadonlyArray, +const makeRestrictions = ( + field: string, + min: number, + max: number, + normalize: (value: number) => number = (value) => value +): (values: Iterable) => Set => +(values) => { + const restrictions: Array = [] + for (const value of values) { + if (!Number.isInteger(value) || value < min || value > max) { + throw new RangeError(`${field} must contain only integers between ${min} and ${max}`) + } + restrictions.push(normalize(value)) + } + return new Set(Arr.sort(restrictions, N.Order)) +} + +const restrictions = { + seconds: makeRestrictions("seconds", 0, 59), + minutes: makeRestrictions("minutes", 0, 59), + hours: makeRestrictions("hours", 0, 23), + days: makeRestrictions("days", 1, 31), + months: makeRestrictions("months", 1, 12), + weekdays: makeRestrictions("weekdays", 0, 7, (value) => value === 7 ? 0 : value) +} + +const makeLookupTable = ( size: number, dir: "next" | "prev" -): Array => { +): (values: ReadonlyArray) => Array => +(values) => { const result = new Array(size).fill(undefined) if (values.length === 0) { return result @@ -444,7 +416,26 @@ const lookupTable = ( return result } -const CronParseErrorTypeId = "~effect/time/Cron/CronParseError" +const lookup = { + prev: { + second: makeLookupTable(60, "prev"), + minute: makeLookupTable(60, "prev"), + hour: makeLookupTable(24, "prev"), + day: makeLookupTable(32, "prev"), + month: makeLookupTable(13, "prev"), + weekday: makeLookupTable(7, "prev") + }, + next: { + second: makeLookupTable(60, "next"), + minute: makeLookupTable(60, "next"), + hour: makeLookupTable(24, "next"), + day: makeLookupTable(32, "next"), + month: makeLookupTable(13, "next"), + weekday: makeLookupTable(7, "next") + } +} + +const CronParseErrorTypeId = "~effect/Cron/CronParseError" /** * Represents an error that occurs when parsing a cron expression fails. @@ -460,21 +451,21 @@ const CronParseErrorTypeId = "~effect/time/Cron/CronParseError" * * **Example** (Handling cron parse failures) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const result = Cron.parse("invalid expression") - * if (Result.isFailure(result)) { - * const error: Cron.CronParseError = result.failure - * console.log(error.message) // "Invalid number of segments in cron expression" - * console.log(error.input) // "invalid expression" - * } + * const expected = Result.fail(new Cron.CronParseError({ + * message: "Invalid number of segments in cron expression", + * input: "invalid expression" + * })) + * + * Cron.parse("invalid expression") // => expected * ``` * * @see {@link parse} for the parser that returns this error in `Result.fail` * @see {@link isCronParseError} for narrowing unknown values to this error type * - * @category models + * @category errors * @since 4.0.0 */ export class CronParseError extends Data.TaggedError("CronParseError")<{ @@ -499,17 +490,12 @@ export class CronParseError extends Data.TaggedError("CronParseError")<{ * * **Example** (Checking cron parse errors) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const result = Cron.parse("invalid cron expression") - * if (Result.isFailure(result)) { - * const error = result.failure - * console.log(Cron.isCronParseError(error)) // true - * } - * - * console.log(Cron.isCronParseError(new Error("regular error"))) // false - * console.log(Cron.isCronParseError("not an error")) // false + * Result.mapError(Cron.parse("invalid cron expression"), Cron.isCronParseError) // => Result.fail(true) + * Cron.isCronParseError(new Error("regular error")) // => false + * Cron.isCronParseError("not an error") // => false * ``` * * @see {@link CronParseError} for the parse error type @@ -534,26 +520,20 @@ export const isCronParseError = (u: unknown): u is CronParseError => hasProperty * The expression may contain five fields, where seconds default to `0`, or six * fields including seconds. Fields support `*`, comma-separated values, ranges, * steps, and month or weekday aliases. Invalid expressions fail with - * `CronParseError`. + * `CronParseError`. When both the day-of-month and weekday fields are + * restricted, a date matches if either field matches. When either field starts + * with `*`, both fields must match; an unrestricted field always matches. * * **Example** (Parsing cron expressions) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" - * import * as assert from "node:assert" * * // At 04:00 on every day-of-month from 8 through 14. - * assert.deepStrictEqual( - * Cron.parse("0 0 4 8-14 * *"), - * Result.succeed(Cron.make({ - * seconds: [0], - * minutes: [0], - * hours: [4], - * days: [8, 9, 10, 11, 12, 13, 14], - * months: [], - * weekdays: [] - * })) - * ) + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *")) + * + * Array.from(cron.hours) // => [4] + * Array.from(cron.days) // => [8, 9, 10, 11, 12, 13, 14] * ``` * * @see {@link parseUnsafe} for throwing on invalid cron expressions @@ -563,7 +543,7 @@ export const isCronParseError = (u: unknown): u is CronParseError => hasProperty * @since 2.0.0 */ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Result => { - const segments = cron.split(" ").filter(String.isNonEmpty) + const segments = cron.trim().split(/\s+/).filter(String.isNonEmpty) if (segments.length !== 5 && segments.length !== 6) { return Result.fail(new CronParseError({ message: `Invalid number of segments in cron expression`, input: cron })) } @@ -588,7 +568,18 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Res days: parseSegment(days, dayOptions), months: parseSegment(months, monthOptions), weekdays: parseSegment(weekdays, weekdayOptions) - }).pipe(Result.map(make)) + }).pipe(Result.map(({ tz, seconds, minutes, hours, days, months, weekdays }) => + make({ + tz, + seconds: seconds.values, + minutes: minutes.values, + hours: hours.values, + days: days.values, + months: months.values, + weekdays: weekdays.values, + and: (days.wildcard || weekdays.wildcard) && days.values.size !== 0 && weekdays.values.size !== 0 + }) + )) } /** @@ -601,17 +592,19 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Res * * **Example** (Parsing cron expressions unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * // At 04:00 on every day-of-month from 8 through 14 - * const cron = Cron.parseUnsafe("0 0 4 8-14 * *") + * const cron = Cron.parseUnsafe("0 0 4 8-14 * *", "UTC") * * // With timezone * const cronWithTz = Cron.parseUnsafe("0 0 9 * * *", "America/New_York") * * // This would throw an error * // const invalid = Cron.parseUnsafe("invalid expression") + * Cron.match(cron, "2024-01-10T04:00:00Z") // => true + * Cron.match(cronWithTz, "2024-01-01T14:00:00Z") // => true * ``` * * @category constructors @@ -619,6 +612,69 @@ export const parse = (cron: string, tz?: DateTime.TimeZone | string): Result.Res */ export const parseUnsafe = (cron: string, tz?: DateTime.TimeZone | string): Cron => Result.getOrThrow(parse(cron, tz)) +/** + * Formats a `Cron` instance as a cron expression. + * + * **Details** + * + * The default seconds field (`0`) is omitted unless `includeSeconds` is `true`. + * Other seconds configurations are always included. + * + * **Gotchas** + * + * Formatting drops the timezone information and the `and` restriction between + * days and weekdays. Parsing the result is therefore not guaranteed to produce + * an equivalent schedule. + * + * **Example** (Formatting a cron expression) + * + * ```ts import.meta.vitest + * import { Cron } from "effect" + * + * const cron = Cron.parseUnsafe("23 0-20/2 * * 0", "UTC") + * + * Cron.format(cron) // => "23 0-20/2 * * 0" + * Cron.format(cron, { includeSeconds: true }) // => "0 23 0-20/2 * * 0" + * ``` + * + * @category getters + * @since 4.0.0 + */ +export const format = (cron: Cron, options?: { + readonly includeSeconds?: boolean | undefined +}): string => { + const segments = [cron.seconds, cron.minutes, cron.hours, cron.days, cron.months, cron.weekdays] + .map(formatSegment) + return ( + options?.includeSeconds !== true && cron.seconds.size === 1 && cron.seconds.has(0) ? segments.slice(1) : segments + ).join(" ") +} + +const formatSegment = (values: ReadonlySet): string => { + if (values.size === 0) { + return "*" + } + const array = Array.from(values) + const segments: Array = [] + let index = 0 + while (index < array.length) { + const start = array[index]! + const step = array[index + 1]! - start + if (index + 2 < array.length && array[index + 2]! - array[index + 1]! === step) { + let end = index + 2 + while (end + 1 < array.length && array[end + 1]! - array[end]! === step) { + end++ + } + segments.push(`${start}-${array[end]}${step === 1 ? "" : `/${step}`}`) + index = end + 1 + } else { + segments.push(`${start}`) + index++ + } + } + return segments.join(",") +} + /** * Returns `true` when a date/time matches a `Cron` schedule. * @@ -628,27 +684,25 @@ export const parseUnsafe = (cron: string, tz?: DateTime.TimeZone | string): Cron * * **Details** * - * Seconds, minutes, hours, months, and the optional timezone are checked - * directly. For day constraints, an empty `days` or `weekdays` set means that - * field matches every value; when both sets are non-empty, a date matches if - * either the day-of-month or weekday matches. + * The schedule's timezone determines which calendar fields are read from the + * input; the host system's timezone is used when the schedule has no timezone. + * Seconds, minutes, hours, and months are checked against their restrictions; + * an empty set leaves that field unrestricted. If only `days` or `weekdays` is + * restricted, that field must match. If both are restricted, either may match + * unless the schedule was created with `and: true`, which requires both to + * match. * * **Example** (Matching dates against a schedule) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *")) + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *", "UTC")) * * // Check if specific dates match - * const matches1 = Cron.match(cron, new Date("2021-01-08T04:00:00Z")) - * console.log(matches1) // true - 4 AM on the 8th - * - * const matches2 = Cron.match(cron, new Date("2021-01-08T05:00:00Z")) - * console.log(matches2) // false - wrong hour - * - * const matches3 = Cron.match(cron, new Date("2021-01-07T04:00:00Z")) - * console.log(matches3) // false - wrong day + * Cron.match(cron, "2021-01-08T04:00:00Z") // => true + * Cron.match(cron, "2021-01-08T05:00:00Z") // => false + * Cron.match(cron, "2021-01-07T04:00:00Z") // => false * ``` * * @see {@link next} for finding the next matching date/time @@ -682,6 +736,11 @@ export const match = (cron: Cron, date: DateTime.DateTime.Input): boolean => { return true } + if (cron.and) { + return (cron.days.size === 0 || cron.days.has(parts.day)) && + (cron.weekdays.size === 0 || cron.weekdays.has(parts.weekDay)) + } + if (cron.weekdays.size === 0) { return cron.days.has(parts.day) } @@ -712,19 +771,13 @@ const daysInMonth = (date: Date): number => * * **Example** (Finding the next occurrence) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *")) + * const cron = Result.getOrThrow(Cron.parse("0 0 4 8-14 * *", "UTC")) * * // Get next run after a specific date - * const after = new Date("2021-01-01T00:00:00Z") - * const nextRun = Cron.next(cron, after) - * console.log(nextRun) // 2021-01-08T04:00:00.000Z - * - * // Get next run from current time - * const nextFromNow = Cron.next(cron) - * console.log(nextFromNow) // Next occurrence from now + * Cron.next(cron, "2021-01-01T00:00:00Z").toISOString() // => "2021-01-08T04:00:00.000Z" * ``` * * @see {@link prev} for finding the previous scheduled occurrence @@ -843,46 +896,69 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio } if (cron.weekdays.size !== 0 || cron.days.size !== 0) { - let a: number = reverse ? -Infinity : Infinity - let b: number = reverse ? -Infinity : Infinity - - if (cron.weekdays.size !== 0) { - const currentWeekday = current.getUTCDay() - const nextWeekday = table.weekday[currentWeekday] - if (nextWeekday === undefined) { - a = reverse ? - currentWeekday - 7 + boundary.weekday : - 7 - currentWeekday + boundary.weekday - } else { - a = nextWeekday - currentWeekday + if (cron.and) { + const matchesDay = cron.days.size === 0 || cron.days.has(current.getUTCDate()) + const matchesWeekday = cron.weekdays.size === 0 || cron.weekdays.has(current.getUTCDay()) + if (!matchesDay || !matchesWeekday) { + current.setUTCDate(current.getUTCDate() + tick) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue } - } - - if (cron.days.size !== 0 && a !== 0) { - const currentDay = current.getUTCDate() - const nextDay = table.day[currentDay] - if (nextDay === undefined) { - if (reverse) { - const prevMonthDays = daysInMonth(new Date(Date.UTC(current.getUTCFullYear(), current.getUTCMonth(), 0))) - b = -(currentDay + (prevMonthDays - boundary.day)) + } else { + let a: number = reverse ? -Infinity : Infinity + let b: number = reverse ? -Infinity : Infinity + + if (cron.weekdays.size !== 0) { + const currentWeekday = current.getUTCDay() + const nextWeekday = table.weekday[currentWeekday] + if (nextWeekday === undefined) { + a = reverse ? + boundary.weekday - 7 - currentWeekday : + 7 - currentWeekday + boundary.weekday } else { + a = nextWeekday - currentWeekday + } + } + + if (cron.days.size !== 0 && a !== 0) { + const currentDay = current.getUTCDate() + const nextDay = table.day[currentDay] + if (nextDay === undefined) { + if (reverse) { + const previous = new Date(current) + // Day zero is the previous month's last day. These two probes cover every + // valid day-of-month. + previous.setUTCDate(0) + let day = table.day[previous.getUTCDate()] + if (day === undefined) { + previous.setUTCDate(0) + day = table.day[previous.getUTCDate()] + } + if (day === undefined) { + throw new Error("Unable to find cron date") + } + previous.setUTCDate(day) + b = (previous.getTime() - current.getTime()) / 86_400_000 + } else { + b = daysInMonth(current) - currentDay + boundary.day + } + } else if (!reverse && nextDay > daysInMonth(current)) { + // The next matching day does not exist in the current month. Setting it + // directly would overflow and skip earlier matching days next month. b = daysInMonth(current) - currentDay + boundary.day + } else { + b = nextDay - currentDay } - } else if (!reverse && nextDay > daysInMonth(current)) { - // The next matching day does not exist in the current month. Setting it - // directly would overflow and skip earlier matching days next month. - b = daysInMonth(current) - currentDay + boundary.day - } else { - b = nextDay - currentDay } - } - const addDays = reverse ? Math.max(a, b) : Math.min(a, b) - if (addDays !== 0) { - current.setUTCDate(current.getUTCDate() + addDays) - current.setUTCHours(boundary.hour, boundary.minute, boundary.second) - adjustDst(current) - continue + const addDays = reverse ? Math.max(a, b) : Math.min(a, b) + if (addDays !== 0) { + current.setUTCDate(current.getUTCDate() + addDays) + current.setUTCHours(boundary.hour, boundary.minute, boundary.second) + adjustDst(current) + continue + } } } @@ -890,11 +966,11 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio const currentMonth = current.getUTCMonth() + 1 const nextMonth = table.month[currentMonth] const clampBoundaryDay = (targetMonthIndex: number): number => { - if (cron.days.size !== 0) { - return boundary.day - } const maxDayInMonth = daysInMonth(new Date(Date.UTC(current.getUTCFullYear(), targetMonthIndex + 1, 0))) - return Math.min(boundary.day, maxDayInMonth) + if (cron.days.size !== 0 && cron.weekdays.size === 0) { + return reverse ? table.day[maxDayInMonth] ?? maxDayInMonth : boundary.day + } + return reverse ? maxDayInMonth : 1 } if (nextMonth === undefined) { current.setUTCFullYear(current.getUTCFullYear() + tick) @@ -915,7 +991,7 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio return } - throw new Error("Unable to find " + direction + " cron date") + throw new Error("Unable to find cron date") }) return dateTime.toDateUtc(result) @@ -936,17 +1012,23 @@ const stepCron = (cron: Cron, now: DateTime.DateTime.Input | undefined, directio * * **Example** (Iterating scheduled occurrences) * - * ```ts + * ```ts import.meta.vitest * import { Cron, Result } from "effect" * - * const cron = Result.getOrThrow(Cron.parse("0 0 9 * * 1-5")) // 9 AM weekdays + * const cron = Result.getOrThrow(Cron.parse("0 0 9 * * 1-5", "UTC")) // 9 AM weekdays * * // Get first 5 occurrences - * const iterator = Cron.sequence(cron, new Date("2023-01-01")) - * const next5 = Array.from({ length: 5 }, () => iterator.next().value) - * - * console.log(next5) - * // [Mon Jan 02 2023 09:00:00, Tue Jan 03 2023 09:00:00, ...] + * const iterator = Cron.sequence(cron, "2023-01-01T00:00:00Z") + * const next5 = Array.from({ length: 5 }, () => iterator.next().value.toISOString()) + * const expected = [ + * "2023-01-02T09:00:00.000Z", + * "2023-01-03T09:00:00.000Z", + * "2023-01-04T09:00:00.000Z", + * "2023-01-05T09:00:00.000Z", + * "2023-01-06T09:00:00.000Z" + * ] + * + * next5 // => expected * ``` * * @see {@link next} for computing one next occurrence @@ -961,8 +1043,8 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It } /** - * Equivalence instance for comparing the field restrictions of two `Cron` - * schedules. + * Equivalence instance for comparing the timezone, field restrictions, and + * day-matching mode of two `Cron` schedules. * * **When to use** * @@ -971,12 +1053,12 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It * * **Details** * - * This comparison checks seconds, minutes, hours, days, months, and weekdays. - * It does not compare the optional timezone. + * This comparison checks the optional timezone, the `and` day-matching mode, + * seconds, minutes, hours, days, months, and weekdays. * * **Example** (Comparing schedules with equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * const cron1 = Cron.make({ @@ -995,7 +1077,7 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It * weekdays: [1, 2, 3, 4, 5] * }) * - * console.log(Cron.Equivalence(cron1, cron2)) // true + * Cron.Equivalence(cron1, cron2) // => true * ``` * * @see {@link equals} for directly comparing two `Cron` values @@ -1004,6 +1086,8 @@ export const sequence = function*(cron: Cron, now?: DateTime.DateTime.Input): It * @since 2.0.0 */ export const Equivalence: Equ.Equivalence = Equ.make((self, that) => + Equal.equals(self.tz, that.tz) && + self.and === that.and && restrictionsEquals(self.seconds, that.seconds) && restrictionsEquals(self.minutes, that.minutes) && restrictionsEquals(self.hours, that.hours) && @@ -1017,21 +1101,22 @@ const restrictionsEquals = (self: ReadonlySet, that: ReadonlySet restrictionsArrayEquals(Arr.fromIterable(self), Arr.fromIterable(that)) /** - * Checks whether two `Cron` instances have the same field restrictions. + * Checks whether two `Cron` instances have equal timezone values, field + * restrictions, and day-matching modes. * * **When to use** * - * Use to directly compare whether two cron schedules have the same field - * restrictions. + * Use to directly compare two cron schedules, including their timezones and + * day-matching modes. * * **Details** * - * The comparison checks seconds, minutes, hours, days, months, and weekdays. - * It does not compare the optional timezone. + * The comparison checks the optional timezone, the `and` day-matching mode, + * seconds, minutes, hours, days, months, and weekdays. * * **Example** (Checking schedule equality) * - * ```ts + * ```ts import.meta.vitest * import { Cron } from "effect" * * const cron1 = Cron.make({ @@ -1050,8 +1135,8 @@ const restrictionsEquals = (self: ReadonlySet, that: ReadonlySet * weekdays: [1, 2, 3, 4, 5] * }) * - * console.log(Cron.equals(cron1, cron2)) // true - * console.log(Cron.equals(cron1)(cron2)) // true (curried form) + * Cron.equals(cron1, cron2) // => true + * Cron.equals(cron1)(cron2) // => true * ``` * * @see {@link Equivalence} for the reusable equivalence instance @@ -1068,6 +1153,12 @@ interface SegmentOptions { min: number max: number aliases?: Record | undefined + normalize?: ((value: number) => number) | undefined +} + +interface ParsedSegment { + readonly values: Set + readonly wildcard: boolean } const secondOptions: SegmentOptions = { @@ -1111,7 +1202,8 @@ const monthOptions: SegmentOptions = { const weekdayOptions: SegmentOptions = { min: 0, - max: 6, + max: 7, + normalize: (value) => value === 7 ? 0 : value, aliases: { sun: 0, mon: 1, @@ -1126,17 +1218,21 @@ const weekdayOptions: SegmentOptions = { const parseSegment = ( input: string, options: SegmentOptions -): Result.Result, CronParseError> => { - const capacity = options.max - options.min + 1 +): Result.Result => { const values = new Set() const fields = input.split(",") - - for (const field of fields) { - const [raw, step] = splitStep(field) - if (raw === "*" && step === undefined) { - return Result.succeed(new Set()) + const first = splitStep(fields[0]!) + const wildcard = first[0] === "*" + const normalize = options.normalize ?? ((value: number) => value) + const add = wildcard && (first[1] === undefined || first[1] === 1) ? + constVoid : + (value: number) => { + values.add(normalize(value)) } + for (let index = 0; index < fields.length; index++) { + const field = fields[index]! + const [raw, step] = index === 0 ? first : splitStep(field) if (step !== undefined) { if (!Number.isInteger(step)) { return Result.fail(new CronParseError({ message: `Expected step value to be a positive integer`, input })) @@ -1145,13 +1241,18 @@ const parseSegment = ( return Result.fail(new CronParseError({ message: `Expected step value to be greater than 0`, input })) } if (step > options.max) { - return Result.fail(new CronParseError({ message: `Expected step value to be less than ${options.max}`, input })) + return Result.fail( + new CronParseError({ message: `Expected step value to be less than or equal to ${options.max}`, input }) + ) } } if (raw === "*") { + if (index === 0 && (step === undefined || step === 1)) { + continue + } for (let i = options.min; i <= options.max; i += step ?? 1) { - values.add(i) + add(i) } } else { const [left, right] = splitRange(raw, options.aliases) @@ -1165,7 +1266,9 @@ const parseSegment = ( } if (right === undefined) { - values.add(left) + for (let i = left; i <= (step === undefined ? left : options.max); i += step ?? 1) { + add(i) + } } else { if (!Number.isInteger(right)) { return Result.fail(new CronParseError({ message: `Expected a positive integer`, input })) @@ -1180,23 +1283,20 @@ const parseSegment = ( } for (let i = left; i <= right; i += step ?? 1) { - values.add(i) + add(i) } } } - - if (values.size >= capacity) { - return Result.succeed(new Set()) - } } - return Result.succeed(values) + return Result.succeed({ values, wildcard }) } const splitStep = (input: string): [string, number | undefined] => { const separator = input.indexOf("/") if (separator !== -1) { - return [input.slice(0, separator), Number(input.slice(separator + 1))] + const step = input.slice(separator + 1) + return [input.slice(0, separator), decimalRegex.test(step) ? Number(step) : NaN] } return [input, undefined] @@ -1212,5 +1312,7 @@ const splitRange = (input: string, aliases?: Record): [number, n } function aliasOrValue(field: string, aliases?: Record): number { - return aliases?.[field.toLocaleLowerCase()] ?? Number(field) + return aliases?.[String.toLowerCase(field)] ?? (decimalRegex.test(field) ? Number(field) : NaN) } + +const decimalRegex = /^\d+$/ diff --git a/packages/effect/src/Crypto.ts b/packages/effect/src/Crypto.ts index d3cdc018b..8a332c44b 100644 --- a/packages/effect/src/Crypto.ts +++ b/packages/effect/src/Crypto.ts @@ -11,9 +11,11 @@ */ import * as Context from "./Context.ts" import * as Effect from "./Effect.ts" +import * as random from "./internal/random.ts" +import * as Uuid from "./internal/uuid.ts" import * as PlatformError from "./PlatformError.ts" -const TypeId = "~effect/platform/Crypto" +const TypeId = "~effect/Crypto" /** * Digest algorithms supported by the platform `Crypto` service. @@ -25,7 +27,7 @@ const TypeId = "~effect/platform/Crypto" * * **Example** (Using a digest algorithm) * - * ```ts + * ```ts import.meta.vitest * import { Crypto } from "effect" * * const algorithm: Crypto.DigestAlgorithm = "SHA-256" @@ -47,7 +49,7 @@ export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" * * **Example** (Using cryptographic operations) * - * ```ts + * ```ts import.meta.vitest * import { Crypto, Effect, Layer } from "effect" * * const TestCrypto = Layer.succeed( @@ -62,15 +64,14 @@ export type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512" * const crypto = yield* Crypto.Crypto * const bytes = yield* crypto.randomBytes(16) * const uuidv4 = yield* crypto.randomUUIDv4 - * const uuidv7 = yield* crypto.randomUUIDv7 * const hash = yield* crypto.digest("SHA-256", bytes) - * return { uuidv4, uuidv7, hash } + * return [bytes.length, uuidv4.length, hash.length] * }) * - * Effect.runPromise(Effect.provide(program, TestCrypto)) + * await Effect.runPromise(Effect.provide(program, TestCrypto)) // => [16, 36, 16] * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface Crypto { @@ -196,16 +197,15 @@ export const Crypto: Context.Service = Context.Service("effect/C * * **Example** (Creating a Crypto service) * - * ```ts - * import { Crypto, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Crypto, Effect } from "effect" * - * const TestCrypto = Layer.succeed( - * Crypto.Crypto, - * Crypto.make({ - * randomBytes: (size) => new Uint8Array(size), - * digest: (_algorithm, data) => Effect.succeed(data) - * }) - * ) + * const testCrypto = Crypto.make({ + * randomBytes: (size) => new Uint8Array(size), + * digest: (_algorithm, data) => Effect.succeed(data) + * }) + * + * await Effect.runPromise(testCrypto.randomBytes(4)) // => new Uint8Array([0, 0, 0, 0]) * ``` * * @category constructors @@ -224,15 +224,24 @@ export const make = ( const randomBytes: Crypto["randomBytes"] = (size) => Effect.map(validateSize("randomBytes", size), randomBytesUnsafe) - const nextDoubleUnsafe = (): number => { - const bytes = randomBytesUnsafe(7) - const value = ((bytes[0] & 0x1f) * 2 ** 48) + (bytes[1] * 2 ** 40) + (bytes[2] * 2 ** 32) + - (bytes[3] * 2 ** 24) + (bytes[4] * 2 ** 16) + (bytes[5] * 2 ** 8) + bytes[6] - return value / 2 ** 53 - } + const readUint53 = (bytes: Uint8Array): number => + ((bytes[0] & 0x1f) * 2 ** 48) + (bytes[1] * 2 ** 40) + (bytes[2] * 2 ** 32) + + (bytes[3] * 2 ** 24) + (bytes[4] * 2 ** 16) + (bytes[5] * 2 ** 8) + bytes[6] - const nextIntUnsafe = (): number => - Math.floor(nextDoubleUnsafe() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER + 1)) + Number.MIN_SAFE_INTEGER + const nextDoubleUnsafe = (): number => readUint53(randomBytesUnsafe(7)) / 2 ** 53 + + const nextIntUnsafe = (): number => { + while (true) { + const bytes = randomBytesUnsafe(7) + const value = readUint53(bytes) + if ((bytes[0] & 0x20) === 0) { + return value + Number.MIN_SAFE_INTEGER + } + if (value < Number.MAX_SAFE_INTEGER) { + return value + 1 + } + } + } return Crypto.of({ [TypeId]: TypeId, @@ -243,7 +252,7 @@ export const make = ( random: Effect.sync(() => nextDoubleUnsafe()), randomBoolean: Effect.sync(() => nextDoubleUnsafe() > 0.5), randomInt: Effect.sync(() => nextIntUnsafe()), - randomBetween: (min, max) => Effect.sync(() => nextDoubleUnsafe() * (max - min) + min), + randomBetween: (min, max) => Effect.sync(() => random.nextBetween(min, max, nextDoubleUnsafe())), randomIntBetween(min, max, options) { const extra = options?.halfOpen === true ? 0 : 1 return Effect.sync(() => { @@ -263,9 +272,9 @@ export const make = ( } return buffer }), - randomUUIDv4: Effect.sync(() => formatUUIDv4(randomBytesUnsafe(16))), + randomUUIDv4: Effect.sync(() => Uuid.v4String(randomBytesUnsafe(16))), randomUUIDv7: Effect.clockWith((clock) => - Effect.succeed(formatUUIDv7(clock.currentTimeMillisUnsafe(), randomBytesUnsafe(16))) + Effect.succeed(Uuid.v7String(clock.currentTimeMillisUnsafe(), randomBytesUnsafe(16))) ) }) } @@ -278,41 +287,3 @@ const validateSize = (method: string, size: number): Effect.Effect byte.toString(16).padStart(2, "0") - -const formatUUID = (bytes: Uint8Array): string => { - const segments = [ - bytes.subarray(0, 4), - bytes.subarray(4, 6), - bytes.subarray(6, 8), - bytes.subarray(8, 10), - bytes.subarray(10, 16) - ] - - return segments.map((segment) => Array.from(segment, hex).join("")).join("-") -} - -const formatUUIDv4 = (bytes: Uint8Array): string => { - bytes[6] = (bytes[6] & 0x0f) | 0x40 - bytes[8] = (bytes[8] & 0x3f) | 0x80 - - return formatUUID(bytes) -} - -const maxUUIDv7Timestamp = 2 ** 48 - 1 - -const formatUUIDv7 = (timestampMillis: number, bytes: Uint8Array): string => { - const timestamp = Math.min(Math.max(0, Math.trunc(timestampMillis)), maxUUIDv7Timestamp) - - bytes[0] = Math.floor(timestamp / 2 ** 40) - bytes[1] = Math.floor(timestamp / 2 ** 32) & 0xff - bytes[2] = Math.floor(timestamp / 2 ** 24) & 0xff - bytes[3] = Math.floor(timestamp / 2 ** 16) & 0xff - bytes[4] = Math.floor(timestamp / 2 ** 8) & 0xff - bytes[5] = timestamp & 0xff - bytes[6] = (bytes[6] & 0x0f) | 0x70 - bytes[8] = (bytes[8] & 0x3f) | 0x80 - - return formatUUID(bytes) -} diff --git a/packages/effect/src/Data.ts b/packages/effect/src/Data.ts index 206297749..2228bb0e9 100644 --- a/packages/effect/src/Data.ts +++ b/packages/effect/src/Data.ts @@ -10,6 +10,7 @@ */ import type * as Cause from "./Cause.ts" import * as core from "./internal/core.ts" +import * as InternalRecord from "./internal/record.ts" import * as Pipeable from "./Pipeable.ts" import * as Predicate from "./Predicate.ts" import type * as Types from "./Types.ts" @@ -30,16 +31,12 @@ import type { Unify } from "./Unify.ts" * * **Example** (Defining a value class) * - * ```ts + * ```ts import.meta.vitest * import { Data, Equal } from "effect" * * class Person extends Data.Class<{ readonly name: string }> {} * - * const mike1 = new Person({ name: "Mike" }) - * const mike2 = new Person({ name: "Mike" }) - * - * console.log(Equal.equals(mike1, mike2)) - * // true + * Equal.equals(new Person({ name: "Mike" }), new Person({ name: "Mike" })) // => true * ``` * * @see {@link TaggedClass} — adds a `_tag` field @@ -51,10 +48,10 @@ import type { Unify } from "./Unify.ts" export const Class: new = {}>( args: Types.VoidIfEmpty<{ readonly [P in keyof A]: A[P] }> ) => Readonly & Pipeable.Pipeable = class extends Pipeable.Class { - constructor(props: any) { + constructor(props: object | undefined) { super() if (props) { - Object.assign(this, props) + InternalRecord.assignProperties(this, props) } } } as any @@ -74,16 +71,14 @@ export const Class: new = {}>( * * **Example** (Defining a tagged class) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * class Person extends Data.TaggedClass("Person")<{ * readonly name: string * }> {} * - * const mike = new Person({ name: "Mike" }) - * console.log(mike._tag) - * // "Person" + * new Person({ name: "Mike" })._tag // => "Person" * ``` * * @see {@link Class} — without a `_tag` @@ -120,7 +115,7 @@ export const TaggedClass = ( * * **Example** (Defining a tagged enum) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type HttpError = Data.TaggedEnum<{ @@ -134,9 +129,7 @@ export const TaggedClass = ( * * const { BadRequest, NotFound } = Data.taggedEnum() * - * const err = BadRequest({ status: 400, message: "missing id" }) - * console.log(err._tag) - * // "BadRequest" + * BadRequest({ status: 400, message: "missing id" })._tag // => "BadRequest" * ``` * * @see {@link taggedEnum} — constructors and matchers for a `TaggedEnum` @@ -197,7 +190,7 @@ export declare namespace TaggedEnum { * * **Example** (Defining a generic tagged enum) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type MyResult = Data.TaggedEnum<{ @@ -213,6 +206,7 @@ export declare namespace TaggedEnum { * * const ok = Success({ value: 42 }) * // ok: { readonly _tag: "Success"; readonly value: number } + * ok // => { value: 42, _tag: "Success" } * ``` * * @see {@link Kind} — apply concrete types to a `WithGenerics` definition @@ -241,7 +235,7 @@ export declare namespace TaggedEnum { * * **Example** (Applying generics) * - * ```ts + * ```ts import.meta.vitest * import type { Data } from "effect" * * type Option = Data.TaggedEnum<{ @@ -252,9 +246,9 @@ export declare namespace TaggedEnum { * readonly taggedEnum: Option * } * - * // Resolve to the concrete union for `string` - * type StringOption = Data.TaggedEnum.Kind + * // Resolves to the concrete union for `string`: * // { _tag: "None" } | { _tag: "Some"; value: string } + * type StringOption = Data.TaggedEnum.Kind * ``` * * @see {@link WithGenerics} — define the generic shape @@ -290,7 +284,7 @@ export declare namespace TaggedEnum { * * **Example** (Extracting variant args) * - * ```ts + * ```ts import.meta.vitest * import type { Data } from "effect" * * type Result = @@ -327,7 +321,7 @@ export declare namespace TaggedEnum { * * **Example** (Extracting a variant type) * - * ```ts + * ```ts import.meta.vitest * import type { Data } from "effect" * * type Result = @@ -367,7 +361,7 @@ export declare namespace TaggedEnum { * * **Example** (Using the constructor object) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type Shape = @@ -378,21 +372,19 @@ export declare namespace TaggedEnum { * * const shape = Circle({ radius: 10 }) * - * // Type guard * if ($is("Circle")(shape)) { - * console.log(shape.radius) + * shape.radius // => 10 * } * - * // Pattern matching - * const label = $match(shape, { + * $match(shape, { * Circle: (s) => `circle r=${s.radius}`, * Rect: (s) => `rect ${s.w}x${s.h}` - * }) + * }) // => "circle r=10" * ``` * * @see {@link taggedEnum} — creates constructors and matchers * - * @category types + * @category utility types * @since 3.1.0 */ export type Constructor = Types.Simplify< @@ -539,7 +531,7 @@ export declare namespace TaggedEnum { * * **Example** (Creating and matching tagged enum values) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type HttpError = Data.TaggedEnum<{ @@ -551,20 +543,17 @@ export declare namespace TaggedEnum { * * const err = NotFound({ url: "/missing" }) * - * // Type guard - * console.log($is("NotFound")(err)) // true + * $is("NotFound")(err) // => true * - * // Pattern matching - * const msg = $match(err, { + * $match(err, { * BadRequest: (e) => e.message, * NotFound: (e) => `${e.url} not found` - * }) - * console.log(msg) // "/missing not found" + * }) // => "/missing not found" * ``` * * **Example** (Defining a generic tagged enum) * - * ```ts + * ```ts import.meta.vitest * import { Data } from "effect" * * type MyResult = Data.TaggedEnum<{ @@ -578,6 +567,7 @@ export declare namespace TaggedEnum { * * const ok = Success({ value: 42 }) * // ok: { readonly _tag: "Success"; readonly value: number } + * ok // => { value: 42, _tag: "Success" } * ``` * * @see {@link TaggedEnum} — the type-level companion @@ -698,8 +688,8 @@ function taggedMatch< * * **Example** (Defining a yieldable error) * - * ```ts - * import { Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" * * class NetworkError extends Data.Error<{ * readonly code: number @@ -710,8 +700,7 @@ function taggedMatch< * return yield* new NetworkError({ code: 500, message: "timeout" }) * }) * - * // The effect fails with a NetworkError - * Effect.runSync(Effect.exit(program)) + * Effect.runSync(Effect.exit(program)) // => Exit.fail(new NetworkError({ code: 500, message: "timeout" })) * ``` * * @see {@link TaggedError} — adds a `_tag` for `Effect.catchTag` @@ -740,7 +729,7 @@ export const Error: new = {}>( * * **Example** (Recovering by tag) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class NotFound extends Data.TaggedError("NotFound")<{ @@ -759,6 +748,8 @@ export const Error: new = {}>( * Effect.catchTag("NotFound", (e) => * Effect.succeed(`missing: ${e.resource}`)) * ) + * + * await Effect.runPromise(recovered) // => "missing: /users/42" * ``` * * @see {@link Error} — without a `_tag` diff --git a/packages/effect/src/DateTime.ts b/packages/effect/src/DateTime.ts index 36df2d897..c121861a2 100644 --- a/packages/effect/src/DateTime.ts +++ b/packages/effect/src/DateTime.ts @@ -353,21 +353,21 @@ export declare namespace TimeZone { * * **Example** (Resolving ambiguous local times) * - * ```ts - * import { DateTime } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * * // Fall-back example: 01:30 on Nov 2, 2025 in New York happens twice - * const ambiguousTime = { year: 2025, month: 11, day: 2, hours: 1, minutes: 30 } + * const ambiguousTime = { year: 2025, month: 11, day: 2, hour: 1, minute: 30 } * const timeZone = DateTime.zoneMakeNamedUnsafe("America/New_York") * - * DateTime.makeZoned(ambiguousTime, { + * const earlier = DateTime.makeZoned(ambiguousTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "earlier" * }) * // Earlier occurrence (DST time): 2025-11-02T05:30:00.000Z * - * DateTime.makeZoned(ambiguousTime, { + * const later = DateTime.makeZoned(ambiguousTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "later" @@ -375,21 +375,26 @@ export declare namespace TimeZone { * // Later occurrence (standard time): 2025-11-02T06:30:00.000Z * * // Gap example: 02:30 on Mar 9, 2025 in New York doesn't exist - * const gapTime = { year: 2025, month: 3, day: 9, hours: 2, minutes: 30 } + * const gapTime = { year: 2025, month: 3, day: 9, hour: 2, minute: 30 } * - * DateTime.makeZoned(gapTime, { + * const beforeGap = DateTime.makeZoned(gapTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "earlier" * }) * // Time before gap: 2025-03-09T06:30:00.000Z (01:30 EST) * - * DateTime.makeZoned(gapTime, { + * const afterGap = DateTime.makeZoned(gapTime, { * timeZone, * adjustForTimeZone: true, * disambiguation: "later" * }) * // Time after gap: 2025-03-09T07:30:00.000Z (03:30 EDT) + * + * earlier.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-11-02T05:30:00.000Z" + * later.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-11-02T06:30:00.000Z" + * beforeGap.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-03-09T06:30:00.000Z" + * afterGap.pipe(Option.getOrThrow, DateTime.formatIso) // => "2025-03-09T07:30:00.000Z" * ``` * * @category models @@ -510,7 +515,7 @@ export const isZoned: (self: DateTime) => self is Zoned = Internal.isZoned * * **Example** (Comparing DateTime values for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") @@ -518,7 +523,7 @@ export const isZoned: (self: DateTime) => self is Zoned = Internal.isZoned * timeZone: "Europe/London" * }) * - * console.log(DateTime.Equivalence(utc, zoned)) // true + * DateTime.Equivalence(utc, zoned) // => true * ``` * * @category instances @@ -536,7 +541,7 @@ export const Equivalence: Equ.Equivalence = Internal.Equivalence * * **Example** (Sorting DateTime values chronologically) * - * ```ts + * ```ts import.meta.vitest * import { Array, DateTime } from "effect" * * const dates = [ @@ -545,8 +550,7 @@ export const Equivalence: Equ.Equivalence = Internal.Equivalence * DateTime.makeUnsafe("2024-02-01") * ] * - * const sorted = Array.sort(dates, DateTime.Order) - * // Results in chronological order: 2024-01-01, 2024-02-01, 2024-03-01 + * Array.sort(dates, DateTime.Order).map(DateTime.formatIsoDateUtc) // => ["2024-01-01", "2024-02-01", "2024-03-01"] * ``` * * @category instances @@ -565,15 +569,14 @@ export const Order: order.Order = Internal.Order * * **Example** (Clamping DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const min = DateTime.makeUnsafe("2024-01-01") * const max = DateTime.makeUnsafe("2024-12-31") * const date = DateTime.makeUnsafe("2025-06-15") * - * const clamped = DateTime.clamp(date, { minimum: min, maximum: max }) - * // clamped equals max (2024-12-31) + * DateTime.clamp(date, { minimum: min, maximum: max }) // => DateTime.makeUnsafe("2024-12-31") * ``` * * @category ordering @@ -602,13 +605,10 @@ export const clamp: { * * **Example** (Creating DateTime values from Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const date = new Date("2024-01-01T12:00:00Z") - * const dateTime = DateTime.fromDateUnsafe(date) - * - * console.log(DateTime.formatIso(dateTime)) // "2024-01-01T12:00:00.000Z" + * DateTime.fromDateUnsafe(new Date("2024-01-01T12:00:00Z")) // => DateTime.makeUnsafe("2024-01-01T12:00:00Z") * ``` * * @category constructors @@ -634,20 +634,17 @@ export const fromDateUnsafe: (date: Date) => Utc = Internal.fromDateUnsafe * * **Example** (Creating DateTime values unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // from Date - * const fromDate = DateTime.makeUnsafe(new Date("2024-01-01T12:00:00Z")) - * console.log(DateTime.formatIso(fromDate)) // "2024-01-01T12:00:00.000Z" + * DateTime.makeUnsafe(new Date("2024-01-01T12:00:00Z")) // => DateTime.makeUnsafe("2024-01-01T12:00:00Z") * * // from parts - * const fromParts = DateTime.makeUnsafe({ year: 2024 }) - * console.log(DateTime.formatIso(fromParts)) // "2024-01-01T00:00:00.000Z" + * DateTime.makeUnsafe({ year: 2024 }) // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * * // from string - * const fromString = DateTime.makeUnsafe("2024-01-01") - * console.log(DateTime.formatIso(fromString)) // "2024-01-01T00:00:00.000Z" + * DateTime.makeUnsafe("2024-01-01") // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * ``` * * @category constructors @@ -655,6 +652,22 @@ export const fromDateUnsafe: (date: Date) => Utc = Internal.fromDateUnsafe */ export const makeUnsafe: (input: A) => DateTime.PreserveZone = Internal.makeUnsafe +/** + * Creates a `DateTime.Utc` from the number of seconds since the Unix epoch. + * + * **Example** (Creating from epoch seconds) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * DateTime.fromEpochSeconds(1704067200).toJSON() // => "2024-01-01T00:00:00.000Z" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fromEpochSeconds: (seconds: number) => Utc = Internal.fromEpochSeconds + /** * Create a `DateTime.Zoned` using `DateTime.makeUnsafe` and a time zone. * @@ -678,14 +691,14 @@ export const makeUnsafe: (input: A) => DateTime.Preser * * **Example** (Creating zoned DateTime values unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { * timeZone: "Europe/London" * }) * - * console.log(DateTime.formatIsoZoned(zoned)) // "2024-06-15T15:30:00.000+01:00[Europe/London]" + * DateTime.formatIsoZoned(zoned) // => "2024-06-15T15:30:00.000+01:00[Europe/London]" * ``` * * @category constructors @@ -720,17 +733,14 @@ export const makeZonedUnsafe: (input: DateTime.Input, options?: { * * **Example** (Creating optional zoned DateTime values) * - * ```ts - * import { DateTime } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * * const result = DateTime.makeZoned("2024-06-15T14:30:00Z", { * timeZone: "Europe/London" * }) * - * console.log(result._tag) // "Some" - * if (result._tag === "Some") { - * console.log(DateTime.formatIsoZoned(result.value)) // "2024-06-15T15:30:00.000+01:00[Europe/London]" - * } + * result.pipe(Option.map(DateTime.formatIsoZoned)) // => Option.some("2024-06-15T15:30:00.000+01:00[Europe/London]") * ``` * * @category constructors @@ -762,23 +772,19 @@ export const makeZoned: ( * * **Example** (Creating optional DateTime values) * - * ```ts - * import { DateTime } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * * // from Date - * const fromDate = DateTime.make(new Date("2024-01-01T12:00:00Z")) - * console.log(fromDate._tag) // "Some" + * DateTime.make(new Date("2024-01-01T12:00:00Z")) // => Option.some(DateTime.makeUnsafe("2024-01-01T12:00:00Z")) * * // from parts - * const fromParts = DateTime.make({ year: 2024 }) - * console.log(fromParts._tag) // "Some" + * DateTime.make({ year: 2024 }) // => Option.some(DateTime.makeUnsafe("2024-01-01T00:00:00Z")) * * // from string - * const fromString = DateTime.make("2024-01-01") - * console.log(fromString._tag) // "Some" + * DateTime.make("2024-01-01") // => Option.some(DateTime.makeUnsafe("2024-01-01T00:00:00Z")) * - * const invalid = DateTime.make("not a date") - * console.log(invalid._tag) // "None" + * DateTime.make("not a date") // => Option.none() * ``` * * @category constructors @@ -798,19 +804,15 @@ export const make: (input: A) => Option.Option Option.some("2024-01-01T11:00:00.000+01:00[Europe/Berlin]") * - * const invalid = DateTime.makeZonedFromString("invalid") - * console.log(invalid._tag === "None") // true + * DateTime.makeZonedFromString("2024-01-01T12:00:00Z") // => Option.none() + * DateTime.makeZonedFromString("invalid") // => Option.none() * ``` * * @category constructors @@ -819,17 +821,15 @@ export const make: (input: A) => Option.Option Option.Option = Internal.makeZonedFromString /** - * Gets the current time using the `Clock` service and convert it to a `DateTime`. + * Gets the current time using the `Clock` service and converts it to a `DateTime`. * * **Example** (Getting the current DateTime) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * Effect.gen(function*() { - * const now = yield* DateTime.nowAsDate - * console.log(now instanceof Date) // true - * }) + * await Effect.runPromise(Effect.map(DateTime.now, DateTime.isDateTime)) // => true * ``` * * @category constructors @@ -843,12 +843,11 @@ export const now: Effect.Effect = Internal.now * * **Example** (Getting the current Date) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * }) + * await Effect.runPromise(Effect.map(DateTime.nowAsDate, (now) => now instanceof Date)) // => true * ``` * * @category constructors @@ -871,11 +870,10 @@ export const nowAsDate: Effect.Effect = Internal.nowAsDate * * **Example** (Getting the current DateTime unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const now = DateTime.nowUnsafe() - * console.log(DateTime.formatIso(now)) + * Number.isFinite(DateTime.toEpochMillis(DateTime.nowUnsafe())) // => true * ``` * * @category constructors @@ -901,7 +899,7 @@ export const nowUnsafe: LazyArg = Internal.nowUnsafe * * **Example** (Converting DateTime values to UTC) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const now = DateTime.makeZonedUnsafe({ year: 2024 }, { @@ -910,9 +908,10 @@ export const nowUnsafe: LazyArg = Internal.nowUnsafe * * // set as UTC * const utc: DateTime.Utc = DateTime.toUtc(now) + * utc // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * ``` * - * @category time zones + * @category converting * @since 3.13.0 */ export const toUtc: (self: DateTime) => Utc = Internal.toUtc @@ -922,19 +921,16 @@ export const toUtc: (self: DateTime) => Utc = Internal.toUtc * * **Example** (Setting time zones) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") + * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") + * const zoned: DateTime.Zoned = DateTime.setZone(DateTime.makeUnsafe("2024-01-01"), zone) * - * // set the time zone - * const zoned: DateTime.Zoned = DateTime.setZone(now, zone) - * }) + * DateTime.isZoned(zoned) // => true * ``` * - * @category time zones + * @category transforming * @since 3.6.0 */ export const setZone: { @@ -957,18 +953,16 @@ export const setZone: { * * **Example** (Setting fixed-offset time zones) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const zoned: DateTime.Zoned = DateTime.setZoneOffset(dateTime, 3 * 60 * 60 * 1000) * - * // set the offset time zone in milliseconds - * const zoned: DateTime.Zoned = DateTime.setZoneOffset(now, 3 * 60 * 60 * 1000) - * }) + * DateTime.zoneToString(zoned.zone) // => "+03:00" * ``` * - * @category time zones + * @category transforming * @since 3.6.0 */ export const setZoneOffset: { @@ -996,20 +990,17 @@ export const setZoneOffset: { * * **Example** (Creating named time zones unsafely) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const londonZone = DateTime.zoneMakeNamedUnsafe("Europe/London") - * console.log(DateTime.zoneToString(londonZone)) // "Europe/London" - * - * const tokyoZone = DateTime.zoneMakeNamedUnsafe("Asia/Tokyo") - * console.log(DateTime.zoneToString(tokyoZone)) // "Asia/Tokyo" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) // => "Europe/London" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Asia/Tokyo")) // => "Asia/Tokyo" * * // This would throw an IllegalArgumentError: * // DateTime.zoneMakeNamedUnsafe("Invalid/Zone") * ``` * - * @category time zones + * @category constructors * @since 4.0.0 */ export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal.zoneMakeNamedUnsafe @@ -1024,7 +1015,7 @@ export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal. * * **Example** (Creating fixed-offset time zones) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // Create a time zone with +3 hours offset @@ -1033,9 +1024,10 @@ export const zoneMakeNamedUnsafe: (zoneId: string) => TimeZone.Named = Internal. * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: zone * }) + * DateTime.formatIsoZoned(dt) // => "2024-01-01T15:00:00.000+03:00" * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeOffset: (offset: number) => TimeZone.Offset = Internal.zoneMakeOffset @@ -1049,17 +1041,14 @@ export const zoneMakeOffset: (offset: number) => TimeZone.Offset = Internal.zone * * **Example** (Creating optional named time zones) * - * ```ts - * import { DateTime } from "effect" - * - * const validZone = DateTime.zoneMakeNamed("Europe/London") - * console.log(validZone._tag === "Some") // true + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * - * const invalidZone = DateTime.zoneMakeNamed("Invalid/Zone") - * console.log(invalidZone._tag === "None") // true + * DateTime.zoneMakeNamed("Europe/London").pipe(Option.map(DateTime.zoneToString)) // => Option.some("Europe/London") + * DateTime.zoneMakeNamed("Invalid/Zone") // => Option.none() * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeNamed: (zoneId: string) => Option.Option = Internal.zoneMakeNamed @@ -1074,7 +1063,7 @@ export const zoneMakeNamed: (zoneId: string) => Option.Option = * * **Example** (Creating named time zones effectfully) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const program = Effect.gen(function*() { @@ -1082,9 +1071,11 @@ export const zoneMakeNamed: (zoneId: string) => Option.Option = * const now = yield* DateTime.now * return DateTime.setZone(now, zone) * }) + * + * DateTime.zoneToString((await Effect.runPromise(program)).zone) // => "Europe/London" * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeNamedEffect: (zoneId: string) => Effect.Effect = @@ -1100,14 +1091,13 @@ export const zoneMakeNamedEffect: (zoneId: string) => Effect.Effect true * ``` * - * @category time zones + * @category constructors * @since 3.6.0 */ export const zoneMakeLocal: () => TimeZone.Named = Internal.zoneMakeLocal @@ -1121,19 +1111,15 @@ export const zoneMakeLocal: () => TimeZone.Named = Internal.zoneMakeLocal * * **Example** (Parsing time zones) * - * ```ts - * import { DateTime } from "effect" - * - * const namedZone = DateTime.zoneFromString("Europe/London") - * const offsetZone = DateTime.zoneFromString("+03:00") - * const invalid = DateTime.zoneFromString("invalid") + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * - * console.log(namedZone._tag === "Some") // true - * console.log(offsetZone._tag === "Some") // true - * console.log(invalid._tag === "None") // true + * DateTime.zoneFromString("Europe/London").pipe(Option.map(DateTime.zoneToString)) // => Option.some("Europe/London") + * DateTime.zoneFromString("+03:00").pipe(Option.map(DateTime.zoneToString)) // => Option.some("+03:00") + * DateTime.zoneFromString("invalid") // => Option.none() * ``` * - * @category time zones + * @category decoding * @since 3.6.0 */ export const zoneFromString: (zone: string) => Option.Option = Internal.zoneFromString @@ -1143,17 +1129,14 @@ export const zoneFromString: (zone: string) => Option.Option = Interna * * **Example** (Formatting time zones) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * // Outputs "+03:00" - * DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) - * - * // Outputs "Europe/London" - * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) + * DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) // => "+03:00" + * DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Europe/London")) // => "Europe/London" * ``` * - * @category time zones + * @category encoding * @since 3.6.0 */ export const zoneToString: (self: TimeZone) => string = Internal.zoneToString @@ -1164,17 +1147,16 @@ export const zoneToString: (self: TimeZone) => string = Internal.zoneToString * * **Example** (Setting named time zones safely) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Option } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * // set the time zone, returns an Option - * DateTime.setZoneNamed(now, "Europe/London") - * }) + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const result = DateTime.setZoneNamed(dateTime, "Europe/London").pipe(Option.map(DateTime.formatIsoZoned)) + * + * result // => Option.some("2024-01-01T00:00:00.000+00:00[Europe/London]") * ``` * - * @category time zones + * @category transforming * @since 3.6.0 */ export const setZoneNamed: { @@ -1194,17 +1176,16 @@ export const setZoneNamed: { * * **Example** (Setting named time zones unsafely) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * // set the time zone - * DateTime.setZoneNamedUnsafe(now, "Europe/London") - * }) + * const dateTime = DateTime.makeUnsafe("2024-01-01") + * const zoned = DateTime.setZoneNamedUnsafe(dateTime, "Europe/London") + * + * DateTime.zoneToString(zoned.zone) // => "Europe/London" * ``` * - * @category time zones + * @category transforming * @since 4.0.0 */ export const setZoneNamedUnsafe: { @@ -1234,16 +1215,13 @@ export const setZoneNamedUnsafe: { * * **Example** (Measuring distance between DateTime values) * - * ```ts - * import { DateTime, Effect } from "effect" + * ```ts import.meta.vitest + * import { DateTime, Duration } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * const other = DateTime.add(now, { minutes: 1 }) + * const start = DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * const end = DateTime.add(start, { minutes: 1 }) * - * // returns Duration.minutes(1) - * DateTime.distance(now, other) - * }) + * DateTime.distance(start, end) // => Duration.minutes(1) * ``` * * @category comparisons @@ -1259,14 +1237,13 @@ export const distance: { * * **Example** (Selecting the earlier DateTime) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-02-01") * - * const earlier = DateTime.min(date1, date2) - * // earlier equals date1 (2024-01-01) + * DateTime.min(date1, date2) // => DateTime.makeUnsafe("2024-01-01") * ``` * * @category comparisons @@ -1282,14 +1259,13 @@ export const min: { * * **Example** (Selecting the later DateTime) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-02-01") * - * const later = DateTime.max(date1, date2) - * // later equals date2 (2024-02-01) + * DateTime.max(date1, date2) // => DateTime.makeUnsafe("2024-02-01") * ``` * * @category comparisons @@ -1305,14 +1281,14 @@ export const max: { * * **Example** (Checking whether a DateTime is later) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-02-01") * const date2 = DateTime.makeUnsafe("2024-01-01") * - * console.log(DateTime.isGreaterThan(date1, date2)) // true - * console.log(DateTime.isGreaterThan(date2, date1)) // false + * DateTime.isGreaterThan(date1, date2) // => true + * DateTime.isGreaterThan(date2, date1) // => false * ``` * * @category comparisons @@ -1328,16 +1304,16 @@ export const isGreaterThan: { * * **Example** (Checking whether a DateTime is later or equal) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-01-01") * const date3 = DateTime.makeUnsafe("2024-02-01") * - * console.log(DateTime.isGreaterThanOrEqualTo(date1, date2)) // true - * console.log(DateTime.isGreaterThanOrEqualTo(date3, date1)) // true - * console.log(DateTime.isGreaterThanOrEqualTo(date1, date3)) // false + * DateTime.isGreaterThanOrEqualTo(date1, date2) // => true + * DateTime.isGreaterThanOrEqualTo(date3, date1) // => true + * DateTime.isGreaterThanOrEqualTo(date1, date3) // => false * ``` * * @category comparisons @@ -1353,14 +1329,14 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking whether a DateTime is earlier) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-02-01") * - * console.log(DateTime.isLessThan(date1, date2)) // true - * console.log(DateTime.isLessThan(date2, date1)) // false + * DateTime.isLessThan(date1, date2) // => true + * DateTime.isLessThan(date2, date1) // => false * ``` * * @category comparisons @@ -1376,16 +1352,16 @@ export const isLessThan: { * * **Example** (Checking whether a DateTime is earlier or equal) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const date1 = DateTime.makeUnsafe("2024-01-01") * const date2 = DateTime.makeUnsafe("2024-01-01") * const date3 = DateTime.makeUnsafe("2024-02-01") * - * console.log(DateTime.isLessThanOrEqualTo(date1, date2)) // true - * console.log(DateTime.isLessThanOrEqualTo(date1, date3)) // true - * console.log(DateTime.isLessThanOrEqualTo(date3, date1)) // false + * DateTime.isLessThanOrEqualTo(date1, date2) // => true + * DateTime.isLessThanOrEqualTo(date1, date3) // => true + * DateTime.isLessThanOrEqualTo(date3, date1) // => false * ``` * * @category comparisons @@ -1401,14 +1377,14 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking whether a DateTime is within bounds) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const min = DateTime.makeUnsafe("2024-01-01") * const max = DateTime.makeUnsafe("2024-12-31") * const date = DateTime.makeUnsafe("2024-06-15") * - * console.log(DateTime.between(date, { minimum: min, maximum: max })) // true + * DateTime.between(date, { minimum: min, maximum: max }) // => true * ``` * * @category comparisons @@ -1428,14 +1404,12 @@ export const between: { * * **Example** (Checking future DateTime values effectfully) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * const program = Effect.gen(function*() { - * const futureDate = DateTime.add(yield* DateTime.now, { hours: 1 }) - * const isFuture = yield* DateTime.isFuture(futureDate) - * console.log(isFuture) // true - * }) + * const futureDate = DateTime.makeUnsafe(1) + * await Effect.runPromise(Effect.provide(DateTime.isFuture(futureDate), TestClock.layer())) // => true * ``` * * @category comparisons @@ -1460,11 +1434,8 @@ export const isFuture: (self: DateTime) => Effect.Effect = Internal.isF * ```ts * import { DateTime } from "effect" * - * const now = DateTime.nowUnsafe() - * const futureDate = DateTime.add(now, { hours: 1 }) - * - * console.log(DateTime.isFutureUnsafe(futureDate)) // true - * console.log(DateTime.isFutureUnsafe(now)) // false + * const oneHourFromNow = DateTime.add(DateTime.nowUnsafe(), { hours: 1 }) + * DateTime.isFutureUnsafe(oneHourFromNow) * ``` * * @category comparisons @@ -1481,14 +1452,12 @@ export const isFutureUnsafe: (self: DateTime) => boolean = Internal.isFutureUnsa * * **Example** (Checking past DateTime values effectfully) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" + * import { TestClock } from "effect/testing" * - * const program = Effect.gen(function*() { - * const pastDate = DateTime.subtract(yield* DateTime.now, { hours: 1 }) - * const isPast = yield* DateTime.isPast(pastDate) - * console.log(isPast) // true - * }) + * const pastDate = DateTime.makeUnsafe(-1) + * await Effect.runPromise(Effect.provide(DateTime.isPast(pastDate), TestClock.layer())) // => true * ``` * * @category comparisons @@ -1513,11 +1482,8 @@ export const isPast: (self: DateTime) => Effect.Effect = Internal.isPas * ```ts * import { DateTime } from "effect" * - * const now = DateTime.nowUnsafe() - * const pastDate = DateTime.subtract(now, { hours: 1 }) - * - * console.log(DateTime.isPastUnsafe(pastDate)) // true - * console.log(DateTime.isPastUnsafe(now)) // false + * const oneHourAgo = DateTime.subtract(DateTime.nowUnsafe(), { hours: 1 }) + * DateTime.isPastUnsafe(oneHourAgo) * ``` * * @category comparisons @@ -1538,15 +1504,14 @@ export const isPastUnsafe: (self: DateTime) => boolean = Internal.isPastUnsafe * * **Example** (Converting DateTime values to UTC Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: "Europe/London" * }) * - * const utcDate = DateTime.toDateUtc(dt) - * console.log(utcDate.toISOString()) // "2024-01-01T12:00:00.000Z" + * DateTime.toDateUtc(dt).toISOString() // => "2024-01-01T12:00:00.000Z" * ``` * * @category converting @@ -1564,7 +1529,7 @@ export const toDateUtc: (self: DateTime) => Date = Internal.toDateUtc * * **Example** (Converting DateTime values to Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") @@ -1572,8 +1537,8 @@ export const toDateUtc: (self: DateTime) => Date = Internal.toDateUtc * timeZone: "Europe/London" * }) * - * console.log(DateTime.toDate(utc).toISOString()) - * console.log(DateTime.toDate(zoned).toISOString()) + * DateTime.toDate(utc).toISOString() // => "2024-01-01T12:00:00.000Z" + * DateTime.toDate(zoned).toISOString() // => "2024-01-01T12:00:00.000Z" * ``` * * @category converting @@ -1591,15 +1556,14 @@ export const toDate: (self: DateTime) => Date = Internal.toDate * * **Example** (Reading zoned offsets) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: "Europe/London" * }) * - * const offset = DateTime.zonedOffset(zoned) - * console.log(offset) // 0 (London is UTC+0 in winter) + * DateTime.zonedOffset(zoned) // => 0 * ``` * * @category converting @@ -1616,15 +1580,14 @@ export const zonedOffset: (self: Zoned) => number = Internal.zonedOffset * * **Example** (Formatting zoned offsets) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) // +3 hours * }) * - * const offsetString = DateTime.zonedOffsetIso(zoned) - * console.log(offsetString) // "+03:00" + * DateTime.zonedOffsetIso(zoned) // => "+03:00" * ``` * * @category converting @@ -1641,13 +1604,11 @@ export const zonedOffsetIso: (self: Zoned) => string = Internal.zonedOffsetIso * * **Example** (Reading epoch milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z") - * const epochMillis = DateTime.toEpochMillis(dt) - * - * console.log(epochMillis) // 1704067200000 + * DateTime.toEpochMillis(dt) // => 1704067200000 * ``` * * @category converting @@ -1655,13 +1616,35 @@ export const zonedOffsetIso: (self: Zoned) => string = Internal.zonedOffsetIso */ export const toEpochMillis: (self: DateTime) => number = Internal.toEpochMillis +/** + * Converts a `DateTime` to the number of seconds since the Unix epoch. + * + * **Details** + * + * This returns the UTC timestamp regardless of any time zone information. + * The result is floored to the nearest second. + * + * **Example** (Reading epoch seconds) + * + * ```ts import.meta.vitest + * import { DateTime } from "effect" + * + * const dt = DateTime.makeUnsafe("2024-01-01T00:00:00Z") + * DateTime.toEpochSeconds(dt) // => 1704067200 + * ``` + * + * @category converting + * @since 4.0.0 + */ +export const toEpochSeconds: (self: DateTime) => number = Internal.toEpochSeconds + /** * Removes the time aspect of a `DateTime`, first adjusting for the time * zone. It will return a `DateTime.Utc` only containing the date. * * **Example** (Removing time components) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-01T00:00:00Z" @@ -1671,7 +1654,7 @@ export const toEpochMillis: (self: DateTime) => number = Internal.toEpochMillis * }).pipe( * DateTime.removeTime, * DateTime.formatIso - * ) + * ) // => "2024-01-01T00:00:00.000Z" * ``` * * @category converting @@ -1692,26 +1675,16 @@ export const removeTime: (self: DateTime) => Utc = Internal.removeTime * * **Example** (Reading DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T12:30:45.123Z") * const parts = DateTime.toParts(dt) * - * console.log(parts) - * // { - * // year: 2024, - * // month: 1, - * // day: 1, - * // hours: 12, - * // minutes: 30, - * // seconds: 45, - * // millis: 123, - * // weekDay: 1 // Monday - * // } + * const selectedParts = [parts.year, parts.month, parts.day, parts.hour] // => [2024, 1, 1, 12] * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.toParts @@ -1725,7 +1698,7 @@ export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.t * * **Example** (Reading UTC DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:30:45.123Z", { @@ -1733,11 +1706,10 @@ export const toParts: (self: DateTime) => DateTime.PartsWithWeekday = Internal.t * }) * const parts = DateTime.toPartsUtc(zoned) * - * console.log(parts) - * // Always returns UTC parts regardless of time zone + * const selectedParts = [parts.year, parts.month, parts.day, parts.hour] // => [2024, 1, 1, 12] * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const toPartsUtc: (self: DateTime) => DateTime.PartsWithWeekday = Internal.toPartsUtc @@ -1751,15 +1723,14 @@ export const toPartsUtc: (self: DateTime) => DateTime.PartsWithWeekday = Interna * * **Example** (Reading UTC DateTime parts by key) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dateTime = DateTime.makeUnsafe({ year: 2024 }) - * const year = DateTime.getPartUtc(dateTime, "year") - * console.log(year) // 2024 + * DateTime.getPartUtc(dateTime, "year") // => 2024 * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const getPartUtc: { @@ -1776,17 +1747,16 @@ export const getPartUtc: { * * **Example** (Reading DateTime parts by key) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dateTime = DateTime.makeZonedUnsafe({ year: 2024 }, { * timeZone: "Europe/London" * }) - * const year = DateTime.getPart(dateTime, "year") - * console.log(year) // 2024 + * DateTime.getPart(dateTime, "year") // => 2024 * ``` * - * @category parts + * @category getters * @since 3.6.0 */ export const getPart: { @@ -1803,20 +1773,20 @@ export const getPart: { * * **Example** (Updating DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") + * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { timeZone: "UTC" }) * const updated = DateTime.setParts(dt, { * year: 2025, * month: 6, * day: 15 * }) * - * console.log(DateTime.formatIso(updated)) // "2025-06-15T12:00:00.000Z" + * updated // => DateTime.makeZonedUnsafe("2025-06-15T12:00:00Z", { timeZone: "UTC" }) * ``` * - * @category parts + * @category transforming * @since 3.6.0 */ export const setParts: { @@ -1833,7 +1803,7 @@ export const setParts: { * * **Example** (Updating UTC DateTime parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") @@ -1842,10 +1812,10 @@ export const setParts: { * hour: 18 * }) * - * console.log(DateTime.formatIso(updated)) // "2025-01-01T18:00:00.000Z" + * updated // => DateTime.makeUnsafe("2025-01-01T18:00:00Z") * ``` * - * @category parts + * @category transforming * @since 3.6.0 */ export const setPartsUtc: { @@ -1869,21 +1839,19 @@ export const setPartsUtc: { * * **Example** (Accessing the current time zone service) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const program = Effect.gen(function*() { - * // Access the current time zone service - * const zone = yield* DateTime.CurrentTimeZone - * console.log(DateTime.zoneToString(zone)) + * return DateTime.zoneToString(yield* DateTime.CurrentTimeZone) * }) * * // Provide a time zone * const layer = DateTime.layerCurrentZoneNamed("Europe/London") - * Effect.provide(program, layer) + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" * ``` * - * @category current time zone + * @category services * @since 3.11.0 */ export class CurrentTimeZone extends Context.Service()( @@ -1896,18 +1864,16 @@ export class CurrentTimeZone extends Context.Service( * * **Example** (Setting the current time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * const now = yield* DateTime.now - * - * // set the time zone to "Europe/London" - * const zoned = yield* DateTime.setZoneCurrent(now) - * }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) + * await Effect.runPromise(Effect.gen(function*() { + * const zoned = yield* DateTime.setZoneCurrent(DateTime.makeUnsafe("2024-01-01")) + * return DateTime.zoneToString(zoned.zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" * ``` * - * @category current time zone + * @category accessors * @since 3.6.0 */ export const setZoneCurrent = (self: DateTime): Effect.Effect => @@ -1918,17 +1884,18 @@ export const setZoneCurrent = (self: DateTime): Effect.Effect "Europe/London" * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZone: { @@ -1942,16 +1909,15 @@ export const withCurrentZone: { * * **Example** (Providing the local time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * // will use the system's local time zone - * const now = yield* DateTime.nowInCurrentZone - * }).pipe(DateTime.withCurrentZoneLocal) + * await Effect.runPromise(Effect.gen(function*() { + * return DateTime.isZoned(yield* DateTime.nowInCurrentZone) + * }).pipe(DateTime.withCurrentZoneLocal)) // => true * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZoneLocal = ( @@ -1964,16 +1930,17 @@ export const withCurrentZoneLocal = ( * * **Example** (Providing a fixed-offset time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * const zone = yield* DateTime.CurrentTimeZone - * console.log(DateTime.zoneToString(zone)) // "+03:00" + * const program = Effect.gen(function*() { + * return DateTime.zoneToString(yield* DateTime.CurrentTimeZone) * }).pipe(DateTime.withCurrentZoneOffset(3 * 60 * 60 * 1000)) + * + * await Effect.runPromise(program) // => "+03:00" * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZoneOffset: { @@ -1997,16 +1964,16 @@ export const withCurrentZoneOffset: { * * **Example** (Providing a named time zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * // will use the "Europe/London" time zone - * const now = yield* DateTime.nowInCurrentZone - * }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) + * await Effect.runPromise(Effect.gen(function*() { + * const zoned = yield* DateTime.setZoneCurrent(DateTime.makeUnsafe("2024-01-01")) + * return DateTime.zoneToString(zoned.zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" * ``` * - * @category current time zone + * @category providing services * @since 3.6.0 */ export const withCurrentZoneNamed: { @@ -2031,16 +1998,15 @@ export const withCurrentZoneNamed: { * * **Example** (Getting the current time in the current zone) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * - * Effect.gen(function*() { - * // will use the "Europe/London" time zone - * const now = yield* DateTime.nowInCurrentZone - * }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) + * await Effect.runPromise(Effect.gen(function*() { + * return DateTime.zoneToString((yield* DateTime.nowInCurrentZone).zone) + * }).pipe(DateTime.withCurrentZoneNamed("Europe/London"))) // => "Europe/London" * ``` * - * @category current time zone + * @category accessors * @since 3.6.0 */ export const nowInCurrentZone: Effect.Effect = Effect.flatMap(now, setZoneCurrent) @@ -2066,17 +2032,15 @@ export const nowInCurrentZone: Effect.Effect = Ef * * **Example** (Mutating DateTime values with Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T12:00:00Z") * - * const modified = DateTime.mutate(dt, (date) => { + * DateTime.mutate(dt, (date) => { * date.setHours(15) // Set to 3 PM * date.setMinutes(30) // Set to 30 minutes * }) - * - * console.log(DateTime.formatIso(modified)) // "2024-01-01T15:30:00.000Z" * ``` * * @category mapping @@ -2108,7 +2072,7 @@ export const mutate: { * * **Example** (Mutating DateTime values with UTC Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { @@ -2119,7 +2083,7 @@ export const mutate: { * date.setUTCHours(18) // Set UTC time to 6 PM * }) * - * console.log(DateTime.formatIso(modified)) // "2024-01-01T18:00:00.000Z" + * modified // => DateTime.makeZonedUnsafe("2024-01-01T18:00:00Z", { timeZone: "Europe/London" }) * ``` * * @category mapping @@ -2136,13 +2100,14 @@ export const mutateUtc: { * * **Example** (Mapping epoch milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // add 10 milliseconds - * DateTime.makeUnsafe(0).pipe( + * const result = DateTime.makeUnsafe(0).pipe( * DateTime.mapEpochMillis((millis) => millis + 10) * ) + * result // => DateTime.makeUnsafe(10) * ``` * * @category mapping @@ -2165,13 +2130,13 @@ export const mapEpochMillis: { * * **Example** (Applying time zone adjusted Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // get the time zone adjusted date in milliseconds * DateTime.makeZonedUnsafe(0, { timeZone: "Europe/London" }).pipe( * DateTime.withDate((date) => date.getTime()) - * ) + * ) // => 3600000 * ``` * * @category mapping @@ -2193,13 +2158,13 @@ export const withDate: { * * **Example** (Applying UTC Dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // get the date in milliseconds * DateTime.makeUnsafe(0).pipe( * DateTime.withDateUtc((date) => date.getTime()) - * ) + * ) // => 0 * ``` * * @category mapping @@ -2215,7 +2180,7 @@ export const withDateUtc: { * * **Example** (Pattern matching DateTime variants) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt1 = DateTime.makeUnsafe("2024-01-01T12:00:00Z") // Utc @@ -2233,8 +2198,8 @@ export const withDateUtc: { * onZoned: (zoned) => `Zoned: ${DateTime.formatIsoZoned(zoned)}` * }) * - * console.log(result1) // "UTC: 2024-01-01T12:00:00.000Z" - * console.log(result2) // "Zoned: 2024-06-15T15:30:00.000+01:00[Europe/London]" + * result1 // => "UTC: 2024-01-01T12:00:00.000Z" + * result2 // => "Zoned: 2024-06-15T15:30:00.000+01:00[Europe/London]" * ``` * * @category mapping @@ -2276,13 +2241,13 @@ export const match: { * * **Example** (Adding durations) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // add 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.addDuration("5 minutes") - * ) + * ) // => DateTime.makeUnsafe(300000) * ``` * * @see {@link add} for calendar-aware date/time part arithmetic @@ -2301,13 +2266,13 @@ export const addDuration: { * * **Example** (Subtracting durations) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // subtract 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.subtractDuration("5 minutes") - * ) + * ) // => DateTime.makeUnsafe(-300000) * ``` * * @category math @@ -2328,13 +2293,13 @@ export const subtractDuration: { * * **Example** (Adding date and time parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // add 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.add({ minutes: 5 }) - * ) + * ) // => DateTime.makeUnsafe(300000) * ``` * * @category math @@ -2350,13 +2315,13 @@ export const add: { * * **Example** (Subtracting date and time parts) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // subtract 5 minutes * DateTime.makeUnsafe(0).pipe( * DateTime.subtract({ minutes: 5 }) - * ) + * ) // => DateTime.makeUnsafe(-300000) * ``` * * @category math @@ -2377,14 +2342,13 @@ export const subtract: { * * **Example** (Rounding down DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-01T00:00:00Z" * DateTime.makeUnsafe("2024-01-01T12:00:00Z").pipe( * DateTime.startOf("day"), - * DateTime.formatIso - * ) + * ) // => DateTime.makeUnsafe("2024-01-01T00:00:00Z") * ``` * * @category math @@ -2412,14 +2376,13 @@ export const startOf: { * * **Example** (Rounding up DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-01T23:59:59.999Z" * DateTime.makeUnsafe("2024-01-01T12:00:00Z").pipe( * DateTime.endOf("day"), - * DateTime.formatIso - * ) + * ) // => DateTime.makeUnsafe("2024-01-01T23:59:59.999Z") * ``` * * @category math @@ -2447,14 +2410,13 @@ export const endOf: { * * **Example** (Rounding DateTime values to nearest units) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * // returns "2024-01-02T00:00:00Z" * DateTime.makeUnsafe("2024-01-01T12:01:00Z").pipe( * DateTime.nearest("day"), - * DateTime.formatIso - * ) + * ) // => DateTime.makeUnsafe("2024-01-02T00:00:00Z") * ``` * * @category math @@ -2490,20 +2452,18 @@ export const nearest: { * * **Example** (Formatting DateTime values with Intl options) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { * timeZone: "Europe/London" * }) * - * const formatted = DateTime.format(dt, { + * DateTime.format(dt, { * dateStyle: "full", * timeStyle: "short", * locale: "en-US" - * }) - * - * console.log(formatted) // "Saturday, June 15, 2024 at 3:30 PM" + * }) // => "Saturday, June 15, 2024 at 3:30 PM" * ``` * * @category formatting @@ -2533,21 +2493,19 @@ export const format: { * * **Example** (Formatting DateTime values locally) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-06-15T14:30:00Z") * * // Uses system local time zone and locale - * const local = DateTime.formatLocal(dt, { + * DateTime.formatLocal(dt, { * year: "numeric", * month: "long", * day: "numeric", * hour: "2-digit", * minute: "2-digit" * }) - * - * console.log(local) // Output depends on system locale/timezone * ``` * * @category formatting @@ -2580,7 +2538,7 @@ export const formatLocal: { * * **Example** (Formatting DateTime values in UTC) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { @@ -2588,7 +2546,7 @@ export const formatLocal: { * }) * * // Force UTC formatting regardless of time zone - * const utcFormatted = DateTime.formatUtc(dt, { + * DateTime.formatUtc(dt, { * year: "numeric", * month: "2-digit", * day: "2-digit", @@ -2596,8 +2554,6 @@ export const formatLocal: { * minute: "2-digit", * timeZoneName: "short" * }) - * - * console.log(utcFormatted) // "06/15/2024, 02:30 PM UTC" * ``` * * @category formatting @@ -2636,7 +2592,7 @@ export const formatUtc: { * * **Example** (Formatting DateTime values with custom formatters) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-06-15T14:30:00Z") @@ -2651,8 +2607,7 @@ export const formatUtc: { * timeZone: "Europe/Berlin" * }) * - * const formatted = DateTime.formatIntl(dt, formatter) - * console.log(formatted.length > 0) // true + * DateTime.formatIntl(dt, formatter).length > 0 // => true * ``` * * @see {@link formatUtc} for formatting with options forced to UTC @@ -2675,16 +2630,15 @@ export const formatIntl: { * * **Example** (Formatting DateTime values as ISO strings) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * - * const dt = DateTime.makeUnsafe("2024-01-01T12:30:45.123Z") - * console.log(DateTime.formatIso(dt)) // "2024-01-01T12:30:45.123Z" + * DateTime.formatIso(DateTime.makeUnsafe("2024-01-01T12:30:45.123Z")) // => "2024-01-01T12:30:45.123Z" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:30:45.123Z", { * timeZone: "Europe/London" * }) - * console.log(DateTime.formatIso(zoned)) // "2024-01-01T12:30:45.123Z" + * DateTime.formatIso(zoned) // => "2024-01-01T12:30:45.123Z" * ``` * * @category formatting @@ -2701,16 +2655,16 @@ export const formatIso: (self: DateTime) => string = Internal.formatIso * * **Example** (Formatting DateTime values as ISO dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T23:30:00Z") - * console.log(DateTime.formatIsoDate(dt)) // "2024-01-01" + * DateTime.formatIsoDate(dt) // => "2024-01-01" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { * timeZone: "Pacific/Auckland" // UTC+12/13 * }) - * console.log(DateTime.formatIsoDate(zoned)) // "2024-01-02" (next day in Auckland) + * DateTime.formatIsoDate(zoned) // => "2024-01-02" * ``` * * @category formatting @@ -2727,16 +2681,16 @@ export const formatIsoDate: (self: DateTime) => string = Internal.formatIsoDate * * **Example** (Formatting DateTime values as UTC ISO dates) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const dt = DateTime.makeUnsafe("2024-01-01T23:30:00Z") - * console.log(DateTime.formatIsoDateUtc(dt)) // "2024-01-01" + * DateTime.formatIsoDateUtc(dt) // => "2024-01-01" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { * timeZone: "Pacific/Auckland" * }) - * console.log(DateTime.formatIsoDateUtc(zoned)) // "2024-01-01" (always UTC) + * DateTime.formatIsoDateUtc(zoned) // => "2024-01-01" * ``` * * @category formatting @@ -2754,16 +2708,16 @@ export const formatIsoDateUtc: (self: DateTime) => string = Internal.formatIsoDa * * **Example** (Formatting DateTime values with offsets) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const utc = DateTime.makeUnsafe("2024-01-01T12:00:00Z") - * console.log(DateTime.formatIsoOffset(utc)) // "2024-01-01T12:00:00.000Z" + * DateTime.formatIsoOffset(utc) // => "2024-01-01T12:00:00.000Z" * * const zoned = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { * timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) * }) - * console.log(DateTime.formatIsoOffset(zoned)) // "2024-01-01T15:00:00.000+03:00" + * DateTime.formatIsoOffset(zoned) // => "2024-01-01T15:00:00.000+03:00" * ``` * * @category formatting @@ -2780,22 +2734,20 @@ export const formatIsoOffset: (self: DateTime) => string = Internal.formatIsoOff * * **Example** (Formatting zoned DateTime values) * - * ```ts + * ```ts import.meta.vitest * import { DateTime } from "effect" * * const zoned = DateTime.makeZonedUnsafe("2024-06-15T14:30:45.123Z", { * timeZone: "Europe/London" * }) * - * const formatted = DateTime.formatIsoZoned(zoned) - * console.log(formatted) // "2024-06-15T15:30:45.123+01:00[Europe/London]" + * DateTime.formatIsoZoned(zoned) // => "2024-06-15T15:30:45.123+01:00[Europe/London]" * * const offsetZone = DateTime.makeZonedUnsafe("2024-06-15T14:30:45.123Z", { * timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000) * }) * - * const offsetFormatted = DateTime.formatIsoZoned(offsetZone) - * console.log(offsetFormatted) // "2024-06-15T17:30:45.123+03:00" + * DateTime.formatIsoZoned(offsetZone) // => "2024-06-15T17:30:45.123+03:00" * ``` * * @category formatting @@ -2812,7 +2764,7 @@ export const formatIsoZoned: (self: Zoned) => string = Internal.formatIsoZoned * * **Example** (Providing current time zone layers) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const zone = DateTime.zoneMakeNamedUnsafe("Europe/London") @@ -2820,14 +2772,14 @@ export const formatIsoZoned: (self: Zoned) => string = Internal.formatIsoZoned * * const program = Effect.gen(function*() { * const now = yield* DateTime.nowInCurrentZone - * return DateTime.formatIsoZoned(now) + * return DateTime.zoneToString(now.zone) * }) * * // Use the layer to provide the time zone - * Effect.provide(program, layer) + * await Effect.runPromise(Effect.provide(program, layer)) // => "Europe/London" * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZone: (resource: NoInfer) => Layer.Layer = Layer.succeed( @@ -2843,7 +2795,7 @@ export const layerCurrentZone: (resource: NoInfer) => Layer.Layer) => Layer.Layer "+03:00" * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZoneOffset = (offset: number): Layer.Layer => @@ -2873,20 +2825,20 @@ export const layerCurrentZoneOffset = (offset: number): Layer.Layer "Europe/London" * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZoneNamed: (zoneId: string) => Layer.Layer< @@ -2904,19 +2856,19 @@ export const layerCurrentZoneNamed: (zoneId: string) => Layer.Layer< * * **Example** (Providing local time zone layers) * - * ```ts + * ```ts import.meta.vitest * import { DateTime, Effect } from "effect" * * const program = Effect.gen(function*() { * const now = yield* DateTime.nowInCurrentZone - * return DateTime.formatIsoZoned(now) + * return DateTime.isZoned(now) * }) * * // Use the system's local time zone - * Effect.provide(program, DateTime.layerCurrentZoneLocal) + * await Effect.runPromise(Effect.provide(program, DateTime.layerCurrentZoneLocal)) // => true * ``` * - * @category current time zone + * @category layers * @since 3.6.0 */ export const layerCurrentZoneLocal: Layer.Layer = Layer.sync(CurrentTimeZone)(zoneMakeLocal) diff --git a/packages/effect/src/Deferred.ts b/packages/effect/src/Deferred.ts index a2f626078..14e75b964 100644 --- a/packages/effect/src/Deferred.ts +++ b/packages/effect/src/Deferred.ts @@ -33,36 +33,23 @@ const TypeId = "~effect/Deferred" * * **Example** (Creating a Deferred for inter-fiber communication) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber } from "effect" * - * // Create and use a Deferred for inter-fiber communication * const program = Effect.gen(function*() { - * // Create a Deferred that will hold a string value * const deferred: Deferred.Deferred = yield* Deferred.make() - * - * // Fork a fiber that will set the deferred value * const producer = yield* Effect.forkChild( * Effect.gen(function*() { - * yield* Effect.sleep("100 millis") * yield* Deferred.succeed(deferred, "Hello, World!") * }) * ) * - * // Fork a fiber that will await the deferred value - * const consumer = yield* Effect.forkChild( - * Effect.gen(function*() { - * const value = yield* Deferred.await(deferred) - * console.log("Received:", value) - * return value - * }) - * ) - * - * // Wait for both fibers to complete + * const consumer = yield* Effect.forkChild(Deferred.await(deferred)) * yield* Fiber.join(producer) - * const result = yield* Fiber.join(consumer) - * return result + * return yield* Fiber.join(consumer) * }) + * + * await Effect.runPromise(program) // => "Hello, World!" * ``` * * @category models @@ -130,6 +117,12 @@ const DeferredProto = { } } +const DeferredImpl = function(this: any) { + this.resumes = undefined + this.effect = undefined +} as unknown as { new(): Deferred; prototype: any } +DeferredImpl.prototype = DeferredProto + /** * Creates an empty `Deferred` synchronously outside the `Effect` runtime. * @@ -140,22 +133,17 @@ const DeferredProto = { * * **Example** (Creating a Deferred unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Deferred } from "effect" * * const deferred = Deferred.makeUnsafe() - * console.log(deferred) + * Deferred.isDoneUnsafe(deferred) // => false * ``` * * @category unsafe * @since 4.0.0 */ -export const makeUnsafe = (): Deferred => { - const self = Object.create(DeferredProto) - self.resumes = undefined - self.effect = undefined - return self -} +export const makeUnsafe = (): Deferred => new DeferredImpl() /** * Creates a new `Deferred`. @@ -166,15 +154,16 @@ export const makeUnsafe = (): Deferred => { * * **Example** (Creating a Deferred) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.succeed(deferred, 42) - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category constructors @@ -188,8 +177,12 @@ const _await = (self: Deferred): Effect => self.resumes ??= [] self.resumes.push(resume) return internalEffect.sync(() => { - const index = self.resumes!.indexOf(resume) - self.resumes!.splice(index, 1) + // Completion resumes all waiters and clears `resumes`, so a cleanup + // running after completion has nothing to unregister. + const resumes = self.resumes + if (resumes === undefined) return + const index = resumes.indexOf(resume) + if (index >= 0) resumes.splice(index, 1) }) }) @@ -209,16 +202,17 @@ export { * * **Example** (Awaiting a Deferred value) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.succeed(deferred, 42) * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link complete} for completing from an effect and memoizing its result @@ -246,17 +240,17 @@ export { * * **Example** (Completing a Deferred from an effect) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const completed = yield* Deferred.complete(deferred, Effect.succeed(42)) - * console.log(completed) // true - * * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return [completed, value] * }) + * + * await Effect.runPromise(program) // => [true, 42] * ``` * * @see {@link completeWith} for storing an effect directly without memoizing its result @@ -293,17 +287,17 @@ export const complete: { * * **Example** (Completing a Deferred with an effect) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const completed = yield* Deferred.completeWith(deferred, Effect.succeed(42)) - * console.log(completed) // true - * * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return [completed, value] * }) + * + * await Effect.runPromise(program) // => [true, 42] * ``` * * @see {@link complete} for running an effect once and sharing its result @@ -336,16 +330,16 @@ export const completeWith: { * * **Example** (Completing a Deferred with an Exit) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.done(deferred, Exit.succeed(42)) - * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Effect.exit(Deferred.await(deferred)) * }) + * + * await Effect.runPromise(program) // => Exit.succeed(42) * ``` * * @see {@link complete} for completing from an effect and memoizing its result @@ -376,14 +370,17 @@ export const done: { * * **Example** (Failing a Deferred with an error) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.fail(deferred, "Operation failed") - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Operation failed")] * ``` * * @category completion @@ -411,14 +408,17 @@ export const fail: { * * **Example** (Failing a Deferred with a lazy error) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.failSync(deferred, () => "Lazy error") - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Lazy error")] * ``` * * @category completion @@ -448,17 +448,17 @@ export const failSync: { * * **Example** (Failing a Deferred with a Cause) * - * ```ts - * import { Cause, Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.failCause( - * deferred, - * Cause.fail("Operation failed") - * ) - * console.log(success) // true + * const success = yield* Deferred.failCause(deferred, Cause.fail("Operation failed")) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Operation failed"))] * ``` * * @category completion @@ -489,17 +489,17 @@ export const failCause: { * * **Example** (Failing a Deferred with a lazy Cause) * - * ```ts - * import { Cause, Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.failCauseSync( - * deferred, - * () => Cause.fail("Lazy error") - * ) - * console.log(success) // true + * const success = yield* Deferred.failCauseSync(deferred, () => Cause.fail("Lazy error")) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Lazy error"))] * ``` * * @category completion @@ -529,17 +529,18 @@ export const failCauseSync: { * * **Example** (Killing a Deferred with a defect) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * + * const defect = new Error("Something went wrong") * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.die( - * deferred, - * new Error("Something went wrong") - * ) - * console.log(success) // true + * const success = yield* Deferred.die(deferred, defect) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.die(defect)] * ``` * * @category completion @@ -566,17 +567,18 @@ export const die: { * * **Example** (Killing a Deferred with a lazy defect) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * + * const defect = new Error("Lazy error") * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() - * const success = yield* Deferred.dieSync( - * deferred, - * () => new Error("Lazy error") - * ) - * console.log(success) // true + * const success = yield* Deferred.dieSync(deferred, () => defect) + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.die(defect)] * ``` * * @category completion @@ -607,14 +609,19 @@ export const dieSync: { * * **Example** (Interrupting a Deferred) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.interrupt(deferred) - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] as const * }) + * + * const [success, exit] = await Effect.runPromise(program) + * success // => true + * Exit.hasInterrupts(exit) // => true * ``` * * @category completion @@ -639,14 +646,17 @@ export const interrupt = (self: Deferred): Effect => * * **Example** (Interrupting a Deferred with a fiber id) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Exit } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const success = yield* Deferred.interruptWith(deferred, 42) - * console.log(success) // true + * const exit = yield* Effect.exit(Deferred.await(deferred)) + * return [success, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.interrupt(42)] * ``` * * @category completion @@ -671,21 +681,21 @@ export const interruptWith: { * * **Example** (Checking Deferred completion) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const beforeCompletion = yield* Deferred.isDone(deferred) - * console.log(beforeCompletion) // false - * * yield* Deferred.succeed(deferred, 42) * const afterCompletion = yield* Deferred.isDone(deferred) - * console.log(afterCompletion) // true + * return [beforeCompletion, afterCompletion] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category getters + * @category predicates * @since 2.0.0 */ export const isDone = (self: Deferred): Effect => internalEffect.sync(() => isDoneUnsafe(self)) @@ -701,7 +711,7 @@ export const isDone = (self: Deferred): Effect => internalE * @see {@link isDone} for checking completion inside `Effect` * @see {@link poll} for reading the completed effect when available * - * @category getters + * @category predicates * @since 4.0.0 */ export const isDoneUnsafe = (self: Deferred): boolean => self.effect !== undefined @@ -718,18 +728,19 @@ export const isDoneUnsafe = (self: Deferred): boolean => self.effect * * **Example** (Polling Deferred completion) * - * ```ts - * import { Deferred, Effect } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Option } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * const beforeCompletion = yield* Deferred.poll(deferred) - * console.log(beforeCompletion._tag === "None") // true - * * yield* Deferred.succeed(deferred, 42) * const afterCompletion = yield* Deferred.poll(deferred) - * console.log(afterCompletion._tag === "Some") // true + * const afterValue = yield* Effect.transposeOption(afterCompletion) + * return [beforeCompletion, afterValue] * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] * ``` * * @category getters @@ -754,16 +765,17 @@ export function poll(self: Deferred): Effect() * yield* Deferred.succeed(deferred, 42) * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category completion @@ -791,16 +803,16 @@ export const succeed: { * * **Example** (Completing a Deferred with a lazy value) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* Deferred.make() * yield* Deferred.sync(deferred, () => 42) - * - * const value = yield* Deferred.await(deferred) - * console.log(value) // 42 + * return yield* Deferred.await(deferred) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category completion @@ -832,12 +844,11 @@ export const sync: { * * **Example** (Completing a Deferred unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * * const deferred = Deferred.makeUnsafe() - * const success = Deferred.doneUnsafe(deferred, Effect.succeed(42)) - * console.log(success) // true + * Deferred.doneUnsafe(deferred, Effect.succeed(42)) // => true * ``` * * @category unsafe @@ -847,10 +858,14 @@ export const doneUnsafe = (self: Deferred, effect: Effect): bo if (self.effect) return false self.effect = effect if (self.resumes) { - for (let i = 0; i < self.resumes.length; i++) { - self.resumes[i](effect) - } + // Clear `resumes` before resuming: a waiter resumed with an interrupt + // cause dies synchronously inside `resume`, and its await cleanup would + // otherwise splice this array mid-iteration and skip the next waiter. + const resumes = self.resumes self.resumes = undefined + for (let i = 0; i < resumes.length; i++) { + resumes[i](effect) + } } return true } @@ -873,33 +888,22 @@ export const doneUnsafe = (self: Deferred, effect: Effect): bo * * **Example** (Completing a Deferred from an effect result) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect } from "effect" * - * // Define an effect that succeeds * const successEffect = Effect.succeed(42) * * const program = Effect.gen(function*() { - * // Create a deferred * const deferred = yield* Deferred.make() - * - * // Complete the deferred using the successEffect * const isCompleted = yield* Deferred.into(successEffect, deferred) - * - * // Access the value of the deferred * const value = yield* Deferred.await(deferred) - * console.log(value) - * - * return isCompleted + * return [isCompleted, value] * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // 42 - * // true + * await Effect.runPromise(program) // => [true, 42] * ``` * - * @category Synchronization Utilities + * @category completion * @since 4.0.0 */ export const into: { diff --git a/packages/effect/src/Duration.ts b/packages/effect/src/Duration.ts index d2fe8eb41..2ffd92993 100644 --- a/packages/effect/src/Duration.ts +++ b/packages/effect/src/Duration.ts @@ -23,10 +23,12 @@ import { pipeArguments } from "./Pipeable.ts" import { hasProperty, isNumber } from "./Predicate.ts" import * as Reducer from "./Reducer.ts" -const TypeId = "~effect/time/Duration" +const TypeId = "~effect/Duration" const bigint0 = BigInt(0) const bigint1 = BigInt(1) +const bigint2 = BigInt(2) +const bigint10 = BigInt(10) const bigint24 = BigInt(24) const bigint60 = BigInt(60) const bigint1e3 = BigInt(1_000) @@ -38,8 +40,20 @@ const roundTiesAwayFromZero = (input: number): bigint => const roundMillisToNanos = (millis: number): bigint => roundTiesAwayFromZero(millis * 1_000_000) -const parseNanos = (input: string, scale: bigint): bigint => - input.includes(".") ? roundTiesAwayFromZero(Number(input) * Number(scale)) : BigInt(input) * scale +const parseNanos = (input: string, scale: bigint): bigint => { + const decimalIndex = input.indexOf(".") + if (decimalIndex === -1) return BigInt(input) * scale + + const isNegative = input[0] === "-" + const fractional = input.slice(decimalIndex + 1) + const fractionalScale = bigint10 ** BigInt(fractional.length) + const scaled = ( + BigInt(input.slice(isNegative ? 1 : 0, decimalIndex)) * fractionalScale + BigInt(fractional) + ) * scale + const rounded = scaled / fractionalScale + + (scaled % fractionalScale * bigint2 >= fractionalScale ? bigint1 : bigint0) + return isNegative ? -rounded : rounded +} const nanosToHrTime = (nanos: bigint): [seconds: number, nanos: number] => { const sign = nanos < bigint0 ? -bigint1 : bigint1 @@ -175,12 +189,12 @@ export type Input = * * **Example** (Combining duration object fields) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.fromInputUnsafe({ seconds: 30 }) - * Duration.fromInputUnsafe({ days: 1 }) - * Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) + * Duration.fromInputUnsafe({ seconds: 30 }) // => Duration.seconds(30) + * Duration.fromInputUnsafe({ days: 1 }) // => Duration.days(1) + * Duration.fromInputUnsafe({ seconds: 1, nanoseconds: 500 }) // => Duration.nanos(1_000_000_500n) * ``` * * @category models @@ -213,13 +227,13 @@ const DURATION_REGEXP = /^(-?\d+(?:\.\d+)?)\s+(nanos?|micros?|millis?|seconds?|m * * **Example** (Decoding duration inputs) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration1 = Duration.fromInputUnsafe(1000) // 1000 milliseconds - * const duration2 = Duration.fromInputUnsafe("5 seconds") - * const duration3 = Duration.fromInputUnsafe("Infinity") - * const duration4 = Duration.fromInputUnsafe([2, 500_000_000]) // 2 seconds and 500ms + * Duration.fromInputUnsafe(1000) // => Duration.millis(1000) + * Duration.fromInputUnsafe("5 seconds") // => Duration.seconds(5) + * Duration.fromInputUnsafe("Infinity") // => Duration.infinity + * Duration.fromInputUnsafe([2, 500_000_000]) // => Duration.nanos(2_500_000_000n) * ``` * * @category constructors @@ -316,12 +330,11 @@ const invalid = (input: unknown): never => { * * **Example** (Safely decoding duration inputs) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Option } from "effect" * - * Duration.fromInput(1000).pipe(Option.map(Duration.toSeconds)) // Some(1) - * - * Duration.fromInput("invalid" as any) // None + * Duration.fromInput(1000) // => Option.some(Duration.seconds(1)) + * Duration.fromInput("invalid" as any) // => Option.none() * ``` * * @category constructors @@ -338,7 +351,18 @@ const negativeInfinityDurationValue: DurationValue = { _tag: "NegativeInfinity" const DurationProto: Omit = { [TypeId]: TypeId, [Hash.symbol](this: Duration) { - return Hash.structure(this.value) + // Hash equal finite durations using the same canonical nanoseconds + // representation used by `equals`. + switch (this.value._tag) { + case "Millis": { + const nanos = this.value.millis * 1_000_000 + return Number.isFinite(nanos) ? Hash.hash(roundTiesAwayFromZero(nanos)) : Hash.number(this.value.millis) + } + case "Nanos": + return Hash.hash(this.value.nanos) + default: + return Hash.structure(this.value) + } }, [Equal.symbol](this: Duration, that: unknown): boolean { return isDuration(that) && equals(this, that) @@ -400,11 +424,11 @@ const make = (input: number | bigint): Duration => { * * **Example** (Checking for durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isDuration(Duration.seconds(1))) // true - * console.log(Duration.isDuration(1000)) // false + * Duration.isDuration(Duration.seconds(1)) // => true + * Duration.isDuration(1000) // => false * ``` * * @category guards @@ -417,14 +441,14 @@ export const isDuration = (u: unknown): u is Duration => hasProperty(u, TypeId) * * **Example** (Checking finite durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isFinite(Duration.seconds(5))) // true - * console.log(Duration.isFinite(Duration.infinity)) // false + * Duration.isFinite(Duration.seconds(5)) // => true + * Duration.isFinite(Duration.infinity) // => false * ``` * - * @category guards + * @category predicates * @since 2.0.0 */ export const isFinite = (self: Duration): boolean => @@ -435,14 +459,14 @@ export const isFinite = (self: Duration): boolean => * * **Example** (Checking for zero durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isZero(Duration.zero)) // true - * console.log(Duration.isZero(Duration.seconds(1))) // false + * Duration.isZero(Duration.zero) // => true + * Duration.isZero(Duration.seconds(1)) // => false * ``` * - * @category guards + * @category predicates * @since 3.5.0 */ export const isZero = (self: Duration): boolean => { @@ -462,15 +486,15 @@ export const isZero = (self: Duration): boolean => { * * **Example** (Checking for negative durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isNegative(Duration.seconds(-5))) // true - * console.log(Duration.isNegative(Duration.zero)) // false - * console.log(Duration.isNegative(Duration.negativeInfinity)) // true + * Duration.isNegative(Duration.seconds(-5)) // => true + * Duration.isNegative(Duration.zero) // => false + * Duration.isNegative(Duration.negativeInfinity) // => true * ``` * - * @category guards + * @category predicates * @since 4.0.0 */ export const isNegative = (self: Duration): boolean => { @@ -491,15 +515,15 @@ export const isNegative = (self: Duration): boolean => { * * **Example** (Checking for positive durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.isPositive(Duration.seconds(5))) // true - * console.log(Duration.isPositive(Duration.zero)) // false - * console.log(Duration.isPositive(Duration.infinity)) // true + * Duration.isPositive(Duration.seconds(5)) // => true + * Duration.isPositive(Duration.zero) // => false + * Duration.isPositive(Duration.infinity) // => true * ``` * - * @category guards + * @category predicates * @since 4.0.0 */ export const isPositive = (self: Duration): boolean => { @@ -520,11 +544,11 @@ export const isPositive = (self: Duration): boolean => { * * **Example** (Taking absolute duration values) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.toMillis(Duration.abs(Duration.seconds(-5))) // 5000 - * Duration.abs(Duration.negativeInfinity) === Duration.infinity // true + * Duration.abs(Duration.seconds(-5)) // => Duration.seconds(5) + * Duration.abs(Duration.negativeInfinity) // => Duration.infinity * ``` * * @category math @@ -547,11 +571,11 @@ export const abs = (self: Duration): Duration => { * * **Example** (Negating durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.toMillis(Duration.negate(Duration.seconds(5))) // -5000 - * Duration.negate(Duration.infinity) === Duration.negativeInfinity // true + * Duration.negate(Duration.seconds(5)) // => Duration.seconds(-5) + * Duration.negate(Duration.infinity) // => Duration.negativeInfinity * ``` * * @category math @@ -575,10 +599,10 @@ export const negate = (self: Duration): Duration => { * * **Example** (Referencing the zero duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.zero)) // 0 + * Duration.toMillis(Duration.zero) // => 0 * ``` * * @category constructors @@ -591,10 +615,10 @@ export const zero: Duration = make(0) * * **Example** (Referencing infinite duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.infinity)) // Infinity + * Duration.toMillis(Duration.infinity) // => Infinity * ``` * * @category constructors @@ -607,10 +631,10 @@ export const infinity: Duration = make(Infinity) * * **Example** (Referencing negative infinite duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.negativeInfinity)) // -Infinity + * Duration.toMillis(Duration.negativeInfinity) // => -Infinity * ``` * * @category constructors @@ -623,11 +647,10 @@ export const negativeInfinity: Duration = make(-Infinity) * * **Example** (Creating durations from nanoseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.nanos(BigInt(500_000_000)) - * console.log(Duration.toMillis(duration)) // 500 + * Duration.nanos(500_000_000n) // => Duration.nanos(500_000_000n) * ``` * * @category constructors @@ -640,11 +663,10 @@ export const nanos = (nanos: bigint): Duration => make(nanos) * * **Example** (Creating durations from microseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.micros(BigInt(500_000)) - * console.log(Duration.toMillis(duration)) // 500 + * Duration.micros(500_000n) // => Duration.nanos(500_000_000n) * ``` * * @category constructors @@ -657,11 +679,10 @@ export const micros = (micros: bigint): Duration => make(micros * bigint1e3) * * **Example** (Creating durations from milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.millis(1000) - * console.log(Duration.toMillis(duration)) // 1000 + * Duration.toMillis(Duration.millis(1000)) // => 1000 * ``` * * @category constructors @@ -674,11 +695,10 @@ export const millis = (millis: number): Duration => make(millis) * * **Example** (Creating durations from seconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.seconds(30) - * console.log(Duration.toMillis(duration)) // 30000 + * Duration.toMillis(Duration.seconds(30)) // => 30_000 * ``` * * @category constructors @@ -691,11 +711,10 @@ export const seconds = (seconds: number): Duration => make(seconds * 1000) * * **Example** (Creating durations from minutes) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.minutes(5) - * console.log(Duration.toMillis(duration)) // 300000 + * Duration.toMillis(Duration.minutes(5)) // => 300_000 * ``` * * @category constructors @@ -708,11 +727,10 @@ export const minutes = (minutes: number): Duration => make(minutes * 60_000) * * **Example** (Creating durations from hours) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.hours(2) - * console.log(Duration.toMillis(duration)) // 7200000 + * Duration.toMillis(Duration.hours(2)) // => 7_200_000 * ``` * * @category constructors @@ -725,11 +743,10 @@ export const hours = (hours: number): Duration => make(hours * 3_600_000) * * **Example** (Creating durations from days) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.days(1) - * console.log(Duration.toMillis(duration)) // 86400000 + * Duration.toMillis(Duration.days(1)) // => 86_400_000 * ``` * * @category constructors @@ -742,11 +759,10 @@ export const days = (days: number): Duration => make(days * 86_400_000) * * **Example** (Creating durations from weeks) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.weeks(1) - * console.log(Duration.toMillis(duration)) // 604800000 + * Duration.toMillis(Duration.weeks(1)) // => 604_800_000 * ``` * * @category constructors @@ -759,11 +775,11 @@ export const weeks = (weeks: number): Duration => make(weeks * 604_800_000) * * **Example** (Converting durations to milliseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMillis(Duration.seconds(5))) // 5000 - * console.log(Duration.toMillis(Duration.minutes(2))) // 120000 + * Duration.toMillis(Duration.seconds(5)) // => 5000 + * Duration.toMillis(Duration.minutes(2)) // => 120_000 * ``` * * @category getters @@ -782,11 +798,11 @@ export const toMillis = (self: Input): number => * * **Example** (Converting durations to seconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toSeconds(Duration.millis(5000))) // 5 - * console.log(Duration.toSeconds(Duration.minutes(2))) // 120 + * Duration.toSeconds(Duration.millis(5000)) // => 5 + * Duration.toSeconds(Duration.minutes(2)) // => 120 * ``` * * @category getters @@ -805,11 +821,11 @@ export const toSeconds = (self: Input): number => * * **Example** (Converting durations to minutes) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toMinutes(Duration.seconds(120))) // 2 - * console.log(Duration.toMinutes(Duration.hours(1))) // 60 + * Duration.toMinutes(Duration.seconds(120)) // => 2 + * Duration.toMinutes(Duration.hours(1)) // => 60 * ``` * * @category getters @@ -828,11 +844,11 @@ export const toMinutes = (self: Input): number => * * **Example** (Converting durations to hours) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toHours(Duration.minutes(120))) // 2 - * console.log(Duration.toHours(Duration.days(1))) // 24 + * Duration.toHours(Duration.minutes(120)) // => 2 + * Duration.toHours(Duration.days(1)) // => 24 * ``` * * @category getters @@ -851,11 +867,11 @@ export const toHours = (self: Input): number => * * **Example** (Converting durations to days) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toDays(Duration.hours(48))) // 2 - * console.log(Duration.toDays(Duration.weeks(1))) // 7 + * Duration.toDays(Duration.hours(48)) // => 2 + * Duration.toDays(Duration.weeks(1)) // => 7 * ``` * * @category getters @@ -874,11 +890,11 @@ export const toDays = (self: Input): number => * * **Example** (Converting durations to weeks) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * console.log(Duration.toWeeks(Duration.days(14))) // 2 - * console.log(Duration.toWeeks(Duration.days(7))) // 1 + * Duration.toWeeks(Duration.days(14)) // => 2 + * Duration.toWeeks(Duration.days(7)) // => 1 * ``` * * @category getters @@ -911,12 +927,10 @@ export const toWeeks = (self: Input): number => * * **Example** (Reading nanoseconds unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.seconds(2) - * const nanos = Duration.toNanosUnsafe(duration) - * console.log(nanos) // 2000000000n + * Duration.toNanosUnsafe(Duration.seconds(2)) // => 2_000_000_000n * * // Duration.toNanosUnsafe(Duration.infinity) * // throws Error: "Cannot convert infinite duration to nanos" @@ -947,13 +961,11 @@ export const toNanosUnsafe = (input: Input): bigint => { * * **Example** (Safely reading nanoseconds) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Option } from "effect" * - * Duration.toNanos(Duration.seconds(1)) // Some(1000000000n) - * - * Duration.toNanos(Duration.infinity) // None - * Option.getOrUndefined(Duration.toNanos(Duration.infinity)) // undefined + * Duration.toNanos(Duration.seconds(1)) // => Option.some(1_000_000_000n) + * Duration.toNanos(Duration.infinity) // => Option.none() * ``` * * @category getters @@ -966,12 +978,10 @@ export const toNanos: (self: Input) => Option.Option = Option.liftThrowa * * **Example** (Converting durations to high-resolution time) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const duration = Duration.millis(1500) - * const hrtime = Duration.toHrTime(duration) - * console.log(hrtime) // [1, 500000000] + * Duration.toHrTime(Duration.millis(1500)) // => [1, 500_000_000] * ``` * * @category getters @@ -1002,15 +1012,14 @@ export const toHrTime = (input: Input): [seconds: number, nanos: number] => { * * **Example** (Pattern matching on duration representations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const result = Duration.match(Duration.seconds(5), { + * Duration.match(Duration.seconds(5), { * onMillis: (millis) => `${millis} milliseconds`, * onNanos: (nanos) => `${nanos} nanoseconds`, * onInfinity: () => "infinite" - * }) - * console.log(result) // "5000 milliseconds" + * }) // => "5000 milliseconds" * ``` * * @category pattern matching @@ -1060,15 +1069,14 @@ export const match: { * * **Example** (Pattern matching on duration pairs) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const sum = Duration.matchPair(Duration.seconds(3), Duration.seconds(2), { + * Duration.matchPair(Duration.seconds(3), Duration.seconds(2), { * onMillis: (a, b) => a + b, * onNanos: (a, b) => Number(a + b), * onInfinity: () => Infinity - * }) - * console.log(sum) // 5000 + * }) // => 5000 * ``` * * @category pattern matching @@ -1123,7 +1131,7 @@ export const matchPair: { * * **Example** (Sorting durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * * const durations = [ @@ -1131,8 +1139,7 @@ export const matchPair: { * Duration.seconds(1), * Duration.seconds(2) * ] - * const sorted = durations.sort((a, b) => Duration.Order(a, b)) - * console.log(sorted.map(Duration.toSeconds)) // [1, 2, 3] + * durations.sort((a, b) => Duration.Order(a, b)).map(Duration.toSeconds) // => [1, 2, 3] * ``` * * @category instances @@ -1173,14 +1180,13 @@ export const Order: order.Order = order.make((self, that) => * * **Example** (Checking duration ranges) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isInRange = Duration.between(Duration.seconds(3), { + * Duration.between(Duration.seconds(3), { * minimum: Duration.seconds(2), * maximum: Duration.seconds(5) - * }) - * console.log(isInRange) // true + * }) // => true * ``` * * @see {@link clamp} for constraining a duration to a range @@ -1200,11 +1206,10 @@ export const between: { * * **Example** (Comparing durations for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isEqual = Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) - * console.log(isEqual) // true + * Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) // => true * ``` * * @category instances @@ -1222,11 +1227,10 @@ export const Equivalence: Equ.Equivalence = (self, that) => * * **Example** (Selecting the shorter duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const shorter = Duration.min(Duration.seconds(5), Duration.seconds(3)) - * console.log(Duration.toSeconds(shorter)) // 3 + * Duration.min(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(3) * ``` * * @category ordering @@ -1242,11 +1246,10 @@ export const min: { * * **Example** (Selecting the longer duration) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const longer = Duration.max(Duration.seconds(5), Duration.seconds(3)) - * console.log(Duration.toSeconds(longer)) // 5 + * Duration.max(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(5) * ``` * * @category ordering @@ -1262,14 +1265,13 @@ export const max: { * * **Example** (Clamping durations to a range) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const clamped = Duration.clamp(Duration.seconds(10), { + * Duration.clamp(Duration.seconds(10), { * minimum: Duration.seconds(2), * maximum: Duration.seconds(5) - * }) - * console.log(Duration.toSeconds(clamped)) // 5 + * }) // => Duration.seconds(5) * ``` * * @category ordering @@ -1291,13 +1293,11 @@ export const clamp: { * * **Example** (Safely dividing durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Option } from "effect" * - * const d = Duration.divide(Duration.seconds(10), 2) - * console.log(Option.map(d, Duration.toSeconds)) // Some(5) - * - * Duration.divide(Duration.seconds(10), 0) // None + * Duration.divide(Duration.seconds(10), 2) // => Option.some(Duration.seconds(5)) + * Duration.divide(Duration.seconds(10), 0) // => Option.none() * ``` * * @category math @@ -1345,14 +1345,11 @@ export const divide: { * * **Example** (Dividing durations unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const half = Duration.divideUnsafe(Duration.seconds(10), 2) - * console.log(Duration.toSeconds(half)) // 5 - * - * const infinite = Duration.divideUnsafe(Duration.seconds(10), 0) - * console.log(Duration.toMillis(infinite)) // Infinity + * Duration.divideUnsafe(Duration.seconds(10), 2) // => Duration.seconds(5) + * Duration.divideUnsafe(Duration.seconds(10), 0) // => Duration.infinity * ``` * * @category math @@ -1399,11 +1396,10 @@ export const divideUnsafe: { * * **Example** (Multiplying durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const doubled = Duration.times(Duration.seconds(5), 2) - * console.log(Duration.toSeconds(doubled)) // 10 + * Duration.times(Duration.seconds(5), 2) // => Duration.seconds(10) * ``` * * @category math @@ -1437,11 +1433,10 @@ export const times: { * * **Example** (Subtracting durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const result = Duration.subtract(Duration.seconds(10), Duration.seconds(3)) - * console.log(Duration.toSeconds(result)) // 7 + * Duration.subtract(Duration.seconds(10), Duration.seconds(3)) // => Duration.seconds(7) * ``` * * @category math @@ -1481,11 +1476,10 @@ export const subtract: { * * **Example** (Adding durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const total = Duration.sum(Duration.seconds(5), Duration.seconds(3)) - * console.log(Duration.toSeconds(total)) // 8 + * Duration.sum(Duration.seconds(5), Duration.seconds(3)) // => Duration.seconds(8) * ``` * * @category math @@ -1518,11 +1512,10 @@ export const sum: { * * **Example** (Comparing durations with less than) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isLess = Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) - * console.log(isLess) // true + * Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) // => true * ``` * * @category predicates @@ -1538,14 +1531,13 @@ export const isLessThan: { * * **Example** (Comparing durations with less than or equal) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isLessOrEqual = Duration.isLessThanOrEqualTo( + * Duration.isLessThanOrEqualTo( * Duration.seconds(5), * Duration.seconds(5) - * ) - * console.log(isLessOrEqual) // true + * ) // => true * ``` * * @category predicates @@ -1561,11 +1553,10 @@ export const isLessThanOrEqualTo: { * * **Example** (Comparing durations with greater than) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isGreater = Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) - * console.log(isGreater) // true + * Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) // => true * ``` * * @category predicates @@ -1581,14 +1572,13 @@ export const isGreaterThan: { * * **Example** (Comparing durations with greater than or equal) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isGreaterOrEqual = Duration.isGreaterThanOrEqualTo( + * Duration.isGreaterThanOrEqualTo( * Duration.seconds(5), * Duration.seconds(5) - * ) - * console.log(isGreaterOrEqual) // true + * ) // => true * ``` * * @category predicates @@ -1604,11 +1594,10 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking duration equality) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * const isEqual = Duration.equals(Duration.seconds(5), Duration.millis(5000)) - * console.log(isEqual) // true + * Duration.equals(Duration.seconds(5), Duration.millis(5000)) // => true * ``` * * @category predicates @@ -1630,7 +1619,7 @@ export const equals: { * * **Example** (Decomposing durations into parts) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * * // Create a complex duration by adding multiple parts @@ -1641,28 +1630,10 @@ export const equals: { * ), * Duration.millis(123) * ) - * const components = Duration.parts(duration) - * console.log(components) - * // { - * // days: 1, - * // hours: 2, - * // minutes: 30, - * // seconds: 45, - * // millis: 123, - * // nanos: 0 - * // } + * Duration.parts(duration) // => ({ days: 1, hours: 2, minutes: 30, seconds: 45, millis: 123, nanos: 0 }) * * const complex = Duration.sum(Duration.hours(25), Duration.minutes(90)) - * const complexParts = Duration.parts(complex) - * console.log(complexParts) - * // { - * // days: 1, - * // hours: 2, - * // minutes: 30, - * // seconds: 0, - * // millis: 0, - * // nanos: 0 - * // } + * Duration.parts(complex) // => ({ days: 1, hours: 2, minutes: 30, seconds: 0, millis: 0, nanos: 0 }) * ``` * * @category converting @@ -1722,11 +1693,11 @@ export const parts = (self: Duration): { * * **Example** (Formatting durations) * - * ```ts + * ```ts import.meta.vitest * import { Duration } from "effect" * - * Duration.format(Duration.millis(1000)) // "1s" - * Duration.format(Duration.millis(1001)) // "1s 1ms" + * Duration.format(Duration.millis(1000)) // => "1s" + * Duration.format(Duration.millis(1001)) // => "1s 1ms" * ``` * * @category converting diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index f15b5f49f..b3139bfe9 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -15,7 +15,7 @@ import type * as Cause from "./Cause.ts" import type { Clock } from "./Clock.ts" import * as Context from "./Context.ts" import * as Duration from "./Duration.ts" -import type { ExecutionPlan } from "./ExecutionPlan.ts" +import type * as ExecutionPlan from "./ExecutionPlan.ts" import * as Exit from "./Exit.ts" import type { Fiber } from "./Fiber.ts" import type * as Filter from "./Filter.ts" @@ -26,6 +26,7 @@ import * as core from "./internal/core.ts" import * as internal from "./internal/effect.ts" import * as internalExecutionPlan from "./internal/executionPlan.ts" import * as internalLayer from "./internal/layer.ts" +import * as InternalRecord from "./internal/record.ts" import * as internalRequest from "./internal/request.ts" import * as internalSchedule from "./internal/schedule.ts" import type * as Layer from "./Layer.ts" @@ -39,7 +40,7 @@ import { CurrentLogAnnotations, CurrentLogSpans } from "./References.ts" import type * as Request from "./Request.ts" import type { RequestResolver } from "./RequestResolver.ts" import type * as Result from "./Result.ts" -import type { Schedule } from "./Schedule.ts" +import type { Metadata as ScheduleMetadata, Schedule } from "./Schedule.ts" import type { Scheduler } from "./Scheduler.ts" import type { Scope } from "./Scope.ts" import type { @@ -137,7 +138,7 @@ export interface EffectUnify { /** * Type lambda used to represent `Effect` in higher-kinded APIs. * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface EffectTypeLambda extends TypeLambda { @@ -167,7 +168,7 @@ export interface Variance { * @see {@link Error} for extracting the failure type from the same `Effect` * @see {@link Services} for extracting the required services from the same `Effect` * - * @category models + * @category utility types * @since 2.0.0 */ export type Success = T extends Effect ? _A @@ -188,7 +189,7 @@ export type Success = T extends Effect ? _A * @see {@link Success} for extracting the success value type instead * @see {@link Services} for extracting the required services type instead * - * @category models + * @category utility types * @since 2.0.0 */ export type Error = T extends Effect ? _E @@ -205,7 +206,7 @@ export type Error = T extends Effect ? _E * @see {@link Success} for extracting the success value type instead * @see {@link Error} for extracting the failure type instead * - * @category models + * @category utility types * @since 4.0.0 */ export type Services = T extends Effect ? _R @@ -216,11 +217,11 @@ export type Services = T extends Effect ? _R * * **Example** (Checking whether a value is an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * console.log(Effect.isEffect(Effect.succeed(1))) // true - * console.log(Effect.isEffect("hello")) // false + * Effect.isEffect(Effect.succeed(1)) // => true + * Effect.isEffect("hello") // => false * ``` * * @category guards @@ -330,13 +331,8 @@ export declare namespace All { ] ? Mode extends true ? Result.Result<_A, _E> : _A : never }, - Mode extends true ? never - : keyof T extends never ? never - : T[keyof T] extends Effect ? _E - : never, - keyof T extends never ? never - : T[keyof T] extends Effect ? _R - : never + Mode extends true ? never : Error>, + Services> > : never @@ -376,6 +372,8 @@ export declare namespace All { : [Arg] extends [Iterable] ? ReturnIterable, IsResult> : [Arg] extends [Record] ? ReturnObject, IsResult> : never + + type ObjectValues = T extends unknown ? T[keyof T] : never } /** @@ -403,112 +401,91 @@ export declare namespace All { * * **Example** (Collecting tuple results in order) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const tupleOfEffects = [ - * Effect.succeed(42).pipe(Effect.tap(Console.log)), - * Effect.succeed("Hello").pipe(Effect.tap(Console.log)) + * Effect.succeed(42), + * Effect.succeed("Hello") * ] as const * * // ┌─── Effect<[number, string], never, never> * // ▼ * const resultsAsTuple = Effect.all(tupleOfEffects) * - * Effect.runPromise(resultsAsTuple).then(console.log) - * // Output: - * // 42 - * // Hello - * // [ 42, 'Hello' ] + * await Effect.runPromise(resultsAsTuple) // => [42, "Hello"] * ``` * * **Example** (Collecting iterable results in order) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const iterableOfEffects: Iterable> = [1, 2, 3].map( - * (n) => Effect.succeed(n).pipe(Effect.tap(Console.log)) + * Effect.succeed * ) * * // ┌─── Effect * // ▼ * const resultsAsArray = Effect.all(iterableOfEffects) * - * Effect.runPromise(resultsAsArray).then(console.log) - * // Output: - * // 1 - * // 2 - * // 3 - * // [ 1, 2, 3 ] + * await Effect.runPromise(resultsAsArray) // => [1, 2, 3] * ``` * * **Example** (Collecting struct results by key) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const structOfEffects = { - * a: Effect.succeed(42).pipe(Effect.tap(Console.log)), - * b: Effect.succeed("Hello").pipe(Effect.tap(Console.log)) + * a: Effect.succeed(42), + * b: Effect.succeed("Hello") * } * * // ┌─── Effect<{ a: number; b: string; }, never, never> * // ▼ * const resultsAsStruct = Effect.all(structOfEffects) * - * Effect.runPromise(resultsAsStruct).then(console.log) - * // Output: - * // 42 - * // Hello - * // { a: 42, b: 'Hello' } + * await Effect.runPromise(resultsAsStruct) // => { a: 42, b: "Hello" } * ``` * * **Example** (Collecting record results by key) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const recordOfEffects: Record> = { - * key1: Effect.succeed(1).pipe(Effect.tap(Console.log)), - * key2: Effect.succeed(2).pipe(Effect.tap(Console.log)) + * key1: Effect.succeed(1), + * key2: Effect.succeed(2) * } * * // ┌─── Effect<{ [x: string]: number; }, never, never> * // ▼ * const resultsAsRecord = Effect.all(recordOfEffects) * - * Effect.runPromise(resultsAsRecord).then(console.log) - * // Output: - * // 1 - * // 2 - * // { key1: 1, key2: 2 } + * await Effect.runPromise(resultsAsRecord) // => { key1: 1, key2: 2 } * ``` * * **Example** (Stopping on the first failure) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * const program = Effect.all([ - * Effect.succeed("Task1").pipe(Effect.tap(Console.log)), - * Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), + * Effect.succeed("Task1").pipe(Effect.tap(record)), + * Effect.fail("Task2: Oh no!").pipe(Effect.tap(record)), * // Won't execute due to earlier failure - * Effect.succeed("Task3").pipe(Effect.tap(Console.log)) + * Effect.succeed("Task3").pipe(Effect.tap(record)) * ]) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: - * // Task1 - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { _id: 'Cause', _tag: 'Fail', failure: 'Task2: Oh no!' } - * // } + * const outcome = await Effect.runPromiseExit(program) + * const observation = [output, outcome] // => [["Task1"], Exit.fail("Task2: Oh no!")] * ``` * * @see {@link forEach} for iterating over elements and applying an effect. - * @category collecting + * @category combining * @since 2.0.0 */ export const all: < @@ -539,18 +516,17 @@ export const all: < * * **Example** (Separating successes and failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.partition([0, 1, 2, 3], (n) => * n % 2 === 0 ? Effect.fail(`${n} is even`) : Effect.succeed(n) * ) * - * Effect.runPromise(program).then(console.log) - * // [ ["0 is even", "2 is even"], [1, 3] ] + * await Effect.runPromise(program) // => [['0 is even', '2 is even'], [1, 3]] * ``` * - * @category collecting + * @category filtering * @since 2.0.0 */ export const partition: { @@ -565,6 +541,55 @@ export const partition: { ): Effect<[excluded: Array, satisfying: Array], never, R> } = internal.partition +/** + * Reduces elements from left to right with an effectful accumulator function. + * + * **When to use** + * + * Use when each accumulation step is effectful and must run sequentially in + * iteration order. + * + * **Details** + * + * The accumulator function receives the current accumulator, the current + * element, and its zero-based index. The `zero` function is evaluated each + * time the effect runs. An empty iterable succeeds with its result. If a step + * fails, remaining elements are not processed. + * + * **Example** (Summing values sequentially) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * + * const program = Effect.reduce( + * [1, 2, 3], + * () => 0, + * (total, value, index) => + * Effect.sync(() => { output.push(`Adding ${value} at index ${index}`) }).pipe( + * Effect.as(total + value) + * ) + * ) + * + * void output.push(await Effect.runPromise(program)) + * output // => ["Adding 1 at index 0", "Adding 2 at index 1", "Adding 3 at index 2", 6] + * ``` + * + * @category folding + * @since 2.0.0 + */ +export const reduce: { + ( + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect + ): (elements: Iterable) => Effect + ( + elements: Iterable, + zero: LazyArg, + f: (z: Z, a: A, i: number) => Effect + ): Effect +} = internal.reduce + /** * Applies an effectful function to each element and accumulates all failures. * @@ -579,28 +604,17 @@ export const partition: { * * **Example** (Validating every element) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * const program = Effect.validate([0, 1, 2, 3], (n) => * n % 2 === 0 ? Effect.fail(`${n} is even`) : Effect.succeed(n) * ) * - * Effect.runPromiseExit(program).then(console.log) - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { - * // _id: 'Cause', - * // reasons: [ - * // { _id: 'Reason', _tag: 'Fail', error: '0 is even' }, - * // { _id: 'Reason', _tag: 'Fail', error: '2 is even' } - * // ] - * // } - * // } + * await Effect.runPromiseExit(program) // => Exit.fail(["0 is even", "2 is even"]) * ``` * - * @category error accumulation + * @category validation * @since 2.0.0 */ export const validate: { @@ -646,16 +660,15 @@ export const validate: { * * **Example** (Finding the first successful match) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.findFirst([1, 2, 3, 4], (n) => Effect.succeed(n > 2)) * - * Effect.runPromise(program).then(console.log) - * // { _id: 'Option', _tag: 'Some', value: 3 } + * await Effect.runPromise(program) // => Option.some(3) * ``` * - * @category collecting + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -683,7 +696,7 @@ export const findFirst: { * * @see {@link findFirst} for the simpler effectful predicate-based variant * - * @category collecting + * @category searching * @since 4.0.0 */ export const findFirstFilter: { @@ -724,57 +737,49 @@ export const findFirstFilter: { * * **Example** (Mapping over an iterable with effects) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const result = Effect.forEach( * [1, 2, 3, 4, 5], * (n, index) => - * Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)) + * Effect.sync(() => { output.push(`Currently at index ${index}`) }).pipe(Effect.as(n * 2)) * ) * - * Effect.runPromise(result).then(console.log) - * // Output: - * // Currently at index 0 - * // Currently at index 1 - * // Currently at index 2 - * // Currently at index 3 - * // Currently at index 4 - * // [ 2, 4, 6, 8, 10 ] + * void output.push(await Effect.runPromise(result)) + * output // => ["Currently at index 0", "Currently at index 1", "Currently at index 2", "Currently at index 3", "Currently at index 4", [2, 4, 6, 8, 10]] * ``` * * **Example** (Running effects without collecting results) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * // Apply effects but discard the results * const result = Effect.forEach( * [1, 2, 3, 4, 5], * (n, index) => - * Console.log(`Currently at index ${index}`).pipe(Effect.as(n * 2)), + * Effect.sync(() => { output.push(`Currently at index ${index}`) }).pipe(Effect.as(n * 2)), * { discard: true } * ) * - * Effect.runPromise(result).then(console.log) - * // Output: - * // Currently at index 0 - * // Currently at index 1 - * // Currently at index 2 - * // Currently at index 3 - * // Currently at index 4 - * // undefined + * void output.push(await Effect.runPromise(result)) + * output // => ["Currently at index 0", "Currently at index 1", "Currently at index 2", "Currently at index 3", "Currently at index 4", undefined] * ``` * * @see {@link all} for combining multiple effects into one. - * @category collecting + * @category sequencing * @since 2.0.0 */ export const forEach: { - , const Discard extends boolean = false>( - f: (a: Arr.ReadonlyArray.Infer, i: number) => Effect, + = Iterable, const Discard extends boolean = false>( + f: (a: A, i: number) => Effect, options?: { readonly concurrency?: Concurrency | undefined; readonly discard?: Discard | undefined } | undefined - ): (self: S) => Effect : void, E, R> + ): ( + self: [S] extends [never] ? Iterable : S + ) => Effect : void, E, R> , const Discard extends boolean = false>( self: S, f: (a: Arr.ReadonlyArray.Infer, i: number) => Effect, @@ -782,32 +787,56 @@ export const forEach: { ): Effect : void, E, R> } = internal.forEach +/** + * Returns the first element of the iterable produced by an effect, or fails + * with `NoSuchElementError` if the iterable is empty. + * + * **When to use** + * + * Use when an effect produces a collection that must contain at least one + * element and absence should be represented in the typed error channel. + * + * **Example** (Getting the first element) + * + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * + * const first = await Effect.runPromise(Effect.head(Effect.succeed([1, 2, 3]))) + * first // => 1 + * + * const empty = Effect.head(Effect.succeed([] as Array)).pipe(Effect.catchNoSuchElement) + * await Effect.runPromise(empty) // => Option.none() + * ``` + * + * @category getters + * @since 2.0.0 + */ +export const head: ( + self: Effect, E, R> +) => Effect = internal.head + /** * Executes a body effect repeatedly while a condition holds true. * * **Example** (Repeating an effectful loop) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * let counter = 0 * * const program = Effect.whileLoop({ * while: () => counter < 5, * body: () => Effect.sync(() => ++counter), - * step: (n) => console.log(`Current count: ${n}`) + * step: (n) => void output.push(`Current count: ${n}`) * }) * - * Effect.runPromise(program) - * // Output: - * // Current count: 1 - * // Current count: 2 - * // Current count: 3 - * // Current count: 4 - * // Current count: 5 + * await Effect.runPromise(program) + * output // => ["Current count: 1", "Current count: 2", "Current count: 3", "Current count: 4", "Current count: 5"] * ``` * - * @category collecting + * @category repetition * @since 2.0.0 */ export const whileLoop: (options: { @@ -844,22 +873,16 @@ export const whileLoop: (options: { * * **Example** (Wrapping a non-rejecting Promise) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const delay = (message: string) => - * Effect.promise( - * () => - * new Promise((resolve) => { - * setTimeout(() => { - * resolve(message) - * }, 2000) - * }) - * ) + * const succeedAsync = (message: string) => + * Effect.promise(() => Promise.resolve(message)) * * // ┌─── Effect * // ▼ - * const program = delay("Async operation completed successfully!") + * const program = succeedAsync("Async operation completed successfully!") + * await Effect.runPromise(program) // => "Async operation completed successfully!" * ``` * * @see {@link tryPromise} for a version that can handle failures. @@ -903,48 +926,49 @@ export const promise: ( * * **Example** (Wrapping a fetch request that may fail) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const getTodo = (id: number) => - * // Will catch any errors and propagate them as UnknownError - * Effect.tryPromise(() => - * fetch(`https://jsonplaceholder.typicode.com/todos/${id}`) - * ) + * Effect.tryPromise(() => Promise.resolve({ id, completed: false })) * - * // ┌─── Effect + * // ┌─── Effect<{ id: number; completed: boolean }, UnknownError, never> * // ▼ * const program = getTodo(1) + * await Effect.runPromise(program) // => { id: 1, completed: false } * ``` * * **Example** (Mapping Promise rejections to a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class TodoFetchError extends Data.TaggedError("TodoFetchError")<{ readonly cause: unknown }> {} * * const getTodo = (id: number) => * Effect.tryPromise({ - * try: () => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`), + * try: () => Promise.reject(`Todo ${id} is unavailable`), * // remap the error * catch: (cause) => new TodoFetchError({ cause }) * }) * - * // ┌─── Effect + * // ┌─── Effect * // ▼ - * const program = getTodo(1) + * const program = Effect.flip(getTodo(1)) + * const error = await Effect.runPromise(program) + * error._tag // => "TodoFetchError" * ``` * * @see {@link promise} if the effectful computation is asynchronous and does not throw errors. * @category constructors * @since 2.0.0 */ -export const tryPromise: ( - options: - | { readonly try: (signal: AbortSignal) => PromiseLike; readonly catch: (error: unknown) => E } - | ((signal: AbortSignal) => PromiseLike) -) => Effect = internal.tryPromise +export const tryPromise: { + (options: (signal: AbortSignal) => PromiseLike): Effect + ( + options: { readonly try: (signal: AbortSignal) => PromiseLike; readonly catch: (error: unknown) => E } + ): Effect +} = internal.tryPromise /** * Creates an `Effect` that always succeeds with a given value. @@ -956,7 +980,7 @@ export const tryPromise: ( * * **Example** (Creating a successful effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Creating an effect that represents a successful scenario @@ -964,6 +988,7 @@ export const tryPromise: ( * // ┌─── Effect * // ▼ * const success = Effect.succeed(42) + * Effect.runSync(success) // => 42 * ``` * * @see {@link fail} to create an effect that represents a failure. @@ -977,13 +1002,12 @@ export const succeed: (value: A) => Effect = internal.succeed * * **Example** (Succeeding with Option.none) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.succeedNone * - * Effect.runPromise(program).then(console.log) - * // Output: { _id: 'Option', _tag: 'None' } + * Effect.runSync(program) // => Option.none() * ``` * * @category constructors @@ -996,13 +1020,12 @@ export const succeedNone: Effect> = internal.succeedNone * * **Example** (Succeeding with Option.some) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.succeedSome(42) * - * Effect.runPromise(program).then(console.log) - * // Output: { _id: 'Option', _tag: 'Some', value: 42 } + * Effect.runSync(program) // => Option.some(42) * ``` * * @category constructors @@ -1028,7 +1051,7 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * * **Example** (Lazily evaluating side effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * let i = 0 @@ -1037,16 +1060,16 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * * const good = Effect.suspend(() => Effect.succeed(i++)) * - * console.log(Effect.runSync(bad)) // Output: 0 - * console.log(Effect.runSync(bad)) // Output: 0 + * Effect.runSync(bad) // => 0 + * Effect.runSync(bad) // => 0 * - * console.log(Effect.runSync(good)) // Output: 1 - * console.log(Effect.runSync(good)) // Output: 2 + * Effect.runSync(good) // => 1 + * Effect.runSync(good) // => 2 * ``` * * **Example** (Suspending recursive Fibonacci evaluation) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const blowsUp = (n: number): Effect.Effect => @@ -1066,13 +1089,12 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * (a, b) => a + b * ) * - * console.log(Effect.runSync(allGood(32))) - * // Output: 3524578 + * Effect.runSync(allGood(16)) // => 1597 * ``` * * **Example** (Helping TypeScript infer recursive effect types) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Without suspend, TypeScript may struggle with type inference. @@ -1093,6 +1115,8 @@ export const succeedSome: (value: A) => Effect> = internal.succeedS * ? Effect.fail(new Error("Cannot divide by zero")) * : Effect.succeed(a / b) * ) + * + * Effect.runSync(withSuspend(6, 2)) // => 3 * ``` * * @category constructors @@ -1121,17 +1145,20 @@ export const suspend: ( * * **Example** (Capturing synchronous logging in an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const log = (message: string) => * Effect.sync(() => { - * console.log(message) // side effect + * void output.push(message) // side effect * }) * * // ┌─── Effect * // ▼ * const program = log("Hello, World!") + * Effect.runSync(program) + * output // => ["Hello, World!"] * ``` * * @see {@link try_ | try} for a version that can handle failures. @@ -1179,19 +1206,20 @@ export { * * **Example** (Integrating callback APIs) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * - * const delay = (ms: number) => + * const fromCallback = (message: string) => * Effect.callback((resume) => { - * const timeoutId = setTimeout(() => { + * queueMicrotask(() => { + * void output.push(message) * resume(Effect.void) - * }, ms) - * // Cleanup function for interruption - * return Effect.sync(() => clearTimeout(timeoutId)) + * }) * }) * - * const program = delay(1000) + * await Effect.runPromise(fromCallback("callback completed")) + * output // => ["callback completed"] * ``` * * @category constructors @@ -1211,17 +1239,11 @@ export const callback: ( * * **Example** (Creating a never-ending effect) * - * ```ts - * import { Effect } from "effect" - * - * // This effect will never complete - * const program = Effect.never - * - * // This will run forever (or until interrupted) - * // Effect.runPromise(program) // Never resolves + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * // Use with timeout for practical applications - * const timedProgram = Effect.timeout(program, "1 second") + * const program = Effect.timeoutOption(Effect.never, 0) + * await Effect.runPromise(program) // => Option.none() * ``` * * @category constructors @@ -1235,7 +1257,7 @@ export const never: Effect = internal.never * * **Example** (Starting do notation) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" * * const program = pipe( @@ -1244,9 +1266,11 @@ export const never: Effect = internal.never * Effect.bind("y", ({ x }) => Effect.succeed(x + 1)), * Effect.let("sum", ({ x, y }) => x + y) * ) + * + * Effect.runSync(program) // => { x: 2, y: 3, sum: 5 } * ``` * - * @category do notation + * @category constructors * @since 2.0.0 */ export const Do: Effect<{}> = internal.Do @@ -1263,7 +1287,7 @@ export const Do: Effect<{}> = internal.Do * @see {@link Do} for starting from an empty accumulated record * @see {@link bind} for adding fields produced by effects * - * @category do notation + * @category mapping * @since 2.0.0 */ export const bindTo: { @@ -1305,7 +1329,7 @@ export { * @see {@link Do} for starting from an empty accumulated record * @see {@link gen} for sequencing without accumulating a record * - * @category do notation + * @category mapping * @since 2.0.0 */ let_ as let @@ -1335,7 +1359,7 @@ export { * @see {@link bindTo} for naming the success value of an existing effect * @see {@link gen} for generator-based sequencing without accumulating a record * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bind: { @@ -1368,7 +1392,7 @@ export const bind: { * * **Example** (Sequencing effects with generators) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} @@ -1397,6 +1421,8 @@ export const bind: { * const finalAmount = addServiceCharge(discountedAmount) * return `Final amount to charge: ${finalAmount}` * }) + * + * await Effect.runPromise(program) // => "Final amount to charge: 96" * ``` * * @category constructors @@ -1459,7 +1485,7 @@ export declare namespace gen { * * **Example** (Creating a failed effect) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class OperationFailedError extends Data.TaggedError("OperationFailedError")<{}> {} @@ -1469,6 +1495,7 @@ export declare namespace gen { * const failure = Effect.fail( * new OperationFailedError() * ) + * Effect.runSync(Effect.flip(failure))._tag // => "OperationFailedError" * ``` * * @see {@link succeed} to create an effect that represents a successful value. @@ -1490,15 +1517,14 @@ export const fail: (error: E) => Effect = internal.fail * * **Example** (Lazily creating failures) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * - * class ProgramError extends Data.TaggedError("ProgramError")<{ readonly failedAt: Date }> {} + * class ProgramError extends Data.TaggedError("ProgramError")<{ readonly operation: string }> {} * - * const program = Effect.failSync(() => new ProgramError({ failedAt: new Date() })) + * const program = Effect.failSync(() => new ProgramError({ operation: "sync" })) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: { _id: 'Exit', _tag: 'Failure', cause: ... } + * Effect.runSync(Effect.flip(program)).operation // => "sync" * ``` * * @category constructors @@ -1522,15 +1548,14 @@ export const failSync: (evaluate: LazyArg) => Effect = internal. * * **Example** (Failing with a full Cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const program = Effect.failCause( * Cause.fail("Network error") * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: { _id: 'Exit', _tag: 'Failure', cause: ... } + * Effect.runSync(Effect.flip(program)) // => "Network error" * ``` * * @category constructors @@ -1551,15 +1576,14 @@ export const failCause: (cause: Cause.Cause) => Effect = interna * * **Example** (Lazily creating a Cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const program = Effect.failCauseSync(() => * Cause.fail("Error computed at runtime") * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: { _id: 'Exit', _tag: 'Failure', cause: ... } + * Effect.runSync(Effect.flip(program)) // => "Error computed at runtime" * ``` * * @category constructors @@ -1588,22 +1612,20 @@ export const failCauseSync: ( * * **Example** (Failing on division by zero) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * + * const defect = new Error("Cannot divide by zero") * const divide = (a: number, b: number) => * b === 0 - * ? Effect.die(new Error("Cannot divide by zero")) + * ? Effect.die(defect) * : Effect.succeed(a / b) * * // ┌─── Effect * // ▼ * const program = divide(1, 0) * - * Effect.runPromise(program).catch(console.error) - * // Output: - * // (FiberFailure) Error: Cannot divide by zero - * // ...stack trace... + * Effect.runSyncExit(program) // => Exit.die(defect) * ``` * * @category constructors @@ -1611,12 +1633,13 @@ export const failCauseSync: ( */ export const die: (defect: unknown) => Effect = internal.die -const try_: ( - options: { +const try_: { + (options: LazyArg): Effect + (options: { readonly try: LazyArg readonly catch: (error: unknown) => E - } | LazyArg -) => Effect = internal.try + }): Effect +} = internal.try export { /** @@ -1646,23 +1669,23 @@ export { * * **Example** (Parsing JSON) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const parseJSON = (input: string) => * Effect.try(() => JSON.parse(input)) * * // Success case - * Effect.runPromise(parseJSON("{\"name\": \"Alice\"}")).then(console.log) - * // Output: { name: "Alice" } + * await Effect.runPromise(parseJSON("{\"name\": \"Alice\"}")) // => { name: 'Alice' } * * // Failure case maps the thrown value to UnknownError - * Effect.runPromiseExit(parseJSON("invalid json")).then(console.log) + * const exit = await Effect.runPromiseExit(parseJSON("invalid json")) + * exit._tag // => "Failure" * ``` * * **Example** (Mapping exceptions to a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class JsonParsingError extends Data.TaggedError("JsonParsingError")<{ readonly cause: unknown }> {} @@ -1673,8 +1696,8 @@ export { * catch: (cause) => new JsonParsingError({ cause }) * }) * - * Effect.runPromiseExit(parseJSON("invalid json")).then(console.log) - * // Output: Exit.failure with custom Error message + * const error = await Effect.runPromise(Effect.flip(parseJSON("invalid json"))) + * error._tag // => "JsonParsingError" * ``` * * @see {@link sync} if the effectful computation is synchronous and does not @@ -1690,16 +1713,18 @@ export { * * **Example** (Yielding to other fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * console.log("Before yield") + * void output.push("Before yield") * yield* Effect.yieldNow - * console.log("After yield") + * void output.push("After yield") * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) + * output // => ["Before yield", "After yield"] * ``` * * @category constructors @@ -1712,16 +1737,18 @@ export const yieldNow: Effect = internal.yieldNow * * **Example** (Yielding with priority) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * console.log("High priority task") + * void output.push("High priority task") * yield* Effect.yieldNowWith(10) // Higher priority - * console.log("Continued after yield") + * void output.push("Continued after yield") * }) * - * Effect.runPromise(program) + * await Effect.runPromise(program) + * output // => ["High priority task", "Continued after yield"] * ``` * * @category constructors @@ -1734,15 +1761,12 @@ export const yieldNowWith: (priority?: number) => Effect = internal.yieldN * * **Example** (Reading the current fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const program = Effect.withFiber((fiber) => - * Effect.succeed(`Fiber ID: ${fiber.id}`) - * ) + * const program = Effect.withFiber((fiber) => Effect.succeed(typeof fiber.id)) * - * Effect.runPromise(program).then(console.log) - * // Output: Fiber ID: 1 + * Effect.runSync(program) // => "number" * ``` * * @category constructors @@ -1752,6 +1776,26 @@ export const withFiber: ( evaluate: (fiber: Fiber) => Effect ) => Effect = core.withFiber +/** + * Accesses the current fiber to compute a successful value. + * + * **Example** (Computing a value from the current fiber) + * + * ```ts import.meta.vitest + * import { Effect } from "effect" + * + * const program = Effect.withFiberSucceed((fiber) => typeof fiber.id) + * + * Effect.runSync(program) // => "number" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const withFiberSucceed: ( + evaluate: (fiber: Fiber) => A +) => Effect = core.withFiberSucceed + // ----------------------------------------------------------------------------- // Conversions // ----------------------------------------------------------------------------- @@ -1761,8 +1805,9 @@ export const withFiber: ( * * **Example** (Converting a Result into an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Result } from "effect" + * const output: Array = [] * * const success = Result.succeed(42) * const failure = Result.fail("Something went wrong") @@ -1770,9 +1815,9 @@ export const withFiber: ( * const effect1 = Effect.fromResult(success) * const effect2 = Effect.fromResult(failure) * - * Effect.runPromise(effect1).then(console.log) // 42 - * Effect.runPromiseExit(effect2).then(console.log) - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Something went wrong' } } + * void output.push(Effect.runSync(effect1)) + * void output.push(Effect.runSync(Effect.flip(effect2))) + * output // => [42, "Something went wrong"] * ``` * * @category converting @@ -1797,8 +1842,9 @@ export const fromResult: (result: Result.Result) => Effect = i * * **Example** (Converting an Option into an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option } from "effect" + * const output: Array = [] * * const some = Option.some(42) * const none = Option.none() @@ -1807,18 +1853,28 @@ export const fromResult: (result: Result.Result) => Effect = i * const effect2 = Effect.fromOption(none) * const effect3 = Effect.fromOption(none, () => new Error("missing")) * - * Effect.runPromise(effect1).then(console.log) // 42 - * Effect.runPromiseExit(effect2).then(console.log) - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: { _id: 'NoSuchElementError' } } } + * void output.push(Effect.runSync(effect1)) + * void output.push(Effect.runSync(Effect.flip(effect2))._tag) + * void output.push(Effect.runSync(Effect.flip(effect3)).message) + * output // => [42, "NoSuchElementError", "missing"] * ``` * * @category converting * @since 4.0.0 */ -export const fromOption: | LazyArg, E = Cause.NoSuchElementError>( - arg: Arg, - ...rest: [Arg] extends [Option] ? [onNone?: LazyArg] : [] -) => [Arg] extends [Option] ? Effect +export const fromOption: < + Arg extends Option | LazyArg, + Rest extends [] | [onNone: LazyArg | undefined] = [] +>( + arg: Arg & (Rest extends [] ? unknown : Option), + ...rest: Rest +) => [Arg] extends [Option] ? Effect< + A, + Rest extends [LazyArg] ? E + : Rest extends [undefined] ? Cause.NoSuchElementError + : Rest extends [LazyArg | undefined] ? E | Cause.NoSuchElementError + : Cause.NoSuchElementError + > : [Arg] extends [LazyArg] ? (option: Option) => Effect : never = internal.fromOption @@ -1838,7 +1894,7 @@ export const fromOption: | LazyArg, E = Ca * * **Example** (Transposing an Option of an Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option } from "effect" * * const some = Option.some(Effect.succeed(42)) @@ -1847,8 +1903,7 @@ export const fromOption: | LazyArg, E = Ca * // ▼ * const program = Effect.transposeOption(some) * - * Effect.runPromise(program).then(console.log) - * // Output: { _id: 'Option', _tag: 'Some', value: 42 } + * Effect.runSync(program) // => Option.some(42) * ``` * * @category converting @@ -1864,20 +1919,20 @@ export const transposeOption: ( * * **Example** (Failing on nullish values) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.fn(function*(input: string | null) { * const value = yield* Effect.fromNullishOr(input) - * yield* Console.log(value) + * yield* Effect.sync(() => { output.push(value) }) * }, - * Effect.catch(() => Console.log("missing")) + * Effect.catch(() => Effect.sync(() => { output.push("missing") })) * ) * - * Effect.runPromise(program(null)) - * // Output: missing - * Effect.runPromise(program("hello")) - * // Output: hello + * await Effect.runPromise(program(null)) + * await Effect.runPromise(program("hello")) + * output // => ["missing", "hello"] * ``` * * @category converting @@ -1911,8 +1966,9 @@ export const fromNullishOr: (value: A) => Effect, Cause.NoSuch * * **Example** (Choosing flatMap syntax variants) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" + * const output: Array = [] * * const myEffect = Effect.succeed(1) * const transformation = (n: number) => Effect.succeed(n + 1) @@ -1920,11 +1976,18 @@ export const fromNullishOr: (value: A) => Effect, Cause.NoSuch * const flatMappedWithPipe = pipe(myEffect, Effect.flatMap(transformation)) * const flatMappedWithDataFirst = Effect.flatMap(myEffect, transformation) * const flatMappedWithMethod = myEffect.pipe(Effect.flatMap(transformation)) + * + * void output.push(Effect.runSync(Effect.all([ + * flatMappedWithPipe, + * flatMappedWithDataFirst, + * flatMappedWithMethod + * ]))) + * output // => [[2, 2, 2]] * ``` * * **Example** (Sequencing dependent effects) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, pipe } from "effect" * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} @@ -1947,8 +2010,7 @@ export const fromNullishOr: (value: A) => Effect, Cause.NoSuch * Effect.flatMap((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(finalAmount).then(console.log) - * // Output: 95 + * await Effect.runPromise(finalAmount) // => 95 * ``` * * @see {@link tap} for a version that ignores the result of the effect. @@ -1970,16 +2032,19 @@ export const flatMap: { * * **Example** (Flattening nested effects) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const nested = Effect.succeed(Effect.succeed("hello")) * * const program = Effect.gen(function*() { * const value = yield* Effect.flatten(nested) - * yield* Console.log(value) - * // Output: hello + * yield* Effect.sync(() => { output.push(value) }) * }) + * + * Effect.runSync(program) + * output // => ["hello"] * ``` * * @category sequencing @@ -2009,8 +2074,9 @@ export const flatten: (self: Effect, E2, R2>) = * * **Example** (Choosing andThen syntax variants) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" + * const output: Array = [] * * const myEffect = Effect.succeed(1) * const anotherEffect = Effect.succeed("done") @@ -2018,11 +2084,18 @@ export const flatten: (self: Effect, E2, R2>) = * const transformedWithPipe = pipe(myEffect, Effect.andThen(anotherEffect)) * const transformedWithDataFirst = Effect.andThen(myEffect, anotherEffect) * const transformedWithMethod = myEffect.pipe(Effect.andThen(anotherEffect)) + * + * void output.push(Effect.runSync(Effect.all([ + * transformedWithPipe, + * transformedWithDataFirst, + * transformedWithMethod + * ]))) + * output // => [['done', 'done', 'done']] * ``` * * **Example** (Sequencing a discount calculation after fetching a total) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, pipe } from "effect" * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} @@ -2046,8 +2119,7 @@ export const flatten: (self: Effect, E2, R2>) = * Effect.flatMap((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(result1).then(console.log) - * // Output: 190 + * await Effect.runPromise(result1) // => 190 * * // Using Effect.andThen * const result2 = pipe( @@ -2056,8 +2128,7 @@ export const flatten: (self: Effect, E2, R2>) = * Effect.andThen((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(result2).then(console.log) - * // Output: 190 + * await Effect.runPromise(result2) // => 190 * ``` * * @category sequencing @@ -2098,8 +2169,9 @@ export const andThen: { * * **Example** (Logging a step in a pipeline) * - * ```ts - * import { Console, Data, Effect, pipe } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, pipe } from "effect" + * const output: Array = [] * * class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {} * @@ -2118,15 +2190,13 @@ export const andThen: { * const finalAmount = pipe( * fetchTransactionAmount, * // Log the fetched transaction amount - * Effect.tap((amount) => Console.log(`Apply a discount to: ${amount}`)), + * Effect.tap((amount) => Effect.sync(() => { output.push(`Apply a discount to: ${amount}`) })), * // `amount` is still available! * Effect.flatMap((amount) => applyDiscount(amount, 5)) * ) * - * Effect.runPromise(finalAmount).then(console.log) - * // Output: - * // Apply a discount to: 100 - * // 95 + * void output.push(await Effect.runPromise(finalAmount)) + * output // => ["Apply a discount to: 100", 95] * ``` * * @category sequencing @@ -2180,8 +2250,8 @@ export const tap: { * * **Example** (Capturing success or failure as Result) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Result } from "effect" * * const success = Effect.succeed(42) * const failure = Effect.fail("Something went wrong") @@ -2189,17 +2259,15 @@ export const tap: { * const program1 = Effect.result(success) * const program2 = Effect.result(failure) * - * Effect.runPromise(program1).then(console.log) - * // { _id: 'Result', _tag: 'Success', value: 42 } + * Effect.runSync(program1) // => Result.succeed(42) * - * Effect.runPromise(program2).then(console.log) - * // { _id: 'Result', _tag: 'Failure', error: 'Something went wrong' } + * Effect.runSync(program2) // => Result.fail("Something went wrong") * ``` * * @see {@link option} for a version that uses `Option` instead. * @see {@link exit} for a version that encapsulates both recoverable errors and defects in an `Exit`. * - * @category outcome encapsulation + * @category error handling * @since 4.0.0 */ export const result: (self: Effect) => Effect, never, R> = internal.result @@ -2228,26 +2296,21 @@ export const result: (self: Effect) => Effect [Option.some(1), Option.none()] * ``` * * @see {@link result} for a version that uses `Result` instead. * @see {@link exit} for a version that encapsulates both recoverable errors and defects in an `Exit`. * - * @category outcome encapsulation + * @category error handling * @since 2.0.0 */ export const option: (self: Effect) => Effect, never, R> = internal.option @@ -2272,8 +2335,8 @@ export const option: (self: Effect) => Effect, never * * **Example** (Capturing completion as Exit) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * const success = Effect.succeed(42) * const failure = Effect.fail("Something went wrong") @@ -2281,17 +2344,15 @@ export const option: (self: Effect) => Effect, never * const program1 = Effect.exit(success) * const program2 = Effect.exit(failure) * - * Effect.runPromise(program1).then(console.log) - * // { _id: 'Exit', _tag: 'Success', value: 42 } + * Effect.runSync(program1) // => Exit.succeed(42) * - * Effect.runPromise(program2).then(console.log) - * // { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'Something went wrong' } } + * Effect.runSync(program2) // => Exit.fail("Something went wrong") * ``` * * @see {@link option} for a version that uses `Option` instead. * @see {@link result} for a version that uses `Result` instead. * - * @category outcome encapsulation + * @category error handling * @since 2.0.0 */ export const exit: ( @@ -2318,8 +2379,9 @@ export const exit: ( * * **Example** (Choosing map syntax variants) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" + * const output: Array = [] * * const myEffect = Effect.succeed(1) * const transformation = (n: number) => n + 1 @@ -2327,11 +2389,18 @@ export const exit: ( * const mappedWithPipe = pipe(myEffect, Effect.map(transformation)) * const mappedWithDataFirst = Effect.map(myEffect, transformation) * const mappedWithMethod = myEffect.pipe(Effect.map(transformation)) + * + * void output.push(Effect.runSync(Effect.all([ + * mappedWithPipe, + * mappedWithDataFirst, + * mappedWithMethod + * ]))) + * output // => [[2, 2, 2]] * ``` * * **Example** (Adding a service charge) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" * * const addServiceCharge = (amount: number) => amount + 1 @@ -2343,8 +2412,7 @@ export const exit: ( * Effect.map(addServiceCharge) * ) * - * Effect.runPromise(finalAmount).then(console.log) - * // Output: 101 + * await Effect.runPromise(finalAmount) // => 101 * ``` * * @see {@link mapError} for a version that operates on the error channel. @@ -2373,14 +2441,13 @@ export const map: { * * **Example** (Replacing a success value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, pipe } from "effect" * * // Replaces the value 5 with the constant "new value" * const program = pipe(Effect.succeed(5), Effect.as("new value")) * - * Effect.runPromise(program).then(console.log) - * // Output: "new value" + * Effect.runSync(program) // => "new value" * ``` * * @see {@link map} for deriving the replacement value from the success value @@ -2399,13 +2466,12 @@ export const as: { * * **Example** (Wrapping success in Option.some) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * * const program = Effect.asSome(Effect.succeed(42)) * - * Effect.runPromise(program).then(console.log) - * // { _id: 'Option', _tag: 'Some', value: 42 } + * Effect.runSync(program) // => Option.some(42) * ``` * * @category mapping @@ -2418,13 +2484,12 @@ export const asSome: (self: Effect) => Effect, E, R> * * **Example** (Discarding success values) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.asVoid(Effect.succeed(42)) * - * Effect.runPromise(program).then(console.log) - * // undefined (void) + * Effect.runSync(program) // => undefined * ``` * * @category mapping @@ -2445,7 +2510,7 @@ export const asVoid: (self: Effect) => Effect = in * * **Example** (Swapping success and failure channels) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // ┌─── Effect @@ -2455,6 +2520,7 @@ export const asVoid: (self: Effect) => Effect = in * // ┌─── Effect * // ▼ * const flipped = Effect.flip(program) + * Effect.runSync(flipped) // => "Oh uh!" * ``` * * @category mapping @@ -2485,17 +2551,11 @@ export const flip: (self: Effect) => Effect = interna * * **Example** (Combining two effects sequentially) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task1 = Effect.succeed(1).pipe( - * Effect.delay("200 millis"), - * Effect.tap(Effect.log("task1 done")) - * ) - * const task2 = Effect.succeed("hello").pipe( - * Effect.delay("100 millis"), - * Effect.tap(Effect.log("task2 done")) - * ) + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") * * // Combine the two effects together * // @@ -2503,35 +2563,21 @@ export const flip: (self: Effect) => Effect = interna * // ▼ * const program = Effect.zip(task1, task2) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // timestamp=... level=INFO fiber=#0 message="task1 done" - * // timestamp=... level=INFO fiber=#0 message="task2 done" - * // [ 1, 'hello' ] + * Effect.runSync(program) // => [1, 'hello'] * ``` * * **Example** (Combining two effects concurrently) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task1 = Effect.succeed(1).pipe( - * Effect.delay("200 millis"), - * Effect.tap(Effect.log("task1 done")) - * ) - * const task2 = Effect.succeed("hello").pipe( - * Effect.delay("100 millis"), - * Effect.tap(Effect.log("task2 done")) - * ) + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") * * // Run both effects concurrently using the concurrent option * const program = Effect.zip(task1, task2, { concurrent: true }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // timestamp=... level=INFO fiber=#0 message="task2 done" - * // timestamp=... level=INFO fiber=#0 message="task1 done" - * // [ 1, 'hello' ] + * await Effect.runPromise(program) // => [1, 'hello'] * ``` * * @see {@link zipWith} for a version that combines the results with a custom function. @@ -2570,17 +2616,11 @@ export const zip: { * * **Example** (Combining two success values with a function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task1 = Effect.succeed(1).pipe( - * Effect.delay("200 millis"), - * Effect.tap(Effect.log("task1 done")) - * ) - * const task2 = Effect.succeed("hello").pipe( - * Effect.delay("100 millis"), - * Effect.tap(Effect.log("task2 done")) - * ) + * const task1 = Effect.succeed(1) + * const task2 = Effect.succeed("hello") * * const task3 = Effect.zipWith( * task1, @@ -2589,11 +2629,7 @@ export const zip: { * (number, string) => number + string.length * ) * - * Effect.runPromise(task3).then(console.log) - * // Output: - * // timestamp=... level=INFO fiber=#3 message="task1 done" - * // timestamp=... level=INFO fiber=#2 message="task2 done" - * // 6 + * Effect.runSync(task3) // => 6 * ``` * * @category zipping @@ -2672,7 +2708,7 @@ export { * * **Example** (Handling a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * class NetworkError { @@ -2685,13 +2721,16 @@ export { * constructor(readonly message: string) {} * } * - * declare const task: Effect.Effect + * const task: Effect.Effect = + * Effect.fail(new NetworkError("offline")) * * const program = Effect.catchTag( * task, * "NetworkError", * (error) => Effect.succeed(`Recovered from network error: ${error.message}`) * ) + * + * Effect.runSync(program) // => "Recovered from network error: offline" * ``` * * @see {@link catchTags} for handling multiple tagged errors in one call @@ -2770,7 +2809,7 @@ export const catchTag: { * * **Example** (Handling multiple tagged errors) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * // Define tagged error types @@ -2783,7 +2822,8 @@ export const catchTag: { * }> {} * * // An effect that might fail with multiple error types - * declare const program: Effect.Effect + * const program: Effect.Effect = + * Effect.fail(new NetworkError({ statusCode: 503 })) * * // Handle multiple error types at once * const handled = Effect.catchTags(program, { @@ -2791,6 +2831,8 @@ export const catchTag: { * Effect.succeed(`Validation failed: ${error.message}`), * NetworkError: (error) => Effect.succeed(`Network error: ${error.statusCode}`) * }) + * + * Effect.runSync(handled) // => "Network error: 503" * ``` * * @category error handling @@ -2875,7 +2917,7 @@ export const catchTags: { * * **Example** (Handling an error reason) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ @@ -2890,7 +2932,9 @@ export const catchTags: { * reason: RateLimitError | QuotaExceededError * }> {} * - * declare const program: Effect.Effect + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) + * ) * * // Handle rate limits specifically * const handled = program.pipe( @@ -2898,6 +2942,8 @@ export const catchTags: { * Effect.succeed(`Retry after ${reason.retryAfter}s`) * ) * ) + * + * Effect.runSync(handled) // => "Retry after 30s" * ``` * * @see {@link catchReasons} for handling several nested reason tags @@ -2968,7 +3014,7 @@ export const catchReason: { * * **Example** (Handling multiple error reasons) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ @@ -2983,7 +3029,9 @@ export const catchReason: { * reason: RateLimitError | QuotaExceededError * }> {} * - * declare const program: Effect.Effect + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new QuotaExceededError({ limit: 100 }) }) + * ) * * const handled = program.pipe( * Effect.catchReasons("AiError", { @@ -2993,6 +3041,8 @@ export const catchReason: { * Effect.succeed(`Quota exceeded: ${reason.limit}`) * }) * ) + * + * Effect.runSync(handled) // => "Quota exceeded: 100" * ``` * * @category error handling @@ -3114,7 +3164,7 @@ export type TagsWithReason = { * * **Example** (Extracting the reason from a tagged error) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class RateLimitError extends Data.TaggedError("RateLimitError")<{ @@ -3129,11 +3179,14 @@ export type TagsWithReason = { * reason: RateLimitError | QuotaExceededError * }> {} * - * declare const program: Effect.Effect + * const program: Effect.Effect = Effect.fail( + * new AiError({ reason: new RateLimitError({ retryAfter: 30 }) }) + * ) * * // Before: Effect * // After: Effect * const unwrapped = program.pipe(Effect.unwrapReason("AiError")) + * Effect.runSync(Effect.flip(unwrapped))._tag // => "RateLimitError" * ``` * * @category error handling @@ -3177,8 +3230,9 @@ export const unwrapReason: { * * **Example** (Recovering from full failure causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * const output: Array = [] * * // An effect that might fail in different ways * const program = Effect.die("Something went wrong") @@ -3186,12 +3240,15 @@ export const unwrapReason: { * // Recover from any cause (including defects) * const recovered = Effect.catchCause(program, (cause) => { * if (Cause.hasDies(cause)) { - * return Console.log("Caught defect").pipe( + * return Effect.sync(() => { output.push("Caught defect") }).pipe( * Effect.as("Recovered from defect") * ) * } * return Effect.succeed("Unknown error") * }) + * + * void output.push(Effect.runSync(recovered)) + * output // => ["Caught defect", "Recovered from defect"] * ``` * * @category error handling @@ -3227,8 +3284,9 @@ export const catchCause: { * * **Example** (Recovering from defects) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * // An effect that might throw an unexpected error (defect) * const program = Effect.sync(() => { @@ -3237,10 +3295,13 @@ export const catchCause: { * * // Recover from defects only * const recovered = Effect.catchDefect(program, (defect) => { - * return Console.log(`Caught defect: ${defect}`).pipe( + * return Effect.sync(() => { output.push(`Caught defect: ${(defect as Error).message}`) }).pipe( * Effect.as("Recovered from defect") * ) * }) + * + * void output.push(Effect.runSync(recovered)) + * output // => ["Caught defect: Unexpected error", "Recovered from defect"] * ``` * * @category error handling @@ -3271,7 +3332,7 @@ export const catchDefect: { * * **Example** (Recovering when a predicate matches) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Filter } from "effect" * * class NotFound extends Data.TaggedError("NotFound")<{ id: string }> {} @@ -3293,6 +3354,8 @@ export const catchDefect: { * (error) => Effect.succeed(`missing:${error.id}`) * ) * ) + * + * Effect.runSync(Effect.all([recovered, recovered2])) // => ['missing:user-1', 'missing:user-1'] * ``` * * @category error handling @@ -3382,14 +3445,16 @@ export const catchFilter: { * * **Example** (Recovering from missing Option values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option } from "effect" + * const output: Array = [] * * const some = Effect.fromNullishOr(1).pipe(Effect.catchNoSuchElement) * const none = Effect.fromNullishOr(null).pipe(Effect.catchNoSuchElement) * - * Effect.runPromise(some).then(console.log) // { _id: 'Option', _tag: 'Some', value: 1 } - * Effect.runPromise(none).then(console.log) // { _id: 'Option', _tag: 'None' } + * void output.push(Effect.runSync(some)) + * void output.push(Effect.runSync(none)) + * output // => [Option.some(1), Option.none()] * ``` * * @see {@link fromOption} for converting `Option.none` into `NoSuchElementError` @@ -3418,8 +3483,9 @@ export const catchNoSuchElement: ( * * **Example** (Recovering from selected causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect } from "effect" + * const output: Array = [] * * const httpRequest = Effect.fail("Network Error") * @@ -3429,14 +3495,13 @@ export const catchNoSuchElement: ( * Cause.hasFails, * (cause) => * Effect.gen(function*() { - * yield* Console.log(`Caught network error: ${Cause.squash(cause)}`) + * yield* Effect.sync(() => { output.push(`Caught network error: ${Cause.squash(cause)}`) }) * return "Fallback response" * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: "Caught network error: Network Error" - * // Then: "Fallback response" + * void output.push(Effect.runSync(program)) + * output // => ["Caught network error: Network Error", "Fallback response"] * ``` * * @see {@link catchCause} for recovering from every cause @@ -3507,7 +3572,7 @@ export const catchCauseFilter: { * * **Example** (Transforming the error channel) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} @@ -3522,6 +3587,7 @@ export const catchCauseFilter: { * simulatedTask, * (message) => new TaskError({ message }) * ) + * Effect.runSync(Effect.flip(mapped)).message // => "Oh no!" * ``` * * @see {@link map} for a version that operates on the success channel. @@ -3552,7 +3618,7 @@ export const mapError: { * * **Example** (Transforming success and failure channels) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} @@ -3567,6 +3633,7 @@ export const mapError: { * onFailure: (message) => new TaskError({ message }), * onSuccess: (n) => n > 0 * }) + * Effect.runSync(Effect.flip(modified)).message // => "Oh no!" * ``` * * @see {@link map} for a version that operates on the success channel. @@ -3596,8 +3663,8 @@ export const mapBoth: { * * **Example** (Converting typed failures into defects) * - * ```ts - * import { Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" * * class DivideByZeroError extends Data.TaggedError("DivideByZeroError")<{}> {} * @@ -3610,13 +3677,10 @@ export const mapBoth: { * // ▼ * const program = Effect.orDie(divide(1, 0)) * - * Effect.runPromise(program).catch(console.error) - * // Output: - * // (FiberFailure) DivideByZeroError - * // ...stack trace... + * Effect.runSyncExit(program) // => Exit.die(new DivideByZeroError()) * ``` * - * @category converting failures to defects + * @category error handling * @since 2.0.0 */ export const orDie: (self: Effect) => Effect = internal.orDie @@ -3633,8 +3697,9 @@ export const orDie: (self: Effect) => Effect = in * * **Example** (Running effects on failure) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * // Simulate a task that fails with an error * const task: Effect.Effect = Effect.fail("NetworkError") @@ -3642,12 +3707,11 @@ export const orDie: (self: Effect) => Effect = in * // Use tapError to log the error message when the task fails * const tapping = Effect.tapError( * task, - * (error) => Console.log(`expected error: ${error}`) + * (error) => Effect.sync(() => { output.push(`expected error: ${error}`) }) * ) * - * Effect.runFork(tapping) - * // Output: - * // expected error: NetworkError + * void output.push(Effect.runSyncExit(tapping)) + * output // => ["expected error: NetworkError", Exit.fail("NetworkError")] * ``` * * @category sequencing @@ -3674,8 +3738,9 @@ export const tapError: { * * **Example** (Running effects for tagged failures) * - * ```ts - * import { Console, Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Exit } from "effect" + * const output: Array = [] * * class NetworkError extends Data.TaggedError("NetworkError")<{ * statusCode: number @@ -3689,12 +3754,11 @@ export const tapError: { * Effect.fail(new NetworkError({ statusCode: 504 })) * * const program = Effect.tapErrorTag(task, "NetworkError", (error) => - * Console.log(`expected error: ${error.statusCode}`) + * Effect.sync(() => { output.push(`expected error: ${error.statusCode}`) }) * ) * - * Effect.runPromiseExit(program) - * // Output: - * // expected error: 504 + * void output.push(Effect.runSyncExit(program)) + * output // => ["expected error: 504", Exit.fail(new NetworkError({ statusCode: 504 }))] * ``` * * @category sequencing @@ -3737,19 +3801,19 @@ export const tapErrorTag: { * * **Example** (Observing full failure causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.fail("Something went wrong") * * const program = Effect.tapCause( * task, - * (cause) => Console.log(`Logging cause: ${Cause.squash(cause)}`) + * (cause) => Effect.sync(() => { output.push(`Logging cause: ${Cause.squash(cause)}`) }) * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: "Logging cause: Error: Something went wrong" - * // Then: { _id: 'Exit', _tag: 'Failure', cause: ... } + * void output.push(Effect.runSyncExit(program)) + * output // => ["Logging cause: Something went wrong", Exit.fail("Something went wrong")] * ``` * * @category sequencing @@ -3776,8 +3840,9 @@ export const tapCause: { * * **Example** (Observing selected failure causes) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.fail("Network timeout") * @@ -3785,12 +3850,11 @@ export const tapCause: { * const program = Effect.tapCauseIf( * task, * Cause.hasFails, - * (cause) => Console.log(`Logging failure cause: ${Cause.squash(cause)}`) + * (cause) => Effect.sync(() => { output.push(`Logging failure cause: ${Cause.squash(cause)}`) }) * ) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: "Logging failure cause: Network timeout" - * // Then: { _id: 'Exit', _tag: 'Failure', cause: ... } + * void output.push(Effect.runSyncExit(program)) + * output // => ["Logging failure cause: Network timeout", Exit.fail("Network timeout")] * ``` * * @category sequencing @@ -3854,20 +3918,9 @@ export const tapCauseFilter: { * * **Example** (Observing defects) * - * ```ts - * import { Console, Effect } from "effect" - * - * // Simulate a task that fails with a recoverable error - * const task1: Effect.Effect = Effect.fail("NetworkError") - * - * // tapDefect won't log anything because NetworkError is not a defect - * const tapping1 = Effect.tapDefect( - * task1, - * (cause) => Console.log(`defect: ${cause}`) - * ) - * - * Effect.runFork(tapping1) - * // No Output + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * // Simulate a severe failure in the system * const task2: Effect.Effect = Effect.die( @@ -3877,20 +3930,18 @@ export const tapCauseFilter: { * // Log the defect using tapDefect * const tapping2 = Effect.tapDefect( * task2, - * (cause) => Console.log(`defect: ${cause}`) + * (defect) => Effect.sync(() => { output.push(`defect: ${defect}`) }) * ) * - * Effect.runFork(tapping2) - * // Output: - * // defect: RuntimeException: Something went wrong - * // ... stack trace ... + * void output.push(Effect.runSyncExit(tapping2)) + * output // => ["defect: Something went wrong", Exit.die("Something went wrong")] * ``` * * @category sequencing * @since 2.0.0 */ export const tapDefect: { - (f: (defect: unknown) => Effect): (self: Effect) => Effect + (f: (defect: unknown) => Effect): (self: Effect) => Effect (self: Effect, f: (defect: unknown) => Effect): Effect } = internal.tapDefect @@ -3903,14 +3954,15 @@ export const tapDefect: { * * **Example** (Retrying until success) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * let attempts = 0 * * const flaky = Effect.gen(function*() { * attempts++ - * yield* Console.log(`Attempt ${attempts}`) + * yield* Effect.sync(() => { output.push(`Attempt ${attempts}`) }) * if (attempts < 3) { * return yield* Effect.fail("Not ready") * } @@ -3919,12 +3971,8 @@ export const tapDefect: { * * const program = Effect.eventually(flaky) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Attempt 1 - * // Attempt 2 - * // Attempt 3 - * // Ready + * void output.push(await Effect.runPromise(program)) + * output // => ["Attempt 1", "Attempt 2", "Attempt 3", "Ready"] * ``` * * @category repetition @@ -4010,7 +4058,7 @@ export declare namespace Retry { * * **Example** (Retrying with a schedule) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Schedule } from "effect" * * class AttemptError extends Data.TaggedError("AttemptError")<{ readonly attempt: number }> {} @@ -4025,11 +4073,10 @@ export declare namespace Retry { * } * }) * - * const policy = Schedule.addDelay(Schedule.recurs(5), () => Effect.succeed("100 millis")) + * const policy = Schedule.recurs(5) * const program = Effect.retry(task, policy) * - * Effect.runPromise(program).then(console.log) - * // Output: "Success!" (after 2 retries) + * await Effect.runPromise(program) // => "Success!" * ``` * * @see {@link retryOrElse} for a version that allows you to run a fallback. @@ -4078,15 +4125,16 @@ export const retry: { * * **Example** (Falling back after retries are exhausted) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Data, Effect, Schedule } from "effect" + * const output: Array = [] * * class NetworkTimeoutError extends Data.TaggedError("NetworkTimeoutError")<{}> {} * * let attempt = 0 * const networkRequest = Effect.gen(function*() { * attempt++ - * yield* Console.log(`Network attempt ${attempt}`) + * yield* Effect.sync(() => { output.push(`Network attempt ${attempt}`) }) * if (attempt < 3) { * return yield* Effect.fail(new NetworkTimeoutError()) * } @@ -4099,17 +4147,13 @@ export const retry: { * Schedule.recurs(2), * (error, retryCount) => * Effect.gen(function*() { - * yield* Console.log(`All ${retryCount} retries failed, using cache`) + * yield* Effect.sync(() => { output.push(`All ${retryCount} retries failed, using cache`) }) * return "Cached data" * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Network attempt 1 - * // Network attempt 2 - * // Network attempt 3 - * // Network data + * void output.push(await Effect.runPromise(program)) + * output // => ["Network attempt 1", "Network attempt 2", "Network attempt 3", "Network data"] * ``` * * @see {@link retry} for a version that does not run a fallback effect. @@ -4139,7 +4183,7 @@ export const retryOrElse: { * * **Example** (Exposing failures as causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const task = Effect.fail("Something went wrong") @@ -4150,8 +4194,7 @@ export const retryOrElse: { * return `Caught cause: ${Cause.squash(result)}` * }) * - * Effect.runPromise(program).then(console.log) - * // Output: "Caught cause: Something went wrong" + * Effect.runSync(program) // => "Caught cause: Something went wrong" * ``` * * @category error handling @@ -4176,7 +4219,7 @@ export const sandbox: ( * * **Example** (Discarding success and failure values) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // ┌─── Effect @@ -4186,17 +4229,18 @@ export const sandbox: ( * // ┌─── Effect * // ▼ * const program = task.pipe(Effect.ignore) + * Effect.runSync(program) // => undefined * ``` * * **Example** (Logging failures while ignoring results) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const task = Effect.fail("Uh oh!") * - * const program = task.pipe(Effect.ignore({ log: true })) - * const programWarn = task.pipe(Effect.ignore({ log: "Warn", message: "Ignoring task failure" })) + * const program = task.pipe(Effect.ignore) + * Effect.runSync(program) // => undefined * ``` * * @category error handling @@ -4234,13 +4278,13 @@ export const ignore: < * * **Example** (Ignoring failures and logging causes) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const task = Effect.fail("boom") * * const program = task.pipe(Effect.ignoreCause) - * const programLog = task.pipe(Effect.ignoreCause({ log: true, message: "Ignoring failure cause" })) + * Effect.runSync(program) // => undefined * ``` * * @category error handling @@ -4273,9 +4317,17 @@ export const ignoreCause: < * and retry timing is derived per step (the first attempt uses the remaining * attempts schedule; later retries apply the step schedule at least once). * + * Attempts can be observed from outside the effect by passing + * `options.onEvent`, which receives an `ExecutionPlan.Event` before each + * attempt and after it settles. The handler is awaited inline before and after + * every attempt, so events are strictly ordered; keep it cheap. It cannot + * fail, which keeps observation from changing the plan's outcome, and its + * requirements are added to the resulting effect. Terminal events run like + * finalizers, so they are emitted even when the attempt is interrupted. + * * **Example** (Retrying with an execution plan) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, ExecutionPlan, Layer } from "effect" * * const Endpoint = Context.Service<{ url: string }>("Endpoint") @@ -4294,21 +4346,58 @@ export const ignoreCause: < * ) * * const program = Effect.withExecutionPlan(fetchUrl, plan) + * Effect.runSync(program) // => "good" * ``` * - * @category fallback + * **Example** (Observing execution-plan attempts) + * + * ```ts import.meta.vitest + * import { Context, Effect, ExecutionPlan, Layer } from "effect" + * + * const Endpoint = Context.Service<{ url: string }>("Endpoint") + * + * const fetchUrl = Effect.gen(function*() { + * const endpoint = yield* Effect.service(Endpoint) + * if (endpoint.url === "bad") { + * return yield* Effect.fail("Unavailable") + * } + * return endpoint.url + * }) + * + * const plan = ExecutionPlan.make( + * { provide: Layer.succeed(Endpoint, { url: "bad" }) }, + * { provide: Layer.succeed(Endpoint, { url: "good" }) } + * ) + * + * const events: Array = [] + * const program = Effect.withExecutionPlan(fetchUrl, plan, { + * onEvent: (event) => Effect.sync(() => events.push(`${event._tag}:${event.stepIndex}`)) + * }) + * + * await Effect.runPromise(program) // => "good" + * + * events // => ["AttemptStart:0", "AttemptFailure:0", "AttemptStart:1", "AttemptSuccess:1"] + * ``` + * + * @category error handling * @since 3.16.0 */ export const withExecutionPlan: { - ( - plan: ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }> + ( + plan: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }>, + options?: { + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect) | undefined + } ): ( effect: Effect - ) => Effect | PlanR> - ( + ) => Effect | PlanR | RX> + ( effect: Effect, - plan: ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }> - ): Effect | PlanR> + plan: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PlanE; requirements: PlanR }>, + options?: { + readonly onEvent?: ((event: ExecutionPlan.Event) => Effect) | undefined + } + ): Effect | PlanR | RX> } = internalExecutionPlan.withExecutionPlan /** @@ -4329,8 +4418,8 @@ export const withErrorReporting: < >( effectOrOptions: Arg, options?: { readonly defectsOnly?: boolean | undefined } | undefined -) => [Arg] extends [Effect] ? Arg : (self: Effect) => Effect = - internal.withErrorReporting +) => [Arg] extends [Effect] ? Effect + : (self: Effect) => Effect = internal.withErrorReporting // ----------------------------------------------------------------------------- // Fallback @@ -4349,8 +4438,8 @@ export const withErrorReporting: < * * **Example** (Replacing failures with a value) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * const validate = (age: number): Effect.Effect => { * if (age < 0) { @@ -4364,12 +4453,10 @@ export const withErrorReporting: < * * const program = Effect.orElseSucceed(validate(-1), () => 18) * - * console.log(Effect.runSyncExit(program)) - * // Output: - * // { _id: 'Exit', _tag: 'Success', value: 18 } + * Effect.runSyncExit(program) // => Exit.succeed(18) * ``` * - * @category fallback + * @category error handling * @since 2.0.0 */ export const orElseSucceed: { @@ -4404,7 +4491,7 @@ export const orElseSucceed: { * * **Example** (Trying alternatives until one succeeds) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const primary = Effect.fail("primary unavailable") @@ -4419,11 +4506,10 @@ export const orElseSucceed: { * tertiary * ]) * - * console.log(Effect.runSync(program)) - * // Output: "secondary result" + * Effect.runSync(program) // => "secondary result" * ``` * - * @category fallback + * @category error handling * @since 2.0.0 */ export const firstSuccessOf: >( @@ -4447,8 +4533,8 @@ export const firstSuccessOf: >( * * The `timeout` function allows you to specify a time limit for an * effect's execution. If the effect does not complete within the given time, a - * `TimeoutException` is raised. This can be useful for controlling how long - * your program waits for a task to finish, ensuring that it doesn't hang + * `TimeoutError` is raised. This can be useful for controlling how long your + * program waits for a task to finish, ensuring that it doesn't hang * indefinitely if the task takes too long. * * **Gotchas** @@ -4457,32 +4543,12 @@ export const firstSuccessOf: >( * * **Example** (Failing when work takes too long) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task = Effect.gen(function*() { - * console.log("Start processing...") - * yield* Effect.sleep("2 seconds") // Simulates a delay in processing - * console.log("Processing complete.") - * return "Result" - * }) - * - * // Output will show a TimeoutException as the task takes longer - * // than the specified timeout duration - * const timedEffect = task.pipe(Effect.timeout("1 second")) - * - * Effect.runPromiseExit(timedEffect).then(console.log) - * // Output: - * // Start processing... - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { - * // _id: 'Cause', - * // _tag: 'Fail', - * // failure: { _tag: 'TimeoutException' } - * // } - * // } + * const timedEffect = Effect.never.pipe(Effect.timeout(0)) + * const error = await Effect.runPromise(Effect.flip(timedEffect)) + * error._tag // => "TimeoutError" * ``` * * @see {@link timeoutOption} for returning `Option.none` on timeout. @@ -4518,33 +4584,14 @@ export const timeout: { * * **Example** (Returning None on timeout) * - * ```ts - * import { Effect } from "effect" - * - * const task = Effect.gen(function*() { - * console.log("Start processing...") - * yield* Effect.sleep("2 seconds") // Simulates a delay in processing - * console.log("Processing complete.") - * return "Result" - * }) - * - * const timedOutEffect = Effect.all([ - * task.pipe(Effect.timeoutOption("3 seconds")), - * task.pipe(Effect.timeoutOption("1 second")) - * ]) + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * Effect.runPromise(timedOutEffect).then(console.log) - * // Output: - * // Start processing... - * // Processing complete. - * // Start processing... - * // [ - * // { _id: 'Option', _tag: 'Some', value: 'Result' }, - * // { _id: 'Option', _tag: 'None' } - * // ] + * const timedOutEffect = Effect.never.pipe(Effect.timeoutOption(0)) + * await Effect.runPromise(timedOutEffect) // => Option.none() * ``` * - * @see {@link timeout} for a version that raises a `TimeoutException`. + * @see {@link timeout} for a version that raises a `TimeoutError`. * @see {@link timeoutOrElse} for a version that allows specifying both success and timeout handlers. * * @category delays & timeouts @@ -4561,51 +4608,27 @@ export const timeoutOption: { } = internal.timeoutOption /** - * Applies a timeout to an effect, with a fallback effect executed if the timeout is reached. - * - * **When to use** - * - * Use when a timeout of an `Effect` should switch to a fallback effect. - * - * **Details** - * - * The fallback effect is created lazily by `orElse` and may introduce its own - * success, failure, and requirement types. - * - * **Gotchas** - * - * If the timeout wins, the source effect is interrupted before the fallback is - * run. + * Applies a timeout to an effect, lazily evaluating `orElse` after interrupting + * the source if the timeout is reached. * * **Example** (Falling back on timeout) * - * ```ts - * import { Console, Effect } from "effect" - * - * const slowQuery = Effect.gen(function*() { - * yield* Console.log("Starting database query...") - * yield* Effect.sleep("5 seconds") - * return "Database result" - * }) + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * // Use cached data as fallback when timeout is reached - * const program = Effect.timeoutOrElse(slowQuery, { - * duration: "2 seconds", - * orElse: () => - * Effect.gen(function*() { - * yield* Console.log("Query timed out, using cached data") - * return "Cached result" - * }) + * const program = Effect.timeoutOrElse(Effect.never, { + * duration: 0, + * orElse: () => Effect.sync(() => { output.push("Query timed out, using cached data") }).pipe( + * Effect.as("Cached result") + * ) * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Starting database query... - * // Query timed out, using cached data - * // Cached result + * void output.push(await Effect.runPromise(program)) + * output // => ["Query timed out, using cached data", "Cached result"] * ``` * - * @see {@link timeout} for failing with a `TimeoutException`. + * @see {@link timeout} for failing with a `TimeoutError`. * @see {@link timeoutOption} for returning `Option.none` on timeout. * * @category delays & timeouts @@ -4631,16 +4654,14 @@ export const timeoutOrElse: { * * **Example** (Delaying an effect) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * const program = Effect.delay( - * Console.log("Delayed message"), - * "1 second" - * ) + * const program = Effect.delay(Effect.sync(() => { output.push("Delayed message") }), 0) * - * Effect.runFork(program) - * // Waits 1 second, then prints: "Delayed message" + * await Effect.runPromise(program) + * output // => ["Delayed message"] * ``` * * @category delays & timeouts @@ -4662,18 +4683,18 @@ export const delay: { * * **Example** (Pausing without blocking) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * yield* Console.log("Start") - * yield* Effect.sleep("2 seconds") - * yield* Console.log("End") + * yield* Effect.sync(() => { output.push("Start") }) + * yield* Effect.sleep(0) + * yield* Effect.sync(() => { output.push("End") }) * }) * - * Effect.runFork(program) - * // Output: "Start" (immediately) - * // Output: "End" (after 2 seconds) + * await Effect.runPromise(program) + * output // => ["Start", "End"] * ``` * * @category delays & timeouts @@ -4691,13 +4712,15 @@ export const sleep: (duration: Duration.Input) => Effect = internal.sleep * * **Example** (Measuring execution time) * - * ```ts - * import { Console, Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const program = Effect.gen(function*() { - * const [duration, value] = yield* Effect.timed(Effect.succeed("ok")) - * yield* Console.log(`took ${Duration.toMillis(duration)}ms: ${value}`) + * const [, value] = yield* Effect.timed(Effect.succeed("ok")) + * return value * }) + * + * Effect.runSync(program) // => "ok" * ``` * * @category delays & timeouts @@ -4727,18 +4750,14 @@ export const timed: (self: Effect) => Effect<[duration: Durati * * **Example** (Racing many effects) * - * ```ts - * import { Duration, Effect } from "effect" - * - * // Multiple effects with different delays - * const effect1 = Effect.delay(Effect.succeed("Fast"), Duration.millis(100)) - * const effect2 = Effect.delay(Effect.succeed("Slow"), Duration.millis(500)) - * const effect3 = Effect.delay(Effect.succeed("Very Slow"), Duration.millis(1000)) - * - * // Race all effects - the first to succeed wins - * const raced = Effect.raceAll([effect1, effect2, effect3]) + * ```ts import.meta.vitest + * import { Effect } from "effect" * - * // Result: "Fast" (after ~100ms) + * const raced = Effect.raceAll([ + * Effect.succeed("Fast"), + * Effect.never + * ]) + * await Effect.runPromise(raced) // => "Fast" * ``` * * @see {@link race} for a version that handles only two effects. @@ -4768,18 +4787,14 @@ export const raceAll: >( * * **Example** (Taking the first settled result) * - * ```ts - * import { Duration, Effect } from "effect" - * - * // Multiple effects with different delays and potential failures - * const effect1 = Effect.delay(Effect.succeed("First"), Duration.millis(200)) - * const effect2 = Effect.delay(Effect.fail("Second failed"), Duration.millis(100)) - * const effect3 = Effect.delay(Effect.succeed("Third"), Duration.millis(300)) - * - * // Race all effects - the first to succeed wins - * const raced = Effect.raceAllFirst([effect1, effect2, effect3]) + * ```ts import.meta.vitest + * import { Effect } from "effect" * - * // Result: "First" (after ~200ms, even though effect2 completes first but fails) + * const raced = Effect.raceAllFirst([ + * Effect.fail("First failed"), + * Effect.never + * ]) + * await Effect.runPromise(Effect.flip(raced)) // => "First failed" * ``` * * @category racing @@ -4806,19 +4821,20 @@ export const raceAllFirst: >( * * **Example** (Racing two effects) * - * ```ts - * import { Console, Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * const fastFail = Effect.delay(Effect.fail("fast-fail"), Duration.millis(10)) - * const slowSuccess = Effect.delay(Effect.succeed("slow-success"), Duration.millis(50)) + * const fastFail = Effect.fail("fast-fail") + * const slowSuccess = Effect.succeed("slow-success") * * const program = Effect.gen(function*() { * const result = yield* Effect.race(fastFail, slowSuccess) - * yield* Console.log(`winner: ${result}`) + * yield* Effect.sync(() => { output.push(`winner: ${result}`) }) * }) * - * Effect.runPromise(program) - * // Output: winner: slow-success + * await Effect.runPromise(program) + * output // => ["winner: slow-success"] * ``` * * @category racing @@ -4859,22 +4875,23 @@ export const race: { * * **Example** (Observing the winning fiber) * - * ```ts - * import { Console, Duration, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * - * const fastFail = Effect.delay(Effect.fail("fast-fail"), Duration.millis(10)) - * const slowSuccess = Effect.delay(Effect.succeed("slow-success"), Duration.millis(50)) + * const fastFail = Effect.fail("fast-fail") + * const slowSuccess = Effect.never * * const program = Effect.gen(function*() { * const message = yield* Effect.match(Effect.raceFirst(fastFail, slowSuccess), { * onFailure: (error) => `failed: ${error}`, * onSuccess: (value) => `succeeded: ${value}` * }) - * yield* Console.log(message) + * yield* Effect.sync(() => { output.push(message) }) * }) * - * Effect.runPromise(program) - * // Output: failed: fast-fail + * await Effect.runPromise(program) + * output // => ["failed: fast-fail"] * ``` * * @category racing @@ -4910,8 +4927,9 @@ export const raceFirst: { * * **Example** (Filtering success values) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // Sync predicate * const evens = Effect.filter([1, 2, 3, 4], (n) => n % 2 === 0) @@ -4919,7 +4937,9 @@ export const raceFirst: { * // Effectful predicate * const checked = Effect.filter([1, 2, 3], (n) => Effect.succeed(n > 1)) * - * // Use Effect.filterMapEffect for effectful Filter.Filter callbacks + * void output.push(Effect.runSync(evens)) + * void output.push(Effect.runSync(checked)) + * output // => [[2, 4], [2, 3]] * ``` * * @category filtering @@ -5033,7 +5053,7 @@ export const filterMapEffect: { * * **Example** (Filtering with a fallback effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // An effect that produces a number @@ -5046,7 +5066,7 @@ export const filterMapEffect: { * (n) => Effect.succeed(`Number ${n} is odd`) * ) * - * // Result: "Number 5 is odd" (since 5 is not even) + * Effect.runSync(filtered) // => "Number 5 is odd" * ``` * * @category filtering @@ -5116,7 +5136,7 @@ export const filterMapOrElse: { * * **Example** (Filtering with a custom failure) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // An effect that produces a number @@ -5129,7 +5149,7 @@ export const filterMapOrElse: { * (n) => `Expected even number, got ${n}` * ) * - * // Result: Effect.fail("Expected even number, got 5") + * Effect.runSync(Effect.flip(filtered)) // => "Expected even number, got 5" * ``` * * @category filtering @@ -5231,22 +5251,22 @@ export const filterMapOrFail: { * * **Example** (Conditionally running an effect) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" + * const output: Array = [] * * const shouldLog = true * * const program = Effect.when( - * Console.log("Condition is true!"), + * Effect.sync(() => { output.push("Condition is true!") }), * Effect.succeed(shouldLog) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: "Condition is true!" - * // { _id: 'Option', _tag: 'Some', value: undefined } + * void output.push(Effect.runSync(program)) + * output // => ["Condition is true!", Option.some(undefined)] * ``` * - * @category conditional operators + * @category filtering * @since 2.0.0 */ export const when: { @@ -5281,7 +5301,7 @@ export const when: { * * **Example** (Matching success and failure values) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class ExampleError extends Data.TaggedError("ExampleError")<{ readonly message: string }> {} @@ -5294,8 +5314,7 @@ export const when: { * }) * * // Run and log the result of the successful effect - * Effect.runPromise(program1).then(console.log) - * // Output: "success: 42" + * Effect.runSync(program1) // => "success: 42" * * const failure: Effect.Effect = Effect.fail( * new ExampleError({ message: "Uh oh!" }) @@ -5307,8 +5326,7 @@ export const when: { * }) * * // Run and log the result of the failed effect - * Effect.runPromise(program2).then(console.log) - * // Output: "failure: Uh oh!" + * Effect.runSync(program2) // => "failure: Uh oh!" * ``` * * @see {@link matchEffect} if you need to perform side effects in the handlers. @@ -5347,16 +5365,20 @@ export const match: { * * **Example** (Pattern matching eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const result = yield* Effect.matchEager(Effect.succeed(42), { * onFailure: (error) => `Failed: ${error}`, * onSuccess: (value) => `Success: ${value}` * }) - * console.log(result) // "Success: 42" + * void output.push(result) * }) + * + * Effect.runSync(program) + * output // => ["Success: 42"] * ``` * * @see {@link match} for the non-eager version. @@ -5393,7 +5415,7 @@ export const matchEager: { * * **Example** (Matching on success or failure causes) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect } from "effect" * * const task = Effect.fail("Something went wrong") @@ -5403,8 +5425,7 @@ export const matchEager: { * onSuccess: (value) => `Success: ${value}` * }) * - * Effect.runPromise(program).then(console.log) - * // Output: "Failed: Error: Something went wrong" + * Effect.runSync(program) // => "Failed: Something went wrong" * ``` * * @see {@link matchCauseEffect} if you need to perform side effects in the @@ -5443,13 +5464,14 @@ export const matchCause: { * * **Example** (Eagerly matching already completed effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const handleResult = Effect.matchCauseEager(Effect.succeed(42), { * onSuccess: (value) => `Success: ${value}`, * onFailure: (cause) => `Failed: ${cause}` * }) + * Effect.runSync(handleResult) // => "Success: 42" * ``` * * @category pattern matching @@ -5524,8 +5546,9 @@ export const matchCauseEffectEager: { * * **Example** (Effectfully matching on causes) * - * ```ts - * import { Cause, Console, Data, Effect, Result } from "effect" + * ```ts import.meta.vitest + * import { Cause, Data, Effect, Result } from "effect" + * const output: Array = [] * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} * @@ -5537,25 +5560,23 @@ export const matchCauseEffectEager: { * if (Cause.hasFails(cause)) { * const error = Cause.findError(cause) * if (Result.isSuccess(error)) { - * yield* Console.log(`Handling error: ${error.success.message}`) + * yield* Effect.sync(() => { output.push(`Handling error: ${error.success.message}`) }) * } * return "recovered from error" * } else { - * yield* Console.log("Handling interruption or defect") + * yield* Effect.sync(() => { output.push("Handling interruption or defect") }) * return "recovered from interruption/defect" * } * }), * onSuccess: (value) => * Effect.gen(function*() { - * yield* Console.log(`Success: ${value}`) + * yield* Effect.sync(() => { output.push(`Success: ${value}`) }) * return `processed ${value}` * }) * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Handling error: Task failed - * // recovered from error + * void output.push(Effect.runSync(program)) + * output // => ["Handling error: Task failed", "recovered from error"] * ``` * * @see {@link matchCause} if you don't need side effects and only want to handle the result or failure. @@ -5594,7 +5615,7 @@ export const matchCauseEffect: { * * **Example** (Matching success and failure with effectful handlers) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class ExampleError extends Data.TaggedError("ExampleError")<{ readonly message: string }> {} @@ -5606,31 +5627,21 @@ export const matchCauseEffect: { * * const program1 = Effect.matchEffect(success, { * onFailure: (error) => - * Effect.succeed(`failure: ${error.message}`).pipe( - * Effect.tap(Effect.log) - * ), + * Effect.succeed(`failure: ${error.message}`), * onSuccess: (value) => - * Effect.succeed(`success: ${value}`).pipe(Effect.tap(Effect.log)) + * Effect.succeed(`success: ${value}`) * }) * - * console.log(Effect.runSync(program1)) - * // Output: - * // timestamp=... level=INFO fiber=#0 message="success: 42" - * // success: 42 + * Effect.runSync(program1) // => "success: 42" * * const program2 = Effect.matchEffect(failure, { * onFailure: (error) => - * Effect.succeed(`failure: ${error.message}`).pipe( - * Effect.tap(Effect.log) - * ), + * Effect.succeed(`failure: ${error.message}`), * onSuccess: (value) => - * Effect.succeed(`success: ${value}`).pipe(Effect.tap(Effect.log)) + * Effect.succeed(`success: ${value}`) * }) * - * console.log(Effect.runSync(program2)) - * // Output: - * // timestamp=... level=INFO fiber=#1 message="failure: Uh oh!" - * // failure: Uh oh! + * Effect.runSync(program2) // => "failure: Uh oh!" * ``` * * @see {@link match} if you don't need side effects and only want to handle the @@ -5665,19 +5676,20 @@ export const matchEffect: { * * **Example** (Checking whether an effect fails) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const failed = yield* Effect.isFailure(Effect.fail("Uh oh!")) - * yield* Console.log(failed) + * yield* Effect.sync(() => { output.push(failed) }) * }) * - * Effect.runPromise(program) - * // Output: true + * Effect.runSync(program) + * output // => [true] * ``` * - * @category condition checking + * @category predicates * @since 2.0.0 */ export const isFailure: (self: Effect) => Effect = internal.isFailure @@ -5692,23 +5704,22 @@ export const isFailure: (self: Effect) => Effect = [] * * const program = Effect.gen(function*() { * const ok = yield* Effect.isSuccess(Effect.succeed("done")) * const failed = yield* Effect.isSuccess(Effect.fail("Uh oh")) - * yield* Console.log(`ok: ${ok}`) - * yield* Console.log(`failed: ${failed}`) + * yield* Effect.sync(() => { output.push(`ok: ${ok}`) }) + * yield* Effect.sync(() => { output.push(`failed: ${failed}`) }) * }) * - * Effect.runPromise(program) - * // Output: - * // ok: true - * // failed: false + * Effect.runSync(program) + * output // => ["ok: true", "failed: false"] * ``` * - * @category condition checking + * @category predicates * @since 2.0.0 */ export const isSuccess: (self: Effect) => Effect = internal.isSuccess @@ -5732,8 +5743,9 @@ export const isSuccess: (self: Effect) => Effect = [] * * const Logger = Context.Service<{ * log: (msg: string) => void @@ -5749,20 +5761,22 @@ export const isSuccess: (self: Effect) => Effect { output.push(`Logger available: ${Option.isSome(loggerOption)}`) }) + * yield* Effect.sync(() => { output.push(`Database available: ${Option.isSome(databaseOption)}`) }) * }) * - * const context = Context.make(Logger, { log: console.log }) + * const context = Context.make(Logger, { log: () => {} }) * .pipe(Context.add(Database, { query: () => "result" })) * * const provided = Effect.provideContext(program, context) + * Effect.runSync(provided) + * output // => ["Logger available: true", "Database available: true"] * ``` * * @see {@link contextWith} for deriving an effect from the complete context * @see {@link service} for reading one service from the context * - * @category environment + * @category accessors * @since 2.0.0 */ export const context: () => Effect, never, R> = internal.context @@ -5782,8 +5796,9 @@ export const context: () => Effect, never, R> = in * * **Example** (Deriving values from the context) * - * ```ts - * import { Console, Context, Effect, Option } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Option } from "effect" + * const output: Array = [] * * const Logger = Context.Service<{ * log: (msg: string) => void @@ -5792,19 +5807,19 @@ export const context: () => Effect, never, R> = in * get: (key: string) => string | null * }>("Cache") * - * const program = Effect.contextWith((services) => { + * const program = Effect.contextWith((services: Context.Context>) => { * const cacheOption = Context.getOption(services, Cache) * const hasCache = Option.isSome(cacheOption) * * if (hasCache) { * return Effect.gen(function*() { * const cache = yield* Effect.service(Cache) - * yield* Console.log("Using cached data") + * yield* Effect.sync(() => { output.push("Using cached data") }) * return cache.get("user:123") || "default" * }) * } else { * return Effect.gen(function*() { - * yield* Console.log("No cache available, using fallback") + * yield* Effect.sync(() => { output.push("No cache available, using fallback") }) * return "fallback data" * }) * } @@ -5813,12 +5828,14 @@ export const context: () => Effect, never, R> = in * const withCache = Effect.provideService(program, Cache, { * get: () => "cached_value" * }) + * void output.push(Effect.runSync(withCache)) + * output // => ["Using cached data", "cached_value"] * ``` * * @see {@link context} for reading the complete context as a value * @see {@link service} for reading one service from the context * - * @category environment + * @category accessors * @since 2.0.0 */ export const contextWith: ( @@ -5832,7 +5849,7 @@ export const contextWith: ( * * **Example** (Providing dependencies with a layer) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * interface Database { @@ -5841,7 +5858,7 @@ export const contextWith: ( * * const Database = Context.Service("Database") * - * const DatabaseLive = Layer.succeed(Database)({ + * const DatabaseLayer = Layer.succeed(Database)({ * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`)) * }) * @@ -5850,13 +5867,12 @@ export const contextWith: ( * return yield* db.query("SELECT * FROM users") * }) * - * const provided = Effect.provide(program, DatabaseLive) + * const provided = Effect.provide(program, DatabaseLayer) * - * Effect.runPromise(provided).then(console.log) - * // Output: "Result for: SELECT * FROM users" + * await Effect.runPromise(provided) // => "Result for: SELECT * FROM users" * ``` * - * @category environment + * @category providing services * @since 2.0.0 */ export const provide: { @@ -5918,8 +5934,9 @@ export const provide: { * * **Example** (Providing a complete context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define service keys * const Logger = Context.Service<{ @@ -5930,7 +5947,7 @@ export const provide: { * }>("Database") * * // Create a context with multiple services - * const context = Context.make(Logger, { log: console.log }) + * const context = Context.make(Logger, { log: (message) => { output.push(message) } }) * .pipe(Context.add(Database, { query: () => "result" })) * * // An effect that requires both services @@ -5942,9 +5959,11 @@ export const provide: { * }) * * const provided = Effect.provideContext(program, context) + * void output.push(Effect.runSync(provided)) + * output // => ["Querying database", "result"] * ``` * - * @category environment + * @category providing services * @since 4.0.0 */ export const provideContext: { @@ -5957,12 +5976,58 @@ export const provideContext: { ): Effect> } = internal.provideContext +/** + * Runs an effect with the provided context as its complete environment. + * + * **When to use** + * + * Use when you already have a `Context` containing every service required by + * the effect and want the wrapped effect to run with exactly that context. + * + * **Gotchas** + * + * `setContext` replaces the current context for the wrapped effect. Services + * from an outer context are not inherited unless they are also present in the + * context passed to `setContext`. + * + * **Example** (Running with a complete context) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * + * class Config extends Context.Service()("Config") {} + * + * const program = Effect.gen(function*() { + * const config = yield* Effect.service(Config) + * return `${config.greeting}, World!` + * }) + * + * const context = Context.make(Config, { greeting: "Hello" }) + * + * const runnable = Effect.setContext(program, context) + * + * Effect.runSync(runnable) // => "Hello, World!" + * ``` + * + * @see {@link provideContext} for partially satisfying an effect's context requirements. + * @see {@link updateContext} for deriving the required context from the current one. + * + * @category providing services + * @since 4.0.0 + */ +export const setContext: { + (context: Context.Context): (self: Effect) => Effect + (self: Effect, context: Context.Context): Effect +} = internal.setContext + /** * Accesses a service from the context. * * **Example** (Accessing a required service) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * interface Database { @@ -5975,9 +6040,14 @@ export const provideContext: { * const db = yield* Effect.service(Database) * return yield* db.query("SELECT * FROM users") * }) + * + * const runnable = Effect.provideService(program, Database, { + * query: (sql) => Effect.succeed(`Result for: ${sql}`) + * }) + * Effect.runSync(runnable) // => "Result for: SELECT * FROM users" * ``` * - * @category context + * @category accessors * @since 4.0.0 */ export const service: (service: Context.Key) => Effect = internal.service @@ -5999,8 +6069,9 @@ export const service: (service: Context.Key) => Effect * * **Example** (Accessing an optional service) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Option } from "effect" + * const output: Array = [] * * // Define a service key * const Logger = Context.Service<{ @@ -6014,12 +6085,15 @@ export const service: (service: Context.Key) => Effect * if (Option.isSome(maybeLogger)) { * maybeLogger.value.log("Service is available") * } else { - * console.log("Service not available") + * void output.push("Service not available") * } * }) + * + * Effect.runSync(program) + * output // => ["Service not available"] * ``` * - * @category context + * @category accessors * @since 2.0.0 */ export const serviceOption: (key: Context.Key) => Effect> = internal.serviceOption @@ -6034,7 +6108,7 @@ export const serviceOption: (key: Context.Key) => Effect> * * **Example** (Updating the context before running) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * // Define services @@ -6051,18 +6125,19 @@ export const serviceOption: (key: Context.Key) => Effect> * * // Transform services by providing Config while keeping Logger requirement * const configured = program.pipe( - * Effect.updateContext((context: Context.Context) => + * Effect.updateContext((context: Context.Context>) => * Context.add(context, Config, { name: "World" }) * ) * ) * * // The effect now requires only Logger service * const result = Effect.provideService(configured, Logger, { - * log: (msg) => console.log(msg) + * log: () => {} * }) + * Effect.runSync(result) // => "Hello World!" * ``` * - * @category context + * @category providing services * @since 4.0.0 */ export const updateContext: { @@ -6086,15 +6161,16 @@ export const updateContext: { * * **Example** (Replacing a service for one effect) * - * ```ts - * import { Console, Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define a counter service * const Counter = Context.Service<{ count: number }>("Counter") * * const program = Effect.gen(function*() { * const updatedCounter = yield* Effect.service(Counter) - * yield* Console.log(`Updated count: ${updatedCounter.count}`) + * yield* Effect.sync(() => { output.push(`Updated count: ${updatedCounter.count}`) }) * return updatedCounter.count * }).pipe( * Effect.updateService(Counter, (counter) => ({ count: counter.count + 1 })) @@ -6102,26 +6178,92 @@ export const updateContext: { * * // Provide initial service and run * const result = Effect.provideService(program, Counter, { count: 0 }) - * Effect.runPromise(result).then(console.log) - * // Output: Updated count: 1 - * // 1 + * void output.push(Effect.runSync(result)) + * output // => ["Updated count: 1", 1] * ``` * - * @category context + * @category providing services * @since 2.0.0 */ export const updateService: { ( service: Context.Key, - f: (value: A) => A + f: (value: A) => NoInfer ): (self: Effect) => Effect ( self: Effect, service: Context.Key, - f: (value: A) => A + f: (value: A) => NoInfer ): Effect } = internal.updateService +/** + * Updates a service for the lifetime of the current scope and restores its + * previous value when the scope closes. + * + * **When to use** + * + * Use when you need a setup effect to change a service for subsequent effects + * in the same scope. + * + * **Details** + * + * The updater receives the currently visible service value. A + * `Context.Service` remains in the requirements, while a `Context.Reference` + * uses its default when no override is present and adds no service requirement. + * The returned effect always requires `Scope`. The optional `reset` function + * receives the original, updated, and current values when the scope closes, + * allowing changes to be merged during restoration. It defaults to returning + * the original value. + * + * **Example** (Updating a reference within a scope) + * + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] + * + * const CurrentNumber = Context.Reference("CurrentNumber", { + * defaultValue: () => 1 + * }) + * + * const program = Effect.gen(function*() { + * const before = yield* CurrentNumber + * const during = yield* Effect.scoped( + * Effect.gen(function*() { + * yield* Effect.updateServiceScoped( + * CurrentNumber, + * (value) => value + 1, + * { + * // Optional: when omitted, the original value is restored + * reset: (original, updated, current) => + * Math.max(original, updated, current) + 1 + * } + * ) + * return yield* CurrentNumber + * }) + * ) + * const after = yield* CurrentNumber + * + * void output.push([before, during, after]) + * }) + * + * await Effect.runPromise(program) + * output // => [[1, 2, 3]] + * ``` + * + * @see {@link updateService} for updating a service only within a wrapped effect + * + * @category providing services + * @since 4.0.0 + */ +export const updateServiceScoped: ( + service: Context.Key, + f: (value: A) => NoInfer, + options?: { + readonly reset?: ((original: A, updated: A, current: A) => A) | undefined + } | undefined +) => Effect = internal.updateServiceScoped + /** * Provides one concrete service implementation to an effect. * @@ -6136,8 +6278,9 @@ export const updateService: { * * **Example** (Providing a service value) * - * ```ts - * import { Console, Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define a service for configuration * const Config = Context.Service<{ @@ -6147,8 +6290,8 @@ export const updateService: { * * const fetchData = Effect.gen(function*() { * const config = yield* Effect.service(Config) - * yield* Console.log(`Fetching from: ${config.apiUrl}`) - * yield* Console.log(`Timeout: ${config.timeout}ms`) + * yield* Effect.sync(() => { output.push(`Fetching from: ${config.apiUrl}`) }) + * yield* Effect.sync(() => { output.push(`Timeout: ${config.timeout}ms`) }) * return "data" * }) * @@ -6158,17 +6301,14 @@ export const updateService: { * timeout: 5000 * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Fetching from: https://api.example.com - * // Timeout: 5000ms - * // data + * void output.push(Effect.runSync(program)) + * output // => ["Fetching from: https://api.example.com", "Timeout: 5000ms", "data"] * ``` * * @see {@link provide} for providing multiple layers to an effect. * @see {@link provideServiceEffect} for acquiring the service implementation effectfully. * @see {@link provideContext} for providing a complete context. - * @category context + * @category providing services * @since 2.0.0 */ export const provideService: { @@ -6206,8 +6346,9 @@ export const provideService: { * * **Example** (Providing a service with an effect) * - * ```ts - * import { Console, Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * // Define a database connection service * interface DatabaseConnection { @@ -6217,9 +6358,8 @@ export const provideService: { * * // Effect that creates a database connection * const createConnection = Effect.gen(function*() { - * yield* Console.log("Establishing database connection...") - * yield* Effect.sleep("100 millis") // Simulate connection time - * yield* Console.log("Database connected!") + * yield* Effect.sync(() => { output.push("Establishing database connection...") }) + * yield* Effect.sync(() => { output.push("Database connected!") }) * return { * query: (sql: string) => Effect.succeed(`Result for: ${sql}`) * } @@ -6237,74 +6377,25 @@ export const provideService: { * createConnection * ) * - * Effect.runPromise(withDatabase).then(console.log) - * // Output: - * // Establishing database connection... - * // Database connected! - * // Result for: SELECT * FROM users + * void output.push(await Effect.runPromise(withDatabase)) + * output // => ["Establishing database connection...", "Database connected!", "Result for: SELECT * FROM users"] * ``` * - * @category context + * @category providing services * @since 2.0.0 */ export const provideServiceEffect: { ( service: Context.Key, - acquire: Effect + acquire: Effect, E2, R2> ): (self: Effect) => Effect | R2> ( self: Effect, service: Context.Key, - acquire: Effect + acquire: Effect, E2, R2> ): Effect | R2> } = internal.provideServiceEffect -// ----------------------------------------------------------------------------- -// References -// ----------------------------------------------------------------------------- - -/** - * Sets the concurrency level for parallel operations within an effect. - * - * **Example** (Setting local concurrency) - * - * ```ts - * import { Console, Effect } from "effect" - * - * const task = (id: number) => - * Effect.gen(function*() { - * yield* Console.log(`Task ${id} starting`) - * yield* Effect.sleep("100 millis") - * yield* Console.log(`Task ${id} completed`) - * return id - * }) - * - * // Run tasks with limited concurrency (max 2 at a time) - * const program = Effect.gen(function*() { - * const tasks = [1, 2, 3, 4, 5].map(task) - * return yield* Effect.all(tasks, { concurrency: 2 }) - * }).pipe( - * Effect.withConcurrency(2) - * ) - * - * Effect.runPromise(program).then(console.log) - * // Tasks will run with max 2 concurrent operations - * // [1, 2, 3, 4, 5] - * ``` - * - * @category references - * @since 2.0.0 - */ -export const withConcurrency: { - ( - concurrency: number | "unbounded" - ): (self: Effect) => Effect - ( - self: Effect, - concurrency: number | "unbounded" - ): Effect -} = internal.withConcurrency - // ----------------------------------------------------------------------------- // Resource management & finalization // ----------------------------------------------------------------------------- @@ -6314,28 +6405,25 @@ export const withConcurrency: { * * **Example** (Accessing the current scope) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const currentScope = yield* Effect.scope - * yield* Console.log("Got scope for resource management") + * yield* Effect.sync(() => { output.push("Got scope for resource management") }) * * // Use the scope to manually manage resources if needed * const resource = yield* Effect.acquireRelease( - * Console.log("Acquiring resource").pipe(Effect.as("resource")), - * () => Console.log("Releasing resource") + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) * ) * * return resource * }) * - * Effect.runPromise(Effect.scoped(program)).then(console.log) - * // Output: - * // Got scope for resource management - * // Acquiring resource - * // resource - * // Releasing resource + * void output.push(Effect.runSync(Effect.scoped(program))) + * output // => ["Got scope for resource management", "Acquiring resource", "Releasing resource", "resource"] * ``` * * @category resource management @@ -6357,26 +6445,25 @@ export const scope: Effect = internal.scope * * **Example** (Running a scoped acquisition) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const resource = Effect.acquireRelease( - * Console.log("Acquiring resource").pipe(Effect.as("resource")), - * () => Console.log("Releasing resource") + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) * ) * * const program = Effect.scoped( * Effect.gen(function*() { * const res = yield* resource - * yield* Console.log(`Using ${res}`) + * yield* Effect.sync(() => { output.push(`Using ${res}`) }) * return res * }) * ) * - * Effect.runFork(program) - * // Output: "Acquiring resource" - * // Output: "Using resource" - * // Output: "Releasing resource" + * Effect.runSync(program) + * output // => ["Acquiring resource", "Using resource", "Releasing resource"] * ``` * * @category resource management @@ -6396,21 +6483,22 @@ export const scoped: ( * * **Example** (Working with an explicit scope) * - * ```ts - * import { Console, Effect, Scope } from "effect" + * ```ts import.meta.vitest + * import { Effect, Scope } from "effect" + * const output: Array = [] * * const program = Effect.scopedWith((scope) => * Effect.gen(function*() { - * yield* Console.log("Inside scoped context") + * yield* Effect.sync(() => { output.push("Inside scoped context") }) * * // Manually add a finalizer to the scope - * yield* Scope.addFinalizer(scope, Console.log("Manual finalizer")) + * yield* Scope.addFinalizer(scope, Effect.sync(() => { output.push("Manual finalizer") })) * * // Create a scoped resource * const resource = yield* Effect.scoped( * Effect.acquireRelease( - * Console.log("Acquiring resource").pipe(Effect.as("resource")), - * () => Console.log("Releasing resource") + * Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")), + * () => Effect.sync(() => { output.push("Releasing resource") }) * ) * ) * @@ -6418,13 +6506,8 @@ export const scoped: ( * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Inside scoped context - * // Acquiring resource - * // resource - * // Releasing resource - * // Manual finalizer + * void output.push(Effect.runSync(program)) + * output // => ["Inside scoped context", "Acquiring resource", "Releasing resource", "Manual finalizer", "resource"] * ``` * * @category resource management @@ -6453,8 +6536,9 @@ export const scopedWith: ( * * **Example** (Acquiring and releasing a resource) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * // Simulate a resource that needs cleanup * interface FileHandle { @@ -6464,17 +6548,17 @@ export const scopedWith: ( * * // Acquire a file handle * const acquire = Effect.gen(function*() { - * yield* Console.log("Opening file") + * yield* Effect.sync(() => { output.push("Opening file") }) * return { path: "/tmp/file.txt", content: "file content" } * }) * * // Release the file handle * const release = (handle: FileHandle, exit: Exit.Exit) => - * Console.log( + * Effect.sync(() => { output.push( * `Closing file ${handle.path} with exit: ${ * Exit.isSuccess(exit) ? "success" : "failure" * }` - * ) + * ) }) * * // Create a scoped resource * const resource = Effect.acquireRelease(acquire, release) @@ -6483,10 +6567,13 @@ export const scopedWith: ( * const program = Effect.scoped( * Effect.gen(function*() { * const handle = yield* resource - * yield* Console.log(`Using file: ${handle.path}`) + * yield* Effect.sync(() => { output.push(`Using file: ${handle.path}`) }) * return handle.content * }) * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Opening file", "Using file: /tmp/file.txt", "Closing file /tmp/file.txt with exit: success", "file content"] * ``` * * @see {@link acquireDisposable} for resources that implement JavaScript disposal protocols @@ -6523,22 +6610,25 @@ export const acquireRelease: ( * * **Example** (Acquiring a disposable resource) * - * ```ts - * import sqlite from "node:sqlite"; + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * - * const program = Effect.scoped( - * Effect.gen(function* () { - * // acquire database connection - * // database will be closed when the scope is closed - * const db = yield* Effect.acquireDisposable( - * Effect.sync(() => new sqlite.DatabaseSync(":memory:")) - * ) + * class Resource implements Disposable { + * [Symbol.dispose]() { + * void output.push("disposed") + * } + * } * - * const row = db.prepare("SELECT 1 AS value").get() - * yield* Effect.log(row) // { value: 1 } + * const program = Effect.scoped( + * Effect.gen(function*() { + * yield* Effect.acquireDisposable(Effect.succeed(new Resource())) + * void output.push("acquired") * }) * ) + * + * Effect.runSync(program) + * output // => ["acquired", "disposed"] * ``` * * @see {@link acquireRelease} for resources that need an explicit finalizer @@ -6578,8 +6668,9 @@ export const acquireDisposable: ( * * **Example** (Acquiring resources with cleanup) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * interface Database { * readonly connection: string @@ -6589,7 +6680,7 @@ export const acquireDisposable: ( * const program = Effect.acquireUseRelease( * // Acquire - connect to database * Effect.gen(function*() { - * yield* Console.log("Connecting to database...") + * yield* Effect.sync(() => { output.push("Connecting to database...") }) * return { * connection: "db://localhost:5432", * query: (sql: string) => Effect.succeed(`Result for: ${sql}`) @@ -6598,28 +6689,24 @@ export const acquireDisposable: ( * // Use - perform database operations * (db) => * Effect.gen(function*() { - * yield* Console.log(`Connected to ${db.connection}`) + * yield* Effect.sync(() => { output.push(`Connected to ${db.connection}`) }) * const result = yield* db.query("SELECT * FROM users") - * yield* Console.log(`Query result: ${result}`) + * yield* Effect.sync(() => { output.push(`Query result: ${result}`) }) * return result * }), * // Release - close database connection * (db, exit) => * Effect.gen(function*() { * if (Exit.isSuccess(exit)) { - * yield* Console.log(`Closing connection to ${db.connection} (success)`) + * yield* Effect.sync(() => { output.push(`Closing connection to ${db.connection} (success)`) }) * } else { - * yield* Console.log(`Closing connection to ${db.connection} (failure)`) + * yield* Effect.sync(() => { output.push(`Closing connection to ${db.connection} (failure)`) }) * } * }) * ) * - * Effect.runPromise(program) - * // Output: - * // Connecting to database... - * // Connected to db://localhost:5432 - * // Query result: Result for: SELECT * FROM users - * // Closing connection to db://localhost:5432 (success) + * await Effect.runPromise(program) + * output // => ["Connecting to database...", "Connected to db://localhost:5432", "Query result: Result for: SELECT * FROM users", "Closing connection to db://localhost:5432 (success)"] * ``` * * @see {@link acquireRelease} for scoped resources whose use happens later @@ -6647,32 +6734,30 @@ export const acquireUseRelease: ( * * **Example** (Registering scope finalizers) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const program = Effect.scoped( * Effect.gen(function*() { * // Add a finalizer that runs when the scope closes * yield* Effect.addFinalizer((exit) => - * Console.log( + * Effect.sync(() => { output.push( * Exit.isSuccess(exit) * ? "Cleanup: Operation completed successfully" * : "Cleanup: Operation failed, cleaning up resources" - * ) + * ) }) * ) * - * yield* Console.log("Performing main operation...") + * yield* Effect.sync(() => { output.push("Performing main operation...") }) * * // This could succeed or fail * return "operation result" * }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Performing main operation... - * // Cleanup: Operation completed successfully - * // operation result + * void output.push(Effect.runSync(program)) + * output // => ["Performing main operation...", "Cleanup: Operation completed successfully", "operation result"] * ``` * * @see {@link acquireRelease} for resource acquisition with a release finalizer @@ -6700,28 +6785,24 @@ export const addFinalizer: ( * * **Example** (Always running cleanup) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const task = Effect.gen(function*() { - * yield* Console.log("Task started") - * yield* Effect.sleep("1 second") - * yield* Console.log("Task completed") + * yield* Effect.sync(() => { output.push("Task started") }) + * yield* Effect.sync(() => { output.push("Task completed") }) * return 42 * }) * * // Ensure cleanup always runs, regardless of success or failure * const program = Effect.ensuring( * task, - * Console.log("Cleanup: This always runs!") + * Effect.sync(() => { output.push("Cleanup: This always runs!") }) * ) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Task started - * // Task completed - * // Cleanup: This always runs! - * // 42 + * void output.push(Effect.runSync(program)) + * output // => ["Task started", "Task completed", "Cleanup: This always runs!", 42] * ``` * * @category resource management @@ -6743,22 +6824,22 @@ export const ensuring: { * * **Example** (Running cleanup on failure) * - * ```ts - * import { Cause, Console, Data, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Data, Effect, Exit } from "effect" + * const output: Array = [] * * class TaskError extends Data.TaggedError("TaskError")<{ readonly message: string }> {} * - * const task = Effect.fail(new TaskError({ message: "Something went wrong" })) + * const error = new TaskError({ message: "Something went wrong" }) + * const task = Effect.fail(error) * * const program = Effect.onError( * task, - * (cause) => Console.log(`Cleanup on error: ${Cause.squash(cause)}`) + * (cause) => Effect.sync(() => { output.push(`Cleanup on error: ${Cause.squash(cause)}`) }) * ) * - * Effect.runPromise(program).catch(console.error) - * // Output: - * // Cleanup on error: TaskError: Something went wrong - * // TaskError: Something went wrong + * void output.push(Effect.runSyncExit(program)) + * output // => ["Cleanup on error: TaskError: Something went wrong", Exit.fail(error)] * ``` * * @category resource management @@ -6780,8 +6861,9 @@ export const onError: { * * **Example** (Running cleanup for selected failures) * - * ```ts - * import { Cause, Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.fail("boom") * @@ -6790,9 +6872,12 @@ export const onError: { * Cause.hasFails, * (cause) => * Effect.gen(function*() { - * yield* Console.log(`Cause: ${Cause.pretty(cause)}`) + * yield* Effect.sync(() => { output.push(`Cause: ${Cause.squash(cause)}`) }) * }) * ) + * + * void output.push(Effect.runSyncExit(program)) + * output // => ["Cause: boom", Exit.fail("boom")] * ``` * * @category resource management @@ -6851,7 +6936,8 @@ export const onErrorFilter: { * **Details** * * This low-level operator preserves the source effect's result unless the - * finalizer fails. Prefer `onExit` for normal cleanup logic. + * finalizer fails. If both the source effect and the finalizer fail, the two + * causes are merged. Prefer `onExit` for normal cleanup logic. * * @see {@link onExit} for ordinary exit-aware cleanup whose finalizer always returns an effect * @@ -6868,24 +6954,27 @@ export const onExitPrimitive: ( * Ensures that a cleanup function runs whether this effect succeeds, fails, or * is interrupted. * + * **Details** + * + * If both the effect and the cleanup function fail, the two causes are merged. + * * **Example** (Observing every exit) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const task = Effect.succeed(42) * * const program = Effect.onExit(task, (exit) => - * Console.log( + * Effect.sync(() => { output.push( * Exit.isSuccess(exit) * ? `Task succeeded with: ${exit.value}` * : `Task failed: ${Exit.isFailure(exit) ? exit.cause : "interrupted"}` - * )) + * ) })) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Task succeeded with: 42 - * // 42 + * void output.push(Effect.runSync(program)) + * output // => ["Task succeeded with: 42", 42] * ``` * * @category resource management @@ -6907,17 +6996,21 @@ export const onExit: { * * **Example** (Observing selected exits) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const program = Effect.onExitIf( * Effect.succeed(42), * Exit.isSuccess, * (exit) => * Exit.isSuccess(exit) - * ? Console.log(`Succeeded with: ${exit.value}`) + * ? Effect.sync(() => { output.push(`Succeeded with: ${exit.value}`) }) * : Effect.void * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Succeeded with: 42", 42] * ``` * * @category resource management @@ -6990,40 +7083,29 @@ export const onExitFilter: { * * **Example** (Memoizing an effect until invalidated) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * let i = 1 - * const expensiveTask = Effect.promise(() => { - * console.log("expensive task...") - * return new Promise((resolve) => { - * setTimeout(() => { - * resolve(`result ${i++}`) - * }, 100) - * }) + * const expensiveTask = Effect.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` * }) * * const program = Effect.gen(function*() { - * console.log("non-cached version:") - * yield* expensiveTask.pipe(Effect.andThen(Console.log)) - * yield* expensiveTask.pipe(Effect.andThen(Console.log)) - * console.log("cached version:") + * void output.push("non-cached version:") + * yield* expensiveTask.pipe(Effect.andThen(record)) + * yield* expensiveTask.pipe(Effect.andThen(record)) + * void output.push("cached version:") * const cached = yield* Effect.cached(expensiveTask) - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* cached.pipe(Effect.andThen(Console.log)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) * }) * - * Effect.runFork(program) - * // Output: - * // non-cached version: - * // expensive task... - * // result 1 - * // expensive task... - * // result 2 - * // cached version: - * // expensive task... - * // result 3 - * // result 3 + * await Effect.runPromise(program) + * output // => ["non-cached version:", "expensive task...", "result 1", "expensive task...", "result 2", "cached version:", "expensive task...", "result 3", "result 3"] * ``` * * @see {@link cachedWithTTL} for a similar function that includes a @@ -7058,34 +7140,26 @@ export const cached: (self: Effect) => Effect> * * **Example** (Memoizing an effect with TTL) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * let i = 1 - * const expensiveTask = Effect.promise(() => { - * console.log("expensive task...") - * return new Promise((resolve) => { - * setTimeout(() => { - * resolve(`result ${i++}`) - * }, 100) - * }) + * const expensiveTask = Effect.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` * }) * * const program = Effect.gen(function*() { - * const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis") - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* Effect.sleep("100 millis") - * yield* cached.pipe(Effect.andThen(Console.log)) + * const cached = yield* Effect.cachedWithTTL(expensiveTask, "1 hour") + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) * }) * - * Effect.runFork(program) - * // Output: - * // expensive task... - * // result 1 - * // result 1 - * // expensive task... - * // result 2 + * Effect.runSync(program) + * output // => ["expensive task...", "result 1", "result 1", "result 1"] * ``` * * @see {@link cached} for a similar function that caches the result @@ -7125,17 +7199,15 @@ export const cachedWithTTL: { * * **Example** (Memoizing with TTL and invalidation) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] + * const record = (value: unknown) => Effect.sync(() => { output.push(value) }) * * let i = 1 - * const expensiveTask = Effect.promise(() => { - * console.log("expensive task...") - * return new Promise((resolve) => { - * setTimeout(() => { - * resolve(`result ${i++}`) - * }, 100) - * }) + * const expensiveTask = Effect.sync(() => { + * void output.push("expensive task...") + * return `result ${i++}` * }) * * const program = Effect.gen(function*() { @@ -7143,19 +7215,14 @@ export const cachedWithTTL: { * expensiveTask, * "1 hour" * ) - * yield* cached.pipe(Effect.andThen(Console.log)) - * yield* cached.pipe(Effect.andThen(Console.log)) + * yield* cached.pipe(Effect.andThen(record)) + * yield* cached.pipe(Effect.andThen(record)) * yield* invalidate - * yield* cached.pipe(Effect.andThen(Console.log)) + * yield* cached.pipe(Effect.andThen(record)) * }) * - * Effect.runFork(program) - * // Output: - * // expensive task... - * // result 1 - * // result 1 - * // expensive task... - * // result 2 + * Effect.runSync(program) + * output // => ["expensive task...", "result 1", "result 1", "expensive task...", "result 2"] * ``` * * @see {@link cached} for a similar function that caches the result @@ -7179,7 +7246,7 @@ export const cachedInvalidateWithTTL: { * * **Example** (Creating an interrupted effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { @@ -7187,8 +7254,7 @@ export const cachedInvalidateWithTTL: { * yield* Effect.succeed("This won't execute and is unreachable") * }) * - * Effect.runPromise(program).catch(console.error) - * // Throws: InterruptedException + * Effect.runSyncExit(program)._tag // => "Failure" * ``` * * @category interruption @@ -7201,16 +7267,13 @@ export const interrupt: Effect = internal.interrupt * * **Example** (Allowing interruption) * - * ```ts - * import { Effect } from "effect" - * - * const longRunning = Effect.forever(Effect.succeed("working...")) - * - * const program = Effect.interruptible(longRunning) + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * // This effect can now be interrupted - * const fiber = Effect.runFork(program) - * // Later: fiber.interrupt() + * const program = Effect.interruptible(Effect.never).pipe( + * Effect.timeoutOption(0) + * ) + * await Effect.runPromise(program) // => Option.none() * ``` * * @category interruption @@ -7225,20 +7288,20 @@ export const interruptible: ( * * **Example** (Running cleanup on interruption) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * const output: Array = [] * * const task = Effect.forever(Effect.succeed("working...")) * * const program = Effect.onInterrupt( * task, - * () => Console.log("Task was interrupted, cleaning up...") + * () => Effect.sync(() => { output.push("Task was interrupted, cleaning up...") }) * ) * * const fiber = Effect.runFork(program) - * // Later interrupt the task - * Effect.runFork(Fiber.interrupt(fiber)) - * // Output: Task was interrupted, cleaning up... + * await Effect.runPromise(Fiber.interrupt(fiber)) + * output // => ["Task was interrupted, cleaning up..."] * ``` * * @category interruption @@ -7259,20 +7322,19 @@ export const onInterrupt: { * * **Example** (Preventing interruption) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const criticalTask = Effect.gen(function*() { - * yield* Console.log("Starting critical section...") - * yield* Effect.sleep("2 seconds") - * yield* Console.log("Critical section completed") + * yield* Effect.sync(() => { output.push("Starting critical section...") }) + * yield* Effect.sync(() => { output.push("Critical section completed") }) * }) * * const program = Effect.uninterruptible(criticalTask) * - * const fiber = Effect.runFork(program) - * // Even if interrupted, the critical task will complete - * Effect.runPromise(Fiber.interrupt(fiber)) + * Effect.runSync(program) + * output // => ["Starting critical section...", "Critical section completed"] * ``` * * @category interruption @@ -7288,25 +7350,26 @@ export const uninterruptible: ( * * **Example** (Restoring interruption in protected regions) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.uninterruptibleMask((restore) => * Effect.gen(function*() { - * yield* Console.log("Uninterruptible phase...") - * yield* Effect.sleep("1 second") - * + * yield* Effect.sync(() => { output.push("Uninterruptible phase...") }) * // Restore interruptibility for this part * yield* restore( * Effect.gen(function*() { - * yield* Console.log("Interruptible phase...") - * yield* Effect.sleep("2 seconds") + * yield* Effect.sync(() => { output.push("Interruptible phase...") }) * }) * ) * - * yield* Console.log("Back to uninterruptible") + * yield* Effect.sync(() => { output.push("Back to uninterruptible") }) * }) * ) + * + * Effect.runSync(program) + * output // => ["Uninterruptible phase...", "Interruptible phase...", "Back to uninterruptible"] * ``` * * @category interruption @@ -7324,25 +7387,26 @@ export const uninterruptibleMask: ( * * **Example** (Controlling interruptibility locally) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.interruptibleMask((restore) => * Effect.gen(function*() { - * yield* Console.log("Interruptible phase...") - * yield* Effect.sleep("1 second") - * + * yield* Effect.sync(() => { output.push("Interruptible phase...") }) * // Make this part uninterruptible * yield* restore( * Effect.gen(function*() { - * yield* Console.log("Uninterruptible phase...") - * yield* Effect.sleep("2 seconds") + * yield* Effect.sync(() => { output.push("Uninterruptible phase...") }) * }) * ) * - * yield* Console.log("Back to interruptible") + * yield* Effect.sync(() => { output.push("Back to interruptible") }) * }) * ) + * + * Effect.runSync(program) + * output // => ["Interruptible phase...", "Uninterruptible phase...", "Back to interruptible"] * ``` * * @category interruption @@ -7440,26 +7504,11 @@ export declare namespace Repeat { * * **Example** (Repeating forever) * - * ```ts - * import { Console, Effect, Fiber } from "effect" - * - * const task = Effect.gen(function*() { - * yield* Console.log("Task running...") - * yield* Effect.sleep("1 second") - * }) - * - * // This will run forever, printing every second - * const program = task.pipe(Effect.forever) - * - * // This will run forever, without yielding every iteration - * const programNoYield = task.pipe(Effect.forever({ disableYield: true })) + * ```ts import.meta.vitest + * import { Effect, Option } from "effect" * - * // Run for 5 seconds then interrupt - * const timedProgram = Effect.gen(function*() { - * const fiber = yield* Effect.forkChild(program) - * yield* Effect.sleep("5 seconds") - * yield* Fiber.interrupt(fiber) - * }) + * const program = Effect.forever(Effect.never).pipe(Effect.timeoutOption(0)) + * await Effect.runPromise(program) // => Option.none() * ``` * * @category repetition @@ -7512,41 +7561,45 @@ export const forever: < * * **Example** (Repeating successful effects with a schedule) * - * ```ts + * ```ts import.meta.vitest * // Success Example - * import { Console, Effect, Schedule } from "effect" + * import { Effect, Schedule } from "effect" + * const output: Array = [] * - * const action = Console.log("success") - * const policy = Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("100 millis")) + * const action = Effect.sync(() => { output.push("success") }) + * const policy = Schedule.recurs(2) * const program = Effect.repeat(action, policy) * - * // Effect.runPromise(program).then((n) => console.log(`repetitions: ${n}`)) + * void output.push(Effect.runSync(program)) + * output // => ["success", "success", "success", 2] * ``` * * **Example** (Stopping repetition on failure) * - * ```ts + * ```ts import.meta.vitest * // Failure Example * import { Effect, Schedule } from "effect" + * const output: Array = [] * * let count = 0 * * // Define a callback effect that simulates an action with possible failures * const action = Effect.callback((resume) => { * if (count > 1) { - * console.log("failure") + * void output.push("failure") * resume(Effect.fail("Uh oh!")) * } else { * count++ - * console.log("success") + * void output.push("success") * resume(Effect.succeed("yay!")) * } * }) * - * const policy = Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("100 millis")) + * const policy = Schedule.recurs(2) * const program = Effect.repeat(action, policy) * - * // Effect.runPromiseExit(program).then(console.log) + * void output.push((await Effect.runPromiseExit(program))._tag) + * output // => ["success", "success", "failure", "Failure"] * ``` * * @see {@link retry} for failure-based repetition @@ -7596,30 +7649,34 @@ export const repeat: { * * **Example** (Recovering after repetition stops) * - * ```ts - * import { Console, Effect, Option, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Option, Schedule } from "effect" + * const output: Array = [] * * let attempt = 0 * const task = Effect.gen(function*() { * attempt++ * if (attempt <= 2) { - * yield* Console.log(`Attempt ${attempt} failed`) + * yield* Effect.sync(() => { output.push(`Attempt ${attempt} failed`) }) * return yield* Effect.fail(`Error ${attempt}`) * } - * yield* Console.log(`Attempt ${attempt} succeeded`) + * yield* Effect.sync(() => { output.push(`Attempt ${attempt} succeeded`) }) * return "success" * }) * * const program = Effect.repeatOrElse( * task, * Schedule.recurs(3), - * (error, attempts) => - * Console.log( + * (error, previous) => + * Effect.sync(() => { output.push( * `Final failure: ${error}, after ${ - * Option.getOrElse(attempts, () => 0) + * Option.isSome(previous) ? previous.value.attempt : 0 * } attempts` - * ).pipe(Effect.map(() => 0)) + * ) }).pipe(Effect.map(() => 0)) * ) + * + * void output.push(Effect.runSync(program)) + * output // => ["Attempt 1 failed", "Final failure: Error 1, after 0 attempts", 0] * ``` * * @category repetition @@ -7628,12 +7685,12 @@ export const repeat: { export const repeatOrElse: { ( schedule: Schedule, - orElse: (error: E | E2, option: Option) => Effect + orElse: (error: E | E2, option: Option>) => Effect ): (self: Effect) => Effect ( self: Effect, schedule: Schedule, - orElse: (error: E | E2, option: Option) => Effect + orElse: (error: E | E2, option: Option>) => Effect ): Effect } = internalSchedule.repeatOrElse @@ -7652,7 +7709,7 @@ export const repeatOrElse: { * @see {@link all} for running the returned effects and collecting results * @see {@link replicateEffect} for repeating an effect and collecting results in one step with concurrency and discard options * - * @category collecting + * @category repetition * @since 2.0.0 */ export const replicate: { @@ -7674,16 +7731,20 @@ export const replicate: { * * **Example** (Replicating an effect) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const results = yield* Effect.replicateEffect(3)(Effect.succeed(1)) - * yield* Console.log(results) + * yield* Effect.sync(() => { output.push(results) }) * }) + * + * Effect.runSync(program) + * output // => [[1, 1, 1]] * ``` * - * @category collecting + * @category repetition * @since 2.0.0 */ export const replicateEffect: { @@ -7726,26 +7787,19 @@ export const replicateEffect: { * * **Example** (Scheduling repeated execution) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * const output: Array = [] * * const task = Effect.gen(function*() { - * yield* Console.log("Task executing...") - * return Math.random() + * yield* Effect.sync(() => { output.push("Task executing...") }) + * return 1 * }) * - * // Repeat 3 times with 1 second delay between executions - * const program = Effect.schedule( - * task, - * Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("1 second")) - * ) + * const program = Effect.schedule(task, Schedule.recurs(2)) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // Task executing... (immediate) - * // Task executing... (after 1 second) - * // Task executing... (after 1 second) - * // Returns the count from Schedule.recurs + * void output.push(Effect.runSync(program)) + * output // => ["Task executing...", "Task executing...", 2] * ``` * * @see {@link scheduleFrom} for a variant that allows the schedule's decision @@ -7757,15 +7811,15 @@ export const replicateEffect: { export const schedule: { ( schedule: Schedule - ): (self: Effect) => Effect + ): (self: Effect) => Effect ( self: Effect, schedule: Schedule - ): Effect + ): Effect } = dual(2, ( self: Effect, schedule: Schedule -): Effect => scheduleFrom(self, undefined, schedule)) +): Effect => scheduleFrom(self, undefined, schedule)) /** * Runs an effect repeatedly according to a schedule that is initialized with a @@ -7781,12 +7835,13 @@ export const schedule: { * * **Example** (Scheduling from an initial value) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * const output: Array = [] * * const task = (input: number) => * Effect.gen(function*() { - * yield* Console.log(`Processing: ${input}`) + * yield* Effect.sync(() => { output.push(`Processing: ${input}`) }) * return input + 1 * }) * @@ -7797,8 +7852,8 @@ export const schedule: { * Schedule.recurs(2) * ) * - * Effect.runPromise(program).then(console.log) - * // Returns the schedule count + * void output.push(Effect.runSync(program)) + * output // => ["Processing: 0", "Processing: 0", 2] * ``` * * @category repetition @@ -7808,12 +7863,12 @@ export const scheduleFrom: { ( initial: Input, schedule: Schedule - ): (self: Effect) => Effect + ): (self: Effect) => Effect ( self: Effect, initial: Input, schedule: Schedule - ): Effect + ): Effect } = internalSchedule.scheduleFrom // ----------------------------------------------------------------------------- @@ -7825,14 +7880,15 @@ export const scheduleFrom: { * * **Example** (Accessing the current tracer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const currentTracer = yield* Effect.tracer - * yield* Effect.log(`Using tracer: ${currentTracer}`) - * return "operation completed" + * return typeof currentTracer.span * }) + * + * Effect.runSync(program) // => "function" * ``` * * @category tracing @@ -7845,16 +7901,15 @@ export const tracer: Effect = internal.tracer * * **Example** (Providing a tracer) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { - * yield* Effect.log("Using tracer") - * return "completed" + * const tracer = yield* Effect.tracer + * return yield* Effect.withTracer(Effect.succeed("completed"), tracer) * }) * - * // withTracer provides a tracer to the effect context - * // const traced = Effect.withTracer(program, customTracer) + * Effect.runSync(program) // => "completed" * ``` * * @category tracing @@ -7875,14 +7930,15 @@ export const withTracer: { * * **Example** (Enabling or disabling tracing) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * Effect.succeed(42).pipe( + * const program = Effect.succeed(42).pipe( * Effect.withSpan("my-span"), * // the span will not be registered with the tracer * Effect.withTracerEnabled(false) * ) + * Effect.runSync(program) // => 42 * ``` * * @category tracing @@ -7898,14 +7954,15 @@ export const withTracerEnabled: { * * **Example** (Enabling or disabling tracing timing) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * Effect.succeed(42).pipe( + * const program = Effect.succeed(42).pipe( * Effect.withSpan("my-span"), * // the span will not have timing information * Effect.withTracerTiming(false) * ) + * Effect.runSync(program) // => 42 * ``` * * @category tracing @@ -7921,13 +7978,10 @@ export const withTracerTiming: { * * **Example** (Annotating all spans) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.log("Doing some work...") - * return "result" - * }) + * const program = Effect.succeed("result") * * // Add single annotation * const annotated1 = Effect.annotateSpans(program, "user", "john") @@ -7938,6 +7992,8 @@ export const withTracerTiming: { * version: "1.0.0", * environment: "production" * }) + * + * Effect.runSync(Effect.all([annotated1, annotated2])) // => ['result', 'result'] * ``` * * @category tracing @@ -7967,20 +8023,19 @@ export const annotateSpans: { * * **Example** (Annotating the current span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * yield* Effect.annotateCurrentSpan("userId", "123") * yield* Effect.annotateCurrentSpan({ - * operation: "user-lookup", - * timestamp: Date.now() + * operation: "user-lookup" * }) - * yield* Effect.log("User lookup completed") * return "success" * }) * * const traced = Effect.withSpan(program, "user-operation") + * Effect.runSync(traced) // => "success" * ``` * * @category tracing @@ -8001,16 +8056,16 @@ export const annotateCurrentSpan: { * * **Example** (Reading the current span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const span = yield* Effect.currentSpan - * yield* Effect.log(`Current span: ${span}`) - * return "done" + * return span.name * }) * * const traced = Effect.withSpan(program, "my-span") + * Effect.runSync(traced) // => "my-span" * ``` * * @category tracing @@ -8029,21 +8084,18 @@ export const currentSpan: Effect = internal.curr * * **Example** (Reading the parent span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const childOperation = Effect.gen(function*() { * const parentSpan = yield* Effect.currentParentSpan - * yield* Effect.log(`Parent span: ${parentSpan}`) - * return "child completed" + * return parentSpan._tag * }) * - * const program = Effect.gen(function*() { - * yield* Effect.withSpan(childOperation, "child-span") - * return "parent completed" - * }) + * const program = Effect.withSpan(childOperation, "child-span") * * const traced = Effect.withSpan(program, "parent-span") + * Effect.runSync(traced) // => "Span" * ``` * * @category tracing @@ -8061,23 +8113,15 @@ export const currentParentSpan: Effect = inte * * **Example** (Providing span annotations) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { - * // Add some annotations to the current span - * yield* Effect.annotateCurrentSpan("userId", "123") - * yield* Effect.annotateCurrentSpan("operation", "data-processing") - * - * // Retrieve all annotations * const annotations = yield* Effect.spanAnnotations - * - * console.log("Current span annotations:", annotations) * return annotations - * }) + * }).pipe(Effect.annotateSpans({ userId: "123", operation: "data-processing" })) * - * Effect.runPromise(program).then(console.log) - * // Output: Current span annotations: { userId: "123", operation: "data-processing" } + * Effect.runSync(program) // => { userId: '123', operation: 'data-processing' } * ``` * * @category tracing @@ -8095,15 +8139,16 @@ export const spanAnnotations: Effect>> = intern * * **Example** (Providing span links) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * // Get the current span links * const links = yield* Effect.spanLinks - * console.log(`Current span has ${links.length} links`) * return links * }) + * + * Effect.runSync(program).length // => 0 * ``` * * @category tracing @@ -8122,44 +8167,37 @@ export const spanLinks: Effect> = internal.spanLinks * * **Example** (Linking one span to another span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const parentEffect = Effect.withSpan("parent-operation")( - * Effect.succeed("parent result") - * ) - * - * const childEffect = Effect.withSpan("child-operation")( - * Effect.succeed("child result") - * ) - * - * // Link the child span to the parent span - * const program = Effect.gen(function*() { + * const program = Effect.withSpan(Effect.gen(function*() { * const parentSpan = yield* Effect.currentSpan - * const result = yield* childEffect.pipe( + * return yield* Effect.spanLinks.pipe( * Effect.linkSpans(parentSpan, { relationship: "follows" }) * ) - * return result - * }) + * }), "parent-operation") + * + * Effect.runSync(program).length // => 1 * ``` * * **Example** (Linking multiple spans at once) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * // Link multiple spans * const program = Effect.gen(function*() { - * const span1 = yield* Effect.currentSpan - * const span2 = yield* Effect.currentSpan + * const span1 = yield* Effect.makeSpan("span-1") + * const span2 = yield* Effect.makeSpan("span-2") * - * return yield* Effect.succeed("result").pipe( + * return yield* Effect.spanLinks.pipe( * Effect.linkSpans([span1, span2], { * type: "dependency", * source: "multiple-operations" * }) * ) * }) + * + * Effect.runSync(program).length // => 2 * ``` * * @category tracing @@ -8188,14 +8226,15 @@ export const linkSpans: { * * **Example** (Creating a span manually) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const span = yield* Effect.makeSpan("my-operation") - * yield* Effect.log("Operation in progress") - * return "completed" + * return span.name * }) + * + * Effect.runSync(program) // => "my-operation" * ``` * * @category tracing @@ -8214,17 +8253,18 @@ export const makeSpan: (name: string, options?: SpanOptionsNoTrace) => Effect "scoped-operation" * ``` * * @category tracing @@ -8246,17 +8286,14 @@ export const makeSpanScoped: ( * * **Example** (Running an effect with a standalone span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.useSpan( * "user-operation", - * (span) => - * Effect.gen(function*() { - * yield* Effect.log("Processing user data") - * return "success" - * }) + * (span) => Effect.succeed(`${span.name}: success`) * ) + * Effect.runSync(program) // => "user-operation: success" * ``` * * @category tracing @@ -8272,17 +8309,15 @@ export const useSpan: { * * **Example** (Wrapping an effect in a child span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task = Effect.gen(function*() { - * yield* Effect.log("Executing task") - * return "result" - * }) + * const task = Effect.succeed("result") * * const traced = Effect.withSpan(task, "my-task", { * attributes: { version: "1.0" } * }) + * Effect.runSync(traced) // => "result" * ``` * * @category tracing @@ -8313,16 +8348,17 @@ export const withSpan: { * * **Example** (Creating a scoped child span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.scoped( * Effect.gen(function*() { - * const task = Effect.log("Working...") + * const task = Effect.succeed("working") * yield* Effect.withSpanScoped(task, "scoped-task") * return "completed" * }) * ) + * Effect.runSync(program) // => "completed" * ``` * * @category tracing @@ -8347,15 +8383,16 @@ export const withSpanScoped: { * * **Example** (Setting a parent span) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const program = Effect.gen(function*() { * const span = yield* Effect.makeSpan("parent-span") - * const childTask = Effect.log("Child operation") + * const childTask = Effect.succeed("child operation") * yield* Effect.withParentSpan(childTask, span) * return "completed" * }) + * Effect.runSync(program) // => "completed" * ``` * * @category tracing @@ -8379,8 +8416,9 @@ export const withParentSpan: { * * **Example** (Executing a request through a resolver) * - * ```ts - * import { Console, Effect, Exit, Request, RequestResolver } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" + * const output: Array = [] * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" @@ -8398,13 +8436,16 @@ export const withParentSpan: { * * const program = Effect.gen(function*() { * const name = yield* Effect.request(GetUser({ id: 1 }), resolver) - * yield* Console.log(name) + * yield* Effect.sync(() => { output.push(name) }) * }) + * + * await Effect.runPromise(program) + * output // => ["user-1"] * ``` * * @see {@link requestUnsafe} for the low-level entry point when you already have a `Context` and need to enqueue outside an `Effect` * - * @category requests & batching + * @category running * @since 2.0.0 */ export const request: { @@ -8431,7 +8472,7 @@ export const request: { * * @see {@link request} for the `Effect`-returning API used for normal request execution * - * @category requests & batching + * @category unsafe * @since 4.0.0 */ export const requestUnsafe: ( @@ -8472,28 +8513,21 @@ export const requestUnsafe: ( * * **Example** (Forking a child fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * - * const longRunningTask = Effect.gen(function*() { - * yield* Effect.sleep("2 seconds") - * yield* Effect.log("Task completed") - * return "result" - * }) + * const task = Effect.succeed("result") * * const program = Effect.gen(function*() { - * const fiber = yield* longRunningTask.pipe(Effect.forkChild) - * - * // or fork a fiber that starts immediately: - * yield* longRunningTask.pipe(Effect.forkChild({ startImmediately: true })) - * - * yield* Effect.log("Task forked, continuing...") + * const fiber = yield* task.pipe(Effect.forkChild) * const result = yield* Fiber.join(fiber) * return result * }) + * + * await Effect.runPromise(program) // => "result" * ``` * - * @category supervision & fibers + * @category forking * @since 4.0.0 */ export const forkChild: < @@ -8519,26 +8553,24 @@ export const forkChild: < * * **Example** (Forking into a supplied scope) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const task = Effect.gen(function*() { - * yield* Effect.sleep("10 seconds") - * return "completed" - * }) + * const task = Effect.never * * const program = Effect.scoped( * Effect.gen(function*() { * const scope = yield* Effect.scope * const fiber = yield* Effect.forkIn(task, scope) - * yield* Effect.sleep("1 second") * // Fiber will be interrupted when scope closes * return "done" * }) * ) + * + * await Effect.runPromise(program) // => "done" * ``` * - * @category supervision & fibers + * @category forking * @since 2.0.0 */ export const forkIn: { @@ -8564,32 +8596,24 @@ export const forkIn: { * * **Example** (Forking into the current scope) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const backgroundTask = Effect.gen(function*() { - * yield* Effect.sleep("5 seconds") - * yield* Effect.log("Background task completed") - * return "result" - * }) + * const backgroundTask = Effect.never * * const program = Effect.scoped( * Effect.gen(function*() { - * const fiber = yield* backgroundTask.pipe(Effect.forkScoped) - * - * // or fork a fiber that starts immediately: - * yield* backgroundTask.pipe(Effect.forkScoped({ startImmediately: true })) - * - * yield* Effect.log("Task forked in scope") - * yield* Effect.sleep("1 second") + * yield* backgroundTask.pipe(Effect.forkScoped) * * // Fiber will be interrupted when scope closes * return "scope completed" * }) * ) + * + * await Effect.runPromise(program) // => "scope completed" * ``` * - * @category supervision & fibers + * @category forking * @since 2.0.0 */ export const forkScoped: < @@ -8616,30 +8640,20 @@ export const forkScoped: < * * **Example** (Forking a detached fiber) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * - * const daemonTask = Effect.gen(function*() { - * while (true) { - * yield* Effect.sleep("1 second") - * yield* Effect.log("Daemon running...") - * } - * }) + * const daemonTask = Effect.succeed("daemon result") * * const program = Effect.gen(function*() { * const fiber = yield* daemonTask.pipe(Effect.forkDetach) - * - * // or fork a fiber that starts immediately: - * yield* daemonTask.pipe(Effect.forkDetach({ startImmediately: true })) - * - * yield* Effect.log("Daemon started") - * yield* Effect.sleep("3 seconds") - * // Daemon continues running after this effect completes - * return "main completed" + * return yield* Fiber.join(fiber) * }) + * + * await Effect.runPromise(program) // => "daemon result" * ``` * - * @category supervision & fibers + * @category forking * @since 4.0.0 */ export const forkDetach: < @@ -8678,7 +8692,7 @@ export const forkDetach: < * @see {@link forkIn} for forking into an explicit scope * @see {@link forkScoped} for forking fibers tied to the current scope * - * @category supervision & fibers + * @category sequencing * @since 2.0.0 */ export const awaitAllChildren: (self: Effect) => Effect = internal.awaitAllChildren @@ -8688,16 +8702,20 @@ export const awaitAllChildren: (self: Effect) => Effect = [] * * const program = Effect.gen(function*() { * const fiber = yield* Effect.fiber - * yield* Console.log(`Fiber id: ${fiber.id}`) + * yield* Effect.sync(() => { output.push(typeof fiber.id) }) * }) + * + * Effect.runSync(program) + * output // => ["number"] * ``` * - * @category supervision & fibers + * @category accessors * @since 4.0.0 */ export const fiber: Effect> = internal.fiber @@ -8707,21 +8725,14 @@ export const fiber: Effect> = internal.fiber * * **Example** (Accessing the current fiber id) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * const program = Effect.log("event").pipe( - * // Read the current span with the fiber id for tagging. - * Effect.andThen(Effect.all([Effect.currentSpan, Effect.fiberId])), - * Effect.withSpan("A"), - * Effect.map(([span, fiberId]) => ({ - * spanName: span.name, - * fiberId - * })) - * ) + * const program = Effect.fiberId.pipe(Effect.map((id) => typeof id)) + * Effect.runSync(program) // => "number" * ``` * - * @category supervision & fibers + * @category accessors * @since 2.0.0 */ export const fiberId: Effect = internal.fiberId @@ -8770,23 +8781,20 @@ export interface RunOptions { * * **Example** (Running an effect in the background) * - * ```ts - * import { Console, Effect, Fiber, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" + * const output: Array = [] * * // ┌─── Effect * // ▼ - * const program = Effect.repeat( - * Console.log("running..."), - * Schedule.spaced("200 millis") - * ) + * const program = Effect.sync(() => { output.push("running...") }).pipe(Effect.as("done")) * * // ┌─── RuntimeFiber * // ▼ * const fiber = Effect.runFork(program) * - * setTimeout(() => { - * Effect.runFork(Fiber.interrupt(fiber)) - * }, 500) + * void output.push(await Effect.runPromise(Fiber.join(fiber))) + * output // => ["running...", "done"] * ``` * * @category running @@ -8805,8 +8813,9 @@ export const runFork: (effect: Effect, options?: RunOptions | * * **Example** (Running with services in the background) * - * ```ts - * import { Context, Effect } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber } from "effect" + * const output: Array = [] * * interface Logger { * log: (message: string) => void @@ -8815,7 +8824,7 @@ export const runFork: (effect: Effect, options?: RunOptions | * const Logger = Context.Service("Logger") * * const services = Context.make(Logger, { - * log: (message) => console.log(message) + * log: (message) => void output.push(message) * }) * * const program = Effect.gen(function*() { @@ -8825,6 +8834,8 @@ export const runFork: (effect: Effect, options?: RunOptions | * }) * * const fiber = Effect.runForkWith(services)(program) + * void output.push(await Effect.runPromise(Fiber.join(fiber))) + * output // => ["Hello from service!", "done"] * ``` * * @category running @@ -8848,8 +8859,9 @@ export const runForkWith: ( * * **Example** (Running with services and a callback) * - * ```ts - * import { Console, Context, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect } from "effect" + * const output: Array = [] * * interface Logger { * log: (message: string) => Effect.Effect @@ -8858,7 +8870,7 @@ export const runForkWith: ( * const Logger = Context.Service("Logger") * * const services = Context.make(Logger, { - * log: (message) => Console.log(message) + * log: (message) => Effect.sync(() => { output.push(message) }) * }) * * const program = Effect.gen(function*() { @@ -8867,16 +8879,15 @@ export const runForkWith: ( * return "done" * }) * - * const interrupt = Effect.runCallbackWith(services)(program, { - * onExit: (exit) => { - * if (Exit.isFailure(exit)) { - * // handle failure or interruption + * await new Promise((resolve) => { + * Effect.runCallbackWith(services)(program, { + * onExit: (exit) => { + * void output.push(exit._tag) + * resolve() * } - * } + * }) * }) - * - * // Use the interruptor if you need to cancel the fiber later. - * interrupt() + * output // => ["Started", "Success"] * ``` * * @category running @@ -8900,30 +8911,30 @@ export const runCallbackWith: ( * * **Example** (Running with a callback) * - * ```ts - * import { Console, Effect, Exit } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * yield* Console.log("working") + * yield* Effect.sync(() => { output.push("working") }) * return "done" * }) * - * const interrupt = Effect.runCallback(program, { - * onExit: (exit) => { - * Effect.runSync( - * Exit.match(exit, { - * onFailure: () => Console.log("failed"), - * onSuccess: (value) => Console.log(`success: ${value}`) - * }) - * ) - * } + * await new Promise((resolve) => { + * Effect.runCallback(program, { + * onExit: (exit) => { + * Effect.runSync( + * Exit.match(exit, { + * onFailure: () => Effect.sync(() => { output.push("failed") }), + * onSuccess: (value) => Effect.sync(() => { output.push(`success: ${value}`) }) + * }) + * ) + * resolve() + * } + * }) * }) * - * // Output: - * // working - * // success: done - * - * // interrupt() to cancel the fiber if needed + * output // => ["working", "success: done"] * ``` * * @category running @@ -8948,22 +8959,23 @@ export const runCallback: ( * * **Example** (Running a successful effect as a Promise) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * - * Effect.runPromise(Effect.succeed(1)).then(console.log) - * // Output: 1 + * await Effect.runPromise(Effect.succeed(1)) // => 1 * ``` * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * //Example: Handling a Failing Effect as a Rejected Promise * import { Effect } from "effect" + * const output: Array = [] * - * Effect.runPromise(Effect.fail("my error")).catch(console.error) - * // Output: - * // (FiberFailure) Error: my error + * await Effect.runPromise(Effect.fail("my error")).catch(() => { + * void output.push("rejected") + * }) + * output // => ["rejected"] * ``` * * @see {@link runPromiseExit} for a version that returns an `Exit` type instead of rejecting. @@ -8985,7 +8997,7 @@ export const runPromise: ( * * **Example** (Running with services as a promise) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * interface Config { @@ -9003,7 +9015,7 @@ export const runPromise: ( * return `Connecting to ${config.apiUrl}` * }) * - * Effect.runPromiseWith(context)(program).then(console.log) + * await Effect.runPromiseWith(context)(program) // => "Connecting to https://api.example.com" * ``` * * @category running @@ -9030,30 +9042,14 @@ export const runPromiseWith: ( * * **Example** (Observing promise results as Exit) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" * * // Execute a successful effect and get the Exit result as a Promise - * Effect.runPromiseExit(Effect.succeed(1)).then(console.log) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Success", - * // value: 1 - * // } + * await Effect.runPromiseExit(Effect.succeed(1)) // => Exit.succeed(1) * * // Execute a failing effect and get the Exit result as a Promise - * Effect.runPromiseExit(Effect.fail("my error")).then(console.log) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Failure", - * // cause: { - * // _id: "Cause", - * // _tag: "Fail", - * // failure: "my error" - * // } - * // } + * await Effect.runPromiseExit(Effect.fail("my error")) // => Exit.fail("my error") * ``` * * @see {@link runPromise} for a version that rejects on failure. @@ -9076,8 +9072,9 @@ export const runPromiseExit: ( * * **Example** (Running with services as an Exit promise) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Exit } from "effect" + * const output: Array = [] * * interface Database { * query: (sql: string) => string @@ -9094,11 +9091,11 @@ export const runPromiseExit: ( * return db.query("SELECT * FROM users") * }) * - * Effect.runPromiseExitWith(services)(program).then((exit) => { - * if (Exit.isSuccess(exit)) { - * console.log("Success:", exit.value) - * } - * }) + * const exit = await Effect.runPromiseExitWith(services)(program) + * if (Exit.isSuccess(exit)) { + * void output.push(`Success: ${exit.value}`) + * } + * output // => ["Success: Result for: SELECT * FROM users"] * ``` * * @category running @@ -9125,43 +9122,39 @@ export const runPromiseExitWith: ( * * **Example** (Running a synchronous effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * const program = Effect.sync(() => { - * console.log("Hello, World!") + * void output.push("Hello, World!") * return 1 * }) * * const result = Effect.runSync(program) - * // Output: Hello, World! - * - * console.log(result) - * // Output: 1 + * void output.push(result) + * output // => ["Hello, World!", 1] * ``` * * **Example** (Throwing for failed or async effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * try { * // Attempt to run an effect that fails * Effect.runSync(Effect.fail("my error")) * } catch (e) { - * console.error(e) + * void output.push("failed effect") * } - * // Output: - * // (FiberFailure) Error: my error - * * try { * // Attempt to run an effect that involves async work * Effect.runSync(Effect.promise(() => Promise.resolve(1))) * } catch (e) { - * console.error(e) + * void output.push("async effect") * } - * // Output: - * // (FiberFailure) AsyncFiberException: Fiber #0 cannot be resolved synchronously. This is caused by using runSync on an effect that performs async work + * output // => ["failed effect", "async effect"] * ``` * * @see {@link runSyncExit} for a version that returns an `Exit` type instead of @@ -9181,7 +9174,7 @@ export const runSync: (effect: Effect) => A = internal.runSync * * **Example** (Running synchronously with services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect } from "effect" * * interface MathService { @@ -9200,7 +9193,7 @@ export const runSync: (effect: Effect) => A = internal.runSync * }) * * const result = Effect.runSyncWith(context)(program) - * console.log(result) // 5 + * result // => 5 * ``` * * @category running @@ -9231,50 +9224,25 @@ export const runSyncWith: ( * * **Example** (Observing synchronous results as Exit) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit } from "effect" + * + * Effect.runSyncExit(Effect.succeed(1)) // => Exit.succeed(1) * - * console.log(Effect.runSyncExit(Effect.succeed(1))) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Success", - * // value: 1 - * // } - * - * console.log(Effect.runSyncExit(Effect.fail("my error"))) - * // Output: - * // { - * // _id: "Exit", - * // _tag: "Failure", - * // cause: { - * // _id: "Cause", - * // _tag: "Fail", - * // failure: "my error" - * // } - * // } + * Effect.runSyncExit(Effect.fail("my error")) // => Exit.fail("my error") * ``` * * **Example** (Capturing async work as a Die cause) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit } from "effect" + * + * const exit = Effect.runSyncExit(Effect.promise(() => Promise.resolve(1))) + * const isAsyncDie = Exit.hasDies(exit) && exit.cause.reasons.some( + * (reason) => Cause.isDieReason(reason) && Cause.isAsyncFiberError(reason.defect) + * ) * - * console.log(Effect.runSyncExit(Effect.promise(() => Promise.resolve(1)))) - * // Output: - * // { - * // _id: 'Exit', - * // _tag: 'Failure', - * // cause: { - * // _id: 'Cause', - * // _tag: 'Die', - * // defect: [Fiber #0 cannot be resolved synchronously. This is caused by using runSync on an effect that performs async work] { - * // fiber: [FiberRuntime], - * // _tag: 'AsyncFiberException', - * // name: 'AsyncFiberException' - * // } - * // } - * // } + * isAsyncDie // => true * ``` * * @see {@link runSync} for a version that throws on failure. @@ -9294,8 +9262,9 @@ export const runSyncExit: (effect: Effect) => Exit.Exit = inte * * **Example** (Running synchronously with services as Exit) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Exit } from "effect" + * const output: Array = [] * * // Define a logger service * const Logger = Context.Service<{ @@ -9310,19 +9279,17 @@ export const runSyncExit: (effect: Effect) => Exit.Exit = inte * * // Prepare context * const context = Context.make(Logger, { - * log: (msg) => console.log(`[LOG] ${msg}`) + * log: (msg) => void output.push(`[LOG] ${msg}`) * }) * * const exit = Effect.runSyncExitWith(context)(program) * * if (Exit.isSuccess(exit)) { - * console.log(`Success: ${exit.value}`) + * void output.push(`Success: ${exit.value}`) * } else { - * console.log(`Failure: ${exit.cause}`) + * void output.push(`Failure: ${exit.cause}`) * } - * // Output: - * // [LOG] Computing result... - * // Success: 42 + * output // => ["[LOG] Computing result...", "Success: 42"] * ``` * * @category running @@ -9356,7 +9323,7 @@ export declare namespace fn { * * **Example** (Annotating an Effect function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -9368,11 +9335,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Annotating a parametric Effect function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -9384,6 +9352,7 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello" * ``` * * @category utility types @@ -13446,7 +13415,7 @@ export declare namespace fn { * * **Example** (Defining untraced effect functions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -13458,11 +13427,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Transforming the returned Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced( @@ -13476,11 +13446,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello: 5" * ``` * * **Example** (Annotating an untraced non-parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -13492,11 +13463,12 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Annotating an untraced parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fnUntraced(function*( @@ -13508,9 +13480,10 @@ export declare namespace fn { * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello" * ``` * - * @category functions + * @category constructors * @since 3.12.0 */ export const fnUntraced: fn.Untraced = internal.fnUntraced @@ -13543,7 +13516,7 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * * **Example** (Defining traced effect functions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("calculateLength")(function*(value: string) { @@ -13553,11 +13526,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Transforming the returned Effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("formatLength")( @@ -13571,11 +13545,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello: 5" * ``` * * **Example** (Binding this) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * class Counter { @@ -13595,11 +13570,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = counter.increment(1) + * Effect.runSync(program) // => 1 * ``` * * **Example** (Annotating a traced non-parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("calculateLength")(function*( @@ -13611,11 +13587,12 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => 5 * ``` * * **Example** (Annotating a traced parametric function) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const f = Effect.fn("succeed")(function*( @@ -13627,9 +13604,10 @@ export const fnUntraced: fn.Untraced = internal.fnUntraced * // ┌─── Effect.Effect * // ▼ * const program = f("hello") + * Effect.runSync(program) // => "hello" * ``` * - * @category functions + * @category constructors * @since 3.11.0 */ export const fn: fn.Traced & { @@ -13646,22 +13624,19 @@ export const fn: fn.Traced & { * * **Example** (Accessing the Clock service) * - * ```ts - * import { Console, Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect } from "effect" * * const program = Effect.clockWith((clock) => * clock.currentTimeMillis.pipe( - * Effect.map((currentTime) => `Current time is: ${currentTime}`), - * Effect.tap(Console.log) + * Effect.map(() => "Clock is available") * ) * ) * - * Effect.runFork(program) - * // Example Output: - * // Current time is: 1735484929744 + * Effect.runSync(program) // => "Clock is available" * ``` * - * @category clock + * @category accessors * @since 2.0.0 */ export const clockWith: ( @@ -13682,14 +13657,24 @@ export const clockWith: ( * * **Example** (Logging at a dynamic level) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] * * const logWarn = Effect.logWithLevel("Warn") * * const program = Effect.gen(function*() { - * yield* logWarn("Cache miss", { key: "user:1" }) + * yield* logWarn("Cache miss") + * }) + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) * }) + * const runnable = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Debug"), + * Effect.provide(Logger.layer([logger])) + * ) + * Effect.runSync(runnable) + * output // => ["Warn: Cache miss"] * ``` * * @category logging @@ -13703,23 +13688,22 @@ export const logWithLevel: (level?: Severity) => (...message: ReadonlyArray * * **Example** (Logging at the default level) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * yield* Effect.log("Starting computation") * const result = 2 + 2 * yield* Effect.log("Result:", result) - * yield* Effect.log("Multiple", "values", "can", "be", "logged") * return result * }) * - * Effect.runPromise(program).then(console.log) - * // Output: - * // timestamp=2023-... level=INFO message="Starting computation" - * // timestamp=2023-... level=INFO message="Result: 4" - * // timestamp=2023-... level=INFO message="Multiple values can be logged" - * // 4 + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = Effect.provide(program, Logger.layer([logger])) + * void output.push(Effect.runSync(runnable)) + * output // => ["Info: Result: 4", 4] * ``` * * @category logging @@ -13732,24 +13716,20 @@ export const log: (...message: ReadonlyArray) => Effect = internal.lo * * **Example** (Logging fatal messages) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { - * try { - * // Simulate a critical system failure - * throw new Error("System memory exhausted") - * } catch (error) { - * const errorMessage = error instanceof Error ? error.message : String(error) - * yield* Effect.logFatal("Critical system failure:", errorMessage) - * yield* Effect.logFatal("System shutting down") - * } + * yield* Effect.logFatal("Critical system failure") * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=FATAL message="Critical system failure: System memory exhausted" - * // timestamp=2023-... level=FATAL message="System shutting down" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = Effect.provide(program, Logger.layer([logger])) + * Effect.runSync(runnable) + * output // => ["Fatal: Critical system failure"] * ``` * * @category logging @@ -13762,25 +13742,19 @@ export const logFatal: (...message: ReadonlyArray) => Effect = intern * * **Example** (Logging warnings) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logWarning("API rate limit approaching") - * yield* Effect.logWarning("Retries remaining:", 2, "Operation:", "fetchData") - * - * // Useful for non-critical issues - * const deprecated = true - * if (deprecated) { - * yield* Effect.logWarning("Using deprecated API endpoint") - * } * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=WARN message="API rate limit approaching" - * // timestamp=2023-... level=WARN message="Retries remaining: 2 Operation: fetchData" - * // timestamp=2023-... level=WARN message="Using deprecated API endpoint" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["Warn: API rate limit approaching"] * ``` * * @category logging @@ -13793,28 +13767,19 @@ export const logWarning: (...message: ReadonlyArray) => Effect = inte * * **Example** (Logging errors) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logError("Database connection failed") - * yield* Effect.logError( - * "Error code:", - * 500, - * "Message:", - * "Internal server error" - * ) - * - * // Can be used with error objects - * const error = new Error("Something went wrong") - * yield* Effect.logError("Caught error:", error.message) * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=ERROR message="Database connection failed" - * // timestamp=2023-... level=ERROR message="Error code: 500 Message: Internal server error" - * // timestamp=2023-... level=ERROR message="Caught error: Something went wrong" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["Error: Database connection failed"] * ``` * * @category logging @@ -13827,23 +13792,19 @@ export const logError: (...message: ReadonlyArray) => Effect = intern * * **Example** (Logging information) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logInfo("Application starting up") - * yield* Effect.logInfo("Config loaded:", "production", "Port:", 3000) - * - * // Useful for general information - * const version = "1.2.3" - * yield* Effect.logInfo("Application version:", version) * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=INFO message="Application starting up" - * // timestamp=2023-... level=INFO message="Config loaded: production Port: 3000" - * // timestamp=2023-... level=INFO message="Application version: 1.2.3" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["Info: Application starting up"] * ``` * * @category logging @@ -13856,24 +13817,23 @@ export const logInfo: (...message: ReadonlyArray) => Effect = interna * * **Example** (Logging debug messages) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logDebug("Debug mode enabled") - * - * const userInput = { name: "Alice", age: 30 } - * yield* Effect.logDebug("Processing user input:", userInput) - * - * // Useful for detailed diagnostic information - * yield* Effect.logDebug("Variable state:", "x=10", "y=20", "z=30") * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=DEBUG message="Debug mode enabled" - * // timestamp=2023-... level=DEBUG message="Processing user input: [object Object]" - * // timestamp=2023-... level=DEBUG message="Variable state: x=10 y=20 z=30" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Debug"), + * Effect.provide(Logger.layer([logger])) + * ) + * Effect.runSync(runnable) + * output // => ["Debug: Debug mode enabled"] * ``` * * @category logging @@ -13886,27 +13846,23 @@ export const logDebug: (...message: ReadonlyArray) => Effect = intern * * **Example** (Logging trace messages) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.logTrace("Entering function processData") - * - * // Trace detailed execution flow - * for (let i = 0; i < 3; i++) { - * yield* Effect.logTrace("Loop iteration:", i, "Processing item") - * } - * - * yield* Effect.logTrace("Exiting function processData") * }) * - * Effect.runPromise(program) - * // Output: - * // timestamp=2023-... level=TRACE message="Entering function processData" - * // timestamp=2023-... level=TRACE message="Loop iteration: 0 Processing item" - * // timestamp=2023-... level=TRACE message="Loop iteration: 1 Processing item" - * // timestamp=2023-... level=TRACE message="Loop iteration: 2 Processing item" - * // timestamp=2023-... level=TRACE message="Exiting function processData" + * const logger = Logger.make(({ logLevel, message }) => { + * void output.push(`${logLevel}: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) + * }) + * const runnable = program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Trace"), + * Effect.provide(Logger.layer([logger])) + * ) + * Effect.runSync(runnable) + * output // => ["Trace: Entering function processData"] * ``` * * @category logging @@ -13919,12 +13875,13 @@ export const logTrace: (...message: ReadonlyArray) => Effect = intern * * **Example** (Adding a logger to an effect) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * const output: Array = [] * * // Create a custom logger that logs to the console - * const customLogger = Logger.make(({ message }) => - * Effect.sync(() => console.log(`[CUSTOM]: ${message}`)) + * const customLogger = Logger.make(({ message }) => + * void output.push(`[CUSTOM]: ${Array.isArray(message) ? message.map(String).join(" ") : String(message)}`) * ) * * const program = Effect.gen(function*() { @@ -13935,8 +13892,8 @@ export const logTrace: (...message: ReadonlyArray) => Effect = intern * // Add the custom logger to the effect * const programWithLogger = Effect.withLogger(program, customLogger) * - * Effect.runPromise(programWithLogger) - * // Output includes both default and custom log outputs + * Effect.runSync(Effect.provide(programWithLogger, Logger.layer([]))) + * output // => ["[CUSTOM]: This will go to both default and custom logger"] * ``` * * @category logging @@ -13962,13 +13919,12 @@ export const withLogger = dual< * * **Example** (Adding log annotations) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * yield* Effect.log("Starting operation") - * yield* Effect.log("Processing data") - * yield* Effect.log("Operation completed") * }) * * // Add annotations to all log messages @@ -13980,8 +13936,14 @@ export const withLogger = dual< * // Also supports single key-value annotations * const singleAnnotated = Effect.annotateLogs(program, "requestId", "req-456") * - * Effect.runPromise(annotatedProgram) - * // All log messages will include the userId and operation annotations + * const logger = Logger.make(({ message }) => + * void output.push(Array.isArray(message) ? message.join(" ") : String(message)) + * ) + * const run = (effect: Effect.Effect) => + * Effect.runSync(Effect.provide(effect, Logger.layer([logger]))) + * run(annotatedProgram) + * run(singleAnnotated) + * output // => ["Starting operation", "Starting operation"] * ``` * * @category logging @@ -14015,11 +13977,11 @@ export const annotateLogs = dual< ...args: [Record] | [key: string, value: unknown] ): Effect => internal.updateService(effect, CurrentLogAnnotations, (annotations) => { - const newAnnotations = { ...annotations } + const newAnnotations = args.length === 1 ? { ...annotations, ...args[0] } : { ...annotations } if (args.length === 1) { - Object.assign(newAnnotations, args[0]) + return newAnnotations } else { - newAnnotations[args[0]] = args[1] + InternalRecord.assignProperty(newAnnotations, args[0], args[1]) } return newAnnotations }) @@ -14040,8 +14002,9 @@ export const annotateLogs = dual< * * **Example** (Adding scoped log annotations) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const program = Effect.scoped( * Effect.gen(function*() { @@ -14051,7 +14014,11 @@ export const annotateLogs = dual< * }) * ) * - * Effect.runPromise(program) + * const logger = Logger.make(({ message }) => + * void output.push(Array.isArray(message) ? message.join(" ") : String(message)) + * ) + * Effect.runSync(Effect.provide(program, Logger.layer([logger]))) + * output // => ["before", "inside scope"] * ``` * * @see {@link annotateLogs} for annotating one effect @@ -14069,8 +14036,9 @@ export const annotateLogsScoped: { * * **Example** (Adding a log span) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * const output: Array = [] * * const databaseOperation = Effect.gen(function*() { * yield* Effect.log("Connecting to database") @@ -14088,8 +14056,11 @@ export const annotateLogsScoped: { * * const program = Effect.withLogSpan(httpRequest, "http-handler") * - * Effect.runPromise(program) - * // All log messages will include span information showing the nested operation context + * const logger = Logger.make(({ message }) => + * void output.push(Array.isArray(message) ? message.join(" ") : String(message)) + * ) + * void output.push(Effect.runSync(Effect.provide(program, Logger.layer([logger])))) + * output // => ["Making HTTP request", "Connecting to database", "Executing query", "Processing results", "Sending response", "data"] * ``` * * @category logging @@ -14113,16 +14084,12 @@ export const withLogSpan = dual< // ----------------------------------------------------------------------------- /** - * Updates the `Metric` every time the `Effect` is executed. - * - * **Details** + * Updates a metric after each effect execution, optionally mapping its `Exit` to + * the metric's input. * - * Also accepts an optional function which can be used to map the `Exit` value - * of the `Effect` into a valid `Input` for the `Metric`. - * - * **Example** (Incrementing a metric for each execution) + * **Example** (Counting executions) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const counter = Metric.counter("effect_executions", { @@ -14133,19 +14100,15 @@ export const withLogSpan = dual< * Effect.track(counter) * ) * - * // This will increment the counter by 1 when executed - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(counter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(counter)).count // => 1 * ``` * - * **Example** (Mapping exits before updating a metric) + * **Example** (Mapping exits) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Metric } from "effect" * - * // Track different exit types with custom mapping * const exitTracker = Metric.frequency("exit_types", { * description: "Tracks success/failure/defect counts" * }) @@ -14159,16 +14122,18 @@ export const withLogSpan = dual< * const effect = Effect.succeed("result").pipe( * Effect.track(exitTracker, mapExitToString) * ) + * Effect.runSync(effect) + * Effect.runSync(Metric.value(exitTracker)).occurrences.get("success") // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const track: { ( metric: Metric.Metric, f: (exit: Exit.Exit) => Input - ): (self: Effect) => Effect + ): (self: Effect) => Effect ( metric: Metric.Metric, NoInfer>, State> ): (self: Effect) => Effect @@ -14205,7 +14170,7 @@ export const track: { * * **Example** (Counting successful results) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const successCounter = Metric.counter("successes").pipe( @@ -14216,15 +14181,13 @@ export const track: { * Effect.trackSuccesses(successCounter) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(successCounter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(successCounter)).count // => 1 * ``` * * **Example** (Mapping successes before tracking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * // Track successful request sizes @@ -14234,13 +14197,11 @@ export const track: { * Effect.trackSuccesses(requestSizeGauge, (value: string) => value.length) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(requestSizeGauge)).then(console.log) - * // Output: { value: 12 } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(requestSizeGauge)).value // => 12 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackSuccesses: { @@ -14284,7 +14245,7 @@ export const trackSuccesses: { * * **Example** (Counting expected failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const errorCounter = Metric.counter("errors").pipe( @@ -14295,15 +14256,13 @@ export const trackSuccesses: { * Effect.trackErrors(errorCounter) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(errorCounter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(errorCounter)).count // => 1 * ``` * * **Example** (Mapping errors before tracking) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect, Metric } from "effect" * * class ConnectionFailedError extends Data.TaggedError("ConnectionFailedError")<{}> {} @@ -14315,13 +14274,11 @@ export const trackSuccesses: { * Effect.trackErrors(errorTypeFrequency, (error: ConnectionFailedError) => error._tag) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(errorTypeFrequency)).then(console.log) - * // Output: { occurrences: Map(1) { "ConnectionFailedError" => 1 } } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(errorTypeFrequency)).occurrences.get("ConnectionFailedError") // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackErrors: { @@ -14365,7 +14322,7 @@ export const trackErrors: { * * **Example** (Counting defects) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const defectCounter = Metric.counter("defects").pipe( @@ -14376,15 +14333,13 @@ export const trackErrors: { * Effect.trackDefects(defectCounter) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(defectCounter)).then(console.log) - * // Output: { count: 1, incremental: false } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(defectCounter)).count // => 1 * ``` * * **Example** (Mapping defects before tracking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * // Track defect types using frequency metric @@ -14397,13 +14352,11 @@ export const trackErrors: { * }) * ) * - * Effect.runPromiseExit(program).then(() => - * Effect.runPromise(Metric.value(defectTypeFrequency)).then(console.log) - * // Output: { occurrences: Map(1) { "Error" => 1 } } - * ) + * Effect.runSyncExit(program) + * Effect.runSync(Metric.value(defectTypeFrequency)).occurrences.get("Error") // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackDefects: { @@ -14444,40 +14397,36 @@ export const trackDefects: { * * **Example** (Recording execution duration) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const executionTimer = Metric.timer("execution_time") * - * const program = Effect.sleep("100 millis").pipe( + * const program = Effect.succeed("done").pipe( * Effect.trackDuration(executionTimer) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(executionTimer)).then(console.log) - * // Output: { count: 1, min: 100000000, max: 100000000, sum: 100000000 } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(executionTimer)).count // => 1 * ``` * * **Example** (Mapping duration before tracking) * - * ```ts - * import { Duration, Effect, Metric } from "effect" + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * // Track execution time in milliseconds using custom mapping * const durationGauge = Metric.gauge("execution_millis") * - * const program = Effect.sleep("200 millis").pipe( - * Effect.trackDuration(durationGauge, (duration) => Duration.toMillis(duration)) + * const program = Effect.succeed("done").pipe( + * Effect.trackDuration(durationGauge, () => 1) * ) * - * Effect.runPromise(program).then(() => - * Effect.runPromise(Metric.value(durationGauge)).then(console.log) - * // Output: { value: 200 } - * ) + * Effect.runSync(program) + * Effect.runSync(Metric.value(durationGauge)).value // => 1 * ``` * - * @category tracking + * @category metrics * @since 4.0.0 */ export const trackDuration: { @@ -14505,9 +14454,9 @@ export const trackDuration: { f: ((duration: Duration.Duration) => Input) | undefined ): Effect => clockWith((clock) => { - const startTime = clock.currentTimeNanosUnsafe() + const startTime = clock.monotonicTimeNanosUnsafe() return onExit(self, () => { - const endTime = clock.currentTimeNanosUnsafe() + const endTime = clock.monotonicTimeNanosUnsafe() const duration = Duration.subtract( Duration.fromInputUnsafe(endTime), Duration.fromInputUnsafe(startTime) @@ -14532,7 +14481,7 @@ export const trackDuration: { * * **Example** (Building transactions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Transaction class for software transactional memory operations @@ -14541,9 +14490,15 @@ export const trackDuration: { * // Use transaction for coordinated state changes * return "Transaction complete" * }) + * + * const runnable = Effect.provideService(txEffect, Effect.Transaction, { + * retry: false, + * journal: new Map() + * }) + * Effect.runSync(runnable) // => "Transaction complete" * ``` * - * @category transactions + * @category services * @since 4.0.0 */ export class Transaction extends Context.Service< @@ -14579,8 +14534,9 @@ export class Transaction extends Context.Service< * * **Example** (Running a transaction) * - * ```ts + * ```ts import.meta.vitest * import { Effect, TxRef } from "effect" + * const output: Array = [] * * const program = Effect.gen(function*() { * const ref1 = yield* TxRef.make(0) @@ -14591,12 +14547,15 @@ export class Transaction extends Context.Service< * yield* TxRef.set(ref1, 10) * yield* Effect.tx(TxRef.set(ref2, 20)) * const sum = (yield* TxRef.get(ref1)) + (yield* TxRef.get(ref2)) - * console.log(`Transaction sum: ${sum}`) + * void output.push(`Transaction sum: ${sum}`) * })) * - * console.log(`Final ref1: ${yield* TxRef.get(ref1)}`) // 10 - * console.log(`Final ref2: ${yield* TxRef.get(ref2)}`) // 20 + * void output.push(`Final ref1: ${yield* TxRef.get(ref1)}`) + * void output.push(`Final ref2: ${yield* TxRef.get(ref2)}`) * }) + * + * Effect.runSync(program) + * output // => ["Transaction sum: 30", "Final ref1: 10", "Final ref2: 20"] * ``` * * @category transactions @@ -14606,11 +14565,12 @@ export const tx = ( effect: Effect ): Effect> => withFiber((fiber) => { - if (fiber.context.mapUnsafe.has(Transaction.key)) { + let state = Context.getOrUndefined(fiber.context, Transaction) + if (state) { return effect as Effect> } // Create transaction state only at the outermost boundary - const state: Transaction["Service"] = { journal: new Map(), retry: false } + state = { journal: new Map(), retry: false } let result: Exit.Exit | undefined return uninterruptibleMask((restore) => flatMap( @@ -14700,31 +14660,28 @@ function clearTransaction(state: Transaction["Service"]) { * * **Example** (Retrying transactions) * - * ```ts - * import { Effect, TxRef } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, TxRef } from "effect" * * const program = Effect.gen(function*() { - * // create a transactional reference * const ref = yield* TxRef.make(0) + * const update = yield* Deferred.make() * - * // forks a fiber that increases the value of `ref` every 100 millis - * yield* Effect.forkChild(Effect.forever( - * // update to transactional value - * Effect.tx(TxRef.update(ref, (n) => n + 1)).pipe(Effect.delay("100 millis")) - * )) + * yield* Effect.forkChild( + * Deferred.await(update).pipe(Effect.andThen(Effect.tx(TxRef.set(ref, 1)))) + * ) * - * // the following will retry 10 times until the `ref` value is 10 - * yield* Effect.tx(Effect.gen(function*() { + * return yield* Effect.tx(Effect.gen(function*() { * const value = yield* TxRef.get(ref) - * if (value < 10) { - * yield* Effect.log(`retry due to value: ${value}`) + * if (value === 0) { + * yield* Deferred.succeed(update, undefined) * return yield* Effect.txRetry * } - * yield* Effect.log(`transaction done with value: ${value}`) + * return value * })) * }) * - * Effect.runPromise(program).catch(console.error) + * await Effect.runPromise(program) // => 1 * ``` * * @category transactions @@ -14754,7 +14711,7 @@ export declare namespace Effectify { /** * Converts a callback-based function type into an `Effect`-returning function type. * - * @category effectify + * @category utility types * @since 4.0.0 */ export type Effectify = T extends { @@ -14902,7 +14859,7 @@ export declare namespace Effectify { /** * Extracts the callback error type from a callback-based function type. * - * @category effectify + * @category utility types * @since 4.0.0 */ export type EffectifyError = T extends { @@ -14983,6 +14940,9 @@ export declare namespace Effectify { : never } +type EffectifyArgs) => any> = Parameters extends [...infer Args, any] ? Args + : Parameters + /** * Converts an error-first callback API into a function that returns an * `Effect`. @@ -14997,50 +14957,54 @@ export declare namespace Effectify { * * **Example** (Converting callbacks to effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" - * import * as fs from "fs" * - * // Convert Node.js readFile to an Effect - * const readFile = Effect.effectify(fs.readFile) + * const uppercase = ( + * input: string, + * callback: (error: Error | null, value?: string) => void + * ) => queueMicrotask(() => callback(null, input.toUpperCase())) * - * // Use the effectified function - * const program = readFile("package.json", "utf8") + * const effectfulUppercase = Effect.effectify(uppercase) + * const program = effectfulUppercase("hello") * - * Effect.runPromise(program).then(console.log) - * // Output: contents of package.json + * await Effect.runPromise(program) // => "HELLO" * ``` * * **Example** (Mapping callback errors to typed failures) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" - * import * as fs from "fs" * - * const readFile = Effect.effectify( - * fs.readFile, - * (error, args) => new Error(`Failed to read file ${args[0]}: ${error.message}`) + * const fail = ( + * input: string, + * callback: (error: Error | null, value?: string) => void + * ) => queueMicrotask(() => callback(new Error("unavailable"))) + * + * const effectfulFail = Effect.effectify( + * fail, + * (error, args) => new Error(`Failed to process ${args[0]}: ${error.message}`) * ) * - * const program = readFile("nonexistent.txt", "utf8") + * const program = Effect.flip(effectfulFail("hello")) * - * Effect.runPromiseExit(program).then(console.log) - * // Output: Exit.failure with custom error message + * const error = await Effect.runPromise(program) + * error.message // => "Failed to process hello: unavailable" * ``` * - * @category effectify + * @category converting * @since 4.0.0 */ export const effectify: { ) => any>(fn: F): Effectify.Effectify> ) => any, E>( fn: F, - onError: (error: Effectify.EffectifyError, args: Parameters) => E + onError: (error: Effectify.EffectifyError, args: EffectifyArgs) => E ): Effectify.Effectify ) => any, E, E2>( fn: F, - onError: (error: Effectify.EffectifyError, args: Parameters) => E, - onSyncError: (error: unknown, args: Parameters) => E2 + onError: (error: Effectify.EffectifyError, args: EffectifyArgs) => E, + onSyncError: (error: unknown, args: EffectifyArgs) => E2 ): Effectify.Effectify } = ((fn: Function, onError?: (e: any, args: any) => any, onSyncError?: (e: any, args: any) => any) => @@ -15073,7 +15037,7 @@ export const effectify: { * * **Example** (Constraining the success type) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // Define a constraint that the success type must be a number @@ -15081,6 +15045,7 @@ export const effectify: { * * // This works - Effect<42, never, never> extends Effect * const validEffect = satisfiesNumber(Effect.succeed(42)) + * Effect.runSync(validEffect) // => 42 * * // This would cause a TypeScript compilation error: * // const invalidEffect = satisfiesNumber(Effect.succeed("string")) @@ -15103,7 +15068,7 @@ export const satisfiesSuccessType = () => (effect: Effect * * **Example** (Constraining the error type) * - * ```ts + * ```ts import.meta.vitest * import { Data, Effect } from "effect" * * class ValidationError extends Data.TaggedError("ValidationError")<{}> {} @@ -15113,6 +15078,7 @@ export const satisfiesSuccessType = () => (effect: Effect * * // This works - Effect extends the constrained type * const validEffect = satisfiesError(Effect.fail(new ValidationError())) + * Effect.runSync(Effect.flip(validEffect))._tag // => "ValidationError" * * // This would cause a TypeScript compilation error: * // const invalidEffect = satisfiesError(Effect.fail("string error")) @@ -15135,7 +15101,7 @@ export const satisfiesErrorType = () => (effect: Effect() => (effect: Effec * * **Example** (Mapping already completed effects) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // For resolved effects, the mapping is applied immediately @@ -15178,11 +15144,13 @@ export const satisfiesServicesType = () => (effect: Effec * const mapped = Effect.mapEager(resolved, (n) => n * 2) // Applied eagerly * * // For pending effects, behaves like regular map - * const pending = Effect.delay(Effect.succeed(5), "100 millis") + * const pending = Effect.delay(Effect.succeed(5), 0) * const mappedPending = Effect.mapEager(pending, (n) => n * 2) // Uses regular map + * + * await Effect.runPromise(Effect.all([mapped, mappedPending])) // => [10, 10] * ``` * - * @category eager + * @category mapping * @since 4.0.0 */ export const mapEager: { @@ -15207,22 +15175,29 @@ export const mapEager: { * * **Example** (Mapping errors eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // For resolved failure effects, the error mapping is applied immediately * const failed = Effect.fail("original error") * const mapped = Effect.mapErrorEager(failed, (err: string) => `mapped: ${err}`) // Applied eagerly * * // For pending effects, behaves like regular mapError - * const pending = Effect.delay(Effect.fail("error"), "100 millis") + * const pending = Effect.delay(Effect.fail("error"), 0) * const mappedPending = Effect.mapErrorEager( * pending, * (err: string) => `mapped: ${err}` * ) // Uses regular mapError + * + * void output.push(await Effect.runPromise(Effect.all([ + * Effect.flip(mapped), + * Effect.flip(mappedPending) + * ]))) + * output // => [['mapped: original error', 'mapped: error']] * ``` * - * @category eager + * @category error handling * @since 4.0.0 */ export const mapErrorEager: { @@ -15246,8 +15221,9 @@ export const mapErrorEager: { * * **Example** (Mapping both channels eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // For resolved effects, the appropriate mapping is applied immediately * const success = Effect.succeed(5) @@ -15261,9 +15237,13 @@ export const mapErrorEager: { * onFailure: (err: string) => `Failed: ${err}`, * onSuccess: (n: number) => n * 2 * }) // onFailure applied eagerly + * + * void output.push(Effect.runSync(mapped)) + * void output.push(Effect.runSync(Effect.flip(mappedError))) + * output // => [10, "Failed: error"] * ``` * - * @category eager + * @category mapping * @since 4.0.0 */ export const mapBothEager: { @@ -15292,7 +15272,7 @@ export const mapBothEager: { * * **Example** (Flat mapping eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // For resolved effects, the flatMap is applied immediately @@ -15300,14 +15280,16 @@ export const mapBothEager: { * const flatMapped = Effect.flatMapEager(resolved, (n) => Effect.succeed(n * 2)) // Applied eagerly * * // For pending effects, behaves like regular flatMap - * const pending = Effect.delay(Effect.succeed(5), "100 millis") + * const pending = Effect.delay(Effect.succeed(5), 0) * const flatMappedPending = Effect.flatMapEager( * pending, * (n) => Effect.succeed(n * 2) * ) // Uses regular flatMap + * + * await Effect.runPromise(Effect.all([flatMapped, flatMappedPending])) // => [10, 10] * ``` * - * @category eager + * @category sequencing * @since 4.0.0 */ export const flatMapEager: { @@ -15331,8 +15313,9 @@ export const flatMapEager: { * * **Example** (Catching failures eagerly when possible) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" + * const output: Array = [] * * // For resolved failure effects, the catch function is applied immediately * const failed = Effect.fail("original error") @@ -15349,14 +15332,21 @@ export const flatMapEager: { * ) // Returns success as-is * * // For pending effects, behaves like regular catch - * const pending = Effect.delay(Effect.fail("error"), "100 millis") + * const pending = Effect.delay(Effect.fail("error"), 0) * const recoveredPending = Effect.catchEager( * pending, * (err: string) => Effect.succeed(`recovered from: ${err}`) * ) // Uses regular catch + * + * void output.push(await Effect.runPromise(Effect.all([ + * recovered, + * unchanged, + * recoveredPending + * ]))) + * output // => [['recovered from: original error', 42, 'recovered from: error']] * ``` * - * @category eager + * @category error handling * @since 4.0.0 */ export const catchEager: { @@ -15379,7 +15369,7 @@ export const catchEager: { * * **Example** (Defining eager untraced effect functions) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * const computation = Effect.fnUntracedEager(function*() { @@ -15389,9 +15379,10 @@ export const catchEager: { * }) * * const effect = computation() // Executed immediately if all effects are sync + * Effect.runSync(effect) // => "computed eagerly" * ``` * - * @category eager + * @category constructors * @since 4.0.0 */ export const fnUntracedEager: fn.Untraced = internal.fnUntracedEager diff --git a/packages/effect/src/Effectable.ts b/packages/effect/src/Effectable.ts index f4a114ad8..7a1ca626f 100644 --- a/packages/effect/src/Effectable.ts +++ b/packages/effect/src/Effectable.ts @@ -1,6 +1,6 @@ /** * Low-level helpers for making custom values behave like Effects. The module - * exposes a prototype builder and an abstract base class that let + * exposes a prototype builder, an abstract base class, and a mixin that let * domain-specific values, such as service keys or configuration descriptions, * be evaluated by Effect and yielded inside `Effect.gen`. * @@ -8,7 +8,7 @@ */ import type * as Effect from "./Effect.ts" import type * as Fiber from "./Fiber.ts" -import { evaluate, makePrimitiveProto } from "./internal/core.ts" +import { type EffectTypeId, evaluate, makePrimitiveProto } from "./internal/core.ts" /** * Create a low-level `Effect` prototype. @@ -24,6 +24,7 @@ import { evaluate, makePrimitiveProto } from "./internal/core.ts" * When the effect is evaluated, it calls `evaluate` with the current fiber. * * @see {@link Class} for a class-based approach to defining custom Effect values + * @see {@link Mixin} for wrapping an existing class constructor * * @category prototypes * @since 4.0.0 @@ -40,14 +41,16 @@ export const Prototype = >(options: { [evaluate]: options.evaluate }) as any +const proto = Prototype>({ + label: "Effectable", + evaluate(_) { + return this.asEffect() + } +}) + const Base: new() => Effect.Effect = (() => { const Base = function() {} - Base.prototype = Prototype({ - label: "Effectable", - evaluate(_) { - return this - } - }) + Base.prototype = proto return Base as any })() @@ -60,9 +63,75 @@ const Base: new() => Effect.Effect = (() => { * as `Effect` values. * * @see {@link Prototype} for a lower-level primitive approach to creating custom Effect-like values without a class + * @see {@link Mixin} for wrapping an existing class constructor * @category constructors * @since 2.0.0 */ export abstract class Class extends Base { - abstract override: Effect.Effect + abstract asEffect(): Effect.Effect +} + +type AsEffectReturn = Self extends { + asEffect(): infer A extends Effect.Effect +} ? A + : never + +declare abstract class MixinBase extends Class { + constructor(...args: ReadonlyArray) + override readonly [EffectTypeId]: AsEffectReturn[typeof EffectTypeId] + override [Symbol.iterator](): Effect.EffectIterator> +} + +/** + * Returns a subclass of the provided class that inserts the Effect prototype + * into the inheritance chain. + * + * **When to use** + * + * Use to make instances of an existing class behave as `Effect` values without + * extending {@link Class} or modifying the original prototype. + * + * **Details** + * + * Pass the class to wrap, then implement `asEffect` on the final class. The + * returned class is abstract, and the success, error, and service types are + * inferred from the concrete `asEffect` return type. Concrete and abstract base + * classes are supported. Constructor parameters and instance members are + * preserved, except that Effect's prototype members shadow base prototype + * members with the same name: `pipe`, `toString`, `toJSON`, `[Symbol.iterator]`, + * and `[Symbol.for("nodejs.util.inspect.custom")]`. + * + * **Example** (Evaluating a mixed-in class) + * + * ```ts import.meta.vitest + * import { Effect, Effectable } from "effect" + * + * class Box { + * constructor(readonly value: number) {} + * } + * + * class EffectBox extends Effectable.Mixin(Box) { + * asEffect() { + * return Effect.succeed(this.value) + * } + * } + * + * const box = new EffectBox(2) + * Effect.isEffect(box) // => true + * await Effect.runPromise(box) // => 2 + * ``` + * + * @see {@link Prototype} for a lower-level primitive approach to creating custom Effect-like values without a class + * @see {@link Class} for a base constructor to extend + * @category constructors + * @since 4.0.0 + */ +export const Mixin = ) => object>( + klass: TBase +): TBase & typeof MixinBase => { + abstract class Mixed extends klass { + abstract asEffect(): Effect.Effect + } + Object.defineProperties(Mixed.prototype, Object.getOwnPropertyDescriptors(proto)) + return Mixed as TBase & typeof MixinBase } diff --git a/packages/effect/src/Encoding.ts b/packages/effect/src/Encoding.ts index 11885fb90..25dcc1312 100644 --- a/packages/effect/src/Encoding.ts +++ b/packages/effect/src/Encoding.ts @@ -34,7 +34,7 @@ import * as Result from "./Result.ts" * @category type IDs * @since 4.0.0 */ -export const EncodingErrorTypeId = "~effect/encoding/EncodingError" as const +export const EncodingErrorTypeId = "~effect/Encoding/EncodingError" as const /** * Literal type of the `EncodingErrorTypeId` marker. @@ -64,7 +64,7 @@ export type EncodingErrorTypeId = typeof EncodingErrorTypeId * message. * * @see {@link isEncodingError} for checking whether a value is an EncodingError - * @category constructors + * @category errors * @since 4.0.0 */ export class EncodingError extends Data.TaggedError("EncodingError")<{ @@ -126,15 +126,15 @@ export const isEncodingError = (u: unknown): u is EncodingError => hasProperty(u * * **Example** (Encoding Base64 strings and bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding } from "effect" * * // Encode a string - * console.log(Encoding.encodeBase64("hello")) // "aGVsbG8=" + * Encoding.encodeBase64("hello") // => "aGVsbG8=" * * // Encode binary data * const bytes = new Uint8Array([72, 101, 108, 108, 111]) - * console.log(Encoding.encodeBase64(bytes)) // "SGVsbG8=" + * Encoding.encodeBase64(bytes) // => "SGVsbG8=" * ``` * * @see {@link decodeBase64} for decoding standard Base64 to bytes @@ -162,13 +162,10 @@ export const encodeBase64: (input: Uint8Array | string) => string = (input) => * * **Example** (Decoding Base64 bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeBase64("SGVsbG8=") - * if (Result.isSuccess(result)) { - * console.log(Array.from(result.success)) // [72, 101, 108, 108, 111] - * } + * Encoding.decodeBase64("SGVsbG8=") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) * ``` * * @category decoding @@ -242,13 +239,10 @@ export const decodeBase64 = (str: string): Result.Result Result.map(decodeBase64(str), * * **Example** (Encoding URL-safe Base64) * - * ```ts + * ```ts import.meta.vitest * import { Encoding } from "effect" * * // URL-safe base64 encoding (uses - and _ instead of + and /) - * console.log(Encoding.encodeBase64Url("hello?")) // "aGVsbG8_" + * Encoding.encodeBase64Url("hello?") // => "aGVsbG8_" * * const bytes = new Uint8Array([72, 101, 108, 108, 111, 63]) - * console.log(Encoding.encodeBase64Url(bytes)) // "SGVsbG8_" + * Encoding.encodeBase64Url(bytes) // => "SGVsbG8_" * ``` * * @see {@link decodeBase64Url} for decoding URL-safe Base64 to bytes @@ -313,13 +307,10 @@ export const encodeBase64Url: (input: Uint8Array | string) => string = (input) = * * **Example** (Decoding URL-safe Base64 bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeBase64Url("SGVsbG8_") - * if (Result.isSuccess(result)) { - * console.log(Array.from(result.success)) // [72, 101, 108, 108, 111, 63] - * } + * Encoding.decodeBase64Url("SGVsbG8_") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111, 63])) * ``` * * @category decoding @@ -373,13 +364,10 @@ export const decodeBase64Url = (str: string): Result.Result Result.succeed("hello?") * ``` * * @category decoding @@ -400,15 +388,15 @@ export const decodeBase64UrlString = (str: string) => Result.map(decodeBase64Url * * **Example** (Encoding hex strings and bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding } from "effect" * * // Encode a string to hex - * console.log(Encoding.encodeHex("hello")) // "68656c6c6f" + * Encoding.encodeHex("hello") // => "68656c6c6f" * * // Encode binary data to hex * const bytes = new Uint8Array([72, 101, 108, 108, 111]) - * console.log(Encoding.encodeHex(bytes)) // "48656c6c6f" + * Encoding.encodeHex(bytes) // => "48656c6c6f" * ``` * * @category encoding @@ -417,6 +405,125 @@ export const decodeBase64UrlString = (str: string) => Result.map(decodeBase64Url export const encodeHex: (input: Uint8Array | string) => string = (input) => typeof input === "string" ? hexEncodeUint8Array(encoder.encode(input)) : hexEncodeUint8Array(input) +/** + * Generates a random lowercase hexadecimal string, optimized for lengths that + * are multiples of 8. + * + * **Details** + * + * `length` is not validated. The function generates `length >>> 3` random + * 8-character words, so non-negative lengths below `2 ** 32` are rounded down + * to a multiple of 8 and other values follow JavaScript's unsigned 32-bit + * coercion rules. + * + * This function uses `Math.random()` and is not cryptographically secure. For + * security-sensitive values, use the `Crypto.Crypto` service's `randomBytes` + * method and encode the result with {@link encodeHex}. + * + * @category encoding + * @since 4.0.0 + */ +export const randomHex = (length: number): string => { + switch (length) { + case 16: + return randomHex16() + case 32: + return randomHex32() + default: { + let result = "" + for (let i = length >>> 3; i > 0; i--) { + result += randomHex8() + } + return result + } + } +} + +const hexCharCodes = Uint8Array.from("0123456789abcdef", (c) => c.charCodeAt(0)) + +const randomWord = (): number => (Math.random() * 0x100000000) >>> 0 + +// Trace and span identifiers are the common lengths. A single +// String.fromCharCode call produces a flat string, which avoids rope +// flattening when the identifier is later serialized. +const randomHex8 = (): string => { + const a = randomWord() + return String.fromCharCode( + hexCharCodes[a >>> 28], + hexCharCodes[(a >>> 24) & 15], + hexCharCodes[(a >>> 20) & 15], + hexCharCodes[(a >>> 16) & 15], + hexCharCodes[(a >>> 12) & 15], + hexCharCodes[(a >>> 8) & 15], + hexCharCodes[(a >>> 4) & 15], + hexCharCodes[a & 15] + ) +} + +const randomHex16 = (): string => { + const a = randomWord() + const b = randomWord() + return String.fromCharCode( + hexCharCodes[a >>> 28], + hexCharCodes[(a >>> 24) & 15], + hexCharCodes[(a >>> 20) & 15], + hexCharCodes[(a >>> 16) & 15], + hexCharCodes[(a >>> 12) & 15], + hexCharCodes[(a >>> 8) & 15], + hexCharCodes[(a >>> 4) & 15], + hexCharCodes[a & 15], + hexCharCodes[b >>> 28], + hexCharCodes[(b >>> 24) & 15], + hexCharCodes[(b >>> 20) & 15], + hexCharCodes[(b >>> 16) & 15], + hexCharCodes[(b >>> 12) & 15], + hexCharCodes[(b >>> 8) & 15], + hexCharCodes[(b >>> 4) & 15], + hexCharCodes[b & 15] + ) +} + +const randomHex32 = (): string => { + const a = randomWord() + const b = randomWord() + const c = randomWord() + const d = randomWord() + return String.fromCharCode( + hexCharCodes[a >>> 28], + hexCharCodes[(a >>> 24) & 15], + hexCharCodes[(a >>> 20) & 15], + hexCharCodes[(a >>> 16) & 15], + hexCharCodes[(a >>> 12) & 15], + hexCharCodes[(a >>> 8) & 15], + hexCharCodes[(a >>> 4) & 15], + hexCharCodes[a & 15], + hexCharCodes[b >>> 28], + hexCharCodes[(b >>> 24) & 15], + hexCharCodes[(b >>> 20) & 15], + hexCharCodes[(b >>> 16) & 15], + hexCharCodes[(b >>> 12) & 15], + hexCharCodes[(b >>> 8) & 15], + hexCharCodes[(b >>> 4) & 15], + hexCharCodes[b & 15], + hexCharCodes[c >>> 28], + hexCharCodes[(c >>> 24) & 15], + hexCharCodes[(c >>> 20) & 15], + hexCharCodes[(c >>> 16) & 15], + hexCharCodes[(c >>> 12) & 15], + hexCharCodes[(c >>> 8) & 15], + hexCharCodes[(c >>> 4) & 15], + hexCharCodes[c & 15], + hexCharCodes[d >>> 28], + hexCharCodes[(d >>> 24) & 15], + hexCharCodes[(d >>> 20) & 15], + hexCharCodes[(d >>> 16) & 15], + hexCharCodes[(d >>> 12) & 15], + hexCharCodes[(d >>> 8) & 15], + hexCharCodes[(d >>> 4) & 15], + hexCharCodes[d & 15] + ) +} + /** * Decodes a hexadecimal string into bytes safely. * @@ -432,13 +539,10 @@ export const encodeHex: (input: Uint8Array | string) => string = (input) => * * **Example** (Decoding hex bytes) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeHex("48656c6c6f") - * if (Result.isSuccess(result)) { - * console.log(Array.from(result.success)) // [72, 101, 108, 108, 111] - * } + * Encoding.decodeHex("48656c6c6f") // => Result.succeed(new Uint8Array([72, 101, 108, 108, 111])) * ``` * * @category decoding @@ -494,13 +598,10 @@ export const decodeHex = (str: string): Result.Result * * **Example** (Decoding hex strings) * - * ```ts + * ```ts import.meta.vitest * import { Encoding, Result } from "effect" * - * const result = Encoding.decodeHexString("68656c6c6f") - * if (Result.isSuccess(result)) { - * console.log(result.success) // "hello" - * } + * Encoding.decodeHexString("68656c6c6f") // => Result.succeed("hello") * ``` * * @category decoding @@ -761,12 +862,13 @@ const base64UrlEncodeUint8Array = (data: Uint8Array) => // Hex internals -const hexEncodeUint8Array = (bytes: Uint8Array) => { +const byteToHex: Array = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")) + +const hexEncodeUint8Array = (bytes: Uint8Array): string => { let result = "" - for (let i = 0; i < bytes.length; ++i) { - result += bytesToHex[bytes[i]] + for (let i = 0; i < bytes.length; i++) { + result += byteToHex[bytes[i]] } - return result } @@ -785,262 +887,3 @@ const fromHexChar = (byte: number) => { throw new TypeError("Invalid input") } - -const bytesToHex = [ - "00", - "01", - "02", - "03", - "04", - "05", - "06", - "07", - "08", - "09", - "0a", - "0b", - "0c", - "0d", - "0e", - "0f", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "1a", - "1b", - "1c", - "1d", - "1e", - "1f", - "20", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "28", - "29", - "2a", - "2b", - "2c", - "2d", - "2e", - "2f", - "30", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "38", - "39", - "3a", - "3b", - "3c", - "3d", - "3e", - "3f", - "40", - "41", - "42", - "43", - "44", - "45", - "46", - "47", - "48", - "49", - "4a", - "4b", - "4c", - "4d", - "4e", - "4f", - "50", - "51", - "52", - "53", - "54", - "55", - "56", - "57", - "58", - "59", - "5a", - "5b", - "5c", - "5d", - "5e", - "5f", - "60", - "61", - "62", - "63", - "64", - "65", - "66", - "67", - "68", - "69", - "6a", - "6b", - "6c", - "6d", - "6e", - "6f", - "70", - "71", - "72", - "73", - "74", - "75", - "76", - "77", - "78", - "79", - "7a", - "7b", - "7c", - "7d", - "7e", - "7f", - "80", - "81", - "82", - "83", - "84", - "85", - "86", - "87", - "88", - "89", - "8a", - "8b", - "8c", - "8d", - "8e", - "8f", - "90", - "91", - "92", - "93", - "94", - "95", - "96", - "97", - "98", - "99", - "9a", - "9b", - "9c", - "9d", - "9e", - "9f", - "a0", - "a1", - "a2", - "a3", - "a4", - "a5", - "a6", - "a7", - "a8", - "a9", - "aa", - "ab", - "ac", - "ad", - "ae", - "af", - "b0", - "b1", - "b2", - "b3", - "b4", - "b5", - "b6", - "b7", - "b8", - "b9", - "ba", - "bb", - "bc", - "bd", - "be", - "bf", - "c0", - "c1", - "c2", - "c3", - "c4", - "c5", - "c6", - "c7", - "c8", - "c9", - "ca", - "cb", - "cc", - "cd", - "ce", - "cf", - "d0", - "d1", - "d2", - "d3", - "d4", - "d5", - "d6", - "d7", - "d8", - "d9", - "da", - "db", - "dc", - "dd", - "de", - "df", - "e0", - "e1", - "e2", - "e3", - "e4", - "e5", - "e6", - "e7", - "e8", - "e9", - "ea", - "eb", - "ec", - "ed", - "ee", - "ef", - "f0", - "f1", - "f2", - "f3", - "f4", - "f5", - "f6", - "f7", - "f8", - "f9", - "fa", - "fb", - "fc", - "fd", - "fe", - "ff" -] diff --git a/packages/effect/src/Equal.ts b/packages/effect/src/Equal.ts index d02b47412..f696fe444 100644 --- a/packages/effect/src/Equal.ts +++ b/packages/effect/src/Equal.ts @@ -28,7 +28,7 @@ import { hasProperty } from "./Predicate.ts" * * **Example** (Implementing Equal on a class) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash } from "effect" * * class UserId implements Equal.Equal { @@ -42,6 +42,9 @@ import { hasProperty } from "./Predicate.ts" * return Hash.string(this.id) * } * } + * + * Equal.equals(new UserId("1"), new UserId("1")) // => true + * Equal.equals(new UserId("1"), new UserId("2")) // => false * ``` * * @see {@link Equal} — the interface that uses this symbol @@ -49,7 +52,7 @@ import { hasProperty } from "./Predicate.ts" * @category symbols * @since 2.0.0 */ -export const symbol = "~effect/interfaces/Equal" +export const symbol = "~effect/Equal" /** * The interface for types that define their own equality logic. @@ -76,7 +79,7 @@ export const symbol = "~effect/interfaces/Equal" * * **Example** (Comparing coordinates by value) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash } from "effect" * * class Coordinate implements Equal.Equal { @@ -93,8 +96,8 @@ export const symbol = "~effect/interfaces/Equal" * } * } * - * console.log(Equal.equals(new Coordinate(1, 2), new Coordinate(1, 2))) // true - * console.log(Equal.equals(new Coordinate(1, 2), new Coordinate(3, 4))) // false + * Equal.equals(new Coordinate(1, 2), new Coordinate(1, 2)) // => true + * Equal.equals(new Coordinate(1, 2), new Coordinate(3, 4)) // => false * ``` * * @see {@link symbol} — the property key used by the equality method @@ -139,30 +142,25 @@ export interface Equal extends Hash.Hash { * * **Example** (Comparing values) * - * ```ts + * ```ts import.meta.vitest * import { Equal } from "effect" * - * // Primitives - * console.log(Equal.equals(1, 1)) // true - * console.log(Equal.equals(NaN, NaN)) // true - * console.log(Equal.equals("a", "b")) // false + * Equal.equals(1, 1) // => true + * Equal.equals(NaN, NaN) // => true + * Equal.equals("a", "b") // => false * - * // Objects and arrays - * console.log(Equal.equals({ a: 1, b: 2 }, { a: 1, b: 2 })) // true - * console.log(Equal.equals([1, [2, 3]], [1, [2, 3]])) // true + * Equal.equals({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true + * Equal.equals([1, [2, 3]], [1, [2, 3]]) // => true * - * // Dates - * console.log(Equal.equals(new Date("2024-01-01"), new Date("2024-01-01"))) // true + * Equal.equals(new Date("2024-01-01"), new Date("2024-01-01")) // => true * - * // Maps (order-independent) * const m1 = new Map([["a", 1], ["b", 2]]) * const m2 = new Map([["b", 2], ["a", 1]]) - * console.log(Equal.equals(m1, m2)) // true + * Equal.equals(m1, m2) // => true * - * // Curried form * const is5 = Equal.equals(5) - * console.log(is5(5)) // true - * console.log(is5(3)) // false + * is5(5) // => true + * is5(3) // => false * ``` * * @see {@link Equal} — the interface for custom equality @@ -235,7 +233,9 @@ function compareObjects(self: object, that: object): boolean { return false } else if (self instanceof Date) { if (!(that instanceof Date)) return false - return self.toISOString() === that.toISOString() + const selfTime = self.getTime() + const thatTime = that.getTime() + return selfTime === thatTime || (Number.isNaN(selfTime) && Number.isNaN(thatTime)) } else if (self instanceof RegExp) { if (!(that instanceof RegExp)) return false return self.toString() === that.toString() @@ -256,9 +256,21 @@ function compareObjects(self: object, that: object): boolean { } return compareArrays(self, that) } else if (ArrayBuffer.isView(self)) { - if (!ArrayBuffer.isView(that) || self.byteLength !== that.byteLength) { + const selfIsDataView = self instanceof DataView + if ( + !ArrayBuffer.isView(that) || + self.byteLength !== that.byteLength || + selfIsDataView !== (that instanceof DataView) + ) { return false } + if (selfIsDataView) { + const thatDataView = that as DataView + return compareTypedArrays( + new Uint8Array(self.buffer, self.byteOffset, self.byteLength), + new Uint8Array(thatDataView.buffer, thatDataView.byteOffset, thatDataView.byteLength) + ) + } return compareTypedArrays(self as Uint8Array, that as Uint8Array) } else if (self instanceof Map) { if (!(that instanceof Map) || self.size !== that.size) { @@ -348,10 +360,14 @@ function compareRecords( /** @internal */ export function makeCompareMap(keyEquivalence: Equivalence, valueEquivalence: Equivalence) { return function compareMaps(self: Iterable<[K, V]>, that: Iterable<[K, V]>): boolean { + const thatEntries = Array.from(that) for (const [selfKey, selfValue] of self) { let found = false - for (const [thatKey, thatValue] of that) { + for (let i = 0; i < thatEntries.length; i++) { + const [thatKey, thatValue] = thatEntries[i] if (keyEquivalence(selfKey, thatKey) && valueEquivalence(selfValue, thatValue)) { + thatEntries[i] = thatEntries[thatEntries.length - 1] + thatEntries.pop() found = true break } @@ -370,10 +386,14 @@ const compareMaps = makeCompareMap(compareBoth, compareBoth) /** @internal */ export function makeCompareSet(equivalence: Equivalence) { return function compareSets(self: Iterable, that: Iterable): boolean { + const thatValues = Array.from(that) for (const selfValue of self) { let found = false - for (const thatValue of that) { + for (let i = 0; i < thatValues.length; i++) { + const thatValue = thatValues[i] if (equivalence(selfValue, thatValue)) { + thatValues[i] = thatValues[thatValues.length - 1] + thatValues.pop() found = true break } @@ -406,7 +426,7 @@ const compareSets = makeCompareSet(compareBoth) * * **Example** (Checking Equal values) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash } from "effect" * * class Token implements Equal.Equal { @@ -419,9 +439,9 @@ const compareSets = makeCompareSet(compareBoth) * } * } * - * console.log(Equal.isEqual(new Token("abc"))) // true - * console.log(Equal.isEqual({ x: 1 })) // false - * console.log(Equal.isEqual(42)) // false + * Equal.isEqual(new Token("abc")) // => true + * Equal.isEqual({ x: 1 }) // => false + * Equal.isEqual(42) // => false * ``` * * @see {@link Equal} — the interface being checked @@ -447,12 +467,10 @@ export const isEqual = (u: unknown): u is Equal => hasProperty(u, symbol) * * **Example** (Deduplicating with Equal semantics) * - * ```ts + * ```ts import.meta.vitest * import { Array, Equal } from "effect" * - * const eq = Equal.asEquivalence() - * const result = Array.dedupeWith([1, 2, 2, 3, 1], eq) - * console.log(result) // [1, 2, 3] + * Array.dedupeWith([1, 2, 2, 3, 1], Equal.asEquivalence()) // => [1, 2, 3] * ``` * * @see {@link equals} — the underlying comparison function @@ -481,18 +499,18 @@ export const asEquivalence: () => Equivalence = () => equals * * **Example** (Opting out of structural equality) * - * ```ts + * ```ts import.meta.vitest * import { Equal } from "effect" * * const a = { x: 1 } * const b = { x: 1 } * - * console.log(Equal.equals(a, b)) // true (structural) + * Equal.equals(a, b) // => true * * const aRef = Equal.byReference(a) - * console.log(Equal.equals(aRef, b)) // false (reference) - * console.log(Equal.equals(aRef, aRef)) // true (same reference) - * console.log(aRef.x) // 1 (proxy reads through) + * Equal.equals(aRef, b) // => false + * Equal.equals(aRef, aRef) // => true + * aRef.x // => 1 * ``` * * @see {@link byReferenceUnsafe} — same effect without a proxy (mutates the @@ -526,17 +544,17 @@ export const byReference = (obj: T): T => byReferenceUnsafe(ne * * **Example** (Marking an object for reference equality) * - * ```ts + * ```ts import.meta.vitest * import { Equal } from "effect" * * const obj1 = { a: 1, b: 2 } * const obj2 = { a: 1, b: 2 } * - * Equal.byReferenceUnsafe(obj1) + * const marked = Equal.byReferenceUnsafe(obj1) * - * console.log(Equal.equals(obj1, obj2)) // false (reference) - * console.log(Equal.equals(obj1, obj1)) // true (same reference) - * console.log(obj1 === Equal.byReferenceUnsafe(obj1)) // true (same object) + * Equal.equals(obj1, obj2) // => false + * Equal.equals(obj1, obj1) // => true + * marked === obj1 // => true * ``` * * @see {@link byReference} — safer alternative that creates a proxy diff --git a/packages/effect/src/Equivalence.ts b/packages/effect/src/Equivalence.ts index 9a5a20fc5..e1fe8ce5d 100644 --- a/packages/effect/src/Equivalence.ts +++ b/packages/effect/src/Equivalence.ts @@ -27,18 +27,18 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Defining simple number equivalence) * - * ```ts + * ```ts import.meta.vitest * import type { Equivalence } from "effect" * * const numberEq: Equivalence.Equivalence = (a, b) => a === b * - * console.log(numberEq(1, 1)) // true - * console.log(numberEq(1, 2)) // false + * numberEq(1, 1) // => true + * numberEq(1, 2) // => false * ``` * * **Example** (Defining custom object equivalence) * - * ```ts + * ```ts import.meta.vitest * import type { Equivalence } from "effect" * * interface Point { @@ -49,12 +49,12 @@ import * as Reducer from "./Reducer.ts" * const pointEq: Equivalence.Equivalence = (a, b) => * a.x === b.x && a.y === b.y * - * console.log(pointEq({ x: 1, y: 2 }, { x: 1, y: 2 })) // true + * pointEq({ x: 1, y: 2 }, { x: 1, y: 2 }) // => true * ``` * * @see {@link make} * @see {@link strictEqual} - * @category type class + * @category models * @since 2.0.0 */ export type Equivalence = (self: A, that: A) => boolean @@ -73,7 +73,7 @@ export type Equivalence = (self: A, that: A) => boolean * * **Example** (Type-level usage) * - * ```ts + * ```ts import.meta.vitest * import type { Equivalence, HKT } from "effect" * * // Used internally for type-level computations @@ -89,7 +89,7 @@ export type Equivalence = (self: A, that: A) => boolean * * @see {@link Equivalence} * @see {@link TypeLambda} - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface EquivalenceTypeLambda extends TypeLambda { @@ -113,30 +113,30 @@ export interface EquivalenceTypeLambda extends TypeLambda { * * **Example** (Case-insensitive string equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitive = Equivalence.make((a, b) => * a.toLowerCase() === b.toLowerCase() * ) * - * console.log(caseInsensitive("Hello", "HELLO")) // true - * console.log(caseInsensitive("foo", "bar")) // false + * caseInsensitive("Hello", "HELLO") // => true + * caseInsensitive("foo", "bar") // => false * * // Same reference optimization * const str = "test" - * console.log(caseInsensitive(str, str)) // true (fast path) + * caseInsensitive(str, str) // => true * ``` * * **Example** (Comparing numbers with tolerance) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const tolerance = Equivalence.make((a, b) => Math.abs(a - b) < 0.0001) * - * console.log(tolerance(1.0, 1.001)) // false - * console.log(tolerance(1.0, 1.00001)) // true + * tolerance(1.0, 1.001) // => false + * tolerance(1.0, 1.00001) // => true * ``` * * @see {@link strictEqual} @@ -169,26 +169,26 @@ const isStrictEquivalent = (x: unknown, y: unknown) => x === y * * **Example** (Comparing primitive types) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const strictEq = Equivalence.strictEqual() * - * console.log(strictEq(1, 1)) // true - * console.log(strictEq(1, 2)) // false - * console.log(strictEq(NaN, NaN)) // false (NaN !== NaN) + * strictEq(1, 1) // => true + * strictEq(1, 2) // => false + * strictEq(NaN, NaN) // => false * ``` * * **Example** (Comparing objects by reference) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const obj = { value: 42 } * const strictObjEq = Equivalence.strictEqual() * - * console.log(strictObjEq(obj, obj)) // true - * console.log(strictObjEq(obj, { value: 42 })) // false (different references) + * strictObjEq(obj, obj) // => true + * strictObjEq(obj, { value: 42 }) // => false * ``` * * @see {@link make} @@ -207,11 +207,11 @@ export const strictEqual: () => Equivalence = () => isStrictEquivalent * * **Example** (Comparing strings) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.String("hello", "hello")) // true - * console.log(Equivalence.String("hello", "world")) // false + * Equivalence.String("hello", "hello") // => true + * Equivalence.String("hello", "world") // => false * ``` * * @category instances @@ -228,12 +228,12 @@ export const String: Equivalence = isStrictEquivalent * * **Example** (Comparing numbers) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.Number(1, 1)) // true - * console.log(Equivalence.Number(1, 2)) // false - * console.log(Equivalence.Number(NaN, NaN)) // true + * Equivalence.Number(1, 1) // => true + * Equivalence.Number(1, 2) // => false + * Equivalence.Number(NaN, NaN) // => true * ``` * * @category instances @@ -252,11 +252,11 @@ export const Number: Equivalence = make((self, that) => * * **Example** (Comparing booleans) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.Boolean(true, true)) // true - * console.log(Equivalence.Boolean(true, false)) // false + * Equivalence.Boolean(true, true) // => true + * Equivalence.Boolean(true, false) // => false * ``` * * @category instances @@ -273,11 +273,11 @@ export const Boolean: Equivalence = isStrictEquivalent * * **Example** (Comparing bigints) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * - * console.log(Equivalence.BigInt(1n, 1n)) // true - * console.log(Equivalence.BigInt(1n, 2n)) // false + * Equivalence.BigInt(1n, 1n) // => true + * Equivalence.BigInt(1n, 2n) // => false * ``` * * @category instances @@ -301,7 +301,7 @@ export const BigInt: Equivalence = isStrictEquivalent * * **Example** (Combining name and age equivalences) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface Person { @@ -325,8 +325,8 @@ export const BigInt: Equivalence = isStrictEquivalent * const person2 = { name: "Alice", age: 30 } * const person3 = { name: "Alice", age: 31 } * - * console.log(personEquivalence(person1, person2)) // true - * console.log(personEquivalence(person1, person3)) // false (different age) + * personEquivalence(person1, person2) // => true + * personEquivalence(person1, person3) // => false * ``` * * @see {@link combineAll} @@ -356,7 +356,7 @@ export const combine: { * * **Example** (Combining multiple field equivalences) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface Point3D { @@ -384,18 +384,18 @@ export const combine: { * const point2 = { x: 1, y: 2, z: 3 } * const point3 = { x: 1, y: 2, z: 4 } * - * console.log(point3DEq(point1, point2)) // true - * console.log(point3DEq(point1, point3)) // false (different z) + * point3DEq(point1, point2) // => true + * point3DEq(point1, point3) // => false * ``` * * **Example** (Handling empty collections) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * // Empty collection always returns true * const alwaysEq = Equivalence.combineAll([]) - * console.log(alwaysEq("anything", "else")) // true + * alwaysEq("anything", "else") // => true * ``` * * @see {@link combine} @@ -431,7 +431,7 @@ export const combineAll = (collection: Iterable>): Equivalence * * **Example** (Deriving equivalence from an object property) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface User { @@ -450,13 +450,13 @@ export const combineAll = (collection: Iterable>): Equivalence * const user2 = { id: 1, name: "Alice Smith", email: "alice.smith@example.com" } * const user3 = { id: 2, name: "Bob", email: "bob@example.com" } * - * console.log(userByIdEq(user1, user2)) // true (same ID) - * console.log(userByIdEq(user1, user3)) // false (different ID) + * userByIdEq(user1, user2) // => true + * userByIdEq(user1, user3) // => false * ``` * * **Example** (Case-insensitive string equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitiveEq = Equivalence.mapInput( @@ -464,8 +464,8 @@ export const combineAll = (collection: Iterable>): Equivalence * (s: string) => s.toLowerCase() * ) * - * console.log(caseInsensitiveEq("Hello", "HELLO")) // true - * console.log(caseInsensitiveEq("Hello", "World")) // false + * caseInsensitiveEq("Hello", "HELLO") // => true + * caseInsensitiveEq("Hello", "World") // => false * ``` * * @see {@link combine} @@ -499,7 +499,7 @@ export const mapInput: { * * **Example** (Comparing homogeneous tuples) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const stringTupleEq = Equivalence.Tuple([ @@ -512,13 +512,13 @@ export const mapInput: { * const tuple2 = ["hello", "world", "test"] as const * const tuple3 = ["hello", "world", "different"] as const * - * console.log(stringTupleEq(tuple1, tuple2)) // true - * console.log(stringTupleEq(tuple1, tuple3)) // false (different third element) + * stringTupleEq(tuple1, tuple2) // => true + * stringTupleEq(tuple1, tuple3) // => false * ``` * * **Example** (Comparing tuples with custom equivalences) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitive = Equivalence.mapInput( @@ -532,9 +532,7 @@ export const mapInput: { * caseInsensitive * ]) * - * console.log( - * customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) - * ) // true + * customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) // => true * ``` * * @category combinators @@ -588,19 +586,19 @@ export { * * **Example** (Comparing number arrays) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const numberArrayEq = Equivalence.Array(Equivalence.strictEqual()) * - * console.log(numberArrayEq([1, 2, 3], [1, 2, 3])) // true - * console.log(numberArrayEq([1, 2, 3], [1, 2, 4])) // false - * console.log(numberArrayEq([1, 2], [1, 2, 3])) // false (different length) + * numberArrayEq([1, 2, 3], [1, 2, 3]) // => true + * numberArrayEq([1, 2, 3], [1, 2, 4]) // => false + * numberArrayEq([1, 2], [1, 2, 3]) // => false * ``` * * **Example** (Case-insensitive string array) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const caseInsensitive = Equivalence.mapInput( @@ -609,9 +607,9 @@ export { * ) * const stringArrayEq = Equivalence.Array(caseInsensitive) * - * console.log(stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"])) // true - * console.log(stringArrayEq(["Hello"], ["Hi"])) // false - * console.log(stringArrayEq([], [])) // true (empty arrays) + * stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"]) // => true + * stringArrayEq(["Hello"], ["Hi"]) // => false + * stringArrayEq([], []) // => true * ``` * * @see {@link Tuple} @@ -640,7 +638,7 @@ export { * * **Example** (Comparing structs with different equivalences per field) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * interface Person { @@ -664,13 +662,13 @@ export { * const person2 = { name: "ALICE", age: 30, email: "ALICE@EXAMPLE.COM" } * const person3 = { name: "Alice", age: 31, email: "alice@example.com" } * - * console.log(personEq(person1, person2)) // true (case-insensitive match) - * console.log(personEq(person1, person3)) // false (different age) + * personEq(person1, person2) // => true + * personEq(person1, person3) // => false * ``` * * **Example** (Comparing specific fields) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const nameAgeEq = Equivalence.Struct({ @@ -681,7 +679,7 @@ export { * // Only compares name and age, ignores other properties * const obj1 = { name: "Alice", age: 30, extra: "ignored" } * const obj2 = { name: "Alice", age: 30, extra: "different" } - * console.log(nameAgeEq(obj1, obj2)) // true + * nameAgeEq(obj1, obj2) // => true * ``` * * @see {@link Record} @@ -721,7 +719,7 @@ export function Struct>>( * * **Example** (Defining records with string values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const stringRecordEq = Equivalence.Record(Equivalence.strictEqual()) @@ -731,14 +729,14 @@ export function Struct>>( * const record3 = { a: "hello", b: "different" } * const record4 = { a: "hello" } // missing key 'b' * - * console.log(stringRecordEq(record1, record2)) // true - * console.log(stringRecordEq(record1, record3)) // false - * console.log(stringRecordEq(record1, record4)) // false (different keys) + * stringRecordEq(record1, record2) // => true + * stringRecordEq(record1, record3) // => false + * stringRecordEq(record1, record4) // => false * ``` * * **Example** (Defining records with number values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const numberRecordEq = Equivalence.Record(Equivalence.strictEqual()) @@ -747,8 +745,8 @@ export function Struct>>( * const scores2 = { alice: 100, bob: 85 } * const scores3 = { alice: 100, bob: 90 } * - * console.log(numberRecordEq(scores1, scores2)) // true - * console.log(numberRecordEq(scores1, scores3)) // false + * numberRecordEq(scores1, scores2) // => true + * numberRecordEq(scores1, scores3) // => false * ``` * * @category combinators @@ -787,7 +785,7 @@ export function Record(value: Equivalence): Equivalence() @@ -798,8 +796,8 @@ export function Record(value: Equivalence): Equivalence true + * combined(1, 1.5) // => false * ``` * * @see {@link combine} Combine two equivalences @@ -831,7 +829,7 @@ export function makeReducer() { * * **Example** (Comparing Date values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const d1 = new Date("2020-01-01T00:00:00.000Z") @@ -840,22 +838,22 @@ export function makeReducer() { * const invalidDate1 = new Date("foo") * const invalidDate2 = new Date("bar") * - * console.log(Equivalence.Date(d1, d2)) // true - * console.log(Equivalence.Date(d1, d3)) // false - * console.log(Equivalence.Date(invalidDate1, invalidDate2)) // true - * console.log(Equivalence.Date(invalidDate1, d1)) // false + * Equivalence.Date(d1, d2) // => true + * Equivalence.Date(d1, d3) // => false + * Equivalence.Date(invalidDate1, invalidDate2) // => true + * Equivalence.Date(invalidDate1, d1) // => false * ``` * * **Example** (Comparing reference and value equality) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence } from "effect" * * const d1 = new Date(0) * const d2 = new Date(0) * - * console.log(d1 === d2) // false (different references) - * console.log(Equivalence.Date(d1, d2)) // true (same time value) + * d1 === d2 // => false + * Equivalence.Date(d1, d2) // => true * ``` * * @see {@link Number} for the numeric equivalence applied to each `Date#getTime()` result diff --git a/packages/effect/src/ErrorReporter.ts b/packages/effect/src/ErrorReporter.ts index 1fbc6ccc8..220c141e0 100644 --- a/packages/effect/src/ErrorReporter.ts +++ b/packages/effect/src/ErrorReporter.ts @@ -70,7 +70,7 @@ export const TypeId: TypeId = "~effect/ErrorReporter" * @see {@link report} for manually reporting a `Cause` * @see {@link Effect.withErrorReporting} for reporting failures from an effect * - * @category models + * @category services * @since 4.0.0 */ export interface ErrorReporter { @@ -97,17 +97,24 @@ export interface ErrorReporter { * and resolves the `ignore`, `severity`, and `attributes` annotations on * each error before invoking your callback. * - * **Example** (Forwarding errors to the console) + * **Example** (Forwarding errors to a callback) * - * ```ts - * import { ErrorReporter } from "effect" + * ```ts import.meta.vitest + * import { Effect, ErrorReporter } from "effect" * - * // Forward every failure to the console - * const consoleReporter = ErrorReporter.make( - * ({ error, severity, attributes }) => { - * console.error(`[${severity}]`, error.message, attributes) - * } + * const reports: Array<{ message: string; severity: string; attributes: object }> = [] + * const reporter = ErrorReporter.make(({ error, severity, attributes }) => { + * reports.push({ message: error.message, severity, attributes }) + * }) + * + * const program = Effect.fail(new Error("boom")).pipe( + * Effect.withErrorReporting, + * Effect.provide(ErrorReporter.layer([reporter])), + * Effect.exit * ) + * + * await Effect.runPromise(program) + * reports // => [{ message: "boom", severity: "Info", attributes: {} }] * ``` * * @see {@link layer} for registering reporters in the environment @@ -163,7 +170,7 @@ export const make = ( * Use when you need to read or replace the current set of error reporters * directly. * - * @category references + * @category services * @since 4.0.0 */ export const CurrentErrorReporters: Context.Reference> = references.CurrentErrorReporters @@ -185,33 +192,37 @@ export const CurrentErrorReporters: Context.Reference * * **Example** (Providing error reporters) * - * ```ts + * ```ts import.meta.vitest * import { Effect, ErrorReporter } from "effect" * - * const consoleReporter = ErrorReporter.make(({ error, severity }) => { - * console.error(`[${severity}]`, error.message) + * const reports: Array = [] + * const firstReporter = ErrorReporter.make(({ error, severity }) => { + * reports.push(`[${severity}] ${error.message}`) * }) - * - * const metricsReporter = ErrorReporter.make(({ severity }) => { - * // increment an error counter by severity + * const secondReporter = ErrorReporter.make(({ error, severity }) => { + * reports.push(`${severity}: ${error.message}`) * }) * * // Replace all existing reporters - * const ReporterLive = ErrorReporter.layer([ - * consoleReporter, - * metricsReporter + * const ReporterLayer = ErrorReporter.layer([ + * firstReporter, + * secondReporter * ]) * * // Add to existing reporters instead of replacing * const ReporterMerged = ErrorReporter.layer( - * [metricsReporter], + * [secondReporter], * { mergeWithExisting: true } * ) * * const program = Effect.fail("boom").pipe( * Effect.withErrorReporting, - * Effect.provide(ReporterLive) + * Effect.provide(ReporterLayer), + * Effect.exit * ) + * + * await Effect.runPromise(program) + * reports // => ["[Info] boom", "Info: boom"] * ``` * * @see {@link make} for creating an `ErrorReporter` from a callback @@ -255,18 +266,25 @@ export const layer = < * * **Example** (Reporting a cause manually) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, ErrorReporter } from "effect" * - * // Log the cause for monitoring, then continue with a fallback + * const messages: Array = [] * const program = Effect.gen(function*() { * const cause = Cause.fail("something went wrong") * yield* ErrorReporter.report(cause) * return "fallback value" * }) + * + * const reporter = ErrorReporter.make(({ error }) => messages.push(error.message)) + * const output = await Effect.runPromise( + * Effect.provide(program, ErrorReporter.layer([reporter])) + * ) + * messages // => ["something went wrong"] + * output // => "fallback value" * ``` * - * @category Reporting + * @category logging * @since 4.0.0 */ export const report = (cause: Cause.Cause): Effect.Effect => @@ -347,12 +365,14 @@ export type ignore = "~effect/ErrorReporter/ignore" * * **Example** (Marking errors as ignored) * - * ```ts + * ```ts import.meta.vitest * import { Data, ErrorReporter } from "effect" * * class NotFoundError extends Data.TaggedError("NotFoundError")<{}> { * readonly [ErrorReporter.ignore] = true * } + * + * ErrorReporter.isIgnored(new NotFoundError()) // => true * ``` * * @see {@link isIgnored} for checking whether a value carries this annotation @@ -375,7 +395,7 @@ export const ignore: ignore = "~effect/ErrorReporter/ignore" * * @see {@link ignore} for the annotation key this predicate reads * - * @category annotations + * @category predicates * @since 4.0.0 */ export const isIgnored = (u: unknown): boolean => @@ -414,12 +434,14 @@ export type severity = "~effect/ErrorReporter/severity" * * **Example** (Setting error severity annotations) * - * ```ts + * ```ts import.meta.vitest * import { Data, ErrorReporter } from "effect" * * class DeprecationWarning extends Data.TaggedError("DeprecationWarning")<{}> { * readonly [ErrorReporter.severity] = "Warn" as const * } + * + * ErrorReporter.getSeverity(new DeprecationWarning()) // => "Warn" * ``` * * @see {@link getSeverity} for reading the severity stored under this key @@ -488,7 +510,7 @@ export type attributes = "~effect/ErrorReporter/attributes" * * **Example** (Setting error attributes) * - * ```ts + * ```ts import.meta.vitest * import { Data, ErrorReporter } from "effect" * * class PaymentError extends Data.TaggedError("PaymentError")<{ @@ -498,6 +520,8 @@ export type attributes = "~effect/ErrorReporter/attributes" * orderId: this.orderId * } * } + * + * ErrorReporter.getAttributes(new PaymentError({ orderId: "order-123" })) // => { orderId: "order-123" } * ``` * * @see {@link ignore} for suppressing reports for expected object errors diff --git a/packages/effect/src/ExecutionPlan.ts b/packages/effect/src/ExecutionPlan.ts index fd00503b5..135385f76 100644 --- a/packages/effect/src/ExecutionPlan.ts +++ b/packages/effect/src/ExecutionPlan.ts @@ -10,7 +10,9 @@ * @since 3.16.0 */ import type { NonEmptyReadonlyArray } from "./Array.ts" +import type * as Cause from "./Cause.ts" import * as Context from "./Context.ts" +import type * as Duration from "./Duration.ts" import type * as Effect from "./Effect.ts" import { constant } from "./Function.ts" import * as effect from "./internal/effect.ts" @@ -66,41 +68,20 @@ export const isExecutionPlan = (u: unknown): u is ExecutionPlan => Predicat * * **Example** (Defining fallback execution steps) * - * ```ts - * import { Effect, ExecutionPlan, Schedule } from "effect" - * import type { Layer } from "effect" - * import type { LanguageModel } from "effect/unstable/ai" - * - * declare const layerBad: Layer.Layer - * declare const layerGood: Layer.Layer + * ```ts import.meta.vitest + * import { Context, ExecutionPlan } from "effect" * * const ThePlan = ExecutionPlan.make( * { - * // First try with the bad layer 2 times with a 3 second delay between attempts - * provide: layerBad, - * attempts: 2, - * schedule: Schedule.spaced(3000) - * }, - * // Then try with the bad layer 3 times with a 1 second delay between attempts - * { - * provide: layerBad, - * attempts: 3, - * schedule: Schedule.spaced(1000) + * provide: Context.empty(), + * attempts: 2 * }, - * // Finally try with the good layer. - * // - * // If `attempts` is omitted, the plan will only attempt once, unless a schedule is provided. * { - * provide: layerGood + * provide: Context.empty() * } * ) * - * declare const effect: Effect.Effect< - * void, - * never, - * LanguageModel.LanguageModel - * > - * const withPlan: Effect.Effect = Effect.withExecutionPlan(effect, ThePlan) + * ThePlan.steps.map((step) => step.attempts ?? 1) // => [2, 1] * ``` * * @category models @@ -166,41 +147,20 @@ export type ConfigBase = { * * **Example** (Creating an execution plan) * - * ```ts - * import { Effect, ExecutionPlan, Schedule } from "effect" - * import type { Layer } from "effect" - * import type { LanguageModel } from "effect/unstable/ai" - * - * declare const layerBad: Layer.Layer - * declare const layerGood: Layer.Layer + * ```ts import.meta.vitest + * import { Context, ExecutionPlan } from "effect" * * const ThePlan = ExecutionPlan.make( * { - * // First try with the bad layer 2 times with a 3 second delay between attempts - * provide: layerBad, - * attempts: 2, - * schedule: Schedule.spaced(3000) + * provide: Context.empty(), + * attempts: 2 * }, - * // Then try with the bad layer 3 times with a 1 second delay between attempts * { - * provide: layerBad, - * attempts: 3, - * schedule: Schedule.spaced(1000) - * }, - * // Finally try with the good layer. - * // - * // If `attempts` is omitted, the plan will only attempt once, unless a schedule is provided. - * { - * provide: layerGood + * provide: Context.empty() * } * ) * - * declare const effect: Effect.Effect< - * void, - * never, - * LanguageModel.LanguageModel - * > - * const withPlan: Effect.Effect = Effect.withExecutionPlan(effect, ThePlan) + * ThePlan.steps.length // => 2 * ``` * * @category constructors @@ -221,7 +181,7 @@ export const make = >( | (Steps[number]["schedule"] extends Schedule.Schedule ? R : never) }> => makeProto(steps.map((options, i) => { - if (options.attempts && options.attempts < 1) { + if (options.attempts !== undefined && options.attempts < 1) { throw new Error(`ExecutionPlan.make: step[${i}].attempts must be greater than 0`) } return { @@ -329,6 +289,9 @@ const Proto: Omit, "steps"> = { return effect.contextWith((context: Context.Context) => effect.succeed(makeProto(self.steps.map((step) => ({ ...step, + while: step.while + ? (input: any) => effect.provideContext(step.while!(input), context) + : undefined, provide: Layer.isLayer(step.provide) ? Layer.provide(step.provide, Layer.succeedContext(context)) : step.provide @@ -405,7 +368,7 @@ export interface Metadata { * Use to read the active plan step and attempt while code is running under an * execution plan. * - * @category metadata + * @category services * @since 4.0.0 */ export const CurrentMetadata = Context.Reference("effect/ExecutionPlan/CurrentMetadata", { @@ -414,3 +377,78 @@ export const CurrentMetadata = Context.Reference("effect/ExecutionPlan stepIndex: 0 }) }) + +/** + * Lifecycle event emitted before an execution-plan attempt runs. + * + * **Details** + * + * `attempt` is the cumulative 1-based attempt number across all steps and + * matches `CurrentMetadata.attempt` for the same attempt. `stepAttempt` is the + * 1-based attempt number within the current step, and `stepIndex` is the + * 0-based index of the step being attempted. + * + * @category models + * @since 4.0.0 + */ +export interface AttemptStart { + readonly _tag: "AttemptStart" + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number +} + +/** + * Lifecycle event emitted when an execution-plan attempt succeeds. + * + * **Details** + * + * A successful attempt completes the plan, so this is always the final event. + * `duration` is the elapsed time of the attempt. + * + * @category models + * @since 4.0.0 + */ +export interface AttemptSuccess { + readonly _tag: "AttemptSuccess" + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number + readonly duration: Duration.Duration +} + +/** + * Lifecycle event emitted when an execution-plan attempt fails. + * + * **Details** + * + * `cause` holds the full failure cause, so defects and interruption are + * reported as well as expected errors. Whether the plan retries or fails over + * afterwards is decided by the step's `attempts`, `while`, and `schedule`; a + * following `AttemptStart` indicates another attempt was made. + * + * @category models + * @since 4.0.0 + */ +export interface AttemptFailure { + readonly _tag: "AttemptFailure" + readonly attempt: number + readonly stepAttempt: number + readonly stepIndex: number + readonly duration: Duration.Duration + readonly cause: Cause.Cause +} + +/** + * Union of the lifecycle events emitted while an execution plan runs. + * + * **Details** + * + * Every `AttemptStart` is followed by exactly one terminal event, either + * `AttemptSuccess` or `AttemptFailure`. An interrupted attempt emits + * `AttemptFailure` with the interruption cause. + * + * @category models + * @since 4.0.0 + */ +export type Event = AttemptStart | AttemptSuccess | AttemptFailure diff --git a/packages/effect/src/Exit.ts b/packages/effect/src/Exit.ts index d3e45bdad..889d81d48 100644 --- a/packages/effect/src/Exit.ts +++ b/packages/effect/src/Exit.ts @@ -37,16 +37,16 @@ const TypeId = core.ExitTypeId * * **Example** (Pattern matching on an Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * * const success: Exit.Exit = Exit.succeed(42) * const failure: Exit.Exit = Exit.fail("error") * - * const result = Exit.match(success, { + * Exit.match(success, { * onSuccess: (value) => `Got value: ${value}`, * onFailure: (cause) => `Got error: ${cause}` - * }) + * }) // => "Got value: 42" * ``` * * @see {@link Success} for the success case @@ -99,14 +99,13 @@ export declare namespace Exit { * * **Example** (Accessing the success value) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * * const success = Exit.succeed(42) * * if (Exit.isSuccess(success)) { - * console.log(success._tag) // "Success" - * console.log(success.value) // 42 + * success.value // => 42 * } * ``` * @@ -136,14 +135,13 @@ export interface Success extends Exit.Proto { * * **Example** (Accessing the failure cause) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" * * const failure = Exit.fail("something went wrong") * * if (Exit.isFailure(failure)) { - * console.log(failure._tag) // "Failure" - * console.log(failure.cause) // Cause representing the error + * failure.cause // => Cause.fail("something went wrong") * } * ``` * @@ -173,12 +171,12 @@ export interface Failure extends Exit.Proto { * * **Example** (Checking if a value is an Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.isExit(Exit.succeed(42))) // true - * console.log(Exit.isExit(Exit.fail("err"))) // true - * console.log(Exit.isExit("not an exit")) // false + * Exit.isExit(Exit.succeed(42)) // => true + * Exit.isExit(Exit.fail("err")) // => true + * Exit.isExit("not an exit") // => false * ``` * * @see {@link isSuccess} to check for a successful Exit @@ -203,11 +201,10 @@ export const isExit: (u: unknown) => u is Exit = core.isExit * * **Example** (Creating a successful Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.succeed(42) - * console.log(Exit.isSuccess(exit)) // true + * Exit.succeed(42) // => Exit.succeed(42) * ``` * * @see {@link fail} to create a failed Exit @@ -234,12 +231,10 @@ export const succeed: (a: A) => Exit = core.exitSucceed * * **Example** (Creating a failed Exit from a Cause) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Exit } from "effect" * - * const cause = Cause.fail("Something went wrong") - * const exit = Exit.failCause(cause) - * console.log(Exit.isFailure(exit)) // true + * Exit.failCause(Cause.fail("Something went wrong")) // => Exit.fail("Something went wrong") * ``` * * @see {@link fail} to create a Failure from a plain error value @@ -265,11 +260,10 @@ export const failCause: (cause: Cause.Cause) => Exit = core.exit * * **Example** (Creating a failed Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.fail("Something went wrong") - * console.log(Exit.isFailure(exit)) // true + * Exit.fail("Something went wrong") // => Exit.fail("Something went wrong") * ``` * * @see {@link succeed} to create a successful Exit @@ -298,11 +292,10 @@ export const fail: (e: E) => Exit = core.exitFail * * **Example** (Creating a defect Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.die(new Error("Unexpected error")) - * console.log(Exit.isFailure(exit)) // true + * Exit.die("Unexpected error") // => Exit.die("Unexpected error") * ``` * * @see {@link fail} to create a Failure from a typed error @@ -327,12 +320,10 @@ export const die: (defect: unknown) => Exit = core.exitDie * * **Example** (Creating an interruption Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.interrupt(123) - * console.log(Exit.isFailure(exit)) // true - * console.log(Exit.hasInterrupts(exit)) // true + * Exit.interrupt(123) // => Exit.interrupt(123) * ``` * * @see {@link hasInterrupts} to check whether an Exit contains interruptions @@ -358,11 +349,10 @@ export { * * **Example** (Referencing the void Exit) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.void - * console.log(Exit.isSuccess(exit)) // true + * Exit.void // => Exit.succeed(undefined) * ``` * * @see {@link succeed} to create a success with a specific value @@ -384,13 +374,13 @@ export { * * **Example** (Narrowing to success) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * * const exit = Exit.succeed(42) * * if (Exit.isSuccess(exit)) { - * console.log(exit.value) // 42 + * exit.value // => 42 * } * ``` * @@ -412,13 +402,13 @@ export const isSuccess: (self: Exit) => self is Success = effe * * **Example** (Narrowing to failure) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Cause, Exit } from "effect" * * const exit = Exit.fail("error") * * if (Exit.isFailure(exit)) { - * console.log(exit.cause) + * exit.cause // => Cause.fail("error") * } * ``` * @@ -444,12 +434,12 @@ export const isFailure: (self: Exit) => self is Failure = effe * * **Example** (Checking for typed errors) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.hasFails(Exit.fail("err"))) // true - * console.log(Exit.hasFails(Exit.die(new Error("bug")))) // false - * console.log(Exit.hasFails(Exit.succeed(42))) // false + * Exit.hasFails(Exit.fail("err")) // => true + * Exit.hasFails(Exit.die("bug")) // => false + * Exit.hasFails(Exit.succeed(42)) // => false * ``` * * @see {@link hasDies} to check for defects @@ -474,12 +464,12 @@ export const hasFails: (self: Exit) => self is Failure = effec * * **Example** (Checking for defects) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.hasDies(Exit.die(new Error("bug")))) // true - * console.log(Exit.hasDies(Exit.fail("err"))) // false - * console.log(Exit.hasDies(Exit.succeed(42))) // false + * Exit.hasDies(Exit.die("bug")) // => true + * Exit.hasDies(Exit.fail("err")) // => false + * Exit.hasDies(Exit.succeed(42)) // => false * ``` * * @see {@link hasFails} to check for typed errors @@ -504,12 +494,12 @@ export const hasDies: (self: Exit) => self is Failure = effect * * **Example** (Checking for interruptions) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * console.log(Exit.hasInterrupts(Exit.interrupt(1))) // true - * console.log(Exit.hasInterrupts(Exit.fail("err"))) // false - * console.log(Exit.hasInterrupts(Exit.succeed(42))) // false + * Exit.hasInterrupts(Exit.interrupt(1)) // => true + * Exit.hasInterrupts(Exit.fail("err")) // => false + * Exit.hasInterrupts(Exit.succeed(42)) // => false * ``` * * @see {@link hasFails} to check for typed errors @@ -540,13 +530,10 @@ export const hasInterrupts: (self: Exit) => self is Failure = * * **Example** (Filtering for success) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Result } from "effect" * - * const exit = Exit.succeed(42) - * const result = Exit.filterSuccess(exit) - * - * console.log(Result.isSuccess(result)) // true + * Exit.filterSuccess(Exit.succeed(42)) // => Result.succeed(Exit.succeed(42)) * ``` * * @see {@link filterFailure} for the inverse @@ -580,13 +567,10 @@ export const filterSuccess: ( * * **Example** (Filtering for the value) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Result } from "effect" * - * const exit = Exit.succeed(42) - * const result = Exit.filterValue(exit) - * - * console.log(Result.isSuccess(result) && result.success) // 42 + * Exit.filterValue(Exit.succeed(42)) // => Result.succeed(42) * ``` * * @see {@link filterSuccess} to get the full Success object @@ -617,13 +601,10 @@ export const filterValue: (self: Exit) => Result.Result Result.succeed(Exit.fail("err")) * ``` * * @see {@link filterSuccess} for the inverse @@ -655,13 +636,10 @@ export const filterFailure: (self: Exit) => Result.Result Result.succeed(Cause.fail("err")) * ``` * * @see {@link filterFailure} to get the full Failure object @@ -692,16 +670,11 @@ export const filterCause: (self: Exit) => Result.Result Result.succeed("not found") + * Exit.findError(Exit.die("bug")) // => Result.fail(Exit.die("bug")) * ``` * * @see {@link findErrorOption} to get the error as an Option instead @@ -732,16 +705,11 @@ export const findError: (input: Exit) => Result.Result * * **Example** (Finding the first defect) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Result } from "effect" * - * const exit = Exit.die("boom") - * const result = Exit.findDefect(exit) - * console.log(Result.isSuccess(result) && result.success) // "boom" - * - * const typed = Exit.fail("err") - * const noDefect = Exit.findDefect(typed) - * console.log(Result.isFailure(noDefect)) // true + * Exit.findDefect(Exit.die("boom")) // => Result.succeed("boom") + * Exit.findDefect(Exit.fail("err")) // => Result.fail(Exit.fail("err")) * ``` * * @see {@link findError} to find typed errors instead @@ -767,16 +735,13 @@ export const findDefect: (input: Exit) => Result.Result `Got: ${value}`, * onFailure: () => "Failed" - * }) - * console.log(result) // "Got: 42" + * }) // => "Got: 42" * ``` * * @see {@link isSuccess} and {@link isFailure} for simple boolean checks @@ -813,12 +778,10 @@ export const match: { * * **Example** (Mapping over a success) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.succeed(21) - * const doubled = Exit.map(exit, (x) => x * 2) - * console.log(Exit.isSuccess(doubled) && doubled.value) // 42 + * Exit.map(Exit.succeed(21), (x) => x * 2) // => Exit.succeed(42) * ``` * * @see {@link mapError} to transform the error @@ -852,14 +815,10 @@ export const map: { * * **Example** (Mapping over an error) * - * ```ts - * import { Data, Exit } from "effect" - * - * class ExitError extends Data.TaggedError("ExitError")<{ readonly input: string }> {} + * ```ts import.meta.vitest + * import { Exit } from "effect" * - * const exit = Exit.fail("bad input") - * const mapped = Exit.mapError(exit, (e) => new ExitError({ input: e })) - * console.log(Exit.isFailure(mapped)) // true + * Exit.mapError(Exit.fail("bad input"), (error) => error.toUpperCase()) // => Exit.fail("BAD INPUT") * ``` * * @see {@link map} to transform the success value @@ -893,17 +852,13 @@ export const mapError: { * * **Example** (Mapping both channels) * - * ```ts - * import { Data, Exit } from "effect" - * - * class ExitError extends Data.TaggedError("ExitError")<{ readonly input: string }> {} + * ```ts import.meta.vitest + * import { Exit } from "effect" * - * const exit = Exit.succeed(42) - * const mapped = Exit.mapBoth(exit, { + * Exit.mapBoth(Exit.succeed(42), { * onSuccess: (x) => String(x), - * onFailure: (e: string) => new ExitError({ input: e }) - * }) - * console.log(Exit.isSuccess(mapped) && mapped.value) // "42" + * onFailure: (error: string) => error.toUpperCase() + * }) // => Exit.succeed("42") * ``` * * @see {@link map} to transform only the success value @@ -938,12 +893,10 @@ export const mapBoth: { * * **Example** (Discarding the success value) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exit = Exit.succeed(42) - * const voided = Exit.asVoid(exit) - * console.log(Exit.isSuccess(voided)) // true + * Exit.asVoid(Exit.succeed(42)) // => Exit.succeed(undefined) * ``` * * @see {@link void_ void} for a pre-allocated void success @@ -971,14 +924,11 @@ export const asVoid: (self: Exit) => Exit = effect.exitAsVo * * **Example** (Combining exits) * - * ```ts + * ```ts import.meta.vitest * import { Exit } from "effect" * - * const exits = [Exit.succeed(1), Exit.succeed(2), Exit.succeed(3)] - * console.log(Exit.isSuccess(Exit.asVoidAll(exits))) // true - * - * const mixed = [Exit.succeed(1), Exit.fail("err"), Exit.succeed(3)] - * console.log(Exit.isFailure(Exit.asVoidAll(mixed))) // true + * Exit.asVoidAll([Exit.succeed(1), Exit.succeed(2), Exit.succeed(3)]) // => Exit.succeed(undefined) + * Exit.asVoidAll([Exit.succeed(1), Exit.fail("err"), Exit.succeed(3)]) // => Exit.fail("err") * ``` * * @see {@link asVoid} to discard the value of a single Exit @@ -1004,17 +954,17 @@ export const asVoidAll: >>( * * **Example** (Getting the success value) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Exit, Option } from "effect" * - * console.log(Exit.getSuccess(Exit.succeed(42))) // { _tag: "Some", value: 42 } - * console.log(Exit.getSuccess(Exit.fail("err"))) // { _tag: "None" } + * Exit.getSuccess(Exit.succeed(42)) // => Option.some(42) + * Exit.getSuccess(Exit.fail("err")) // => Option.none() * ``` * * @see {@link getCause} to extract the Cause of a failure * @see {@link filterValue} for filter-pipeline usage * - * @category accessors + * @category getters * @since 4.0.0 */ export const getSuccess: (self: Exit) => Option = effect.exitGetSuccess @@ -1033,17 +983,17 @@ export const getSuccess: (self: Exit) => Option = effect.exitGetS * * **Example** (Getting the failure cause) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Cause, Exit, Option } from "effect" * - * console.log(Exit.getCause(Exit.fail("err"))) // { _tag: "Some", value: ... } - * console.log(Exit.getCause(Exit.succeed(42))) // { _tag: "None" } + * Exit.getCause(Exit.fail("err")) // => Option.some(Cause.fail("err")) + * Exit.getCause(Exit.succeed(42)) // => Option.none() * ``` * * @see {@link getSuccess} to extract the success value * @see {@link filterCause} for filter-pipeline usage * - * @category accessors + * @category getters * @since 4.0.0 */ export const getCause: (self: Exit) => Option> = effect.exitGetCause @@ -1068,18 +1018,18 @@ export const getCause: (self: Exit) => Option> = effe * * **Example** (Getting the first error) * - * ```ts - * import { Exit } from "effect" + * ```ts import.meta.vitest + * import { Exit, Option } from "effect" * - * console.log(Exit.findErrorOption(Exit.fail("err"))) // { _tag: "Some", value: "err" } - * console.log(Exit.findErrorOption(Exit.die(new Error("bug")))) // { _tag: "None" } - * console.log(Exit.findErrorOption(Exit.succeed(42))) // { _tag: "None" } + * Exit.findErrorOption(Exit.fail("err")) // => Option.some("err") + * Exit.findErrorOption(Exit.die("bug")) // => Option.none() + * Exit.findErrorOption(Exit.succeed(42)) // => Option.none() * ``` * * @see {@link findError} for filter-pipeline usage * @see {@link getCause} to get the full Cause as an Option * - * @category accessors + * @category getters * @since 4.0.0 */ export const findErrorOption: (self: Exit) => Option = effect.exitFindErrorOption diff --git a/packages/effect/src/Fiber.ts b/packages/effect/src/Fiber.ts index ed60b91e9..4eae1625c 100644 --- a/packages/effect/src/Fiber.ts +++ b/packages/effect/src/Fiber.ts @@ -12,17 +12,17 @@ import type * as Context from "./Context.ts" import type { Effect } from "./Effect.ts" import type { Exit } from "./Exit.ts" import * as effect from "./internal/effect.ts" -import { version } from "./internal/version.ts" import type { LogLevel } from "./LogLevel.ts" +import type { FiberRuntimeMetricsService } from "./Metric.ts" import type { Pipeable } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" import type { StackFrame } from "./References.ts" import type { Scheduler, SchedulerDispatcher } from "./Scheduler.ts" import type { Scope } from "./Scope.ts" -import type { AnySpan } from "./Tracer.ts" +import type { AnySpan, Tracer } from "./Tracer.ts" import type { Covariant } from "./Types.ts" -const TypeId = `~effect/Fiber/${version}` +const TypeId = "~effect/Fiber" /** * A runtime fiber is a lightweight thread that executes Effects. Fibers are @@ -49,8 +49,8 @@ const TypeId = `~effect/Fiber/${version}` * * **Example** (Awaiting a forked fiber) * - * ```ts - * import { Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" * * const program = Effect.gen(function*() { * // Fork an effect to run in a new fiber @@ -58,10 +58,11 @@ const TypeId = `~effect/Fiber/${version}` * * // Wait for the fiber to complete and get its result * const result = yield* Fiber.await(fiber) - * console.log(result) // Exit.succeed(42) - * * return result * }) + * + * const actual = await Effect.runPromise(program) + * actual // => Exit.succeed(42) * ``` * * @category models @@ -75,14 +76,8 @@ export interface Fiber extends Pipeable { readonly getRef: (ref: Context.Reference) => A readonly context: Context.Context setContext(context: Context.Context): void - readonly currentScheduler: Scheduler + readonly cache: Fiber.Cache readonly currentDispatcher: SchedulerDispatcher - readonly currentSpan?: AnySpan | undefined - readonly currentLogLevel: LogLevel - readonly minimumLogLevel: LogLevel - readonly currentStackFrame?: StackFrame | undefined - readonly maxOpsBeforeYield: number - readonly currentPreventYield: boolean readonly addObserver: (cb: (exit: Exit) => void) => () => void readonly interruptUnsafe: ( fiberId?: number | undefined, @@ -106,7 +101,7 @@ export interface Fiber extends Pipeable { * * **Example** (Working with fiber types) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -116,13 +111,12 @@ export interface Fiber extends Pipeable { * // Use namespace types for variance * const typedFiber: Fiber.Fiber = fiber * - * // Access fiber properties - * console.log(`Fiber ID: ${fiber.id}`) - * * // Join the fiber - * const result = yield* Fiber.join(fiber) - * return result // 42 + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 * ``` * * @since 2.0.0 @@ -139,12 +133,14 @@ export declare namespace Fiber { * * **Example** (Upcasting fibers safely) * - * ```ts - * import type { Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * * // Variance allows safe subtyping - * declare const fiber: Fiber.Fiber + * const fiber: Fiber.Fiber = Effect.runFork(Effect.succeed(1)) * const upcast: Fiber.Fiber = fiber + * const actual = await Effect.runPromise(Fiber.join(upcast)) + * actual // => 1 * ``` * * @category models @@ -154,6 +150,36 @@ export declare namespace Fiber { readonly _A: Covariant readonly _E: Covariant } + + /** + * Context-derived values cached for the fiber's current `Context`. + * + * **When to use** + * + * Use to read runtime services resolved from the fiber's context, such as + * the scheduler, current span, or log levels. + * + * **Details** + * + * The cache object is computed once per context cache root and shared by + * every fiber running with that root, so it must be treated as immutable. + * + * @category models + * @since 4.0.0 + */ + export interface Cache { + readonly scheduler: Scheduler + readonly tracer: Tracer | undefined + readonly tracerContext: Tracer["context"] | undefined + readonly tracerEnabled: boolean + readonly span: AnySpan | undefined + readonly logLevel: LogLevel + readonly minimumLogLevel: LogLevel + readonly stackFrame: StackFrame | undefined + readonly runtimeMetrics: FiberRuntimeMetricsService | undefined + readonly maxOpsBeforeYield: number + readonly preventYield: boolean + } } const await_: (self: Fiber) => Effect> = effect.fiberAwait @@ -178,14 +204,16 @@ export { * * **Example** (Awaiting a fiber exit) * - * ```ts - * import { Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" * * const program = Effect.gen(function*() { * const fiber = yield* Effect.forkChild(Effect.succeed(42)) - * const exit = yield* Fiber.await(fiber) - * console.log(exit) // Exit.succeed(42) + * return yield* Fiber.await(fiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => Exit.succeed(42) * ``` * * @category combinators @@ -213,15 +241,17 @@ export { * * **Example** (Awaiting multiple fiber exits) * - * ```ts - * import { Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Fiber } from "effect" * * const program = Effect.gen(function*() { * const fiber1 = yield* Effect.forkChild(Effect.succeed(1)) * const fiber2 = yield* Effect.forkChild(Effect.succeed(2)) - * const exits = yield* Fiber.awaitAll([fiber1, fiber2]) - * console.log(exits) // [Exit.succeed(1), Exit.succeed(2)] + * return yield* Fiber.awaitAll([fiber1, fiber2]) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [Exit.succeed(1), Exit.succeed(2)] * ``` * * @category combinators @@ -254,14 +284,16 @@ export const awaitAll: >( * * **Example** (Joining a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { * const fiber = yield* Effect.forkChild(Effect.succeed(42)) - * const result = yield* Fiber.join(fiber) - * console.log(result) // 42 + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => 42 * ``` * * @see {@link await_ await} for inspecting the fiber outcome as an Exit @@ -302,7 +334,7 @@ export const joinAll: >>( A, A extends Iterable> ? _A : never >, - A extends Fiber ? _E : never + A extends Iterable> ? _E : never > = effect.fiberJoinAll /** @@ -325,7 +357,7 @@ export const joinAll: >>( * * **Example** (Interrupting a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -333,8 +365,9 @@ export const joinAll: >>( * Effect.delay("1 second")(Effect.succeed(42)) * ) * yield* Fiber.interrupt(fiber) - * console.log("Fiber interrupted") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interruptAs} for specifying the interrupting fiber ID @@ -365,7 +398,7 @@ export const interrupt: (self: Fiber) => Effect = effect.fiber * * **Example** (Interrupting a fiber as another fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -375,8 +408,9 @@ export const interrupt: (self: Fiber) => Effect = effect.fiber * * // Interrupt the fiber, specifying fiber ID 123 as the interruptor * yield* Fiber.interruptAs(targetFiber, 123) - * console.log("Fiber interrupted by fiber #123") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interrupt} for using the current fiber as the interruptor @@ -417,41 +451,17 @@ export const interruptAs: { * * **Example** (Interrupting multiple fibers) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { - * // Create multiple long-running fibers - * const fiber1 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("5 seconds") - * yield* Console.log("Task 1 completed") - * return "result1" - * }) - * ) - * - * const fiber2 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("3 seconds") - * yield* Console.log("Task 2 completed") - * return "result2" - * }) - * ) - * - * const fiber3 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("4 seconds") - * yield* Console.log("Task 3 completed") - * return "result3" - * }) - * ) - * - * // Wait a bit, then interrupt all fibers - * yield* Effect.sleep("1 second") - * yield* Console.log("Interrupting all fibers...") + * const fiber1 = yield* Effect.forkChild(Effect.never) + * const fiber2 = yield* Effect.forkChild(Effect.never) + * const fiber3 = yield* Effect.forkChild(Effect.never) * yield* Fiber.interruptAll([fiber1, fiber2, fiber3]) - * yield* Console.log("All fibers have been interrupted") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interruptAllAs} for specifying the interrupting fiber ID @@ -485,36 +495,20 @@ export const interruptAll: >>( * * **Example** (Interrupting multiple fibers as another fiber) * - * ```ts - * import { Console, Effect, Fiber } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { * // Create a controlling fiber * const controllerFiber = yield* Effect.forkChild(Effect.succeed("controller")) * - * // Create multiple worker fibers - * const worker1 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("5 seconds") - * yield* Console.log("Worker 1 completed") - * return "worker1" - * }) - * ) - * - * const worker2 = yield* Effect.forkChild( - * Effect.gen(function*() { - * yield* Effect.sleep("3 seconds") - * yield* Console.log("Worker 2 completed") - * return "worker2" - * }) - * ) + * const worker1 = yield* Effect.forkChild(Effect.never) + * const worker2 = yield* Effect.forkChild(Effect.never) * - * // Interrupt all workers using the controller fiber's ID - * yield* Effect.sleep("1 second") - * yield* Console.log("Interrupting workers from controller...") * yield* Fiber.interruptAllAs([worker1, worker2], controllerFiber.id) - * yield* Console.log("All workers interrupted by controller") * }) + * + * await Effect.runPromise(program) * ``` * * @see {@link interruptAll} for using the current fiber as the interruptor @@ -543,7 +537,7 @@ export const interruptAllAs: { * * **Example** (Checking for fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { @@ -551,18 +545,11 @@ export const interruptAllAs: { * const fiber = yield* Effect.forkChild(Effect.succeed(42)) * * // Test if values are fibers - * console.log(Fiber.isFiber(fiber)) // true - * console.log(Fiber.isFiber("hello")) // false - * console.log(Fiber.isFiber(42)) // false - * console.log(Fiber.isFiber(null)) // false - * - * // Use as a type guard - * const maybeValue: unknown = fiber - * if (Fiber.isFiber(maybeValue)) { - * // TypeScript knows maybeValue is a Fiber here - * console.log(`Fiber ID: ${maybeValue.id}`) - * } + * return [Fiber.isFiber(fiber), Fiber.isFiber("hello"), Fiber.isFiber(42), Fiber.isFiber(null)] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [true, false, false, false] * ``` * * @category guards @@ -588,18 +575,19 @@ export const isFiber = ( * * **Example** (Getting the current fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber } from "effect" * * const program = Effect.gen(function*() { * const current = Fiber.getCurrent() - * if (current) { - * console.log(`Current fiber ID: ${current.id}`) - * } + * return current !== undefined * }) + * + * const actual = await Effect.runPromise(program) + * actual // => true * ``` * - * @category accessors + * @category getters * @since 4.0.0 */ export const getCurrent: () => Fiber | undefined = effect.getCurrentFiber diff --git a/packages/effect/src/FiberHandle.ts b/packages/effect/src/FiberHandle.ts index fe1b3f834..2317b7a42 100644 --- a/packages/effect/src/FiberHandle.ts +++ b/packages/effect/src/FiberHandle.ts @@ -35,18 +35,20 @@ const TypeId = "~effect/FiberHandle" * * **Example** (Managing a single fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * // Create a FiberHandle that can hold fibers producing strings * const handle = yield* FiberHandle.make() * * // The handle can store and manage a single fiber * const fiber = yield* FiberHandle.run(handle, Effect.succeed("hello")) - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category models @@ -69,18 +71,20 @@ export interface FiberHandle extends Pipeable, * * **Example** (Checking fiber handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * - * console.log(FiberHandle.isFiberHandle(handle)) // true - * console.log(FiberHandle.isFiberHandle("not a handle")) // false + * return [FiberHandle.isFiberHandle(handle), FiberHandle.isFiberHandle("not a handle")] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isFiberHandle = (u: unknown): u is FiberHandle => Predicate.hasProperty(u, TypeId) @@ -115,10 +119,10 @@ const makeUnsafe = (): FiberHandle => { * * **Example** (Creating a scoped fiber handle) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * * // run some effects @@ -126,10 +130,14 @@ const makeUnsafe = (): FiberHandle => { * // this will interrupt the previous fiber * yield* FiberHandle.run(handle, Effect.never) * - * yield* Effect.sleep(1000) + * yield* Effect.yieldNow + * return handle.state._tag === "Open" && handle.state.fiber !== undefined * }).pipe( * Effect.scoped // The fiber will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(program) + * actual // => true * ``` * * @category constructors @@ -162,20 +170,24 @@ export const make = (): Effect.Effect() * * // Run effects and get fibers back - * const fiberA = run(Effect.succeed("first")) + * const fiberA = run(Effect.never) * const fiberB = run(Effect.succeed("second")) * * // The second fiber will interrupt the first * const resultA = yield* Fiber.await(fiberA) * const resultB = yield* Fiber.await(fiberB) + * return [resultA, resultB] * }).pipe(Effect.scoped) + * + * const actual = await Effect.runPromise(program) + * actual // => [Exit.failCause(Cause.interrupt(-1)), Exit.succeed("second")] * ``` * * @category constructors @@ -218,17 +230,19 @@ export const makeRuntime = (): Effect.Effect< * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const run = yield* FiberHandle.makeRuntimePromise() * * // Run effects and get promises back * const promise = run(Effect.succeed("hello")) - * const result = yield* Effect.promise(() => promise) - * console.log(result) // "hello" + * return yield* Effect.promise(() => promise) * }).pipe(Effect.scoped) + * + * const actual = await Effect.runPromise(program) + * actual // => "hello" * ``` * * @category constructors @@ -269,10 +283,10 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * * **Example** (Setting a fiber unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const fiber = Effect.runFork(Effect.succeed("hello")) * @@ -280,9 +294,11 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * FiberHandle.setUnsafe(handle, fiber) * * // The fiber is now managed by the handle - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category combinators @@ -316,10 +332,10 @@ export const setUnsafe: { fiber.interruptUnsafe(internalFiberId) return } else if (self.state.fiber !== undefined) { - if (options?.onlyIfMissing === true) { - fiber.interruptUnsafe(internalFiberId) + if (self.state.fiber === fiber) { return - } else if (self.state.fiber === fiber) { + } else if (options?.onlyIfMissing === true) { + fiber.interruptUnsafe(internalFiberId) return } self.state.fiber.interruptUnsafe(internalFiberId) @@ -355,10 +371,10 @@ export const setUnsafe: { * * **Example** (Setting a fiber safely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const fiber = Effect.runFork(Effect.succeed("hello")) * @@ -366,9 +382,11 @@ export const setUnsafe: { * yield* FiberHandle.set(handle, fiber) * * // The fiber is now managed by the handle - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category combinators @@ -415,21 +433,23 @@ export const set: { * * **Example** (Reading the current fiber unsafely) * - * ```ts - * import { Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * * // No fiber initially * const emptyFiber = FiberHandle.getUnsafe(handle) - * console.log(emptyFiber._tag === "None") // true * * // Add a fiber - * yield* FiberHandle.run(handle, Effect.succeed("hello")) + * yield* FiberHandle.run(handle, Effect.never) * const fiber = FiberHandle.getUnsafe(handle) - * console.log(fiber._tag === "Some") // true + * return [emptyFiber, Option.map(fiber, () => true)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [Option.none(), Option.some(true)] * ``` * * @category combinators @@ -444,22 +464,22 @@ export function getUnsafe(self: FiberHandle): Option.Option true) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some(true) * ``` * * @category combinators @@ -475,10 +495,10 @@ export function get(self: FiberHandle): Effect.Effect(self: FiberHandle): Effect.Effect Option.none() * ``` * * @category combinators @@ -501,10 +523,11 @@ export const clear = (self: FiberHandle): Effect.Effect => if (self.state._tag === "Closed" || self.state.fiber === undefined) { return Effect.void } + const fiber = self.state.fiber return Effect.andThen( - restore(Fiber.interruptAs(self.state.fiber, internalFiberId)), + restore(Fiber.interruptAs(fiber, internalFiberId)), Effect.sync(() => { - if (self.state._tag === "Open") { + if (self.state._tag === "Open" && self.state.fiber === fiber) { self.state.fiber = undefined } }) @@ -532,22 +555,24 @@ const constInterruptedFiber = (function() { * * **Example** (Running an effect in a fiber handle) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * * // Run an effect and get the fiber * const fiber = yield* FiberHandle.run(handle, Effect.succeed("hello")) - * const result = yield* Fiber.await(fiber) - * console.log(result) // "hello" + * const result = yield* Fiber.join(fiber) * * // Running another effect will interrupt the previous one * const fiber2 = yield* FiberHandle.run(handle, Effect.succeed("world")) - * const result2 = yield* Fiber.await(fiber2) - * console.log(result2) // "world" + * const result2 = yield* Fiber.join(fiber2) + * return [result, result2] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category combinators @@ -611,28 +636,32 @@ const runImpl = ( * * **Example** (Capturing a runtime for fiber handles) * - * ```ts - * import { Context, Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberHandle } from "effect" * - * interface Users { - * readonly _: unique symbol - * } - * const Users = Context.Service> - * }>("Users") + * class Users extends Context.Service> + * }>()("Users") {} * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const run = yield* FiberHandle.runtime(handle)() * * // run an effect and set the fiber in the handle - * run(Effect.andThen(Users, (_) => _.getAll)) + * const fiberA = run(Effect.andThen(Users, (_) => _.getAll)) * * // this will interrupt the previous fiber - * run(Effect.andThen(Users, (_) => _.getAll)) + * const fiberB = run(Effect.andThen(Users, (_) => _.getAll)) + * yield* Fiber.await(fiberA) + * return (yield* Fiber.join(fiberB)).length * }).pipe( * Effect.scoped // The fiber will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(Effect.provideService(program, Users, { + * getAll: Effect.succeed([]) + * })) + * actual // => 0 * ``` * * @category combinators @@ -694,18 +723,20 @@ export const runtime: ( * * **Example** (Capturing a runtime for promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * const runPromise = yield* FiberHandle.runtimePromise(handle)() * * // Run an effect and get a promise * const promise = runPromise(Effect.succeed("hello")) - * const result = yield* Effect.promise(() => promise) - * console.log(result) // "hello" + * return yield* Effect.promise(() => promise) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "hello" * ``` * * @category combinators @@ -764,16 +795,19 @@ export const runtimePromise = (self: FiberHandle): () => * * **Example** (Propagating fiber failures) * - * ```ts - * import { Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, FiberHandle } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * yield* FiberHandle.set(handle, Effect.runFork(Effect.fail("error"))) * * // parent fiber will fail with "error" * yield* FiberHandle.join(handle) * }) + * + * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program))) + * actual // => Exit.fail("error") * ``` * * @category combinators @@ -787,20 +821,22 @@ export const join = (self: FiberHandle): Effect.Effect => * * **Example** (Waiting for a fiber to complete) * - * ```ts - * import { Effect, FiberHandle } from "effect" + * ```ts import.meta.vitest + * import { Effect, FiberHandle, Option } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const handle = yield* FiberHandle.make() * - * // Start a long-running effect - * yield* FiberHandle.run(handle, Effect.sleep(1000)) + * yield* FiberHandle.run(handle, Effect.yieldNow) * * // Wait for the fiber to complete * yield* FiberHandle.awaitEmpty(handle) * - * console.log("Fiber completed") + * return yield* FiberHandle.get(handle) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.none() * ``` * * @category combinators diff --git a/packages/effect/src/FiberMap.ts b/packages/effect/src/FiberMap.ts index 968413277..a8b7f1fbe 100644 --- a/packages/effect/src/FiberMap.ts +++ b/packages/effect/src/FiberMap.ts @@ -35,7 +35,7 @@ const TypeId = "~effect/FiberMap" * * **Example** (Managing fibers in a map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * // Create a FiberMap with string keys @@ -47,9 +47,11 @@ const TypeId = "~effect/FiberMap" * yield* FiberMap.run(map, "task2", Effect.never) * * // Get the size of the map - * const size = yield* FiberMap.size(map) - * console.log(size) // 2 + * return yield* FiberMap.size(map) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 2 * ``` * * @category models @@ -77,19 +79,20 @@ export interface FiberMap * * **Example** (Checking if a value is a FiberMap) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * - * console.log(FiberMap.isFiberMap(map)) // true - * console.log(FiberMap.isFiberMap({})) // false - * console.log(FiberMap.isFiberMap(null)) // false + * return [FiberMap.isFiberMap(map), FiberMap.isFiberMap({}), FiberMap.isFiberMap(null)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false, false] * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isFiberMap = (u: unknown): u is FiberMap => Predicate.hasProperty(u, TypeId) @@ -133,20 +136,24 @@ const makeUnsafe = ( * * **Example** (Creating a scoped FiberMap) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * * // run some effects and add the fibers to the map * yield* FiberMap.run(map, "fiber a", Effect.never) * yield* FiberMap.run(map, "fiber b", Effect.never) * - * yield* Effect.sleep(1000) + * yield* Effect.yieldNow + * return yield* FiberMap.size(map) * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(program) + * actual // => 2 * ``` * * @category constructors @@ -183,7 +190,7 @@ export const make = (): Effect.Effect(): Effect.Effect ["Hello", "World"] * ``` * * @category constructors @@ -240,7 +247,7 @@ export const makeRuntime = (): Effect.Effect< * * **Example** (Creating a promise runtime) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -251,11 +258,11 @@ export const makeRuntime = (): Effect.Effect< * const promise2 = run("task2", Effect.succeed("World")) * * // Convert to Effect and await - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "Hello", "World" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] * ``` * * @category constructors @@ -298,11 +305,12 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * * When the fiber completes, it is removed from the map. If the key already has * a fiber, that previous fiber is interrupted unless `onlyIfMissing` is set; - * in that case the new fiber is interrupted and the existing entry is kept. + * in that case a different new fiber is interrupted and the existing entry is + * kept, while re-registering the existing fiber is a no-op. * * **Example** (Adding a fiber unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -316,9 +324,11 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * yield* Deferred.succeed(deferred, "Hello") * * // Join the fiber to get its successful value - * const result = yield* Fiber.join(fiber) - * console.log(result) // "Hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "Hello" * ``` * * @category combinators @@ -358,16 +368,19 @@ export const setUnsafe: { const previous = MutableHashMap.get(self.state.backing, key) if (previous._tag === "Some") { - if (options?.onlyIfMissing === true) { - fiber.interruptUnsafe(internalFiberId) + if (previous.value === fiber) { return - } else if (previous.value === fiber) { + } else if (options?.onlyIfMissing === true) { + fiber.interruptUnsafe(internalFiberId) return } - previous.value.interruptUnsafe(internalFiberId) } + // Install the replacement before interruption can re-enter the map through a finalizer. MutableHashMap.set(self.state.backing, key, fiber) + if (previous._tag === "Some") { + previous.value.interruptUnsafe(internalFiberId) + } fiber.addObserver((exit) => { if (self.state._tag === "Closed") { return @@ -396,13 +409,14 @@ export const setUnsafe: { * * When the fiber completes, it is removed from the map. If the key already has * a fiber, that previous fiber is interrupted unless `onlyIfMissing` is set; - * in that case the new fiber is interrupted and the existing entry is kept. + * in that case a different new fiber is interrupted and the existing entry is + * kept, while re-registering the existing fiber is a no-op. * * This is the Effect-wrapped version of `setUnsafe`. * * **Example** (Adding a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Deferred, Effect, Fiber, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -416,9 +430,11 @@ export const setUnsafe: { * yield* Deferred.succeed(deferred, "Hello") * * // Join the fiber to get its successful value - * const result = yield* Fiber.join(fiber) - * console.log(result) // "Hello" + * return yield* Fiber.join(fiber) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => "Hello" * ``` * * @category combinators @@ -462,8 +478,8 @@ export const set: { * * **Example** (Retrieving a fiber unsafely) * - * ```ts - * import { Deferred, Effect, Fiber, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap, Option } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() @@ -475,13 +491,13 @@ export const set: { * * // Retrieve the fiber * const retrieved = FiberMap.getUnsafe(map, "greeting") - * if (retrieved._tag === "Some") { - * yield* Deferred.succeed(deferred, "Hello") - * - * const result = yield* Fiber.join(retrieved.value) - * console.log(result) // "Hello" - * } + * yield* Deferred.succeed(deferred, "Hello") + * const result = yield* Fiber.join(fiber) + * return Option.map(retrieved, () => result) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some("Hello") * ``` * * @category combinators @@ -506,8 +522,8 @@ export const getUnsafe: { * * **Example** (Retrieving a fiber) * - * ```ts - * import { Deferred, Effect, Fiber, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Deferred, Effect, Fiber, FiberMap, Option } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() @@ -519,13 +535,13 @@ export const getUnsafe: { * * // Retrieve the fiber with error handling * const retrieved = yield* FiberMap.get(map, "greeting") - * if (retrieved._tag === "Some") { - * yield* Deferred.succeed(deferred, "Hello") - * - * const result = yield* Fiber.join(retrieved.value) - * console.log(result) // "Hello" - * } + * yield* Deferred.succeed(deferred, "Hello") + * const result = yield* Fiber.join(fiber) + * return Option.map(retrieved, () => result) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => Option.some("Hello") * ``` * * @category combinators @@ -545,7 +561,7 @@ export const get: { * * **Example** (Checking if a key exists unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -555,9 +571,11 @@ export const get: { * yield* FiberMap.run(map, "task1", Effect.never) * * // Check if keys exist - * console.log(FiberMap.hasUnsafe(map, "task1")) // true - * console.log(FiberMap.hasUnsafe(map, "task2")) // false + * return [FiberMap.hasUnsafe(map, "task1"), FiberMap.hasUnsafe(map, "task2")] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * * @category combinators @@ -578,7 +596,7 @@ export const hasUnsafe: { * * **Example** (Checking if a key exists) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -588,12 +606,11 @@ export const hasUnsafe: { * yield* FiberMap.run(map, "task1", Effect.never) * * // Check if keys exist using Effect - * const exists1 = yield* FiberMap.has(map, "task1") - * const exists2 = yield* FiberMap.has(map, "task2") - * - * console.log(exists1) // true - * console.log(exists2) // false + * return [yield* FiberMap.has(map, "task1"), yield* FiberMap.has(map, "task2")] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * * @category combinators @@ -612,7 +629,7 @@ export const has: { * * **Example** (Removing a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -622,13 +639,16 @@ export const has: { * yield* FiberMap.run(map, "task1", Effect.never) * yield* FiberMap.run(map, "task2", Effect.never) * - * console.log(yield* FiberMap.size(map)) // 2 + * const sizeBefore = yield* FiberMap.size(map) * * // Remove a specific fiber (this will interrupt it) * yield* FiberMap.remove(map, "task1") * - * console.log(yield* FiberMap.size(map)) // 1 + * return [sizeBefore, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [2, 1] * ``` * * @category combinators @@ -662,7 +682,7 @@ export const remove: { * * **Example** (Clearing all fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -673,13 +693,16 @@ export const remove: { * yield* FiberMap.run(map, "task2", Effect.never) * yield* FiberMap.run(map, "task3", Effect.never) * - * console.log(yield* FiberMap.size(map)) // 3 + * const sizeBefore = yield* FiberMap.size(map) * * // Clear all fibers (this will interrupt all of them) * yield* FiberMap.clear(map) * - * console.log(yield* FiberMap.size(map)) // 0 + * return [sizeBefore, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [3, 0] * ``` * * @category combinators @@ -713,7 +736,7 @@ const constInterruptedFiber = (function() { * * **Example** (Forking effects into a map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -726,10 +749,11 @@ const constInterruptedFiber = (function() { * // Join the fibers to get their successful values * const result1 = yield* Fiber.join(fiber1) * const result2 = yield* Fiber.join(fiber2) - * - * console.log(result1, result2) // "Hello", "World" - * console.log(yield* FiberMap.size(map)) // 0 (fibers are removed after completion) + * return [result1, result2, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World", 0] * ``` * * @category combinators @@ -798,26 +822,29 @@ const runImpl = ( * * **Example** (Capturing a runtime) * - * ```ts - * import { Context, Effect, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberMap } from "effect" * - * interface Users { - * readonly _: unique symbol - * } - * const Users = Context.Service> - * }>("Users") + * class Users extends Context.Service> + * }>()("Users") {} * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * const run = yield* FiberMap.runtime(map)() * * // run some effects and add the fibers to the map - * run("effect-a", Effect.andThen(Users, (_) => _.getAll)) - * run("effect-b", Effect.andThen(Users, (_) => _.getAll)) + * const fiberA = run("effect-a", Effect.andThen(Users, (_) => _.getAll)) + * const fiberB = run("effect-b", Effect.andThen(Users, (_) => _.getAll)) + * return [(yield* Fiber.join(fiberA)).length, (yield* Fiber.join(fiberB)).length] * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(Effect.provideService(program, Users, { + * getAll: Effect.succeed([]) + * })) + * actual // => [0, 0] * ``` * * @category combinators @@ -878,7 +905,7 @@ export const runtime: ( * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { @@ -890,11 +917,11 @@ export const runtime: ( * const promise2 = runPromise("task2", Effect.succeed("World")) * * // Convert promises back to Effects and await - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "Hello", "World" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["Hello", "World"] * ``` * * @category combinators @@ -941,20 +968,23 @@ export const runtimePromise = (self: FiberMap): () * * **Example** (Checking the map size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * - * console.log(yield* FiberMap.size(map)) // 0 + * const sizeBefore = yield* FiberMap.size(map) * * // Add some fibers * yield* FiberMap.run(map, "task1", Effect.never) * yield* FiberMap.run(map, "task2", Effect.never) * - * console.log(yield* FiberMap.size(map)) // 2 + * return [sizeBefore, yield* FiberMap.size(map)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [0, 2] * ``` * * @category combinators @@ -975,16 +1005,19 @@ export const size = (self: FiberMap): Effect.Effect => * * **Example** (Joining failing fibers) * - * ```ts - * import { Effect, FiberMap } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, FiberMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * yield* FiberMap.set(map, "a", Effect.runFork(Effect.fail("error"))) * * // parent fiber will fail with "error" * yield* FiberMap.join(map) * }) + * + * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program))) + * actual // => Exit.fail("error") * ``` * * @category combinators @@ -999,24 +1032,23 @@ export const join = (self: FiberMap): Effect.Effect = * * **Example** (Waiting for an empty map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberMap } from "effect" * * const program = Effect.gen(function*() { * const map = yield* FiberMap.make() * - * // Add some fibers that will complete after a delay - * yield* FiberMap.run(map, "task1", Effect.sleep(1000)) - * yield* FiberMap.run(map, "task2", Effect.sleep(2000)) - * - * console.log("Waiting for all fibers to complete...") + * yield* FiberMap.run(map, "task1", Effect.yieldNow) + * yield* FiberMap.run(map, "task2", Effect.yieldNow) * * // Wait for the map to be empty * yield* FiberMap.awaitEmpty(map) * - * console.log("All fibers completed!") - * console.log(yield* FiberMap.size(map)) // 0 + * return yield* FiberMap.size(map) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 * ``` * * @category combinators diff --git a/packages/effect/src/FiberSet.ts b/packages/effect/src/FiberSet.ts index 7b7b36689..f99040716 100644 --- a/packages/effect/src/FiberSet.ts +++ b/packages/effect/src/FiberSet.ts @@ -31,7 +31,7 @@ const TypeId = "~effect/FiberSet" * * **Example** (Managing fibers in a set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -43,7 +43,11 @@ const TypeId = "~effect/FiberSet" * * // Wait for all fibers to complete * yield* FiberSet.awaitEmpty(set) + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 * ``` * * @category models @@ -67,18 +71,20 @@ export interface FiberSet * * **Example** (Checking if a value is a FiberSet) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * - * console.log(FiberSet.isFiberSet(set)) // true - * console.log(FiberSet.isFiberSet({})) // false + * return [FiberSet.isFiberSet(set), FiberSet.isFiberSet({})] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [true, false] * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isFiberSet = (u: unknown): u is FiberSet => Predicate.hasProperty(u, TypeId) @@ -94,7 +100,7 @@ const Proto = { ...PipeInspectableProto, toJSON(this: FiberSet) { return { - _id: "FiberMap", + _id: "FiberSet", state: this.state } } @@ -122,20 +128,24 @@ const makeUnsafe = ( * * **Example** (Creating a scoped FiberSet) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * * // run some effects and add the fibers to the set * yield* FiberSet.run(set, Effect.never) * yield* FiberSet.run(set, Effect.never) * - * yield* Effect.sleep(1000) + * yield* Effect.yieldNow + * return yield* FiberSet.size(set) * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(program) + * actual // => 2 * ``` * * @category constructors @@ -166,7 +176,7 @@ export const make = (): Effect.Effect, * * **Example** (Creating a scoped runtime) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -176,11 +186,11 @@ export const make = (): Effect.Effect, * const fiber1 = runFork(Effect.succeed("hello")) * const fiber2 = runFork(Effect.succeed("world")) * - * const result1 = yield* Fiber.await(fiber1) - * const result2 = yield* Fiber.await(fiber2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category constructors @@ -216,7 +226,7 @@ export const makeRuntime = (): Effect.Effec * * **Example** (Creating a promise runtime) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -226,11 +236,11 @@ export const makeRuntime = (): Effect.Effec * const promise1 = runPromise(Effect.succeed("hello")) * const promise2 = runPromise(Effect.succeed("world")) * - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category constructors @@ -272,19 +282,22 @@ const isInternalInterruption = Filter.toPredicate(Filter.compose( * * **Example** (Adding a fiber unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() - * const fiber = yield* Effect.forkChild(Effect.succeed("hello")) + * const fiber = yield* Effect.forkChild(Effect.never) * * // Unsafe add - doesn't return an Effect * FiberSet.addUnsafe(set, fiber) * * // The fiber is now managed by the set - * console.log(yield* FiberSet.size(set)) // 1 + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 1 * ``` * * @category combinators @@ -341,19 +354,22 @@ export const addUnsafe: { * * **Example** (Adding a fiber) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() - * const fiber = yield* Effect.forkChild(Effect.succeed("hello")) + * const fiber = yield* Effect.forkChild(Effect.never) * * // Add the fiber to the set * yield* FiberSet.add(set, fiber) * * // The fiber is now managed by the set - * console.log(yield* FiberSet.size(set)) // 1 + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 1 * ``` * * @category combinators @@ -389,7 +405,7 @@ export const add: { * * **Example** (Clearing all fibers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -399,13 +415,16 @@ export const add: { * yield* FiberSet.run(set, Effect.never) * yield* FiberSet.run(set, Effect.never) * - * console.log(yield* FiberSet.size(set)) // 2 + * const sizeBefore = yield* FiberSet.size(set) * * // Clear all fibers * yield* FiberSet.clear(set) * - * console.log(yield* FiberSet.size(set)) // 0 + * return [sizeBefore, yield* FiberSet.size(set)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [2, 0] * ``` * * @category combinators @@ -435,7 +454,7 @@ const constInterruptedFiber = (function() { * * **Example** (Forking effects into a set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -446,11 +465,11 @@ const constInterruptedFiber = (function() { * const fiber2 = yield* FiberSet.run(set, Effect.succeed("world")) * * // Get results - * const result1 = yield* Fiber.await(fiber1) - * const result2 = yield* Fiber.await(fiber2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @category combinators @@ -504,25 +523,28 @@ const runImpl = ( * * **Example** (Capturing a runtime) * - * ```ts - * import { Context, Effect, FiberSet } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Fiber, FiberSet } from "effect" * - * interface Users { - * readonly _: unique symbol - * } - * const Users = Context.Service> - * }>("Users") + * class Users extends Context.Service> + * }>()("Users") {} * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * const run = yield* FiberSet.runtime(set)() * * // run some effects and add the fibers to the set - * run(Effect.andThen(Users, (_) => _.getAll)) + * const fiber = run(Effect.andThen(Users, (_) => _.getAll)) + * return (yield* Fiber.join(fiber)).length * }).pipe( * Effect.scoped // The fibers will be interrupted when the scope is closed * ) + * + * const actual = await Effect.runPromise(Effect.provideService(program, Users, { + * getAll: Effect.succeed([]) + * })) + * actual // => 0 * ``` * * @category combinators @@ -554,7 +576,7 @@ export const runtime: ( return constInterruptedFiber() } const fiber = runFork(effect, options) - addUnsafe(self, fiber) + addUnsafe(self, fiber, options) return fiber } } @@ -575,7 +597,7 @@ export const runtime: ( * * **Example** (Running effects as promises) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { @@ -586,11 +608,11 @@ export const runtime: ( * const promise1 = runPromise(Effect.succeed("hello")) * const promise2 = runPromise(Effect.succeed("world")) * - * const result1 = yield* Effect.promise(() => promise1) - * const result2 = yield* Effect.promise(() => promise2) - * - * console.log(result1, result2) // "hello" "world" + * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => ["hello", "world"] * ``` * * @see {@link runtime} for a runner that returns the forked `Fiber` @@ -634,20 +656,23 @@ export const runtimePromise = (self: FiberSet): () => Eff * * **Example** (Checking the set size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * - * console.log(yield* FiberSet.size(set)) // 0 + * const sizeBefore = yield* FiberSet.size(set) * * // Add some fibers * yield* FiberSet.run(set, Effect.never) * yield* FiberSet.run(set, Effect.never) * - * console.log(yield* FiberSet.size(set)) // 2 + * return [sizeBefore, yield* FiberSet.size(set)] * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => [0, 2] * ``` * * @category combinators @@ -662,16 +687,19 @@ export const size = (self: FiberSet): Effect.Effect => * * **Example** (Joining failing fibers) * - * ```ts - * import { Effect, FiberSet } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, FiberSet } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * yield* FiberSet.add(set, Effect.runFork(Effect.fail("error"))) * * // parent fiber will fail with "error" * yield* FiberSet.join(set) * }) + * + * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program))) + * actual // => Exit.fail("error") * ``` * * @category combinators @@ -685,21 +713,23 @@ export const join = (self: FiberSet): Effect.Effect => * * **Example** (Waiting for an empty set) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FiberSet } from "effect" * * const program = Effect.gen(function*() { * const set = yield* FiberSet.make() * - * // Add some fibers that will complete - * yield* FiberSet.run(set, Effect.sleep(100)) - * yield* FiberSet.run(set, Effect.sleep(200)) + * yield* FiberSet.run(set, Effect.yieldNow) + * yield* FiberSet.run(set, Effect.yieldNow) * * // Wait for all fibers to complete * yield* FiberSet.awaitEmpty(set) * - * console.log(yield* FiberSet.size(set)) // 0 + * return yield* FiberSet.size(set) * }) + * + * const actual = await Effect.runPromise(Effect.scoped(program)) + * actual // => 0 * ``` * * @category combinators diff --git a/packages/effect/src/FileSystem.ts b/packages/effect/src/FileSystem.ts index 0b4b191e5..adb2a03a9 100644 --- a/packages/effect/src/FileSystem.ts +++ b/packages/effect/src/FileSystem.ts @@ -5,13 +5,13 @@ * Platform packages provide concrete layers, while this module defines the * operations for reading, writing, inspecting, streaming, and watching files. * Operations return `Effect`, `Stream`, or `Sink` values and fail with - * `PlatformError`. The module also includes file handles, size helpers, open - * flags, watch events, and the watch backend service. + * `PlatformError`. The module also includes file handles, open flags, watch + * events, and the watch backend service. * * @since 4.0.0 */ import * as Arr from "./Array.ts" -import * as Brand from "./Brand.ts" +import * as ByteSize from "./ByteSize.ts" import * as Cause from "./Cause.ts" import * as Context from "./Context.ts" import * as Effect from "./Effect.ts" @@ -25,7 +25,7 @@ import type { Scope } from "./Scope.ts" import * as Sink from "./Sink.ts" import * as Stream from "./Stream.ts" -const TypeId = "~effect/platform/FileSystem" +const TypeId = "~effect/FileSystem" /** * Core interface for file system operations in Effect. @@ -38,8 +38,15 @@ const TypeId = "~effect/platform/FileSystem" * * **Example** (Accessing file system operations) * - * ```ts - * import { Console, Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { ByteSize, Effect, FileSystem } from "effect" + * + * const fileSystem = FileSystem.makeNoop({ + * exists: () => Effect.succeed(true), + * makeDirectory: () => Effect.void, + * stat: () => Effect.succeed({ size: ByteSize.bytes(22) } as FileSystem.File.Info), + * readFileString: () => Effect.succeed("{\"env\": \"development\"}") + * }) * * const program = Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem @@ -55,15 +62,17 @@ const TypeId = "~effect/platform/FileSystem" * * // File information * const stats = yield* fs.stat("./config.json") - * yield* Console.log(`File size: ${stats.size} bytes`) - * - * // Streaming operations + * // Read the file contents * const content = yield* fs.readFileString("./config.json") - * yield* Console.log("Config:", content) + * return { size: stats.size, content } * }) + * + * const result = Effect.runSync(Effect.provideService(program, FileSystem.FileSystem, fileSystem)) + * ByteSize.toBigInt(result.size) // => 22n + * result.content // => "{\"env\": \"development\"}" * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface FileSystem { @@ -118,6 +127,16 @@ export interface FileSystem { uid: number, gid: number ) => Effect.Effect + /** + * Glob a directory. + */ + readonly glob: ( + pattern: string, + options?: { + readonly root?: string | undefined + readonly exclude?: ReadonlyArray | undefined + } + ) => Effect.Effect, PlatformError> /** * Checks whether a path exists. */ @@ -280,7 +299,7 @@ export interface FileSystem { } ) => Sink.Sink /** - * Get information about a file at `path`. + * Get information about a file at `path`. See `File.Info` for metadata limits. */ readonly stat: ( path: string @@ -302,9 +321,9 @@ export interface FileSystem { readonly stream: ( path: string, options?: { - readonly bytesToRead?: SizeInput | undefined - readonly chunkSize?: SizeInput | undefined - readonly offset?: SizeInput | undefined + readonly bytesToRead?: ByteSize.Input | undefined + readonly chunkSize?: number | undefined + readonly offset?: ByteSize.Input | undefined } ) => Stream.Stream /** @@ -320,7 +339,7 @@ export interface FileSystem { */ readonly truncate: ( path: string, - length?: SizeInput + length?: number ) => Effect.Effect /** * Change the file system timestamps of the file at `path`. @@ -331,9 +350,15 @@ export interface FileSystem { mtime: Date | number ) => Effect.Effect /** - * Watch a directory or file for changes + * Watch a directory or file for changes. + * + * **Details** + * + * By default, only changes to the direct children of the directory are + * reported. Set the `recursive` option to `true` to watch for changes in + * subdirectories as well. */ - readonly watch: (path: string) => Stream.Stream + readonly watch: (path: string, options?: WatchOptions) => Stream.Stream /** * Write data to a file at `path`. */ @@ -358,285 +383,6 @@ export interface FileSystem { ) => Effect.Effect } -/** - * Represents a file size in bytes using a branded bigint. - * - * **Details** - * - * This type ensures type safety when working with file sizes, preventing - * accidental mixing of regular numbers with size values. The underlying - * bigint allows for handling very large file sizes beyond JavaScript's - * number precision limits. - * - * **Example** (Creating branded file sizes) - * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * // Create sizes using the Size constructor - * const smallFile = FileSystem.Size(1024) // 1 KB - * const largeFile = FileSystem.Size(BigInt("9007199254740992")) // Very large - * - * // Use with file operations - * const truncateToSize = Effect.fnUntraced(function*(path: string, size: FileSystem.Size) { - * const fs = yield* FileSystem.FileSystem - * return yield* fs.truncate(path, size) - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export type Size = Brand.Branded - -/** - * Input type for size parameters that accepts multiple numeric types. - * - * **Details** - * - * This union type allows file system operations to accept size values in - * different formats for convenience, which are then normalized to the - * branded `Size` type internally. - * - * **Example** (Using size inputs) - * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // All of these are valid SizeInput values - * yield* fs.truncate("file1.txt", 1024) // number - * yield* fs.truncate("file2.txt", BigInt(2048)) // bigint - * yield* fs.truncate("file3.txt", FileSystem.Size(4096)) // Size - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export type SizeInput = bigint | number | Size - -/** - * Creates a `Size` from various numeric input types. - * - * **Details** - * - * Converts numbers, bigints, or existing Size values into a properly - * branded Size type. This function handles the conversion and ensures - * type safety for file size operations. - * - * **Example** (Converting size inputs) - * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * // From number - * const size1 = FileSystem.Size(1024) - * console.log(typeof size1) // "bigint" - * - * // From bigint - * const size2 = FileSystem.Size(BigInt(2048)) - * - * // From existing Size (identity) - * const size3 = FileSystem.Size(size1) - * - * // Use in file operations - * const readChunk = (path: string, chunkSize: number) => - * Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * return fs.stream(path, { - * chunkSize: FileSystem.Size(chunkSize) - * }) - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export const Size = (bytes: SizeInput): Size => typeof bytes === "bigint" ? bytes as Size : BigInt(bytes) as Size - -/** - * Creates a `Size` representing kilobytes (1024 bytes). - * - * **Details** - * - * Converts a number of kilobytes to the equivalent size in bytes. - * Uses binary kilobytes (1024 bytes) rather than decimal (1000 bytes). - * - * **Example** (Creating kibibyte sizes) - * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Create a 64 KiB buffer size for streaming - * const bufferSize = FileSystem.KiB(64) - * - * const stream = fs.stream("large-file.txt", { - * chunkSize: bufferSize - * }) - * - * // Truncate file to 100 KiB - * yield* fs.truncate("data.txt", FileSystem.KiB(100)) - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export const KiB = (n: number): Size => Size(n * 1024) - -/** - * Creates a `Size` representing mebibytes (1024² bytes). - * - * **Details** - * - * Converts a number of mebibytes to the equivalent size in bytes. - * Uses binary mebibytes (1,048,576 bytes) rather than decimal megabytes. - * - * **Example** (Creating mebibyte sizes) - * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Set a 10 MiB chunk size for large file operations - * const largeChunkSize = FileSystem.MiB(10) - * - * const stream = fs.stream("video.mp4", { - * chunkSize: largeChunkSize - * }) - * - * // Check if file is larger than 100 MiB - * const stats = yield* fs.stat("archive.zip") - * const maxSize = FileSystem.MiB(100) - * if (stats.size > maxSize) { - * yield* Effect.log("File is very large!") - * } - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export const MiB = (n: number): Size => Size(n * 1024 * 1024) - -/** - * Creates a `Size` representing gibibytes (1024³ bytes). - * - * **Details** - * - * Converts a number of gibibytes to the equivalent size in bytes. - * Uses binary gibibytes (1,073,741,824 bytes) rather than decimal gigabytes. - * - * **Example** (Creating gibibyte sizes) - * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Use GiB values as size thresholds - * const maxArchiveSize = FileSystem.GiB(1) - * console.log(maxArchiveSize.toString()) // "1073741824" - * - * const tempFile = yield* fs.makeTempFile({ prefix: "archive-" }) - * yield* fs.writeFileString(tempFile, "backup data") - * - * const info = yield* fs.stat(tempFile) - * console.log(info.size < maxArchiveSize) // true - * - * yield* fs.remove(tempFile) - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export const GiB = (n: number): Size => Size(n * 1024 * 1024 * 1024) - -/** - * Creates a `Size` representing tebibytes (1024⁴ bytes). - * - * **Details** - * - * Converts a number of tebibytes to the equivalent size in bytes. - * Uses binary tebibytes (1,099,511,627,776 bytes) rather than decimal terabytes. - * - * **Example** (Creating tebibyte sizes) - * - * ```ts - * import { Console, Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Check if we're dealing with very large files - * const stats = yield* fs.stat("database-backup.sql") - * const oneTiB = FileSystem.TiB(1) - * - * if (stats.size > oneTiB) { - * yield* Console.log("This is a very large database backup!") - * - * // Use larger chunk sizes for such files - * const stream = fs.stream("database-backup.sql", { - * chunkSize: FileSystem.MiB(100) // 100 MiB chunks - * }) - * } - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export const TiB = (n: number): Size => Size(n * 1024 * 1024 * 1024 * 1024) - -const bigint1024 = BigInt(1024) -const bigintPiB = bigint1024 * bigint1024 * bigint1024 * bigint1024 * bigint1024 - -/** - * Creates a `Size` representing pebibytes (1024⁵ bytes). - * - * **Details** - * - * Converts a number of pebibytes to the equivalent size in bytes. - * Uses binary pebibytes (1,125,899,906,842,624 bytes) rather than decimal petabytes. - * This function uses BigInt arithmetic to handle the very large numbers involved. - * - * **Example** (Creating pebibyte sizes) - * - * ```ts - * import { Console, Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // For extremely large data processing scenarios - * const massiveDataset = FileSystem.PiB(2) // 2 PiB - * - * // This would typically be used in enterprise/cloud scenarios - * yield* Console.log(`Processing ${massiveDataset} bytes of data`) - * - * // Such large files would require specialized streaming - * const stream = fs.stream("massive-dataset.bin", { - * chunkSize: FileSystem.GiB(1), // 1 GiB chunks - * offset: FileSystem.TiB(100) // Start from 100 TiB offset - * }) - * }) - * ``` - * - * @category sizes - * @since 4.0.0 - */ -export const PiB = (n: number): Size => Size(BigInt(n) * bigintPiB) - /** * File open flags that determine how a file is opened and what operations are allowed. * @@ -658,24 +404,11 @@ export const PiB = (n: number): Size => Size(BigInt(n) * bigintPiB) * * **Example** (Opening files with flags) * - * ```ts - * import { Effect, FileSystem } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Open for reading only - * const readFile = yield* fs.open("data.txt", { flag: "r" }) + * ```ts import.meta.vitest + * import type { FileSystem } from "effect" * - * // Open for writing, truncating existing content - * const writeFile = yield* fs.open("output.txt", { flag: "w" }) - * - * // Open for appending - * const appendFile = yield* fs.open("log.txt", { flag: "a" }) - * - * // Open for read/write, but fail if file doesn't exist - * const editFile = yield* fs.open("config.json", { flag: "r+" }) - * }) + * const flags: ReadonlyArray = ["r", "w", "a", "r+"] + * flags // => ["r", "w", "a", "r+"] * ``` * * @category models @@ -707,38 +440,34 @@ export type OpenFlag = * * **Example** (Accessing and providing FileSystem) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem } from "effect" * + * const customFs = FileSystem.makeNoop({ + * exists: () => Effect.succeed(true), + * readFileString: () => Effect.succeed("contents") + * }) + * * // Access the FileSystem service * const program = Effect.gen(function*() { * const fs = yield* FileSystem.FileSystem * * const exists = yield* fs.exists("./data.txt") - * if (exists) { - * const content = yield* fs.readFileString("./data.txt") - * yield* Effect.log("File content:", content) - * } + * return exists ? yield* fs.readFileString("./data.txt") : undefined * }) * - * // Provide a custom FileSystem implementation - * declare const platformImpl: Omit< - * FileSystem.FileSystem, - * "exists" | "readFileString" | "stream" | "sink" | "writeFileString" - * > - * const customFs = FileSystem.make(platformImpl) - * * const withCustomFs = Effect.provideService( * program, * FileSystem.FileSystem, * customFs * ) + * Effect.runSync(withCustomFs) // => "contents" * ``` * * @category services * @since 4.0.0 */ -export const FileSystem: Context.Service = Context.Service("effect/platform/FileSystem") +export const FileSystem: Context.Service = Context.Service("effect/FileSystem") /** * Creates a FileSystem implementation from a partial implementation. @@ -790,12 +519,16 @@ export const make = ( })), stream: Effect.fnUntraced(function*(path, options) { const file = yield* impl.open(path, { flag: "r" }) - if (options?.offset) { - yield* file.seek(options.offset, "start") + const offset = options?.offset === undefined ? undefined : ByteSize.fromInputUnsafe(options.offset) + if (offset) { + yield* file.seek(offset, "start") } - const bytesToRead = options?.bytesToRead !== undefined ? Size(options.bytesToRead) : undefined + const bytesToRead = options?.bytesToRead === undefined + ? undefined + : ByteSize.fromInputUnsafe(options.bytesToRead) let totalBytesRead = BigInt(0) - const chunkSize = Size(options?.chunkSize ?? 64 * 1024) + // Validate chunk sizes even for zero-byte reads. + const chunkSize = Number(BigInt(options?.chunkSize ?? 64 * 1024)) const readChunk = file.readAlloc(chunkSize) return Stream.fromPull(Effect.succeed( Effect.flatMap( @@ -804,7 +537,7 @@ export const make = ( return Cause.done() } return bytesToRead !== undefined && (bytesToRead - totalBytesRead) < chunkSize - ? file.readAlloc(bytesToRead - totalBytesRead) + ? file.readAlloc(Number(bytesToRead - totalBytesRead)) : readChunk }), Option.match({ @@ -819,7 +552,7 @@ export const make = ( }, Stream.unwrap), sink: (path, options) => pipe( - impl.open(path, { flag: "w", ...options }), + impl.open(path, { ...options, flag: options?.flag ?? "w" }), Effect.map((file) => Sink.forEach((_: Uint8Array) => file.writeAll(_))), Sink.unwrap ), @@ -860,7 +593,7 @@ const notFound = (method: string, path: string) => * * **Example** (Creating a no-op FileSystem) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem, PlatformError } from "effect" * * // Create a test filesystem that only allows reading specific files @@ -885,7 +618,7 @@ const notFound = (method: string, path: string) => * // Use in tests * const program = Effect.gen(function*() { * const content = yield* testFs.readFileString("test-config.json") - * // Will succeed with mocked content + * return content * }) * * // Test with the no-op filesystem @@ -894,6 +627,7 @@ const notFound = (method: string, path: string) => * FileSystem.FileSystem, * testFs * ) + * Effect.runSync(testProgram) // => "{\"test\": true}" * ``` * * @category constructors @@ -917,6 +651,9 @@ export const makeNoop = (fileSystem: Partial): FileSystem => copyFile(path) { return Effect.fail(notFound("copyFile", path)) }, + glob(pattern) { + return Effect.fail(notFound("glob", pattern)) + }, exists() { return Effect.succeed(false) }, @@ -1002,7 +739,7 @@ export const makeNoop = (fileSystem: Partial): FileSystem => * * **Example** (Providing a no-op FileSystem layer) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem } from "effect" * * // Create a test layer with specific behaviors @@ -1019,6 +756,7 @@ export const makeNoop = (fileSystem: Partial): FileSystem => * * // Provide the test layer * const testProgram = Effect.provide(program, testLayer) + * Effect.runSync(testProgram) // => "mocked content" * ``` * * @category layers @@ -1042,7 +780,7 @@ export const layerNoop = (fileSystem: Partial): Layer.Layer hasProperty(u, FileTypeId) @@ -1077,49 +815,57 @@ export const isFile = (u: unknown): u is File => hasProperty(u, FileTypeId) * * **Example** (Working with file handles) * - * ```ts - * import { Console, Effect, FileSystem } from "effect" + * ```ts import.meta.vitest + * import { ByteSize, Effect, FileSystem, Option } from "effect" + * + * const file: FileSystem.File = { + * [FileSystem.FileTypeId]: FileSystem.FileTypeId, + * stat: Effect.succeed({ size: ByteSize.bytes(5) } as FileSystem.File.Info), + * seek: () => Effect.succeed(BigInt(0)), + * sync: Effect.void, + * read: (buffer) => Effect.sync(() => { + * buffer.set([1, 2, 3, 4, 5]) + * return 5 + * }), + * readAlloc: () => Effect.succeed(Option.none()), + * truncate: () => Effect.void, + * write: (buffer) => Effect.succeed(buffer.length), + * writeAll: () => Effect.void + * } * * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // Open a file and work with the handle - * yield* Effect.scoped( - * Effect.gen(function*() { - * const file = yield* fs.open("./data.txt", { flag: "r+" }) - * - * // Get file information - * const stats = yield* file.stat - * yield* Console.log(`File size: ${stats.size} bytes`) - * - * // Read from specific position - * yield* file.seek(10, "start") - * const buffer = new Uint8Array(5) - * const bytesRead = yield* file.read(buffer) - * yield* Console.log(`Read ${bytesRead} bytes:`, buffer) - * - * // Write data - * const data = new TextEncoder().encode("Hello") - * yield* file.write(data) - * yield* file.sync // Flush to disk - * }) - * ) + * const stats = yield* file.stat + * const buffer = new Uint8Array(5) + * const bytesRead = yield* file.read(buffer) + * yield* file.writeAll(new TextEncoder().encode("Hello")) + * yield* file.sync + * return { size: stats.size, bytesRead, buffer: Array.from(buffer) } * }) + * + * const result = Effect.runSync(program) + * ByteSize.toBigInt(result.size) // => 5n + * result.bytesRead // => 5 + * result.buffer // => [1, 2, 3, 4, 5] * ``` * - * @category file + * @category models * @since 4.0.0 */ export interface File { readonly [FileTypeId]: typeof FileTypeId - readonly fd: File.Descriptor + /** + * Get information about the open file. See `File.Info` for metadata limits. + */ readonly stat: Effect.Effect - readonly seek: (offset: SizeInput, from: SeekMode) => Effect.Effect + /** + * Seeks before the start fail with `BadArgument` and leave the cursor unchanged. + */ + readonly seek: (offset: bigint, from: SeekMode) => Effect.Effect readonly sync: Effect.Effect - readonly read: (buffer: Uint8Array) => Effect.Effect - readonly readAlloc: (size: SizeInput) => Effect.Effect, PlatformError> - readonly truncate: (length?: SizeInput) => Effect.Effect - readonly write: (buffer: Uint8Array) => Effect.Effect + readonly read: (buffer: Uint8Array) => Effect.Effect + readonly readAlloc: (size: number) => Effect.Effect, PlatformError> + readonly truncate: (length?: number) => Effect.Effect + readonly write: (buffer: Uint8Array) => Effect.Effect readonly writeAll: (buffer: Uint8Array) => Effect.Effect } @@ -1130,19 +876,6 @@ export interface File { * @since 4.0.0 */ export declare namespace File { - /** - * Branded type for file descriptors. - * - * **Details** - * - * File descriptors are numeric handles used by the operating system - * to identify open files. The branded type ensures type safety. - * - * @category file - * @since 4.0.0 - */ - export type Descriptor = Brand.Branded - /** * Enumeration of possible file system entry types. * @@ -1151,7 +884,7 @@ export declare namespace File { * Represents the different types of entries that can exist in a file system, * from regular files to special device files and symbolic links. * - * @category file + * @category models * @since 4.0.0 */ export type Type = @@ -1173,40 +906,46 @@ export declare namespace File { * permissions, and size information. This structure is returned by file * stat operations. * - * **Example** (Inspecting file information) - * - * ```ts - * import { Effect, FileSystem, Option } from "effect" - * - * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * const path = yield* fs.makeTempFile({ prefix: "info-" }) - * yield* fs.writeFileString(path, "hello") + * Node and Bun preserve `size` and `blksize` exactly. Unsafe numeric metadata + * (such as `ino` or `dev`) fails the entire stat operation with `BadArgument`, + * including optional fields. Inode values above `Number.MAX_SAFE_INTEGER` + * can therefore prevent stat and HTTP file serving even for small files. * - * const info: FileSystem.File.Info = yield* fs.stat(path) + * **Example** (Inspecting file information) * - * console.log(`File type: ${info.type}`) // "File type: File" - * console.log(`File size: ${info.size} bytes`) // "File size: 5 bytes" - * console.log(`Mode: ${info.mode.toString(8)}`) // Octal permissions + * ```ts import.meta.vitest + * import { ByteSize, FileSystem, Option } from "effect" * - * // Handle optional timestamps without inventing a fallback date - * const modified = Option.match(info.mtime, { - * onNone: () => "unavailable", - * onSome: (mtime) => mtime.toISOString() - * }) - * console.log(`Modified: ${modified}`) + * const info: FileSystem.File.Info = { + * type: "File", + * mtime: Option.none(), + * atime: Option.none(), + * birthtime: Option.none(), + * dev: 1, + * ino: Option.none(), + * mode: 0o644, + * nlink: Option.none(), + * uid: Option.none(), + * gid: Option.none(), + * rdev: Option.none(), + * size: ByteSize.bytes(5), + * blksize: Option.none(), + * blocks: Option.none() + * } * - * // Check if it's a regular file - * if (info.type === "File") { - * console.log("Processing regular file...") // "Processing regular file..." - * } + * info.type // => "File" + * ByteSize.toBigInt(info.size) // => 5n + * info.mode.toString(8) // => "644" * - * yield* fs.remove(path) + * const modified = Option.match(info.mtime, { + * onNone: () => "unavailable", + * onSome: (mtime) => mtime.toISOString() * }) + * modified // => "unavailable" + * info.type === "File" // => true * ``` * - * @category file + * @category models * @since 4.0.0 */ export interface Info { @@ -1221,38 +960,12 @@ export declare namespace File { readonly uid: Option.Option readonly gid: Option.Option readonly rdev: Option.Option - readonly size: Size - readonly blksize: Option.Option + readonly size: ByteSize.ByteSize + readonly blksize: Option.Option readonly blocks: Option.Option } } -/** - * Creates a `File.Descriptor` from a number. - * - * **When to use** - * - * Use to brand an operating-system file descriptor number when implementing a - * `FileSystem` that returns custom `File` handles. - * - * **Details** - * - * `File.Descriptor` is a branded integer handle used by operating systems to - * identify open files. - * - * **Gotchas** - * - * This constructor is nominal and does not check that the number is an integer - * or that it refers to an open file descriptor. - * - * @see {@link File.Descriptor} for the branded descriptor type produced by this constructor - * @see {@link File} for file handles that expose a descriptor through `fd` - * - * @category constructors - * @since 4.0.0 - */ -export const FileDescriptor = Brand.nominal() - /** * Specifies the reference point for seeking within an open file. * @@ -1274,6 +987,19 @@ export const FileDescriptor = Brand.nominal() */ export type SeekMode = "start" | "current" +/** + * Options for watching files or directories. + * + * @category models + * @since 4.0.0 + */ +export interface WatchOptions { + /** + * When `true`, changes in subdirectories are also reported. + */ + readonly recursive?: boolean | undefined +} + /** * Represents file system events emitted when watching files or directories. * @@ -1360,7 +1086,7 @@ export declare namespace WatchEvent { * * **Example** (Providing a custom watch backend) * - * ```ts + * ```ts import.meta.vitest * import { Effect, FileSystem, Option, Stream } from "effect" * * // Custom watch backend implementation @@ -1371,12 +1097,11 @@ export declare namespace WatchEvent { * } * } * - * // Provide custom watch backend * const program = Effect.gen(function*() { - * const fs = yield* FileSystem.FileSystem - * - * // File watching will use the custom backend - * const watcher = fs.watch("./directory") + * const backend = yield* FileSystem.WatchBackend + * return Option.isSome( + * backend.register("./directory", { type: "Directory" } as FileSystem.File.Info) + * ) * }) * * const withCustomBackend = Effect.provideService( @@ -1384,11 +1109,16 @@ export declare namespace WatchEvent { * FileSystem.WatchBackend, * customWatchBackend * ) + * Effect.runSync(withCustomBackend) // => true * ``` * - * @category file watcher + * @category services * @since 4.0.0 */ export class WatchBackend extends Context.Service Option.Option> -}>()("effect/platform/FileSystem/WatchBackend") {} + readonly register: ( + path: string, + stat: File.Info, + options?: WatchOptions + ) => Option.Option> +}>()("effect/FileSystem/WatchBackend") {} diff --git a/packages/effect/src/Filter.ts b/packages/effect/src/Filter.ts index c492eb2e7..631d3e03d 100644 --- a/packages/effect/src/Filter.ts +++ b/packages/effect/src/Filter.ts @@ -28,14 +28,14 @@ import type { EqualsWith, ExcludeTag, ExtractReason, ExtractTag, ReasonTags, Tag * * **Example** (Defining a positive number filter) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * // A filter that only passes positive numbers * const positiveFilter: Filter.Filter = (n) => n > 0 ? Result.succeed(n) : Result.fail(n) * - * console.log(positiveFilter(5)) // Result.succeed(5) - * console.log(positiveFilter(-3)) // Result.fail(-3) + * positiveFilter(5) // => Result.succeed(5) + * positiveFilter(-3) // => Result.fail(-3) * ``` * * @category models @@ -56,7 +56,7 @@ export interface Filter { * * **Example** (Defining an effectful user filter) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Filter, Result } from "effect" * * // An effectful filter that validates user data @@ -74,6 +74,9 @@ export interface Filter { * const user: User = { id, isActive: id.length > 0 } * return user.isActive ? Result.succeed(user) : Result.fail(user) * }) + * + * await Effect.runPromise(validateUser("alice")) // => Result.succeed({ id: "alice", isActive: true }) + * await Effect.runPromise(validateUser("")) // => Result.fail({ id: "", isActive: false }) * ``` * * @category models @@ -103,7 +106,7 @@ export interface FilterEffect< * * **Example** (Creating custom filters) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * // Create a filter for positive numbers @@ -113,6 +116,8 @@ export interface FilterEffect< * const uppercaseFilter = Filter.make((s: string) => * s.length > 0 ? Result.succeed(s.toUpperCase()) : Result.fail(s) * ) + * positiveFilter(1) // => Result.succeed(1) + * uppercaseFilter("ok") // => Result.succeed("OK") * ``` * * @category constructors @@ -133,7 +138,7 @@ export const make = ( * * **Example** (Creating effectful filters) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Filter, Result } from "effect" * * // Create an effectful filter that validates async @@ -143,6 +148,8 @@ export const make = ( * return isValid ? Result.succeed(id) : Result.fail(id) * }) * ) + * + * await Effect.runPromise(asyncValidate("id")) // => Result.succeed("id") * ``` * * @category constructors @@ -201,7 +208,7 @@ export { * * **Example** (Creating filters from predicates) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * // Create filter from predicate @@ -212,6 +219,9 @@ export { * const isString = Filter.fromPredicate((x: unknown): x is string => * typeof x === "string" * ) + * positiveNumbers(1) // => Result.succeed(1) + * nonEmptyStrings("") // => Result.fail("") + * isString("ok") // => Result.succeed("ok") * ``` * * @category constructors @@ -259,11 +269,11 @@ export const toPredicate = ( * * **Example** (Filtering strings) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * - * console.log(Filter.string("hello")) // Result.succeed("hello") - * console.log(Filter.string(42)) // fail + * Filter.string("hello") // => Result.succeed("hello") + * Filter.string(42) // => Result.fail(42) * ``` * * @category constructors @@ -350,11 +360,11 @@ export const instanceOf = * * **Example** (Filtering numbers) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * - * console.log(Filter.number(42)) // Result.succeed(42) - * console.log(Filter.number("42")) // fail + * Filter.number(42) // => Result.succeed(42) + * Filter.number("42") // => Result.fail("42") * ``` * * @category constructors @@ -617,13 +627,14 @@ export const zipWith: { * * **Example** (Zipping filters) * - * ```ts - * import { Filter } from "effect" + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" * * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) * const evenNumbers = Filter.fromPredicate((n: number) => n % 2 === 0) * * const positiveAndEven = Filter.zip(positiveNumbers, evenNumbers) + * positiveAndEven(2) // => Result.succeed([2, 2]) * ``` * * @category combinators @@ -650,13 +661,14 @@ export const zip: { * * **Example** (Keeping the left filter result) * - * ```ts - * import { Filter } from "effect" + * ```ts import.meta.vitest + * import { Filter, Result } from "effect" * * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) * const evenNumbers = Filter.fromPredicate((n: number) => n % 2 === 0) * * const positiveEven = Filter.andLeft(positiveNumbers, evenNumbers) + * positiveEven(2) // => Result.succeed(2) * ``` * * @category combinators @@ -682,7 +694,7 @@ export const andLeft: { * * **Example** (Keeping the right filter result) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * const positiveNumbers = Filter.fromPredicate((n: number) => n > 0) @@ -691,6 +703,7 @@ export const andLeft: { * ) * * const positiveDoubled = Filter.andRight(positiveNumbers, doubleNumbers) + * positiveDoubled(2) // => Result.succeed(4) * ``` * * @category combinators @@ -716,7 +729,7 @@ export const andRight: { * * **Example** (Composing filters) * - * ```ts + * ```ts import.meta.vitest * import { Filter, Result } from "effect" * * const stringFilter = Filter.string @@ -725,6 +738,7 @@ export const andRight: { * ) * * const stringToUpper = Filter.compose(stringFilter, nonEmptyUpper) + * stringToUpper("hello") // => Result.succeed("HELLO") * ``` * * @category combinators diff --git a/packages/effect/src/Formatter.ts b/packages/effect/src/Formatter.ts index 426131f10..9121c252d 100644 --- a/packages/effect/src/Formatter.ts +++ b/packages/effect/src/Formatter.ts @@ -25,13 +25,12 @@ import { getRedacted, redact, symbolRedactable } from "./Redactable.ts" * * **Example** (Defining a custom formatter) * - * ```ts + * ```ts import.meta.vitest * import type { Formatter } from "effect" * * const upper: Formatter.Formatter = (s) => s.toUpperCase() * - * console.log(upper("hello")) - * // HELLO + * upper("hello") // => "HELLO" * ``` * * @see {@link format} @@ -58,6 +57,7 @@ export interface Formatter { * - Handles `BigInt`, `Symbol`, `Set`, `Map`, `Date`, `RegExp`, and class * instances that `JSON.stringify` cannot represent. * - Circular references are shown as `"[Circular]"` instead of throwing. + * - Failures while inspecting a value are rendered as diagnostic placeholders instead of throwing. * - Primitives: stringified naturally (`null`, `undefined`, `123`, `true`). * Strings are JSON-quoted. * - Objects with a custom `toString` (not `Object.prototype.toString`): @@ -75,37 +75,29 @@ export interface Formatter { * * **Example** (Formatting compact output) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.format({ a: 1, b: [2, 3] })) - * // {"a":1,"b":[2,3]} + * Formatter.format({ a: 1, b: [2, 3] }) // => "{\"a\":1,\"b\":[2,3]}" * ``` * * **Example** (Pretty-printed output) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.format({ a: 1, b: [2, 3] }, { space: 2 })) - * // { - * // "a": 1, - * // "b": [ - * // 2, - * // 3 - * // ] - * // } + * const output = Formatter.format({ a: 1, b: [2, 3] }, { space: 2 }) + * output // => "{\n \"a\": 1,\n \"b\": [\n 2,\n 3\n ]\n}" * ``` * * **Example** (Handling circular references) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * * const obj: any = { name: "loop" } * obj.self = obj - * console.log(Formatter.format(obj)) - * // {"name":"loop","self":[Circular]} + * Formatter.format(obj) // => "{\"name\":\"loop\",\"self\":[Circular]}" * ``` * * @see {@link formatJson} @@ -118,7 +110,7 @@ export function format(input: unknown, options?: { readonly ignoreToString?: boolean | undefined }): string { const space = options?.space ?? 0 - const seen = new WeakSet() + const ancestors = new WeakSet() const gap = !space ? "" : (typeof space === "number" ? " ".repeat(space) : space) const ind = (d: number) => gap.repeat(d) @@ -136,30 +128,15 @@ export function format(input: unknown, options?: { } function recur(v: unknown, d = 0): string { - if (Array.isArray(v)) { - if (seen.has(v)) return CIRCULAR - seen.add(v) - if (!gap || v.length <= 1) return `[${v.map((x) => recur(x, d)).join(",")}]` - const inner = v.map((x) => recur(x, d + 1)).join(",\n" + ind(d + 1)) - return `[\n${ind(d + 1)}${inner}\n${ind(d)}]` - } - - if (v instanceof Date) return formatDate(v) - - if ( - !options?.ignoreToString && - Predicate.hasProperty(v, "toString") && - typeof v["toString"] === "function" && - v["toString"] !== Object.prototype.toString && - v["toString"] !== Array.prototype.toString - ) { - const s = safeToString(v) - if (v instanceof Error && v.cause) { - return `${s} (cause: ${recur(v.cause, d)})` - } - return s + try { + return recurUnsafe(v, d) + } catch { + if ((typeof v === "object" && v !== null) || typeof v === "function") ancestors.delete(v) + return "[inspection threw]" } + } + function recurUnsafe(v: unknown, d = 0): string { if (typeof v === "string") return JSON.stringify(v) if ( @@ -172,24 +149,43 @@ export function format(input: unknown, options?: { if (typeof v === "bigint") return String(v) + "n" if (typeof v === "object" || typeof v === "function") { - if (seen.has(v)) return CIRCULAR - seen.add(v) + if (ancestors.has(v)) return CIRCULAR + ancestors.add(v) - if (symbolRedactable in v) return format(getRedacted(v as any)) - - if (Symbol.iterator in v) { - return `${v.constructor.name}(${recur(Array.from(v as any), d)})` + let output: string + if (symbolRedactable in v) { + output = recur(getRedacted(v as any), d) + } else if (Array.isArray(v)) { + output = !gap || v.length <= 1 + ? `[${v.map((x) => recur(x, d)).join(",")}]` + : `[\n${ind(d + 1)}${v.map((x) => recur(x, d + 1)).join(",\n" + ind(d + 1))}\n${ind(d)}]` + } else if (v instanceof Date) { + output = formatDate(v) + } else if ( + !options?.ignoreToString && + Predicate.hasProperty(v, "toString") && + typeof v["toString"] === "function" && + v["toString"] !== Object.prototype.toString && + v["toString"] !== Array.prototype.toString + ) { + const s = safeToString(v) + output = v instanceof Error && v.cause !== undefined ? `${s} (cause: ${recur(v.cause, d)})` : s + } else if (Symbol.iterator in v) { + output = `${v.constructor.name}(${recur(Array.from(v as any), d)})` + } else { + const keys = ownKeys(v) + if (!gap || keys.length <= 1) { + const body = `{${keys.map((k) => `${formatPropertyKey(k)}:${recur(safeGet(v, k), d)}`).join(",")}}` + output = wrap(v, body) + } else { + const body = `{\n${ + keys.map((k) => `${ind(d + 1)}${formatPropertyKey(k)}: ${recur(safeGet(v, k), d + 1)}`).join(",\n") + }\n${ind(d)}}` + output = wrap(v, body) + } } - - const keys = ownKeys(v) - if (!gap || keys.length <= 1) { - const body = `{${keys.map((k) => `${formatPropertyKey(k)}:${recur((v as any)[k], d)}`).join(",")}}` - return wrap(v, body) - } - const body = `{\n${ - keys.map((k) => `${ind(d + 1)}${formatPropertyKey(k)}: ${recur((v as any)[k], d + 1)}`).join(",\n") - }\n${ind(d)}}` - return wrap(v, body) + ancestors.delete(v) + return output } return String(v) @@ -239,6 +235,14 @@ function safeToString(input: any): string { } } +function safeGet(input: object, key: PropertyKey): unknown { + try { + return (input as any)[key] + } catch { + return "[property access threw]" + } +} + /** * Stringifies a value to JSON safely, silently dropping circular references. * @@ -253,40 +257,41 @@ function safeToString(input: any): string { * Uses `JSON.stringify` internally with a replacer that tracks the current * object ancestry. Circular references are replaced with `undefined`, which * omits them from object output. `Redactable` values are automatically redacted - * before serialization. Values not supported by JSON, such as `BigInt`, - * `Symbol`, `undefined`, and functions, follow standard `JSON.stringify` + * before serialization. `BigInt` values are stringified with an `n` suffix. + * Values not supported by JSON otherwise follow standard `JSON.stringify` * behavior. The `space` parameter controls indentation and defaults to `0`. * + * **Gotchas** + * + * When the root input is `undefined`, a symbol, or a function, `formatJson` + * returns `"null"` instead of the `undefined` returned by `JSON.stringify`. + * Nested values retain standard `JSON.stringify` behavior. + * * **Example** (Formatting compact JSON) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.formatJson({ name: "Alice", age: 30 })) - * // {"name":"Alice","age":30} + * Formatter.formatJson({ name: "Alice", age: 30 }) // => "{\"name\":\"Alice\",\"age\":30}" * ``` * * **Example** (Handling circular references) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * * const obj: any = { name: "test" } * obj.self = obj - * console.log(Formatter.formatJson(obj)) - * // {"name":"test"} + * Formatter.formatJson(obj) // => "{\"name\":\"test\"}" * ``` * * **Example** (Pretty-printed JSON) * - * ```ts + * ```ts import.meta.vitest * import { Formatter } from "effect" * - * console.log(Formatter.formatJson({ name: "Alice", age: 30 }, { space: 2 })) - * // { - * // "name": "Alice", - * // "age": 30 - * // } + * const output = Formatter.formatJson({ name: "Alice", age: 30 }, { space: 2 }) + * output // => "{\n \"name\": \"Alice\",\n \"age\": 30\n}" * ``` * * @see {@link format} @@ -300,8 +305,14 @@ export function formatJson(input: unknown, options?: { const ancestors: Array = [] return JSON.stringify( input, - function(this: unknown, _key: string, value: unknown) { - const redacted = redact(value) + function(this: object, key: string, value: unknown) { + const original = Object.getOwnPropertyDescriptor(this, key)?.value + const redacted = Predicate.hasProperty(original, symbolRedactable) + ? redact(original) + : redact(value) + if (typeof redacted === "bigint") { + return format(redacted) + } if (typeof redacted !== "object" || redacted === null) { return redacted } @@ -315,5 +326,5 @@ export function formatJson(input: unknown, options?: { return redacted }, options?.space - ) + ) ?? "null" } diff --git a/packages/effect/src/Function.ts b/packages/effect/src/Function.ts index 02506c83f..f12e50a74 100644 --- a/packages/effect/src/Function.ts +++ b/packages/effect/src/Function.ts @@ -21,7 +21,7 @@ import { pipeArguments } from "./Pipeable.ts" * * **Example** (Creating a function type with a type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { Function, HKT } from "effect" * * // Create a function type using the type lambda @@ -29,7 +29,7 @@ import { pipeArguments } from "./Pipeable.ts" * // Equivalent to: (a: string) => number * ``` * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface FunctionTypeLambda extends TypeLambda { @@ -53,7 +53,7 @@ export interface FunctionTypeLambda extends TypeLambda { * * **Example** (Selecting data-first or data-last style by arity) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe } from "effect" * * const sum = Function.dual< @@ -61,13 +61,13 @@ export interface FunctionTypeLambda extends TypeLambda { * (self: number, that: number) => number * >(2, (self, that) => self + that) * - * console.log(sum(2, 3)) // 5 - * console.log(pipe(2, sum(3))) // 5 + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 * ``` * * **Example** (Defining overloads with call signatures) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe } from "effect" * * const sum: { @@ -75,13 +75,13 @@ export interface FunctionTypeLambda extends TypeLambda { * (self: number, that: number): number * } = Function.dual(2, (self: number, that: number): number => self + that) * - * console.log(sum(2, 3)) // 5 - * console.log(pipe(2, sum(3))) // 5 + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 * ``` * * **Example** (Selecting data-first or data-last style with a predicate) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe } from "effect" * * const sum = Function.dual< @@ -92,8 +92,8 @@ export interface FunctionTypeLambda extends TypeLambda { * (self, that) => self + that * ) * - * console.log(sum(2, 3)) // 5 - * console.log(pipe(2, sum(3))) // 5 + * sum(2, 3) // => 5 + * pipe(2, sum(3)) // => 5 * ``` * * @category combinators @@ -169,11 +169,10 @@ export const dual: { * * **Example** (Applying an argument to a function) * - * ```ts + * ```ts import.meta.vitest * import { Function, pipe, String } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(pipe(String.length, Function.apply("hello")), 5) + * pipe(String.length, Function.apply("hello")) // => 5 * ``` * * @see {@link pipe} for building left-to-right pipelines @@ -192,10 +191,11 @@ export const apply = (a: A) => (self: (a: A) => B): B => self(a) * * **Example** (Creating a lazy argument) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" * * const constNull: Function.LazyArg = Function.constant(null) + * constNull() // => null * ``` * * @category models @@ -213,12 +213,11 @@ export type LazyArg = () => A * * **Example** (Typing a variadic function) * - * ```ts + * ```ts import.meta.vitest * import type { Function } from "effect" - * import * as assert from "node:assert" * * const sum: Function.FunctionN<[number, number], number> = (a, b) => a + b - * assert.deepStrictEqual(sum(2, 3), 5) + * sum(2, 3) // => 5 * ``` * * @category models @@ -235,11 +234,10 @@ export type FunctionN, B> = (...args: A) => B * * **Example** (Returning the same value) * - * ```ts + * ```ts import.meta.vitest * import { identity } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(identity(5), 5) + * identity(5) // => 5 * ``` * * @category combinators @@ -258,17 +256,14 @@ export const identity = (a: A): A => a * * **Example** (Checking an expression against a type) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * const test1 = Function.satisfies()(5 as const) + * const test1 = Function.satisfies()(5 as const) // => 5 * // ^? const test: 5 * // @ts-expect-error * const test2 = Function.satisfies()(5) * // ^? Argument of type 'number' is not assignable to parameter of type 'string' - * - * assert.deepStrictEqual(Function.satisfies()(5), 5) * ``` * * @see {@link cast} for changing only the static TypeScript type @@ -308,14 +303,13 @@ export const cast: (a: A) => B = identity as any * * **Example** (Creating a constant thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const constNull = Function.constant(null) * - * assert.deepStrictEqual(constNull(), null) - * assert.deepStrictEqual(constNull(), null) + * constNull() // => null + * constNull() // => null * ``` * * @category constructors @@ -332,11 +326,10 @@ export const constant = (value: A): LazyArg => () => value * * **Example** (Returning true from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constTrue(), true) + * Function.constTrue() // => true * ``` * * @category constants @@ -353,11 +346,10 @@ export const constTrue: LazyArg = constant(true) * * **Example** (Returning false from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constFalse(), false) + * Function.constFalse() // => false * ``` * * @category constants @@ -374,11 +366,10 @@ export const constFalse: LazyArg = constant(false) * * **Example** (Returning null from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constNull(), null) + * Function.constNull() // => null * ``` * * @category constants @@ -395,11 +386,10 @@ export const constNull: LazyArg = constant(null) * * **Example** (Returning undefined from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constUndefined(), undefined) + * Function.constUndefined() // => undefined * ``` * * @category constants @@ -417,11 +407,10 @@ export const constUndefined: LazyArg = constant(undefined) * * **Example** (Returning void from a thunk) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.constVoid(), undefined) + * Function.constVoid() // => undefined * ``` * * @category constants @@ -439,13 +428,12 @@ export const constVoid: LazyArg = constUndefined * * **Example** (Flipping curried arguments) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const f = (a: number) => (b: string) => a - b.length * - * assert.deepStrictEqual(Function.flip(f)("aaa")(2), -1) + * Function.flip(f)("aaa")(2) // => -1 * ``` * * @category combinators @@ -467,14 +455,13 @@ export const flip = , B extends Array, C>( * * **Example** (Composing two functions) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const increment = (n: number) => n + 1 * const square = (n: number) => n * n * - * assert.strictEqual(Function.compose(increment, square)(2), 9) + * Function.compose(increment, square)(2) // => 9 * ``` * * @see {@link flow} for composing a left-to-right sequence of functions @@ -504,7 +491,7 @@ export const compose: { * * **Example** (Handling impossible values) * - * ```ts + * ```ts import.meta.vitest * import { absurd } from "effect" * * const handleNever = (value: never) => { @@ -528,13 +515,12 @@ export const absurd = (_: never): A => { * * **Example** (Converting arguments to a tuple) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const sumTupled = Function.tupled((x: number, y: number): number => x + y) * - * assert.deepStrictEqual(sumTupled([1, 2]), 3) + * sumTupled([1, 2]) // => 3 * ``` * * @see {@link untupled} for adapting a tuple-argument function back to multiple arguments @@ -553,13 +539,12 @@ export const tupled = , B>(f: (...a: A) => B): * * **Example** (Converting a tuple to arguments) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * * const getFirst = Function.untupled((tuple: [A, B]): A => tuple[0]) * - * assert.deepStrictEqual(getFirst(1, 2), 1) + * getFirst(1, 2) // => 1 * ``` * * @see {@link tupled} for adapting a multi-argument function to one tuple argument @@ -593,82 +578,33 @@ export const untupled = , B>(f: (a: A) => B): ( * In this example, `1` is passed to the first function, and each result becomes * the input for the next function. * - * ```ts + * ```ts import.meta.vitest * import { pipe } from "effect" * - * const result = pipe( + * pipe( * 1, * (n) => n + 1, * (n) => n * 2, * (n) => `result: ${n}` - * ) - * - * console.log(result) // "result: 4" - * ``` - * - * **Example** (Chaining methods before conversion) - * - * ```ts - * const numbers = [1, 2, 3, 4] - * const double = (n: number) => n * 2 - * const greaterThanFour = (n: number) => n > 4 - * - * const result = numbers.map(double).filter(greaterThanFour) - * - * console.log(result) // [6, 8] + * ) // => "result: 4" * ``` * * **Example** (Rewriting method chains with pipe) * * The same transformation can be written with data-last functions. * - * ```ts + * ```ts import.meta.vitest * import { Array, pipe } from "effect" * * const numbers = [1, 2, 3, 4] * const double = (n: number) => n * 2 * const greaterThanFour = (n: number) => n > 4 * - * const result = pipe( + * pipe( * numbers, * Array.map(double), * Array.filter(greaterThanFour) - * ) - * - * console.log(result) // [6, 8] - * ``` - * - * **Example** (Chaining arithmetic operations) - * - * ```ts - * import { pipe } from "effect" - * - * // Define simple arithmetic operations - * const increment = (x: number) => x + 1 - * const double = (x: number) => x * 2 - * const subtractTen = (x: number) => x - 10 - * - * // Sequentially apply these operations using `pipe` - * const result = pipe(5, increment, double, subtractTen) - * - * console.log(result) - * // Output: 2 - * ``` - * - * **Example** (Building a simple transformation pipeline) - * - * ```ts - * import { pipe } from "effect" - * - * // Simple transformation pipeline - * const result = pipe( - * 5, - * (x) => x * 2, // 10 - * (x) => x + 1, // 11 - * (x) => x.toString() // "11" - * ) - * - * console.log(result) // "11" + * ) // => [6, 8] * ``` * * @category combinators @@ -1141,16 +1077,15 @@ export function pipe(a: unknown, ...args: Array): unknown { * * **Example** (Composing functions left to right) * - * ```ts + * ```ts import.meta.vitest * import { flow } from "effect" - * import * as assert from "node:assert" * * const len = (s: string): number => s.length * const double = (n: number): number => n * 2 * * const f = flow(len, double) * - * assert.strictEqual(f("aaa"), 6) + * f("aaa") // => 6 * ``` * * @see {@link pipe} for applying a value through a left-to-right sequence immediately @@ -1338,7 +1273,7 @@ export function flow( * * **Example** (Creating a development placeholder) * - * ```ts + * ```ts import.meta.vitest * import { hole } from "effect" * * // Intentionally not called: `hole` throws if the placeholder is evaluated. @@ -1347,7 +1282,6 @@ export function flow( * name: hole() * }) * - * console.log(typeof buildUser) // "function" * ``` * * @category utility types @@ -1366,11 +1300,10 @@ export const hole: () => T = cast(absurd) * * **Example** (Discarding the first argument) * - * ```ts + * ```ts import.meta.vitest * import { Function } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Function.SK(0, "hello"), "hello") + * Function.SK(0, "hello") // => "hello" * ``` * * @category combinators @@ -1390,11 +1323,12 @@ export const SK = (_: A, b: B): B => b * **Details** * * Each memoized wrapper owns a private `WeakMap` keyed by object identity. - * Cached `undefined` results are still returned because the cache is checked - * with `WeakMap.has`. * * **Gotchas** * + * `undefined` is reserved to represent a cache miss and is therefore not + * supported as a return value. + * * Structurally equal objects do not share cache entries. If the same object is * mutated after its first call, later calls still return the cached result for * that reference. @@ -1402,14 +1336,50 @@ export const SK = (_: A, b: B): B => b * @category caching * @since 4.0.0 */ -export function memoize(f: (a: A) => O): (ast: A) => O { +export function memoize(f: (a: A) => O): (ast: A) => O { const cache = new WeakMap() return (a) => { - if (cache.has(a)) { - return cache.get(a)! - } + const cached = cache.get(a) + if (cached !== undefined) return cached + const result = f(a) + cache.set(a, result) + return result + } +} + +/** + * Creates a memoized idempotent object transformation that caches both inputs + * and their outputs by object identity. + * + * **When to use** + * + * Use when an object transformation is idempotent and its output can be safely + * reused as a fixed point. + * + * **Details** + * + * After computing an input, the returned function caches both the input and + * the output. Calling it with either reference returns the output without + * invoking the supplied function again. + * + * **Gotchas** + * + * The returned function treats each computed output as a fixed point. If + * applying the supplied function to an output would produce an observably + * different value, this memoization changes that behavior. + * + * @see {@link memoize} for memoizing functions without an idempotence requirement + * @category caching + * @since 4.0.0 + */ +export function memoizeIdempotent(f: (a: A) => A): (a: A) => A { + const cache = new WeakMap() + return (a) => { + const cached = cache.get(a) + if (cached !== undefined) return cached const result = f(a) cache.set(a, result) + cache.set(result, result) return result } } diff --git a/packages/effect/src/Graph.ts b/packages/effect/src/Graph.ts index 0295cc65d..7f9ae698b 100644 --- a/packages/effect/src/Graph.ts +++ b/packages/effect/src/Graph.ts @@ -14,14 +14,14 @@ import * as Equal from "./Equal.ts" import { dual } from "./Function.ts" import * as Hash from "./Hash.ts" import type { Inspectable } from "./Inspectable.ts" -import { NodeInspectSymbol } from "./Inspectable.ts" +import * as internal from "./internal/graph.ts" +import * as csr from "./internal/graphCsr.ts" +import * as MutableHashMap from "./MutableHashMap.ts" import * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" -import { pipeArguments } from "./Pipeable.ts" -import { hasProperty } from "./Predicate.ts" -import type { Mutable } from "./Types.ts" +import type { Covariant, Invariant } from "./Types.ts" -const TypeId = "~effect/collections/Graph" +const TypeId = internal.TypeId /** * Node index for node identification using plain numbers. @@ -41,7 +41,6 @@ const TypeId = "~effect/collections/Graph" * are not reused. * * @see {@link EdgeIndex} for edge identifiers instead of node identifiers - * @see {@link addNode} for creating node identifiers * * @category models * @since 3.18.0 @@ -62,9 +61,6 @@ export type NodeIndex = number * identifiers are not reused. * * @see {@link NodeIndex} for node identifiers instead of edge identifiers - * @see {@link Edge} for the edge value addressed by this identifier - * @see {@link addEdge} for creating edge identifiers - * @see {@link getEdge} for reading edges by identifier * * @category models * @since 3.18.0 @@ -79,18 +75,14 @@ export type EdgeIndex = number * Use as the graph edge value that carries source node, target node, and stored * edge data together. * - * @see {@link getEdge} for reading a single edge by identifier - * @see {@link addEdge} for adding edges to a graph - * @see {@link edges} for iterating graph edges - * * @category models * @since 3.18.0 */ -export class Edge extends Data.Class<{ +export interface Edge { readonly source: NodeIndex readonly target: NodeIndex readonly data: E -}> {} +} /** * Graph type for distinguishing directed and undirected graphs. @@ -100,34 +92,58 @@ export class Edge extends Data.Class<{ * Use when writing graph-polymorphic types or helpers that need to preserve * whether a graph is directed or undirected. * - * @see {@link Graph} for immutable graphs parameterized by kind - * @see {@link MutableGraph} for mutable graphs parameterized by kind - * * @category models * @since 3.18.0 */ export type Kind = "directed" | "undirected" /** - * Common structural interface shared by immutable and mutable graphs. + * A node and its stable index in a graph snapshot. + * + * @category models + * @since 4.0.0 + */ +export interface IndexedNode { + readonly index: NodeIndex + readonly data: N +} + +/** + * An edge and its stable index in a graph snapshot. + * + * @category models + * @since 4.0.0 + */ +export interface IndexedEdge extends Edge { + readonly index: EdgeIndex +} + +/** + * Active indexed structure used to reconstruct an immutable graph. + * + * **When to use** + * + * Use when serializing or importing graph structure while preserving active + * node and edge identifiers. * * **Details** * - * Contains the node and edge maps, adjacency indexes, allocation counters, and - * shared protocols used by both `Graph` and `MutableGraph`. + * Node and edge indexes must be non-negative safe integers in strictly + * increasing order. Every edge endpoint must reference an indexed node. + * + * **Gotchas** + * + * A snapshot records only active identifiers, not allocator history. After + * reconstruction, new identifiers continue after the greatest active index. * + * @see {@link fromSnapshot} for reconstructing a graph * @category models - * @since 3.18.0 + * @since 4.0.0 */ -export interface Proto extends Iterable, Equal.Equal, Pipeable, Inspectable { - readonly [TypeId]: typeof TypeId - readonly nodes: Map - readonly edges: Map> - readonly adjacency: Map> - readonly reverseAdjacency: Map> - nextNodeIndex: NodeIndex - nextEdgeIndex: EdgeIndex - acyclic: Option.Option +export interface Snapshot { + readonly type: T + readonly nodes: ReadonlyArray> + readonly edges: ReadonlyArray> } /** @@ -138,18 +154,43 @@ export interface Proto extends Iterable, * Use as the immutable graph model for code that queries, traverses, * transforms, or analyzes graph structure without mutating it. * + * **Gotchas** + * + * After a graph is hashed, its transitively contained node and edge payloads + * used by hashing must remain immutable, as with other Effect values. + * * @see {@link MutableGraph} for the mutable counterpart used inside mutation scopes - * @see {@link DirectedGraph} for a `Graph` fixed to directed edges - * @see {@link UndirectedGraph} for a `Graph` fixed to undirected edges * * @category models * @since 3.18.0 */ -export interface Graph extends Proto { +export interface Graph + extends Iterable, Equal.Equal, Pipeable, Inspectable +{ + readonly [TypeId]: Graph.Variance readonly type: T readonly mutable: false } +/** + * Companion namespace containing type-level metadata for immutable graphs. + * + * @category models + * @since 4.0.0 + */ +export declare namespace Graph { + /** + * Type-level variance marker for immutable graphs. + * + * @category utility types + * @since 4.0.0 + */ + export interface Variance { + readonly _N: Covariant + readonly _E: Covariant + } +} + /** * Mutable graph interface. * @@ -158,19 +199,52 @@ export interface Graph extends Proto< * Use when adding, removing, or updating nodes and edges inside a graph * mutation scope. * + * **Gotchas** + * + * A callback invoked by another graph operation may query the same mutable + * graph, but cannot mutate or finalize it. Mutation is allowed in callbacks + * passed to graph constructors and `mutate`, where mutation is the purpose. + * * @see {@link Graph} for the immutable graph interface * @see {@link mutate} for scoped mutation of an immutable graph - * @see {@link beginMutation} for opening a mutable graph manually - * @see {@link endMutation} for returning to an immutable graph * * @category models * @since 3.18.0 */ -export interface MutableGraph extends Proto { +export interface MutableGraph + extends Iterable, Equal.Equal, Pipeable, Inspectable +{ + readonly [TypeId]: MutableGraph.Variance readonly type: T readonly mutable: true } +/** + * Companion namespace containing type-level metadata for scoped mutable graphs. + * + * @category models + * @since 4.0.0 + */ +export declare namespace MutableGraph { + /** + * Type-level variance marker for scoped mutable graphs. + * + * @category utility types + * @since 4.0.0 + */ + export interface Variance { + readonly _N: Invariant + readonly _E: Invariant + } +} + +/** @internal */ +const copyEdge = (edge: Edge): Edge => ({ + source: edge.source, + target: edge.target, + data: edge.data +}) + /** * Immutable graph type for source-to-target relationships. * @@ -185,9 +259,7 @@ export interface MutableGraph extends * `N` and edge data of type `E`. * * @see {@link directed} for constructing directed graphs - * @see {@link Graph} for the generic immutable graph type * @see {@link UndirectedGraph} for graphs whose edges connect both endpoints - * @see {@link MutableDirectedGraph} for the mutable directed graph type * * @category models * @since 3.18.0 @@ -208,7 +280,6 @@ export type DirectedGraph = Graph * * @see {@link undirected} for constructing undirected graphs * @see {@link DirectedGraph} for graphs whose edges have source-to-target direction - * @see {@link MutableUndirectedGraph} for the mutable undirected graph type * * @category models * @since 3.18.0 @@ -224,7 +295,6 @@ export type UndirectedGraph = Graph * whose edges have source-to-target direction. * * @see {@link MutableGraph} for the generic mutable graph type - * @see {@link DirectedGraph} for the immutable directed graph type * @see {@link MutableUndirectedGraph} for mutable graphs without edge direction * * @category models @@ -241,7 +311,6 @@ export type MutableDirectedGraph = MutableGraph * whose edges connect both endpoints without direction. * * @see {@link MutableDirectedGraph} for mutable graphs with directed edges - * @see {@link UndirectedGraph} for the immutable undirected graph type * @see {@link MutableGraph} for the generic mutable graph type * * @category models @@ -249,81 +318,6 @@ export type MutableDirectedGraph = MutableGraph */ export type MutableUndirectedGraph = MutableGraph -// ============================================================================= -// Proto Objects -// ============================================================================= - -/** @internal */ -const ProtoGraph = { - [TypeId]: TypeId, - [Symbol.iterator](this: Graph) { - return this.nodes[Symbol.iterator]() - }, - [NodeInspectSymbol](this: Graph) { - return this.toJSON() - }, - [Equal.symbol](this: Graph, that: Equal.Equal): boolean { - if (isGraph(that)) { - if ( - this.nodes.size !== that.nodes.size || - this.edges.size !== that.edges.size || - this.type !== that.type - ) { - return false - } - // Compare nodes - for (const [nodeIndex, nodeData] of this.nodes) { - if (!that.nodes.has(nodeIndex)) { - return false - } - const otherNodeData = that.nodes.get(nodeIndex)! - if (!Equal.equals(nodeData, otherNodeData)) { - return false - } - } - // Compare edges - for (const [edgeIndex, edgeData] of this.edges) { - if (!that.edges.has(edgeIndex)) { - return false - } - const otherEdge = that.edges.get(edgeIndex)! - if (!Equal.equals(edgeData, otherEdge)) { - return false - } - } - return true - } - return false - }, - [Hash.symbol](this: Graph): number { - let hash = Hash.string("Graph") - hash = hash ^ Hash.string(this.type) - hash = hash ^ Hash.number(this.nodes.size) - hash = hash ^ Hash.number(this.edges.size) - for (const [nodeIndex, nodeData] of this.nodes) { - hash = hash ^ (Hash.hash(nodeIndex) + Hash.hash(nodeData)) - } - for (const [edgeIndex, edgeData] of this.edges) { - hash = hash ^ (Hash.hash(edgeIndex) + Hash.hash(edgeData)) - } - return hash - }, - toJSON(this: Graph) { - return { - _id: "Graph", - nodeCount: this.nodes.size, - edgeCount: this.edges.size, - type: this.type - } - }, - toString(this: Graph) { - return `Graph(${this.type}, ${this.nodes.size}, ${this.edges.size})` - }, - pipe() { - return pipeArguments(this, arguments) - } -} - // ============================================================================= // Errors // ============================================================================= @@ -350,13 +344,49 @@ export class GraphError extends Data.TaggedError("GraphError")<{ /** @internal */ const missingNode = (node: number) => new GraphError({ message: `Node ${node} does not exist` }) +/** @internal */ +const traversalRadius = (radius: number | undefined, defaultRadius: number): number => { + const value = radius ?? defaultRadius + if (value !== Infinity && (!Number.isInteger(value) || value < 0)) { + throw new GraphError({ message: "Traversal radius must be a non-negative integer or Infinity" }) + } + return value +} + +/** @internal */ +function assertMutable( + graph: Graph | MutableGraph +): asserts graph is MutableGraph { + if (!graph.mutable) { + throw new GraphError({ message: "Graph is not mutable" }) + } +} + +/** @internal */ +const getMutableImplForMutation = ( + graph: MutableGraph +): internal.GraphImpl => { + assertMutable(graph) + if (internal.isTransforming(graph)) { + throw new GraphError({ message: "Cannot mutate graph during a transformation" }) + } + csr.invalidate(graph) + return internal.toImpl(graph) +} + +/** @internal */ +const withMutationGuard = ( + graph: Graph | MutableGraph, + evaluate: () => A +): A => graph.mutable ? internal.withTransformation(graph, evaluate) : evaluate() + // ============================================================================= // Constructors // ============================================================================= /** * Returns `true` if a value has the graph runtime type identifier, narrowing - * it to a `Graph`. + * it to an immutable or mutable graph. * * **When to use** * @@ -365,19 +395,212 @@ const missingNode = (node: number) => new GraphError({ message: `Node ${node} do * **Gotchas** * * This guard checks the shared graph runtime type identifier and does not - * distinguish immutable graphs from mutable graphs. + * distinguish immutable graphs from mutable graphs or directed graphs from + * undirected graphs. * * @category guards * @since 4.0.0 */ -export const isGraph = (u: unknown): u is Graph => hasProperty(u, TypeId) +export const isGraph: ( + u: U | Graph | MutableGraph +) => u is Graph | MutableGraph = internal.isGraph + +/** + * Reconstructs an immutable graph from its indexed active structure. + * + * **When to use** + * + * Use when importing a snapshot or other externally indexed graph structure. + * Prefer `directed` or `undirected` when creating a new graph without existing + * identifiers. + * + * **Gotchas** + * + * The node and edge arrays must be ordered by strictly increasing, + * non-negative safe integer indexes, and every edge endpoint must reference a + * node in the snapshot. Invalid snapshots throw a `GraphError`. Historical + * removed identifiers after the greatest active index are not retained. + * + * **Example** (Preserving graph indexes) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.fromSnapshot({ + * type: "directed", + * nodes: [{ index: 2, data: "A" }, { index: 5, data: "B" }], + * edges: [{ index: 3, source: 2, target: 5, data: 1 }] + * }) + * + * Graph.toSnapshot(graph).edges[0].index // => 3 + * ``` + * + * @see {@link toSnapshot} for capturing a graph snapshot + * @category constructors + * @since 4.0.0 + */ +export const fromSnapshot = (snapshot: Snapshot): Graph => { + if (snapshot.type !== "directed" && snapshot.type !== "undirected") { + throw new GraphError({ message: "Snapshot type must be directed or undirected" }) + } + + let previous = -1 + const nodeIndexes = new Set() + for (let i = 0; i < snapshot.nodes.length; i++) { + const node = snapshot.nodes[i] + if (node === undefined || node === null) { + throw new GraphError({ message: `Node at position ${i} must be defined` }) + } + const index = node.index + if (!Number.isSafeInteger(index) || index < 0) { + throw new GraphError({ message: `Node index at position ${i} must be a non-negative safe integer` }) + } + if (index <= previous) { + throw new GraphError({ message: "Node indexes must be strictly increasing" }) + } + previous = index + nodeIndexes.add(index) + } + + previous = -1 + for (let i = 0; i < snapshot.edges.length; i++) { + const edge = snapshot.edges[i] + if (edge === undefined || edge === null) { + throw new GraphError({ message: `Edge at position ${i} must be defined` }) + } + if (!Number.isSafeInteger(edge.index) || edge.index < 0) { + throw new GraphError({ message: `Edge index at position ${i} must be a non-negative safe integer` }) + } + if (edge.index <= previous) { + throw new GraphError({ message: "Edge indexes must be strictly increasing" }) + } + previous = edge.index + if (!Number.isSafeInteger(edge.source) || edge.source < 0) { + throw new GraphError({ message: `Edge source at position ${i} must be a non-negative safe integer` }) + } + if (!nodeIndexes.has(edge.source)) { + throw new GraphError({ message: `Edge source ${edge.source} does not reference a node` }) + } + if (!Number.isSafeInteger(edge.target) || edge.target < 0) { + throw new GraphError({ message: `Edge target at position ${i} must be a non-negative safe integer` }) + } + if (!nodeIndexes.has(edge.target)) { + throw new GraphError({ message: `Edge target ${edge.target} does not reference a node` }) + } + } + + return internal.hydrate(snapshot) +} + +/** + * Returns the active indexed structure of a graph. + * + * **When to use** + * + * Use when serializing a graph or passing its active structure across a + * boundary where node and edge identifiers must be preserved. + * + * **Details** + * + * Nodes and edges are returned in graph order with their current indexes. + * Undirected edges retain their stored endpoint orientation, and each returned + * node and edge record is newly allocated. The operation runs in `O(V + E)`. + * + * **Gotchas** + * + * Node and edge payloads are not cloned. The snapshot also omits allocator + * history for identifiers that are no longer active. + * + * **Example** (Round-tripping a graph snapshot) + * + * ```ts import.meta.vitest + * import { Equal, Graph } from "effect" + * + * const graph = Graph.fromSnapshot({ + * type: "undirected", + * nodes: [{ index: 2, data: "A" }, { index: 5, data: "B" }], + * edges: [{ index: 3, source: 5, target: 2, data: "A-B" }] + * }) + * + * Equal.equals(Graph.fromSnapshot(Graph.toSnapshot(graph)), graph) // => true + * ``` + * + * @see {@link fromSnapshot} for reconstructing an immutable graph + * @category converting + * @since 4.0.0 + */ +export const toSnapshot = ( + graph: Graph | MutableGraph +): Snapshot => { + const impl = internal.toImpl(graph) + return { + type: graph.type, + nodes: Array.from(impl.nodes, ([index, data]) => ({ index, data })), + edges: Array.from(impl.edges, ([index, edge]) => ({ + index, + source: edge.source, + target: edge.target, + data: edge.data + })) + } +} + +/** + * Creates a graph constructor for the specified graph kind. + * + * **When to use** + * + * Use when the graph kind is selected dynamically. Prefer `directed` or + * `undirected` when the kind is known statically. + * + * **Example** (Constructing by kind) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const makeGraph = Graph.make("directed") + * const graph = makeGraph((mutable) => { + * Graph.addNode(mutable, "A") + * }) + * + * graph.type // => "directed" + * ``` + * + * @see {@link directed} for constructing a directed graph directly + * @see {@link undirected} for constructing an undirected graph directly + * @category constructors + * @since 4.0.0 + */ +export const make = + (type: T) => (mutate?: (mutable: MutableGraph) => undefined): Graph => { + if (type !== "directed" && type !== "undirected") { + throw new GraphError({ message: "Graph type must be directed or undirected" }) + } + if (mutate === undefined) { + return internal.make(type, false) as unknown as Graph + } + + const graph = internal.make(type, true) + const mutable = Equal.byReferenceUnsafe(graph as unknown as MutableGraph) + return mutateScoped(mutable, mutate) + } /** * Creates a directed graph, optionally with initial mutations. * + * **When to use** + * + * Use when relationships have a source-to-target direction, such as + * dependencies, workflows, or routing links. + * + * **Gotchas** + * + * The mutable callback handle is finalized when the callback returns and must + * not be retained for later mutation. + * * **Example** (Creating a directed graph) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Directed graph with initial nodes and edges @@ -388,38 +611,32 @@ export const isGraph = (u: unknown): u is Graph => hasProperty * Graph.addEdge(mutable, a, b, "A->B") * Graph.addEdge(mutable, b, c, "B->C") * }) + * Array.of(Graph.nodeCount(graph), Graph.edgeCount(graph)) // => [3, 2] * ``` * * @category constructors * @since 3.18.0 */ -export const directed = (mutate?: (mutable: MutableDirectedGraph) => void): DirectedGraph => { - const graph: Mutable> = Object.create(ProtoGraph) - graph.type = "directed" - graph.nodes = new Map() - graph.edges = new Map() - graph.adjacency = new Map() - graph.reverseAdjacency = new Map() - graph.nextNodeIndex = 0 - graph.nextEdgeIndex = 0 - graph.acyclic = Option.some(true) - graph.mutable = false - - if (mutate) { - const mutable = beginMutation(graph as DirectedGraph) - mutate(mutable as MutableDirectedGraph) - return endMutation(mutable) - } - - return graph -} +export const directed: ( + mutate?: (mutable: MutableDirectedGraph) => undefined +) => DirectedGraph = make("directed") /** * Creates an undirected graph, optionally with initial mutations. * + * **When to use** + * + * Use when relationships connect both endpoints symmetrically, such as social + * connections or physical links. + * + * **Gotchas** + * + * The mutable callback handle is finalized when the callback returns and must + * not be retained for later mutation. + * * **Example** (Creating an undirected graph) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * // Undirected graph with initial nodes and edges @@ -430,121 +647,139 @@ export const directed = (mutate?: (mutable: MutableDirectedGraph) => * Graph.addEdge(mutable, a, b, "A-B") * Graph.addEdge(mutable, b, c, "B-C") * }) + * Array.of(Graph.nodeCount(graph), Graph.edgeCount(graph)) // => [3, 2] * ``` * * @category constructors * @since 3.18.0 */ -export const undirected = (mutate?: (mutable: MutableUndirectedGraph) => void): UndirectedGraph => { - const graph: Mutable> = Object.create(ProtoGraph) - graph.type = "undirected" - graph.nodes = new Map() - graph.edges = new Map() - graph.adjacency = new Map() - graph.reverseAdjacency = new Map() - graph.nextNodeIndex = 0 - graph.nextEdgeIndex = 0 - graph.acyclic = Option.some(true) - graph.mutable = false - - if (mutate) { - const mutable = beginMutation(graph) - mutate(mutable as MutableUndirectedGraph) - return endMutation(mutable) - } - - return graph -} +export const undirected: ( + mutate?: (mutable: MutableUndirectedGraph) => undefined +) => UndirectedGraph = make("undirected") // ============================================================================= // Scoped Mutable API // ============================================================================= /** - * Creates a mutable scope for safe graph mutations by copying the data structure. + * Creates a mutable copy of an immutable graph for a manual mutation scope. + * + * **When to use** + * + * Use when a mutation scope must span code that cannot be expressed as one + * `mutate` callback. + * + * **Gotchas** + * + * The graph structure is copied, but node and edge payload objects remain + * shared by reference. Always finish the scope with `endMutation`; prefer + * `mutate` when a callback is sufficient. * * **Example** (Beginning a mutation scope) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed() * const mutable = Graph.beginMutation(graph) * // Now mutable can be safely modified without affecting original graph + * Array.of(Graph.nodeCount(mutable), Graph.nodeCount(graph)) // => [0, 0] * ``` * + * @see {@link endMutation} for finalizing the mutable graph + * @see {@link mutate} for automatically scoped mutation * @category mutations * @since 3.18.0 */ export const beginMutation = ( graph: Graph ): MutableGraph => { - // Copy adjacency maps with deep cloned arrays - const adjacency = new Map>() - const reverseAdjacency = new Map>() - - for (const [nodeIndex, edges] of graph.adjacency) { - adjacency.set(nodeIndex, [...edges]) - } - - for (const [nodeIndex, edges] of graph.reverseAdjacency) { - reverseAdjacency.set(nodeIndex, [...edges]) - } - - const mutable: Mutable> = Object.create(ProtoGraph) - mutable.type = graph.type - mutable.nodes = new Map(graph.nodes) - mutable.edges = new Map(graph.edges) - mutable.adjacency = adjacency - mutable.reverseAdjacency = reverseAdjacency - mutable.nextNodeIndex = graph.nextNodeIndex - mutable.nextEdgeIndex = graph.nextEdgeIndex - mutable.acyclic = graph.acyclic - mutable.mutable = true - - return mutable + const source = internal.toImpl(graph) + const mutable = internal.clone(source, true) + return Equal.byReferenceUnsafe(mutable as unknown as MutableGraph) } /** * Converts a mutable graph back to an immutable graph, ending the mutation scope. * + * **When to use** + * + * Use to finish a mutation scope opened with `beginMutation`. + * + * **Gotchas** + * + * Finalization is terminal. Later public mutation operations on the same + * mutable handle fail with a `GraphError`. + * * **Example** (Ending a mutation scope) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed() * const mutable = Graph.beginMutation(graph) * // ... perform mutations on mutable ... - * const newGraph = Graph.endMutation(mutable) + * Graph.nodeCount(Graph.endMutation(mutable)) // => 0 * ``` * + * @see {@link beginMutation} for opening a manual mutation scope + * @see {@link mutate} for automatically scoped mutation * @category mutations * @since 3.18.0 */ export const endMutation = ( mutable: MutableGraph ): Graph => { - const graph: Mutable> = Object.create(ProtoGraph) - graph.type = mutable.type - graph.nodes = new Map(mutable.nodes) - graph.edges = new Map(mutable.edges) - graph.adjacency = mutable.adjacency - graph.reverseAdjacency = mutable.reverseAdjacency - graph.nextNodeIndex = mutable.nextNodeIndex - graph.nextEdgeIndex = mutable.nextEdgeIndex - graph.acyclic = mutable.acyclic - graph.mutable = false - - return graph + assertMutable(mutable) + if (internal.isTransforming(mutable)) { + throw new GraphError({ message: "Cannot mutate graph during a transformation" }) + } + const source = internal.toImpl(mutable) + csr.invalidate(mutable) + const graph = internal.finalize(source) + source.mutable = false + + return graph as unknown as Graph +} + +/** @internal */ +const mutateScoped = ( + mutable: MutableGraph, + f: (mutable: MutableGraph) => undefined +): Graph => { + try { + f(mutable) + } catch (error) { + if (mutable.mutable) { + endMutation(mutable) + } + throw error + } + return endMutation(mutable) } /** - * Performs scoped mutations on a graph, automatically managing the mutation lifecycle. + * Returns an immutable graph after applying scoped mutations to a structural copy. + * + * **When to use** + * + * Use when several node or edge + * mutations should be applied together. + * + * **Details** + * + * The original graph remains structurally unchanged. The mutable callback + * handle is finalized whether the callback returns or throws. + * + * **Gotchas** + * + * Payload objects are shared unless the callback replaces them. A callback + * failure is rethrown after the mutable handle is finalized, and the handle + * must not escape for later mutation. * * **Example** (Applying scoped mutations) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed() @@ -554,2410 +789,4823 @@ export const endMutation = ( * Graph.addEdge(mutable, nodeA, nodeB, 1) * }) * - * console.log(Graph.nodeCount(newGraph)) // 2 - * console.log(Graph.edgeCount(newGraph)) // 1 + * Graph.nodeCount(newGraph) // => 2 + * Graph.edgeCount(newGraph) // => 1 * ``` * + * @see {@link beginMutation} for opening a manual mutation scope + * @see {@link endMutation} for finalizing a manual mutation scope * @category mutations * @since 3.18.0 */ export const mutate: { ( - f: (mutable: MutableGraph) => void + f: (mutable: MutableGraph) => undefined ): (graph: Graph) => Graph ( graph: Graph, - f: (mutable: MutableGraph) => void + f: (mutable: MutableGraph) => undefined ): Graph } = dual(2, ( graph: Graph, - f: (mutable: MutableGraph) => void + f: (mutable: MutableGraph) => undefined ): Graph => { const mutable = beginMutation(graph) - f(mutable) - return endMutation(mutable) + return mutateScoped(mutable, f) }) // ============================================================================= -// Basic Node Operations +// Set Operations // ============================================================================= +/** @internal */ +type NodeMaps = { + readonly byIdentity: MutableHashMap.MutableHashMap + readonly byIndex: Map +} + +/** @internal */ +class EdgeIdentity implements Equal.Equal { + readonly type: Kind + readonly source: NI + readonly target: NI + readonly identity: EI + + constructor( + type: Kind, + source: NI, + target: NI, + identity: EI + ) { + this.type = type + this.source = source + this.target = target + this.identity = identity + } + + [Equal.symbol](that: Equal.Equal): boolean { + if (!(that instanceof EdgeIdentity) || this.type !== that.type || !Equal.equals(this.identity, that.identity)) { + return false + } + + if (this.type === "directed") { + return Equal.equals(this.source, that.source) && Equal.equals(this.target, that.target) + } + + return ( + (Equal.equals(this.source, that.source) && Equal.equals(this.target, that.target)) || + (Equal.equals(this.source, that.target) && Equal.equals(this.target, that.source)) + ) + } + + [Hash.symbol](): number { + const hash = Hash.hash(this.identity) + return this.type === "directed" + ? Hash.combine(Hash.hash(this.target))(Hash.combine(Hash.hash(this.source))(hash)) + : Hash.optimize(hash ^ (Hash.hash(this.source) + Hash.hash(this.target))) + } +} + /** - * Adds a new node to a mutable graph and returns its index. + * Configures node and edge identity for graph set operations. * * **When to use** * - * Use to allocate a new node in a mutable graph before storing edges or - * querying it by index. + * Use when logical graph membership should be based on a stable key rather + * than the complete node or edge payload. * * **Details** * - * The returned index is allocated from the graph's next node index. The mutable - * graph stores the node data and initializes empty incoming and outgoing edge - * indexes for the new node. + * Both functions default to using the complete node or edge data. Edge identity + * also includes the identities of its endpoint nodes and the graph kind. + * Projected identities use Effect equality and hashing semantics. * * **Gotchas** * - * `NodeIndex` values are identifiers and are not reused after removals. - * - * **Example** (Adding nodes) - * - * ```ts - * import { Graph } from "effect" - * - * const result = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * console.log(nodeA) // NodeIndex with value 0 - * console.log(nodeB) // NodeIndex with value 1 - * }) - * ``` - * - * @see {@link mutate} for obtaining a mutable graph from an immutable graph - * @see {@link addEdge} for connecting existing nodes - * @see {@link removeNode} for removing nodes from a mutable graph + * Edge identity defines set membership, not edge multiplicity. Parallel edges + * with the same endpoint identities and projected edge identity are treated as + * the same member by graph set operations. * - * @category mutations - * @since 3.18.0 + * @category configuration + * @since 4.0.0 */ -export const addNode = ( - mutable: MutableGraph, - data: N -): NodeIndex => { - const nodeIndex = mutable.nextNodeIndex +export interface IdentityOptions { + readonly nodeIdentity?: (node: N) => NI + readonly edgeIdentity?: (edge: E) => EI +} - // Add node data - mutable.nodes.set(nodeIndex, data) +/** @internal */ +const buildNodeMaps = ( + graph: Graph, + identity: (node: N) => I +): NodeMaps => { + const impl = internal.toImpl(graph) + const byIdentity = MutableHashMap.empty() + const byIndex = new Map() + + for (const [index, data] of impl.nodes) { + const nodeIdentity = identity(data) + MutableHashMap.set(byIdentity, nodeIdentity, data) + byIndex.set(index, nodeIdentity) + } - // Initialize empty adjacency lists - mutable.adjacency.set(nodeIndex, []) - mutable.reverseAdjacency.set(nodeIndex, []) + return { byIdentity, byIndex } +} - // Update graph allocators - mutable.nextNodeIndex = mutable.nextNodeIndex + 1 +/** @internal */ +const nodeIdentityAt = (maps: NodeMaps, index: NodeIndex): I => maps.byIndex.get(index) as I - return nodeIndex +/** @internal */ +const buildEdgeMap = ( + graph: Graph, + nodeMaps: NodeMaps, + identity: (edge: E) => EI +): MutableHashMap.MutableHashMap, E> => { + const impl = internal.toImpl(graph) + const edges = MutableHashMap.empty, E>() + for (const edge of impl.edges.values()) { + const sourceIdentity = nodeIdentityAt(nodeMaps, edge.source) + const targetIdentity = nodeIdentityAt(nodeMaps, edge.target) + MutableHashMap.set( + edges, + new EdgeIdentity(graph.type, sourceIdentity, targetIdentity, identity(edge.data)), + edge.data + ) + } + return edges +} + +/** @internal */ +const addNodesByIdentity = ( + mutable: MutableGraph, + nodes: Iterable +): MutableHashMap.MutableHashMap => { + const indexByIdentity = MutableHashMap.empty() + for (const [identity, data] of nodes) { + MutableHashMap.set(indexByIdentity, identity, addNode(mutable, data)) + } + return indexByIdentity +} + +/** @internal */ +const addEdgeByIdentity = ( + mutable: MutableGraph, + indexByIdentity: MutableHashMap.MutableHashMap, + identity: EdgeIdentity, + data: E +): void => { + const sourceIndex = Option.getOrUndefined(MutableHashMap.get(indexByIdentity, identity.source)) + const targetIndex = Option.getOrUndefined(MutableHashMap.get(indexByIdentity, identity.target)) + if (sourceIndex !== undefined && targetIndex !== undefined) { + addEdge(mutable, sourceIndex, targetIndex, data) + } +} + +/** @internal */ +const assertSameKind = (self: Graph, that: Graph): void => { + if (self.type !== that.type) { + throw new GraphError({ message: `Cannot combine ${self.type} and ${that.type} graphs` }) + } } /** - * Gets the data associated with a node index safely, if it exists. + * Composes two graphs, merging nodes by identity. * - * **Example** (Getting node data) + * **When to use** * - * ```ts - * import { Graph, Option } from "effect" + * Use when combining graphs that describe overlapping logical entities and + * should merge those entities by payload or a projected identity. * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * Graph.addNode(mutable, "Node A") - * }) + * **Details** * - * const nodeIndex = 0 - * const nodeData = Graph.getNode(graph, nodeIndex) + * Nodes and edges present in both graphs use data from `that`. The result has + * the same graph kind as `self`. Throws a `GraphError` when the graph kinds do + * not match. `nodeIdentity` and `edgeIdentity` default to the complete node and + * edge data. Edge identity also includes the endpoint identities. * - * if (Option.isSome(nodeData)) { - * console.log(nodeData.value) // "Node A" - * } - * ``` + * `G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}` * - * @category getters - * @since 3.18.0 - */ -export const getNode: { - ( - nodeIndex: NodeIndex - ): (graph: Graph | MutableGraph) => Option.Option - ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex - ): Option.Option -} = dual(2, ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex -): Option.Option => graph.nodes.has(nodeIndex) ? Option.some(graph.nodes.get(nodeIndex)!) : Option.none()) - -/** - * Checks whether a node with the given index exists in the graph. + * **Gotchas** * - * **Example** (Checking node existence) + * Nodes with equal identities in one input graph are coalesced. The last node + * supplies the data, and redirected edges can collapse or become self-loops. + * Parallel edges with equal identities are also coalesced, with the last edge + * supplying the data. The result allocates new node and edge identifiers. * - * ```ts + * **Example** (Combining graphs) + * + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * Graph.addNode(mutable, "Node A") + * const left = Graph.directed<{ id: string }, string>((mutable) => { + * const a = Graph.addNode(mutable, { id: "A" }) + * const b = Graph.addNode(mutable, { id: "B" }) + * Graph.addEdge(mutable, a, b, "A-B") * }) * - * const nodeIndex = 0 - * const exists = Graph.hasNode(graph, nodeIndex) - * console.log(exists) // true + * const right = Graph.directed<{ id: string }, string>((mutable) => { + * const b = Graph.addNode(mutable, { id: "B" }) + * const c = Graph.addNode(mutable, { id: "C" }) + * Graph.addEdge(mutable, b, c, "B-C") + * }) + * + * const result = Graph.compose(left, right, { + * nodeIdentity: (node) => node.id + * }) * - * const nonExistentIndex = 999 - * const notExists = Graph.hasNode(graph, nonExistentIndex) - * console.log(notExists) // false + * Graph.nodeCount(result) // => 3 + * Graph.edgeCount(result) // => 2 * ``` * - * @category getters - * @since 3.18.0 + * @see {@link sum} for combining graphs without merging equal nodes + * @category set operations + * @since 4.0.0 */ -export const hasNode: { - (nodeIndex: NodeIndex): (graph: Graph | MutableGraph) => boolean - (graph: Graph | MutableGraph, nodeIndex: NodeIndex): boolean -} = dual(2, ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex -): boolean => graph.nodes.has(nodeIndex)) +export const compose: { + ( + that: Graph, + options?: IdentityOptions + ): (self: Graph>) => Graph + ( + self: Graph, + that: Graph>, + options?: IdentityOptions + ): Graph +} = dual( + (args) => isGraph(args[0]) && isGraph(args[1]), + ( + self: Graph, + that: Graph, + options?: IdentityOptions + ): Graph => { + assertSameKind(self, that) + const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI) + const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI) + const selfMaps = buildNodeMaps(self, getNodeIdentity) + const thatMaps = buildNodeMaps(that, getNodeIdentity) + const nodes = MutableHashMap.empty() + const edges = buildEdgeMap(self, selfMaps, getEdgeIdentity) + + for (const [identity, data] of selfMaps.byIdentity) { + MutableHashMap.set(nodes, identity, data) + } + for (const [identity, data] of thatMaps.byIdentity) { + MutableHashMap.set(nodes, identity, data) + } + + for (const [identity, data] of buildEdgeMap(that, thatMaps, getEdgeIdentity)) { + MutableHashMap.set(edges, identity, data) + } + + return make(self.type)((mutable) => { + const indexByIdentity = addNodesByIdentity(mutable, nodes) + + for (const [identity, data] of edges) { + addEdgeByIdentity(mutable, indexByIdentity, identity, data) + } + }) + } +) /** - * Returns the number of nodes in the graph. + * Returns the intersection of two graphs, matching nodes by identity. * - * **Example** (Counting nodes) + * **When to use** * - * ```ts - * import { Graph } from "effect" + * Use when extracting nodes and edges that represent the same logical + * structure in both graphs. * - * const emptyGraph = Graph.directed() - * console.log(Graph.nodeCount(emptyGraph)) // 0 + * **Details** * - * const graphWithNodes = Graph.mutate(emptyGraph, (mutable) => { - * Graph.addNode(mutable, "Node A") - * Graph.addNode(mutable, "Node B") - * Graph.addNode(mutable, "Node C") - * }) + * Node data comes from `self`, and edge data comes from `that`. The result has + * the same graph kind as `self`. Throws a `GraphError` when the graph kinds do + * not match. `nodeIdentity` and `edgeIdentity` default to the complete node and + * edge data. Edge identity also includes the endpoint identities. * - * console.log(Graph.nodeCount(graphWithNodes)) // 3 - * ``` + * `G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}` * - * @category getters - * @since 3.18.0 - */ -export const nodeCount = ( - graph: Graph | MutableGraph -): number => graph.nodes.size - -/** - * Finds the first node that matches the given predicate. + * **Gotchas** * - * **Example** (Finding the first matching node) + * Nodes with equal identities in one input graph are coalesced. The last node + * supplies the data, and redirected edges can collapse or become self-loops. + * The result contains at most one edge for each shared edge identity and + * allocates new node and edge identifiers. + * + * **Example** (Finding shared structure) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * Graph.addNode(mutable, "Node A") - * Graph.addNode(mutable, "Node B") - * Graph.addNode(mutable, "Node C") + * const left = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, a, b, "shared") * }) * - * const result = Graph.findNode(graph, (data) => data.startsWith("Node B")) - * console.log(result) // Option.some(1) + * const right = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, a, b, "shared") + * }) + * + * const result = Graph.intersection(left, right) * - * const notFound = Graph.findNode(graph, (data) => data === "Node D") - * console.log(notFound) // Option.none() + * Graph.nodeCount(result) // => 2 + * Graph.edgeCount(result) // => 1 * ``` * - * @category getters - * @since 3.18.0 + * @see {@link compose} for identity-based graph union + * @category set operations + * @since 4.0.0 */ -export const findNode: { - ( - predicate: (data: N) => boolean - ): (graph: Graph | MutableGraph) => Option.Option - ( - graph: Graph | MutableGraph, - predicate: (data: N) => boolean - ): Option.Option -} = dual(2, ( - graph: Graph | MutableGraph, - predicate: (data: N) => boolean -): Option.Option => { - for (const [index, data] of graph.nodes) { - if (predicate(data)) { - return Option.some(index) +export const intersection: { + ( + that: Graph, + options?: IdentityOptions + ): (self: Graph>) => Graph + ( + self: Graph, + that: Graph>, + options?: IdentityOptions + ): Graph +} = dual((args) => isGraph(args[0]) && isGraph(args[1]), ( + self: Graph, + that: Graph, + options?: IdentityOptions +): Graph => { + assertSameKind(self, that) + const thatImpl = internal.toImpl(that) + const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI) + const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI) + const selfMaps = buildNodeMaps(self, getNodeIdentity) + const thatMaps = buildNodeMaps(that, getNodeIdentity) + const nodes = MutableHashMap.empty() + const selfEdges = buildEdgeMap(self, selfMaps, getEdgeIdentity) + const thatEdges = MutableHashMap.empty, E>() + + for (const [identity, data] of selfMaps.byIdentity) { + if (MutableHashMap.has(thatMaps.byIdentity, identity)) { + MutableHashMap.set(nodes, identity, data) } } - return Option.none() + + for (const edge of thatImpl.edges.values()) { + const sourceIdentity = nodeIdentityAt(thatMaps, edge.source) + const targetIdentity = nodeIdentityAt(thatMaps, edge.target) + if (MutableHashMap.has(nodes, sourceIdentity) && MutableHashMap.has(nodes, targetIdentity)) { + const edgeIdentity = new EdgeIdentity(that.type, sourceIdentity, targetIdentity, getEdgeIdentity(edge.data)) + MutableHashMap.set(thatEdges, edgeIdentity, edge.data) + } + } + + return make(self.type)((mutable) => { + const indexByIdentity = addNodesByIdentity(mutable, nodes) + + for (const [identity, data] of thatEdges) { + if (MutableHashMap.has(selfEdges, identity)) { + addEdgeByIdentity(mutable, indexByIdentity, identity, data) + } + } + }) }) /** - * Finds all nodes that match the given predicate. + * Returns `self` without edges also present in `that`. * - * **Example** (Finding matching nodes) + * **When to use** * - * ```ts - * import { Graph } from "effect" + * Use when retaining all logical nodes from one graph while removing edge + * relationships also represented by another graph. * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * Graph.addNode(mutable, "Start A") - * Graph.addNode(mutable, "Node B") - * Graph.addNode(mutable, "Start C") - * }) + * **Details** * - * const result = Graph.findNodes(graph, (data) => data.startsWith("Start")) - * console.log(result) // [0, 2] + * All nodes from `self` are preserved. Edges are matched by endpoint and edge + * identities. The result has the same graph kind as `self`. Throws a + * `GraphError` when the graph kinds do not match. `nodeIdentity` and + * `edgeIdentity` default to the complete node and edge data. * - * const empty = Graph.findNodes(graph, (data) => data === "Not Found") - * console.log(empty) // [] - * ``` + * `G1 \ G2 = {V1, E1 \ E2}` * - * @category getters - * @since 3.18.0 - */ -export const findNodes: { - ( - predicate: (data: N) => boolean - ): (graph: Graph | MutableGraph) => Array - ( - graph: Graph | MutableGraph, - predicate: (data: N) => boolean - ): Array -} = dual(2, ( - graph: Graph | MutableGraph, - predicate: (data: N) => boolean -): Array => { - const results: Array = [] - for (const [index, data] of graph.nodes) { - if (predicate(data)) { - results.push(index) - } - } - return results -}) - -/** - * Finds the first edge that matches the given predicate. + * **Gotchas** * - * **Example** (Finding the first matching edge) + * Nodes with equal identities in one input graph are coalesced. The last node + * supplies the data, and redirected edges can collapse or become self-loops. + * If `that` contains an edge identity, every parallel edge with that identity + * is removed from `self`. The result allocates new node and edge identifiers. * - * ```ts + * **Example** (Removing shared edges) + * + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const nodeC = Graph.addNode(mutable, "Node C") - * Graph.addEdge(mutable, nodeA, nodeB, 10) - * Graph.addEdge(mutable, nodeB, nodeC, 20) + * const left = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "A-B") + * Graph.addEdge(mutable, b, c, "B-C") + * }) + * + * const right = Graph.directed((mutable) => { + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, b, c, "B-C") * }) * - * const result = Graph.findEdge(graph, (data) => data > 15) - * console.log(result) // Option.some(1) + * const result = Graph.difference(left, right) * - * const notFound = Graph.findEdge(graph, (data) => data > 100) - * console.log(notFound) // Option.none() + * Graph.nodeCount(result) // => 3 + * Graph.edgeCount(result) // => 1 * ``` * - * @category getters - * @since 3.18.0 + * @see {@link symmetricDifference} for retaining edges unique to either graph + * @category set operations + * @since 4.0.0 */ -export const findEdge: { - ( - predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean - ): (graph: Graph | MutableGraph) => Option.Option - ( - graph: Graph | MutableGraph, - predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean - ): Option.Option -} = dual(2, ( - graph: Graph | MutableGraph, - predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean -): Option.Option => { - for (const [edgeIndex, edgeData] of graph.edges) { - if (predicate(edgeData.data, edgeData.source, edgeData.target)) { - return Option.some(edgeIndex) +export const difference: { + ( + that: Graph, + options?: IdentityOptions + ): (self: Graph>) => Graph + ( + self: Graph, + that: Graph>, + options?: IdentityOptions + ): Graph +} = dual((args) => isGraph(args[0]) && isGraph(args[1]), ( + self: Graph, + that: Graph, + options?: IdentityOptions +): Graph => { + assertSameKind(self, that) + const selfImpl = internal.toImpl(self) + const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI) + const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI) + const selfMaps = buildNodeMaps(self, getNodeIdentity) + const thatMaps = buildNodeMaps(that, getNodeIdentity) + const thatEdges = buildEdgeMap(that, thatMaps, getEdgeIdentity) + + return make(self.type)((mutable) => { + const indexByIdentity = addNodesByIdentity(mutable, selfMaps.byIdentity) + + for (const edge of selfImpl.edges.values()) { + const sourceIdentity = nodeIdentityAt(selfMaps, edge.source) + const targetIdentity = nodeIdentityAt(selfMaps, edge.target) + const edgeIdentity = new EdgeIdentity(self.type, sourceIdentity, targetIdentity, getEdgeIdentity(edge.data)) + if (!MutableHashMap.has(thatEdges, edgeIdentity)) { + addEdgeByIdentity(mutable, indexByIdentity, edgeIdentity, edge.data) + } } - } - return Option.none() + }) }) /** - * Finds all edges that match the given predicate. + * Returns edges present in exactly one of two graphs. * - * **Example** (Finding matching edges) + * **When to use** * - * ```ts - * import { Graph } from "effect" + * Use when comparing graphs and retaining relationships unique to either one. * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const nodeC = Graph.addNode(mutable, "Node C") - * Graph.addEdge(mutable, nodeA, nodeB, 10) - * Graph.addEdge(mutable, nodeB, nodeC, 20) - * Graph.addEdge(mutable, nodeC, nodeA, 30) - * }) + * **Details** * - * const result = Graph.findEdges(graph, (data) => data >= 20) - * console.log(result) // [1, 2] + * Keeps nodes from both graphs. Overlapping nodes use data from `that`. The + * result has the same graph kind as `self`. Throws a `GraphError` when the + * graph kinds do not match. `nodeIdentity` and `edgeIdentity` default to the + * complete node and edge data. Edge identity also includes the endpoint + * identities. * - * const empty = Graph.findEdges(graph, (data) => data > 100) - * console.log(empty) // [] - * ``` + * `G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}` * - * @category getters - * @since 3.18.0 - */ -export const findEdges: { - ( - predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean - ): (graph: Graph | MutableGraph) => Array - ( - graph: Graph | MutableGraph, - predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean - ): Array -} = dual(2, ( - graph: Graph | MutableGraph, - predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean -): Array => { - const results: Array = [] - for (const [edgeIndex, edgeData] of graph.edges) { - if (predicate(edgeData.data, edgeData.source, edgeData.target)) { - results.push(edgeIndex) - } - } - return results -}) - -/** - * Updates a single node's data by applying a transformation function. + * **Gotchas** * - * **Example** (Updating node data) + * Edges with different projected identities are distinct. + * Nodes with equal identities in one input graph are coalesced. The last node + * supplies the data, and redirected edges can collapse or become self-loops. + * Parallel edges with equal identities are coalesced before the graphs are + * compared. The result allocates new node and edge identifiers. + * + * **Example** (Finding differing edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.directed((mutable) => { - * Graph.addNode(mutable, "Node A") - * Graph.addNode(mutable, "Node B") - * Graph.updateNode(mutable, 0, (data) => data.toUpperCase()) + * const left = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "A-B") + * Graph.addEdge(mutable, b, c, "B-C") * }) * - * const nodeData = Graph.getNode(graph, 0) - * console.log(nodeData) // Option.some("NODE A") - * ``` - * - * @category transforming - * @since 3.18.0 - */ -export const updateNode = ( - mutable: MutableGraph, - index: NodeIndex, - f: (data: N) => N -): void => { - if (!mutable.nodes.has(index)) { - return - } - - const currentData = mutable.nodes.get(index)! - const newData = f(currentData) - mutable.nodes.set(index, newData) -} - -/** - * Updates a single edge's data by applying a transformation function. - * - * **Example** (Updating edge data) - * - * ```ts - * import { Graph } from "effect" - * - * const result = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const edgeIndex = Graph.addEdge(mutable, nodeA, nodeB, 10) - * Graph.updateEdge(mutable, edgeIndex, (data) => data * 2) + * const right = Graph.directed((mutable) => { + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * const d = Graph.addNode(mutable, "D") + * Graph.addEdge(mutable, b, c, "B-C") + * Graph.addEdge(mutable, c, d, "C-D") * }) * - * const edgeData = Graph.getEdge(result, 0) - * console.log(edgeData) // Option.some(new Graph.Edge({ source: 0, target: 1, data: 20 })) + * const result = Graph.symmetricDifference(left, right) + * + * Graph.nodeCount(result) // => 4 + * Graph.edgeCount(result) // => 2 * ``` * - * @category mutations - * @since 3.18.0 + * @see {@link difference} for removing only the edges found in another graph + * @category set operations + * @since 4.0.0 */ -export const updateEdge = ( - mutable: MutableGraph, - edgeIndex: EdgeIndex, - f: (data: E) => E -): void => { - if (!mutable.edges.has(edgeIndex)) { - return +export const symmetricDifference: { + ( + that: Graph, + options?: IdentityOptions + ): (self: Graph>) => Graph + ( + self: Graph, + that: Graph>, + options?: IdentityOptions + ): Graph +} = dual((args) => isGraph(args[0]) && isGraph(args[1]), ( + self: Graph, + that: Graph, + options?: IdentityOptions +): Graph => { + assertSameKind(self, that) + const getNodeIdentity = options?.nodeIdentity ?? ((node: N) => node as unknown as NI) + const getEdgeIdentity = options?.edgeIdentity ?? ((edge: E) => edge as unknown as EI) + const selfMaps = buildNodeMaps(self, getNodeIdentity) + const thatMaps = buildNodeMaps(that, getNodeIdentity) + const nodes = MutableHashMap.empty() + const selfEdges = buildEdgeMap(self, selfMaps, getEdgeIdentity) + const thatEdges = buildEdgeMap(that, thatMaps, getEdgeIdentity) + + for (const [identity, data] of selfMaps.byIdentity) { + MutableHashMap.set(nodes, identity, data) } - const currentEdge = mutable.edges.get(edgeIndex)! - const newData = f(currentEdge.data) - mutable.edges.set(edgeIndex, new Edge({ ...currentEdge, data: newData })) -} + for (const [identity, data] of thatMaps.byIdentity) { + MutableHashMap.set(nodes, identity, data) + } + + return make(self.type)((mutable) => { + const indexByIdentity = addNodesByIdentity(mutable, nodes) + + for (const [identity, data] of selfEdges) { + if (!MutableHashMap.has(thatEdges, identity)) { + addEdgeByIdentity(mutable, indexByIdentity, identity, data) + } + } + + for (const [identity, data] of thatEdges) { + if (!MutableHashMap.has(selfEdges, identity)) { + addEdgeByIdentity(mutable, indexByIdentity, identity, data) + } + } + }) +}) /** - * Transforms every node's data in a mutable graph in place using the provided - * mapping function. + * Returns the complement over the existing node set. * - * **Details** + * **When to use** * - * Node indices and edges are preserved; only the stored node data is replaced. + * Use when materializing every relationship that is currently absent between + * distinct nodes. * - * **Example** (Mapping node data) + * **Details** * - * ```ts - * import { Graph } from "effect" + * Directed graphs add each missing ordered pair. Undirected graphs add each + * missing unordered pair once. The `createEdge` function receives the source + * and target node data for each added edge. The result has the same graph kind + * as `self`. * - * const graph = Graph.directed((mutable) => { - * Graph.addNode(mutable, "node a") - * Graph.addNode(mutable, "node b") - * Graph.addNode(mutable, "node c") - * Graph.mapNodes(mutable, (data) => data.toUpperCase()) - * }) + * `G' = {V, ((V x V) without self-pairs) \ E}` * - * const nodeData = Graph.getNode(graph, 0) - * console.log(nodeData) // Option.some("NODE A") - * ``` + * **Gotchas** * - * @category transforming - * @since 3.18.0 - */ -export const mapNodes = ( - mutable: MutableGraph, - f: (data: N) => N -): void => { - // Transform existing node data in place - for (const [index, data] of mutable.nodes) { - const newData = f(data) - mutable.nodes.set(index, newData) - } -} - -/** - * Transforms all edge data in a mutable graph using the provided mapping function. + * Self-loops are never created. If any edge already connects a pair, parallel + * complement edges are not added. The result allocates new identifiers. * - * **Example** (Mapping edge data) + * **Example** (Finding missing relationships) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.directed((mutable) => { + * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") - * Graph.addEdge(mutable, a, b, 10) - * Graph.addEdge(mutable, b, c, 20) - * Graph.mapEdges(mutable, (data) => data * 2) + * Graph.addEdge(mutable, a, b, "A-B") * }) * - * const edgeData = Graph.getEdge(graph, 0) - * console.log(edgeData) // Option.some(new Graph.Edge({ source: 0, target: 1, data: 20 })) + * const result = Graph.complement(graph, (source, target) => `${source}-${target}`) + * + * Graph.edgeCount(result) // => 1 * ``` * - * @category transforming - * @since 3.18.0 + * @see {@link hasEdge} for testing one relationship + * @category set operations + * @since 4.0.0 */ -export const mapEdges = ( - mutable: MutableGraph, - f: (data: E) => E -): void => { - // Transform existing edge data in place - for (const [index, edgeData] of mutable.edges) { - const newData = f(edgeData.data) - mutable.edges.set(index, { - ...edgeData, - data: newData - }) - } -} +export const complement: { + ( + createEdge: (source: N, target: N) => E + ): (self: Graph) => Graph + ( + self: Graph, + createEdge: (source: N, target: N) => E + ): Graph +} = dual(2, ( + self: Graph, + createEdge: (source: N, target: N) => E +): Graph => { + const cache = csr.get(self) + const outgoing = csr.getOutgoing(cache) + const neighborMarks = new Uint32Array(cache.nodeIds.length) -/** - * @internal - */ -const rebuildAdjacency = ( - mutable: MutableGraph -): void => { - mutable.adjacency.clear() - mutable.reverseAdjacency.clear() + return make(self.type)((mutable) => { + const newIndices = new Uint32Array(cache.nodeIds.length) - for (const nodeIndex of mutable.nodes.keys()) { - mutable.adjacency.set(nodeIndex, []) - mutable.reverseAdjacency.set(nodeIndex, []) - } + for (let i = 0; i < cache.nodeIds.length; i++) { + newIndices[i] = addNode(mutable, cache.nodeData[i] as N) + } - for (const [edgeIndex, edgeData] of mutable.edges) { - mutable.adjacency.get(edgeData.source)!.push(edgeIndex) - mutable.reverseAdjacency.get(edgeData.target)!.push(edgeIndex) + for (let i = 0; i < cache.nodeIds.length; i++) { + const generation = i + 1 + for (let edge = outgoing.rowOffsets[i]; edge < outgoing.rowOffsets[i + 1]; edge++) { + neighborMarks[outgoing.columnIndices[edge]] = generation + } + const start = self.type === "undirected" ? i + 1 : 0 - if (mutable.type === "undirected") { - mutable.adjacency.get(edgeData.target)!.push(edgeIndex) - mutable.reverseAdjacency.get(edgeData.source)!.push(edgeIndex) + for (let j = start; j < cache.nodeIds.length; j++) { + if (i === j || neighborMarks[j] === generation) { + continue + } + addEdge(mutable, newIndices[i], newIndices[j], createEdge(cache.nodeData[i] as N, cache.nodeData[j] as N)) + } } - } + }) +}) + +/** + * Configuration for selecting a graph neighborhood. + * + * **Details** + * + * `radius` limits the edge distance from the center node and defaults to `1`. + * It accepts non-negative integers and `Infinity`. + * `direction` controls how directed edges are traversed and defaults to + * `"outgoing"`. + * + * @category configuration + * @since 4.0.0 + */ +export interface NeighborhoodConfig { + readonly radius?: number + readonly direction?: TraversalDirection } /** - * Swaps source and target nodes for every edge in a mutable graph. + * Returns the induced subgraph containing nodes within a radius of a node. * - * **Example** (Reversing edge directions) + * **When to use** + * + * Use when extracting a local reachable region around one node. * - * ```ts + * **Details** + * + * The `radius` option is the maximum edge distance from `nodeIndex`, accepts + * non-negative integers and `Infinity`, and defaults to `1`. Invalid radii + * throw a `GraphError`. The `direction` option controls directed graph + * traversal and defaults to `"outgoing"`. The result has the same graph kind + * as `self` and keeps all original edges whose endpoints are both reached. + * `"undirected"` ignores edge direction while finding reachable nodes. + * + * **Gotchas** + * + * Traversal chooses the nodes, then all original edges between reached nodes + * are retained. The result is not merely a traversal tree, and it allocates new + * node and edge identifiers. + * + * **Example** (Getting a local neighborhood) + * + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.directed((mutable) => { + * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") * const b = Graph.addNode(mutable, "B") * const c = Graph.addNode(mutable, "C") - * Graph.addEdge(mutable, a, b, 1) // A -> B - * Graph.addEdge(mutable, b, c, 2) // B -> C - * Graph.reverse(mutable) // Now B -> A, C -> B + * Graph.addEdge(mutable, a, b, "A-B") + * Graph.addEdge(mutable, b, c, "B-C") * }) * - * const edge0 = Graph.getEdge(graph, 0) - * console.log(edge0) // Option.some(new Graph.Edge({ source: 1, target: 0, data: 1 })) + * const result = Graph.neighborhood(graph, 1, { radius: 1 }) + * + * Graph.nodeCount(result) // => 2 * ``` * - * @category transforming - * @since 3.18.0 + * @see {@link inducedSubgraph} for selecting nodes while preserving identifiers + * @category set operations + * @since 4.0.0 */ -export const reverse = ( - mutable: MutableGraph -): void => { - if (mutable.type === "undirected") { - return - } +export const neighborhood: { + ( + nodeIndex: NodeIndex, + options?: NeighborhoodConfig + ): (self: Graph) => Graph + ( + self: Graph, + nodeIndex: NodeIndex, + options?: NeighborhoodConfig + ): Graph +} = dual((args) => isGraph(args[0]), ( + self: Graph, + nodeIndex: NodeIndex, + options?: NeighborhoodConfig +): Graph => { + const selfImpl = internal.toImpl(self) + const radius = traversalRadius(options?.radius, 1) + const direction = options?.direction ?? "outgoing" + const reached = new Set() - // Reverse all edges by swapping source and target - for (const [index, edgeData] of mutable.edges) { - mutable.edges.set( - index, - new Edge({ - source: edgeData.target, - target: edgeData.source, - data: edgeData.data - }) - ) + for (const index of indices(bfs(self, { start: [nodeIndex], direction, radius }))) { + reached.add(index) } - rebuildAdjacency(mutable) + return make(self.type)((mutable) => { + const newIndexMap = new Map() - // Invalidate cycle flag since edge directions changed - mutable.acyclic = Option.none() -} + for (const oldIndex of reached) { + newIndexMap.set(oldIndex, addNode(mutable, Option.getOrThrow(getNode(self, oldIndex)))) + } + + for (const edge of selfImpl.edges.values()) { + if (reached.has(edge.source) && reached.has(edge.target)) { + const sourceIndex = newIndexMap.get(edge.source) + const targetIndex = newIndexMap.get(edge.target) + if (sourceIndex !== undefined && targetIndex !== undefined) { + addEdge(mutable, sourceIndex, targetIndex, edge.data) + } + } + } + }) +}) /** - * Filters and optionally transforms nodes in a mutable graph using a predicate function. - * Nodes that return Option.none are removed along with all their connected edges. + * Returns the subgraph induced by a collection of node indices. * - * **Example** (Filtering and mapping nodes) + * **When to use** * - * ```ts - * import { Graph, Option } from "effect" + * Use when selecting an exact node set while preserving its active node and + * edge identifiers. * - * const graph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "active") - * const b = Graph.addNode(mutable, "inactive") - * const c = Graph.addNode(mutable, "active") - * Graph.addEdge(mutable, a, b, 1) - * Graph.addEdge(mutable, b, c, 2) + * **Details** * - * // Keep only "active" nodes and transform to uppercase - * Graph.filterMapNodes( - * mutable, - * (data) => - * data === "active" ? Option.some(data.toUpperCase()) : Option.none() - * ) - * }) + * Node and edge indices are preserved. Duplicate input indices are ignored, + * output ordering follows the original graph, and every edge whose endpoints + * are both selected is retained. * - * console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain) - * ``` + * **Gotchas** * - * @category transforming - * @since 3.18.0 + * Throws a `GraphError` when a selected node does not exist. + * + * @see {@link neighborhood} for selecting nodes by traversal distance + * + * @category set operations + * @since 4.0.0 */ -export const filterMapNodes = ( - mutable: MutableGraph, - f: (data: N) => Option.Option -): void => { - const nodesToRemove: Array = [] - - // First pass: identify nodes to remove and transform data for nodes to keep - for (const [index, data] of mutable.nodes) { - const result = f(data) - if (Option.isSome(result)) { - // Transform node data - mutable.nodes.set(index, result.value) - } else { - // Mark for removal - nodesToRemove.push(index) +export const inducedSubgraph: { + (nodeIndices: Iterable): (self: Graph) => Graph + ( + self: Graph, + nodeIndices: Iterable + ): Graph +} = dual(2, ( + self: Graph, + nodeIndices: Iterable +): Graph => { + const impl = internal.toImpl(self) + const selected = new Set() + for (const nodeIndex of nodeIndices) { + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) } + selected.add(nodeIndex) } - // Second pass: remove filtered out nodes and their edges - for (const nodeIndex of nodesToRemove) { - removeNode(mutable, nodeIndex) + const nodes: Array> = [] + for (const [index, data] of impl.nodes) { + if (selected.has(index)) { + nodes.push({ index, data }) + } } -} + const edges: Array> = [] + for (const [index, edge] of impl.edges) { + if (selected.has(edge.source) && selected.has(edge.target)) { + edges.push({ index, source: edge.source, target: edge.target, data: edge.data }) + } + } + return fromSnapshot({ type: self.type, nodes, edges }) +}) /** - * Filters and optionally transforms edges in a mutable graph using a predicate function. - * Edges that return Option.none are removed from the graph. + * Returns the disjoint union of two graphs. * - * **Example** (Filtering and mapping edges) + * **When to use** * - * ```ts - * import { Graph, Option } from "effect" + * Use when combining graphs while keeping every node distinct, even when node + * payloads are equal. * - * const graph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") - * Graph.addEdge(mutable, a, b, 5) - * Graph.addEdge(mutable, b, c, 15) - * Graph.addEdge(mutable, c, a, 25) - * - * // Keep only edges with weight >= 10 and double their weight - * Graph.filterMapEdges( - * mutable, - * (data) => data >= 10 ? Option.some(data * 2) : Option.none() - * ) - * }) - * - * console.log(Graph.edgeCount(graph)) // 2 (edges with weight 5 removed) - * ``` - * - * @category transforming - * @since 3.18.0 - */ -export const filterMapEdges = ( - mutable: MutableGraph, - f: (data: E) => Option.Option -): void => { - const edgesToRemove: Array = [] - - // First pass: identify edges to remove and transform data for edges to keep - for (const [index, edgeData] of mutable.edges) { - const result = f(edgeData.data) - if (Option.isSome(result)) { - // Transform edge data - mutable.edges.set(index, { - ...edgeData, - data: result.value - }) - } else { - // Mark for removal - edgesToRemove.push(index) - } - } - - // Second pass: remove filtered out edges - for (const edgeIndex of edgesToRemove) { - removeEdge(mutable, edgeIndex) - } -} - -/** - * Filters nodes by removing those that don't match the predicate. - * This function modifies the mutable graph in place. - * - * **Example** (Filtering nodes) - * - * ```ts - * import { Graph } from "effect" - * - * const graph = Graph.directed((mutable) => { - * Graph.addNode(mutable, "active") - * Graph.addNode(mutable, "inactive") - * Graph.addNode(mutable, "pending") - * Graph.addNode(mutable, "active") - * - * // Keep only "active" nodes - * Graph.filterNodes(mutable, (data) => data === "active") - * }) - * - * console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain) - * ``` - * - * @category transforming - * @since 3.18.0 - */ -export const filterNodes = ( - mutable: MutableGraph, - predicate: (data: N) => boolean -): void => { - const nodesToRemove: Array = [] - - // Identify nodes to remove - for (const [index, data] of mutable.nodes) { - if (!predicate(data)) { - nodesToRemove.push(index) - } - } - - // Remove filtered out nodes (this also removes connected edges) - for (const nodeIndex of nodesToRemove) { - removeNode(mutable, nodeIndex) - } -} - -/** - * Filters edges by removing those that don't match the predicate. - * This function modifies the mutable graph in place. - * - * **Example** (Filtering edges) + * **Details** * - * ```ts - * import { Graph } from "effect" + * Copies all nodes and edges from both graphs without merging equal node data. + * The result has the same graph kind as `self`. Throws a `GraphError` when the + * graph kinds do not match. * - * const graph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") + * `G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}` * - * Graph.addEdge(mutable, a, b, 5) - * Graph.addEdge(mutable, b, c, 15) - * Graph.addEdge(mutable, c, a, 25) + * **Gotchas** * - * // Keep only edges with weight >= 10 - * Graph.filterEdges(mutable, (data) => data >= 10) - * }) + * All node and edge identifiers are newly allocated. * - * console.log(Graph.edgeCount(graph)) // 2 (edge with weight 5 removed) - * ``` + * @see {@link compose} for merging overlapping logical nodes by identity * - * @category transforming - * @since 3.18.0 + * @category set operations + * @since 4.0.0 */ -export const filterEdges = ( - mutable: MutableGraph, - predicate: (data: E) => boolean -): void => { - const edgesToRemove: Array = [] +export const sum: { + (that: Graph): (self: Graph>) => Graph + (self: Graph, that: Graph>): Graph +} = dual(2, (self: Graph, that: Graph): Graph => { + assertSameKind(self, that) + return make(self.type)((mutable) => { + const copyInto = (graph: Graph) => { + const impl = internal.toImpl(graph) + const indexMap = new Map() + + for (const [oldIndex, data] of impl.nodes) { + indexMap.set(oldIndex, addNode(mutable, data)) + } - // Identify edges to remove - for (const [index, edgeData] of mutable.edges) { - if (!predicate(edgeData.data)) { - edgesToRemove.push(index) + for (const edge of impl.edges.values()) { + const sourceIndex = indexMap.get(edge.source) + const targetIndex = indexMap.get(edge.target) + if (sourceIndex !== undefined && targetIndex !== undefined) { + addEdge(mutable, sourceIndex, targetIndex, edge.data) + } + } } - } - - // Remove filtered out edges - for (const edgeIndex of edgesToRemove) { - removeEdge(mutable, edgeIndex) - } -} - -// ============================================================================= -// Cycle Flag Management (Internal) -// ============================================================================= - -/** @internal */ -const invalidateCycleFlagOnRemoval = ( - mutable: MutableGraph -): void => { - // Only invalidate if the graph had cycles (removing edges/nodes cannot introduce cycles in acyclic graphs). - if (mutable.acyclic._tag === "Some" && mutable.acyclic.value === false) { - mutable.acyclic = Option.none() - } -} -/** @internal */ -const invalidateCycleFlagOnAddition = ( - mutable: MutableGraph -): void => { - // Only invalidate if the graph was acyclic (adding edges cannot remove cycles from cyclic graphs). - if (mutable.acyclic._tag === "Some" && mutable.acyclic.value === true) { - mutable.acyclic = Option.none() - } -} + copyInto(self) + copyInto(that) + }) +}) // ============================================================================= -// Edge Operations +// Basic Node Operations // ============================================================================= /** - * Adds a new edge to a mutable graph and returns its index. + * Adds a new node to a mutable graph and returns its index. * * **When to use** * - * Use to connect two existing nodes in a mutable graph while storing edge data - * and receiving the new edge identifier. + * Use to allocate a new node in a mutable graph before storing edges or + * querying it by index. * * **Details** * - * Creates an `Edge` with the source, target, and data at the next edge index, - * updates adjacency indexes, and increments the graph's next edge index. - * Undirected graphs register the same edge for both endpoints. + * The returned index is allocated from the graph's next node index. The mutable + * graph stores the node data and initializes empty incoming and outgoing edge + * indexes for the new node. * * **Gotchas** * - * The source and target nodes must already exist in the mutable graph; missing - * endpoints throw a `GraphError`. + * `NodeIndex` values are identifiers and are not reused after removals. * - * **Example** (Adding edges) + * **Example** (Adding nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const result = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const edge = Graph.addEdge(mutable, nodeA, nodeB, 42) - * console.log(edge) // EdgeIndex with value 0 + * Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") // => 0 + * Graph.addNode(mutable, "Node B") // => 1 * }) * ``` * * @see {@link mutate} for obtaining a mutable graph from an immutable graph - * @see {@link addNode} for creating node indexes before connecting them - * @see {@link getEdge} for reading the returned edge - * @see {@link removeEdge} for removing an edge from a mutable graph + * @see {@link addEdge} for connecting existing nodes * * @category mutations * @since 3.18.0 */ -export const addEdge = ( +export const addNode = ( mutable: MutableGraph, - source: NodeIndex, - target: NodeIndex, - data: E -): EdgeIndex => { - // Validate that both nodes exist - if (!mutable.nodes.has(source)) { - throw missingNode(source) - } - if (!mutable.nodes.has(target)) { - throw missingNode(target) - } - - const edgeIndex = mutable.nextEdgeIndex - - // Create edge data - const edgeData = new Edge({ source, target, data }) - mutable.edges.set(edgeIndex, edgeData) - - // Update adjacency lists - const sourceAdjacency = mutable.adjacency.get(source) - if (sourceAdjacency !== undefined) { - sourceAdjacency.push(edgeIndex) - } - - const targetReverseAdjacency = mutable.reverseAdjacency.get(target) - if (targetReverseAdjacency !== undefined) { - targetReverseAdjacency.push(edgeIndex) + data: N +): NodeIndex => { + const impl = getMutableImplForMutation(mutable) + const nodeIndex = impl.nextNodeIndex + if (!Number.isSafeInteger(nodeIndex)) { + throw new GraphError({ message: "Graph has exhausted safe node indexes" }) } - // For undirected graphs, add reverse connections - if (mutable.type === "undirected") { - const targetAdjacency = mutable.adjacency.get(target) - if (targetAdjacency !== undefined) { - targetAdjacency.push(edgeIndex) - } - - const sourceReverseAdjacency = mutable.reverseAdjacency.get(source) - if (sourceReverseAdjacency !== undefined) { - sourceReverseAdjacency.push(edgeIndex) - } - } + // Add node data + impl.nodes.set(nodeIndex, data) - // Update allocators - mutable.nextEdgeIndex = mutable.nextEdgeIndex + 1 + // Initialize empty adjacency lists + impl.adjacency.set(nodeIndex, []) + impl.reverseAdjacency.set(nodeIndex, []) - // Only invalidate cycle flag if the graph was acyclic - // Adding edges cannot remove cycles from cyclic graphs - invalidateCycleFlagOnAddition(mutable) + // Update graph allocators + impl.nextNodeIndex = impl.nextNodeIndex + 1 - return edgeIndex + return nodeIndex } /** - * Removes a node and all its incident edges from a mutable graph. - * - * **Example** (Removing a node) + * Gets the data associated with a node index safely, if it exists. * - * ```ts - * import { Graph } from "effect" + * **Example** (Getting node data) * - * const result = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * Graph.addEdge(mutable, nodeA, nodeB, 42) + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * - * // Remove nodeA and all edges connected to it - * Graph.removeNode(mutable, nodeA) + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") * }) + * + * Graph.getNode(graph, 0) // => Option.some("Node A") * ``` * - * @category mutations + * @category getters * @since 3.18.0 */ -export const removeNode = ( - mutable: MutableGraph, +export const getNode: { + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => Option.Option + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Option.Option +} = dual(2, ( + graph: Graph | MutableGraph, nodeIndex: NodeIndex -): void => { - // Check if node exists - if (!mutable.nodes.has(nodeIndex)) { - return // Node doesn't exist, nothing to remove - } - - // Collect all incident edges for removal - const edgesToRemove: Array = [] - - // Get outgoing edges - const outgoingEdges = mutable.adjacency.get(nodeIndex) - if (outgoingEdges !== undefined) { - for (const edge of outgoingEdges) { - edgesToRemove.push(edge) - } - } - - // Get incoming edges - const incomingEdges = mutable.reverseAdjacency.get(nodeIndex) - if (incomingEdges !== undefined) { - for (const edge of incomingEdges) { - edgesToRemove.push(edge) - } - } - - // Remove all incident edges - for (const edgeIndex of edgesToRemove) { - removeEdgeInternal(mutable, edgeIndex) - } - - // Remove the node itself - mutable.nodes.delete(nodeIndex) - mutable.adjacency.delete(nodeIndex) - mutable.reverseAdjacency.delete(nodeIndex) - - // Only invalidate cycle flag if the graph wasn't already known to be acyclic - // Removing nodes cannot introduce cycles in an acyclic graph - invalidateCycleFlagOnRemoval(mutable) -} +): Option.Option => { + const impl = internal.toImpl(graph) + return impl.nodes.has(nodeIndex) ? Option.some(impl.nodes.get(nodeIndex)!) : Option.none() +}) /** - * Removes an edge from a mutable graph. + * Checks whether a node with the given index exists in the graph. * - * **Example** (Removing an edge) + * **Example** (Checking node existence) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const result = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const edge = Graph.addEdge(mutable, nodeA, nodeB, 42) - * - * // Remove the edge - * Graph.removeEdge(mutable, edge) + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") * }) + * + * Graph.hasNode(graph, 0) // => true + * Graph.hasNode(graph, 999) // => false * ``` * - * @category mutations + * @category predicates * @since 3.18.0 */ -export const removeEdge = ( - mutable: MutableGraph, - edgeIndex: EdgeIndex -): void => { - const wasRemoved = removeEdgeInternal(mutable, edgeIndex) - - // Only invalidate cycle flag if an edge was actually removed - // and only if the graph wasn't already known to be acyclic - if (wasRemoved) { - invalidateCycleFlagOnRemoval(mutable) - } -} - -/** @internal */ -const removeEdgeInternal = ( - mutable: MutableGraph, - edgeIndex: EdgeIndex -): boolean => { - // Get edge data - const edge = mutable.edges.get(edgeIndex) - if (edge === undefined) { - return false // Edge doesn't exist, no mutation occurred - } - - const { source, target } = edge - - // Remove from adjacency lists - const sourceAdjacency = mutable.adjacency.get(source) - if (sourceAdjacency !== undefined) { - const index = sourceAdjacency.indexOf(edgeIndex) - if (index !== -1) { - sourceAdjacency.splice(index, 1) - } - } - - const targetReverseAdjacency = mutable.reverseAdjacency.get(target) - if (targetReverseAdjacency !== undefined) { - const index = targetReverseAdjacency.indexOf(edgeIndex) - if (index !== -1) { - targetReverseAdjacency.splice(index, 1) - } - } - - // For undirected graphs, remove reverse connections - if (mutable.type === "undirected") { - const targetAdjacency = mutable.adjacency.get(target) - if (targetAdjacency !== undefined) { - const index = targetAdjacency.indexOf(edgeIndex) - if (index !== -1) { - targetAdjacency.splice(index, 1) - } - } - - const sourceReverseAdjacency = mutable.reverseAdjacency.get(source) - if (sourceReverseAdjacency !== undefined) { - const index = sourceReverseAdjacency.indexOf(edgeIndex) - if (index !== -1) { - sourceReverseAdjacency.splice(index, 1) - } - } - } - - // Remove edge data - mutable.edges.delete(edgeIndex) - - return true // Edge was successfully removed -} - -// ============================================================================= -// Edge Query Operations -// ============================================================================= +export const hasNode: { + (nodeIndex: NodeIndex): (graph: Graph | MutableGraph) => boolean + (graph: Graph | MutableGraph, nodeIndex: NodeIndex): boolean +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): boolean => internal.toImpl(graph).nodes.has(nodeIndex)) /** - * Gets the edge data associated with an edge index safely, if it exists. + * Returns the number of nodes in the graph. * - * **Example** (Getting edge data) + * **Example** (Counting nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * Graph.addEdge(mutable, nodeA, nodeB, 42) + * const emptyGraph = Graph.directed() + * Graph.nodeCount(emptyGraph) // => 0 + * + * const graphWithNodes = Graph.mutate(emptyGraph, (mutable) => { + * Graph.addNode(mutable, "Node A") + * Graph.addNode(mutable, "Node B") + * Graph.addNode(mutable, "Node C") * }) * - * const edgeIndex = 0 - * const edgeData = Graph.getEdge(graph, edgeIndex) + * Graph.nodeCount(graphWithNodes) // => 3 + * ``` * - * if (edgeData._tag === "Some") { - * console.log(edgeData.value.data) // 42 - * console.log(edgeData.value.source) // 0 - * console.log(edgeData.value.target) // 1 - * } + * @category getters + * @since 3.18.0 + */ +export const nodeCount = ( + graph: Graph | MutableGraph +): number => internal.toImpl(graph).nodes.size + +/** + * Finds the first node that matches the given predicate. + * + * **Example** (Finding the first matching node) + * + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * Graph.addNode(mutable, "Node A") + * Graph.addNode(mutable, "Node B") + * Graph.addNode(mutable, "Node C") + * }) + * + * Graph.findNode(graph, (data) => data.startsWith("Node B")) // => Option.some(1) + * Graph.findNode(graph, (data) => data === "Node D") // => Option.none() * ``` * * @category getters * @since 3.18.0 */ -export const getEdge: { - ( - edgeIndex: EdgeIndex - ): (graph: Graph | MutableGraph) => Option.Option> +export const findNode: { + ( + predicate: (data: N) => boolean + ): (graph: Graph | MutableGraph) => Option.Option ( graph: Graph | MutableGraph, - edgeIndex: EdgeIndex - ): Option.Option> + predicate: (data: N) => boolean + ): Option.Option } = dual(2, ( graph: Graph | MutableGraph, - edgeIndex: EdgeIndex -): Option.Option> => Option.fromUndefinedOr(graph.edges.get(edgeIndex))) + predicate: (data: N) => boolean +): Option.Option => { + const impl = internal.toImpl(graph) + return withMutationGuard(graph, () => { + for (const [index, data] of impl.nodes) { + if (predicate(data)) { + return Option.some(index) + } + } + return Option.none() + }) +}) /** - * Checks whether an edge exists between two nodes in the graph. + * Finds all nodes that match the given predicate. * - * **Example** (Checking edge existence) + * **Example** (Finding matching nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const nodeC = Graph.addNode(mutable, "Node C") - * Graph.addEdge(mutable, nodeA, nodeB, 42) + * Graph.addNode(mutable, "Start A") + * Graph.addNode(mutable, "Node B") + * Graph.addNode(mutable, "Start C") * }) * - * const nodeA = 0 - * const nodeB = 1 - * const nodeC = 2 - * - * const hasAB = Graph.hasEdge(graph, nodeA, nodeB) - * console.log(hasAB) // true - * - * const hasAC = Graph.hasEdge(graph, nodeA, nodeC) - * console.log(hasAC) // false + * Graph.findNodes(graph, (data) => data.startsWith("Start")) // => [0, 2] + * Graph.findNodes(graph, (data) => data === "Not Found") // => [] * ``` * * @category getters * @since 3.18.0 */ -export const hasEdge: { - ( - source: NodeIndex, - target: NodeIndex - ): (graph: Graph | MutableGraph) => boolean +export const findNodes: { + ( + predicate: (data: N) => boolean + ): (graph: Graph | MutableGraph) => Array ( graph: Graph | MutableGraph, - source: NodeIndex, - target: NodeIndex - ): boolean -} = dual(3, ( + predicate: (data: N) => boolean + ): Array +} = dual(2, ( graph: Graph | MutableGraph, - source: NodeIndex, - target: NodeIndex -): boolean => { - const adjacencyList = graph.adjacency.get(source) - if (adjacencyList === undefined) { - return false - } - - // Check if any edge in the adjacency list connects to the target - for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) - if (edge !== undefined) { - const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target - if (neighbor === target) { - return true + predicate: (data: N) => boolean +): Array => { + const impl = internal.toImpl(graph) + return withMutationGuard(graph, () => { + const results: Array = [] + for (const [index, data] of impl.nodes) { + if (predicate(data)) { + results.push(index) } } - } - - return false + return results + }) }) /** - * Returns the number of edges in the graph. - * - * **Example** (Counting edges) + * Finds the first edge that matches the given predicate. * - * ```ts - * import { Graph } from "effect" + * **Example** (Finding the first matching edge) * - * const emptyGraph = Graph.directed() - * console.log(Graph.edgeCount(emptyGraph)) // 0 + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * - * const graphWithEdges = Graph.mutate(emptyGraph, (mutable) => { + * const graph = Graph.mutate(Graph.directed(), (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") * const nodeB = Graph.addNode(mutable, "Node B") * const nodeC = Graph.addNode(mutable, "Node C") - * Graph.addEdge(mutable, nodeA, nodeB, 1) - * Graph.addEdge(mutable, nodeB, nodeC, 2) - * Graph.addEdge(mutable, nodeC, nodeA, 3) + * Graph.addEdge(mutable, nodeA, nodeB, 10) + * Graph.addEdge(mutable, nodeB, nodeC, 20) * }) * - * console.log(Graph.edgeCount(graphWithEdges)) // 3 + * Graph.findEdge(graph, (data) => data > 15) // => Option.some(1) + * Graph.findEdge(graph, (data) => data > 100) // => Option.none() * ``` * * @category getters * @since 3.18.0 */ -export const edgeCount = ( - graph: Graph | MutableGraph -): number => graph.edges.size - -const getDirectedNeighbors = ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex, - direction: Direction -): Array => { - const adjacencyMap = direction === "incoming" - ? graph.reverseAdjacency - : graph.adjacency - - const adjacencyList = adjacencyMap.get(nodeIndex) - if (adjacencyList === undefined) { - return [] - } - - const result: Array = [] - for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) - if (edge !== undefined) { - result.push(direction === "incoming" ? edge.source : edge.target) +export const findEdge: { + ( + predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean + ): (graph: Graph | MutableGraph) => Option.Option + ( + graph: Graph | MutableGraph, + predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean + ): Option.Option +} = dual(2, ( + graph: Graph | MutableGraph, + predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean +): Option.Option => { + const impl = internal.toImpl(graph) + return withMutationGuard(graph, () => { + for (const [edgeIndex, edgeData] of impl.edges) { + if (predicate(edgeData.data, edgeData.source, edgeData.target)) { + return Option.some(edgeIndex) + } } - } - - return result -} + return Option.none() + }) +}) /** - * Returns the neighboring node indices for a node. - * - * **Details** - * - * For directed graphs, neighbors are the targets of outgoing edges. For - * undirected graphs, neighbors are the other endpoints of incident edges. + * Finds all edges that match the given predicate. * - * **Example** (Getting outgoing neighbors) + * **Example** (Finding matching edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.mutate(Graph.directed(), (mutable) => { * const nodeA = Graph.addNode(mutable, "Node A") * const nodeB = Graph.addNode(mutable, "Node B") * const nodeC = Graph.addNode(mutable, "Node C") - * Graph.addEdge(mutable, nodeA, nodeB, 1) - * Graph.addEdge(mutable, nodeA, nodeC, 2) + * Graph.addEdge(mutable, nodeA, nodeB, 10) + * Graph.addEdge(mutable, nodeB, nodeC, 20) + * Graph.addEdge(mutable, nodeC, nodeA, 30) * }) * - * const nodeA = 0 - * const nodeB = 1 - * const nodeC = 2 - * - * const neighborsA = Graph.neighbors(graph, nodeA) - * console.log(neighborsA) // [1, 2] - * - * const neighborsB = Graph.neighbors(graph, nodeB) - * console.log(neighborsB) // [] + * Graph.findEdges(graph, (data) => data >= 20) // => [1, 2] + * Graph.findEdges(graph, (data) => data > 100) // => [] * ``` * * @category getters * @since 3.18.0 */ -export const neighbors: { - ( - nodeIndex: NodeIndex - ): (graph: Graph | MutableGraph) => Array +export const findEdges: { + ( + predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean + ): (graph: Graph | MutableGraph) => Array ( graph: Graph | MutableGraph, - nodeIndex: NodeIndex - ): Array + predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean + ): Array } = dual(2, ( graph: Graph | MutableGraph, - nodeIndex: NodeIndex -): Array => { - // For undirected graphs, use the specialized helper that returns the other endpoint - if (graph.type === "undirected") { - return getUndirectedNeighbors(graph as any, nodeIndex) - } - - return getDirectedNeighbors(graph as Graph | MutableGraph, nodeIndex, "outgoing") + predicate: (data: E, source: NodeIndex, target: NodeIndex) => boolean +): Array => { + const impl = internal.toImpl(graph) + return withMutationGuard(graph, () => { + const results: Array = [] + for (const [edgeIndex, edgeData] of impl.edges) { + if (predicate(edgeData.data, edgeData.source, edgeData.target)) { + results.push(edgeIndex) + } + } + return results + }) }) /** - * Returns the outgoing neighbor node indices for a node in a directed graph. + * Updates a single node's data by applying a transformation function. * * **When to use** * - * Use when you need the nodes reached by following outgoing edges from a node in - * a directed graph. + * Use when replacing one node payload while preserving its identifier and + * incident edges. * * **Gotchas** * - * Throws a `GraphError` when used with an undirected graph. + * A missing node index is ignored. The transformation may query the graph, but + * cannot mutate or finalize the same graph while it runs. * - * @see {@link predecessors} for incoming neighbors in a directed graph - * @see {@link neighbors} for generic neighbor lookup across graph kinds + * **Example** (Updating node data) * - * @category queries - * @since 4.0.0 + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const graph = Graph.directed((mutable) => { + * Graph.addNode(mutable, "Node A") + * Graph.addNode(mutable, "Node B") + * Graph.updateNode(mutable, 0, (data) => data.toUpperCase()) + * }) + * + * Graph.getNode(graph, 0) // => Option.some("NODE A") + * ``` + * + * @category transforming + * @since 3.18.0 */ -export const successors: { - ( - nodeIndex: NodeIndex - ): (graph: Graph | MutableGraph) => Array - ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex - ): Array -} = dual(2, ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex -): Array => { - if (graph.type === "undirected") { - throw new GraphError({ message: "Cannot get successors of undirected graph" }) - } - return getDirectedNeighbors(graph as Graph | MutableGraph, nodeIndex, "outgoing") -}) +export const updateNode = ( + mutable: MutableGraph, + index: NodeIndex, + f: (data: N) => N +): void => { + const impl = getMutableImplForMutation(mutable) + internal.withTransformation(mutable, () => { + if (!impl.nodes.has(index)) { + return + } + + const currentData = impl.nodes.get(index)! + const newData = f(currentData) + impl.nodes.set(index, newData) + }) +} /** - * Returns the incoming neighbor node indices for a node in a directed graph. + * Updates a single edge's data by applying a transformation function. * * **When to use** * - * Use when you need the nodes that reach a node by following incoming edges in a - * directed graph. + * Use when replacing one edge payload while preserving its identifier and + * endpoints. * * **Gotchas** * - * Throws a `GraphError` when used with an undirected graph. + * A missing edge index is ignored. The transformation may query the graph, but + * cannot mutate or finalize the same graph while it runs. * - * @see {@link successors} for outgoing neighbors in a directed graph - * @see {@link neighbors} for generic neighbor lookup across graph kinds + * **Example** (Updating edge data) * - * @category queries - * @since 4.0.0 + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const result = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * const edgeIndex = Graph.addEdge(mutable, nodeA, nodeB, 10) + * Graph.updateEdge(mutable, edgeIndex, (data) => data * 2) + * }) + * + * Option.map(Graph.getEdge(result, 0), (edge) => edge.data) // => Option.some(20) + * ``` + * + * @category transforming + * @since 3.18.0 */ -export const predecessors: { - ( - nodeIndex: NodeIndex - ): (graph: Graph | MutableGraph) => Array - ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex - ): Array -} = dual(2, ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex -): Array => { - if (graph.type === "undirected") { - throw new GraphError({ message: "Cannot get predecessors of undirected graph" }) - } - return getDirectedNeighbors(graph as Graph | MutableGraph, nodeIndex, "incoming") -}) +export const updateEdge = ( + mutable: MutableGraph, + edgeIndex: EdgeIndex, + f: (data: E) => E +): void => { + const impl = getMutableImplForMutation(mutable) + internal.withTransformation(mutable, () => { + if (!impl.edges.has(edgeIndex)) { + return + } + + const currentEdge = impl.edges.get(edgeIndex)! + const newData = f(currentEdge.data) + impl.edges.set(edgeIndex, { + source: currentEdge.source, + target: currentEdge.target, + data: newData + }) + }) +} /** - * Gets directed neighbors of a node in a specific direction. + * Transforms every node's data in a mutable graph in place using the provided + * mapping function. * * **When to use** * - * Use when maintaining existing code that already passes an explicit traversal - * direction. New code should prefer `successors` or `predecessors`. + * Use when updating every node payload without changing graph structure. * - * **Gotchas** + * **Details** * - * Throws a `GraphError` when used with an undirected graph. + * Node indices and edges are preserved; only the stored node data is replaced. * - * **Example** (Traversing directed neighbors) + * **Gotchas** * - * ```ts - * import { Graph } from "effect" + * This function mutates in place, returns `void`, and cannot change the node + * payload type. The mapping function may query the graph, but cannot mutate or + * finalize the same graph while it runs. * - * const graph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * Graph.addEdge(mutable, a, b, "A->B") - * }) + * **Example** (Mapping node data) * - * const nodeA = 0 - * const nodeB = 1 + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * - * // Get outgoing neighbors (nodes that nodeA points to) - * const outgoing = Graph.neighborsDirected(graph, nodeA, "outgoing") + * const graph = Graph.directed((mutable) => { + * Graph.addNode(mutable, "node a") + * Graph.addNode(mutable, "node b") + * Graph.addNode(mutable, "node c") + * Graph.mapNodes(mutable, (data) => data.toUpperCase()) + * }) * - * // Get incoming neighbors (nodes that point to nodeB) - * const incoming = Graph.neighborsDirected(graph, nodeB, "incoming") + * Graph.getNode(graph, 0) // => Option.some("NODE A") * ``` * - * @deprecated Use {@link successors} for outgoing neighbors or {@link predecessors} for incoming neighbors. - * @see {@link successors} for outgoing neighbors in a directed graph - * @see {@link predecessors} for incoming neighbors in a directed graph - * @category queries + * @see {@link updateNode} for updating one node + * @see {@link filterMapNodes} for mapping while removing nodes + * @category mapping * @since 3.18.0 */ -export const neighborsDirected: { - ( - nodeIndex: NodeIndex, - direction: Direction - ): (graph: Graph | MutableGraph) => Array - ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex, - direction: Direction - ): Array -} = dual(3, ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex, - direction: Direction -): Array => { - if (graph.type === "undirected") { - throw new GraphError({ message: "Cannot get directed neighbors of undirected graph" }) - } - return getDirectedNeighbors(graph as Graph | MutableGraph, nodeIndex, direction) -}) - -// ============================================================================= -// GraphViz Export -// ============================================================================= +export const mapNodes = ( + mutable: MutableGraph, + f: (data: N) => N +): void => { + const impl = getMutableImplForMutation(mutable) + internal.withTransformation(mutable, () => { + // Transform existing node data in place + for (const [index, data] of impl.nodes) { + const newData = f(data) + impl.nodes.set(index, newData) + } + }) +} /** - * Configuration options for GraphViz DOT format generation from graphs. + * Transforms every edge payload in a mutable graph in place. + * + * **When to use** + * + * Use when updating every edge payload without changing graph structure. * * **Details** * - * These options customize node labels, edge labels, and graph naming in DOT - * format compatible with GraphViz tools. + * Edge identifiers and endpoints are preserved. * - * **Example** (Configuring GraphViz labels) + * **Gotchas** * - * ```ts - * import type { Graph } from "effect" + * This function mutates in place, returns `void`, and cannot change the edge + * payload type. The mapping function may query the graph, but cannot mutate or + * finalize the same graph while it runs. * - * // Basic options with custom labels - * const basicOptions: Graph.GraphVizOptions = { - * nodeLabel: (data) => `Node: ${data}`, - * edgeLabel: (data) => `Weight: ${data}` - * } + * **Example** (Mapping edge data) * - * // Complete options with graph naming - * const namedOptions: Graph.GraphVizOptions = { - * nodeLabel: (data) => data.toUpperCase(), - * edgeLabel: (data) => data, - * graphName: "MyDependencyGraph" - * } + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, 10) + * Graph.addEdge(mutable, b, c, 20) + * Graph.mapEdges(mutable, (data) => data * 2) + * }) + * + * Option.map(Graph.getEdge(graph, 0), (edge) => edge.data) // => Option.some(20) * ``` * - * @category options + * @see {@link updateEdge} for updating one edge + * @see {@link filterMapEdges} for mapping while removing edges + * @category mapping * @since 3.18.0 */ -export interface GraphVizOptions { - /** - * Function to generate custom labels for nodes. - * Defaults to String(data) if not provided. - */ - readonly nodeLabel?: (data: N) => string - - /** - * Function to generate custom labels for edges. - * Defaults to String(data) if not provided. - */ - readonly edgeLabel?: (data: E) => string - - /** - * Name for the DOT graph. - * Defaults to "G" if not provided. - */ - readonly graphName?: string +export const mapEdges = ( + mutable: MutableGraph, + f: (data: E) => E +): void => { + const impl = getMutableImplForMutation(mutable) + internal.withTransformation(mutable, () => { + // Transform existing edge data in place + for (const [index, edgeData] of impl.edges) { + const newData = f(edgeData.data) + impl.edges.set(index, { + source: edgeData.source, + target: edgeData.target, + data: newData + }) + } + }) } /** - * Exports a graph to GraphViz DOT format for visualization. + * Swaps source and target nodes for every edge in a mutable graph. * - * **Example** (Exporting GraphViz DOT) + * **When to use** * - * ```ts - * import { Graph } from "effect" + * Use when reversing every relationship in a directed graph, such as creating + * a dependency transpose. * - * const graph = Graph.mutate(Graph.directed(), (mutable) => { - * const nodeA = Graph.addNode(mutable, "Node A") - * const nodeB = Graph.addNode(mutable, "Node B") - * const nodeC = Graph.addNode(mutable, "Node C") - * Graph.addEdge(mutable, nodeA, nodeB, 1) - * Graph.addEdge(mutable, nodeB, nodeC, 2) - * Graph.addEdge(mutable, nodeC, nodeA, 3) + * **Details** + * + * Edge identifiers and payloads are preserved. + * + * **Gotchas** + * + * This operation is a no-op for undirected graphs. + * + * **Example** (Reversing edge directions) + * + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, 1) // A -> B + * Graph.addEdge(mutable, b, c, 2) // B -> C + * Graph.reverse(mutable) // Now B -> A, C -> B * }) * - * const dot = Graph.toGraphViz(graph) - * console.log(dot) - * // digraph G { - * // "0" [label="Node A"]; - * // "1" [label="Node B"]; - * // "2" [label="Node C"]; - * // "0" -> "1" [label="1"]; - * // "1" -> "2" [label="2"]; - * // "2" -> "0" [label="3"]; - * // } + * Option.map(Graph.getEdge(graph, 0), (edge) => edge.source) // => Option.some(1) * ``` * - * @category converting + * @category transforming * @since 3.18.0 */ -export const toGraphViz: { - ( - options?: GraphVizOptions - ): (graph: Graph | MutableGraph) => string - ( - graph: Graph | MutableGraph, - options?: GraphVizOptions - ): string -} = dual((args) => isGraph(args[0]), ( - graph: Graph | MutableGraph, - options?: GraphVizOptions -): string => { - const { - edgeLabel = (data: E) => String(data), - graphName = "G", - nodeLabel = (data: N) => String(data) - } = options ?? {} - - const isDirected = graph.type === "directed" - const graphType = isDirected ? "digraph" : "graph" - const edgeOperator = isDirected ? "->" : "--" - - const lines: Array = [] - lines.push(`${graphType} ${graphName} {`) - - // Add nodes - for (const [nodeIndex, nodeData] of graph.nodes) { - const label = nodeLabel(nodeData).replace(/"/g, "\\\"") - lines.push(` "${nodeIndex}" [label="${label}"];`) +export const reverse = ( + mutable: MutableGraph +): void => { + const impl = getMutableImplForMutation(mutable) + if (impl.type === "undirected") { + return } - // Add edges - for (const [, edgeData] of graph.edges) { - const label = edgeLabel(edgeData.data).replace(/"/g, "\\\"") - lines.push(` "${edgeData.source}" ${edgeOperator} "${edgeData.target}" [label="${label}"];`) + // Reverse all edges by swapping source and target + for (const [index, edgeData] of impl.edges) { + impl.edges.set(index, { + source: edgeData.target, + target: edgeData.source, + data: edgeData.data + }) } - lines.push("}") - return lines.join("\n") -}) + const adjacency = impl.adjacency + impl.adjacency = impl.reverseAdjacency + impl.reverseAdjacency = adjacency -// ============================================================================= -// Mermaid Export -// ============================================================================= + // Invalidate cycle flag since edge directions changed + impl.acyclic = Option.none() +} /** - * Mermaid node shape types for diagram visualization. + * Filters and optionally transforms nodes in a mutable graph using a predicate function. + * Nodes that return Option.none are removed along with all their connected edges. * - * **Details** + * **Gotchas** * - * Each shape produces different visual representations in Mermaid diagrams: - * - `rectangle`: Standard rectangular nodes `A["label"]` - * - `rounded`: Rounded rectangular nodes `A("label")` - * - `circle`: Circular nodes `A(("label"))` - * - `diamond`: Diamond-shaped nodes `A{"label"}` - * - `hexagon`: Hexagonal nodes `A{{"label"}}` - * - `stadium`: Stadium-shaped nodes `A(["label"])` - * - `subroutine`: Subroutine-style nodes `A[["label"]]` - * - `cylindrical`: Cylindrical database-style nodes `A[("label")]` + * The function may query the graph, but cannot mutate or finalize the same + * graph while it runs. Retained payloads must remain the same node type. * - * **Example** (Selecting Mermaid node shapes) + * **Example** (Filtering and mapping nodes) * - * ```ts - * import type { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * - * // Shape selector function for different node types - * const shapeSelector = (nodeData: string): Graph.MermaidNodeShape => { - * if (nodeData.includes("start") || nodeData.includes("end")) return "circle" - * if (nodeData.includes("decision")) return "diamond" - * if (nodeData.includes("process")) return "rectangle" - * if (nodeData.includes("data")) return "cylindrical" - * return "rounded" - * } + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "active") + * const b = Graph.addNode(mutable, "inactive") + * const c = Graph.addNode(mutable, "active") + * Graph.addEdge(mutable, a, b, 1) + * Graph.addEdge(mutable, b, c, 2) * - * const options: Graph.MermaidOptions = { - * nodeShape: shapeSelector - * } + * // Keep only "active" nodes and transform to uppercase + * Graph.filterMapNodes( + * mutable, + * (data) => + * data === "active" ? Option.some(data.toUpperCase()) : Option.none() + * ) + * }) + * + * Graph.nodeCount(graph) // => 2 * ``` * - * @category models + * @category filtering * @since 3.18.0 */ -export type MermaidNodeShape = - | "rectangle" // A["label"] - | "rounded" // A("label") - | "circle" // A(("label")) - | "diamond" // A{"label"} - | "hexagon" // A{{"label"}} - | "stadium" // A(["label"]) - | "subroutine" // A[["label"]] - | "cylindrical" // A[("label")] +export const filterMapNodes = ( + mutable: MutableGraph, + f: (data: N) => Option.Option +): void => { + const impl = getMutableImplForMutation(mutable) + const remove: Array = [] + internal.withTransformation(mutable, () => { + // First pass: identify nodes to remove and transform data for nodes to keep + for (const [index, data] of impl.nodes) { + const result = f(data) + if (Option.isSome(result)) { + // Transform node data + impl.nodes.set(index, result.value) + } else { + // Mark for removal + remove.push(index) + } + } + }) + + // Second pass: remove filtered out nodes and their edges + removeNodes(mutable, remove) +} /** - * Mermaid diagram direction types for controlling layout orientation. + * Filters and optionally transforms edges in a mutable graph using a predicate function. + * Edges that return Option.none are removed from the graph. * - * **Details** + * **Gotchas** * - * Determines the flow direction of nodes and edges in the diagram: - * - `TB`/`TD`: Top to Bottom (vertical layout, default) - * - `BT`: Bottom to Top (reverse vertical) - * - `LR`: Left to Right (horizontal layout) - * - `RL`: Right to Left (reverse horizontal) + * The function may query the graph, but cannot mutate or finalize the same + * graph while it runs. Retained payloads must remain the same edge type. * - * **Example** (Configuring Mermaid directions) + * **Example** (Filtering and mapping edges) * - * ```ts - * import type { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * - * // Horizontal workflow diagram - * const horizontalOptions: Graph.MermaidOptions = { - * direction: "LR" - * } + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, 5) + * Graph.addEdge(mutable, b, c, 15) + * Graph.addEdge(mutable, c, a, 25) * - * // Vertical hierarchy (default) - * const verticalOptions: Graph.MermaidOptions = { - * direction: "TB" - * } + * // Keep only edges with weight >= 10 and double their weight + * Graph.filterMapEdges( + * mutable, + * (data) => data >= 10 ? Option.some(data * 2) : Option.none() + * ) + * }) * - * // Bottom-up flow - * const bottomUpOptions: Graph.MermaidOptions = { - * direction: "BT" - * } + * Graph.edgeCount(graph) // => 2 * ``` * - * @category models + * @category filtering * @since 3.18.0 */ -export type MermaidDirection = - | "TB" // Top to Bottom (default) - | "TD" // Top Down (same as TB) - | "BT" // Bottom to Top - | "RL" // Right to Left - | "LR" // Left to Right +export const filterMapEdges = ( + mutable: MutableGraph, + f: (data: E) => Option.Option +): void => { + const impl = getMutableImplForMutation(mutable) + const remove: Array = [] + internal.withTransformation(mutable, () => { + // First pass: identify edges to remove and transform data for edges to keep + for (const [index, edgeData] of impl.edges) { + const result = f(edgeData.data) + if (Option.isSome(result)) { + // Transform edge data + impl.edges.set(index, { + source: edgeData.source, + target: edgeData.target, + data: result.value + }) + } else { + // Mark for removal + remove.push(index) + } + } + }) + + // Second pass: remove filtered out edges + removeEdges(mutable, remove) +} /** - * Mermaid diagram types for different visualization formats. - * - * **Details** + * Filters nodes by removing those that don't match the predicate. + * This function modifies the mutable graph in place. + * Removed nodes also remove all incident edges; retained node identifiers are + * preserved. * - * Specifies the Mermaid diagram syntax to use: - * - `flowchart`: For directed graphs with arrows (`A --> B`) - * - `graph`: For undirected graphs with lines (`A --- B`) + * **Gotchas** * - * When not specified, automatically selects based on graph type: - * directed graphs use "flowchart", undirected graphs use "graph". + * The predicate may query the graph, but cannot mutate or finalize the same + * graph while it runs. * - * **Example** (Selecting Mermaid diagram types) + * **Example** (Filtering nodes) * - * ```ts - * import type { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph } from "effect" * - * // Force flowchart format (even for undirected graphs) - * const flowchartOptions: Graph.MermaidOptions = { - * diagramType: "flowchart" - * } + * const graph = Graph.directed((mutable) => { + * Graph.addNode(mutable, "active") + * Graph.addNode(mutable, "inactive") + * Graph.addNode(mutable, "pending") + * Graph.addNode(mutable, "active") * - * // Force graph format (shows undirected connections) - * const graphOptions: Graph.MermaidOptions = { - * diagramType: "graph" - * } + * // Keep only "active" nodes + * Graph.filterNodes(mutable, (data) => data === "active") + * }) * - * // Auto-detection (recommended, default behavior) - * const autoOptions: Graph.MermaidOptions = {} + * Graph.nodeCount(graph) // => 2 * ``` * - * @category models + * @category filtering * @since 3.18.0 */ -export type MermaidDiagramType = - | "flowchart" // For directed graphs - | "graph" // For undirected graphs +export const filterNodes = ( + mutable: MutableGraph, + predicate: (data: N) => boolean +): void => { + const impl = getMutableImplForMutation(mutable) + const remove: Array = [] + + internal.withTransformation(mutable, () => { + // Identify nodes to remove + for (const [index, data] of impl.nodes) { + if (!predicate(data)) { + remove.push(index) + } + } + }) + + // Remove filtered out nodes (this also removes connected edges) + removeNodes(mutable, remove) +} /** - * Configuration options for Mermaid diagram generation, following GraphViz pattern. + * Filters edges by removing those that don't match the predicate. + * This function modifies the mutable graph in place. + * Nodes are retained even when removing edges leaves them isolated. * - * @category models - * @since 4.0.0 - */ -/** - * Configuration options for Mermaid diagram generation from graphs. + * **Gotchas** * - * **Details** + * The predicate may query the graph, but cannot mutate or finalize the same + * graph while it runs. * - * These options customize node labels, edge labels, diagram type, layout - * direction, node shapes, and graph naming in Mermaid format. + * **Example** (Filtering edges) * - * **Example** (Configuring Mermaid output) + * ```ts import.meta.vitest + * import { Graph } from "effect" * - * ```ts - * import type { Graph } from "effect" + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") * - * // Basic options with custom labels - * const basicOptions: Graph.MermaidOptions = { - * nodeLabel: (data) => `Node: ${data}`, - * edgeLabel: (data) => `Weight: ${data}` - * } + * Graph.addEdge(mutable, a, b, 5) + * Graph.addEdge(mutable, b, c, 15) + * Graph.addEdge(mutable, c, a, 25) * - * // Advanced options with all features - * const advancedOptions: Graph.MermaidOptions = { - * nodeLabel: (data) => data.toUpperCase(), - * edgeLabel: (data) => data, - * diagramType: "flowchart", - * direction: "LR", - * nodeShape: (data) => data.includes("start") ? "circle" : "rectangle" - * } + * // Keep only edges with weight >= 10 + * Graph.filterEdges(mutable, (data) => data >= 10) + * }) + * + * Graph.edgeCount(graph) // => 2 * ``` * - * @category options + * @category filtering * @since 3.18.0 */ -export interface MermaidOptions { - /** - * Function to generate custom labels for nodes. - * Defaults to String(data) if not provided. - */ - readonly nodeLabel?: (data: N) => string +export const filterEdges = ( + mutable: MutableGraph, + predicate: (data: E) => boolean +): void => { + const impl = getMutableImplForMutation(mutable) + const remove: Array = [] + + internal.withTransformation(mutable, () => { + // Identify edges to remove + for (const [index, edgeData] of impl.edges) { + if (!predicate(edgeData.data)) { + remove.push(index) + } + } + }) - /** - * Function to generate custom labels for edges. - * Defaults to String(data) if not provided. - */ - readonly edgeLabel?: (data: E) => string - - /** - * Diagram type override. If not specified, automatically detects: - * - "flowchart" for directed graphs - * - "graph" for undirected graphs - */ - readonly diagramType?: MermaidDiagramType - - /** - * Direction for diagram layout. - * Defaults to "TD" (Top Down) if not provided. - */ - readonly direction?: MermaidDirection - - /** - * Function to determine node shape for each node. - * Defaults to "rectangle" for all nodes if not provided. - */ - readonly nodeShape?: (data: N) => MermaidNodeShape + // Remove filtered out edges + removeEdges(mutable, remove) } -/** - * Escapes special characters in labels for Mermaid syntax compatibility. - */ -const escapeMermaidLabel = (label: string): string => { - // Escape special characters for Mermaid using HTML entity codes - // According to: https://mermaid.js.org/syntax/flowchart.html#special-characters-that-break-syntax - return label - .replace(/#/g, "#35;") - .replace(/"/g, "#quot;") - .replace(//g, "#gt;") - .replace(/&/g, "#amp;") - .replace(/\[/g, "#91;") - .replace(/\]/g, "#93;") - .replace(/\{/g, "#123;") - .replace(/\}/g, "#125;") - .replace(/\(/g, "#40;") - .replace(/\)/g, "#41;") - .replace(/\|/g, "#124;") - .replace(/\\/g, "#92;") - .replace(/\n/g, "
") +// ============================================================================= +// Cycle Flag Management (Internal) +// ============================================================================= + +/** @internal */ +const invalidateCycleFlagOnRemoval = ( + mutable: internal.GraphImpl +): void => { + // Only invalidate if the graph had cycles (removing edges/nodes cannot introduce cycles in acyclic graphs). + if (Option.isSome(mutable.acyclic) && mutable.acyclic.value === false) { + mutable.acyclic = Option.none() + } } -/** - * Formats a Mermaid node with the specified shape and label. - */ -const formatMermaidNode = ( - nodeId: string, - label: string, - shape: MermaidNodeShape -): string => { - switch (shape) { - case "rectangle": - return `${nodeId}["${label}"]` - case "rounded": - return `${nodeId}("${label}")` - case "circle": - return `${nodeId}(("${label}"))` - case "diamond": - return `${nodeId}{"${label}"}` - case "hexagon": - return `${nodeId}{{"${label}"}}` - case "stadium": - return `${nodeId}(["${label}"])` - case "subroutine": - return `${nodeId}[["${label}"]]` - case "cylindrical": - return `${nodeId}[("${label}")]` - default: - return `${nodeId}["${label}"]` // Default rectangle +/** @internal */ +const invalidateCycleFlagOnAddition = ( + mutable: internal.GraphImpl +): void => { + // Only invalidate if the graph was acyclic (adding edges cannot remove cycles from cyclic graphs). + if (Option.isSome(mutable.acyclic) && mutable.acyclic.value === true) { + mutable.acyclic = Option.none() } } +// ============================================================================= +// Edge Operations +// ============================================================================= + /** - * Exports a graph to Mermaid diagram format for visualization. + * Adds a new edge to a mutable graph and returns its index. + * + * **When to use** + * + * Use to connect two existing nodes in a mutable graph while storing edge data + * and receiving the new edge identifier. * * **Details** * - * Mermaid is a popular diagram-as-code tool that generates flowcharts and other - * visualizations from text-based definitions. This function converts Effect Graph - * structures to valid Mermaid syntax for use in documentation, web applications, - * and visualization tools. + * Self-loops and parallel edges are allowed. Undirected graphs retain the + * supplied source and target orientation in the stored `Edge`, while traversal + * and neighbor queries treat the connection as bidirectional. * - * **Example** (Exporting a directed Mermaid diagram) + * **Gotchas** + * + * The source and target nodes must already exist in the mutable graph; missing + * endpoints throw a `GraphError`. * - * ```ts + * **Example** (Adding edges) + * + * ```ts import.meta.vitest * import { Graph } from "effect" * - * // Basic directed graph export - * const graph = Graph.directed((mutable) => { - * const app = Graph.addNode(mutable, "App") - * const db = Graph.addNode(mutable, "Database") - * const cache = Graph.addNode(mutable, "Cache") - * Graph.addEdge(mutable, app, db, 1) - * Graph.addEdge(mutable, app, cache, 2) + * Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * Graph.addEdge(mutable, nodeA, nodeB, 42) // => 0 * }) - * - * const mermaid = Graph.toMermaid(graph) - * console.log(mermaid) - * // flowchart TD - * // 0["App"] - * // 1["Database"] - * // 2["Cache"] - * // 0 -->|"1"| 1 - * // 0 -->|"2"| 2 * ``` * - * **Example** (Exporting an undirected Mermaid diagram) + * @see {@link mutate} for obtaining a mutable graph from an immutable graph + * @see {@link addNode} for creating node indexes before connecting them * - * ```ts - * import { Graph } from "effect" + * @category mutations + * @since 3.18.0 + */ +export const addEdge = ( + mutable: MutableGraph, + source: NodeIndex, + target: NodeIndex, + data: E +): EdgeIndex => { + const impl = getMutableImplForMutation(mutable) + + // Validate that both nodes exist + if (!impl.nodes.has(source)) { + throw missingNode(source) + } + if (!impl.nodes.has(target)) { + throw missingNode(target) + } + + const edgeIndex = impl.nextEdgeIndex + if (!Number.isSafeInteger(edgeIndex)) { + throw new GraphError({ message: "Graph has exhausted safe edge indexes" }) + } + + // Create edge data + const edgeData: Edge = { source, target, data } + impl.edges.set(edgeIndex, edgeData) + + // Update adjacency lists + const sourceAdjacency = impl.adjacency.get(source) + if (sourceAdjacency !== undefined) { + sourceAdjacency.push(edgeIndex) + } + + const targetReverseAdjacency = impl.reverseAdjacency.get(target) + if (targetReverseAdjacency !== undefined) { + targetReverseAdjacency.push(edgeIndex) + } + + // For undirected graphs, add reverse connections + if (impl.type === "undirected") { + const targetAdjacency = impl.adjacency.get(target) + if (targetAdjacency !== undefined) { + targetAdjacency.push(edgeIndex) + } + + const sourceReverseAdjacency = impl.reverseAdjacency.get(source) + if (sourceReverseAdjacency !== undefined) { + sourceReverseAdjacency.push(edgeIndex) + } + } + + // Update allocators + impl.nextEdgeIndex = impl.nextEdgeIndex + 1 + + // Only invalidate cycle flag if the graph was acyclic + // Adding edges cannot remove cycles from cyclic graphs + invalidateCycleFlagOnAddition(impl) + + return edgeIndex +} + +/** + * Removes a node and all its incident edges from a mutable graph. * - * // Undirected graph with custom labels and direction - * const socialGraph = Graph.undirected<{ name: string }, string>((mutable) => { - * const alice = Graph.addNode(mutable, { name: "Alice" }) - * const bob = Graph.addNode(mutable, { name: "Bob" }) - * const charlie = Graph.addNode(mutable, { name: "Charlie" }) - * Graph.addEdge(mutable, alice, bob, "friends") - * Graph.addEdge(mutable, bob, charlie, "colleagues") - * }) + * **Gotchas** * - * const mermaid = Graph.toMermaid(socialGraph, { - * nodeLabel: (person) => person.name, - * edgeLabel: (relationship) => relationship, - * direction: "LR" - * }) - * console.log(mermaid) - * // graph LR - * // 0["Alice"] - * // 1["Bob"] - * // 2["Charlie"] - * // 0 ---|"friends"| 1 - * // 1 ---|"colleagues"| 2 - * ``` + * A missing node index is ignored. * - * **Example** (Customizing Mermaid node shapes) + * **Example** (Removing a node) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * - * // Advanced styling with node shapes for flowchart - * const workflow = Graph.directed<{ type: string; name: string }, string>( - * (mutable) => { - * const start = Graph.addNode(mutable, { type: "start", name: "Begin" }) - * const process = Graph.addNode(mutable, { - * type: "process", - * name: "Process Data" - * }) - * const decision = Graph.addNode(mutable, { - * type: "decision", - * name: "Valid?" - * }) - * const end = Graph.addNode(mutable, { type: "end", name: "Complete" }) - * Graph.addEdge(mutable, start, process, "") - * Graph.addEdge(mutable, process, decision, "") - * Graph.addEdge(mutable, decision, end, "yes") - * } - * ) - * - * const mermaid = Graph.toMermaid(workflow, { - * nodeLabel: (node) => node.name, - * nodeShape: (node) => { - * switch (node.type) { - * case "start": - * return "stadium" - * case "process": - * return "rectangle" - * case "decision": - * return "diamond" - * case "end": - * return "stadium" - * default: - * return "rectangle" - * } - * } + * const result = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * Graph.addEdge(mutable, nodeA, nodeB, 42) + * + * // Remove nodeA and all edges connected to it + * Graph.removeNode(mutable, nodeA) * }) - * console.log(mermaid) - * // flowchart TD - * // 0(["Begin"]) - * // 1["Process Data"] - * // 2{"Valid?"} - * // 3(["Complete"]) - * // 0 --> 1 - * // 1 --> 2 - * // 2 --> 3 + * Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [1, 0] * ``` * - * **Example** (Visualizing dependency graphs) + * @category mutations + * @since 3.18.0 + */ +export const removeNode = ( + mutable: MutableGraph, + nodeIndex: NodeIndex +): void => { + const impl = getMutableImplForMutation(mutable) + if (removeNodeInternal(impl, nodeIndex)) { + invalidateCycleFlagOnRemoval(impl) + } +} + +/** + * Removes multiple nodes and all their incident edges from a mutable graph. * - * ```ts - * import { Graph } from "effect" + * **When to use** * - * // Real-world example: Software dependency graph - * interface Dependency { - * name: string - * version: string - * type: "library" | "framework" | "tool" - * } + * Use when deleting a collection of nodes in one mutation pass. * - * const dependencyGraph = Graph.directed((mutable) => { - * const app = Graph.addNode(mutable, { - * name: "MyApp", - * version: "1.0.0", - * type: "library" - * }) - * const react = Graph.addNode(mutable, { - * name: "React", - * version: "18.0.0", - * type: "framework" - * }) - * const lodash = Graph.addNode(mutable, { - * name: "Lodash", - * version: "4.17.0", - * type: "library" - * }) - * const webpack = Graph.addNode(mutable, { - * name: "Webpack", - * version: "5.0.0", - * type: "tool" - * }) - * - * Graph.addEdge(mutable, app, react, "depends on") - * Graph.addEdge(mutable, app, lodash, "depends on") - * Graph.addEdge(mutable, app, webpack, "builds with") - * }) + * **Details** * - * const dependencyDiagram = Graph.toMermaid(dependencyGraph, { - * nodeLabel: (dep) => `${dep.name}\\nv${dep.version}`, - * edgeLabel: (edge) => edge, - * nodeShape: (dep) => - * dep.type === "framework" ? - * "hexagon" : - * dep.type === "tool" - * ? "diamond" - * : "rectangle", - * direction: "TB" - * }) + * The input is collected before mutation, so it may be backed by an iterator + * over the same graph. * - * console.log(dependencyDiagram) - * // flowchart TB - * // 0["MyApp\nv1.0.0"] - * // 1{{"React\nv18.0.0"}} - * // 2["Lodash\nv4.17.0"] - * // 3{"Webpack\nv5.0.0"} - * // 0 -->|"depends on"| 1 - * // 0 -->|"depends on"| 2 - * // 0 -->|"builds with"| 3 - * ``` + * **Gotchas** * - * @category converting - * @since 3.18.0 + * Missing and duplicate node indices are ignored. Removing a node also removes + * all of its incident edges. + * + * @see {@link removeNode} for removing one node + * + * @category mutations + * @since 4.0.0 */ -export const toMermaid: { - ( - options?: MermaidOptions - ): (graph: Graph | MutableGraph) => string - ( - graph: Graph | MutableGraph, - options?: MermaidOptions - ): string -} = dual((args) => isGraph(args[0]), ( - graph: Graph | MutableGraph, - options?: MermaidOptions -): string => { - // Extract and validate options with defaults - const { - diagramType, - direction = "TD", - edgeLabel = (data: E) => String(data), - nodeLabel = (data: N) => String(data), - nodeShape = () => "rectangle" as const - } = options ?? {} +export const removeNodes = ( + mutable: MutableGraph, + nodeIndices: Iterable +): void => { + assertMutable(mutable) + if (internal.isTransforming(mutable)) { + throw new GraphError({ message: "Cannot mutate graph during a transformation" }) + } + const indices = internal.withTransformation(mutable, () => Array.from(nodeIndices)) + const impl = getMutableImplForMutation(mutable) - // Auto-detect diagram type if not specified - const finalDiagramType = diagramType ?? - (graph.type === "directed" ? "flowchart" : "graph") + let removed = false + for (const nodeIndex of indices) { + if (removeNodeInternal(impl, nodeIndex)) { + removed = true + } + } - // Generate diagram header - const lines: Array = [] - lines.push(`${finalDiagramType} ${direction}`) + if (removed) { + invalidateCycleFlagOnRemoval(impl) + } +} - // Add nodes - for (const [nodeIndex, nodeData] of graph.nodes) { - const nodeId = String(nodeIndex) - const label = escapeMermaidLabel(nodeLabel(nodeData)) - const shape = nodeShape(nodeData) - const formattedNode = formatMermaidNode(nodeId, label, shape) - lines.push(` ${formattedNode}`) +/** @internal */ +const removeNodeInternal = ( + impl: internal.GraphImpl, + nodeIndex: NodeIndex +): boolean => { + // Check if node exists + if (!impl.nodes.has(nodeIndex)) { + return false // Node doesn't exist, nothing to remove } - // Add edges - const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---" - for (const [, edgeData] of graph.edges) { - const sourceId = String(edgeData.source) - const targetId = String(edgeData.target) - const label = escapeMermaidLabel(edgeLabel(edgeData.data)) + const edgesToRemove = new Set(impl.adjacency.get(nodeIndex)!) + for (const edgeIndex of impl.reverseAdjacency.get(nodeIndex)!) { + edgesToRemove.add(edgeIndex) + } - if (label) { - lines.push(` ${sourceId} ${edgeOperator}|"${label}"| ${targetId}`) - } else { - lines.push(` ${sourceId} ${edgeOperator} ${targetId}`) + for (const edgeIndex of edgesToRemove) { + const edge = impl.edges.get(edgeIndex)! + if (edge.source !== nodeIndex) { + const adjacency = impl.adjacency.get(edge.source)! + adjacency.splice(adjacency.indexOf(edgeIndex), 1) + if (impl.type === "undirected") { + const reverseAdjacency = impl.reverseAdjacency.get(edge.source)! + reverseAdjacency.splice(reverseAdjacency.indexOf(edgeIndex), 1) + } } + if (edge.target !== nodeIndex) { + const reverseAdjacency = impl.reverseAdjacency.get(edge.target)! + reverseAdjacency.splice(reverseAdjacency.indexOf(edgeIndex), 1) + if (impl.type === "undirected") { + const adjacency = impl.adjacency.get(edge.target)! + adjacency.splice(adjacency.indexOf(edgeIndex), 1) + } + } + impl.edges.delete(edgeIndex) } - return lines.join("\n") -}) + // Remove the node itself + impl.nodes.delete(nodeIndex) + impl.adjacency.delete(nodeIndex) + impl.reverseAdjacency.delete(nodeIndex) -// ============================================================================= -// Direction Types for Bidirectional Traversal -// ============================================================================= + return true +} + +/** + * Removes an edge from a mutable graph. + * + * **Gotchas** + * + * A missing edge index is ignored. + * + * **Example** (Removing an edge) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const result = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * const edge = Graph.addEdge(mutable, nodeA, nodeB, 42) + * + * // Remove the edge + * Graph.removeEdge(mutable, edge) + * }) + * Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [2, 0] + * ``` + * + * @category mutations + * @since 3.18.0 + */ +export const removeEdge = ( + mutable: MutableGraph, + edgeIndex: EdgeIndex +): void => { + const impl = getMutableImplForMutation(mutable) + // Only invalidate cycle flag if an edge was actually removed + // and only if the graph wasn't already known to be acyclic + if (removeEdgeInternal(impl, edgeIndex)) { + invalidateCycleFlagOnRemoval(impl) + } +} + +/** + * Removes multiple edges from a mutable graph. + * + * **When to use** + * + * Use when deleting a collection of edges in one mutation pass. + * + * **Details** + * + * The input is collected before mutation, so it may be backed by an iterator + * over the same graph. + * + * **Gotchas** + * + * Missing and duplicate edge indices are ignored. Nodes are never removed. + * + * @see {@link removeEdge} for removing one edge + * + * @category mutations + * @since 4.0.0 + */ +export const removeEdges = ( + mutable: MutableGraph, + edgeIndices: Iterable +): void => { + assertMutable(mutable) + if (internal.isTransforming(mutable)) { + throw new GraphError({ message: "Cannot mutate graph during a transformation" }) + } + const indices = internal.withTransformation(mutable, () => Array.from(edgeIndices)) + const impl = getMutableImplForMutation(mutable) + + let removed = false + for (const edgeIndex of indices) { + if (removeEdgeInternal(impl, edgeIndex)) { + removed = true + } + } + + if (removed) { + invalidateCycleFlagOnRemoval(impl) + } +} + +/** @internal */ +const removeEdgeInternal = ( + mutable: internal.GraphImpl, + edgeIndex: EdgeIndex +): boolean => { + // Get edge data + const edge = mutable.edges.get(edgeIndex) + if (edge === undefined) { + return false // Edge doesn't exist, no mutation occurred + } + + const { source, target } = edge + + // Remove from adjacency lists + const sourceAdjacency = mutable.adjacency.get(source) + if (sourceAdjacency !== undefined) { + const index = sourceAdjacency.indexOf(edgeIndex) + if (index !== -1) { + sourceAdjacency.splice(index, 1) + } + } + + const targetReverseAdjacency = mutable.reverseAdjacency.get(target) + if (targetReverseAdjacency !== undefined) { + const index = targetReverseAdjacency.indexOf(edgeIndex) + if (index !== -1) { + targetReverseAdjacency.splice(index, 1) + } + } + + // For undirected graphs, remove reverse connections + if (mutable.type === "undirected") { + const targetAdjacency = mutable.adjacency.get(target) + if (targetAdjacency !== undefined) { + const index = targetAdjacency.indexOf(edgeIndex) + if (index !== -1) { + targetAdjacency.splice(index, 1) + } + } + + const sourceReverseAdjacency = mutable.reverseAdjacency.get(source) + if (sourceReverseAdjacency !== undefined) { + const index = sourceReverseAdjacency.indexOf(edgeIndex) + if (index !== -1) { + sourceReverseAdjacency.splice(index, 1) + } + } + } + + // Remove edge data + mutable.edges.delete(edgeIndex) + + return true // Edge was successfully removed +} + +// ============================================================================= +// Edge Query Operations +// ============================================================================= + +/** + * Gets the edge data associated with an edge index safely, if it exists. + * + * **Example** (Getting edge data) + * + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * Graph.addEdge(mutable, nodeA, nodeB, 42) + * }) + * + * Graph.getEdge(graph, 0) // => Option.some({ source: 0, target: 1, data: 42 }) + * ``` + * + * @category getters + * @since 3.18.0 + */ +export const getEdge: { + (edgeIndex: EdgeIndex): ( + graph: Graph | MutableGraph + ) => Option.Option> + ( + graph: Graph | MutableGraph, + edgeIndex: EdgeIndex + ): Option.Option> +} = dual(2, ( + graph: Graph | MutableGraph, + edgeIndex: EdgeIndex +): Option.Option> => { + const edge = internal.toImpl(graph).edges.get(edgeIndex) + return edge === undefined ? Option.none() : Option.some(copyEdge(edge)) +}) + +/** + * Checks whether an edge exists between two nodes in the graph. + * + * **Details** + * + * Directed graphs test only `source` to `target`; undirected graphs accept + * either stored orientation. Parallel edges still produce one boolean result. + * + * **Gotchas** + * + * Returns `false` when either node does not exist. + * + * **Example** (Checking edge existence) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * const nodeC = Graph.addNode(mutable, "Node C") + * Graph.addEdge(mutable, nodeA, nodeB, 42) + * }) + * + * Graph.hasEdge(graph, 0, 1) // => true + * Graph.hasEdge(graph, 0, 2) // => false + * ``` + * + * @see {@link edgesBetween} for all matching edge identifiers + * @category predicates + * @since 3.18.0 + */ +export const hasEdge: { + ( + source: NodeIndex, + target: NodeIndex + ): (graph: Graph | MutableGraph) => boolean + ( + graph: Graph | MutableGraph, + source: NodeIndex, + target: NodeIndex + ): boolean +} = dual(3, ( + graph: Graph | MutableGraph, + source: NodeIndex, + target: NodeIndex +): boolean => { + const impl = internal.toImpl(graph) + const adjacencyList = impl.adjacency.get(source) + if (adjacencyList === undefined) { + return false + } + + // Check if any edge in the adjacency list connects to the target + for (const edgeIndex of adjacencyList) { + const edge = impl.edges.get(edgeIndex) + if (edge !== undefined) { + const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target + if (neighbor === target) { + return true + } + } + } + + return false +}) + +/** + * Returns the number of edges in the graph. + * + * **Example** (Counting edges) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const emptyGraph = Graph.directed() + * Graph.edgeCount(emptyGraph) // => 0 + * + * const graphWithEdges = Graph.mutate(emptyGraph, (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * const nodeC = Graph.addNode(mutable, "Node C") + * Graph.addEdge(mutable, nodeA, nodeB, 1) + * Graph.addEdge(mutable, nodeB, nodeC, 2) + * Graph.addEdge(mutable, nodeC, nodeA, 3) + * }) + * + * Graph.edgeCount(graphWithEdges) // => 3 + * ``` + * + * @category getters + * @since 3.18.0 + */ +export const edgeCount = ( + graph: Graph | MutableGraph +): number => internal.toImpl(graph).edges.size + +/** + * Returns the indices of all edges incident to a node. + * + * **Details** + * + * Each edge is returned once in graph edge order, including self-loops. + * Throws a `GraphError` when the node does not exist. + * + * @category getters + * @since 4.0.0 + */ +export const incidentEdges: { + ( + nodeIndex: NodeIndex + ): (graph: Graph | MutableGraph) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Array +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + const impl = internal.toImpl(graph) + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) + } + const outgoing = impl.adjacency.get(nodeIndex)! + if (graph.type === "undirected") { + const result: Array = [] + let previous = -1 + for (const edgeIndex of outgoing) { + if (edgeIndex !== previous) { + result.push(edgeIndex) + previous = edgeIndex + } + } + return result + } + + const incoming = impl.reverseAdjacency.get(nodeIndex)! + const result: Array = [] + let outgoingPosition = 0 + let incomingPosition = 0 + while (outgoingPosition < outgoing.length && incomingPosition < incoming.length) { + const outgoingEdge = outgoing[outgoingPosition] + const incomingEdge = incoming[incomingPosition] + if (outgoingEdge < incomingEdge) { + result.push(outgoingEdge) + outgoingPosition++ + } else if (incomingEdge < outgoingEdge) { + result.push(incomingEdge) + incomingPosition++ + } else { + result.push(outgoingEdge) + outgoingPosition++ + incomingPosition++ + } + } + while (outgoingPosition < outgoing.length) result.push(outgoing[outgoingPosition++]) + while (incomingPosition < incoming.length) result.push(incoming[incomingPosition++]) + return result +}) + +/** + * Returns the indices of outgoing edges for a node in a directed graph. + * + * **Details** + * + * Parallel edges and self-loops are returned separately in adjacency order. + * Throws a `GraphError` for an undirected graph or missing node. + * + * @category getters + * @since 4.0.0 + */ +export const outgoingEdges: { + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Array +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get outgoing edges of undirected graph" }) + } + const impl = internal.toImpl(graph) + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) + } + return Array.from(impl.adjacency.get(nodeIndex)!) +}) + +/** + * Returns the indices of incoming edges for a node in a directed graph. + * + * **Details** + * + * Parallel edges and self-loops are returned separately in reverse-adjacency + * order. Throws a `GraphError` for an undirected graph or missing node. + * + * @category getters + * @since 4.0.0 + */ +export const incomingEdges: { + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Array +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get incoming edges of undirected graph" }) + } + const impl = internal.toImpl(graph) + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) + } + return Array.from(impl.reverseAdjacency.get(nodeIndex)!) +}) + +/** + * Returns all edge indices connecting the supplied nodes. + * + * **Details** + * + * Directed graphs only include edges from `source` to `target`; undirected + * graphs include either stored orientation. Parallel edges are retained. + * Throws a `GraphError` when either node does not exist. + * + * @category getters + * @since 4.0.0 + */ +export const edgesBetween: { + (source: NodeIndex, target: NodeIndex): ( + graph: Graph | MutableGraph + ) => Array + ( + graph: Graph | MutableGraph, + source: NodeIndex, + target: NodeIndex + ): Array +} = dual(3, ( + graph: Graph | MutableGraph, + source: NodeIndex, + target: NodeIndex +): Array => { + const impl = internal.toImpl(graph) + if (!impl.nodes.has(source)) { + throw missingNode(source) + } + if (!impl.nodes.has(target)) { + throw missingNode(target) + } + const result: Array = [] + let previous = -1 + for (const edgeIndex of impl.adjacency.get(source)!) { + if (edgeIndex === previous) { + continue + } + previous = edgeIndex + const edge = impl.edges.get(edgeIndex)! + const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target + if (neighbor === target) { + result.push(edgeIndex) + } + } + return result +}) + +/** + * Returns the degree of a node in an undirected graph. + * + * **Details** + * + * Parallel edges count separately and a self-loop contributes two. Throws a + * `GraphError` for a directed graph or missing node. + * + * @category getters + * @since 4.0.0 + */ +export const degree: { + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => number + (graph: Graph | MutableGraph, nodeIndex: NodeIndex): number +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): number => { + if (graph.type === "directed") { + throw new GraphError({ message: "Cannot get degree of directed graph" }) + } + const impl = internal.toImpl(graph) + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) + } + return impl.adjacency.get(nodeIndex)!.length +}) + +/** + * Returns the out-degree of a node in a directed graph. + * + * **Details** + * + * Parallel edges count separately and a self-loop contributes one. Throws a + * `GraphError` for an undirected graph or missing node. + * + * @category getters + * @since 4.0.0 + */ +export const outDegree: { + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => number + (graph: Graph | MutableGraph, nodeIndex: NodeIndex): number +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): number => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get outgoing edges of undirected graph" }) + } + const impl = internal.toImpl(graph) + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) + } + return impl.adjacency.get(nodeIndex)!.length +}) + +/** + * Returns the in-degree of a node in a directed graph. + * + * **Details** + * + * Parallel edges count separately and a self-loop contributes one. Throws a + * `GraphError` for an undirected graph or missing node. + * + * @category getters + * @since 4.0.0 + */ +export const inDegree: { + (nodeIndex: NodeIndex): ( + graph: Graph | MutableGraph + ) => number + (graph: Graph | MutableGraph, nodeIndex: NodeIndex): number +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): number => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get incoming edges of undirected graph" }) + } + const impl = internal.toImpl(graph) + if (!impl.nodes.has(nodeIndex)) { + throw missingNode(nodeIndex) + } + return impl.reverseAdjacency.get(nodeIndex)!.length +}) + +const getDirectedNeighbors = ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex, + direction: Direction +): Array => { + const impl = internal.toImpl(graph) + + if (!graph.mutable) { + const cache = csr.peek(graph) + if (cache !== undefined) { + const node = csr.getNodeIndex(cache, nodeIndex) + if (node === undefined) { + return [] + } + + const adjacency = direction === "incoming" + ? csr.getIncoming(cache) + : csr.getOutgoing(cache) + + const start = adjacency.rowOffsets[node] + const result = new Array(adjacency.rowOffsets[node + 1] - start) + for (let i = 0; i < result.length; i++) { + result[i] = cache.nodeIds[adjacency.columnIndices[start + i]] + } + + return result + } + } + + const adjacencyMap = direction === "incoming" + ? impl.reverseAdjacency + : impl.adjacency + + const adjacencyList = adjacencyMap.get(nodeIndex) + if (adjacencyList === undefined) { + return [] + } + + const result: Array = [] + for (const edgeIndex of adjacencyList) { + const edge = impl.edges.get(edgeIndex) + if (edge !== undefined) { + result.push(direction === "incoming" ? edge.source : edge.target) + } + } + + return result +} + +const getUniqueDirectedNeighbors = ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex, + direction: Direction +): Array => Array.from(new Set(getDirectedNeighbors(graph, nodeIndex, direction))) + +/** + * Returns the neighboring node indices for a node. + * + * **Details** + * + * For directed graphs, neighbors are the targets of outgoing edges. For + * undirected graphs, neighbors are the other endpoints of incident edges. + * Each neighbor appears once in first edge occurrence order, including the + * queried node when it has a self-loop. + * + * **Gotchas** + * + * Returns an empty array when the node does not exist. For directed graphs, + * use `predecessors` when incoming neighbors are required. + * + * **Example** (Getting outgoing neighbors) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * const nodeC = Graph.addNode(mutable, "Node C") + * Graph.addEdge(mutable, nodeA, nodeB, 1) + * Graph.addEdge(mutable, nodeA, nodeC, 2) + * }) + * + * Graph.neighbors(graph, 0) // => [1, 2] + * Graph.neighbors(graph, 1) // => [] + * ``` + * + * @category getters + * @since 3.18.0 + */ +export const neighbors: { + ( + nodeIndex: NodeIndex + ): (graph: Graph | MutableGraph) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Array +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + // For undirected graphs, use the specialized helper that returns the other endpoint + if (graph.type === "undirected") { + return getUndirectedNeighbors(graph as any, nodeIndex) + } + + return getUniqueDirectedNeighbors(graph as any, nodeIndex, "outgoing") +}) + +/** + * Returns the outgoing neighbor node indices for a node in a directed graph. + * + * **When to use** + * + * Use when you need the nodes reached by following outgoing edges from a node in + * a directed graph. + * + * Each node appears once in first outgoing edge occurrence order. A self-loop + * contributes the queried node once. + * + * **Gotchas** + * + * Throws a `GraphError` when used with an undirected graph. A missing node + * returns an empty array. + * + * @see {@link predecessors} for incoming neighbors in a directed graph + * @see {@link neighbors} for generic neighbor lookup across graph kinds + * + * @category getters + * @since 4.0.0 + */ +export const successors: { + ( + nodeIndex: NodeIndex + ): (graph: Graph | MutableGraph) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Array +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get successors of undirected graph" }) + } + return getUniqueDirectedNeighbors(graph as any, nodeIndex, "outgoing") +}) + +/** + * Returns the incoming neighbor node indices for a node in a directed graph. + * + * **When to use** + * + * Use when you need the nodes that reach a node by following incoming edges in a + * directed graph. + * + * Each node appears once in first incoming edge occurrence order. A self-loop + * contributes the queried node once. + * + * **Gotchas** + * + * Throws a `GraphError` when used with an undirected graph. A missing node + * returns an empty array. + * + * @see {@link successors} for outgoing neighbors in a directed graph + * @see {@link neighbors} for generic neighbor lookup across graph kinds + * + * @category getters + * @since 4.0.0 + */ +export const predecessors: { + ( + nodeIndex: NodeIndex + ): (graph: Graph | MutableGraph) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex + ): Array +} = dual(2, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get predecessors of undirected graph" }) + } + return getUniqueDirectedNeighbors(graph as any, nodeIndex, "incoming") +}) + +/** + * Gets directed neighbors of a node in a specific direction. + * + * **When to use** + * + * Use when maintaining existing code that already passes an explicit traversal + * direction. New code should prefer `successors` or `predecessors`. + * Results contain each node once in first edge occurrence order, and a self-loop + * contributes the queried node once. + * + * **Gotchas** + * + * Throws a `GraphError` when used with an undirected graph. + * + * **Example** (Traversing directed neighbors) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, a, b, "A->B") + * }) + * + * const nodeA = 0 + * const nodeB = 1 + * + * // Get outgoing neighbors (nodes that nodeA points to) + * const outgoing = Graph.neighborsDirected(graph, nodeA, "outgoing") + * + * // Get incoming neighbors (nodes that point to nodeB) + * const incoming = Graph.neighborsDirected(graph, nodeB, "incoming") + * Array.of(outgoing, incoming) // => [[1], [0]] + * ``` + * + * @deprecated Use {@link successors} for outgoing neighbors or {@link predecessors} for incoming neighbors. + * @see {@link successors} for outgoing neighbors in a directed graph + * @see {@link predecessors} for incoming neighbors in a directed graph + * @category getters + * @since 3.18.0 + */ +export const neighborsDirected: { + ( + nodeIndex: NodeIndex, + direction: Direction + ): (graph: Graph | MutableGraph) => Array + ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex, + direction: Direction + ): Array +} = dual(3, ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex, + direction: Direction +): Array => { + if (graph.type === "undirected") { + throw new GraphError({ message: "Cannot get directed neighbors of undirected graph" }) + } + return getUniqueDirectedNeighbors(graph as any, nodeIndex, direction) +}) + +// ============================================================================= +// GraphViz Export +// ============================================================================= + +/** + * Configuration options for GraphViz DOT format generation from graphs. + * + * **When to use** + * + * Use when customizing labels or the graph name produced by `toGraphViz`. + * + * **Details** + * + * These options customize node labels, edge labels, and graph naming in DOT + * format compatible with GraphViz tools. + * + * **Example** (Configuring GraphViz labels) + * + * ```ts import.meta.vitest + * import type { Graph } from "effect" + * + * // Basic options with custom labels + * const basicOptions: Graph.GraphVizOptions = { + * nodeLabel: (data) => `Node: ${data}`, + * edgeLabel: (data) => `Weight: ${data}` + * } + * + * // Complete options with graph naming + * const namedOptions: Graph.GraphVizOptions = { + * nodeLabel: (data) => data.toUpperCase(), + * edgeLabel: (data) => data, + * graphName: "MyDependencyGraph" + * } + * Array.of(basicOptions.nodeLabel?.("A"), namedOptions.graphName) // => ["Node: A", "MyDependencyGraph"] + * ``` + * + * @see {@link toGraphViz} for generating DOT output + * @category configuration + * @since 3.18.0 + */ +export interface GraphVizOptions { + /** + * Function to generate custom labels for nodes. + * Defaults to String(data) if not provided. + */ + readonly nodeLabel?: (data: N) => string + + /** + * Function to generate custom labels for edges. + * Defaults to String(data) if not provided. + */ + readonly edgeLabel?: (data: E) => string + + /** + * Name for the DOT graph. + * Defaults to "G" if not provided. + */ + readonly graphName?: string +} + +const escapeGraphVizString = (value: string): string => + value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\r\n|\r|\n/g, "\\n") + +/** + * Exports a graph to GraphViz DOT format for visualization. + * + * **When to use** + * + * Use when sending graph structure to GraphViz-compatible visualization or + * documentation tools. + * + * **Example** (Exporting GraphViz DOT) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.mutate(Graph.directed(), (mutable) => { + * const nodeA = Graph.addNode(mutable, "Node A") + * const nodeB = Graph.addNode(mutable, "Node B") + * const nodeC = Graph.addNode(mutable, "Node C") + * Graph.addEdge(mutable, nodeA, nodeB, 1) + * Graph.addEdge(mutable, nodeB, nodeC, 2) + * Graph.addEdge(mutable, nodeC, nodeA, 3) + * }) + * + * Graph.toGraphViz(graph).split("\n") // => ['digraph "G" {', ' "0" [label="Node A"];', ' "1" [label="Node B"];', ' "2" [label="Node C"];', ' "0" -> "1" [label="1"];', ' "1" -> "2" [label="2"];', ' "2" -> "0" [label="3"];', "}"] + * ``` + * + * @see {@link toMermaid} for Mermaid diagram output + * @category converting + * @since 3.18.0 + */ +export const toGraphViz: { + ( + options?: GraphVizOptions + ): (graph: Graph | MutableGraph) => string + ( + graph: Graph | MutableGraph, + options?: GraphVizOptions + ): string +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + options?: GraphVizOptions +): string => { + const impl = internal.toImpl(graph) + const { + edgeLabel = (data: E) => String(data), + graphName = "G", + nodeLabel = (data: N) => String(data) + } = options ?? {} + + const isDirected = graph.type === "directed" + const graphType = isDirected ? "digraph" : "graph" + const edgeOperator = isDirected ? "->" : "--" + const graphId = `"${escapeGraphVizString(graphName)}"` + + return withMutationGuard(graph, () => { + const lines: Array = [] + lines.push(`${graphType} ${graphId} {`) + + // Add nodes + for (const [nodeIndex, nodeData] of impl.nodes) { + const label = escapeGraphVizString(nodeLabel(nodeData)) + lines.push(` "${nodeIndex}" [label="${label}"];`) + } + + // Add edges + for (const [, edgeData] of impl.edges) { + const label = escapeGraphVizString(edgeLabel(edgeData.data)) + lines.push(` "${edgeData.source}" ${edgeOperator} "${edgeData.target}" [label="${label}"];`) + } + + lines.push("}") + return lines.join("\n") + }) +}) + +// ============================================================================= +// Mermaid Export +// ============================================================================= + +/** + * Mermaid node shape types for diagram visualization. + * + * **Details** + * + * Each shape produces different visual representations in Mermaid diagrams: + * - `rectangle`: Standard rectangular nodes `A["label"]` + * - `rounded`: Rounded rectangular nodes `A("label")` + * - `circle`: Circular nodes `A(("label"))` + * - `diamond`: Diamond-shaped nodes `A{"label"}` + * - `hexagon`: Hexagonal nodes `A{{"label"}}` + * - `stadium`: Stadium-shaped nodes `A(["label"])` + * - `subroutine`: Subroutine-style nodes `A[["label"]]` + * - `cylindrical`: Cylindrical database-style nodes `A[("label")]` + * + * **Example** (Selecting Mermaid node shapes) + * + * ```ts import.meta.vitest + * import type { Graph } from "effect" + * + * // Shape selector function for different node types + * const shapeSelector = (nodeData: string): Graph.MermaidNodeShape => { + * if (nodeData.includes("start") || nodeData.includes("end")) return "circle" + * if (nodeData.includes("decision")) return "diamond" + * if (nodeData.includes("process")) return "rectangle" + * if (nodeData.includes("data")) return "cylindrical" + * return "rounded" + * } + * + * const options: Graph.MermaidOptions = { + * nodeShape: shapeSelector + * } + * options.nodeShape?.("decision") // => "diamond" + * ``` + * + * @category models + * @since 3.18.0 + */ +export type MermaidNodeShape = + | "rectangle" // A["label"] + | "rounded" // A("label") + | "circle" // A(("label")) + | "diamond" // A{"label"} + | "hexagon" // A{{"label"}} + | "stadium" // A(["label"]) + | "subroutine" // A[["label"]] + | "cylindrical" // A[("label")] + +/** + * Mermaid diagram direction types for controlling layout orientation. + * + * **Details** + * + * Determines the flow direction of nodes and edges in the diagram: + * - `TB`/`TD`: Top to Bottom (vertical layout, default) + * - `BT`: Bottom to Top (reverse vertical) + * - `LR`: Left to Right (horizontal layout) + * - `RL`: Right to Left (reverse horizontal) + * + * **Example** (Configuring Mermaid directions) + * + * ```ts import.meta.vitest + * import type { Graph } from "effect" + * + * // Horizontal workflow diagram + * const horizontalOptions: Graph.MermaidOptions = { + * direction: "LR" + * } + * + * // Vertical hierarchy (default) + * const verticalOptions: Graph.MermaidOptions = { + * direction: "TB" + * } + * + * // Bottom-up flow + * const bottomUpOptions: Graph.MermaidOptions = { + * direction: "BT" + * } + * Array.of(horizontalOptions.direction, verticalOptions.direction, bottomUpOptions.direction) // => ["LR", "TB", "BT"] + * ``` + * + * @category models + * @since 3.18.0 + */ +export type MermaidDirection = + | "TB" // Top to Bottom (default) + | "TD" // Top Down (same as TB) + | "BT" // Bottom to Top + | "RL" // Right to Left + | "LR" // Left to Right + +/** + * Mermaid diagram types for different visualization formats. + * + * **Details** + * + * Specifies the Mermaid diagram syntax to use: + * - `flowchart`: For directed graphs with arrows (`A --> B`) + * - `graph`: For undirected graphs with lines (`A --- B`) + * + * When not specified, automatically selects based on graph type: + * directed graphs use "flowchart", undirected graphs use "graph". + * + * **Example** (Selecting Mermaid diagram types) + * + * ```ts import.meta.vitest + * import type { Graph } from "effect" + * + * // Force flowchart format (even for undirected graphs) + * const flowchartOptions: Graph.MermaidOptions = { + * diagramType: "flowchart" + * } + * + * // Force graph format (shows undirected connections) + * const graphOptions: Graph.MermaidOptions = { + * diagramType: "graph" + * } + * + * // Auto-detection (recommended, default behavior) + * const autoOptions: Graph.MermaidOptions = {} + * Array.of(flowchartOptions.diagramType, graphOptions.diagramType, autoOptions.diagramType) // => ["flowchart", "graph", undefined] + * ``` + * + * @category models + * @since 3.18.0 + */ +export type MermaidDiagramType = + | "flowchart" // For directed graphs + | "graph" // For undirected graphs + +/** + * Configuration options for Mermaid diagram generation from graphs. + * + * **When to use** + * + * Use when customizing labels, layout, node shapes, or syntax emitted by + * `toMermaid`. + * + * **Details** + * + * These options customize node labels, edge labels, diagram type, layout + * direction and node shapes in Mermaid format. + * + * **Example** (Configuring Mermaid output) + * + * ```ts import.meta.vitest + * import type { Graph } from "effect" + * + * // Basic options with custom labels + * const basicOptions: Graph.MermaidOptions = { + * nodeLabel: (data) => `Node: ${data}`, + * edgeLabel: (data) => `Weight: ${data}` + * } + * + * // Advanced options with all features + * const advancedOptions: Graph.MermaidOptions = { + * nodeLabel: (data) => data.toUpperCase(), + * edgeLabel: (data) => data, + * diagramType: "flowchart", + * direction: "LR", + * nodeShape: (data) => data.includes("start") ? "circle" : "rectangle" + * } + * Array.of(basicOptions.nodeLabel?.("A"), advancedOptions.nodeShape?.("start")) // => ["Node: A", "circle"] + * ``` + * + * @see {@link toMermaid} for generating Mermaid output + * @category configuration + * @since 3.18.0 + */ +export interface MermaidOptions { + /** + * Function to generate custom labels for nodes. + * Defaults to String(data) if not provided. + */ + readonly nodeLabel?: (data: N) => string + + /** + * Function to generate custom labels for edges. + * Defaults to String(data) if not provided. + */ + readonly edgeLabel?: (data: E) => string + + /** + * Diagram type override. If not specified, automatically detects: + * - "flowchart" for directed graphs + * - "graph" for undirected graphs + */ + readonly diagramType?: MermaidDiagramType + + /** + * Direction for diagram layout. + * Defaults to "TD" (Top Down) if not provided. + */ + readonly direction?: MermaidDirection + + /** + * Function to determine node shape for each node. + * Defaults to "rectangle" for all nodes if not provided. + */ + readonly nodeShape?: (data: N) => MermaidNodeShape +} + +/** + * Escapes special characters in labels for Mermaid syntax compatibility. + */ +const escapeMermaidLabel = (label: string): string => { + // Escape special characters for Mermaid using HTML entity codes + // According to: https://mermaid.js.org/syntax/flowchart.html#special-characters-that-break-syntax + return label + .replace(/#/g, "#35;") + .replace(/"/g, "#quot;") + .replace(//g, "#gt;") + .replace(/&/g, "#amp;") + .replace(/\[/g, "#91;") + .replace(/\]/g, "#93;") + .replace(/\{/g, "#123;") + .replace(/\}/g, "#125;") + .replace(/\(/g, "#40;") + .replace(/\)/g, "#41;") + .replace(/\|/g, "#124;") + .replace(/\\/g, "#92;") + .replace(/\r\n|\r|\n/g, "
") +} + +/** + * Formats a Mermaid node with the specified shape and label. + */ +const formatMermaidNode = ( + nodeId: string, + label: string, + shape: MermaidNodeShape +): string => { + switch (shape) { + case "rectangle": + return `${nodeId}["${label}"]` + case "rounded": + return `${nodeId}("${label}")` + case "circle": + return `${nodeId}(("${label}"))` + case "diamond": + return `${nodeId}{"${label}"}` + case "hexagon": + return `${nodeId}{{"${label}"}}` + case "stadium": + return `${nodeId}(["${label}"])` + case "subroutine": + return `${nodeId}[["${label}"]]` + case "cylindrical": + return `${nodeId}[("${label}")]` + default: + return `${nodeId}["${label}"]` // Default rectangle + } +} + +/** + * Exports a graph to Mermaid diagram format for visualization. + * + * **When to use** + * + * Use when embedding graph diagrams in Markdown, documentation sites, or other + * Mermaid-compatible tools. + * + * **Details** + * + * Directed graphs default to `flowchart` with arrow edges, while undirected + * graphs default to `graph` with line edges. Labels and node shapes can be + * customized with `MermaidOptions`. + * + * **Example** (Exporting a Mermaid diagram) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const app = Graph.addNode(mutable, "App") + * const database = Graph.addNode(mutable, "Database") + * Graph.addEdge(mutable, app, database, "queries") + * }) + * + * Graph.toMermaid(graph).split("\n") // => ["flowchart TD", ' 0["App"]', ' 1["Database"]', ' 0 -->|"queries"| 1'] + * ``` + * + * @see {@link toGraphViz} for GraphViz DOT output + * @category converting + * @since 3.18.0 + */ +export const toMermaid: { + ( + options?: MermaidOptions + ): (graph: Graph | MutableGraph) => string + ( + graph: Graph | MutableGraph, + options?: MermaidOptions + ): string +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + options?: MermaidOptions +): string => { + const impl = internal.toImpl(graph) + // Extract and validate options with defaults + const { + diagramType, + direction = "TD", + edgeLabel = (data: E) => String(data), + nodeLabel = (data: N) => String(data), + nodeShape = () => "rectangle" as const + } = options ?? {} + + // Auto-detect diagram type if not specified + const finalDiagramType = diagramType ?? + (graph.type === "directed" ? "flowchart" : "graph") + + return withMutationGuard(graph, () => { + // Generate diagram header + const lines: Array = [] + lines.push(`${finalDiagramType} ${direction}`) + + // Add nodes + for (const [nodeIndex, nodeData] of impl.nodes) { + const nodeId = String(nodeIndex) + const label = escapeMermaidLabel(nodeLabel(nodeData)) + const shape = nodeShape(nodeData) + const formattedNode = formatMermaidNode(nodeId, label, shape) + lines.push(` ${formattedNode}`) + } + + // Add edges + const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---" + for (const [, edgeData] of impl.edges) { + const sourceId = String(edgeData.source) + const targetId = String(edgeData.target) + const label = escapeMermaidLabel(edgeLabel(edgeData.data)) + + if (label) { + lines.push(` ${sourceId} ${edgeOperator}|"${label}"| ${targetId}`) + } else { + lines.push(` ${sourceId} ${edgeOperator} ${targetId}`) + } + } + + return lines.join("\n") + }) +}) + +// ============================================================================= +// Edge Direction Types +// ============================================================================= + +/** + * Direction of directed edges relative to a node. + * + * **Details** + * + * `"outgoing"` selects edges whose source is the node, while `"incoming"` + * selects edges whose target is the node. + * + * @category models + * @since 3.18.0 + */ +export type Direction = "outgoing" | "incoming" + +/** + * Controls how traversal follows directed edges. + * + * **Details** + * + * `"outgoing"` follows edges from source to target, `"incoming"` follows them + * from target to source, and `"undirected"` allows traversal in either + * direction. + * + * **Example** (Traversing by direction) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "A-B") + * Graph.addEdge(mutable, a, c, "A-C") + * }) + * + * Array.from(Graph.indices(Graph.bfs(graph, { start: [0], direction: "outgoing" }))) // => [0, 1, 2] + * Array.from(Graph.indices(Graph.bfs(graph, { start: [1], direction: "incoming" }))) // => [1, 0] + * Array.from(Graph.indices(Graph.bfs(graph, { start: [1], direction: "undirected" }))) // => [1, 0, 2] + * ``` + * + * @category models + * @since 4.0.0 + */ +export type TraversalDirection = Direction | "undirected" + +// ============================================================================= +// Graph Structure Analysis Algorithms +// ============================================================================= + +/** + * A cycle witness containing a closed node path and its traversed edges. + * + * **Details** + * + * `path` repeats its first node at the end, so `edges.length` is always + * `path.length - 1`. + * + * @category models + * @since 4.0.0 + */ +export interface CycleResult { + readonly path: Array + readonly edges: Array +} + +/** + * Returns one cycle in a graph, if present. + * + * **When to use** + * + * Use when you need the nodes and edges of a concrete cycle for diagnostics or + * reporting. + * + * **Details** + * + * Directed cycles respect edge orientation. A self-loop is represented as a + * one-edge cycle, and two parallel undirected edges form a two-edge cycle. + * + * @see {@link isAcyclic} when only a boolean cycle check is needed + * @category algorithms + * @since 4.0.0 + */ +export const findCycle = ( + graph: Graph | MutableGraph +): Option.Option => { + const impl = internal.toImpl(graph) + const colors = new Map() + const parentNodes = new Map() + const parentEdges = new Map() + + const makeCycle = (ancestor: NodeIndex, current: NodeIndex, closingEdge: EdgeIndex): CycleResult => { + const path = [current] + const edges: Array = [] + let cursor = current + while (cursor !== ancestor) { + edges.push(parentEdges.get(cursor)!) + cursor = parentNodes.get(cursor)! + path.push(cursor) + } + path.reverse() + edges.reverse() + path.push(ancestor) + edges.push(closingEdge) + return { path, edges } + } + + for (const start of impl.nodes.keys()) { + if ((colors.get(start) ?? 0) !== 0) { + continue + } + colors.set(start, 1) + const stack: Array<{ readonly node: NodeIndex; position: number }> = [{ node: start, position: 0 }] + while (stack.length > 0) { + const frame = stack[stack.length - 1] + const adjacency = impl.adjacency.get(frame.node)! + if (frame.position >= adjacency.length) { + colors.set(frame.node, 2) + stack.pop() + continue + } + + const edgeIndex = adjacency[frame.position++] + if (graph.type === "undirected" && parentEdges.get(frame.node) === edgeIndex) { + continue + } + const edge = impl.edges.get(edgeIndex)! + const neighbor = getTraversableNeighbor(graph, frame.node, edge) + const color = colors.get(neighbor) ?? 0 + if (color === 1) { + return Option.some(makeCycle(neighbor, frame.node, edgeIndex)) + } + if (color === 0) { + colors.set(neighbor, 1) + parentNodes.set(neighbor, frame.node) + parentEdges.set(neighbor, edgeIndex) + stack.push({ node: neighbor, position: 0 }) + } + } + } + return Option.none() +} + +/** + * Checks whether the graph is acyclic (contains no cycles). + * + * **When to use** + * + * Use when validating that a graph contains no cycle and a cycle witness is + * not needed. + * + * **Details** + * + * Directed cycles respect edge orientation. Self-loops are cycles, and two + * parallel edges form a cycle in an undirected graph. + * + * **Example** (Checking cycles) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * // Acyclic directed graph (DAG) + * const dag = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "A->B") + * Graph.addEdge(mutable, b, c, "B->C") + * }) + * Graph.isAcyclic(dag) // => true + * + * // Cyclic directed graph + * const cyclic = Graph.directed((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * Graph.addEdge(mutable, a, b, "A->B") + * Graph.addEdge(mutable, b, a, "B->A") // Creates cycle + * }) + * Graph.isAcyclic(cyclic) // => false + * ``` + * + * @see {@link findCycle} for retrieving one cycle witness + * @see {@link topo} for ordering a directed acyclic graph + * @category algorithms + * @since 3.18.0 + */ +export const isAcyclic = ( + graph: Graph | MutableGraph +): boolean => { + const impl = internal.toImpl(graph) + // Use existing cycle flag if available + if (Option.isSome(impl.acyclic)) { + return impl.acyclic.value + } + + const cache = csr.get(graph) + const outgoing = csr.getOutgoingWithEdges(cache) + if (graph.type === "undirected") { + // Each undirected edge occurs in both endpoint rows; ignore only the edge used to enter the node. + const visited = new Uint8Array(cache.nodeIds.length) + const stack: Array = [] + const parentEdges: Array = [] + + for (let start = 0; start < cache.nodeIds.length; start++) { + if (visited[start] !== 0) { + continue + } + visited[start] = 1 + stack.push(start) + parentEdges.push(-1) + + while (stack.length > 0) { + const node = stack.pop()! + const parentEdge = parentEdges.pop()! + for (let i = outgoing.rowOffsets[node]; i < outgoing.rowOffsets[node + 1]; i++) { + const edge = outgoing.edgeIndices[i] + if (edge === parentEdge) { + continue + } + const neighbor = outgoing.columnIndices[i] + if (visited[neighbor] !== 0) { + impl.acyclic = Option.some(false) + return false + } + visited[neighbor] = 1 + stack.push(neighbor) + parentEdges.push(edge) + } + } + } + } else { + // Colors encode unseen, active, and finished nodes; row positions make the recursive DFS stack explicit. + const colors = new Uint8Array(cache.nodeIds.length) + const stack: Array = [] + const positions: Array = [] + + for (let start = 0; start < cache.nodeIds.length; start++) { + if (colors[start] !== 0) { + continue + } + colors[start] = 1 + stack.push(start) + positions.push(outgoing.rowOffsets[start]) + + while (stack.length > 0) { + const frame = stack.length - 1 + const node = stack[frame] + const position = positions[frame] + if (position < outgoing.rowOffsets[node + 1]) { + positions[frame] = position + 1 + const neighbor = outgoing.columnIndices[position] + if (colors[neighbor] === 1) { + impl.acyclic = Option.some(false) + return false + } + if (colors[neighbor] === 0) { + colors[neighbor] = 1 + stack.push(neighbor) + positions.push(outgoing.rowOffsets[neighbor]) + } + } else { + colors[node] = 2 + stack.pop() + positions.pop() + } + } + } + } + + impl.acyclic = Option.some(true) + return true +} + +/** + * Checks whether an undirected graph is bipartite. + * + * **When to use** + * + * Use when validating that nodes can be divided into two groups with every + * edge crossing between the groups. + * + * **Details** + * + * A bipartite graph is one whose vertices can be divided into two disjoint sets + * such that no two vertices within the same set are adjacent. + * + * **Example** (Checking bipartite graphs) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * // Bipartite graph (alternating coloring possible) + * const bipartite = Graph.undirected((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * const d = Graph.addNode(mutable, "D") + * Graph.addEdge(mutable, a, b, "edge") // Set 1: {A, C}, Set 2: {B, D} + * Graph.addEdge(mutable, b, c, "edge") + * Graph.addEdge(mutable, c, d, "edge") + * }) + * Graph.isBipartite(bipartite) // => true + * + * // Non-bipartite graph (odd cycle) + * const triangle = Graph.undirected((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * Graph.addEdge(mutable, a, b, "edge") + * Graph.addEdge(mutable, b, c, "edge") + * Graph.addEdge(mutable, c, a, "edge") // Triangle (3-cycle) + * }) + * Graph.isBipartite(triangle) // => false + * ``` + * + * @see {@link maximumBipartiteMatching} for matching nodes after validation + * @category algorithms + * @since 3.18.0 + */ +export const isBipartite = ( + graph: Graph | MutableGraph +): boolean => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot determine bipartite status of directed graph" }) + } + const cache = csr.get(graph) + const outgoing = csr.getOutgoing(cache) + // -1 is uncolored; compact indices let coloring and the queue stay in typed arrays. + const colors = new Int8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + colors.fill(-1) + let head = 0 + let tail = 0 + + for (let start = 0; start < cache.nodeIds.length; start++) { + if (colors[start] !== -1) { + continue + } + colors[start] = 0 + queue[tail++] = start + + while (head < tail) { + const current = queue[head++] + const neighborColor = colors[current] === 0 ? 1 : 0 + for (let i = outgoing.rowOffsets[current]; i < outgoing.rowOffsets[current + 1]; i++) { + const neighbor = outgoing.columnIndices[i] + if (colors[neighbor] === -1) { + colors[neighbor] = neighborColor + queue[tail++] = neighbor + } else if (colors[neighbor] === colors[current]) { + return false + } + } + } + } + + return true +} + +/** + * A pair of matched nodes and the edge that realizes the match. + * + * **Details** + * + * `left` and `right` refer to the bipartition derived by + * `maximumBipartiteMatching`, not to the stored edge orientation. + * + * @category models + * @since 4.0.0 + */ +export interface BipartiteMatch { + readonly left: NodeIndex + readonly right: NodeIndex + readonly edge: EdgeIndex +} + +/** @internal */ +const bipartiteColors = ( + graph: Graph | MutableGraph +): { readonly cache: csr.Csr; readonly colors: Int8Array } => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot find bipartite matching of directed graph" }) + } + const cache = csr.get(graph) + const outgoing = csr.getOutgoing(cache) + const colors = new Int8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + colors.fill(-1) + + for (let start = 0; start < cache.nodeIds.length; start++) { + if (colors[start] !== -1) { + continue + } + let head = 0 + let tail = 1 + colors[start] = 0 + queue[0] = start + while (head < tail) { + const node = queue[head++] + const color = colors[node] === 0 ? 1 : 0 + for (let i = outgoing.rowOffsets[node]; i < outgoing.rowOffsets[node + 1]; i++) { + const neighbor = outgoing.columnIndices[i] + if (colors[neighbor] === -1) { + colors[neighbor] = color + queue[tail++] = neighbor + } else if (colors[neighbor] === colors[node]) { + throw new GraphError({ message: "Cannot find bipartite matching of non-bipartite graph" }) + } + } + } + } + return { cache, colors } +} + +/** + * Returns a maximum-cardinality matching of an undirected bipartite graph. + * + * **When to use** + * + * Use when assigning as many disjoint pairs as possible between the two sides + * of a bipartite graph, such as workers to jobs or users to resources. + * + * **Details** + * + * The bipartition is derived internally. Self-loops and odd cycles throw a + * `GraphError`. Isolated nodes are allowed. Parallel edges do not change the + * matching cardinality, and the first edge in graph order between each matched + * pair is reported. Results follow left-partition graph order. Hopcroft-Karp + * runs in `O(E * sqrt(V))` time. + * + * **Gotchas** + * + * The graph must be undirected and bipartite. The derived left and right sides + * are not based on stored edge orientation. + * + * **Example** (Matching a bipartite graph) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.undirected((mutable) => { + * for (const node of ["A", "B", "X", "Y"]) Graph.addNode(mutable, node) + * Graph.addEdge(mutable, 0, 2, "A-X") + * Graph.addEdge(mutable, 0, 3, "A-Y") + * Graph.addEdge(mutable, 1, 2, "B-X") + * }) + * + * Graph.maximumBipartiteMatching(graph) // => [{ left: 0, right: 3, edge: 1 }, { left: 1, right: 2, edge: 2 }] + * ``` + * + * @see {@link isBipartite} for validating the graph without computing a matching + * @category algorithms + * @since 4.0.0 + */ +export const maximumBipartiteMatching = ( + graph: Graph | MutableGraph +): Array => { + const { cache, colors } = bipartiteColors(graph) + const endpoints = csr.getEdgeEndpoints(cache) + const edgeIds = csr.getEdgeIds(cache) + const adjacency: Array> = Array.from({ + length: cache.nodeIds.length + }, () => []) + const seen = Array.from({ length: cache.nodeIds.length }, () => new Set()) + + for (let edge = 0; edge < edgeIds.length; edge++) { + const source = endpoints.sources[edge] + const target = endpoints.targets[edge] + const left = colors[source] === 0 ? source : target + const right = colors[source] === 0 ? target : source + if (!seen[left].has(right)) { + seen[left].add(right) + adjacency[left].push({ right, edge }) + } + } + + const unmatched = -1 + const infinity = 0x7fffffff + const matchLeft = new Int32Array(cache.nodeIds.length) + const matchRight = new Int32Array(cache.nodeIds.length) + const matchEdge = new Int32Array(cache.nodeIds.length) + const distance = new Int32Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + matchLeft.fill(unmatched) + matchRight.fill(unmatched) + matchEdge.fill(unmatched) + let shortestDistance = infinity + + const hasLayer = (): boolean => { + let head = 0 + let tail = 0 + shortestDistance = infinity + for (let left = 0; left < colors.length; left++) { + if (colors[left] !== 0) { + continue + } + if (matchLeft[left] === unmatched) { + distance[left] = 0 + queue[tail++] = left + } else { + distance[left] = infinity + } + } + while (head < tail) { + const left = queue[head++] + if (distance[left] >= shortestDistance) { + continue + } + for (const arc of adjacency[left]) { + const next = matchRight[arc.right] + if (next === unmatched) { + shortestDistance = distance[left] + 1 + } else if (distance[next] === infinity) { + distance[next] = distance[left] + 1 + queue[tail++] = next + } + } + } + return shortestDistance !== infinity + } + + const augment = (start: number): boolean => { + const stack: Array<{ + readonly left: number + position: number + readonly viaRight: number + readonly viaEdge: number + }> = [{ left: start, position: 0, viaRight: unmatched, viaEdge: unmatched }] + while (stack.length > 0) { + const frame = stack[stack.length - 1] + const arcs = adjacency[frame.left] + if (frame.position >= arcs.length) { + distance[frame.left] = infinity + stack.pop() + continue + } + const arc = arcs[frame.position++] + const next = matchRight[arc.right] + if (next === unmatched && distance[frame.left] + 1 === shortestDistance) { + matchLeft[frame.left] = arc.right + matchRight[arc.right] = frame.left + matchEdge[frame.left] = arc.edge + for (let i = stack.length - 1; i > 0; i--) { + const child = stack[i] + const parent = stack[i - 1] + matchLeft[parent.left] = child.viaRight + matchRight[child.viaRight] = parent.left + matchEdge[parent.left] = child.viaEdge + } + return true + } + if (next === unmatched) { + continue + } + if (distance[next] === distance[frame.left] + 1) { + stack.push({ left: next, position: 0, viaRight: arc.right, viaEdge: arc.edge }) + } + } + return false + } + + while (hasLayer()) { + for (let left = 0; left < colors.length; left++) { + if (colors[left] === 0 && matchLeft[left] === unmatched) { + augment(left) + } + } + } + + const matches: Array = [] + for (let left = 0; left < colors.length; left++) { + if (matchLeft[left] !== unmatched) { + matches.push({ + left: cache.nodeIds[left], + right: cache.nodeIds[matchLeft[left]], + edge: edgeIds[matchEdge[left]] + }) + } + } + return matches +} + +/** + * Get neighbors for undirected graphs by checking both adjacency and reverse adjacency. + * For undirected graphs, we need to find the other endpoint of each edge incident to the node. + */ +const getUndirectedNeighbors = ( + graph: Graph | MutableGraph, + nodeIndex: NodeIndex +): Array => { + const impl = internal.toImpl(graph) + const neighbors = new Set() + + // Check edges where this node is the source + const adjacencyList = impl.adjacency.get(nodeIndex) + if (adjacencyList !== undefined) { + for (const edgeIndex of adjacencyList) { + const edge = impl.edges.get(edgeIndex) + if (edge !== undefined) { + // For undirected graphs, the neighbor is the other endpoint + const otherNode = edge.source === nodeIndex ? edge.target : edge.source + neighbors.add(otherNode) + } + } + } + + return Array.from(neighbors) +} + +const getTraversableNeighbor = ( + graph: Graph | MutableGraph, + current: NodeIndex, + edge: Edge +): NodeIndex => graph.type === "undirected" && edge.target === current ? edge.source : edge.target + +/** + * Configuration for unweighted reachability queries. + * + * **When to use** + * + * Use when controlling whether reachability follows outgoing edges, incoming + * edges, or either direction. + * + * **Details** + * + * `direction` defaults to `"outgoing"` and is ignored for undirected graphs. + * + * @category configuration + * @since 4.0.0 + */ +export interface ReachabilityConfig { + readonly direction?: TraversalDirection +} + +const getUnweightedDistances = ( + graph: Graph | MutableGraph, + source: NodeIndex, + direction: TraversalDirection, + target?: NodeIndex +): Map => { + const impl = internal.toImpl(graph) + if (!impl.nodes.has(source)) { + throw missingNode(source) + } + if (target !== undefined && !impl.nodes.has(target)) { + throw missingNode(target) + } + + const cache = csr.get(graph) + const sourceNode = csr.getNodeIndex(cache, source)! + const targetNode = target === undefined ? undefined : csr.getNodeIndex(cache, target)! + const adjacencies = csr.getAdjacencies(cache, graph.type === "undirected" ? "outgoing" : direction) + const compactDistances = new Int32Array(cache.nodeIds.length) + compactDistances.fill(-1) + compactDistances[sourceNode] = 0 + const queue = new Uint32Array(cache.nodeIds.length) + let head = 0 + let tail = 0 + queue[tail++] = sourceNode + + while (head < tail) { + const current = queue[head++] + if (current === targetNode) { + break + } + const visit = (adjacency: csr.Adjacency) => { + for (let i = adjacency.rowOffsets[current]; i < adjacency.rowOffsets[current + 1]; i++) { + const neighbor = adjacency.columnIndices[i] + if (compactDistances[neighbor] === -1) { + compactDistances[neighbor] = compactDistances[current] + 1 + queue[tail++] = neighbor + } + } + } + visit(adjacencies.primary) + if (adjacencies.secondary !== undefined) { + visit(adjacencies.secondary) + } + } + + const result = new Map() + for (let i = 0; i < cache.nodeIds.length; i++) { + if (compactDistances[i] !== -1) { + result.set(cache.nodeIds[i], compactDistances[i]) + } + } + return result +} + +/** + * Returns minimum unweighted distances from a source to every reachable node. + * + * **When to use** + * + * Use when every edge represents one step and you need hop counts from one + * source. + * + * **Details** + * + * Directed traversal is outgoing by default and can be changed with + * `direction`. + * + * **Gotchas** + * + * Throws a `GraphError` when the source does not exist. + * + * @see {@link hasPath} when only a reachability boolean is needed + * @see {@link bfs} for lazy traversal in increasing hop distance + * @see {@link dijkstra} for weighted shortest paths + * + * @category algorithms + * @since 4.0.0 + */ +export const unweightedDistances: { + (source: NodeIndex, options?: ReachabilityConfig): ( + graph: Graph | MutableGraph + ) => Map + ( + graph: Graph | MutableGraph, + source: NodeIndex, + options?: ReachabilityConfig + ): Map +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + source: NodeIndex, + options?: ReachabilityConfig +): Map => getUnweightedDistances(graph, source, options?.direction ?? "outgoing")) + +/** + * Tests whether a target is reachable from a source. + * + * **When to use** + * + * Use when you only need a reachability boolean rather than distances or a + * reconstructed path. + * + * **Details** + * + * Directed traversal is outgoing by default and can be changed with + * `direction`. A node is reachable from itself. + * + * **Gotchas** + * + * Throws a `GraphError` when either endpoint does not exist. + * + * @see {@link unweightedDistances} for hop distances to all reachable nodes + * @see {@link dijkstra} for a minimum-cost path + * + * @category predicates + * @since 4.0.0 + */ +export const hasPath: { + (source: NodeIndex, target: NodeIndex, options?: ReachabilityConfig): ( + graph: Graph | MutableGraph + ) => boolean + ( + graph: Graph | MutableGraph, + source: NodeIndex, + target: NodeIndex, + options?: ReachabilityConfig + ): boolean +} = dual((args) => isGraph(args[0]), ( + graph: Graph | MutableGraph, + source: NodeIndex, + target: NodeIndex, + options?: ReachabilityConfig +): boolean => { + const impl = internal.toImpl(graph) + if (!impl.nodes.has(source)) { + throw missingNode(source) + } + if (!impl.nodes.has(target)) { + throw missingNode(target) + } + if (source === target) { + return true + } + + const cache = csr.get(graph) + const sourceNode = csr.getNodeIndex(cache, source)! + const targetNode = csr.getNodeIndex(cache, target)! + const adjacencies = csr.getAdjacencies( + cache, + graph.type === "undirected" ? "outgoing" : options?.direction ?? "outgoing" + ) + const visited = new Uint8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + let head = 0 + let tail = 1 + visited[sourceNode] = 1 + queue[0] = sourceNode + + while (head < tail) { + const current = queue[head++] + const primary = adjacencies.primary + for (let i = primary.rowOffsets[current]; i < primary.rowOffsets[current + 1]; i++) { + const neighbor = primary.columnIndices[i] + if (neighbor === targetNode) { + return true + } + if (visited[neighbor] === 0) { + visited[neighbor] = 1 + queue[tail++] = neighbor + } + } + const secondary = adjacencies.secondary + if (secondary !== undefined) { + for (let i = secondary.rowOffsets[current]; i < secondary.rowOffsets[current + 1]; i++) { + const neighbor = secondary.columnIndices[i] + if (neighbor === targetNode) { + return true + } + if (visited[neighbor] === 0) { + visited[neighbor] = 1 + queue[tail++] = neighbor + } + } + } + } + return false +}) + +/** + * Returns the connected components of an undirected graph. + * + * **When to use** + * + * Use when partitioning an undirected graph into groups connected by paths. + * + * **Details** + * + * Each component is represented as an array of node indices. Isolated nodes + * form singleton components. + * + * **Gotchas** + * + * Throws a `GraphError` when used with a directed graph. + * + * **Example** (Finding connected components) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.undirected((mutable) => { + * const a = Graph.addNode(mutable, "A") + * const b = Graph.addNode(mutable, "B") + * const c = Graph.addNode(mutable, "C") + * const d = Graph.addNode(mutable, "D") + * Graph.addEdge(mutable, a, b, "edge") // Component 1: A-B + * Graph.addEdge(mutable, c, d, "edge") // Component 2: C-D + * }) + * + * Graph.connectedComponents(graph) // => [[0, 1], [2, 3]] + * ``` + * + * @see {@link isConnected} when only a boolean connectivity check is needed + * @see {@link weaklyConnectedComponents} for directed graphs with orientation ignored + * @see {@link stronglyConnectedComponents} for mutual directed reachability + * @category algorithms + * @since 3.18.0 + */ +export const connectedComponents = ( + graph: Graph | MutableGraph +): Array> => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot find connected components of directed graph" }) + } + const cache = csr.get(graph) + const outgoing = csr.getOutgoing(cache) + const visited = new Uint8Array(cache.nodeIds.length) + const neighborMarks = new Uint32Array(cache.nodeIds.length) + const components: Array> = [] + let neighborGeneration = 0 + + for (let start = 0; start < cache.nodeIds.length; start++) { + if (visited[start] !== 0) { + continue + } + const component: Array = [] + const stack: Array = [start] + + while (stack.length > 0) { + const current = stack.pop()! + if (visited[current] !== 0) { + continue + } + visited[current] = 1 + component.push(cache.nodeIds[current]) + + // Generation marks deduplicate parallel-edge neighbors without clearing a full-sized array per node. + neighborGeneration++ + for (let i = outgoing.rowOffsets[current]; i < outgoing.rowOffsets[current + 1]; i++) { + const neighbor = outgoing.columnIndices[i] + if (neighborMarks[neighbor] !== neighborGeneration) { + neighborMarks[neighbor] = neighborGeneration + if (visited[neighbor] === 0) { + stack.push(neighbor) + } + } + } + } + + components.push(component) + } + + return components +} + +/** @internal */ +interface LowLinkResult { + readonly bridges: Array + readonly articulationPoints: Array + readonly biconnectedComponents: Array> +} + +/** @internal */ +const analyzeLowLinks = ( + graph: Graph | MutableGraph +): LowLinkResult => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot analyze undirected connectivity of directed graph" }) + } + const cache = csr.get(graph) + const outgoing = csr.getOutgoingWithEdges(cache) + const edgeIds = csr.getEdgeIds(cache) + const endpoints = csr.getEdgeEndpoints(cache) + const discovered = new Int32Array(cache.nodeIds.length) + const low = new Int32Array(cache.nodeIds.length) + const parentNode = new Int32Array(cache.nodeIds.length) + const parentEdge = new Int32Array(cache.nodeIds.length) + const childCount = new Uint32Array(cache.nodeIds.length) + const bridgeMarks = new Uint8Array(edgeIds.length) + const articulationMarks = new Uint8Array(cache.nodeIds.length) + const edgeStack: Array = [] + const components: Array> = [] + const loopNodes = new Set() + discovered.fill(-1) + parentNode.fill(-1) + parentEdge.fill(-1) + let time = 0 + + const popComponent = (stopEdge: number): void => { + const nodes = new Set() + while (edgeStack.length > 0) { + const edge = edgeStack.pop()! + nodes.add(endpoints.sources[edge]) + nodes.add(endpoints.targets[edge]) + if (edge === stopEdge) { + break + } + } + if (nodes.size > 0) { + components.push(Array.from(nodes).sort((a, b) => a - b)) + } + } + + for (let start = 0; start < cache.nodeIds.length; start++) { + if (discovered[start] !== -1) { + continue + } + discovered[start] = low[start] = time++ + const stack: Array<{ readonly node: number; position: number }> = [{ + node: start, + position: outgoing.rowOffsets[start] + }] + + while (stack.length > 0) { + const frame = stack[stack.length - 1] + const end = outgoing.rowOffsets[frame.node + 1] + if (frame.position < end) { + const position = frame.position++ + const edge = outgoing.edgeIndices[position] + const neighbor = outgoing.columnIndices[position] + if (neighbor === frame.node) { + loopNodes.add(frame.node) + continue + } + if (edge === parentEdge[frame.node]) { + continue + } + if (discovered[neighbor] === -1) { + childCount[frame.node]++ + parentNode[neighbor] = frame.node + parentEdge[neighbor] = edge + discovered[neighbor] = low[neighbor] = time++ + edgeStack.push(edge) + stack.push({ node: neighbor, position: outgoing.rowOffsets[neighbor] }) + } else if (discovered[neighbor] < discovered[frame.node]) { + low[frame.node] = Math.min(low[frame.node], discovered[neighbor]) + edgeStack.push(edge) + } + continue + } + + stack.pop() + const parent = parentNode[frame.node] + if (parent === -1) { + if (childCount[frame.node] > 1) { + articulationMarks[frame.node] = 1 + } + } else { + low[parent] = Math.min(low[parent], low[frame.node]) + if (low[frame.node] > discovered[parent]) { + bridgeMarks[parentEdge[frame.node]] = 1 + } + if (low[frame.node] >= discovered[parent]) { + if (parentNode[parent] !== -1) { + articulationMarks[parent] = 1 + } + popComponent(parentEdge[frame.node]) + } + } + } + } + + for (const node of loopNodes) { + components.push([node]) + } + components.sort((left, right) => { + const length = Math.min(left.length, right.length) + for (let i = 0; i < length; i++) { + if (left[i] !== right[i]) { + return left[i] - right[i] + } + } + return left.length - right.length + }) + + const resultBridges: Array = [] + for (let edge = 0; edge < edgeIds.length; edge++) { + if (bridgeMarks[edge] !== 0) { + resultBridges.push(edgeIds[edge]) + } + } + const resultArticulationPoints: Array = [] + for (let node = 0; node < cache.nodeIds.length; node++) { + if (articulationMarks[node] !== 0) { + resultArticulationPoints.push(cache.nodeIds[node]) + } + } + return { + bridges: resultBridges, + articulationPoints: resultArticulationPoints, + biconnectedComponents: components.map((component) => component.map((node) => cache.nodeIds[node])) + } +} + +/** + * Returns the edges whose removal increases the number of connected components. + * + * **When to use** + * + * Use when locating single-edge failure points in an undirected network. + * + * **Details** + * + * Parent edges are tracked by edge index, so a parallel edge prevents either + * edge from being a bridge. Self-loops are never bridges. Results follow graph + * edge order. The iterative low-link traversal is stack-safe and runs in + * `O(V + E)` time. + * + * **Gotchas** + * + * Throws a `GraphError` when used with a directed graph. + * + * **Example** (Finding bridge edges) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.undirected((mutable) => { + * for (let i = 0; i < 3; i++) Graph.addNode(mutable, undefined) + * Graph.addEdge(mutable, 0, 1, undefined) + * Graph.addEdge(mutable, 1, 2, undefined) + * }) + * + * Graph.bridges(graph) // => [0, 1] + * ``` + * + * @see {@link articulationPoints} for single-node failure points + * @see {@link biconnectedComponents} for maximal regions without an articulation split + * @category algorithms + * @since 4.0.0 + */ +export const bridges = ( + graph: Graph | MutableGraph +): Array => analyzeLowLinks(graph).bridges + +/** + * Returns the nodes whose removal increases the number of connected components. + * + * **When to use** + * + * Use when locating single-node failure points in an undirected network. + * + * **Details** + * + * Disconnected components, parallel edges, and self-loops are handled by an + * iterative, stack-safe low-link traversal in `O(V + E)` time. Results follow + * graph node order. + * + * **Gotchas** + * + * Throws a `GraphError` when used with a directed graph. + * + * **Example** (Finding articulation points) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.undirected((mutable) => { + * for (let i = 0; i < 3; i++) Graph.addNode(mutable, undefined) + * Graph.addEdge(mutable, 0, 1, undefined) + * Graph.addEdge(mutable, 1, 2, undefined) + * }) + * + * Graph.articulationPoints(graph) // => [1] + * ``` + * + * @see {@link bridges} for single-edge failure points + * @see {@link biconnectedComponents} for the regions joined at articulation points + * @category algorithms + * @since 4.0.0 + */ +export const articulationPoints = ( + graph: Graph | MutableGraph +): Array => analyzeLowLinks(graph).articulationPoints /** - * Direction for graph traversal, indicating which edges to follow. + * Returns the maximal biconnected node components of an undirected graph. * - * **Example** (Traversing by direction) + * **When to use** + * + * Use when decomposing an undirected graph into maximal regions that remain + * connected after removing any one node from the region. + * + * **Details** + * + * Articulation points can occur in more than one component. Isolated vertices + * are excluded, while a vertex with a self-loop forms a singleton component. + * Nodes within components and the components themselves follow graph order. + * Parallel edges are treated independently. The iterative low-link traversal + * is stack-safe and runs in `O(V + E)` time. + * + * **Gotchas** * - * ```ts + * Throws a `GraphError` when used with a directed graph. + * + * **Example** (Finding biconnected components) + * + * ```ts import.meta.vitest * import { Graph } from "effect" * - * const graph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * Graph.addEdge(mutable, a, b, "A->B") + * const graph = Graph.undirected((mutable) => { + * for (let i = 0; i < 5; i++) Graph.addNode(mutable, undefined) + * Graph.addEdge(mutable, 0, 1, undefined) + * Graph.addEdge(mutable, 1, 2, undefined) + * Graph.addEdge(mutable, 2, 0, undefined) + * Graph.addEdge(mutable, 2, 3, undefined) + * Graph.addEdge(mutable, 3, 4, undefined) + * Graph.addEdge(mutable, 4, 2, undefined) * }) * - * // Follow outgoing edges (normal direction) - * const outgoingNodes = Array.from( - * Graph.indices(Graph.dfs(graph, { start: [0], direction: "outgoing" })) - * ) - * - * // Follow incoming edges (reverse direction) - * const incomingNodes = Array.from( - * Graph.indices(Graph.dfs(graph, { start: [1], direction: "incoming" })) - * ) + * Graph.biconnectedComponents(graph) // => [[0, 1, 2], [2, 3, 4]] * ``` * - * @category models - * @since 3.18.0 + * @see {@link articulationPoints} for the nodes shared between components + * @see {@link bridges} for edges whose removal disconnects the graph + * @category algorithms + * @since 4.0.0 */ -export type Direction = "outgoing" | "incoming" - -// ============================================================================= -// Graph Structure Analysis Algorithms -// ============================================================================= +export const biconnectedComponents = ( + graph: Graph | MutableGraph +): Array> => analyzeLowLinks(graph).biconnectedComponents /** - * Checks whether the graph is acyclic (contains no cycles). + * Configuration for source-to-target flow algorithms. * - * **Details** + * **When to use** * - * Uses depth-first search to detect back edges, which indicate cycles. - * For directed graphs, any back edge creates a cycle. For undirected graphs, - * a back edge that doesn't go to the immediate parent creates a cycle. + * Use when defining endpoints and edge capacities for `maximumFlow` or + * `minimumCut`. * - * **Example** (Checking cycles) + * **Details** * - * ```ts - * import { Graph } from "effect" + * `capacity` receives stored edge data and must return a finite, + * non-negative number. * - * // Acyclic directed graph (DAG) - * const dag = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") - * Graph.addEdge(mutable, a, b, "A->B") - * Graph.addEdge(mutable, b, c, "B->C") - * }) - * console.log(Graph.isAcyclic(dag)) // true + * **Gotchas** * - * // Cyclic directed graph - * const cyclic = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * Graph.addEdge(mutable, a, b, "A->B") - * Graph.addEdge(mutable, b, a, "B->A") // Creates cycle - * }) - * console.log(Graph.isAcyclic(cyclic)) // false - * ``` + * The source and target must be distinct existing nodes in a directed graph. * - * @category algorithms - * @since 3.18.0 + * @category configuration + * @since 4.0.0 */ -export const isAcyclic = ( - graph: Graph | MutableGraph -): boolean => { - // Use existing cycle flag if available - if (Option.isSome(graph.acyclic)) { - return graph.acyclic.value - } +export interface MaximumFlowConfig { + readonly source: NodeIndex + readonly target: NodeIndex + readonly capacity: (edge: E) => number +} - if (graph.type === "undirected") { - const visited = new Set() +/** + * Maximum flow value, per-edge flows, and a corresponding minimum cut. + * + * **Details** + * + * `flows` contains every original edge, including zero-flow edges. `cut` + * contains the crossing edge identifiers of the corresponding minimum cut. + * + * @category models + * @since 4.0.0 + */ +export interface MaximumFlowResult { + readonly value: number + readonly flows: Map + readonly cut: Array +} - for (const startNode of graph.nodes.keys()) { - if (visited.has(startNode)) { - continue - } +/** + * Minimum cut value, crossing edges, and residual-reachability partitions. + * + * **Details** + * + * `source` contains nodes residual-reachable from the configured source and + * `target` contains the remaining nodes. + * + * @category models + * @since 4.0.0 + */ +export interface MinimumCutResult { + readonly value: number + readonly edges: Array + readonly source: Array + readonly target: Array +} - visited.add(startNode) - const stack: Array<{ node: NodeIndex; parent: NodeIndex | null }> = [{ node: startNode, parent: null }] +/** @internal */ +interface FlowSolution extends MaximumFlowResult { + readonly sourceSide: Uint8Array + readonly nodeIds: Array +} - while (stack.length > 0) { - const { node, parent } = stack.pop()! - const nodeNeighbors = getUndirectedNeighbors(graph as any, node) - - for (const neighbor of nodeNeighbors) { - if (!visited.has(neighbor)) { - visited.add(neighbor) - stack.push({ node: neighbor, parent: node }) - } else if (neighbor !== parent) { - graph.acyclic = Option.some(false) - return false - } - } - } - } +/** @internal */ +interface ResidualArc { + readonly from: number + readonly to: number + readonly capacity: number + readonly edge: number + flow: number +} - graph.acyclic = Option.some(true) - return true +/** @internal */ +const solveMaximumFlow = ( + graph: Graph | MutableGraph, + config: MaximumFlowConfig +): FlowSolution => { + if ((graph as Graph | MutableGraph).type === "undirected") { + throw new GraphError({ message: "Cannot compute flow of undirected graph" }) + } + const cache = csr.get(graph) + const source = csr.getNodeIndex(cache, config.source) + if (source === undefined) { + throw missingNode(config.source) + } + const target = csr.getNodeIndex(cache, config.target) + if (target === undefined) { + throw missingNode(config.target) + } + if (source === target) { + throw new GraphError({ message: "Flow source and target must be different nodes" }) } - // Stack-safe DFS cycle detection using iterative approach - const visited = new Set() - const recursionStack = new Set() - - // Stack entry: [node, neighbors, neighborIndex, isFirstVisit] - type DfsStackEntry = [NodeIndex, Array, number, boolean] - - // Get all nodes to handle disconnected components - for (const startNode of graph.nodes.keys()) { - if (visited.has(startNode)) { - continue // Already processed this component + const edges = csr.getEdges(cache) as Array> + const edgeIds = csr.getEdgeIds(cache) + const endpoints = csr.getEdgeEndpoints(cache) + const capacities = new Float64Array(edges.length) + const arcs: Array = [] + const adjacency: Array> = Array.from({ length: cache.nodeIds.length }, () => []) + const forwardArc = new Int32Array(edges.length) + forwardArc.fill(-1) + + withMutationGuard(graph, () => { + for (let edge = 0; edge < edges.length; edge++) { + const capacity = config.capacity(edges[edge].data) + if (!Number.isFinite(capacity) || capacity < 0) { + throw new GraphError({ message: `Edge ${edgeIds[edge]} capacity must be a finite non-negative number` }) + } + capacities[edge] = capacity + const from = endpoints.sources[edge] + const to = endpoints.targets[edge] + if (from === to) { + continue + } + const index = arcs.length + forwardArc[edge] = index + adjacency[from].push(index) + arcs.push({ from, to, capacity, edge, flow: 0 }) + adjacency[to].push(index + 1) + arcs.push({ from: to, to: from, capacity: 0, edge: -1, flow: 0 }) } + }) - // Iterative DFS with explicit stack - const stack: Array = [[startNode, [], 0, true]] - - while (stack.length > 0) { - const [node, neighbors, neighborIndex, isFirstVisit] = stack[stack.length - 1] - - // First visit to this node - if (isFirstVisit) { - if (recursionStack.has(node)) { - // Back edge found - cycle detected - graph.acyclic = Option.some(false) - return false - } - - if (visited.has(node)) { - stack.pop() - continue + const parentArc = new Int32Array(cache.nodeIds.length) + const visited = new Uint8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + let value = 0 + while (true) { + parentArc.fill(-1) + visited.fill(0) + let head = 0 + let tail = 1 + queue[0] = source + visited[source] = 1 + while (head < tail && visited[target] === 0) { + const node = queue[head++] + for (const arcIndex of adjacency[node]) { + const arc = arcs[arcIndex] + if (arc.capacity - arc.flow > 0 && visited[arc.to] === 0) { + visited[arc.to] = 1 + parentArc[arc.to] = arcIndex + queue[tail++] = arc.to + if (arc.to === target) { + break + } } - - visited.add(node) - recursionStack.add(node) - - // Get neighbors for this node - const nodeNeighbors = getDirectedNeighbors( - graph as Graph | MutableGraph, - node, - "outgoing" - ) - stack[stack.length - 1] = [node, nodeNeighbors, 0, false] - continue } + } + if (visited[target] === 0) { + break + } - // Process next neighbor - if (neighborIndex < neighbors.length) { - const neighbor = neighbors[neighborIndex] - stack[stack.length - 1] = [node, neighbors, neighborIndex + 1, false] + let amount = Infinity + for (let node = target; node !== source;) { + const arc = arcs[parentArc[node]] + amount = Math.min(amount, arc.capacity - arc.flow) + node = arc.from + } + if (!Number.isFinite(value + amount)) { + throw new GraphError({ message: "Maximum flow exceeds the finite number range" }) + } + for (let node = target; node !== source;) { + const arcIndex = parentArc[node] + const arc = arcs[arcIndex] + arc.flow += amount + arcs[arcIndex ^ 1].flow -= amount + node = arc.from + } + value += amount + } - if (recursionStack.has(neighbor)) { - // Back edge found - cycle detected - graph.acyclic = Option.some(false) - return false - } + const flows = new Map() + for (let edge = 0; edge < edgeIds.length; edge++) { + const arcIndex = forwardArc[edge] + flows.set(edgeIds[edge], arcIndex === -1 ? 0 : arcs[arcIndex].flow) + } - if (!visited.has(neighbor)) { - stack.push([neighbor, [], 0, true]) - } - } else { - // Done with this node - backtrack - recursionStack.delete(node) - stack.pop() + visited.fill(0) + let head = 0 + let tail = 1 + queue[0] = source + visited[source] = 1 + while (head < tail) { + const node = queue[head++] + for (const arcIndex of adjacency[node]) { + const arc = arcs[arcIndex] + if (arc.capacity - arc.flow > 0 && visited[arc.to] === 0) { + visited[arc.to] = 1 + queue[tail++] = arc.to } } } - // Cache the result - graph.acyclic = Option.some(true) - return true + const cut: Array = [] + for (let edge = 0; edge < edgeIds.length; edge++) { + if ( + endpoints.sources[edge] !== endpoints.targets[edge] && + visited[endpoints.sources[edge]] !== 0 && + visited[endpoints.targets[edge]] === 0 + ) { + cut.push(edgeIds[edge]) + } + } + return { value, flows, cut, sourceSide: visited, nodeIds: cache.nodeIds } } /** - * Checks whether an undirected graph is bipartite. + * Returns a maximum flow and corresponding minimum cut for a directed graph. + * + * **When to use** + * + * Use when computing the greatest transferable capacity from one node to + * another and per-edge flow values are required. * * **Details** * - * A bipartite graph is one whose vertices can be divided into two disjoint sets - * such that no two vertices within the same set are adjacent. Uses BFS coloring - * to determine bipartiteness. + * Parallel edges retain independent capacities, self-loops carry no + * source-to-target flow, and the flow map includes every original edge in graph + * order, including zero-flow edges. Edmonds-Karp runs in `O(V * E^2)` time. * - * **Example** (Checking bipartite graphs) + * **Gotchas** * - * ```ts + * The graph must be directed. Capacities must be finite and non-negative. + * Missing or equal endpoints, invalid capacities, and a total flow outside the + * finite number range throw a `GraphError`. Self-loops always carry zero flow. + * + * **Example** (Computing maximum flow) + * + * ```ts import.meta.vitest * import { Graph } from "effect" * - * // Bipartite graph (alternating coloring possible) - * const bipartite = Graph.undirected((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") - * const d = Graph.addNode(mutable, "D") - * Graph.addEdge(mutable, a, b, "edge") // Set 1: {A, C}, Set 2: {B, D} - * Graph.addEdge(mutable, b, c, "edge") - * Graph.addEdge(mutable, c, d, "edge") + * const graph = Graph.directed((mutable) => { + * for (const node of ["source", "a", "target"]) Graph.addNode(mutable, node) + * Graph.addEdge(mutable, 0, 1, 3) + * Graph.addEdge(mutable, 1, 2, 2) + * Graph.addEdge(mutable, 0, 2, 1) * }) - * console.log(Graph.isBipartite(bipartite)) // true * - * // Non-bipartite graph (odd cycle) - * const triangle = Graph.undirected((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") - * Graph.addEdge(mutable, a, b, "edge") - * Graph.addEdge(mutable, b, c, "edge") - * Graph.addEdge(mutable, c, a, "edge") // Triangle (3-cycle) - * }) - * console.log(Graph.isBipartite(triangle)) // false + * Graph.maximumFlow(graph, { source: 0, target: 2, capacity: (edge) => edge }).value // => 3 * ``` * + * @see {@link minimumCut} for the residual-reachability partition * @category algorithms - * @since 3.18.0 + * @since 4.0.0 */ -export const isBipartite = ( - graph: Graph | MutableGraph -): boolean => { - const coloring = new Map() - const discovered = new Set() - let isBipartiteGraph = true - - // Get all nodes to handle disconnected components - for (const startNode of graph.nodes.keys()) { - if (!discovered.has(startNode)) { - // Start BFS coloring from this component - const queue: Array = [startNode] - coloring.set(startNode, 0) // Color start node with 0 - discovered.add(startNode) - - while (queue.length > 0 && isBipartiteGraph) { - const current = queue.shift()! - const currentColor = coloring.get(current)! - const neighborColor: 0 | 1 = currentColor === 0 ? 1 : 0 - - // Get all neighbors for undirected graph - const nodeNeighbors = getUndirectedNeighbors(graph, current) - for (const neighbor of nodeNeighbors) { - if (!discovered.has(neighbor)) { - // Color unvisited neighbor with opposite color - coloring.set(neighbor, neighborColor) - discovered.add(neighbor) - queue.push(neighbor) - } else { - // Check if neighbor has the same color (conflict) - if (coloring.get(neighbor) === currentColor) { - isBipartiteGraph = false - break - } - } - } - } - - // Early exit if not bipartite - if (!isBipartiteGraph) { - break - } - } - } - - return isBipartiteGraph -} +export const maximumFlow: { + (config: MaximumFlowConfig): ( + graph: Graph | MutableGraph + ) => MaximumFlowResult + ( + graph: Graph | MutableGraph, + config: MaximumFlowConfig + ): MaximumFlowResult +} = dual(2, ( + graph: Graph | MutableGraph, + config: MaximumFlowConfig +): MaximumFlowResult => { + const { cut, flows, value } = solveMaximumFlow(graph, config) + return { value, flows, cut } +}) /** - * Get neighbors for undirected graphs by checking both adjacency and reverse adjacency. - * For undirected graphs, we need to find the other endpoint of each edge incident to the node. + * Returns a minimum cut and its node partitions for a directed graph. + * + * **When to use** + * + * Use when identifying the minimum-capacity edges that separate a source from + * a target, together with the resulting node partitions. + * + * **Details** + * + * The source partition contains nodes reachable from the source in the final + * residual network; the target partition contains its complement. Both follow + * graph node order. Cut edges follow graph edge order, and their total capacity + * equals the returned maximum-flow value. Validation, parallel-edge, + * self-loop, and `O(V * E^2)` complexity behavior match `maximumFlow`. + * + * **Gotchas** + * + * The graph must be directed. Invalid capacities, missing endpoints, or equal + * source and target nodes throw a `GraphError`. + * + * **Example** (Partitioning a minimum cut) + * + * ```ts import.meta.vitest + * import { Graph } from "effect" + * + * const graph = Graph.directed((mutable) => { + * for (const node of ["source", "a", "target"]) Graph.addNode(mutable, node) + * Graph.addEdge(mutable, 0, 1, 2) + * Graph.addEdge(mutable, 1, 2, 1) + * }) + * + * Graph.minimumCut(graph, { source: 0, target: 2, capacity: (edge) => edge }).source // => [0, 1] + * ``` + * + * @see {@link maximumFlow} for per-edge flow values + * @category algorithms + * @since 4.0.0 */ -const getUndirectedNeighbors = ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex -): Array => { - const neighbors = new Set() - - // Check edges where this node is the source - const adjacencyList = graph.adjacency.get(nodeIndex) - if (adjacencyList !== undefined) { - for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) - if (edge !== undefined) { - // For undirected graphs, the neighbor is the other endpoint - const otherNode = edge.source === nodeIndex ? edge.target : edge.source - neighbors.add(otherNode) - } - } +export const minimumCut: { + (config: MaximumFlowConfig): ( + graph: Graph | MutableGraph + ) => MinimumCutResult + ( + graph: Graph | MutableGraph, + config: MaximumFlowConfig + ): MinimumCutResult +} = dual(2, ( + graph: Graph | MutableGraph, + config: MaximumFlowConfig +): MinimumCutResult => { + const solution = solveMaximumFlow(graph, config) + const source: Array = [] + const target: Array = [] + for (let node = 0; node < solution.nodeIds.length; node++) { + ;(solution.sourceSide[node] === 0 ? target : source).push(solution.nodeIds[node]) } - - return Array.from(neighbors) -} - -const getTraversalNeighbors = ( - graph: Graph | MutableGraph, - nodeIndex: NodeIndex, - direction: Direction -): Array => - graph.type === "undirected" - ? getUndirectedNeighbors(graph as any, nodeIndex) - : getDirectedNeighbors(graph as Graph | MutableGraph, nodeIndex, direction) - -const getTraversableNeighbor = ( - graph: Graph | MutableGraph, - current: NodeIndex, - edge: Edge -): NodeIndex => graph.type === "undirected" && edge.target === current ? edge.source : edge.target + return { value: solution.value, edges: solution.cut, source, target } +}) /** - * Finds connected components in an undirected graph. - * Each component is represented as an array of node indices. + * Finds weakly connected components in a directed graph. * - * **Example** (Finding connected components) + * **When to use** * - * ```ts - * import { Graph } from "effect" + * Use when grouping directed nodes by connectivity while ignoring edge + * orientation. * - * const graph = Graph.undirected((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * const c = Graph.addNode(mutable, "C") - * const d = Graph.addNode(mutable, "D") - * Graph.addEdge(mutable, a, b, "edge") // Component 1: A-B - * Graph.addEdge(mutable, c, d, "edge") // Component 2: C-D - * }) + * **Details** * - * const components = Graph.connectedComponents(graph) - * console.log(components) // [[0, 1], [2, 3]] - * ``` + * Edge direction is ignored while partitioning nodes. Isolated nodes form + * singleton components. + * + * **Gotchas** + * + * Throws a `GraphError` when used with an undirected graph. + * + * @see {@link isWeaklyConnected} when only a boolean check is needed + * @see {@link stronglyConnectedComponents} for mutual directed reachability * * @category algorithms - * @since 3.18.0 + * @since 4.0.0 */ -export const connectedComponents = ( - graph: Graph | MutableGraph +export const weaklyConnectedComponents = ( + graph: Graph | MutableGraph ): Array> => { - const visited = new Set() + if ((graph as Graph | MutableGraph).type === "undirected") { + throw new GraphError({ message: "Cannot find weakly connected components of undirected graph" }) + } + + const cache = csr.get(graph) + const { primary, secondary } = csr.getAdjacencies(cache, "undirected") + const nodeCount = cache.nodeIds.length + const visited = new Uint8Array(nodeCount) + const stack = new Uint32Array(primary.columnIndices.length + secondary!.columnIndices.length + 1) const components: Array> = [] - for (const startNode of graph.nodes.keys()) { - if (!visited.has(startNode)) { - // DFS to find all nodes in this component - const component: Array = [] - const stack: Array = [startNode] + for (let start = 0; start < nodeCount; start++) { + if (visited[start] !== 0) { + continue + } + const component: Array = [] + let stackSize = 1 + stack[0] = start + while (stackSize > 0) { + const current = stack[--stackSize] + if (visited[current] !== 0) { + continue + } + visited[current] = 1 + component.push(cache.nodeIds[current]) - while (stack.length > 0) { - const current = stack.pop()! - if (!visited.has(current)) { - visited.add(current) - component.push(current) - - // Add all unvisited neighbors to stack - const nodeNeighbors = getUndirectedNeighbors(graph, current) - for (const neighbor of nodeNeighbors) { - if (!visited.has(neighbor)) { - stack.push(neighbor) - } - } + for (let i = primary.rowOffsets[current]; i < primary.rowOffsets[current + 1]; i++) { + const neighbor = primary.columnIndices[i] + if (visited[neighbor] === 0) { + stack[stackSize++] = neighbor + } + } + for (let i = secondary!.rowOffsets[current]; i < secondary!.rowOffsets[current + 1]; i++) { + const neighbor = secondary!.columnIndices[i] + if (visited[neighbor] === 0) { + stack[stackSize++] = neighbor } } - - components.push(component) } + components.push(component) } - return components } /** - * Finds strongly connected components in a directed graph using Kosaraju's algorithm. - * Each SCC is represented as an array of node indices. + * Returns the strongly connected components of a directed graph. + * + * **When to use** + * + * Use when grouping nodes so every node in a component can reach every other + * node in that component. + * + * **Details** + * + * Each component is represented as an array of node indices and is computed + * with Kosaraju's algorithm. * * **Gotchas** * @@ -2965,7 +5613,7 @@ export const connectedComponents = ( * * **Example** (Finding strongly connected components) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -2977,10 +5625,11 @@ export const connectedComponents = ( * Graph.addEdge(mutable, c, a, "C->A") // Creates SCC: A-B-C * }) * - * const sccs = Graph.stronglyConnectedComponents(graph) - * console.log(sccs) // [[0, 1, 2]] + * Graph.stronglyConnectedComponents(graph) // => [[0, 2, 1]] * ``` * + * @see {@link isStronglyConnected} when only a boolean check is needed + * @see {@link weaklyConnectedComponents} when edge orientation should be ignored * @category algorithms * @since 3.18.0 */ @@ -2991,94 +5640,418 @@ export const stronglyConnectedComponents = ( throw new GraphError({ message: "Cannot find strongly connected components of undirected graph" }) } - const visited = new Set() - const finishOrder: Array = [] - // Iterate directly over node keys + const cache = csr.get(graph) + const outgoing = csr.getOutgoing(cache) + const incoming = csr.getIncoming(cache) + const visited = new Uint8Array(cache.nodeIds.length) + const finishOrder: Array = [] + const stack: Array = [] + const positions: Array = [] + + // First pass records finish order on the original graph using an explicit stack. + for (let start = 0; start < cache.nodeIds.length; start++) { + if (visited[start] !== 0) { + continue + } + visited[start] = 1 + stack.push(start) + positions.push(outgoing.rowOffsets[start]) - // Step 1: Stack-safe DFS on original graph to get finish times - // Stack entry: [node, neighbors, neighborIndex, isFirstVisit] - type DfsStackEntry = [NodeIndex, Array, number, boolean] + while (stack.length > 0) { + const frame = stack.length - 1 + const node = stack[frame] + const position = positions[frame] + if (position < outgoing.rowOffsets[node + 1]) { + positions[frame] = position + 1 + const neighbor = outgoing.columnIndices[position] + if (visited[neighbor] === 0) { + visited[neighbor] = 1 + stack.push(neighbor) + positions.push(outgoing.rowOffsets[neighbor]) + } + } else { + finishOrder.push(node) + stack.pop() + positions.pop() + } + } + } - for (const startNode of graph.nodes.keys()) { - if (visited.has(startNode)) { + visited.fill(0) + const components: Array> = [] + // Reversing finish order and traversing the transpose yields one SCC per search. + for (let i = finishOrder.length - 1; i >= 0; i--) { + const start = finishOrder[i] + if (visited[start] !== 0) { continue } - - const stack: Array = [[startNode, [], 0, true]] + const component: Array = [] + stack.push(start) while (stack.length > 0) { - const [node, nodeNeighbors, neighborIndex, isFirstVisit] = stack[stack.length - 1] + const node = stack.pop()! + if (visited[node] !== 0) { + continue + } + visited[node] = 1 + component.push(cache.nodeIds[node]) - if (isFirstVisit) { - if (visited.has(node)) { - stack.pop() - continue + for (let j = incoming.rowOffsets[node]; j < incoming.rowOffsets[node + 1]; j++) { + const predecessor = incoming.columnIndices[j] + if (visited[predecessor] === 0) { + stack.push(predecessor) } - - visited.add(node) - const nodeNeighborsList = getDirectedNeighbors(graph, node, "outgoing") - stack[stack.length - 1] = [node, nodeNeighborsList, 0, false] - continue } + } + + components.push(component) + } - // Process next neighbor - if (neighborIndex < nodeNeighbors.length) { - const neighbor = nodeNeighbors[neighborIndex] - stack[stack.length - 1] = [node, nodeNeighbors, neighborIndex + 1, false] + return components +} - if (!visited.has(neighbor)) { - stack.push([neighbor, [], 0, true]) +/** @internal */ +const csrReachesAll = ( + nodeCount: number, + primary: csr.Adjacency, + secondary?: csr.Adjacency +): boolean => { + if (nodeCount === 0) { + return true + } + const visited = new Uint8Array(nodeCount) + const queue = new Uint32Array(nodeCount) + let head = 0 + let tail = 1 + visited[0] = 1 + queue[0] = 0 + + while (head < tail) { + const current = queue[head++] + for (let i = primary.rowOffsets[current]; i < primary.rowOffsets[current + 1]; i++) { + const neighbor = primary.columnIndices[i] + if (visited[neighbor] === 0) { + visited[neighbor] = 1 + queue[tail++] = neighbor + } + } + if (secondary !== undefined) { + for (let i = secondary.rowOffsets[current]; i < secondary.rowOffsets[current + 1]; i++) { + const neighbor = secondary.columnIndices[i] + if (visited[neighbor] === 0) { + visited[neighbor] = 1 + queue[tail++] = neighbor } - } else { - // Done with this node - add to finish order (post-order) - finishOrder.push(node) - stack.pop() } } } + return tail === nodeCount +} + +/** + * Tests whether an undirected graph has at most one connected component. + * + * **When to use** + * + * Use when checking undirected connectivity without allocating the component + * partition. + * + * **Gotchas** + * + * The empty graph is considered connected. Throws a `GraphError` when used + * with a directed graph. + * + * @see {@link connectedComponents} for the component partition + * + * @category predicates + * @since 4.0.0 + */ +export const isConnected = ( + graph: Graph | MutableGraph +): boolean => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot find connected components of directed graph" }) + } + const cache = csr.get(graph) + return csrReachesAll(cache.nodeIds.length, csr.getOutgoing(cache)) +} - // Step 2: Stack-safe DFS on transpose graph in reverse finish order - visited.clear() - const sccs: Array> = [] +/** + * Tests whether a directed graph has at most one weakly connected component. + * + * **When to use** + * + * Use when checking whether a directed graph is connected after ignoring edge + * orientation. + * + * **Gotchas** + * + * The empty graph is considered weakly connected. Throws a `GraphError` when + * used with an undirected graph. + * + * @see {@link isStronglyConnected} when edge orientation must be respected + * + * @category predicates + * @since 4.0.0 + */ +export const isWeaklyConnected = ( + graph: Graph | MutableGraph +): boolean => { + if ((graph as Graph | MutableGraph).type === "undirected") { + throw new GraphError({ message: "Cannot find weakly connected components of undirected graph" }) + } + const cache = csr.get(graph) + const { primary, secondary } = csr.getAdjacencies(cache, "undirected") + return csrReachesAll(cache.nodeIds.length, primary, secondary) +} - for (let i = finishOrder.length - 1; i >= 0; i--) { - const startNode = finishOrder[i] - if (visited.has(startNode)) { +/** + * Tests whether a directed graph has at most one strongly connected component. + * + * **When to use** + * + * Use when checking that every node in a directed graph can reach every other + * node. + * + * **Gotchas** + * + * The empty graph is considered strongly connected. Throws a `GraphError` when + * used with an undirected graph. + * + * @see {@link isWeaklyConnected} when edge orientation should be ignored + * + * @category predicates + * @since 4.0.0 + */ +export const isStronglyConnected = ( + graph: Graph | MutableGraph +): boolean => { + if ((graph as Graph | MutableGraph).type === "undirected") { + throw new GraphError({ message: "Cannot find strongly connected components of undirected graph" }) + } + const cache = csr.get(graph) + return csrReachesAll(cache.nodeIds.length, csr.getOutgoing(cache)) && + csrReachesAll(cache.nodeIds.length, csr.getIncoming(cache)) +} + +/** + * Tests whether a non-empty undirected graph is a tree. + * + * **When to use** + * + * Use when validating that an undirected graph is connected and has no cycle. + * + * **Gotchas** + * + * The empty graph is not a tree. Parallel edges and self-loops prevent a graph + * from being a tree. Throws a `GraphError` when used with a directed graph. + * + * @category predicates + * @since 4.0.0 + */ +export const isTree = ( + graph: Graph | MutableGraph +): boolean => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot determine tree status of directed graph" }) + } + const nodes = nodeCount(graph) + return nodes > 0 && edgeCount(graph) === nodes - 1 && isConnected(graph) +} + +/** + * Returns a minimum spanning forest of an undirected graph using Kruskal's + * algorithm. + * + * **When to use** + * + * Use when selecting a minimum-cost acyclic connector for every connected + * component of an undirected graph. + * + * **Details** + * + * All node indices and selected edge indices are preserved. Negative finite + * weights are allowed, `Infinity` marks an unavailable edge, and equal weights + * are resolved by original edge order. Disconnected inputs produce a forest, + * and isolated nodes remain present. + * + * **Gotchas** + * + * Throws a `GraphError` for a directed graph or when a weight is `NaN` or + * `-Infinity`. Edges weighted `Infinity` are omitted. + * + * @category algorithms + * @since 4.0.0 + */ +export const minimumSpanningForest: { + (cost: (edgeData: E) => number): ( + graph: Graph | MutableGraph + ) => Graph + ( + graph: Graph | MutableGraph, + cost: (edgeData: E) => number + ): Graph +} = dual(2, ( + graph: Graph | MutableGraph, + cost: (edgeData: E) => number +): Graph => { + if ((graph as Graph | MutableGraph).type === "directed") { + throw new GraphError({ message: "Cannot find minimum spanning forest of directed graph" }) + } + const impl = internal.toImpl(graph) + const nodes: Array> = [] + const compactByNode = new Map() + for (const [index, data] of impl.nodes) { + compactByNode.set(index, nodes.length) + nodes.push({ index, data }) + } + const weightedEdges: Array<{ readonly index: EdgeIndex; readonly weight: number; readonly order: number }> = [] + let order = 0 + withMutationGuard(graph, () => { + for (const [index, edge] of impl.edges) { + const weight = cost(edge.data) + if (Number.isNaN(weight) || weight === -Infinity) { + throw new GraphError({ message: "Minimum spanning forest does not support NaN or -Infinity edge weights" }) + } + if (weight !== Infinity) { + weightedEdges.push({ index, weight, order }) + } + order++ + } + }) + weightedEdges.sort((self, that) => self.weight - that.weight || self.order - that.order) + + const parents = new Uint32Array(nodes.length) + const ranks = new Uint8Array(nodes.length) + for (let i = 0; i < parents.length; i++) { + parents[i] = i + } + const find = (node: number): number => { + let root = node + while (parents[root] !== root) { + root = parents[root] + } + while (parents[node] !== node) { + const parent = parents[node] + parents[node] = root + node = parent + } + return root + } + const selected = new Set() + for (const weighted of weightedEdges) { + const edge = impl.edges.get(weighted.index)! + let sourceRoot = find(compactByNode.get(edge.source)!) + let targetRoot = find(compactByNode.get(edge.target)!) + if (sourceRoot === targetRoot) { continue } + selected.add(weighted.index) + if (ranks[sourceRoot] < ranks[targetRoot]) { + const swap = sourceRoot + sourceRoot = targetRoot + targetRoot = swap + } + parents[targetRoot] = sourceRoot + if (ranks[sourceRoot] === ranks[targetRoot]) { + ranks[sourceRoot]++ + } + } - const scc: Array = [] - const stack: Array = [startNode] + const edges: Array> = [] + for (const [index, edge] of impl.edges) { + if (selected.has(index)) { + edges.push({ index, source: edge.source, target: edge.target, data: edge.data }) + } + } + return fromSnapshot({ type: "undirected", nodes, edges }) +}) - while (stack.length > 0) { - const node = stack.pop()! +/** + * Returns the transitive reduction of a directed acyclic graph. + * + * **When to use** + * + * Use when simplifying a dependency DAG while preserving which nodes can + * reach which other nodes. + * + * **Details** + * + * The result preserves reachability with the fewest structural source-target + * pairs. Node and retained edge indices are preserved. + * + * **Gotchas** + * + * This operation is structural and ignores edge costs. Parallel edges are + * coalesced by retaining the first edge for each required pair. Throws a + * `GraphError` for an undirected graph or cyclic input. + * + * @category algorithms + * @since 4.0.0 + */ +export const transitiveReduction = ( + graph: Graph | MutableGraph +): Graph => { + if ((graph as Graph | MutableGraph).type === "undirected") { + throw new GraphError({ message: "Cannot transitively reduce undirected graph" }) + } + if (!isAcyclic(graph)) { + throw new GraphError({ message: "Cannot transitively reduce cyclic graph" }) + } - if (visited.has(node)) { - continue - } + const impl = internal.toImpl(graph) + const nodes: Array> = [] + for (const [index, data] of impl.nodes) { + nodes.push({ index, data }) + } + const firstEdges = new Map>() + for (const [edgeIndex, edge] of impl.edges) { + let targets = firstEdges.get(edge.source) + if (targets === undefined) { + targets = new Map() + firstEdges.set(edge.source, targets) + } + if (!targets.has(edge.target)) { + targets.set(edge.target, edgeIndex) + } + } - visited.add(node) - scc.push(node) - - // Use reverse adjacency (transpose graph) - const reverseAdjacency = graph.reverseAdjacency.get(node) - if (reverseAdjacency !== undefined) { - for (const edgeIndex of reverseAdjacency) { - const edge = graph.edges.get(edgeIndex) - if (edge !== undefined) { - const predecessor = edge.source - if (!visited.has(predecessor)) { - stack.push(predecessor) - } + const retained = new Set() + for (const [source, targets] of firstEdges) { + for (const [target, edgeIndex] of targets) { + const visited = new Set([source]) + const queue = [source] + let reachable = false + for (let head = 0; head < queue.length && !reachable; head++) { + const current = queue[head] + for (const candidateIndex of impl.adjacency.get(current)!) { + const candidate = impl.edges.get(candidateIndex)! + if (current === source && candidate.target === target) { + continue + } + if (candidate.target === target) { + reachable = true + break + } + if (!visited.has(candidate.target)) { + visited.add(candidate.target) + queue.push(candidate.target) } } } + if (!reachable) { + retained.add(edgeIndex) + } } - - sccs.push(scc) } - return sccs + const edges: Array> = [] + for (const [index, edge] of impl.edges) { + if (retained.has(index)) { + edges.push({ index, source: edge.source, target: edge.target, data: edge.data }) + } + } + return fromSnapshot({ type: "directed", nodes, edges }) } // ============================================================================= @@ -3091,33 +6064,156 @@ export const stronglyConnectedComponents = ( * **When to use** * * Use to read the successful source-to-target shortest path returned by - * path-finding algorithms, including the ordered node indices, total distance, - * and traversed edge data. + * path-finding algorithms, including the ordered node and edge indices, total + * distance, and traversed edge data. * * **Details** * - * Contains the node-index path, the total numeric distance, and the edge data - * encountered along the path. + * Contains the node-index path, the traversed edge indices, the total numeric + * distance, and the edge data encountered along the path. * * **Gotchas** * * `costs` contains original edge data, not the numeric output of the cost * function unless the edge data is numeric. * - * @see {@link dijkstra} for shortest paths with non-negative edge costs - * @see {@link astar} for heuristic shortest-path search - * @see {@link bellmanFord} for shortest paths that may include negative edge weights - * @see {@link AllPairsResult} for the all-pairs shortest-path result shape - * * @category models * @since 3.18.0 */ export interface PathResult { readonly path: Array + readonly edges: Array readonly distance: number readonly costs: Array } +interface DenseMinHeap { + nodes: Uint32Array + priorities: Float64Array + sequences: Float64Array + positions: Int32Array | undefined + size: number + poppedNode: number + poppedPriority: number +} + +const denseMinHeapMake = (capacity: number, indexed = false): DenseMinHeap => { + const positions = indexed ? new Int32Array(capacity) : undefined + positions?.fill(-1) + return { + nodes: new Uint32Array(Math.max(4, capacity)), + priorities: new Float64Array(Math.max(4, capacity)), + sequences: new Float64Array(Math.max(4, capacity)), + positions, + size: 0, + poppedNode: 0, + poppedPriority: 0 + } +} + +const denseMinHeapPush = ( + heap: DenseMinHeap, + node: number, + priority: number, + sequence: number +): void => { + let index = heap.positions?.[node] ?? -1 + if (index === -1) { + if (heap.size === heap.nodes.length) { + const capacity = heap.size * 2 + const nodes = new Uint32Array(capacity) + const priorities = new Float64Array(capacity) + const sequences = new Float64Array(capacity) + nodes.set(heap.nodes) + priorities.set(heap.priorities) + sequences.set(heap.sequences) + heap.nodes = nodes + heap.priorities = priorities + heap.sequences = sequences + } + index = heap.size++ + } + + while (index > 0) { + const parent = (index - 1) >>> 1 + if ( + priority > heap.priorities[parent] || + (priority === heap.priorities[parent] && sequence >= heap.sequences[parent]) + ) { + break + } + heap.nodes[index] = heap.nodes[parent] + heap.priorities[index] = heap.priorities[parent] + heap.sequences[index] = heap.sequences[parent] + if (heap.positions !== undefined) { + heap.positions[heap.nodes[index]] = index + } + index = parent + } + heap.nodes[index] = node + heap.priorities[index] = priority + heap.sequences[index] = sequence + if (heap.positions !== undefined) { + heap.positions[node] = index + } +} + +const denseMinHeapPop = (heap: DenseMinHeap): boolean => { + if (heap.size === 0) { + return false + } + + heap.poppedNode = heap.nodes[0] + heap.poppedPriority = heap.priorities[0] + if (heap.positions !== undefined) { + heap.positions[heap.poppedNode] = -1 + } + const last = --heap.size + if (last === 0) { + return true + } + + const node = heap.nodes[last] + const priority = heap.priorities[last] + const sequence = heap.sequences[last] + let index = 0 + while (true) { + const left = index * 2 + 1 + if (left >= last) { + break + } + const right = left + 1 + let child = left + if ( + right < last && + (heap.priorities[right] < heap.priorities[left] || + (heap.priorities[right] === heap.priorities[left] && heap.sequences[right] < heap.sequences[left])) + ) { + child = right + } + if ( + heap.priorities[child] > priority || + (heap.priorities[child] === priority && heap.sequences[child] >= sequence) + ) { + break + } + heap.nodes[index] = heap.nodes[child] + heap.priorities[index] = heap.priorities[child] + heap.sequences[index] = heap.sequences[child] + if (heap.positions !== undefined) { + heap.positions[heap.nodes[index]] = index + } + index = child + } + heap.nodes[index] = node + heap.priorities[index] = priority + heap.sequences[index] = sequence + if (heap.positions !== undefined) { + heap.positions[node] = index + } + return true +} + /** * Configuration for finding a shortest path with Dijkstra's algorithm. * @@ -3129,18 +6225,15 @@ export interface PathResult { * **Details** * * Specifies the source and target node indices, plus a cost function that maps - * each edge's data to a non-negative numeric weight. + * each edge's data to a non-negative numeric weight. `Infinity` is allowed and + * behaves like an impassable edge. * * **Gotchas** * * `dijkstra` throws a `GraphError` when either endpoint does not exist or when - * the cost function returns a negative weight. - * - * @see {@link dijkstra} for the algorithm that consumes this configuration - * @see {@link AstarConfig} for heuristic shortest-path search - * @see {@link BellmanFordConfig} for shortest paths that may include negative edge weights + * the cost function returns a negative weight or `NaN`. * - * @category models + * @category configuration * @since 3.18.0 */ export interface DijkstraConfig { @@ -3149,36 +6242,30 @@ export interface DijkstraConfig { cost: (edgeData: E) => number } -const validateNonNegativeEdgeWeights = ( - graph: Graph | MutableGraph, - cost: (edgeData: E) => number, - algorithm: string -): Map => { - const edgeWeights = new Map() - for (const [edgeIndex, edgeData] of graph.edges) { - const weight = cost(edgeData.data) - if (weight < 0 || Number.isNaN(weight)) { - throw new GraphError({ message: `${algorithm} requires non-negative edge weights` }) - } - edgeWeights.set(edgeIndex, weight) - } - return edgeWeights -} - /** * Finds the shortest path from the configured source node to the target node * using Dijkstra's algorithm. * + * **When to use** + * + * Use when you need one source-to-target shortest path and every edge cost is + * non-negative. + * * **Details** * - * Edge costs must be non-negative. Returns `Option.none()` when the target is - * not reachable, and throws a `GraphError` when either endpoint is missing or a - * negative edge cost is encountered. + * Edge costs must be non-negative and not `NaN`. `Infinity` is allowed and + * behaves like an impassable edge. Returns `Option.none()` when the target is + * not reachable. + * + * **Gotchas** + * + * Throws a `GraphError` when either endpoint is missing or an edge cost is + * negative or `NaN`, or when a path distance exceeds the finite number range. * * **Example** (Finding shortest paths with Dijkstra) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") @@ -3195,12 +6282,12 @@ const validateNonNegativeEdgeWeights = ( * cost: (edgeData) => edgeData * }) * - * if (result._tag === "Some") { - * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C - * console.log(result.value.distance) // 7 - total distance - * } + * Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([7, [0, 1, 2]]) * ``` * + * @see {@link astar} when a useful heuristic can guide the search + * @see {@link bellmanFord} when edge costs may be negative + * @see {@link floydWarshall} when shortest paths are needed for all pairs * @category algorithms * @since 3.18.0 */ @@ -3216,123 +6303,107 @@ export const dijkstra: { graph: Graph | MutableGraph, config: DijkstraConfig ): Option.Option> => { + const impl = internal.toImpl(graph) // Validate that source and target nodes exist - if (!graph.nodes.has(config.source)) { + if (!impl.nodes.has(config.source)) { throw missingNode(config.source) } - if (!graph.nodes.has(config.target)) { + if (!impl.nodes.has(config.target)) { throw missingNode(config.target) } - const edgeWeights = validateNonNegativeEdgeWeights(graph, config.cost, "Dijkstra's algorithm") + const cache = csr.get(graph) + const cachedEdges = csr.getEdges(cache) + const cachedEdgeIds = csr.getEdgeIds(cache) + const outgoing = csr.getOutgoingWithEdges(cache) + const source = csr.getNodeIndex(cache, config.source)! + const target = csr.getNodeIndex(cache, config.target)! + const edgeWeights = new Float64Array(cachedEdges.length) + withMutationGuard(graph, () => { + for (let i = 0; i < cachedEdges.length; i++) { + const weight = config.cost(cachedEdges[i].data) + if (Number.isNaN(weight) || weight < 0) { + throw new GraphError({ message: "Dijkstra's algorithm requires non-negative edge weights" }) + } + edgeWeights[i] = weight + } + }) // Early return if source equals target if (config.source === config.target) { return Option.some({ path: [config.source], + edges: [], distance: 0, costs: [] }) } - // Distance tracking and priority queue simulation - const distances = new Map() - const previous = new Map() - const visited = new Set() - - // Initialize distances - // Iterate directly over node keys - for (const node of graph.nodes.keys()) { - distances.set(node, node === config.source ? 0 : Infinity) - previous.set(node, null) - } - - // Simple priority queue using array (can be optimized with proper heap) - const priorityQueue: Array<{ node: NodeIndex; distance: number }> = [ - { node: config.source, distance: 0 } - ] - - while (priorityQueue.length > 0) { - // Find minimum distance node (priority queue extract-min) - let minIndex = 0 - for (let i = 1; i < priorityQueue.length; i++) { - if (priorityQueue[i].distance < priorityQueue[minIndex].distance) { - minIndex = i - } - } - - const current = priorityQueue.splice(minIndex, 1)[0] - const currentNode = current.node - - // Skip if already visited (can happen with duplicate entries) - if (visited.has(currentNode)) { + const distances = new Float64Array(cache.nodeIds.length) + distances.fill(Infinity) + distances[source] = 0 + // Predecessor node and edge arrays reconstruct both the public node path and its edge data. + const previousNode = new Int32Array(cache.nodeIds.length) + const previousEdge = new Int32Array(cache.nodeIds.length) + previousNode.fill(-1) + previousEdge.fill(-1) + const visited = new Uint8Array(cache.nodeIds.length) + const priorityQueue = denseMinHeapMake(cache.nodeIds.length, true) + let sequence = 0 + denseMinHeapPush(priorityQueue, source, 0, sequence++) + + while (priorityQueue.size > 0) { + denseMinHeapPop(priorityQueue) + const currentNode = priorityQueue.poppedNode + const currentDistance = priorityQueue.poppedPriority + if (visited[currentNode] !== 0) { continue } - - visited.add(currentNode) - - // Early termination if we reached the target - if (currentNode === config.target) { + visited[currentNode] = 1 + if (currentNode === target) { break } - // Get current distance - const currentDistance = distances.get(currentNode)! - - // Examine all outgoing edges - const adjacencyList = graph.adjacency.get(currentNode) - if (adjacencyList !== undefined) { - for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) - if (edge !== undefined) { - const neighbor = getTraversableNeighbor(graph, currentNode, edge) - const cost = edgeWeights.get(edgeIndex)! - - const newDistance = currentDistance + cost - const neighborDistance = distances.get(neighbor)! - - // Relaxation step - if (newDistance < neighborDistance) { - distances.set(neighbor, newDistance) - previous.set(neighbor, { node: currentNode, edgeData: edge.data }) - - // Add to priority queue if not visited - if (!visited.has(neighbor)) { - priorityQueue.push({ node: neighbor, distance: newDistance }) - } - } + for (let i: number = outgoing.rowOffsets[currentNode]; i < outgoing.rowOffsets[currentNode + 1]; i++) { + const neighbor = outgoing.columnIndices[i] + const edge = outgoing.edgeIndices[i] + const nextDistance = currentDistance + edgeWeights[edge] + if (edgeWeights[edge] !== Infinity && !Number.isFinite(nextDistance)) { + throw new GraphError({ message: "Dijkstra distance calculation exceeded the finite number range" }) + } + if (nextDistance < distances[neighbor]) { + distances[neighbor] = nextDistance + previousNode[neighbor] = currentNode + previousEdge[neighbor] = edge + if (visited[neighbor] === 0) { + denseMinHeapPush(priorityQueue, neighbor, nextDistance, sequence++) } } } } - // Check if target is reachable - const distance = distances.get(config.target)! - if (distance === Infinity) { - return Option.none() // No path exists + if (distances[target] === Infinity) { + return Option.none() } - // Reconstruct path const path: Array = [] + const edges: Array = [] const costs: Array = [] - let currentNode: NodeIndex | null = config.target - - while (currentNode !== null) { - path.unshift(currentNode) - const prev: { node: NodeIndex; edgeData: E } | null = previous.get(currentNode)! - if (prev !== null) { - costs.unshift(prev.edgeData) - currentNode = prev.node - } else { - currentNode = null + let current = target + while (current !== -1) { + path.push(cache.nodeIds[current]) + const edge = previousEdge[current] + if (edge !== -1) { + edges.push(cachedEdgeIds[edge]) + costs.push(cachedEdges[edge].data) } + current = previousNode[current] } + path.reverse() + edges.reverse() + costs.reverse() - return Option.some({ - path, - distance, - costs - }) + return Option.some({ path, edges, distance: distances[target], costs }) }) /** @@ -3341,16 +6412,14 @@ export const dijkstra: { * **When to use** * * Use when storing or passing around the complete output of `floydWarshall` so - * callers can look up shortest distances, node paths, and edge data for any - * source and target node pair. + * callers can look up shortest distances, node and edge paths, and edge data + * for any source and target node pair. * * **Details** * - * Contains distance, node-path, and edge-data maps keyed by source and target - * node indices. - * - * @see {@link floydWarshall} for computing an all-pairs shortest path result - * @see {@link PathResult} for the single source-to-target result shape used by path-finding algorithms + * Contains distance, node-path, edge-index-path, and edge-data maps keyed by + * source and target node indices. Unreachable pairs have distance `Infinity`, + * path `null`, and empty edge and cost arrays. * * @category models * @since 3.18.0 @@ -3358,6 +6427,7 @@ export const dijkstra: { export interface AllPairsResult { readonly distances: Map> readonly paths: Map | null>> + readonly edges: Map>> readonly costs: Map>> } @@ -3365,15 +6435,26 @@ export interface AllPairsResult { * Finds shortest paths between all pairs of nodes using the Floyd-Warshall * algorithm. * + * **When to use** + * + * Use when many or all node pairs will be queried and cubic computation plus + * quadratic result storage is acceptable. + * * **Details** * * Computes distances, reconstructed node paths, and edge-data paths for every - * source and target pair in O(V^3) time. Negative edge weights are allowed, but - * a `GraphError` is thrown if any negative cycle is detected. + * source and target pair in O(V^3) time. Negative edge weights are allowed, and + * `Infinity` behaves like an impassable edge. + * + * **Gotchas** + * + * A `GraphError` is thrown if any edge weight is `NaN` or `-Infinity`, or if + * finite arithmetic overflows or underflows, or if any negative cycle is + * detected. * * **Example** (Finding all-pairs shortest paths) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -3386,10 +6467,12 @@ export interface AllPairsResult { * }) * * const result = Graph.floydWarshall(graph, (edgeData) => edgeData) - * const distanceAToC = result.distances.get(0)?.get(2) // 5 (A->B->C) - * const pathAToC = result.paths.get(0)?.get(2) // [0, 1, 2] + * const shortest = { distance: result.distances.get(0)?.get(2), path: result.paths.get(0)?.get(2) } + * shortest // => { distance: 5, path: [0, 1, 2] } * ``` * + * @see {@link dijkstra} for one query with non-negative edge costs + * @see {@link bellmanFord} for one query that may include negative edge costs * @category algorithms * @since 3.18.0 */ @@ -3405,120 +6488,132 @@ export const floydWarshall: { graph: Graph | MutableGraph, cost: (edgeData: E) => number ): AllPairsResult => { - // Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration) - const allNodes = Array.from(graph.nodes.keys()) + const cache = csr.get(graph) + const edges = csr.getEdges(cache) + const edgeIds = csr.getEdgeIds(cache) + const edgeCache = csr.getEdgeEndpoints(cache) + const size = cache.nodeIds.length + // Flat matrices keep the O(N^2) working set contiguous and avoid nested map lookups in the O(N^3) loop. + const distancesMatrix = new Float64Array(size * size) + const nextMatrix = new Int32Array(size * size) + const edgeMatrix = new Int32Array(size * size) + distancesMatrix.fill(Infinity) + nextMatrix.fill(-1) + edgeMatrix.fill(-1) + for (let i = 0; i < size; i++) { + distancesMatrix[i * size + i] = 0 + } - // Initialize distance matrix - const distances = new Map>() - const next = new Map>() - const edgeMatrix = new Map>() - - // Initialize with infinity for all pairs - for (const i of allNodes) { - distances.set(i, new Map()) - next.set(i, new Map()) - edgeMatrix.set(i, new Map()) - - for (const j of allNodes) { - distances.get(i)!.set(j, i === j ? 0 : Infinity) - next.get(i)!.set(j, null) - edgeMatrix.get(i)!.set(j, null) - } - } - - // Set edge weights - for (const [, edgeData] of graph.edges) { - const weight = cost(edgeData.data) - const i = edgeData.source - const j = edgeData.target - - // Use minimum weight if multiple edges exist - const currentWeight = distances.get(i)!.get(j)! - if (weight < currentWeight) { - distances.get(i)!.set(j, weight) - next.get(i)!.set(j, j) - edgeMatrix.get(i)!.set(j, edgeData.data) - } - - if (graph.type === "undirected") { - const reverseWeight = distances.get(j)!.get(i)! - if (weight < reverseWeight) { - distances.get(j)!.set(i, weight) - next.get(j)!.set(i, i) - edgeMatrix.get(j)!.set(i, edgeData.data) + withMutationGuard(graph, () => { + for (let edge = 0; edge < edges.length; edge++) { + const weight = cost(edges[edge].data) + if (Number.isNaN(weight) || weight === -Infinity) { + throw new GraphError({ message: "Floyd-Warshall algorithm does not support NaN or -Infinity edge weights" }) + } + const source = edgeCache.sources[edge] + const target = edgeCache.targets[edge] + const position = source * size + target + if (weight < distancesMatrix[position]) { + distancesMatrix[position] = weight + nextMatrix[position] = target + edgeMatrix[position] = edge + } + if (graph.type === "undirected") { + const reverse = target * size + source + if (weight < distancesMatrix[reverse]) { + distancesMatrix[reverse] = weight + nextMatrix[reverse] = source + edgeMatrix[reverse] = edge + } } } - } - - // Floyd-Warshall main loop - for (const k of allNodes) { - for (const i of allNodes) { - for (const j of allNodes) { - const distIK = distances.get(i)!.get(k)! - const distKJ = distances.get(k)!.get(j)! - const distIJ = distances.get(i)!.get(j)! + }) - if (distIK !== Infinity && distKJ !== Infinity && distIK + distKJ < distIJ) { - distances.get(i)!.set(j, distIK + distKJ) - next.get(i)!.set(j, next.get(i)!.get(k)!) + for (let k = 0; k < size; k++) { + const kRow = k * size + for (let i = 0; i < size; i++) { + const iRow = i * size + const distanceIK = distancesMatrix[iRow + k] + if (distanceIK === Infinity) { + continue + } + const nextIK = nextMatrix[iRow + k] + for (let j = 0; j < size; j++) { + const distanceKJ = distancesMatrix[kRow + j] + if (distanceKJ === Infinity) { + continue + } + const candidate = distanceIK + distanceKJ + if (!Number.isFinite(candidate)) { + throw new GraphError({ message: "Floyd-Warshall distance calculation exceeded the finite number range" }) + } + if (candidate < distancesMatrix[iRow + j] && nextIK !== -1) { + distancesMatrix[iRow + j] = candidate + nextMatrix[iRow + j] = nextIK } } } } - // Check for negative cycles - for (const i of allNodes) { - if (distances.get(i)!.get(i)! < 0) { - throw new GraphError({ message: `Negative cycle detected involving node ${i}` }) + for (let i = 0; i < size; i++) { + if (distancesMatrix[i * size + i] < 0) { + throw new GraphError({ message: `Negative cycle detected involving node ${cache.nodeIds[i]}` }) } } - // Build result paths and edge weights + const distances = new Map>() const paths = new Map | null>>() + const edgePaths = new Map>>() const costs = new Map>>() - - for (const i of allNodes) { - paths.set(i, new Map()) - costs.set(i, new Map()) - - for (const j of allNodes) { + for (let i = 0; i < size; i++) { + const source = cache.nodeIds[i] + const distanceRow = new Map() + const pathRow = new Map | null>() + const edgePathRow = new Map>() + const costRow = new Map>() + distances.set(source, distanceRow) + paths.set(source, pathRow) + edgePaths.set(source, edgePathRow) + costs.set(source, costRow) + + for (let j = 0; j < size; j++) { + const target = cache.nodeIds[j] + const distance = distancesMatrix[i * size + j] + distanceRow.set(target, distance) if (i === j) { - paths.get(i)!.set(j, [i]) - costs.get(i)!.set(j, []) - } else if (distances.get(i)!.get(j)! === Infinity) { - paths.get(i)!.set(j, null) - costs.get(i)!.set(j, []) + pathRow.set(target, [source]) + edgePathRow.set(target, []) + costRow.set(target, []) + } else if (distance === Infinity) { + pathRow.set(target, null) + edgePathRow.set(target, []) + costRow.set(target, []) } else { - // Reconstruct path iteratively - const path: Array = [] - const weights: Array = [] + const path = [source] + const pathEdges: Array = [] + const pathCosts: Array = [] let current = i - - path.push(current) while (current !== j) { - const nextNode = next.get(current)!.get(j)! - if (nextNode === null) break - - const edgeData = edgeMatrix.get(current)!.get(nextNode)! - if (edgeData !== null) { - weights.push(edgeData) + const next = nextMatrix[current * size + j] + if (next === -1) { + break } - - current = nextNode - path.push(current) + const edge = edgeMatrix[current * size + next] + if (edge !== -1) { + pathEdges.push(edgeIds[edge]) + pathCosts.push(edges[edge].data) + } + current = next + path.push(cache.nodeIds[current]) } - - paths.get(i)!.set(j, path) - costs.get(i)!.set(j, weights) + pathRow.set(target, path) + edgePathRow.set(target, pathEdges) + costRow.set(target, pathCosts) } } } - return { - distances, - paths, - costs - } + return { distances, paths, edges: edgePaths, costs } }) /** @@ -3531,14 +6626,16 @@ export const floydWarshall: { * * **Details** * - * Specifies the source and target node indices, an edge-cost function, and a - * heuristic that estimates the remaining cost from a node to the target. + * Specifies the source and target node indices, an edge-cost function that maps + * edge data to non-negative weights, and a heuristic that estimates the + * remaining cost from a node to the target. + * + * **Gotchas** * - * @see {@link astar} for the algorithm that consumes this configuration - * @see {@link DijkstraConfig} for shortest paths without a heuristic - * @see {@link BellmanFordConfig} for shortest paths that may include negative edge weights + * Heuristic values must be finite and the heuristic must be consistent for A* + * to guarantee a shortest path. * - * @category models + * @category configuration * @since 3.18.0 */ export interface AstarConfig { @@ -3552,17 +6649,27 @@ export interface AstarConfig { * Finds the shortest path from the configured source node to the target node * using the A* pathfinding algorithm. * + * **When to use** + * + * Use when a meaningful heuristic can reduce point-to-point search compared + * with Dijkstra's algorithm. + * * **Details** * - * The edge-cost function must return non-negative weights, and the heuristic - * should be consistent to preserve shortest-path guarantees. Returns - * `Option.none()` when the target is not reachable, and throws a `GraphError` - * when either endpoint is missing or a negative edge cost is encountered. + * The edge-cost function must return non-negative weights and not `NaN`. + * `Infinity` is allowed and behaves like an impassable edge. Returns + * `Option.none()` when the target is not reachable. + * + * **Gotchas** * - * **Example** (Finding shortest paths with A-star) + * The heuristic must be consistent for the shortest-path guarantee and must + * return finite values. Missing endpoints, invalid edge costs, or non-finite + * heuristic values or arithmetic results throw a `GraphError`. * - * ```ts - * import { Graph } from "effect" + * **Example** (Finding shortest paths with A*) + * + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed<{ x: number; y: number }, number>((mutable) => { * const a = Graph.addNode(mutable, { x: 0, y: 0 }) @@ -3585,12 +6692,11 @@ export interface AstarConfig { * heuristic * }) * - * if (result._tag === "Some") { - * console.log(result.value.path) // [0, 1, 2] - shortest path - * console.log(result.value.distance) // 2 - total distance - * } + * Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([2, [0, 1, 2]]) * ``` * + * @see {@link dijkstra} when no useful heuristic is available + * @see {@link bellmanFord} when edge costs may be negative * @category algorithms * @since 3.18.0 */ @@ -3606,147 +6712,124 @@ export const astar: { graph: Graph | MutableGraph, config: AstarConfig ): Option.Option> => { + const impl = internal.toImpl(graph) // Validate that source and target nodes exist - if (!graph.nodes.has(config.source)) { + if (!impl.nodes.has(config.source)) { throw missingNode(config.source) } - if (!graph.nodes.has(config.target)) { + if (!impl.nodes.has(config.target)) { throw missingNode(config.target) } - const edgeWeights = validateNonNegativeEdgeWeights(graph, config.cost, "A* algorithm") + const cache = csr.get(graph) + const cachedEdges = csr.getEdges(cache) + const cachedEdgeIds = csr.getEdgeIds(cache) + const outgoing = csr.getOutgoingWithEdges(cache) + const source = csr.getNodeIndex(cache, config.source)! + const target = csr.getNodeIndex(cache, config.target)! + const sourceNodeData = cache.nodeData[source] as N + const targetNodeData = cache.nodeData[target] as N + const edgeWeights = new Float64Array(cachedEdges.length) + withMutationGuard(graph, () => { + for (let i = 0; i < cachedEdges.length; i++) { + const weight = config.cost(cachedEdges[i].data) + if (Number.isNaN(weight) || weight < 0) { + throw new GraphError({ message: "A* algorithm requires non-negative edge weights" }) + } + edgeWeights[i] = weight + } + }) // Early return if source equals target if (config.source === config.target) { + if (!Number.isFinite(withMutationGuard(graph, () => config.heuristic(sourceNodeData, targetNodeData)))) { + throw new GraphError({ message: "A* algorithm requires finite heuristic values" }) + } return Option.some({ path: [config.source], + edges: [], distance: 0, costs: [] }) } - // Get target node data for heuristic calculations - const targetNodeData = getNode(graph, config.target) - if (Option.isNone(targetNodeData)) { - throw new GraphError({ message: `Missing node data for target node ${config.target}` }) - } - - // Distance tracking (g-score) and f-score (g + h) - const gScore = new Map() - const fScore = new Map() - const previous = new Map() - const visited = new Set() - - // Initialize scores - // Iterate directly over node keys - for (const node of graph.nodes.keys()) { - gScore.set(node, node === config.source ? 0 : Infinity) - fScore.set(node, Infinity) - previous.set(node, null) - } - - // Calculate initial f-score for source - const sourceNodeData = getNode(graph, config.source) - if (Option.isSome(sourceNodeData)) { - const h = config.heuristic(sourceNodeData.value, targetNodeData.value) - fScore.set(config.source, h) - } - - // Priority queue using f-score (total estimated cost) - const openSet: Array<{ node: NodeIndex; fScore: number }> = [ - { node: config.source, fScore: fScore.get(config.source)! } - ] - - while (openSet.length > 0) { - // Find node with lowest f-score - let minIndex = 0 - for (let i = 1; i < openSet.length; i++) { - if (openSet[i].fScore < openSet[minIndex].fScore) { - minIndex = i - } + const getHeuristic = (nodeData: N): number => { + const value = withMutationGuard(graph, () => config.heuristic(nodeData, targetNodeData)) + if (!Number.isFinite(value)) { + throw new GraphError({ message: "A* algorithm requires finite heuristic values" }) } + return value + } - const current = openSet.splice(minIndex, 1)[0] - const currentNode = current.node - - // Skip if already visited - if (visited.has(currentNode)) { + const scores = new Float64Array(cache.nodeIds.length) + scores.fill(Infinity) + scores[source] = 0 + // Predecessor node and edge arrays preserve path reconstruction while the hot loop uses compact indices. + const previousNode = new Int32Array(cache.nodeIds.length) + const previousEdge = new Int32Array(cache.nodeIds.length) + previousNode.fill(-1) + previousEdge.fill(-1) + const visited = new Uint8Array(cache.nodeIds.length) + const openSet = denseMinHeapMake(cache.nodeIds.length) + let sequence = 0 + denseMinHeapPush(openSet, source, getHeuristic(sourceNodeData), sequence++) + + while (openSet.size > 0) { + denseMinHeapPop(openSet) + const current = openSet.poppedNode + if (visited[current] !== 0) { continue } - - visited.add(currentNode) - - // Early termination if we reached the target - if (currentNode === config.target) { + visited[current] = 1 + if (current === target) { break } - // Get current g-score - const currentGScore = gScore.get(currentNode)! - - // Examine all outgoing edges - const adjacencyList = graph.adjacency.get(currentNode) - if (adjacencyList !== undefined) { - for (const edgeIndex of adjacencyList) { - const edge = graph.edges.get(edgeIndex) - if (edge !== undefined) { - const neighbor = getTraversableNeighbor(graph, currentNode, edge) - const weight = edgeWeights.get(edgeIndex)! - - const tentativeGScore = currentGScore + weight - const neighborGScore = gScore.get(neighbor)! - - // If this path to neighbor is better than any previous one - if (tentativeGScore < neighborGScore) { - // Update g-score and previous - gScore.set(neighbor, tentativeGScore) - previous.set(neighbor, { node: currentNode, edgeData: edge.data }) - - // Calculate f-score using heuristic - const neighborNodeData = getNode(graph, neighbor) - if (Option.isSome(neighborNodeData)) { - const h = config.heuristic(neighborNodeData.value, targetNodeData.value) - const f = tentativeGScore + h - fScore.set(neighbor, f) - - // Add to open set if not visited - if (!visited.has(neighbor)) { - openSet.push({ node: neighbor, fScore: f }) - } - } - } + const currentScore = scores[current] + for (let i: number = outgoing.rowOffsets[current]; i < outgoing.rowOffsets[current + 1]; i++) { + const neighbor = outgoing.columnIndices[i] + if (visited[neighbor] !== 0) { + continue + } + const edge = outgoing.edgeIndices[i] + const tentativeScore = currentScore + edgeWeights[edge] + if (edgeWeights[edge] !== Infinity && !Number.isFinite(tentativeScore)) { + throw new GraphError({ message: "A* distance calculation exceeded the finite number range" }) + } + if (tentativeScore < scores[neighbor]) { + scores[neighbor] = tentativeScore + previousNode[neighbor] = current + previousEdge[neighbor] = edge + const priority = tentativeScore + getHeuristic(cache.nodeData[neighbor] as N) + if (!Number.isFinite(priority)) { + throw new GraphError({ message: "A* priority calculation exceeded the finite number range" }) } + denseMinHeapPush(openSet, neighbor, priority, sequence++) } } } - // Check if target is reachable - const distance = gScore.get(config.target)! - if (distance === Infinity) { - return Option.none() // No path exists + if (scores[target] === Infinity) { + return Option.none() } - // Reconstruct path const path: Array = [] + const edges: Array = [] const costs: Array = [] - let currentNode: NodeIndex | null = config.target - - while (currentNode !== null) { - path.unshift(currentNode) - const prev: { node: NodeIndex; edgeData: E } | null = previous.get(currentNode) ?? null - if (prev !== null) { - costs.unshift(prev.edgeData) - currentNode = prev.node - } else { - currentNode = null + let current = target + while (current !== -1) { + path.push(cache.nodeIds[current]) + const edge = previousEdge[current] + if (edge !== -1) { + edges.push(cachedEdgeIds[edge]) + costs.push(cachedEdges[edge].data) } + current = previousNode[current] } - - return Option.some({ - path, - distance, - costs - }) + path.reverse() + edges.reverse() + costs.reverse() + return Option.some({ path, edges, distance: scores[target], costs }) }) /** @@ -3762,11 +6845,7 @@ export const astar: { * Specifies the source and target node indices, plus a cost function that maps * each edge's data to a numeric weight. * - * @see {@link bellmanFord} for the algorithm that consumes this configuration - * @see {@link DijkstraConfig} for non-negative edge costs - * @see {@link AstarConfig} for heuristic shortest-path search - * - * @category models + * @category configuration * @since 3.18.0 */ export interface BellmanFordConfig { @@ -3779,16 +6858,27 @@ export interface BellmanFordConfig { * Finds the shortest path from the configured source node to the target node * using the Bellman-Ford algorithm. * + * **When to use** + * + * Use when one source-to-target shortest-path query may traverse negative-cost + * edges. + * * **Details** * - * Negative edge weights are allowed. Returns `Option.none()` when the target is - * unreachable or when a negative cycle affects the path to the target. Throws a - * `GraphError` when either endpoint is missing. + * Negative edge weights are allowed, and `Infinity` behaves like an impassable + * edge. Returns `Option.none()` when the target is unreachable. A reachable + * negative cycle only causes failure when it can affect the target. + * + * **Gotchas** + * + * Missing endpoints, unsupported weights, finite-range overflow, or a relevant + * negative cycle throw a `GraphError`. In an undirected graph, any reachable + * negative edge forms a negative cycle because it can be traversed both ways. * * **Example** (Finding shortest paths with Bellman-Ford) * - * ```ts - * import { Graph } from "effect" + * ```ts import.meta.vitest + * import { Graph, Option } from "effect" * * const graph = Graph.directed((mutable) => { * const a = Graph.addNode(mutable, "A") @@ -3805,12 +6895,10 @@ export interface BellmanFordConfig { * cost: (edgeData) => edgeData * }) * - * if (result._tag === "Some") { - * console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C - * console.log(result.value.distance) // 2 - total distance - * } + * Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([2, [0, 1, 2]]) * ``` * + * @see {@link dijkstra} for non-negative edge costs * @category algorithms * @since 3.18.0 */ @@ -3826,130 +6914,500 @@ export const bellmanFord: { graph: Graph | MutableGraph, config: BellmanFordConfig ): Option.Option> => { + const impl = internal.toImpl(graph) // Validate that source and target nodes exist - if (!graph.nodes.has(config.source)) { + if (!impl.nodes.has(config.source)) { throw missingNode(config.source) } - if (!graph.nodes.has(config.target)) { + if (!impl.nodes.has(config.target)) { throw missingNode(config.target) } - // Early return if source equals target - if (config.source === config.target) { - return Option.some({ - path: [config.source], - distance: 0, - costs: [] - }) + const cache = csr.get(graph) + const edges = csr.getEdges(cache) + const edgeIds = csr.getEdgeIds(cache) + const edgeCache = csr.getEdgeEndpoints(cache) + const outgoing = csr.getOutgoingWithEdges(cache) + const source = csr.getNodeIndex(cache, config.source)! + const target = csr.getNodeIndex(cache, config.target)! + const weights = new Float64Array(edges.length) + withMutationGuard(graph, () => { + for (let i = 0; i < edges.length; i++) { + const weight = config.cost(edges[i].data) + if (Number.isNaN(weight) || weight === -Infinity) { + throw new GraphError({ message: "Bellman-Ford algorithm does not support NaN or -Infinity edge weights" }) + } + weights[i] = weight + } + }) + + const addWeight = (distance: number, weight: number): number => { + if (distance === Infinity || weight === Infinity) { + return Infinity + } + const candidate = distance + weight + if (!Number.isFinite(candidate)) { + throw new GraphError({ message: "Bellman-Ford distance calculation exceeded the finite number range" }) + } + return candidate } - // Initialize distances and predecessors - const distances = new Map() - const previous = new Map() + const distances = new Float64Array(cache.nodeIds.length) + const previousNode = new Int32Array(cache.nodeIds.length) + const previousEdge = new Int32Array(cache.nodeIds.length) + distances.fill(Infinity) + previousNode.fill(-1) + previousEdge.fill(-1) + distances[source] = 0 - // Iterate directly over node keys - for (const node of graph.nodes.keys()) { - distances.set(node, node === config.source ? 0 : Infinity) - previous.set(node, null) + for (let iteration = 0; iteration < cache.nodeIds.length - 1; iteration++) { + let hasUpdate = false + for (let edge = 0; edge < edges.length; edge++) { + const edgeSource = edgeCache.sources[edge] + const edgeTarget = edgeCache.targets[edge] + const weight = weights[edge] + const sourceDistance = distances[edgeSource] + const candidate = addWeight(sourceDistance, weight) + if (candidate < distances[edgeTarget]) { + distances[edgeTarget] = candidate + previousNode[edgeTarget] = edgeSource + previousEdge[edgeTarget] = edge + hasUpdate = true + } + if (graph.type === "undirected" && edgeSource !== edgeTarget) { + const targetDistance = distances[edgeTarget] + const reverseCandidate = addWeight(targetDistance, weight) + if (reverseCandidate < distances[edgeSource]) { + distances[edgeSource] = reverseCandidate + previousNode[edgeSource] = edgeTarget + previousEdge[edgeSource] = edge + hasUpdate = true + } + } + } + if (!hasUpdate) { + break + } } - // Collect all edges for relaxation - const edges: Array<{ source: NodeIndex; target: NodeIndex; weight: number; edgeData: E }> = [] - for (const [, edgeData] of graph.edges) { - const weight = config.cost(edgeData.data) - edges.push({ - source: edgeData.source, - target: edgeData.target, - weight, - edgeData: edgeData.data - }) - if (graph.type === "undirected" && edgeData.source !== edgeData.target) { - edges.push({ - source: edgeData.target, - target: edgeData.source, - weight, - edgeData: edgeData.data - }) + // A relaxable edge after N-1 passes marks a reachable negative cycle; propagate to see if it reaches the target. + const affected = new Uint8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + let head = 0 + let tail = 0 + const markAffected = (node: number) => { + if (affected[node] === 0) { + affected[node] = 1 + queue[tail++] = node } } + for (let edge = 0; edge < edges.length; edge++) { + const edgeSource = edgeCache.sources[edge] + const edgeTarget = edgeCache.targets[edge] + const weight = weights[edge] + if (addWeight(distances[edgeSource], weight) < distances[edgeTarget]) { + markAffected(edgeTarget) + } + if ( + graph.type === "undirected" && + edgeSource !== edgeTarget && + addWeight(distances[edgeTarget], weight) < distances[edgeSource] + ) { + markAffected(edgeSource) + } + } + if (tail > 0) { + while (head < tail) { + const node = queue[head++] + for (let i = outgoing.rowOffsets[node]; i < outgoing.rowOffsets[node + 1]; i++) { + if (weights[outgoing.edgeIndices[i]] !== Infinity) { + markAffected(outgoing.columnIndices[i]) + } + } + } + } + if (affected[target] !== 0) { + throw new GraphError({ message: `Negative cycle affects path to node ${config.target}` }) + } + if (distances[target] === Infinity) { + return Option.none() + } - // Relax edges up to V-1 times - const nodeCount = graph.nodes.size - for (let i = 0; i < nodeCount - 1; i++) { - let hasUpdate = false + const path: Array = [] + const pathEdges: Array = [] + const costs: Array = [] + let current = target + let remaining = cache.nodeIds.length + while (current !== -1) { + if (remaining-- === 0) { + throw new GraphError({ message: `Negative cycle affects path to node ${config.target}` }) + } + path.push(cache.nodeIds[current]) + const edge = previousEdge[current] + if (edge !== -1) { + pathEdges.push(edgeIds[edge]) + costs.push(edges[edge].data) + } + current = previousNode[current] + } + path.reverse() + pathEdges.reverse() + costs.reverse() + return Option.some({ path, edges: pathEdges, distance: distances[target], costs }) +}) - for (const edge of edges) { - const sourceDistance = distances.get(edge.source)! - const targetDistance = distances.get(edge.target)! +/** + * A repeatable lazy iterable of edge-aware graph paths. + * + * **When to use** + * + * Use as the lazy result of graph path-enumeration functions. + * + * **Details** + * + * Each fresh iterator repeats the path enumeration. + * + * @category models + * @since 4.0.0 + */ +export interface PathWalker extends Iterable> {} - // Relaxation step - if (sourceDistance !== Infinity && sourceDistance + edge.weight < targetDistance) { - distances.set(edge.target, sourceDistance + edge.weight) - previous.set(edge.target, { node: edge.source, edgeData: edge.edgeData }) - hasUpdate = true +/** + * Configuration for lazy simple-path enumeration. + * + * **When to use** + * + * Use when bounding enumeration of loop-free routes between two nodes. + * + * **Details** + * + * `limit` bounds the number of yielded paths and defaults to `Infinity`. + * + * **Gotchas** + * + * `limit` must be a non-negative integer or `Infinity`. + * + * @category configuration + * @since 4.0.0 + */ +export interface SimplePathsConfig { + readonly source: NodeIndex + readonly target: NodeIndex + readonly limit?: number +} + +/** + * Configuration for enumerating all tied shortest paths. + * + * **When to use** + * + * Use when bounding enumeration of every route tied for minimum total cost. + * + * **Details** + * + * Edge costs must be non-negative. `limit` bounds the number of yielded paths + * and defaults to `Infinity`. + * + * **Gotchas** + * + * Invalid costs and limits throw a `GraphError` when evaluated. + * + * @category configuration + * @since 4.0.0 + */ +export interface AllShortestPathsConfig extends DijkstraConfig { + readonly limit?: number +} + +const pathEnumerationLimit = (limit: number | undefined): number => { + const value = limit ?? Infinity + if (value !== Infinity && (!Number.isInteger(value) || value < 0)) { + throw new GraphError({ message: "Path enumeration limit must be a non-negative integer or Infinity" }) + } + return value +} + +const pathWalker = (iterator: () => Iterator>): PathWalker => ({ + [Symbol.iterator]: iterator +}) + +/** + * Lazily enumerates simple source-to-target paths in depth-first edge order. + * + * **When to use** + * + * Use when you need possible loop-free routes rather than only an optimal + * route. + * + * **Details** + * + * Nodes are never repeated within a path, so enumeration is finite even for + * cyclic graphs. Path distance is the number of traversed edges. + * + * **Gotchas** + * + * The number of simple paths can be exponential. Missing endpoints or an + * invalid `limit` throw a `GraphError`. Mutable graphs are snapshotted when + * iteration begins. + * + * @see {@link allShortestPaths} for enumerating only minimum-cost routes + * + * @category algorithms + * @since 4.0.0 + */ +export const simplePaths: { + (config: SimplePathsConfig): ( + graph: Graph | MutableGraph + ) => PathWalker + ( + graph: Graph | MutableGraph, + config: SimplePathsConfig + ): PathWalker +} = dual(2, ( + graph: Graph | MutableGraph, + config: SimplePathsConfig +): PathWalker => { + const impl = internal.toImpl(graph) + if (!impl.nodes.has(config.source)) { + throw missingNode(config.source) + } + if (!impl.nodes.has(config.target)) { + throw missingNode(config.target) + } + const limit = pathEnumerationLimit(config.limit) + + return pathWalker(function*() { + const cache = csr.get(graph) + const source = csr.getNodeIndex(cache, config.source) + if (source === undefined) { + throw missingNode(config.source) + } + const target = csr.getNodeIndex(cache, config.target) + if (target === undefined) { + throw missingNode(config.target) + } + if (limit === 0) { + return + } + const outgoing = csr.getOutgoingWithEdges(cache) + const edgeIds = csr.getEdgeIds(cache) + const graphEdges = csr.getEdges(cache) + const path = [config.source] + const pathEdges: Array = [] + const costs: Array = [] + const visited = new Uint8Array(cache.nodeIds.length) + visited[source] = 1 + const stack: Array<{ readonly node: number; position: number }> = [{ + node: source, + position: outgoing.rowOffsets[source] + }] + let emitted = 0 + + const backtrack = () => { + const frame = stack.pop()! + if (stack.length > 0) { + visited[frame.node] = 0 + path.pop() + pathEdges.pop() + costs.pop() + } + } + + while (stack.length > 0 && emitted < limit) { + const frame = stack[stack.length - 1] + if (frame.node === target) { + emitted++ + yield { + path: Array.from(path), + edges: Array.from(pathEdges), + distance: pathEdges.length, + costs: Array.from(costs) + } + backtrack() + continue + } + if (frame.position >= outgoing.rowOffsets[frame.node + 1]) { + backtrack() + continue + } + const position = frame.position++ + const neighbor = outgoing.columnIndices[position] + if (visited[neighbor] !== 0) { + continue + } + const edge = outgoing.edgeIndices[position] + visited[neighbor] = 1 + path.push(cache.nodeIds[neighbor]) + pathEdges.push(edgeIds[edge]) + costs.push(graphEdges[edge].data) + stack.push({ node: neighbor, position: outgoing.rowOffsets[neighbor] }) + } + }) +}) + +/** + * Lazily enumerates all simple paths tied for minimum total cost. + * + * **When to use** + * + * Use when every distinct route tied for the minimum total cost is required. + * + * **Details** + * + * Parallel edges produce distinct paths. Edge costs must be non-negative; + * `Infinity` behaves as unavailable. + * + * **Gotchas** + * + * The number of tied paths can still be large. Missing endpoints, invalid + * costs, arithmetic overflow, or an invalid `limit` throw a `GraphError`. + * Mutable graphs are snapshotted when iteration begins. + * + * @see {@link dijkstra} when one shortest path is sufficient + * @see {@link simplePaths} for routes regardless of cost + * + * @category algorithms + * @since 4.0.0 + */ +export const allShortestPaths: { + (config: AllShortestPathsConfig): ( + graph: Graph | MutableGraph + ) => PathWalker + ( + graph: Graph | MutableGraph, + config: AllShortestPathsConfig + ): PathWalker +} = dual(2, ( + graph: Graph | MutableGraph, + config: AllShortestPathsConfig +): PathWalker => { + const impl = internal.toImpl(graph) + if (!impl.nodes.has(config.source)) { + throw missingNode(config.source) + } + if (!impl.nodes.has(config.target)) { + throw missingNode(config.target) + } + const limit = pathEnumerationLimit(config.limit) + + return pathWalker(function*() { + const cache = csr.get(graph) + const source = csr.getNodeIndex(cache, config.source) + if (source === undefined) { + throw missingNode(config.source) + } + const target = csr.getNodeIndex(cache, config.target) + if (target === undefined) { + throw missingNode(config.target) + } + const graphEdges = csr.getEdges(cache) + const edgeIds = csr.getEdgeIds(cache) + const outgoing = csr.getOutgoingWithEdges(cache) + const weights = new Float64Array(graphEdges.length) + withMutationGuard(graph, () => { + for (let edge = 0; edge < graphEdges.length; edge++) { + const weight = config.cost(graphEdges[edge].data) + if (Number.isNaN(weight) || weight < 0) { + throw new GraphError({ message: "All shortest paths requires non-negative edge weights" }) + } + weights[edge] = weight + } + }) + if (limit === 0) { + return + } + + const distances = new Float64Array(cache.nodeIds.length) + distances.fill(Infinity) + distances[source] = 0 + const previous: Array | undefined> = new Array( + cache.nodeIds.length + ) + const queue = denseMinHeapMake(cache.nodeIds.length) + let sequence = 0 + denseMinHeapPush(queue, source, 0, sequence++) + while (queue.size > 0) { + denseMinHeapPop(queue) + const currentNode = queue.poppedNode + const currentDistance = queue.poppedPriority + if (currentDistance !== distances[currentNode]) { + continue + } + for (let i = outgoing.rowOffsets[currentNode]; i < outgoing.rowOffsets[currentNode + 1]; i++) { + const edge = outgoing.edgeIndices[i] + const neighbor = outgoing.columnIndices[i] + const nextDistance = currentDistance + weights[edge] + if (weights[edge] !== Infinity && !Number.isFinite(nextDistance)) { + throw new GraphError({ message: "All shortest paths distance calculation exceeded the finite number range" }) + } + const known = distances[neighbor] + const predecessor = { node: currentNode, edge } + if (nextDistance < known) { + distances[neighbor] = nextDistance + previous[neighbor] = [predecessor] + denseMinHeapPush(queue, neighbor, nextDistance, sequence++) + } else if (nextDistance === known && nextDistance !== Infinity) { + const predecessors = previous[neighbor] + if (predecessors === undefined) { + previous[neighbor] = [predecessor] + } else { + predecessors.push(predecessor) + } + } } } - // Early termination if no updates - if (!hasUpdate) { - break + const distance = distances[target] + if (distance === Infinity) { + return + } + if (source === target) { + yield { path: [config.source], edges: [], distance: 0, costs: [] } + return + } + const reversePath = [target] + const reverseEdges: Array = [] + const visited = new Uint8Array(cache.nodeIds.length) + visited[target] = 1 + const stack: Array<{ readonly node: number; position: number }> = [{ node: target, position: 0 }] + let emitted = 0 + + const backtrack = () => { + const frame = stack.pop()! + if (stack.length > 0) { + visited[frame.node] = 0 + reversePath.pop() + reverseEdges.pop() + } } - } - - // Check for negative cycles - for (const edge of edges) { - const sourceDistance = distances.get(edge.source)! - const targetDistance = distances.get(edge.target)! - - if (sourceDistance !== Infinity && sourceDistance + edge.weight < targetDistance) { - // Negative cycle detected - check if it affects the path to target - const affectedNodes = new Set() - const queue = [edge.target] - - while (queue.length > 0) { - const node = queue.shift()! - if (affectedNodes.has(node)) continue - affectedNodes.add(node) - // Add all nodes reachable from this node - for (const neighbor of getTraversalNeighbors(graph, node, "outgoing")) { - queue.push(neighbor) + while (stack.length > 0 && emitted < limit) { + const frame = stack[stack.length - 1] + if (frame.node === source) { + emitted++ + yield { + path: reversePath.map((node) => cache.nodeIds[node]).reverse(), + edges: reverseEdges.map((edge) => edgeIds[edge]).reverse(), + distance, + costs: reverseEdges.map((edge) => graphEdges[edge].data as E).reverse() } + backtrack() + continue } - - // If target is affected by a negative cycle, no shortest path exists. - if (affectedNodes.has(config.target)) { - return Option.none() + const predecessors = previous[frame.node] ?? [] + if (frame.position >= predecessors.length) { + backtrack() + continue } + const predecessor = predecessors[frame.position++] + if (visited[predecessor.node] !== 0) { + continue + } + visited[predecessor.node] = 1 + reversePath.push(predecessor.node) + reverseEdges.push(predecessor.edge) + stack.push({ node: predecessor.node, position: 0 }) } - } - - // Check if target is reachable - const distance = distances.get(config.target)! - if (distance === Infinity) { - return Option.none() // No path exists - } - - // Reconstruct path - const path: Array = [] - const costs: Array = [] - let currentNode: NodeIndex | null = config.target - - while (currentNode !== null) { - path.unshift(currentNode) - const prev: { node: NodeIndex; edgeData: E } | null = previous.get(currentNode)! - if (prev !== null) { - costs.unshift(prev.edgeData) - currentNode = prev.node - } else { - currentNode = null - } - } - - return Option.some({ - path, - distance, - costs }) }) @@ -3964,7 +7422,7 @@ export const bellmanFord: { * * **Example** (Working with node walkers) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -3983,8 +7441,8 @@ export const bellmanFord: { * } * * // Access node data using values() or entries() - * const nodeData = Array.from(Graph.values(dfsNodes)) // ["A", "B"] - * const nodeEntries = Array.from(Graph.entries(allNodes)) // [[0, "A"], [1, "B"]] + * Array.from(Graph.values(dfsNodes)) // => ["A", "B"] + * Array.from(Graph.entries(allNodes)) // => [[0, "A"], [1, "B"]] * ``` * * @category models @@ -3995,17 +7453,16 @@ export class Walker implements Iterable<[T, N]> { readonly [Symbol.iterator]: () => Iterator<[T, N]> /** - * Visits each element and maps it to a value using the provided function. + * Lazily maps each walker entry with the provided function. * * **Details** * - * Takes a function that receives the index and data, - * and returns an iterable of the mapped values. Skips elements that - * no longer exist in the graph. + * The function receives the index and data and runs as the returned iterable + * is consumed. * * **Example** (Visiting walker elements) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4017,14 +7474,10 @@ export class Walker implements Iterable<[T, N]> { * const dfs = Graph.dfs(graph, { start: [0] }) * * // Map to just the node data - * const values = Array.from(dfs.visit((index, data) => data)) - * console.log(values) // ["A", "B"] + * Array.from(dfs.visit((index, data) => data)) // => ["A", "B"] * * // Map to custom objects - * const custom = Array.from( - * dfs.visit((index, data) => ({ id: index, name: data })) - * ) - * console.log(custom) // [{ id: 0, name: "A" }, { id: 1, name: "B" }] + * Array.from(dfs.visit((index, data) => ({ id: index, name: data }))) // => [{ id: 0, name: "A" }, { id: 1, name: "B" }] * ``` * * @since 4.0.0 @@ -4032,45 +7485,49 @@ export class Walker implements Iterable<[T, N]> { readonly visit: (f: (index: T, data: N) => U) => Iterable constructor( - /** - * Visits each element and maps it to a value using the provided function. - * - * Takes a function that receives the index and data, - * and returns an iterable of the mapped values. Skips elements that - * no longer exist in the graph. - * - * **Example** (Visiting walker elements) - * - * ```ts - * import { Graph } from "effect" - * - * const graph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * Graph.addEdge(mutable, a, b, 1) - * }) - * - * const dfs = Graph.dfs(graph, { start: [0] }) - * - * // Map to just the node data - * const values = Array.from(dfs.visit((index, data) => data)) - * console.log(values) // ["A", "B"] - * - * // Map to custom objects - * const custom = Array.from( - * dfs.visit((index, data) => ({ id: index, name: data })) - * ) - * console.log(custom) // [{ id: 0, name: "A" }, { id: 1, name: "B" }] - * ``` - * - * @category iterators - * @since 4.0.0 - */ visit: (f: (index: T, data: N) => U) => Iterable ) { this.visit = visit - this[Symbol.iterator] = visit((index, data) => [index, data] as [T, N])[Symbol.iterator] + this[Symbol.iterator] = () => visit((index, data) => [index, data] as [T, N])[Symbol.iterator]() + } +} + +const makeCsrNodeWalker = ( + graph: Graph | MutableGraph, + makeIterator: (cache: csr.Csr, f: (index: NodeIndex, data: N) => U) => Iterator +): Walker => { + return new Walker((f) => ({ + // Capture CSR at iterator creation so invalidation cannot change an in-flight mutable traversal. + [Symbol.iterator]: () => + makeIterator(csr.get(graph), (index, data) => withMutationGuard(graph, () => f(index, data))) + })) +} + +const traversalStarts = ( + graph: Graph | MutableGraph, + start: ReadonlyArray | undefined +): Array => { + if (start === undefined) { + return [] + } + for (const nodeIndex of start) { + if (!hasNode(graph, nodeIndex)) { + throw missingNode(nodeIndex) + } } + return Array.from(start) +} + +const traversalStartPositions = (cache: csr.Csr, start: ReadonlyArray): Array => { + const positions = new Array(start.length) + for (let i = 0; i < start.length; i++) { + const position = csr.getNodeIndex(cache, start[i]) + if (position === undefined) { + throw missingNode(start[i]) + } + positions[i] = position + } + return positions } /** @@ -4114,7 +7571,7 @@ export type EdgeWalker = Walker> * * **Example** (Iterating walker indices) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4124,8 +7581,7 @@ export type EdgeWalker = Walker> * }) * * const dfs = Graph.dfs(graph, { start: [0] }) - * const indices = Array.from(Graph.indices(dfs)) - * console.log(indices) // [0, 1] + * Array.from(Graph.indices(dfs)) // => [0, 1] * ``` * * @category iterators @@ -4138,7 +7594,7 @@ export const indices = (walker: Walker): Iterable => walker.visit * * **Example** (Iterating walker values) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4148,8 +7604,7 @@ export const indices = (walker: Walker): Iterable => walker.visit * }) * * const dfs = Graph.dfs(graph, { start: [0] }) - * const values = Array.from(Graph.values(dfs)) - * console.log(values) // ["A", "B"] + * Array.from(Graph.values(dfs)) // => ["A", "B"] * ``` * * @category iterators @@ -4162,7 +7617,7 @@ export const values = (walker: Walker): Iterable => walker.visit( * * **Example** (Iterating walker entries) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4172,8 +7627,7 @@ export const values = (walker: Walker): Iterable => walker.visit( * }) * * const dfs = Graph.dfs(graph, { start: [0] }) - * const entries = Array.from(Graph.entries(dfs)) - * console.log(entries) // [[0, "A"], [1, "B"]] + * Array.from(Graph.entries(dfs)) // => [[0, "A"], [1, "B"]] * ``` * * @category iterators @@ -4193,39 +7647,52 @@ export const entries = (walker: Walker): Iterable<[T, N]> => * **Details** * * `start` supplies the node indices where traversal begins. If it is omitted, - * the iterator is empty. `direction` chooses whether traversal follows - * outgoing or incoming edges. + * the iterator is empty. Distinct starts are prioritized in supplied order and + * duplicates are ignored. `direction` chooses whether traversal follows outgoing + * edges, incoming edges, or ignores edge direction. `radius` limits traversal + * by edge distance from the nearest start node and accepts non-negative integers + * or `Infinity`; omitting it means unbounded traversal. * * **Gotchas** * - * Traversal creation throws a `GraphError` when any configured `start` node - * does not exist. + * Traversal creation validates and copies `start`, and throws a `GraphError` + * when a start node does not exist or `radius` is invalid. Each fresh iterator + * revalidates those starts against the graph snapshot it captures. Later + * mutations are not observed by an active iterator. * - * @see {@link dfs} for depth-first traversal - * @see {@link bfs} for breadth-first traversal - * @see {@link dfsPostOrder} for depth-first postorder traversal - * - * @category models + * @category configuration * @since 3.18.0 */ export interface SearchConfig { readonly start?: Array - readonly direction?: Direction + readonly direction?: TraversalDirection + readonly radius?: number } /** * Creates a lazy depth-first traversal iterator from the configured start * nodes. * + * **When to use** + * + * Use when exploring one branch deeply before visiting sibling branches. + * * **Details** * * If no start nodes are supplied, the iterator is empty. The `direction` option - * chooses whether to follow outgoing or incoming edges. Throws a `GraphError` - * if any configured start node does not exist. + * chooses whether to follow outgoing or incoming edges. The `radius` option + * limits traversal by edge distance from the start nodes. It accepts + * non-negative integers and `Infinity`; omitting it means unbounded traversal. + * + * **Gotchas** + * + * An invalid radius or missing start node throws a `GraphError`. Traversing a + * mutable graph captures a snapshot when iteration begins; later mutations are + * not observed by that iterator. * * **Example** (Traversing depth-first) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4237,16 +7704,13 @@ export interface SearchConfig { * }) * * // Start from a specific node - * const dfs1 = Graph.dfs(graph, { start: [0] }) - * for (const nodeIndex of Graph.indices(dfs1)) { - * console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2 - * } + * Array.from(Graph.indices(Graph.dfs(graph, { start: [0] }))) // => [0, 1, 2] * - * // Empty iterator (no starting nodes) - * const dfs2 = Graph.dfs(graph) - * // Can be used programmatically + * Array.from(Graph.indices(Graph.dfs(graph))) // => [] * ``` * + * @see {@link bfs} for traversal in increasing hop distance + * @see {@link dfsPostOrder} for emitting descendants before ancestors * @category iterators * @since 3.18.0 */ @@ -4262,68 +7726,150 @@ export const dfs: { graph: Graph | MutableGraph, config: SearchConfig = {} ): NodeWalker => { - const start = config.start ?? [] + const radius = traversalRadius(config.radius, Infinity) + const start = traversalStarts(graph, config.start) const direction = config.direction ?? "outgoing" - // Validate that all start nodes exist - for (const nodeIndex of start) { - if (!hasNode(graph, nodeIndex)) { - throw missingNode(nodeIndex) - } - } + return makeCsrNodeWalker(graph, (cache, f) => { + const startPositions = traversalStartPositions(cache, start) + const view = csr.getAdjacencies(cache, direction) + const yielded = new Uint8Array(cache.nodeIds.length) + const stack: Array = [] - return new Walker((f) => ({ - [Symbol.iterator]: () => { - const stack = [...start] - const discovered = new Set() + if (radius === Infinity) { + // Reverse row order before pushing so LIFO traversal observes canonical adjacency order. + for (let i = startPositions.length - 1; i >= 0; i--) { + stack.push(startPositions[i]) + } - const nextMapped = () => { - while (stack.length > 0) { - const current = stack.pop()! + const pushNeighbors = (targets: Uint32Array, offsets: Uint32Array, current: number) => { + for (let i = offsets[current + 1] - 1; i >= offsets[current]; i--) { + const neighbor = targets[i] + if (yielded[neighbor] === 0) { + stack.push(neighbor) + } + } + } - if (discovered.has(current)) { - continue + return { + next() { + while (stack.length > 0) { + const current = stack.pop()! + if (yielded[current] !== 0) { + continue + } + + if (view.secondary !== undefined) { + pushNeighbors(view.secondary.columnIndices, view.secondary.rowOffsets, current) + } + pushNeighbors(view.primary.columnIndices, view.primary.rowOffsets, current) + yielded[current] = 1 + + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } } - discovered.add(current) + return { done: true, value: undefined } as const + } + } + } + + // Radius is shortest edge distance, so determine membership with BFS before imposing DFS order. + const reached = new Uint8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + const depths = new Uint32Array(cache.nodeIds.length) + let head = 0 + let tail = 0 + + for (const position of startPositions) { + if (reached[position] === 0) { + reached[position] = 1 + queue[tail++] = position + } + } + + const enqueue = (targets: Uint32Array, offsets: Uint32Array, current: number, depth: number) => { + for (let i = offsets[current]; i < offsets[current + 1]; i++) { + const neighbor = targets[i] + if (reached[neighbor] === 0) { + reached[neighbor] = 1 + queue[tail] = neighbor + depths[tail++] = depth + 1 + } + } + } + + while (head < tail) { + const current = queue[head] + const depth = depths[head++] + if (depth < radius) { + enqueue(view.primary.columnIndices, view.primary.rowOffsets, current, depth) + if (view.secondary !== undefined) { + enqueue(view.secondary.columnIndices, view.secondary.rowOffsets, current, depth) + } + } + } + + for (let i = startPositions.length - 1; i >= 0; i--) { + stack.push(startPositions[i]) + } + + const pushNeighbors = (targets: Uint32Array, offsets: Uint32Array, current: number) => { + for (let i = offsets[current + 1] - 1; i >= offsets[current]; i--) { + const neighbor = targets[i] + if (reached[neighbor] !== 0 && yielded[neighbor] === 0) { + stack.push(neighbor) + } + } + } - const nodeDataOption = getNode(graph, current) - if (Option.isNone(nodeDataOption)) { + return { + next() { + while (stack.length > 0) { + const current = stack.pop()! + if (yielded[current] !== 0) { continue } - const neighbors = getTraversalNeighbors(graph, current, direction) - for (let i = neighbors.length - 1; i >= 0; i--) { - const neighbor = neighbors[i] - if (!discovered.has(neighbor)) { - stack.push(neighbor) - } + if (view.secondary !== undefined) { + pushNeighbors(view.secondary.columnIndices, view.secondary.rowOffsets, current) } + pushNeighbors(view.primary.columnIndices, view.primary.rowOffsets, current) + yielded[current] = 1 - return { done: false, value: f(current, nodeDataOption.value) } + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } } return { done: true, value: undefined } as const } - - return { next: nextMapped } } - })) + }) }) /** * Creates a lazy breadth-first traversal iterator from the configured start * nodes. * + * **When to use** + * + * Use when visiting nodes in increasing unweighted distance from the start + * nodes. + * * **Details** * * If no start nodes are supplied, the iterator is empty. The `direction` option - * chooses whether to follow outgoing or incoming edges. Throws a `GraphError` - * if any configured start node does not exist. + * chooses whether to follow outgoing or incoming edges. The `radius` option + * limits traversal by edge distance from the start nodes. It accepts + * non-negative integers and `Infinity`; omitting it means unbounded traversal. + * + * **Gotchas** + * + * An invalid radius or missing start node throws a `GraphError`. Traversing a + * mutable graph captures a snapshot when iteration begins; later mutations are + * not observed by that iterator. * * **Example** (Traversing breadth-first) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4335,16 +7881,13 @@ export const dfs: { * }) * * // Start from a specific node - * const bfs1 = Graph.bfs(graph, { start: [0] }) - * for (const nodeIndex of Graph.indices(bfs1)) { - * console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2 - * } + * Array.from(Graph.indices(Graph.bfs(graph, { start: [0] }))) // => [0, 1, 2] * - * // Empty iterator (no starting nodes) - * const bfs2 = Graph.bfs(graph) - * // Can be used programmatically + * Array.from(Graph.indices(Graph.bfs(graph))) // => [] * ``` * + * @see {@link dfs} for branch-first traversal + * @see {@link unweightedDistances} for collecting hop counts * @category iterators * @since 3.18.0 */ @@ -4360,49 +7903,100 @@ export const bfs: { graph: Graph | MutableGraph, config: SearchConfig = {} ): NodeWalker => { - const start = config.start ?? [] + const radius = traversalRadius(config.radius, Infinity) + const start = traversalStarts(graph, config.start) const direction = config.direction ?? "outgoing" - // Validate that all start nodes exist - for (const nodeIndex of start) { - if (!hasNode(graph, nodeIndex)) { - throw missingNode(nodeIndex) + return makeCsrNodeWalker(graph, (cache, f) => { + const startPositions = traversalStartPositions(cache, start) + const view = csr.getAdjacencies(cache, direction) + const discovered = new Uint8Array(cache.nodeIds.length) + // Each compact node enters the queue once, so a fixed-size typed array is sufficient. + const queue = new Uint32Array(cache.nodeIds.length) + let head = 0 + let tail = 0 + + for (const position of startPositions) { + if (discovered[position] === 0) { + discovered[position] = 1 + queue[tail++] = position + } } - } - return new Walker((f) => ({ - [Symbol.iterator]: () => { - const queue = [...start] - const discovered = new Set() + const enqueue = (targets: Uint32Array, from: number, to: number) => { + for (let i = from; i < to; i++) { + const neighbor = targets[i] + if (discovered[neighbor] === 0) { + discovered[neighbor] = 1 + queue[tail++] = neighbor + } + } + } - const nextMapped = () => { - while (queue.length > 0) { - const current = queue.shift()! + if (radius === Infinity) { + return { + next() { + if (head >= tail) { + return { done: true, value: undefined } as const + } - if (!discovered.has(current)) { - discovered.add(current) + const current = queue[head++] + enqueue(view.primary.columnIndices, view.primary.rowOffsets[current], view.primary.rowOffsets[current + 1]) + if (view.secondary !== undefined) { + enqueue( + view.secondary.columnIndices, + view.secondary.rowOffsets[current], + view.secondary.rowOffsets[current + 1] + ) + } - const neighbors = getTraversalNeighbors(graph, current, direction) - for (const neighbor of neighbors) { - if (!discovered.has(neighbor)) { - queue.push(neighbor) - } - } + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } + } + } + } - const nodeData = getNode(graph, current) - if (Option.isSome(nodeData)) { - return { done: false, value: f(current, nodeData.value) } - } - return nextMapped() + const depths = new Uint32Array(cache.nodeIds.length) + const enqueueBounded = (targets: Uint32Array, from: number, to: number, depth: number) => { + for (let i = from; i < to; i++) { + const neighbor = targets[i] + if (discovered[neighbor] === 0) { + discovered[neighbor] = 1 + queue[tail] = neighbor + depths[tail++] = depth + 1 + } + } + } + + return { + next() { + if (head >= tail) { + return { done: true, value: undefined } as const + } + + const current = queue[head] + const depth = depths[head++] + + if (depth < radius) { + enqueueBounded( + view.primary.columnIndices, + view.primary.rowOffsets[current], + view.primary.rowOffsets[current + 1], + depth + ) + if (view.secondary !== undefined) { + enqueueBounded( + view.secondary.columnIndices, + view.secondary.rowOffsets[current], + view.secondary.rowOffsets[current + 1], + depth + ) } } - return { done: true, value: undefined } as const + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } } - - return { next: nextMapped } } - })) + }) }) /** @@ -4422,9 +8016,7 @@ export const bfs: { * * Throws a `GraphError` when any initial node has incoming edges. * - * @see {@link topo} for the iterator that consumes this configuration - * - * @category models + * @category configuration * @since 3.18.0 */ export interface TopoConfig { @@ -4432,16 +8024,28 @@ export interface TopoConfig { } /** - * Creates a new topological sort iterator with optional configuration. + * Creates a lazy topological-order iterator for a directed acyclic graph. + * + * **When to use** + * + * Use when processing dependencies so every predecessor is emitted before the + * nodes that depend on it. * * **Details** * - * The iterator uses Kahn's algorithm to lazily produce nodes in topological order. - * Throws an error if the graph contains cycles. + * The iterator uses Kahn's algorithm. Multiple valid orders may exist; + * `initials` prioritizes eligible zero in-degree nodes without excluding other + * nodes. + * + * **Gotchas** + * + * Undirected or cyclic graphs, missing initial nodes, and initial nodes with + * incoming edges throw a `GraphError`. Traversing a mutable graph captures a + * snapshot when iteration begins; later mutations are not observed. * * **Example** (Sorting topologically) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4452,36 +8056,21 @@ export interface TopoConfig { * Graph.addEdge(mutable, b, c, 1) * }) * - * // Standard topological sort - * const topo1 = Graph.topo(graph) - * for (const nodeIndex of Graph.indices(topo1)) { - * console.log(nodeIndex) // 0, 1, 2 (topological order) - * } - * - * // With initial nodes - * const topo2 = Graph.topo(graph, { initials: [0] }) - * - * // Check before sorting a cyclic graph - * const cyclicGraph = Graph.directed((mutable) => { - * const a = Graph.addNode(mutable, "A") - * const b = Graph.addNode(mutable, "B") - * Graph.addEdge(mutable, a, b, 1) - * Graph.addEdge(mutable, b, a, 2) // Creates cycle - * }) - * - * if (!Graph.isAcyclic(cyclicGraph)) { - * console.log("cyclic graph") // cyclic graph - * } + * Array.from(Graph.indices(Graph.topo(graph))) // => [0, 1, 2] * ``` * + * @see {@link isAcyclic} for checking the required graph property * @category iterators * @since 3.18.0 */ export const topo: { ( config?: TopoConfig - ): (graph: Graph | MutableGraph) => NodeWalker - (graph: Graph | MutableGraph, config?: TopoConfig): NodeWalker + ): (graph: Graph | MutableGraph) => NodeWalker + ( + graph: Graph | MutableGraph, + config?: TopoConfig + ): NodeWalker } = dual((args) => isGraph(args[0]), ( graph: Graph | MutableGraph, config: TopoConfig = {} @@ -4495,7 +8084,7 @@ export const topo: { throw new GraphError({ message: "Cannot perform topological sort on cyclic graph" }) } - const initials = config.initials ?? [] + const initials = Array.from(config.initials ?? []) // Validate that all initial nodes exist for (const nodeIndex of initials) { @@ -4504,93 +8093,98 @@ export const topo: { } } - return new Walker((f) => ({ - [Symbol.iterator]: () => { - const inDegree = new Map() - const remaining = new Set() - const initialSet = new Set(initials) - const queue = [...initials] - - // Initialize in-degree counts - for (const [nodeIndex] of graph.nodes) { - inDegree.set(nodeIndex, 0) - remaining.add(nodeIndex) - } - - // Calculate in-degrees - for (const [, edgeData] of graph.edges) { - const currentInDegree = inDegree.get(edgeData.target) || 0 - inDegree.set(edgeData.target, currentInDegree + 1) + return makeCsrNodeWalker(graph, (cache, f) => { + const outgoing = csr.getOutgoing(cache) + const incoming = csr.getIncoming(cache) + // CSR row lengths are the initial in-degrees used by Kahn's algorithm. + const inDegree = new Uint32Array(cache.nodeIds.length) + const remaining = new Uint8Array(cache.nodeIds.length) + const initialSet = new Uint8Array(cache.nodeIds.length) + const queue: Array = [] + let remainingCount = cache.nodeIds.length + let head = 0 + remaining.fill(1) + + for (let node = 0; node < cache.nodeIds.length; node++) { + inDegree[node] = incoming.rowOffsets[node + 1] - incoming.rowOffsets[node] + } + for (const initial of initials) { + const node = csr.getNodeIndex(cache, initial) + if (node === undefined) { + throw missingNode(initial) } - - for (const nodeIndex of initials) { - if (inDegree.get(nodeIndex)! !== 0) { - throw new GraphError({ message: `Initial node ${nodeIndex} has incoming edges` }) - } + if (inDegree[node] !== 0) { + throw new GraphError({ message: `Initial node ${initial} has incoming edges` }) } - - // Add remaining zero in-degree nodes after prioritized initials. - for (const [nodeIndex, degree] of inDegree) { - if (degree === 0 && !initialSet.has(nodeIndex)) { - queue.push(nodeIndex) - } + initialSet[node] = 1 + queue.push(node) + } + for (let node = 0; node < cache.nodeIds.length; node++) { + if (inDegree[node] === 0 && initialSet[node] === 0) { + queue.push(node) } + } - const nextMapped = () => { - while (queue.length > 0) { - const current = queue.shift()! - - if (remaining.has(current)) { - remaining.delete(current) - - // Process outgoing edges, reducing in-degree of targets - const neighbors = getDirectedNeighbors( - graph as Graph | MutableGraph, - current, - "outgoing" - ) - for (const neighbor of neighbors) { - if (remaining.has(neighbor)) { - const currentInDegree = inDegree.get(neighbor) || 0 - const newInDegree = currentInDegree - 1 - inDegree.set(neighbor, newInDegree) - - // If in-degree becomes 0, add to queue - if (newInDegree === 0) { - queue.push(neighbor) - } + return { + next() { + while (head < queue.length) { + const current = queue[head++] + if (remaining[current] === 0) { + continue + } + remaining[current] = 0 + remainingCount-- + + for (let i = outgoing.rowOffsets[current]; i < outgoing.rowOffsets[current + 1]; i++) { + const neighbor = outgoing.columnIndices[i] + if (remaining[neighbor] !== 0) { + const degree = --inDegree[neighbor] + if (degree === 0) { + queue.push(neighbor) } } - - const nodeData = getNode(graph, current) - if (Option.isSome(nodeData)) { - return { done: false, value: f(current, nodeData.value) } - } - return nextMapped() } + + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } } + if (remainingCount > 0) { + throw new GraphError({ message: "Cannot perform topological sort on cyclic graph" }) + } return { done: true, value: undefined } as const } - - return { next: nextMapped } } - })) + }) }) /** * Creates a lazy depth-first postorder traversal iterator from the configured * start nodes. * + * **When to use** + * + * Use when reachable descendants must be emitted before the nodes that lead to + * them. + * * **Details** * * Nodes are emitted after their reachable descendants have been processed. If * no start nodes are supplied, the iterator is empty. The `direction` option - * chooses whether to follow outgoing or incoming edges. + * chooses whether to follow outgoing or incoming edges. The `radius` option + * limits traversal by edge distance from the start nodes. It accepts + * non-negative integers and `Infinity`; omitting it means unbounded traversal. + * With a finite `radius`, a bounded breadth-first pass first determines + * shortest-distance membership before nodes are emitted in postorder. + * + * **Gotchas** + * + * Invalid radii and missing start nodes throw a `GraphError`. Traversing a + * mutable graph captures a snapshot when iteration begins; later mutations are + * not observed by that iterator. * * **Example** (Traversing in postorder) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4602,12 +8196,10 @@ export const topo: { * }) * * // Postorder: children before parents - * const postOrder = Graph.dfsPostOrder(graph, { start: [0] }) - * for (const node of postOrder) { - * console.log(node) // 1, 2, 0 - * } + * Array.from(Graph.indices(Graph.dfsPostOrder(graph, { start: [0] }))) // => [1, 2, 0] * ``` * + * @see {@link dfs} for emitting nodes when first visited * @category iterators * @since 3.18.0 */ @@ -4623,80 +8215,121 @@ export const dfsPostOrder: { graph: Graph | MutableGraph, config: SearchConfig = {} ): NodeWalker => { - const start = config.start ?? [] + const radius = traversalRadius(config.radius, Infinity) + const start = traversalStarts(graph, config.start) const direction = config.direction ?? "outgoing" - // Validate that all start nodes exist - for (const nodeIndex of start) { - if (!hasNode(graph, nodeIndex)) { - throw missingNode(nodeIndex) - } - } - - return new Walker((f) => ({ - [Symbol.iterator]: () => { - const stack: Array<{ node: NodeIndex; visitedChildren: boolean }> = [] - const discovered = new Set() - const finished = new Set() - - // Initialize stack with start nodes - for (let i = start.length - 1; i >= 0; i--) { - stack.push({ node: start[i], visitedChildren: false }) + return makeCsrNodeWalker(graph, (cache, f) => { + const startPositions = traversalStartPositions(cache, start) + const view = csr.getAdjacencies(cache, direction) + let reached: Uint8Array | undefined + if (radius !== Infinity) { + // Radius is shortest edge distance, so determine membership with BFS before imposing postorder. + const boundedReached = new Uint8Array(cache.nodeIds.length) + const queue = new Uint32Array(cache.nodeIds.length) + const depths = new Uint32Array(cache.nodeIds.length) + let head = 0 + let tail = 0 + + for (const position of startPositions) { + if (boundedReached[position] === 0) { + boundedReached[position] = 1 + queue[tail++] = position + } } - const nextMapped = () => { - while (stack.length > 0) { - const current = stack[stack.length - 1] - - if (!discovered.has(current.node)) { - discovered.add(current.node) - current.visitedChildren = false + const enqueue = (targets: Uint32Array, offsets: Uint32Array, current: number, depth: number) => { + for (let i = offsets[current]; i < offsets[current + 1]; i++) { + const neighbor = targets[i] + if (boundedReached[neighbor] === 0) { + boundedReached[neighbor] = 1 + queue[tail] = neighbor + depths[tail++] = depth + 1 } + } + } - if (!current.visitedChildren) { - current.visitedChildren = true - const neighbors = getTraversalNeighbors(graph, current.node, direction) + while (head < tail) { + const current = queue[head] + const depth = depths[head++] + if (depth < radius) { + enqueue(view.primary.columnIndices, view.primary.rowOffsets, current, depth) + if (view.secondary !== undefined) { + enqueue(view.secondary.columnIndices, view.secondary.rowOffsets, current, depth) + } + } + } + reached = boundedReached + } - for (let i = neighbors.length - 1; i >= 0; i--) { - const neighbor = neighbors[i] - if (!discovered.has(neighbor) && !finished.has(neighbor)) { - stack.push({ node: neighbor, visitedChildren: false }) - } - } - } else { - const nodeToEmit = stack.pop()!.node + const stack: Array = [] + const primaryPositions: Array = [] + const secondaryPositions: Array = [] + const discovered = new Uint8Array(cache.nodeIds.length) + + const push = (node: number) => { + if ((reached === undefined || reached[node] !== 0) && discovered[node] === 0) { + discovered[node] = 1 + stack.push(node) + primaryPositions.push(view.primary.rowOffsets[node]) + secondaryPositions.push(view.secondary?.rowOffsets[node] ?? 0) + } + } - if (!finished.has(nodeToEmit)) { - finished.add(nodeToEmit) + let startPosition = 0 - const nodeData = getNode(graph, nodeToEmit) - if (Option.isSome(nodeData)) { - return { done: false, value: f(nodeToEmit, nodeData.value) } - } - return nextMapped() + return { + next() { + while (true) { + while (stack.length === 0 && startPosition < startPositions.length) { + push(startPositions[startPosition++]) + } + if (stack.length === 0) { + return { done: true, value: undefined } as const + } + const index = stack.length - 1 + const current = stack[index] + const primaryPosition = primaryPositions[index] + if (primaryPosition < view.primary.rowOffsets[current + 1]) { + primaryPositions[index] = primaryPosition + 1 + push(view.primary.columnIndices[primaryPosition]) + continue + } + if (view.secondary !== undefined) { + const secondaryPosition = secondaryPositions[index] + if (secondaryPosition < view.secondary.rowOffsets[current + 1]) { + secondaryPositions[index] = secondaryPosition + 1 + push(view.secondary.columnIndices[secondaryPosition]) + continue } } - } - return { done: true, value: undefined } as const + stack.pop() + primaryPositions.pop() + secondaryPositions.pop() + return { done: false, value: f(cache.nodeIds[current], cache.nodeData[current] as N) } + } } - - return { next: nextMapped } } - })) + }) }) /** - * Creates an iterator over all node indices in the graph. + * Creates a walker over all node index and payload entries in the graph. * * **Details** * - * The iterator produces node indices in the order they were added to the graph. - * This provides access to all nodes regardless of connectivity. + * Entries follow graph node order and include all nodes regardless of + * connectivity. Use `indices` or `values` to project one side of each entry. + * + * **Gotchas** + * + * Mutable graphs are not snapshotted; mutations may affect the remaining + * iteration. * * **Example** (Iterating all nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4706,8 +8339,7 @@ export const dfsPostOrder: { * Graph.addEdge(mutable, a, b, 1) * }) * - * const indices = Array.from(Graph.indices(Graph.nodes(graph))) - * console.log(indices) // [0, 1, 2] + * Array.from(Graph.indices(Graph.nodes(graph))) // => [0, 1, 2] * ``` * * @category iterators @@ -4718,7 +8350,7 @@ export const nodes = ( ): NodeWalker => new Walker((f) => ({ [Symbol.iterator]() { - const nodeMap = graph.nodes + const nodeMap = internal.toImpl(graph).nodes const iterator = nodeMap.entries() return { @@ -4728,23 +8360,28 @@ export const nodes = ( return { done: true, value: undefined } } const [nodeIndex, nodeData] = result.value - return { done: false, value: f(nodeIndex, nodeData) } + return { done: false, value: withMutationGuard(graph, () => f(nodeIndex, nodeData)) } } } } })) /** - * Creates an iterator over all edge indices in the graph. + * Creates a walker over all edge index and edge entries in the graph. * * **Details** * - * The iterator produces edge indices in the order they were added to the graph. - * This provides access to all edges regardless of connectivity. + * Entries follow graph edge order and include all edges regardless of + * connectivity. Use `indices` or `values` to project one side of each entry. + * + * **Gotchas** + * + * Mutable graphs are not snapshotted; mutations may affect the remaining + * iteration. * * **Example** (Iterating all edges) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4755,8 +8392,7 @@ export const nodes = ( * Graph.addEdge(mutable, b, c, 2) * }) * - * const indices = Array.from(Graph.indices(Graph.edges(graph))) - * console.log(indices) // [0, 1] + * Array.from(Graph.indices(Graph.edges(graph))) // => [0, 1] * ``` * * @category iterators @@ -4767,7 +8403,7 @@ export const edges = ( ): EdgeWalker => new Walker((f) => ({ [Symbol.iterator]() { - const edgeMap = graph.edges + const edgeMap = internal.toImpl(graph).edges const iterator = edgeMap.entries() return { @@ -4777,7 +8413,7 @@ export const edges = ( return { done: true, value: undefined } } const [edgeIndex, edgeData] = result.value - return { done: false, value: f(edgeIndex, edgeData) } + return { done: false, value: withMutationGuard(graph, () => f(edgeIndex, copyEdge(edgeData))) } } } } @@ -4798,9 +8434,7 @@ export const edges = ( * nodes with no incoming edges. If omitted, `direction` defaults to * `"outgoing"`. * - * @see {@link externals} for the iterator that consumes this configuration - * - * @category models + * @category configuration * @since 3.18.0 */ export interface ExternalsConfig { @@ -4810,15 +8444,24 @@ export interface ExternalsConfig { /** * Creates an iterator over external nodes (nodes without edges in the specified direction). * + * **When to use** + * + * Use when locating sources, sinks, or isolated boundary nodes. + * * **Details** * * External nodes have no outgoing edges (`direction: "outgoing"`) or no - * incoming edges (`direction: "incoming"`). These are useful for finding - * sources, sinks, or isolated nodes. + * incoming edges (`direction: "incoming"`). + * + * **Gotchas** + * + * For undirected graphs, incoming and outgoing adjacency are equivalent, so + * only isolated nodes are external. Mutable graphs are not snapshotted; + * mutations may affect the remaining iteration. * * **Example** (Iterating external nodes) * - * ```ts + * ```ts import.meta.vitest * import { Graph } from "effect" * * const graph = Graph.directed((mutable) => { @@ -4832,16 +8475,10 @@ export interface ExternalsConfig { * }) * * // Nodes with no outgoing edges (sinks + isolated) - * const sinks = Array.from( - * Graph.indices(Graph.externals(graph, { direction: "outgoing" })) - * ) - * console.log(sinks) // [2, 3] + * Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" }))) // => [2, 3] * * // Nodes with no incoming edges (sources + isolated) - * const sources = Array.from( - * Graph.indices(Graph.externals(graph, { direction: "incoming" })) - * ) - * console.log(sources) // [0, 3] + * Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" }))) // => [0, 3] * ``` * * @category iterators @@ -4863,10 +8500,11 @@ export const externals: { return new Walker((f) => ({ [Symbol.iterator]: () => { - const nodeMap = graph.nodes + const impl = internal.toImpl(graph) + const nodeMap = impl.nodes const adjacencyMap = direction === "incoming" - ? graph.reverseAdjacency - : graph.adjacency + ? impl.reverseAdjacency + : impl.adjacency const nodeIterator = nodeMap.entries() @@ -4878,7 +8516,7 @@ export const externals: { // Node is external if it has no edges in the specified direction if (adjacencyList === undefined || adjacencyList.length === 0) { - return { done: false, value: f(nodeIndex, nodeData) } + return { done: false, value: withMutationGuard(graph, () => f(nodeIndex, nodeData)) } } current = nodeIterator.next() } diff --git a/packages/effect/src/HKT.ts b/packages/effect/src/HKT.ts index 4cbbfead4..ae7a5608d 100644 --- a/packages/effect/src/HKT.ts +++ b/packages/effect/src/HKT.ts @@ -26,7 +26,7 @@ import type * as Types from "./Types.ts" * * **Example** (Linking a type class to a type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { HKT } from "effect" * * interface IdentityTypeLambda extends HKT.TypeLambda { @@ -45,7 +45,6 @@ import type * as Types from "./Types.ts" * type LinkedTypeLambda = typeof identity[typeof HKT.URI] * * const value: HKT.Kind, never, never, never, string> = identity.of("ok") - * console.log(value) // "ok" * ``` * * @category symbols @@ -68,7 +67,7 @@ export declare const URI: unique symbol * * **Example** (Defining higher-kinded type classes) * - * ```ts + * ```ts import.meta.vitest * import type { HKT } from "effect" * * // Define a Functor type class @@ -86,6 +85,8 @@ export declare const URI: unique symbol * f: (a: A) => HKT.Kind * ): HKT.Kind * } + * + * const witness: keyof Monad = "flatMap" * ``` * * @category models @@ -112,7 +113,7 @@ export interface TypeClass { * * **Example** (Defining type lambdas) * - * ```ts + * ```ts import.meta.vitest * import type { Effect, HKT } from "effect" * * // TypeLambda for Array
@@ -129,6 +130,8 @@ export interface TypeClass { * interface FunctionTypeLambda extends HKT.TypeLambda { * readonly type: (a: this["In"]) => this["Target"] * } + * + * const witness: HKT.Kind = ["ok"] * ``` * * @category models @@ -158,8 +161,9 @@ export interface TypeLambda { * * **Example** (Applying type lambdas) * - * ```ts - * import type { Effect, HKT, Option } from "effect" + * ```ts import.meta.vitest + * import { Option } from "effect" + * import type { Effect, HKT } from "effect" * * // Define TypeLambdas * interface OptionTypeLambda extends HKT.TypeLambda { @@ -191,6 +195,8 @@ export interface TypeLambda { * never, * string * > + * + * const witness: OptionString = Option.some("ok") * ``` * * @category utility types diff --git a/packages/effect/src/Hash.ts b/packages/effect/src/Hash.ts index 2d925c337..99f226526 100644 --- a/packages/effect/src/Hash.ts +++ b/packages/effect/src/Hash.ts @@ -28,7 +28,7 @@ import { hasProperty } from "./Predicate.ts" * @category symbols * @since 2.0.0 */ -export const symbol = "~effect/interfaces/Hash" +export const symbol = "~effect/Hash" /** * A type that represents an object that can be hashed. @@ -44,7 +44,7 @@ export const symbol = "~effect/interfaces/Hash" * * **Example** (Implementing Hash) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * class MyClass implements Hash.Hash { @@ -55,8 +55,7 @@ export const symbol = "~effect/interfaces/Hash" * } * } * - * const instance = new MyClass(42) - * console.log(instance[Hash.symbol]()) // hash value of 42 + * new MyClass(42)[Hash.symbol]() // => 42 * ``` * * @category models @@ -90,18 +89,12 @@ export interface Hash { * * **Example** (Hashing different values) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * // Hash primitive values - * console.log(Hash.hash(42)) // numeric hash - * console.log(Hash.hash("hello")) // string hash - * console.log(Hash.hash(true)) // boolean hash - * - * // Hash objects and arrays - * console.log(Hash.hash({ name: "John", age: 30 })) - * console.log(Hash.hash([1, 2, 3])) - * console.log(Hash.hash({ id: "user-1", roles: ["admin", "editor"] })) + * Hash.hash(42) === Hash.hash(42) // => true + * Hash.hash("hello") === Hash.hash("hello") // => true + * Hash.hash([1, 2, 3]) === Hash.hash([1, 2, 3]) // => true * ``` * * @category hashing @@ -113,10 +106,6 @@ export const hash: (self: A) => number = (self: A) => { return number(self) case "bigint": return string(self.toString(10)) - case "boolean": - return string(String(self)) - case "symbol": - return string(String(self)) case "string": return string(self) case "undefined": @@ -126,6 +115,9 @@ export const hash: (self: A) => number = (self: A) => { if (self === null) { return string("null") } else if (self instanceof Date) { + if (Number.isNaN(self.getTime())) { + return string("Invalid Date") + } return string(self.toISOString()) } else if (self instanceof RegExp) { return string(self.toString()) @@ -141,6 +133,8 @@ export const hash: (self: A) => number = (self: A) => { return self[symbol]() } else if (typeof self === "function") { return random(self) + } else if (self instanceof DataView) { + return array(new Uint8Array(self.buffer, self.byteOffset, self.byteLength)) } else if (Array.isArray(self) || ArrayBuffer.isView(self)) { return array(self as any) } else if (self instanceof Map) { @@ -155,9 +149,8 @@ export const hash: (self: A) => number = (self: A) => { } } default: - throw new Error( - `BUG: unhandled typeof ${typeof self} - please report an issue at https://github.com/Effect-TS/effect/issues` - ) + // The remaining primitive types are boolean and symbol. + return string(String(self)) } } @@ -176,17 +169,15 @@ export const hash: (self: A) => number = (self: A) => { * * **Example** (Hashing objects by reference) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * const obj1 = { a: 1 } * const obj2 = { a: 1 } * - * // Same object always returns the same hash - * console.log(Hash.random(obj1) === Hash.random(obj1)) // true + * Hash.random(obj1) === Hash.random(obj1) // => true * - * // Different objects get different hashes - * console.log(Hash.random(obj1) === Hash.random(obj2)) // false + * typeof Hash.random(obj2) // => "number" * ``` * * @category hashing @@ -214,18 +205,14 @@ export const random: (self: A) => number = (self) => { * * **Example** (Combining hash values) * - * ```ts + * ```ts import.meta.vitest * import { Hash, pipe } from "effect" * - * // Can also be used with pipe - * * const hash1 = Hash.hash("hello") * const hash2 = Hash.hash("world") * - * // Combine two hash values * const combined = Hash.combine(hash2)(hash1) - * console.log(combined) - * const result = pipe(hash1, Hash.combine(hash2)) + * combined === pipe(hash1, Hash.combine(hash2)) // => true * ``` * * @see {@link hash} for computing hash values from arbitrary inputs @@ -253,15 +240,10 @@ export const combine: { * * **Example** (Optimizing a hash value) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * const rawHash = 1234567890 - * const optimizedHash = Hash.optimize(rawHash) - * console.log(optimizedHash) // optimized hash value - * - * // Often used internally by other hash functions - * const stringHash = Hash.optimize(Hash.string("hello")) + * Hash.optimize(1234567890) // => 160826066 * ``` * * @category hashing @@ -283,7 +265,7 @@ export const optimize = (n: number): number => (n & 0xbfffffff) | ((n >>> 1) & 0 * * **Example** (Checking for Hash support) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * class MyHashable implements Hash.Hash { @@ -292,10 +274,9 @@ export const optimize = (n: number): number => (n & 0xbfffffff) | ((n >>> 1) & 0 * } * } * - * const obj = new MyHashable() - * console.log(Hash.isHash(obj)) // true - * console.log(Hash.isHash({})) // false - * console.log(Hash.isHash("string")) // false + * Hash.isHash(new MyHashable()) // => true + * Hash.isHash({}) // => false + * Hash.isHash("string") // => false * ``` * * @category guards @@ -318,30 +299,22 @@ export const isHash = (u: unknown): u is Hash => hasProperty(u, symbol) * * **Example** (Hashing numbers) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * console.log(Hash.number(42)) // hash of 42 - * console.log(Hash.number(3.14)) // hash of 3.14 - * console.log(Hash.number(NaN)) // hash of "NaN" - * console.log(Hash.number(Infinity)) // 0 (special case) - * - * // Same numbers produce the same hash - * console.log(Hash.number(100) === Hash.number(100)) // true + * Number.isInteger(Hash.number(42)) // => true + * Number.isInteger(Hash.number(3.14)) // => true + * Hash.number(NaN) === Hash.number(NaN) // => true + * Hash.number(Infinity) === Hash.number(Infinity) // => true + * Hash.number(100) === Hash.number(100) // => true * ``` * * @category hashing * @since 2.0.0 */ export const number = (n: number) => { - if (n !== n) { - return string("NaN") - } - if (n === Infinity) { - return string("Infinity") - } - if (n === -Infinity) { - return string("-Infinity") + if (n !== n || n === Infinity || n === -Infinity) { + return string(String(n)) } let h = n | 0 if (h !== n) { @@ -369,15 +342,13 @@ export const number = (n: number) => { * * **Example** (Hashing strings) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * - * console.log(Hash.string("hello")) // hash of "hello" - * console.log(Hash.string("world")) // hash of "world" - * console.log(Hash.string("")) // hash of empty string - * - * // Same strings produce the same hash - * console.log(Hash.string("test") === Hash.string("test")) // true + * Hash.string("hello") // => 181380007 + * Hash.string("world") // => 164394279 + * Hash.string("") // => 5381 + * Hash.string("test") === Hash.string("test") // => true * ``` * * @category hashing @@ -406,22 +377,20 @@ export const string = (str: string) => { * * **Example** (Hashing selected object keys) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * const person = { name: "John", age: 30, city: "New York" } * - * // Hash only specific keys * const hash1 = Hash.structureKeys(person, ["name", "age"]) * const hash2 = Hash.structureKeys(person, ["name", "city"]) * - * console.log(hash1) // hash based on name and age - * console.log(hash2) // hash based on name and city + * hash1 // => -590673747 + * hash2 // => 284850673 * - * // Same keys produce the same hash * const person2 = { name: "John", age: 30, city: "Boston" } * const hash3 = Hash.structureKeys(person2, ["name", "age"]) - * console.log(hash1 === hash3) // true + * hash1 === hash3 // => true * ``` * * @category hashing @@ -449,19 +418,17 @@ export const structureKeys = (o: object, keys: Iterable) => { * * **Example** (Hashing object structures) * - * ```ts + * ```ts import.meta.vitest * import { Hash } from "effect" * * const obj1 = { name: "John", age: 30 } * const obj2 = { name: "Jane", age: 25 } * const obj3 = { name: "John", age: 30 } * - * console.log(Hash.structure(obj1)) // hash of obj1 - * console.log(Hash.structure(obj2)) // different hash - * console.log(Hash.structure(obj3)) // same as obj1 - * - * // Objects with same properties produce same hash - * console.log(Hash.structure(obj1) === Hash.structure(obj3)) // true + * Hash.structure(obj1) // => -590673747 + * Hash.structure(obj2) // => -590160631 + * Hash.structure(obj3) // => -590673747 + * Hash.structure(obj1) === Hash.structure(obj3) // => true * ``` * * @category hashing @@ -496,19 +463,18 @@ const iterableWith = (seed: number, f: (el: any) => number) => (iter: Iterable 6151 + * Hash.array(arr2) // => 6151 + * Hash.array(arr3) // => 6151 + * Hash.array(arr1) === Hash.array(arr2) // => true + * Hash.array(arr1) === Hash.array(arr3) // => true * ``` * * @see {@link hash} for the general-purpose hash dispatcher diff --git a/packages/effect/src/HashMap.ts b/packages/effect/src/HashMap.ts index 223d78101..7a9e80837 100644 --- a/packages/effect/src/HashMap.ts +++ b/packages/effect/src/HashMap.ts @@ -27,22 +27,21 @@ const TypeId = internal.HashMapTypeId * * **Example** (Using basic HashMap operations) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * // Create a HashMap * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * * // Access values - * const valueA = HashMap.get(map, "a") // Option.some(1) - * const valueD = HashMap.get(map, "d") // Option.none() + * HashMap.get(map, "a") // => Option.some(1) + * HashMap.get(map, "d") // => Option.none() * * // Check if key exists - * console.log(HashMap.has(map, "b")) // true + * HashMap.has(map, "b") // => true * * // Add/update values (returns new HashMap) - * const updated = HashMap.set(map, "d", 4) - * console.log(HashMap.size(updated)) // 4 + * HashMap.set(map, "d", 4) // => HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * ``` * * @category models @@ -58,7 +57,7 @@ export interface HashMap extends Iterable<[Key, Value]>, Equ * * **Example** (Extracting HashMap types) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Create a concrete HashMap for type extraction @@ -82,6 +81,8 @@ export interface HashMap extends Iterable<[Key, Value]>, Equ * // Example of extracted types in action * const newProduct: Product = { quantity: 10, price: 199 } * const updatedInventory = updateInventory("tablet", newProduct) + * processEntry(["tablet", newProduct]) // => "tablet: 10 @ $199" + * updatedInventory // => HashMap.make(["laptop", { quantity: 5, price: 999 }], ["mouse", { quantity: 20, price: 29 }], ["tablet", newProduct]) * ``` * * @since 2.0.0 @@ -94,7 +95,7 @@ export declare namespace HashMap { * * **Example** (Updating values from Options) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) @@ -104,7 +105,7 @@ export declare namespace HashMap { * Option.isSome(option) ? Option.some(option.value + 1) : Option.some(1) * * const updated = HashMap.modifyAt(map, "a", updateFn) - * console.log(HashMap.get(updated, "a")) // Option.some(2) + * HashMap.get(updated, "a") // => Option.some(2) * ``` * * @category models @@ -117,8 +118,8 @@ export declare namespace HashMap { * * **Example** (Extracting key types) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * // Create a HashMap to extract key type from * const userMap = HashMap.make( @@ -131,7 +132,7 @@ export declare namespace HashMap { * * // Use the extracted type in functions * const getUserById = (id: UserKey) => HashMap.get(userMap, id) - * console.log(getUserById("alice")) // Option.some({ name: "Alice", age: 30 }) + * getUserById("alice") // => Option.some({ name: "Alice", age: 30 }) * ``` * * @category utility types @@ -144,8 +145,8 @@ export declare namespace HashMap { * * **Example** (Extracting value types) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * // Create a HashMap with user data * const userMap = HashMap.make( @@ -161,8 +162,9 @@ export declare namespace HashMap { * return user.active ? `${user.name} (active)` : `${user.name} (inactive)` * } * - * const alice = HashMap.get(userMap, "alice") - * // alice has type Option thanks to type extraction + * // The lookup has type Option thanks to type extraction + * HashMap.get(userMap, "alice") // => Option.some({ name: "Alice", age: 30, active: true }) + * processUser({ name: "Alice", age: 30, active: true }) // => "Alice (active)" * ``` * * @category utility types @@ -175,7 +177,7 @@ export declare namespace HashMap { * * **Example** (Extracting entry types) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Create a product catalog HashMap @@ -194,7 +196,7 @@ export declare namespace HashMap { * * // Convert to entries, process, and sort for deterministic output * const descriptions = HashMap.toEntries(catalog).map(processEntry).sort() - * console.log(descriptions) // ["book: $29 (education)", "laptop: $999 (electronics)"] + * descriptions // => ["book: $29 (education)", "laptop: $999 (electronics)"] * ``` * * @category utility types @@ -208,18 +210,18 @@ export declare namespace HashMap { * * **Example** (Checking HashMap values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) * const notMap = { a: 1 } * - * console.log(HashMap.isHashMap(map)) // true - * console.log(HashMap.isHashMap(notMap)) // false - * console.log(HashMap.isHashMap(null)) // false + * HashMap.isHashMap(map) // => true + * HashMap.isHashMap(notMap) // => false + * HashMap.isHashMap(null) // => false * ``` * - * @category refinements + * @category guards * @since 2.0.0 */ export const isHashMap: { @@ -232,12 +234,10 @@ export const isHashMap: { * * **Example** (Creating an empty HashMap) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * - * const map = HashMap.empty() - * console.log(HashMap.isEmpty(map)) // true - * console.log(HashMap.size(map)) // 0 + * HashMap.empty() // => HashMap.empty() * ``` * * @category constructors @@ -250,12 +250,10 @@ export const empty: () => HashMap = internal.empty * * **Example** (Creating a HashMap from entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * - * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * console.log(HashMap.size(map)) // 3 - * console.log(HashMap.get(map, "b")) // Option.some(2) + * HashMap.make(["a", 1], ["b", 2], ["c", 3]) // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) * ``` * * @category constructors @@ -273,13 +271,11 @@ export const make: >( * * **Example** (Creating a HashMap from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const entries = [["a", 1], ["b", 2], ["c", 3]] as const - * const map = HashMap.fromIterable(entries) - * console.log(HashMap.size(map)) // 3 - * console.log(HashMap.get(map, "a")) // Option.some(1) + * HashMap.fromIterable(entries) // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) * ``` * * @category constructors @@ -292,17 +288,17 @@ export const fromIterable: (entries: Iterable) => HashMap * * **Example** (Checking for empty HashMaps) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const emptyMap = HashMap.empty() * const nonEmptyMap = HashMap.make(["a", 1]) * - * console.log(HashMap.isEmpty(emptyMap)) // true - * console.log(HashMap.isEmpty(nonEmptyMap)) // false + * HashMap.isEmpty(emptyMap) // => true + * HashMap.isEmpty(nonEmptyMap) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isEmpty: (self: HashMap) => boolean = internal.isEmpty @@ -313,20 +309,19 @@ export const isEmpty: (self: HashMap) => boolean = internal.isEmpty * * **Example** (Looking up values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) * - * console.log(HashMap.get(map, "a")) // Option.some(1) - * console.log(HashMap.get(map, "c")) // Option.none() + * HashMap.get(map, "a") // => Option.some(1) + * HashMap.get(map, "c") // => Option.none() * * // Using pipe syntax - * const value = HashMap.get("b")(map) - * console.log(value) // Option.some(2) + * HashMap.get("b")(map) // => Option.some(2) * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const get: { @@ -339,8 +334,8 @@ export const get: { * * **Example** (Looking up values with a hash) * - * ```ts - * import { Hash, HashMap } from "effect" + * ```ts import.meta.vitest + * import { Hash, HashMap, Option } from "effect" * * // Useful when implementing custom equality for complex keys * const userMap = HashMap.make( @@ -353,15 +348,13 @@ export const get: { * const precomputedHash = Hash.string(userId) * * // Lookup with custom hash (e.g., cached hash value) - * const user = HashMap.getHash(userMap, userId, precomputedHash) - * console.log(user) // Option.some({ name: "Alice", role: "admin" }) + * HashMap.getHash(userMap, userId, precomputedHash) // => Option.some({ name: "Alice", role: "admin" }) * * // This avoids recomputing the hash when you already have it - * const notFound = HashMap.getHash(userMap, "user999", Hash.string("user999")) - * console.log(notFound) // Option.none() + * HashMap.getHash(userMap, "user999", Hash.string("user999")) // => Option.none() * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const getHash: { @@ -385,7 +378,7 @@ export const getHash: { * * **Example** (Unsafely looking up values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const config = HashMap.make( @@ -395,14 +388,10 @@ export const getHash: { * ) * * // Safe: use when you're certain the key exists - * const apiUrl = HashMap.getUnsafe(config, "api_url") // "https://api.example.com" - * console.log(`Connecting to: ${apiUrl}`) + * HashMap.getUnsafe(config, "api_url") // => "https://api.example.com" * * // Preferred: use get() for uncertain keys - * const dbUrl = HashMap.get(config, "db_url") // Option.none() - * if (Option.isSome(dbUrl)) { - * console.log(`Database: ${dbUrl.value}`) - * } + * HashMap.get(config, "db_url") // => Option.none() * * // This would throw: HashMap.getUnsafe(config, "db_url") * // Error: "HashMap.getUnsafe: key not found" @@ -421,20 +410,19 @@ export const getUnsafe: { * * **Example** (Checking for keys) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) * - * console.log(HashMap.has(map, "a")) // true - * console.log(HashMap.has(map, "c")) // false + * HashMap.has(map, "a") // => true + * HashMap.has(map, "c") // => false * * // Using pipe syntax - * const hasB = HashMap.has("b")(map) - * console.log(hasB) // true + * HashMap.has("b")(map) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -448,7 +436,7 @@ export const has: { * * **Example** (Checking keys with a hash) * - * ```ts + * ```ts import.meta.vitest * import { Hash, HashMap } from "effect" * * // Create a map with case-sensitive keys @@ -459,17 +447,17 @@ export const has: { * * // Check with exact hash * const exactHash = Hash.string("Admin") - * console.log(HashMap.hasHash(userMap, "Admin", exactHash)) // true + * HashMap.hasHash(userMap, "Admin", exactHash) // => true * * // A matching hash does not override key equality - * console.log(HashMap.hasHash(userMap, "admin", exactHash)) // false + * HashMap.hasHash(userMap, "admin", exactHash) // => false * * // A different hash also cannot find the existing key * const lowercaseHash = Hash.string("admin") - * console.log(HashMap.hasHash(userMap, "Admin", lowercaseHash)) // false + * HashMap.hasHash(userMap, "Admin", lowercaseHash) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const hasHash: { @@ -482,15 +470,15 @@ export const hasHash: { * * **Example** (Checking entries by predicate) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const hm = HashMap.make([1, "a"]) - * HashMap.hasBy(hm, (value, key) => value === "a" && key === 1) // -> true - * HashMap.hasBy(hm, (value) => value === "b") // -> false + * HashMap.hasBy(hm, (value, key) => value === "a" && key === 1) // => true + * HashMap.hasBy(hm, (value) => value === "b") // => false * ``` * - * @category elements + * @category predicates * @since 3.16.0 */ export const hasBy: { @@ -504,17 +492,14 @@ export const hasBy: { * * **Example** (Setting a value) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1]) - * const map2 = HashMap.set(map1, "b", 2) - * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.get(map2, "b")) // Option.some(2) + * HashMap.set(map1, "b", 2) // => HashMap.make(["a", 1], ["b", 2]) * * // Original map is unchanged - * console.log(HashMap.size(map1)) // 1 + * map1 // => HashMap.make(["a", 1]) * ``` * * @category transforming @@ -530,12 +515,11 @@ export const set: { * * **Example** (Iterating keys) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const keys = Array.from(HashMap.keys(map)) - * console.log(keys.sort()) // ["a", "b", "c"] + * Array.from(HashMap.keys(map)).sort() // => ["a", "b", "c"] * ``` * * @category getters @@ -548,12 +532,11 @@ export const keys: (self: HashMap) => IterableIterator = internal * * **Example** (Iterating values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const values = Array.from(HashMap.values(map)) - * console.log(values.sort()) // [1, 2, 3] + * Array.from(HashMap.values(map)).sort() // => [1, 2, 3] * ``` * * @category getters @@ -566,7 +549,7 @@ export const values: (self: HashMap) => IterableIterator = intern * * **Example** (Converting values to an array) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const employees = HashMap.make( @@ -577,15 +560,13 @@ export const values: (self: HashMap) => IterableIterator = intern * * // Extract all employee records * const allEmployees = HashMap.toValues(employees) - * console.log(allEmployees.length) // 3 + * allEmployees.length // => 3 * * // Calculate total salary - * const totalSalary = allEmployees.reduce((sum, emp) => sum + emp.salary, 0) - * console.log(totalSalary) // 260000 + * allEmployees.reduce((sum, emp) => sum + emp.salary, 0) // => 260000 * * // Filter by department - * const engineers = allEmployees.filter((emp) => emp.department === "engineering") - * console.log(engineers.length) // 2 + * allEmployees.filter((emp) => emp.department === "engineering").length // => 2 * ``` * * @category getters @@ -598,7 +579,7 @@ export const toValues = (self: HashMap): Array => Array.from(valu * * **Example** (Iterating entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Create a configuration map @@ -613,12 +594,10 @@ export const toValues = (self: HashMap): Array => Array.from(valu * .sort(([left], [right]) => left.localeCompare(right)) * .map(([key, value]) => `Setting ${key} = ${value}`) * - * console.log(settings) - * // ["Setting cache.enabled = true", "Setting database.host = localhost", "Setting database.port = 5432"] + * settings // => ["Setting cache.enabled = true", "Setting database.host = localhost", "Setting database.port = 5432"] * * // Convert to array when you need all entries at once - * const allEntries = Array.from(HashMap.entries(config)) - * console.log(allEntries.length) // 3 + * Array.from(HashMap.entries(config)).length // => 3 * ``` * * @category getters @@ -631,7 +610,7 @@ export const entries: (self: HashMap) => IterableIterator<[K, V]> = * * **Example** (Converting entries to an array) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const gameScores = HashMap.make( @@ -648,11 +627,10 @@ export const entries: (self: HashMap) => IterableIterator<[K, V]> = * .sort(([, a], [, b]) => b - a) * .map(([player, score], rank) => `${rank + 1}. ${player}: ${score}`) * - * console.log(leaderboard) - * // ["1. alice: 1250", "2. charlie: 1100", "3. bob: 980"] + * leaderboard // => ["1. alice: 1250", "2. charlie: 1100", "3. bob: 980"] * * // Convert back to HashMap if needed - * const sortedMap = HashMap.fromIterable(scoreEntries) + * HashMap.fromIterable(scoreEntries) // => HashMap.make(["alice", 1250], ["charlie", 1100], ["bob", 980]) * ``` * * @category getters @@ -665,14 +643,14 @@ export const toEntries = (self: HashMap): Array<[K, V]> => Array.fro * * **Example** (Getting the size) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const emptyMap = HashMap.empty() * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * - * console.log(HashMap.size(emptyMap)) // 0 - * console.log(HashMap.size(map)) // 3 + * HashMap.size(emptyMap) // => 0 + * HashMap.size(map) // => 3 * ``` * * @category getters @@ -690,7 +668,7 @@ export const size: (self: HashMap) => number = internal.size * * **Example** (Beginning batch mutation) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1]) @@ -704,8 +682,7 @@ export const size: (self: HashMap) => number = internal.size * HashMap.remove(mutable, "a") * * // End mutation to get final immutable result - * const result = HashMap.endMutation(mutable) - * console.log(HashMap.size(result)) // 2 + * HashMap.endMutation(mutable) // => HashMap.make(["b", 2], ["c", 3]) * ``` * * @category mutations @@ -718,7 +695,7 @@ export const beginMutation: (self: HashMap) => HashMap = inter * * **Example** (Ending batch mutation) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * // Start with an existing map @@ -733,11 +710,7 @@ export const beginMutation: (self: HashMap) => HashMap = inter * HashMap.set(mutable, "w", 40) * * // End mutation to get final immutable result - * const final = HashMap.endMutation(mutable) - * - * console.log(HashMap.size(final)) // 3 - * console.log(HashMap.has(final, "x")) // false - * console.log(HashMap.get(final, "z")) // Option.some(30) + * HashMap.endMutation(mutable) // => HashMap.make(["y", 20], ["z", 30], ["w", 40]) * ``` * * @category mutations @@ -756,7 +729,7 @@ export const endMutation: (self: HashMap) => HashMap = interna * * **Example** (Applying batched mutations) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1]) @@ -764,7 +737,7 @@ export const endMutation: (self: HashMap) => HashMap = interna * HashMap.set(mutable, "b", 2) * HashMap.set(mutable, "c", 3) * }) - * // Returns a new HashMap with mutations applied + * map2 // => HashMap.make(["a", 1], ["b", 2], ["c", 3]) * ``` * * @category mutations @@ -786,7 +759,7 @@ export const mutate: { * * **Example** (Updating values with Options) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) @@ -796,7 +769,7 @@ export const mutate: { * Option.isSome(option) ? Option.some(option.value + 1) : Option.some(1) * * const updated = HashMap.modifyAt(map, "a", updateFn) - * console.log(HashMap.get(updated, "a")) // Option.some(2) + * HashMap.get(updated, "a") // => Option.some(2) * ``` * * @category transforming @@ -819,7 +792,7 @@ export const modifyAt: { * * **Example** (Updating values with a hash) * - * ```ts + * ```ts import.meta.vitest * import { Hash, HashMap, Option } from "effect" * * // Useful when working with precomputed hashes for performance @@ -840,7 +813,7 @@ export const modifyAt: { * cachedHash, * incrementCounter * ) - * console.log(HashMap.get(updated, "downloads")) // Option.some(101) + * HashMap.get(updated, "downloads") // => Option.some(101) * * // Add new metric with precomputed hash * const newMetric = "clicks" @@ -851,7 +824,7 @@ export const modifyAt: { * clicksHash, * incrementCounter * ) - * console.log(HashMap.get(withClicks, "clicks")) // Option.some(1) + * HashMap.get(withClicks, "clicks") // => Option.some(1) * ``` * * @category transforming @@ -867,14 +840,14 @@ export const modifyHash: { * * **Example** (Modifying existing values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const map2 = HashMap.modify(map1, "a", (value) => value * 3) * - * console.log(HashMap.get(map2, "a")) // Option.some(3) - * console.log(HashMap.get(map2, "b")) // Option.some(2) + * HashMap.get(map2, "a") // => Option.some(3) + * HashMap.get(map2, "b") // => Option.some(2) * ``` * * @category transforming @@ -895,15 +868,15 @@ export const modify: { * * **Example** (Combining HashMaps) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const map2 = HashMap.make(["b", 20], ["c", 3]) * const union = HashMap.union(map1, map2) * - * console.log(HashMap.size(union)) // 3 - * console.log(HashMap.get(union, "b")) // Option.some(20) - map2 wins + * union // => HashMap.make(["a", 1], ["b", 20], ["c", 3]) + * HashMap.get(union, "b") // => Option.some(20) * ``` * * @category combining @@ -920,15 +893,13 @@ export const union: { * * **Example** (Removing a key) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * const map2 = HashMap.remove(map1, "b") * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.has(map2, "b")) // false - * console.log(HashMap.has(map2, "a")) // true + * map2 // => HashMap.make(["a", 1], ["c", 3]) * ``` * * @category transforming @@ -944,15 +915,13 @@ export const remove: { * * **Example** (Removing multiple keys) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * const map2 = HashMap.removeMany(map1, ["b", "d"]) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.has(map2, "a")) // true - * console.log(HashMap.has(map2, "c")) // true + * map2 // => HashMap.make(["a", 1], ["c", 3]) * ``` * * @category transforming @@ -968,16 +937,15 @@ export const removeMany: { * * **Example** (Setting multiple entries) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const newEntries = [["c", 3], ["d", 4], ["a", 10]] as const // "a" will be overwritten * const map2 = HashMap.setMany(map1, newEntries) * - * console.log(HashMap.size(map2)) // 4 - * console.log(HashMap.get(map2, "a")) // Option.some(10) - * console.log(HashMap.get(map2, "c")) // Option.some(3) + * map2 // => HashMap.make(["a", 10], ["b", 2], ["c", 3], ["d", 4]) + * HashMap.get(map2, "a") // => Option.some(10) * ``` * * @category transforming @@ -993,14 +961,14 @@ export const setMany: { * * **Example** (Mapping values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * const map2 = HashMap.map(map1, (value, key) => `${key}:${value * 2}`) * - * console.log(HashMap.get(map2, "a")) // Option.some("a:2") - * console.log(HashMap.get(map2, "b")) // Option.some("b:4") + * HashMap.get(map2, "a") // => Option.some("a:2") + * HashMap.get(map2, "b") // => Option.some("b:4") * ``` * * @category mapping @@ -1020,8 +988,8 @@ export const map: { * * **Example** (Flat mapping values) * - * ```ts - * import { HashMap } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2]) * const map2 = HashMap.flatMap( @@ -1029,9 +997,8 @@ export const map: { * (value, key) => HashMap.make([key + "1", value], [key + "2", value * 2]) * ) * - * console.log(HashMap.size(map2)) // 4 - * console.log(HashMap.get(map2, "a1")) // Option.some(1) - * console.log(HashMap.get(map2, "b2")) // Option.some(4) + * map2 // => HashMap.make(["a1", 1], ["a2", 2], ["b1", 2], ["b2", 4]) + * HashMap.get(map2, "b2") // => Option.some(4) * ``` * * @category sequencing @@ -1047,7 +1014,7 @@ export const flatMap: { * * **Example** (Iterating with side effects) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2]) @@ -1057,7 +1024,7 @@ export const flatMap: { * collected.push([key, value]) * }) * - * console.log(collected.sort()) // [["a", 1], ["b", 2]] + * collected.sort() // => [["a", 1], ["b", 2]] * ``` * * @category traversing @@ -1073,13 +1040,11 @@ export const forEach: { * * **Example** (Reducing values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const sum = HashMap.reduce(map, 0, (acc, value) => acc + value) - * - * console.log(sum) // 6 + * HashMap.reduce(map, 0, (acc, value) => acc + value) // => 6 * ``` * * @category folding @@ -1095,16 +1060,13 @@ export const reduce: { * * **Example** (Filtering entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * const map2 = HashMap.filter(map1, (value) => value % 2 === 0) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.has(map2, "b")) // true - * console.log(HashMap.has(map2, "d")) // true - * console.log(HashMap.has(map2, "a")) // false + * map2 // => HashMap.make(["b", 2], ["d", 4]) * ``` * * @category filtering @@ -1120,7 +1082,7 @@ export const filter: { * * **Example** (Compacting Option values) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map1 = HashMap.make( @@ -1130,9 +1092,8 @@ export const filter: { * ) * const map2 = HashMap.compact(map1) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.get(map2, "a")) // Option.some(1) - * console.log(HashMap.has(map2, "b")) // false + * map2 // => HashMap.make(["a", 1], ["c", 3]) + * HashMap.get(map2, "a") // => Option.some(1) * ``` * * @category filtering @@ -1146,8 +1107,8 @@ export const compact: (self: HashMap>) => HashMap = int * * **Example** (Filtering and mapping Results) * - * ```ts - * import { HashMap, Result } from "effect" + * ```ts import.meta.vitest + * import { HashMap, Option, Result } from "effect" * * const map1 = HashMap.make(["a", 1], ["b", 2], ["c", 3], ["d", 4]) * const map2 = HashMap.filterMap( @@ -1155,9 +1116,8 @@ export const compact: (self: HashMap>) => HashMap = int * (value) => value % 2 === 0 ? Result.succeed(value * 2) : Result.failVoid * ) * - * console.log(HashMap.size(map2)) // 2 - * console.log(HashMap.get(map2, "b")) // Option.some(4) - * console.log(HashMap.get(map2, "d")) // Option.some(8) + * map2 // => HashMap.make(["b", 4], ["d", 8]) + * HashMap.get(map2, "b") // => Option.some(4) * ``` * * @category filtering @@ -1174,16 +1134,14 @@ export const filterMap: { * * **Example** (Finding the first matching entry) * - * ```ts + * ```ts import.meta.vitest * import { HashMap, Option } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) - * const result = HashMap.findFirst(map, (value, key) => key === "b" && value > 1) - * console.log(result) // Option.some(["b", 2]) - * console.log(Option.getOrElse(result, () => ["", 0])) // ["b", 2] + * HashMap.findFirst(map, (value, key) => key === "b" && value > 1) // => Option.some(["b", 2]) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -1196,16 +1154,16 @@ export const findFirst: { * * **Example** (Checking for any matching entry) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * - * console.log(HashMap.some(map, (value) => value > 2)) // true - * console.log(HashMap.some(map, (value) => value > 5)) // false + * HashMap.some(map, (value) => value > 2) // => true + * HashMap.some(map, (value) => value > 5) // => false * ``` * - * @category elements + * @category predicates * @since 3.13.0 */ export const some: { @@ -1218,16 +1176,16 @@ export const some: { * * **Example** (Checking all entries) * - * ```ts + * ```ts import.meta.vitest * import { HashMap } from "effect" * * const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]) * - * console.log(HashMap.every(map, (value) => value > 0)) // true - * console.log(HashMap.every(map, (value) => value > 1)) // false + * HashMap.every(map, (value) => value > 0) // => true + * HashMap.every(map, (value) => value > 1) // => false * ``` * - * @category elements + * @category predicates * @since 3.14.0 */ export const every: { diff --git a/packages/effect/src/HashRing.ts b/packages/effect/src/HashRing.ts index 0b9f9c833..789bf0500 100644 --- a/packages/effect/src/HashRing.ts +++ b/packages/effect/src/HashRing.ts @@ -12,12 +12,13 @@ import { dual } from "./Function.ts" import * as Hash from "./Hash.ts" import { PipeInspectableProto } from "./internal/core.ts" +import * as Count from "./internal/count.ts" import * as Iterable from "./Iterable.ts" import type { Pipeable } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" import * as PrimaryKey from "./PrimaryKey.ts" -const TypeId = "~effect/cluster/HashRing" as const +const TypeId = "~effect/HashRing" as const /** * A weighted consistent-hashing ring for assigning inputs to nodes with stable @@ -145,6 +146,7 @@ export const addMany: { const key = PrimaryKey.value(node) const entry = self.nodes.get(key) if (entry) { + entry[0] = node if (entry[1] === weight) continue toRemove ??= new Set() toRemove.add(key) @@ -311,6 +313,11 @@ export const get = (self: HashRing, input: s * Use to precompute ownership for a fixed number of shard indexes across the * current ring members. * + * **Details** + * + * Finite fractional values of `count` are rounded down. `NaN` and non-positive + * values produce an empty shard distribution. + * * @category combinators * @since 3.19.0 */ @@ -318,6 +325,7 @@ export const getShards = (self: HashRing, co if (self.ring.length === 0) { return undefined } + count = Count.normalize(count) const shards = new Array(count) @@ -412,7 +420,7 @@ function getIndexForInput( return [a, distA] } const range = Math.max(lo, len - lo) - for (let i = 1; i < range; i++) { + for (let i = 1; i <= range; i++) { let index = lo - i if (index >= 0 && index < len && !exclude.has(ring[index][1])) { return [index, Math.abs(ring[index][0] - hash)] diff --git a/packages/effect/src/HashSet.ts b/packages/effect/src/HashSet.ts index fc6e6dd7b..2f088d062 100644 --- a/packages/effect/src/HashSet.ts +++ b/packages/effect/src/HashSet.ts @@ -26,23 +26,23 @@ const TypeId = internal.HashSetTypeId * * **Example** (Creating and updating a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * // Create a HashSet * const set = HashSet.make("apple", "banana", "cherry") * * // Check membership - * console.log(HashSet.has(set, "apple")) // true - * console.log(HashSet.has(set, "grape")) // false + * HashSet.has(set, "apple") // => true + * HashSet.has(set, "grape") // => false * * // Add values (returns new HashSet) * const updated = HashSet.add(set, "grape") - * console.log(HashSet.size(updated)) // 4 + * updated // => HashSet.make("apple", "banana", "cherry", "grape") * * // Remove values (returns new HashSet) * const smaller = HashSet.remove(set, "banana") - * console.log(HashSet.size(smaller)) // 2 + * smaller // => HashSet.make("apple", "cherry") * ``` * * @category models @@ -58,7 +58,7 @@ export interface HashSet extends Iterable, Equal, Pipeable, In * * **Example** (Extracting value types from a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * // Create a concrete HashSet for type extraction @@ -71,6 +71,7 @@ export interface HashSet extends Iterable, Equal, Pipeable, In * const processFruit = (fruit: Fruit) => { * return `Processing ${fruit}` * } + * processFruit("apple") // => "Processing apple" * ``` * * @since 2.0.0 @@ -85,7 +86,7 @@ export declare namespace HashSet { * * **Example** (Extracting a HashSet value type) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3, 4, 5) @@ -94,6 +95,7 @@ export declare namespace HashSet { * type NumberType = HashSet.HashSet.Value // number * * const processNumber = (n: NumberType) => n * 2 + * processNumber(3) // => 6 * ``` * * @category utility types @@ -107,17 +109,17 @@ export declare namespace HashSet { * * **Example** (Creating an empty HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.empty() * - * console.log(HashSet.size(set)) // 0 - * console.log(HashSet.isEmpty(set)) // true + * HashSet.size(set) // => 0 + * HashSet.isEmpty(set) // => true * * // Add some values * const withValues = HashSet.add(HashSet.add(set, "hello"), "world") - * console.log(HashSet.size(withValues)) // 2 + * withValues // => HashSet.make("hello", "world") * ``` * * @category constructors @@ -130,17 +132,14 @@ export const empty: () => HashSet = internal.empty * * **Example** (Creating a HashSet from values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const fruits = HashSet.make("apple", "banana", "cherry") - * console.log(HashSet.size(fruits)) // 3 + * HashSet.make("apple", "banana", "cherry") // => HashSet.make("apple", "banana", "cherry") * - * const numbers = HashSet.make(1, 2, 3, 2, 1) // Duplicates ignored - * console.log(HashSet.size(numbers)) // 3 + * HashSet.make(1, 2, 3, 2, 1) // => HashSet.make(1, 2, 3) * - * const mixed = HashSet.make("hello", 42, true) - * console.log(HashSet.size(mixed)) // 3 + * HashSet.make("hello", 42, true) // => HashSet.make("hello", 42, true) * ``` * * @category constructors @@ -155,17 +154,14 @@ export const make: >( * * **Example** (Creating a HashSet from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const fromArray = HashSet.fromIterable(["a", "b", "c", "b", "a"]) - * console.log(HashSet.size(fromArray)) // 3 + * HashSet.fromIterable(["a", "b", "c", "b", "a"]) // => HashSet.make("a", "b", "c") * - * const fromSet = HashSet.fromIterable(new Set([1, 2, 3])) - * console.log(HashSet.size(fromSet)) // 3 + * HashSet.fromIterable(new Set([1, 2, 3])) // => HashSet.make(1, 2, 3) * - * const fromString = HashSet.fromIterable("hello") - * console.log(Array.from(fromString)) // ["h", "e", "l", "o"] + * HashSet.fromIterable("hello") // => HashSet.make("h", "e", "l", "o") * ``` * * @category constructors @@ -178,15 +174,15 @@ export const fromIterable: (values: Iterable) => HashSet = internal.fro * * **Example** (Checking for a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.make(1, 2, 3) * const array = [1, 2, 3] * - * console.log(HashSet.isHashSet(set)) // true - * console.log(HashSet.isHashSet(array)) // false - * console.log(HashSet.isHashSet(null)) // false + * HashSet.isHashSet(set) // => true + * HashSet.isHashSet(array) // => false + * HashSet.isHashSet(null) // => false * ``` * * @category guards @@ -202,19 +198,18 @@ export const isHashSet: { * * **Example** (Adding values to a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.make("a", "b") * const withC = HashSet.add(set, "c") * - * console.log(HashSet.size(set)) // 2 (original unchanged) - * console.log(HashSet.size(withC)) // 3 - * console.log(HashSet.has(withC, "c")) // true + * set // => HashSet.make("a", "b") + * withC // => HashSet.make("a", "b", "c") + * HashSet.has(withC, "c") // => true * * // Adding existing value has no effect - * const same = HashSet.add(set, "a") - * console.log(HashSet.size(same)) // 2 + * HashSet.add(set, "a") // => HashSet.make("a", "b") * ``` * * @category mutations @@ -233,15 +228,15 @@ export const add: { * * **Example** (Checking HashSet membership) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Hash, HashSet } from "effect" * * // Works with any type that implements Equal * * const set = HashSet.make("apple", "banana", "cherry") * - * console.log(HashSet.has(set, "apple")) // true - * console.log(HashSet.has(set, "grape")) // false + * HashSet.has(set, "apple") // => true + * HashSet.has(set, "grape") // => false * * class Person implements Equal.Equal { * constructor(readonly name: string) {} @@ -256,10 +251,10 @@ export const add: { * } * * const people = HashSet.make(new Person("Alice"), new Person("Bob")) - * console.log(HashSet.has(people, new Person("Alice"))) // true + * HashSet.has(people, new Person("Alice")) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -275,19 +270,18 @@ export const has: { * * **Example** (Removing values from a HashSet) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const set = HashSet.make("a", "b", "c") * const withoutB = HashSet.remove(set, "b") * - * console.log(HashSet.size(set)) // 3 (original unchanged) - * console.log(HashSet.size(withoutB)) // 2 - * console.log(HashSet.has(withoutB, "b")) // false + * set // => HashSet.make("a", "b", "c") + * withoutB // => HashSet.make("a", "c") + * HashSet.has(withoutB, "b") // => false * * // Removing non-existent value has no effect - * const same = HashSet.remove(set, "d") - * console.log(HashSet.size(same)) // 3 + * HashSet.remove(set, "d") // => HashSet.make("a", "b", "c") * ``` * * @category mutations @@ -306,17 +300,14 @@ export const remove: { * * **Example** (Getting the HashSet size) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const empty = HashSet.empty() - * console.log(HashSet.size(empty)) // 0 + * HashSet.size(HashSet.empty()) // => 0 * - * const small = HashSet.make("a", "b") - * console.log(HashSet.size(small)) // 2 + * HashSet.size(HashSet.make("a", "b")) // => 2 * - * const withDuplicates = HashSet.fromIterable(["x", "y", "z", "x", "y"]) - * console.log(HashSet.size(withDuplicates)) // 3 + * HashSet.size(HashSet.fromIterable(["x", "y", "z", "x", "y"])) // => 3 * ``` * * @category getters @@ -329,17 +320,15 @@ export const size: (self: HashSet) => number = internal.size * * **Example** (Checking whether a HashSet is empty) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const empty = HashSet.empty() - * console.log(HashSet.isEmpty(empty)) // true + * HashSet.isEmpty(HashSet.empty()) // => true * - * const nonEmpty = HashSet.make("a") - * console.log(HashSet.isEmpty(nonEmpty)) // false + * HashSet.isEmpty(HashSet.make("a")) // => false * ``` * - * @category getters + * @category predicates * @since 4.0.0 */ export const isEmpty: (self: HashSet) => boolean = internal.isEmpty @@ -349,15 +338,10 @@ export const isEmpty: (self: HashSet) => boolean = internal.isEmpty * * **Example** (Combining HashSets) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const set1 = HashSet.make("a", "b") - * const set2 = HashSet.make("b", "c") - * const combined = HashSet.union(set1, set2) - * - * console.log(Array.from(combined).sort()) // ["a", "b", "c"] - * console.log(HashSet.size(combined)) // 3 + * HashSet.union(HashSet.make("a", "b"), HashSet.make("b", "c")) // => HashSet.make("a", "b", "c") * ``` * * @category combinators @@ -376,15 +360,10 @@ export const union: { * * **Example** (Finding common HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const set1 = HashSet.make("a", "b", "c") - * const set2 = HashSet.make("b", "c", "d") - * const common = HashSet.intersection(set1, set2) - * - * console.log(Array.from(common).sort()) // ["b", "c"] - * console.log(HashSet.size(common)) // 2 + * HashSet.intersection(HashSet.make("a", "b", "c"), HashSet.make("b", "c", "d")) // => HashSet.make("b", "c") * ``` * * @category combinators @@ -403,15 +382,10 @@ export const intersection: { * * **Example** (Finding HashSet differences) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const set1 = HashSet.make("a", "b", "c") - * const set2 = HashSet.make("b", "d") - * const diff = HashSet.difference(set1, set2) - * - * console.log(Array.from(diff).sort()) // ["a", "c"] - * console.log(HashSet.size(diff)) // 2 + * HashSet.difference(HashSet.make("a", "b", "c"), HashSet.make("b", "d")) // => HashSet.make("a", "c") * ``` * * @category combinators @@ -430,20 +404,20 @@ export const difference: { * * **Example** (Checking subset relationships) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const small = HashSet.make("a", "b") * const large = HashSet.make("a", "b", "c", "d") * const other = HashSet.make("x", "y") * - * console.log(HashSet.isSubset(small, large)) // true - * console.log(HashSet.isSubset(large, small)) // false - * console.log(HashSet.isSubset(small, other)) // false - * console.log(HashSet.isSubset(small, small)) // true + * HashSet.isSubset(small, large) // => true + * HashSet.isSubset(large, small) // => false + * HashSet.isSubset(small, other) // => false + * HashSet.isSubset(small, small) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const isSubset: { @@ -459,19 +433,18 @@ export const isSubset: { * * **Example** (Mapping HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3) * const doubled = HashSet.map(numbers, (n) => n * 2) * - * console.log(Array.from(doubled).sort()) // [2, 4, 6] - * console.log(HashSet.size(doubled)) // 3 + * doubled // => HashSet.make(2, 4, 6) * * // Mapping can reduce size if function produces duplicates * const strings = HashSet.make("apple", "banana", "cherry") * const lengths = HashSet.map(strings, (s) => s.length) - * console.log(Array.from(lengths).sort()) // [5, 6] (apple=5, banana=6, cherry=6) + * lengths // => HashSet.make(5, 6) * ``` * * @category mapping @@ -490,14 +463,10 @@ export const map: { * * **Example** (Filtering HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * - * const numbers = HashSet.make(1, 2, 3, 4, 5, 6) - * const evens = HashSet.filter(numbers, (n) => n % 2 === 0) - * - * console.log(Array.from(evens).sort()) // [2, 4, 6] - * console.log(HashSet.size(evens)) // 3 + * HashSet.filter(HashSet.make(1, 2, 3, 4, 5, 6), (n) => n % 2 === 0) // => HashSet.make(2, 4, 6) * ``` * * @category filtering @@ -524,19 +493,18 @@ export const filter: { * * **Example** (Testing whether some values match) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3, 4, 5) * - * console.log(HashSet.some(numbers, (n) => n > 3)) // true - * console.log(HashSet.some(numbers, (n) => n > 10)) // false + * HashSet.some(numbers, (n) => n > 3) // => true + * HashSet.some(numbers, (n) => n > 10) // => false * - * const empty = HashSet.empty() - * console.log(HashSet.some(empty, (n) => n > 0)) // false + * HashSet.some(HashSet.empty(), (n) => n > 0) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const some: { @@ -552,19 +520,18 @@ export const some: { * * **Example** (Testing whether every value matches) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(2, 4, 6, 8) * - * console.log(HashSet.every(numbers, (n) => n % 2 === 0)) // true - * console.log(HashSet.every(numbers, (n) => n > 5)) // false + * HashSet.every(numbers, (n) => n % 2 === 0) // => true + * HashSet.every(numbers, (n) => n > 5) // => false * - * const empty = HashSet.empty() - * console.log(HashSet.every(empty, (n) => n > 0)) // true (vacuously true) + * HashSet.every(HashSet.empty(), (n) => n > 0) // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const every: { @@ -580,17 +547,11 @@ export const every: { * * **Example** (Reducing HashSet values) * - * ```ts + * ```ts import.meta.vitest * import { HashSet } from "effect" * * const numbers = HashSet.make(1, 2, 3, 4, 5) - * const sum = HashSet.reduce(numbers, 0, (acc, n) => acc + n) - * - * console.log(sum) // 15 - * - * const strings = HashSet.make("a", "b", "c") - * const concatenated = HashSet.reduce(strings, "", (acc, s) => acc + s) - * console.log(concatenated) // Order may vary: "abc", "bac", etc. + * HashSet.reduce(numbers, 0, (acc, n) => acc + n) // => 15 * ``` * * @category folding diff --git a/packages/effect/src/Inspectable.ts b/packages/effect/src/Inspectable.ts index 05fed12bf..47843fffa 100644 --- a/packages/effect/src/Inspectable.ts +++ b/packages/effect/src/Inspectable.ts @@ -28,7 +28,7 @@ import { redact } from "./Redactable.ts" * * **Example** (Defining custom Node inspection) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * class CustomObject { @@ -40,7 +40,7 @@ import { redact } from "./Redactable.ts" * } * * const obj = new CustomObject("hello") - * console.log(obj) // Displays: CustomObject(hello) + * obj[Inspectable.NodeInspectSymbol]() // => "CustomObject(hello)" * ``` * * @category symbols @@ -59,7 +59,7 @@ export const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom") * * **Example** (Typing custom Node inspection) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * class CustomObject { @@ -71,7 +71,7 @@ export const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom") * } * * const obj = new CustomObject("test") - * console.log(obj) // CustomObject(test) + * obj[Inspectable.NodeInspectSymbol]() // => "CustomObject(test)" * ``` * * @category symbols @@ -94,7 +94,7 @@ export type NodeInspectSymbol = typeof NodeInspectSymbol * * **Example** (Implementing inspectable objects) * - * ```ts + * ```ts import.meta.vitest * import { Formatter, Inspectable } from "effect" * * class Result implements Inspectable.Inspectable { @@ -117,7 +117,7 @@ export type NodeInspectSymbol = typeof NodeInspectSymbol * } * * const success = new Result("Success", 42) - * console.log(success.toString()) // Pretty formatted JSON + * success.toString() // => "{\"_tag\":\"Success\",\"value\":42}" * ``` * * @category models @@ -130,19 +130,20 @@ export interface Inspectable { } /** - * Converts a value to a JSON-serializable representation safely. + * Converts a value to its structured inspection representation. * * **When to use** * - * Use when you need a safe, JSON-serializable representation of a value + * Use when you need the structured representation of an inspectable value * without risking unhandled errors. * * **Details** * - * This function attempts to extract JSON data from objects that implement the - * `toJSON` method, recursively processes arrays, and handles errors gracefully. - * For objects that don't have a `toJSON` method, it applies redaction to - * protect sensitive information. + * This function applies redaction before extracting data from objects that + * implement `toJSON`, recursively processes arrays, and handles errors + * gracefully. Plain objects are returned unchanged, so the result is not + * guaranteed to be accepted by `JSON.stringify`; it may still contain values + * such as `BigInt`, functions, or circular references. * * @see {@link toStringUnknown} for converting unknown values to strings * @@ -151,6 +152,7 @@ export interface Inspectable { */ export const toJson = (input: unknown): unknown => { try { + input = redact(input) if ( Predicate.hasProperty(input, "toJSON") && Predicate.isFunction(input["toJSON"]) && @@ -160,10 +162,10 @@ export const toJson = (input: unknown): unknown => { } else if (Array.isArray(input)) { return input.map(toJson) } + return input } catch { return "[toJSON threw]" } - return redact(input) } /** @@ -187,7 +189,7 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi return u } try { - return typeof u === "object" ? formatJson(u, { space: whitespace }) : String(u) + return typeof u === "object" ? formatJson(u, { space: whitespace }) : format(u, { space: whitespace }) } catch { return String(u) } @@ -208,7 +210,7 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi * * **Example** (Using the base inspectable prototype) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * // Use as prototype @@ -216,7 +218,7 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi * myObject.name = "example" * myObject.value = 42 * - * console.log(myObject.toString()) // Pretty printed representation + * myObject.toString() // => "\"[toJSON threw]\"" * * // Or extend in a constructor * function MyClass(this: any, name: string) { @@ -256,7 +258,7 @@ export const BaseProto: Inspectable = { * * **Example** (Extending the inspectable base class) * - * ```ts + * ```ts import.meta.vitest * import { Inspectable } from "effect" * * class User extends Inspectable.Class { @@ -279,11 +281,11 @@ export const BaseProto: Inspectable = { * } * * const user = new User(1, "Alice", "alice@example.com") - * console.log(user.toString()) // Pretty printed JSON with _tag, id, name, email - * console.log(user) // In Node.js, shows the same formatted output + * user.toString() // => "{\"_tag\":\"User\",\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\"}" + * user[Inspectable.NodeInspectSymbol]() // => { _tag: "User", id: 1, name: "Alice", email: "alice@example.com" } * ``` * - * @category classes + * @category models * @since 2.0.0 */ export abstract class Class { diff --git a/packages/effect/src/Iterable.ts b/packages/effect/src/Iterable.ts index 5be65e76f..be5547629 100644 --- a/packages/effect/src/Iterable.ts +++ b/packages/effect/src/Iterable.ts @@ -12,6 +12,8 @@ import type { NonEmptyArray } from "./Array.ts" import * as Equal from "./Equal.ts" import { dual } from "./Function.ts" +import * as Count from "./internal/count.ts" +import * as InternalRecord from "./internal/record.ts" import type { Option } from "./Option.ts" import * as O from "./Option.ts" import { isBoolean } from "./Predicate.ts" @@ -26,27 +28,27 @@ import type { NoInfer } from "./Types.ts" * * **Details** * - * The function is called with each index starting from `0`. If no length is - * specified, the iterable is infinite. This is useful for generating - * sequences, patterns, or any indexed data. + * The function is called with each index starting from `0`. If a length is + * provided, it is rounded down and normalized to at least `1`, with `NaN` + * treated as `1`. If no length is specified, the iterable is infinite. * * **Example** (Generating values by index) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Generate first 5 even numbers * const evens = Iterable.makeBy((n) => n * 2, { length: 5 }) - * console.log(Array.from(evens)) // [0, 2, 4, 6, 8] + * Array.from(evens) // => [0, 2, 4, 6, 8] * * // Generate squares * const squares = Iterable.makeBy((n) => n * n, { length: 4 }) - * console.log(Array.from(squares)) // [0, 1, 4, 9] + * Array.from(squares) // => [0, 1, 4, 9] * * // Infinite sequence (be careful when consuming!) * const naturals = Iterable.makeBy((n) => n) * const first10 = Iterable.take(naturals, 10) - * console.log(Array.from(first10)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * Array.from(first10) // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * ``` * * @category constructors @@ -55,7 +57,7 @@ import type { NoInfer } from "./Types.ts" export const makeBy = (f: (i: number) => A, options?: { readonly length?: number }): Iterable => { - const max = options?.length !== undefined ? Math.max(1, Math.floor(options.length)) : Infinity + const max = options?.length !== undefined ? Count.normalizeNonEmpty(options.length) : Infinity return { [Symbol.iterator]() { let i = 0 @@ -82,11 +84,10 @@ export const makeBy = (f: (i: number) => A, options?: { * * **Example** (Creating a range) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Array.from(Iterable.range(1, 3)), [1, 2, 3]) + * Array.from(Iterable.range(1, 3)) // => [1, 2, 3] * ``` * * @category constructors @@ -106,15 +107,15 @@ export const range = (start: number, end?: number): Iterable => { * * **Details** * - * `n` is normalized to an integer greater than or equal to `1`. + * `n` is rounded down and normalized to an integer greater than or equal to + * `1`. `NaN` is treated as `1`. * * **Example** (Repeating a value) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Array.from(Iterable.replicate("a", 3)), ["a", "a", "a"]) + * Array.from(Iterable.replicate("a", 3)) // => ["a", "a", "a"] * ``` * * @category constructors @@ -135,7 +136,8 @@ export const replicate: { * * **Details** * - * The result is lazy. Each repetition obtains a new iterator from `self`. + * The result is lazy. `n` is rounded down and normalized to at least `1`, with + * `NaN` treated as `1`. Each repetition obtains a new iterator from `self`. * * @see {@link forever} for repeating without an upper bound * @see {@link replicate} for repeating a single value @@ -173,15 +175,11 @@ export const forever = (self: Iterable): Iterable => repeat(self, Infin * * **Example** (Converting a record to entries) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(Array.from(Iterable.fromRecord(x)), [["a", 1], ["b", 2], [ - * "c", - * 3 - * ]]) + * Array.from(Iterable.fromRecord(x)) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @category converting @@ -202,17 +200,17 @@ export const fromRecord = (self: Readonly>): I * * **Example** (Prepending an element) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [2, 3, 4] * const withOne = Iterable.prepend(numbers, 1) - * console.log(Array.from(withOne)) // [1, 2, 3, 4] + * Array.from(withOne) // => [1, 2, 3, 4] * * // Works with any iterable * const letters = "abc" * const withZ = Iterable.prepend(letters, "z") - * console.log(Array.from(withZ)) // ["z", "a", "b", "c"] + * Array.from(withZ) // => ["z", "a", "b", "c"] * ``` * * @category combining @@ -228,14 +226,10 @@ export const prepend: { * * **Example** (Prepending another iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Array.from(Iterable.prependAll([1, 2], ["a", "b"])), - * ["a", "b", 1, 2] - * ) + * Array.from(Iterable.prependAll([1, 2], ["a", "b"])) // => ["a", "b", 1, 2] * ``` * * @category combining @@ -269,19 +263,11 @@ export const prependAll: { * * **Example** (Appending an element) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3] - * const withFour = Iterable.append(numbers, 4) - * console.log(Array.from(withFour)) // [1, 2, 3, 4] - * - * // Chain multiple appends - * const result = Iterable.append( - * Iterable.append([1, 2], 3), - * 4 - * ) - * console.log(Array.from(result)) // [1, 2, 3, 4] + * Array.from(Iterable.append(numbers, 4)) // => [1, 2, 3, 4] * ``` * * @see {@link prepend} for adding one element before the existing elements @@ -314,25 +300,21 @@ export const append: { * * **Example** (Concatenating iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * const first = [1, 2, 3] - * const second = [4, 5, 6] - * const combined = Iterable.appendAll(first, second) - * console.log(Array.from(combined)) // [1, 2, 3, 4, 5, 6] + * Array.from(Iterable.appendAll([1, 2, 3], [4, 5, 6])) // => [1, 2, 3, 4, 5, 6] * * // Works with different iterable types * const numbers = [1, 2] * const letters = "abc" * const mixed = Iterable.appendAll(numbers, letters) - * console.log(Array.from(mixed)) // [1, 2, "a", "b", "c"] + * Array.from(mixed) // => [1, 2, "a", "b", "c"] * * // Lazy evaluation - only consumes what's needed * const infinite = Iterable.range(1) * const finite = [0, -1, -2] - * const result = Iterable.take(Iterable.appendAll(finite, infinite), 5) - * console.log(Array.from(result)) // [0, -1, -2, 1, 2] + * Array.from(Iterable.take(Iterable.appendAll(finite, infinite), 5)) // => [0, -1, -2, 1, 2] * ``` * * @see {@link append} for appending one value instead of another iterable @@ -374,23 +356,23 @@ export const appendAll: { * * **Example** (Tracking running results) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Running sum of numbers * const numbers = [1, 2, 3, 4, 5] * const runningSum = Iterable.scan(numbers, 0, (acc, n) => acc + n) - * console.log(Array.from(runningSum)) // [0, 1, 3, 6, 10, 15] + * Array.from(runningSum) // => [0, 1, 3, 6, 10, 15] * * // Build strings progressively * const letters = ["a", "b", "c"] * const progressive = Iterable.scan(letters, "", (acc, letter) => acc + letter) - * console.log(Array.from(progressive)) // ["", "a", "ab", "abc"] + * Array.from(progressive) // => ["", "a", "ab", "abc"] * * // Track maximum values seen so far * const values = [3, 1, 4, 1, 5, 9, 2] * const runningMax = Iterable.scan(values, -Infinity, Math.max) - * console.log(Array.from(runningMax)) // [-Infinity, 3, 3, 4, 4, 5, 9, 9] + * Array.from(runningMax) // => [-Infinity, 3, 3, 4, 4, 5, 9, 9] * ``` * * @category folding @@ -424,12 +406,11 @@ export const scan: { * * **Example** (Checking for emptiness) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Iterable.isEmpty([]), true) - * assert.deepStrictEqual(Iterable.isEmpty([1, 2, 3]), false) + * Iterable.isEmpty([]) // => true + * Iterable.isEmpty([1, 2, 3]) // => false * ``` * * @category guards @@ -445,22 +426,22 @@ export const isEmpty = (self: Iterable): self is Iterable => { * * **Example** (Counting iterable elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] - * console.log(Iterable.size(numbers)) // 5 + * Iterable.size(numbers) // => 5 * * const empty = Iterable.empty() - * console.log(Iterable.size(empty)) // 0 + * Iterable.size(empty) // => 0 * * // Works with any iterable * const letters = "hello" - * console.log(Iterable.size(letters)) // 5 + * Iterable.size(letters) // => 5 * * // Note: This consumes the entire iterable * const range = Iterable.range(1, 100) - * console.log(Iterable.size(range)) // 100 + * Iterable.size(range) // => 100 * ``` * * @category getters @@ -480,24 +461,24 @@ export const size = (self: Iterable): number => { * * **Example** (Getting the first element) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" * * const numbers = [1, 2, 3] - * console.log(Iterable.head(numbers)) // Option.some(1) + * Iterable.head(numbers) // => Option.some(1) * * const empty = Iterable.empty() - * console.log(Iterable.head(empty)) // Option.none() + * Iterable.head(empty) // => Option.none() * * // Safe way to get first element * const firstEven = Iterable.head( * Iterable.filter([1, 3, 4, 5], (x) => x % 2 === 0) * ) - * console.log(firstEven) // Option.some(4) + * firstEven // => Option.some(4) * * // Use with Option methods * const doubled = Option.map(Iterable.head([5, 10, 15]), (x) => x * 2) - * console.log(doubled) // Option.some(10) + * doubled // => Option.some(10) * ``` * * @category getters @@ -523,21 +504,21 @@ export const head = (self: Iterable): Option => { * * **Example** (Getting the first element unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3] - * console.log(Iterable.headUnsafe(numbers)) // 1 + * Iterable.headUnsafe(numbers) // => 1 * * const letters = "hello" - * console.log(Iterable.headUnsafe(letters)) // "h" + * Iterable.headUnsafe(letters) // => "h" * * // Iterable.headUnsafe(Iterable.empty()) * // throws Error: "headUnsafe: empty iterable" * * // Use only when you're certain the iterable is non-empty * const nonEmpty = Iterable.range(1, 10) - * console.log(Iterable.headUnsafe(nonEmpty)) // 1 + * Iterable.headUnsafe(nonEmpty) // => 1 * ``` * * @category getters @@ -555,29 +536,30 @@ export const headUnsafe = (self: Iterable): A => { * * **Details** * - * `n` is normalized to a non-negative integer. + * `n` is rounded down and normalized to a non-negative integer. `NaN` is + * treated as `0`. * * **Example** (Taking from the start) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const firstThree = Iterable.take(numbers, 3) - * console.log(Array.from(firstThree)) // [1, 2, 3] + * Array.from(firstThree) // => [1, 2, 3] * * // Taking more than available returns all elements * const firstTen = Iterable.take(numbers, 10) - * console.log(Array.from(firstTen)) // [1, 2, 3, 4, 5] + * Array.from(firstTen) // => [1, 2, 3, 4, 5] * * // Taking 0 or negative returns empty * const none = Iterable.take(numbers, 0) - * console.log(Array.from(none)) // [] + * Array.from(none) // => [] * * // Useful with infinite iterables * const naturals = Iterable.range(1) * const firstFive = Iterable.take(naturals, 5) - * console.log(Array.from(firstFive)) // [1, 2, 3, 4, 5] + * Array.from(firstFive) // => [1, 2, 3, 4, 5] * ``` * * @category getters @@ -586,21 +568,24 @@ export const headUnsafe = (self: Iterable): A => { export const take: { (n: number): (self: Iterable) => Iterable (self: Iterable, n: number): Iterable -} = dual(2, (self: Iterable, n: number): Iterable => ({ - [Symbol.iterator]() { - let i = 0 - const iterator = self[Symbol.iterator]() - return { - next() { - if (i < n) { - i++ - return iterator.next() +} = dual(2, (self: Iterable, n: number): Iterable => { + const count = Count.normalize(n) + return { + [Symbol.iterator]() { + let i = 0 + const iterator = self[Symbol.iterator]() + return { + next() { + if (i < count) { + i++ + return iterator.next() + } + return { done: true, value: undefined } } - return { done: true, value: undefined } } } } -})) +}) /** * Takes the longest initial `Iterable` prefix for which all elements satisfy the @@ -608,22 +593,22 @@ export const take: { * * **Example** (Taking while a predicate holds) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [2, 4, 6, 8, 3, 10, 12] * const evenPrefix = Iterable.takeWhile(numbers, (x) => x % 2 === 0) - * console.log(Array.from(evenPrefix)) // [2, 4, 6, 8] + * Array.from(evenPrefix) // => [2, 4, 6, 8] * * // With index * const letters = ["a", "b", "c", "d", "e"] * const firstThreeByIndex = Iterable.takeWhile(letters, (_, i) => i < 3) - * console.log(Array.from(firstThreeByIndex)) // ["a", "b", "c"] + * Array.from(firstThreeByIndex) // => ["a", "b", "c"] * * // Stops at first non-matching element * const mixed = [1, 3, 5, 4, 7, 9] * const oddPrefix = Iterable.takeWhile(mixed, (x) => x % 2 === 1) - * console.log(Array.from(oddPrefix)) // [1, 3, 5] + * Array.from(oddPrefix) // => [1, 3, 5] * * // Type refinement * const values: Array = ["a", "b", "c", 1, "d"] @@ -631,7 +616,7 @@ export const take: { * values, * (x): x is string => typeof x === "string" * ) - * console.log(Array.from(stringPrefix)) // ["a", "b", "c"] (typed as string[]) + * Array.from(stringPrefix) // => ["a", "b", "c"] * ``` * * @category getters @@ -663,28 +648,29 @@ export const takeWhile: { * * **Details** * - * `n` is normalized to a non-negative integer. + * `n` is rounded down and normalized to a non-negative integer. `NaN` is + * treated as `0`. * * **Example** (Dropping from the start) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] * const withoutFirstTwo = Iterable.drop(numbers, 2) - * console.log(Array.from(withoutFirstTwo)) // [3, 4, 5] + * Array.from(withoutFirstTwo) // => [3, 4, 5] * * // Dropping more than available returns empty * const withoutFirstTen = Iterable.drop(numbers, 10) - * console.log(Array.from(withoutFirstTen)) // [] + * Array.from(withoutFirstTen) // => [] * * // Dropping 0 or negative returns all elements * const all = Iterable.drop(numbers, 0) - * console.log(Array.from(all)) // [1, 2, 3, 4, 5] + * Array.from(all) // => [1, 2, 3, 4, 5] * * // Combine with take for slicing * const slice = Iterable.take(Iterable.drop(numbers, 1), 3) - * console.log(Array.from(slice)) // [2, 3, 4] + * Array.from(slice) // => [2, 3, 4] * ``` * * @category getters @@ -693,24 +679,27 @@ export const takeWhile: { export const drop: { (n: number): (self: Iterable) => Iterable (self: Iterable, n: number): Iterable -} = dual(2, (self: Iterable, n: number): Iterable => ({ - [Symbol.iterator]() { - const iterator = self[Symbol.iterator]() - let i = 0 - return { - next() { - while (i < n) { - const result = iterator.next() - if (result.done) { - return { done: true, value: undefined } +} = dual(2, (self: Iterable, n: number): Iterable => { + const count = Count.normalize(n) + return { + [Symbol.iterator]() { + const iterator = self[Symbol.iterator]() + let i = 0 + return { + next() { + while (i < count) { + const result = iterator.next() + if (result.done) { + return { done: true, value: undefined } + } + i++ } - i++ + return iterator.next() } - return iterator.next() } } } -})) +}) /** * Returns the first element that satisfies the specified @@ -718,20 +707,20 @@ export const drop: { * * **Example** (Finding the first match) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" * * const numbers = [1, 3, 4, 6, 8] * const firstEven = Iterable.findFirst(numbers, (x) => x % 2 === 0) - * console.log(firstEven) // Option.some(4) + * firstEven // => Option.some(4) * * const firstGreaterThan10 = Iterable.findFirst(numbers, (x) => x > 10) - * console.log(firstGreaterThan10) // Option.none() + * firstGreaterThan10 // => Option.none() * * // With index * const letters = ["a", "b", "c", "d"] * const atEvenIndex = Iterable.findFirst(letters, (_, i) => i % 2 === 0) - * console.log(atEvenIndex) // Option.some("a") + * atEvenIndex // => Option.some("a") * * // Type refinement * const mixed: Array = [1, "hello", 2, "world"] @@ -739,17 +728,17 @@ export const drop: { * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(firstString) // Option.some("hello") + * firstString // => Option.some("hello") * * // Transform during search * const findSquareRoot = Iterable.findFirst([1, 4, 9, 16], (x) => { * const sqrt = Math.sqrt(x) * return Number.isInteger(sqrt) ? Option.some(sqrt) : Option.none() * }) - * console.log(findSquareRoot) // Option.some(1) + * findSquareRoot // => Option.some(1) * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findFirst: { @@ -785,20 +774,20 @@ export const findFirst: { * * **Example** (Finding the last match) * - * ```ts - * import { Iterable } from "effect" + * ```ts import.meta.vitest + * import { Iterable, Option } from "effect" * * const numbers = [1, 3, 4, 6, 8, 2] * const lastEven = Iterable.findLast(numbers, (x) => x % 2 === 0) - * console.log(lastEven) // Option.some(2) + * lastEven // => Option.some(2) * * const lastGreaterThan10 = Iterable.findLast(numbers, (x) => x > 10) - * console.log(lastGreaterThan10) // Option.none() + * lastGreaterThan10 // => Option.none() * * // With index * const letters = ["a", "b", "c", "d", "e"] * const lastAtEvenIndex = Iterable.findLast(letters, (_, i) => i % 2 === 0) - * console.log(lastAtEvenIndex) // Option.some("e") (index 4) + * lastAtEvenIndex // => Option.some("e") * * // Type refinement * const mixed: Array = [1, "hello", 2, "world", 3] @@ -806,10 +795,10 @@ export const findFirst: { * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(lastString) // Option.some("world") + * lastString // => Option.some("world") * ``` * - * @category elements + * @category searching * @since 2.0.0 */ export const findLast: { @@ -846,31 +835,31 @@ export const findLast: { * * **Example** (Zipping iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3] * const letters = ["a", "b", "c"] * const zipped = Iterable.zip(numbers, letters) - * console.log(Array.from(zipped)) // [[1, "a"], [2, "b"], [3, "c"]] + * Array.from(zipped) // => [[1, "a"], [2, "b"], [3, "c"]] * * // Different lengths - shorter one determines result length * const short = [1, 2] * const long = ["a", "b", "c", "d"] * const partial = Iterable.zip(short, long) - * console.log(Array.from(partial)) // [[1, "a"], [2, "b"]] + * Array.from(partial) // => [[1, "a"], [2, "b"]] * * // Works with any iterables * const range = Iterable.range(1, 3) * const word = "abc" * const mixed = Iterable.zip(range, word) - * console.log(Array.from(mixed)) // [[1, "a"], [2, "b"], [3, "c"]] + * Array.from(mixed) // => [[1, "a"], [2, "b"], [3, "c"]] * * // Create indexed pairs * const values = ["apple", "banana", "cherry"] * const indices = Iterable.range(0, 2) * const indexed = Iterable.zip(indices, values) - * console.log(Array.from(indexed)) // [[0, "apple"], [1, "banana"], [2, "cherry"]] + * Array.from(indexed) // => [[0, "apple"], [1, "banana"], [2, "cherry"]] * ``` * * @category zipping @@ -890,14 +879,14 @@ export const zip: { * * **Example** (Zipping with a combining function) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Add corresponding elements * const a = [1, 2, 3, 4] * const b = [10, 20, 30, 40] * const sums = Iterable.zipWith(a, b, (x, y) => x + y) - * console.log(Array.from(sums)) // [11, 22, 33, 44] + * Array.from(sums) // => [11, 22, 33, 44] * * // Combine strings * const firstNames = ["John", "Jane", "Bob"] @@ -907,7 +896,7 @@ export const zip: { * lastNames, * (first, last) => `${first} ${last}` * ) - * console.log(Array.from(fullNames)) // ["John Doe", "Jane Smith", "Bob Johnson"] + * Array.from(fullNames) // => ["John Doe", "Jane Smith", "Bob Johnson"] * * // Different lengths - stops at shorter * const short = [1, 2] @@ -917,7 +906,7 @@ export const zip: { * long, * (num, letter) => `${num}${letter}` * ) - * console.log(Array.from(combined)) // ["1a", "2b"] + * Array.from(combined) // => ["1a", "2b"] * * // Complex transformations * const prices = [10.99, 25.50, 5.00] @@ -925,7 +914,7 @@ export const zip: { * const totals = Iterable.zipWith(prices, quantities, (price, qty) => { * return Math.round(price * qty * 100) / 100 // round to 2 decimal places * }) - * console.log(Array.from(totals)) // [21.98, 25.5, 15] + * Array.from(totals) // => [21.98, 25.5, 15] * ``` * * @category zipping @@ -964,33 +953,33 @@ export const zipWith: { * * **Example** (Interspersing separators) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Join numbers with separator * const numbers = [1, 2, 3, 4] * const withCommas = Iterable.intersperse(numbers, ",") - * console.log(Array.from(withCommas)) // [1, ",", 2, ",", 3, ",", 4] + * Array.from(withCommas) // => [1, ",", 2, ",", 3, ",", 4] * * // Join words with spaces * const words = ["hello", "world", "from", "effect"] * const sentence = Iterable.intersperse(words, " ") - * console.log(Array.from(sentence).join("")) // "hello world from effect" + * Array.from(sentence).join("") // => "hello world from effect" * * // Empty iterable remains empty * const empty = Iterable.empty() * const stillEmpty = Iterable.intersperse(empty, "-") - * console.log(Array.from(stillEmpty)) // [] + * Array.from(stillEmpty) // => [] * * // Single element has no separators added * const single = [42] * const noSeparator = Iterable.intersperse(single, "|") - * console.log(Array.from(noSeparator)) // [42] + * Array.from(noSeparator) // => [42] * * // Build CSS-like strings * const styles = ["color: red", "font-size: 14px", "margin: 10px"] * const css = Iterable.intersperse(styles, "; ") - * console.log(Array.from(css).join("")) // "color: red; font-size: 14px; margin: 10px" + * Array.from(css).join("") // => "color: red; font-size: 14px; margin: 10px" * ``` * * @category combining @@ -1026,7 +1015,7 @@ export const intersperse: { * * **Example** (Checking membership with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Custom equivalence for objects @@ -1035,7 +1024,7 @@ export const intersperse: { * * const users = [{ id: 1 }, { id: 2 }] * const hasUser1 = containsById(users, { id: 1 }) - * console.log(hasUser1) // true (same id) + * hasUser1 // => true * * // Case-insensitive string comparison * const caseInsensitive = (a: string, b: string) => @@ -1044,7 +1033,7 @@ export const intersperse: { * * const words = ["Hello", "World"] * const hasHello = containsCaseInsensitive(words, "hello") - * console.log(hasHello) // true + * hasHello // => true * * // Approximate number comparison * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.1 @@ -1052,10 +1041,10 @@ export const intersperse: { * * const values = [1.0, 2.0, 3.0] * const hasAlmostTwo = containsApprox(values, 2.05) - * console.log(hasAlmostTwo) // true + * hasAlmostTwo // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -1082,29 +1071,29 @@ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { * * **Example** (Checking membership) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5] - * console.log(Iterable.contains(numbers, 3)) // true - * console.log(Iterable.contains(numbers, 6)) // false + * Iterable.contains(numbers, 3) // => true + * Iterable.contains(numbers, 6) // => false * * const letters = "hello" - * console.log(Iterable.contains(letters, "l")) // true - * console.log(Iterable.contains(letters, "x")) // false + * Iterable.contains(letters, "l") // => true + * Iterable.contains(letters, "x") // => false * * // Works with any iterable * const range = Iterable.range(1, 100) - * console.log(Iterable.contains(range, 50)) // true - * console.log(Iterable.contains(range, 150)) // false + * Iterable.contains(range, 50) // => true + * Iterable.contains(range, 150) // => false * * // Curried version * const containsThree = Iterable.contains(3) - * console.log(containsThree([1, 2, 3])) // true - * console.log(containsThree([4, 5, 6])) // false + * containsThree([1, 2, 3]) // => true + * containsThree([4, 5, 6]) // => false * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -1115,28 +1104,27 @@ export const contains: { /** * Splits an `Iterable` into length-`n` pieces. The last piece will be shorter if `n` does not evenly divide the length of * the `Iterable`. + * `n` is rounded down and normalized to at least `1`; `NaN` and non-positive + * values therefore produce singleton pieces. * * **Example** (Chunking an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] * const chunks = Iterable.chunksOf(numbers, 3) - * console.log(Array.from(chunks).map((chunk) => Array.from(chunk))) - * // [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + * Array.from(chunks) // => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] * * // Last chunk can be shorter * const uneven = [1, 2, 3, 4, 5, 6, 7] * const chunks2 = Iterable.chunksOf(uneven, 3) - * console.log(Array.from(chunks2).map((chunk) => Array.from(chunk))) - * // [[1, 2, 3], [4, 5, 6], [7]] + * Array.from(chunks2) // => [[1, 2, 3], [4, 5, 6], [7]] * * // Chunk size larger than iterable * const small = [1, 2] * const chunks3 = Iterable.chunksOf(small, 5) - * console.log(Array.from(chunks3).map((chunk) => Array.from(chunk))) - * // [[1, 2]] + * Array.from(chunks3) // => [[1, 2]] * * // Process data in batches * const data = Iterable.range(1, 100) @@ -1145,7 +1133,7 @@ export const contains: { * batches, * (batch) => Iterable.reduce(batch, 0, (sum, n) => sum + n) * ) - * console.log(Array.from(Iterable.take(batchSums, 3))) // [55, 155, 255] + * Array.from(Iterable.take(batchSums, 3)) // => [55, 155, 255] * ``` * * @category splitting @@ -1155,7 +1143,7 @@ export const chunksOf: { (n: number): (self: Iterable) => Iterable> (self: Iterable, n: number): Iterable> } = dual(2, (self: Iterable, n: number): Iterable> => { - const safeN = Math.max(1, Math.floor(n)) + const safeN = Count.normalizeNonEmpty(n) return ({ [Symbol.iterator]() { let iterator: Iterator | undefined = self[Symbol.iterator]() @@ -1187,35 +1175,31 @@ export const chunksOf: { * * **Example** (Grouping consecutive elements with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Group consecutive equal numbers * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] * const grouped = Iterable.groupWith(numbers, (a, b) => a === b) - * console.log(Array.from(grouped)) - * // [[1, 1], [2, 2, 2], [3], [1, 1]] + * Array.from(grouped) // => [[1, 1], [2, 2, 2], [3], [1, 1]] * * // Case-insensitive grouping of strings * const words = ["Apple", "APPLE", "banana", "Banana", "cherry"] * const caseInsensitive = (a: string, b: string) => * a.toLowerCase() === b.toLowerCase() * const groupedWords = Iterable.groupWith(words, caseInsensitive) - * console.log(Array.from(groupedWords)) - * // [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]] + * Array.from(groupedWords) // => [["Apple", "APPLE"], ["banana", "Banana"], ["cherry"]] * * // Group by approximate equality * const floats = [1.1, 1.12, 1.9, 2.01, 2.05, 3.5] * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.2 * const groupedFloats = Iterable.groupWith(floats, approxEqual) - * console.log(Array.from(groupedFloats)) - * // [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]] + * Array.from(groupedFloats) // => [[1.1, 1.12], [1.9, 2.01, 2.05], [3.5]] * * // Only groups consecutive elements * const scattered = [1, 2, 1, 2, 1] * const scatteredGroups = Iterable.groupWith(scattered, (a, b) => a === b) - * console.log(Array.from(scatteredGroups)) - * // [[1], [2], [1], [2], [1]] (no grouping since none are consecutive) + * Array.from(scatteredGroups) // => [[1], [2], [1], [2], [1]] * ``` * * @category grouping @@ -1266,18 +1250,16 @@ export const groupWith: { * * **Example** (Grouping consecutive elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] * const grouped = Iterable.group(numbers) - * console.log(Array.from(grouped)) - * // [[1, 1], [2, 2, 2], [3], [1, 1]] + * Array.from(grouped) // => [[1, 1], [2, 2, 2], [3], [1, 1]] * * const letters = "aabbccaa" * const groupedLetters = Iterable.group(letters) - * console.log(Array.from(groupedLetters)) - * // [["a", "a"], ["b", "b"], ["c", "c"], ["a", "a"]] + * Array.from(groupedLetters) // => [["a", "a"], ["b", "b"], ["c", "c"], ["a", "a"]] * * // Works with objects using deep equality * const objects = [ @@ -1287,7 +1269,7 @@ export const groupWith: { * { type: "A", value: 1 } * ] * const groupedObjects = Iterable.group(objects) - * console.log(Array.from(groupedObjects).length) // 3 groups + * Array.from(groupedObjects).length // => 3 * // Note: Only consecutive equal objects are grouped together * ``` * @@ -1307,22 +1289,27 @@ export const group: (self: Iterable) => Iterable> = group * that produced that key. Unlike `group`, matching elements do not need to be * consecutive. * + * **Gotchas** + * + * When the key function returns a finite union of string literals or unique + * symbols, the result preserves those keys as optional properties because the + * input may not produce every key. Open `string` and `symbol` key types retain + * their record index signatures. + * * **Example** (Grouping by a key) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Group by string length * const words = ["a", "bb", "ccc", "dd", "eee", "f"] * const byLength = Iterable.groupBy(words, (word) => word.length.toString()) - * console.log(byLength) - * // { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] } + * byLength // => { "1": ["a", "f"], "2": ["bb", "dd"], "3": ["ccc", "eee"] } * * // Group by first letter * const names = ["Alice", "Bob", "Charlie", "David", "Anna", "Betty"] * const byFirstLetter = Iterable.groupBy(names, (name) => name[0]) - * console.log(byFirstLetter) - * // { "A": ["Alice", "Anna"], "B": ["Bob", "Betty"], "C": ["Charlie"], "D": ["David"] } + * byFirstLetter // => { A: ["Alice", "Anna"], B: ["Bob", "Betty"], C: ["Charlie"], D: ["David"] } * * // Group by category * const items = [ @@ -1332,17 +1319,12 @@ export const group: (self: Iterable) => Iterable> = group * { name: "broccoli", category: "vegetable" } * ] * const byCategory = Iterable.groupBy(items, (item) => item.category) - * console.log(byCategory) - * // { - * // "fruit": [{ name: "apple", category: "fruit" }, { name: "banana", category: "fruit" }], - * // "vegetable": [{ name: "carrot", category: "vegetable" }, { name: "broccoli", category: "vegetable" }] - * // } + * Object.keys(byCategory) // => ["fruit", "vegetable"] * * // Group numbers by even/odd * const numbers = [1, 2, 3, 4, 5, 6] * const evenOdd = Iterable.groupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd") - * console.log(evenOdd) - * // { "odd": [1, 3, 5], "even": [2, 4, 6] } + * evenOdd // => { odd: [1, 3, 5], even: [2, 4, 6] } * ``` * * @category grouping @@ -1351,22 +1333,22 @@ export const group: (self: Iterable) => Iterable> = group export const groupBy: { ( f: (a: A) => K - ): (self: Iterable) => Record, NonEmptyArray> + ): (self: Iterable) => Record.ReadonlyRecord.GroupByResult> ( self: Iterable, f: (a: A) => K - ): Record, NonEmptyArray> + ): Record.ReadonlyRecord.GroupByResult> } = dual(2, ( self: Iterable, f: (a: A) => K -): Record, NonEmptyArray> => { +): Record.ReadonlyRecord.GroupByResult> => { const out: Record> = {} for (const a of self) { const k = f(a) if (Object.hasOwn(out, k)) { out[k].push(a) } else { - out[k] = [a] + InternalRecord.assignProperty(out, k, [a]) } } return out @@ -1393,18 +1375,10 @@ const constEmptyIterator: Iterator = { * * **Example** (Creating an empty iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * const empty = Iterable.empty() - * console.log(Array.from(empty)) // [] - * console.log(Iterable.isEmpty(empty)) // true - * - * // Useful as base case for reductions - * const hasData = true - * const result = hasData - * ? Iterable.range(1, 5) - * : Iterable.empty() + * Array.from(Iterable.empty()) // => [] * ``` * * @category constructors @@ -1422,11 +1396,11 @@ export const empty = (): Iterable => constEmpty * * **Example** (Wrapping a single value) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const single = Iterable.of(42) - * console.log(Array.from(single)) // [42] + * Array.from(single) // => [42] * * // Useful for creating homogeneous sequences * const sequences = [ @@ -1441,7 +1415,7 @@ export const empty = (): Iterable => constEmpty * numbers, * (n) => n % 2 === 0 ? Iterable.of(n) : Iterable.empty() * ) - * console.log(Array.from(evensOnly)) // [2, 4] + * Array.from(evensOnly) // => [2, 4] * ``` * * @category constructors @@ -1461,24 +1435,22 @@ export const of = (a: A): Iterable => [a] * * **Example** (Mapping elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Transform numbers to their squares * const numbers = [1, 2, 3, 4, 5] * const squares = Iterable.map(numbers, (x) => x * x) - * console.log(Array.from(squares)) // [1, 4, 9, 16, 25] + * Array.from(squares) // => [1, 4, 9, 16, 25] * * // Use index in transformation * const indexed = Iterable.map(["a", "b", "c"], (char, i) => `${i}: ${char}`) - * console.log(Array.from(indexed)) // ["0: a", "1: b", "2: c"] + * Array.from(indexed) // => ["0: a", "1: b", "2: c"] * - * // Chain transformations - * const result = Iterable.map( + * Array.from(Iterable.map( * Iterable.map([1, 2, 3], (x) => x * 2), * (x) => x + 1 - * ) - * console.log(Array.from(result)) // [3, 5, 7] + * )) // => [3, 5, 7] * ``` * * @category mapping @@ -1510,18 +1482,18 @@ export const map: { * * **Example** (Flat mapping iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Expand each number to a range * const numbers = [1, 2, 3] * const expanded = Iterable.flatMap(numbers, (n) => Iterable.range(1, n)) - * console.log(Array.from(expanded)) // [1, 1, 2, 1, 2, 3] + * Array.from(expanded) // => [1, 1, 2, 1, 2, 3] * * // Split strings into characters * const words = ["hi", "bye"] * const chars = Iterable.flatMap(words, (word) => word) - * console.log(Array.from(chars)) // ["h", "i", "b", "y", "e"] + * Array.from(chars) // => ["h", "i", "b", "y", "e"] * * // Conditional expansion with empty iterables * const values = [1, 2, 3, 4, 5] @@ -1529,7 +1501,7 @@ export const map: { * values, * (n) => n % 2 === 0 ? [n, n * 2, n * 3] : [] * ) - * console.log(Array.from(evenMultiples)) // [2, 4, 6, 4, 8, 12] + * Array.from(evenMultiples) // => [2, 4, 6, 4, 8, 12] * * // Use index in transformation * const letters = ["a", "b", "c"] @@ -1537,7 +1509,7 @@ export const map: { * letters, * (letter, i) => Iterable.replicate(letter, i + 1) * ) - * console.log(Array.from(indexed)) // ["a", "b", "b", "c", "c", "c"] + * Array.from(indexed) // => ["a", "b", "b", "c", "c", "c"] * ``` * * @category sequencing @@ -1558,29 +1530,29 @@ export const flatMap: { * * **Example** (Flattening nested iterables) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Flatten nested arrays * const nested = [[1, 2], [3, 4], [5, 6]] * const flat = Iterable.flatten(nested) - * console.log(Array.from(flat)) // [1, 2, 3, 4, 5, 6] + * Array.from(flat) // => [1, 2, 3, 4, 5, 6] * * // Flatten different iterable types * const mixed: Array> = ["ab", "cd"] * const flatMixed = Iterable.flatten(mixed) - * console.log(Array.from(flatMixed)) // ["a", "b", "c", "d"] + * Array.from(flatMixed) // => ["a", "b", "c", "d"] * * // Flatten deeply nested (only one level) * const deepNested = [[[1, 2]], [[3, 4]]] * const oneLevelFlat = Iterable.flatten(deepNested) - * console.log(Array.from(oneLevelFlat).map((arr) => Array.from(arr))) + * Array.from(oneLevelFlat) // => [[1, 2], [3, 4]] * // [[1, 2], [3, 4]] (still contains arrays) * * // Empty iterables are handled correctly * const withEmpty = [[1, 2], [], [3, 4], []] * const flatWithEmpty = Iterable.flatten(withEmpty) - * console.log(Array.from(flatWithEmpty)) // [1, 2, 3, 4] + * Array.from(flatWithEmpty) // => [1, 2, 3, 4] * ``` * * @category sequencing @@ -1591,19 +1563,20 @@ export const flatten = (self: Iterable>): Iterable => ({ const outerIterator = self[Symbol.iterator]() let innerIterator: Iterator | undefined function next() { - if (innerIterator === undefined) { - const next = outerIterator.next() - if (next.done) { - return next + while (true) { + if (innerIterator === undefined) { + const next = outerIterator.next() + if (next.done) { + return next + } + innerIterator = next.value[Symbol.iterator]() + } + const result = innerIterator.next() + if (!result.done) { + return result } - innerIterator = next.value[Symbol.iterator]() - } - const result = innerIterator.next() - if (result.done) { innerIterator = undefined - return next() } - return result } return { next } } @@ -1620,7 +1593,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * * **Example** (Filtering and transforming Result values) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" * * // Parse strings to numbers, keeping only valid ones @@ -1629,7 +1602,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * const num = parseInt(s) * return isNaN(num) ? Result.failVoid : Result.succeed(num) * }) - * console.log(Array.from(numbers)) // [1, 2, 4] + * Array.from(numbers) // => [1, 2, 4] * * // Extract specific properties from objects * const users = [ @@ -1643,7 +1616,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * (user) => * user.age >= 18 && user.email ? Result.succeed(user.email) : Result.failVoid * ) - * console.log(Array.from(adultEmails)) // ["alice@example.com", "charlie@example.com"] + * Array.from(adultEmails) // => ["alice@example.com", "charlie@example.com"] * * // Use index in transformation * const items = ["a", "b", "c", "d", "e"] @@ -1651,7 +1624,7 @@ export const flatten = (self: Iterable>): Iterable => ({ * items, * (item, i) => i % 2 === 0 ? Result.succeed(`${i}: ${item}`) : Result.failVoid * ) - * console.log(Array.from(evenIndexItems)) // ["0: a", "2: c", "4: e"] + * Array.from(evenIndexItems) // => ["0: a", "2: c", "4: e"] * ``` * * @category filtering @@ -1688,7 +1661,7 @@ export const filterMap: { * * **Example** (Filtering and transforming until failure) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" * * // Parse numbers until we hit an invalid one @@ -1697,7 +1670,7 @@ export const filterMap: { * const num = parseInt(s) * return isNaN(num) ? Result.failVoid : Result.succeed(num) * }) - * console.log(Array.from(numbers)) // [1, 2, 3] (stops at "invalid") + * Array.from(numbers) // => [1, 2, 3] * * // Take elements while they meet a condition and transform them * const values = [2, 4, 6, 7, 8, 10] @@ -1705,7 +1678,7 @@ export const filterMap: { * values, * (n) => n % 2 === 0 ? Result.succeed(n * 2) : Result.failVoid * ) - * console.log(Array.from(doubledEvens)) // [4, 8, 12] (stops at 7) + * Array.from(doubledEvens) // => [4, 8, 12] * * // Process with index until condition fails * const letters = ["a", "b", "c", "d", "e"] @@ -1713,7 +1686,7 @@ export const filterMap: { * letters, * (letter, i) => letter !== "c" ? Result.succeed(`${i}: ${letter}`) : Result.failVoid * ) - * console.log(Array.from(indexedUntilC)) // ["0: a", "1: b"] (stops at "c") + * Array.from(indexedUntilC) // => ["0: a", "1: b"] * ``` * * @category filtering @@ -1747,16 +1720,10 @@ export const filterMapWhile: { * * **Example** (Extracting Some values) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Array.from( - * Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)]) - * ), - * [1, 2] - * ) + * Array.from(Iterable.getSomes([Option.some(1), Option.none(), Option.some(2)])) // => [1, 2] * ``` * * @category filtering @@ -1788,20 +1755,14 @@ export const getSomes = (self: Iterable>): Iterable => { * * **Example** (Extracting failures) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * Array.from( - * Iterable.getFailures([ - * Result.succeed(1), - * Result.fail("err"), - * Result.succeed(2) - * ]) - * ), - * ["err"] - * ) + * + * Array.from(Iterable.getFailures([ + * Result.succeed(1), + * Result.fail("err"), + * Result.succeed(2) + * ])) // => ["err"] * ``` * * @category filtering @@ -1833,20 +1794,14 @@ export const getFailures = (self: Iterable>): Iterable = * * **Example** (Extracting successes) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Result } from "effect" - * import * as assert from "node:assert" - * - * assert.deepStrictEqual( - * Array.from( - * Iterable.getSuccesses([ - * Result.succeed(1), - * Result.fail("err"), - * Result.succeed(2) - * ]) - * ), - * [1, 2] - * ) + * + * Array.from(Iterable.getSuccesses([ + * Result.succeed(1), + * Result.fail("err"), + * Result.succeed(2) + * ])) // => [1, 2] * ``` * * @category filtering @@ -1883,18 +1838,18 @@ export const getSuccesses = (self: Iterable>): Iterable * * **Example** (Filtering elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Filter even numbers * const numbers = [1, 2, 3, 4, 5, 6] * const evens = Iterable.filter(numbers, (x) => x % 2 === 0) - * console.log(Array.from(evens)) // [2, 4, 6] + * Array.from(evens) // => [2, 4, 6] * * // Filter with index * const items = ["a", "b", "c", "d"] * const oddPositions = Iterable.filter(items, (_, i) => i % 2 === 1) - * console.log(Array.from(oddPositions)) // ["b", "d"] + * Array.from(oddPositions) // => ["b", "d"] * * // Type refinement * const mixed: Array = ["hello", 42, "world", 100] @@ -1902,14 +1857,14 @@ export const getSuccesses = (self: Iterable>): Iterable * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(Array.from(onlyStrings)) // ["hello", "world"] (typed as string[]) + * Array.from(onlyStrings) // => ["hello", "world"] * * // Combine with map * const processed = Iterable.map( * Iterable.filter([1, 2, 3, 4, 5], (x) => x > 2), * (x) => x * 10 * ) - * console.log(Array.from(processed)) // [30, 40, 50] + * Array.from(processed) // => [30, 40, 50] * ``` * * @category filtering @@ -1952,7 +1907,7 @@ export const filter: { * * **Example** (Flat mapping nullable results) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Extract valid elements from nullable function results @@ -1961,7 +1916,7 @@ export const filter: { * const num = parseInt(s) * return isNaN(num) ? null : num * 2 * }) - * console.log(Array.from(parsed)) // [2, 4, 8] + * Array.from(parsed) // => [2, 4, 8] * * // Safe property access * const objects = [ @@ -1971,7 +1926,7 @@ export const filter: { * {} * ] * const values = Iterable.flatMapNullishOr(objects, (obj) => obj.nested?.value) - * console.log(Array.from(values)) // [10, 20] + * Array.from(values) // => [10, 20] * * // Working with Map.get (returns undefined for missing keys) * const map = new Map([ @@ -1981,7 +1936,7 @@ export const filter: { * ]) * const keys = ["a", "x", "b", "y", "c"] * const foundValues = Iterable.flatMapNullishOr(keys, (key) => map.get(key)) - * console.log(Array.from(foundValues)) // [1, 2, 3] + * Array.from(foundValues) // => [1, 2, 3] * ``` * * @category sequencing @@ -2004,21 +1959,21 @@ export const flatMapNullishOr: { * * **Example** (Checking whether some element matches) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * const numbers = [1, 3, 5, 7, 8] * const hasEven = Iterable.some(numbers, (x) => x % 2 === 0) - * console.log(hasEven) // true (because of 8) + * hasEven // => true * * const allOdd = [1, 3, 5, 7] * const hasEvenInAllOdd = Iterable.some(allOdd, (x) => x % 2 === 0) - * console.log(hasEvenInAllOdd) // false + * hasEvenInAllOdd // => false * * // With index * const letters = ["a", "b", "c"] * const hasElementAtIndex2 = Iterable.some(letters, (_, i) => i === 2) - * console.log(hasElementAtIndex2) // true + * hasElementAtIndex2 // => true * * // Early termination - stops at first match * const infiniteOdds = Iterable.filter(Iterable.range(1), (x) => x % 2 === 1) @@ -2026,7 +1981,7 @@ export const flatMapNullishOr: { * Iterable.take(infiniteOdds, 1000), * (x) => x % 2 === 0 * ) - * console.log(hasEvenInInfiniteOdds) // false (quickly, doesn't check all 1000) + * hasEvenInInfiniteOdds // => false * * // Type guard usage * const mixed: Array = [1, 2, "hello"] @@ -2034,10 +1989,10 @@ export const flatMapNullishOr: { * mixed, * (x): x is string => typeof x === "string" * ) - * console.log(hasString) // true + * hasString // => true * ``` * - * @category elements + * @category predicates * @since 2.0.0 */ export const some: { @@ -2068,21 +2023,21 @@ export const some: { * * **Example** (Unfolding state into values) * - * ```ts + * ```ts import.meta.vitest * import { Iterable, Option } from "effect" * * // Generate Fibonacci sequence * const fibonacci = Iterable.unfold([0, 1], ([a, b]) => Option.some([a, [b, a + b]])) * const first10Fib = Iterable.take(fibonacci, 10) - * console.log(Array.from(first10Fib)) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] + * Array.from(first10Fib) // => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] * * // Generate powers of 2 up to a limit * const powersOf2 = Iterable.unfold(1, (n) => n <= 1000 ? Option.some([n, n * 2]) : Option.none()) - * console.log(Array.from(powersOf2)) // [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + * Array.from(powersOf2) // => [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] * * // Generate countdown * const countdown = Iterable.unfold(5, (n) => n > 0 ? Option.some([n, n - 1]) : Option.none()) - * console.log(Array.from(countdown)) // [5, 4, 3, 2, 1] + * Array.from(countdown) // => [5, 4, 3, 2, 1] * * // Generate collatz sequence * const collatz = Iterable.unfold(7, (n) => { @@ -2090,7 +2045,7 @@ export const some: { * const next = n % 2 === 0 ? n / 2 : n * 3 + 1 * return Option.some([n, next]) * }) - * console.log(Array.from(collatz)) // [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2] + * Array.from(collatz) // => [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2] * ``` * * @category constructors @@ -2118,37 +2073,40 @@ export const unfold = (b: B, f: (b: B) => Option): Iterab * * **Example** (Iterating with side effects) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * // Print each element + * // Collect each visited element * const numbers = [1, 2, 3, 4, 5] - * Iterable.forEach(numbers, (n) => console.log(n)) - * // Prints: 1, 2, 3, 4, 5 + * const visited: Array = [] + * Iterable.forEach(numbers, (n) => visited.push(n)) + * visited // => [1, 2, 3, 4, 5] * * // Use index in the callback * const letters = ["a", "b", "c"] + * const indexed: Array = [] * Iterable.forEach(letters, (letter, i) => { - * console.log(`${i}: ${letter}`) + * indexed.push(`${i}: ${letter}`) * }) - * // Prints: "0: a", "1: b", "2: c" + * indexed // => ["0: a", "1: b", "2: c"] * * // Side effects with any iterable * const results: Array = [] * Iterable.forEach(Iterable.range(1, 5), (n) => { * results.push(n * n) * }) - * console.log(results) // [1, 4, 9, 16, 25] + * results // => [1, 4, 9, 16, 25] * * // Process in chunks * const data = Iterable.chunksOf([1, 2, 3, 4, 5, 6], 2) + * const processed: Array> = [] * Iterable.forEach(data, (chunk) => { - * console.log(`Processing chunk: ${Array.from(chunk)}`) + * processed.push(Array.from(chunk)) * }) - * // Prints: "Processing chunk: 1,2", "Processing chunk: 3,4", "Processing chunk: 5,6" + * processed // => [[1, 2], [3, 4], [5, 6]] * ``` * - * @category elements + * @category traversing * @since 2.0.0 */ export const forEach: { @@ -2171,18 +2129,17 @@ export const forEach: { * * **Example** (Reducing an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Sum all numbers * const numbers = [1, 2, 3, 4, 5] * const sum = Iterable.reduce(numbers, 0, (acc, n) => acc + n) - * console.log(sum) // 15 + * sum // => 15 * * // Find maximum value * const values = [3, 1, 4, 1, 5, 9, 2] - * const max = Iterable.reduce(values, -Infinity, Math.max) - * console.log(max) // 9 + * Iterable.reduce(values, -Infinity, (max, value) => Math.max(max, value)) // => 9 * * // Build an object from key-value pairs * const pairs = [["a", 1], ["b", 2], ["c", 3]] as const @@ -2194,7 +2151,7 @@ export const forEach: { * return acc * } * ) - * console.log(obj) // { a: 1, b: 2, c: 3 } + * obj // => { a: 1, b: 2, c: 3 } * * // Use index in the reducer * const letters = ["a", "b", "c"] @@ -2206,7 +2163,7 @@ export const forEach: { * return acc * } * ) - * console.log(indexed) // ["0: a", "1: b", "2: c"] + * indexed // => ["0: a", "1: b", "2: c"] * ``` * * @category folding @@ -2232,20 +2189,20 @@ export const reduce: { * * **Example** (Deduplicating adjacent elements with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Remove adjacent duplicates with custom equality * const numbers = [1, 1, 2, 2, 3, 1, 1] * const dedupedNumbers = Iterable.dedupeAdjacentWith(numbers, (a, b) => a === b) - * console.log(Array.from(dedupedNumbers)) // [1, 2, 3, 1] + * Array.from(dedupedNumbers) // => [1, 2, 3, 1] * * // Case-insensitive deduplication * const words = ["Hello", "HELLO", "world", "World", "test"] * const caseInsensitive = (a: string, b: string) => * a.toLowerCase() === b.toLowerCase() * const dedupedWords = Iterable.dedupeAdjacentWith(words, caseInsensitive) - * console.log(Array.from(dedupedWords)) // ["Hello", "world", "test"] + * Array.from(dedupedWords) // => ["Hello", "world", "test"] * * // Deduplication by object property * const users = [ @@ -2257,13 +2214,13 @@ export const reduce: { * ] * const byId = (a: typeof users[0], b: typeof users[0]) => a.id === b.id * const dedupedUsers = Iterable.dedupeAdjacentWith(users, byId) - * console.log(Array.from(dedupedUsers).map((u) => u.id)) // [1, 2, 3] + * Array.from(dedupedUsers, (user) => user.id) // => [1, 2, 3] * * // Approximate numeric equality * const floats = [1.0, 1.01, 1.02, 2.0, 2.01, 3.0] * const approxEqual = (a: number, b: number) => Math.abs(a - b) < 0.1 * const dedupedFloats = Iterable.dedupeAdjacentWith(floats, approxEqual) - * console.log(Array.from(dedupedFloats)) // [1.0, 2.0, 3.0] + * Array.from(dedupedFloats) // => [1, 2, 3] * ``` * * @category filtering @@ -2303,18 +2260,18 @@ export const dedupeAdjacentWith: { * * **Example** (Deduplicating adjacent elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Remove adjacent duplicate numbers * const numbers = [1, 1, 2, 2, 2, 3, 1, 1] * const deduped = Iterable.dedupeAdjacent(numbers) - * console.log(Array.from(deduped)) // [1, 2, 3, 1] + * Array.from(deduped) // => [1, 2, 3, 1] * * // Remove adjacent duplicate characters * const letters = "aabbccaa" * const dedupedLetters = Iterable.dedupeAdjacent(letters) - * console.log(Array.from(dedupedLetters)) // ["a", "b", "c", "a"] + * Array.from(dedupedLetters) // => ["a", "b", "c", "a"] * * // Works with objects using deep equality * const objects = [ @@ -2325,12 +2282,12 @@ export const dedupeAdjacentWith: { * { type: "A" } * ] * const dedupedObjects = Iterable.dedupeAdjacent(objects) - * console.log(Array.from(dedupedObjects).map((o) => o.type)) // ["A", "B", "A"] + * Array.from(dedupedObjects, (object) => object.type) // => ["A", "B", "A"] * * // Clean up streaming data * const sensorData = [100, 100, 100, 101, 101, 102, 102, 102, 100] * const cleanedData = Iterable.dedupeAdjacent(sensorData) - * console.log(Array.from(cleanedData)) // [100, 101, 102, 100] + * Array.from(cleanedData) // => [100, 101, 102, 100] * ``` * * @category filtering @@ -2343,14 +2300,14 @@ export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjac * * **Example** (Combining cartesian products) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // Create coordinate pairs * const xs = [1, 2] * const ys = ["a", "b", "c"] * const coordinates = Iterable.cartesianWith(xs, ys, (x, y) => `(${x},${y})`) - * console.log(Array.from(coordinates)) // ["(1,a)", "(1,b)", "(1,c)", "(2,a)", "(2,b)", "(2,c)"] + * Array.from(coordinates) // => ["(1,a)", "(1,b)", "(1,c)", "(2,a)", "(2,b)", "(2,c)"] * * // Generate all combinations of options * const sizes = ["S", "M", "L"] @@ -2360,18 +2317,13 @@ export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjac * colors, * (size, color) => ({ size, color }) * ) - * console.log(Array.from(products)) - * // [ - * // { size: "S", color: "red" }, { size: "S", color: "blue" }, - * // { size: "M", color: "red" }, { size: "M", color: "blue" }, - * // { size: "L", color: "red" }, { size: "L", color: "blue" } - * // ] + * Array.from(products, ({ color, size }) => `${size}:${color}`) // => ["S:red", "S:blue", "M:red", "M:blue", "L:red", "L:blue"] * * // Mathematical operations on all pairs * const a = [1, 2, 3] * const b = [10, 20] * const mathProducts = Iterable.cartesianWith(a, b, (x, y) => x * y) - * console.log(Array.from(mathProducts)) // [10, 20, 20, 40, 30, 60] + * Array.from(mathProducts) // => [10, 20, 20, 40, 30, 60] * * // Create test data combinations * const userTypes = ["admin", "user"] @@ -2381,11 +2333,10 @@ export const dedupeAdjacent: (self: Iterable) => Iterable = dedupeAdjac * features, * (user, feature) => `${user}_can_${feature}` * ) - * console.log(Array.from(testCases)) - * // ["admin_can_read", "admin_can_write", "admin_can_delete", "user_can_read", "user_can_write", "user_can_delete"] + * Array.from(testCases) // => ["admin_can_read", "admin_can_write", "admin_can_delete", "user_can_read", "user_can_write", "user_can_delete"] * ``` * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesianWith: { @@ -2393,8 +2344,38 @@ export const cartesianWith: { (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable } = dual( 3, - (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable => - flatMap(self, (a) => map(that, (b) => f(a, b))) + (self: Iterable, that: Iterable, f: (a: A, b: B) => C): Iterable => ({ + [Symbol.iterator]() { + const cache: Array = [] + let iterator: Iterator | undefined + let done = false + const replay: Iterable = { + [Symbol.iterator]() { + let index = 0 + return { + next(): IteratorResult { + if (index < cache.length) { + return { done: false, value: cache[index++] } + } + if (done) { + return { done: true, value: undefined } + } + iterator ??= that[Symbol.iterator]() + const result = iterator.next() + if (result.done) { + done = true + return { done: true, value: undefined } + } + cache.push(result.value) + index++ + return result + } + } + } + } + return flatMap(self, (a) => map(replay, (b) => f(a, b)))[Symbol.iterator]() + } + }) ) /** @@ -2402,40 +2383,34 @@ export const cartesianWith: { * * **Example** (Generating cartesian pairs) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * * // All pairs of numbers and letters * const numbers = [1, 2, 3] * const letters = ["a", "b"] * const pairs = Iterable.cartesian(numbers, letters) - * console.log(Array.from(pairs)) - * // [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]] + * Array.from(pairs) // => [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]] * * // Generate coordinate grid * const x = [0, 1, 2] * const y = [0, 1] * const grid = Iterable.cartesian(x, y) - * console.log(Array.from(grid)) - * // [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]] + * Array.from(grid) // => [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]] * * // All combinations for testing * const browsers = ["chrome", "firefox"] * const devices = ["desktop", "mobile", "tablet"] * const testMatrix = Iterable.cartesian(browsers, devices) - * console.log(Array.from(testMatrix)) - * // [ - * // ["chrome", "desktop"], ["chrome", "mobile"], ["chrome", "tablet"], - * // ["firefox", "desktop"], ["firefox", "mobile"], ["firefox", "tablet"] - * // ] + * Array.from(testMatrix, ([browser, device]) => `${browser}:${device}`) // => ["chrome:desktop", "chrome:mobile", "chrome:tablet", "firefox:desktop", "firefox:mobile", "firefox:tablet"] * * // Empty iterable results in empty cartesian product * const empty = Iterable.empty() * const withEmpty = Iterable.cartesian([1, 2], empty) - * console.log(Array.from(withEmpty)) // [] + * Array.from(withEmpty) // => [] * ``` * - * @category elements + * @category combining * @since 2.0.0 */ export const cartesian: { @@ -2451,11 +2426,10 @@ export const cartesian: { * * **Example** (Counting matching elements) * - * ```ts + * ```ts import.meta.vitest * import { Iterable } from "effect" * - * const result = Iterable.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) - * console.log(result) // 2 + * Iterable.countBy([1, 2, 3, 4, 5], (n) => n % 2 === 0) // => 2 * ``` * * @category folding diff --git a/packages/effect/src/JsonPatch.ts b/packages/effect/src/JsonPatch.ts index 5e05cd7d3..8d0943dd3 100644 --- a/packages/effect/src/JsonPatch.ts +++ b/packages/effect/src/JsonPatch.ts @@ -7,9 +7,8 @@ * * @since 4.0.0 */ -import { format } from "./Formatter.ts" +import * as InternalRecord from "./internal/record.ts" import { escapeToken, unescapeToken } from "./JsonPointer.ts" -import * as Predicate from "./Predicate.ts" import type * as Schema from "./Schema.ts" /** @@ -31,7 +30,7 @@ import type * as Schema from "./Schema.ts" * * **Example** (Defining all operation types) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const addOp: JsonPatch.JsonPatchOperation = { @@ -50,6 +49,8 @@ import type * as Schema from "./Schema.ts" * path: "/users/0/name", * value: "Bob" * } + * + * Array.of(addOp.op, removeOp.op, replaceOp.op) // => ["add", "remove", "replace"] * ``` * * @see {@link JsonPatch} for the array of operations forming a complete patch @@ -115,7 +116,7 @@ export type JsonPatchOperation = * * **Example** (Defining a multi-operation patch) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const patch: JsonPatch.JsonPatch = [ @@ -124,8 +125,7 @@ export type JsonPatchOperation = * { op: "remove", path: "/oldField" } * ] * - * const result = JsonPatch.apply(patch, { count: 3, oldField: "value" }) - * // { count: 5, items: ["apple"] } + * JsonPatch.apply(patch, { items: [], count: 3, oldField: "value" }) // => { items: ["apple"], count: 5 } * ``` * * @see {@link JsonPatchOperation} for individual operation types @@ -160,18 +160,16 @@ export type JsonPatch = ReadonlyArray * * **Example** (Computing object diff) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const oldValue = { users: [{ id: 1, name: "Alice" }], count: 1 } * const newValue = { users: [{ id: 1, name: "Bob" }, { id: 2, name: "Charlie" }], count: 2 } * * const patch = JsonPatch.get(oldValue, newValue) - * // [ - * // { op: "replace", path: "/users/0/name", value: "Bob" }, - * // { op: "add", path: "/users/1", value: { id: 2, name: "Charlie" } }, - * // { op: "replace", path: "/count", value: 2 } - * // ] + * patch[0] // => { op: "replace", path: "/count", value: 2 } + * patch[1] // => { op: "replace", path: "/users/0/name", value: "Bob" } + * patch[2] // => { op: "add", path: "/users/1", value: { id: 2, name: "Charlie" } } * ``` * * @see {@link apply} to apply the generated patch to a document @@ -180,9 +178,18 @@ export type JsonPatch = ReadonlyArray * @since 4.0.0 */ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { - if (Object.is(oldValue, newValue)) return [] const patches: Array = [] + getLoop(oldValue, newValue, "", patches) + return patches +} +function getLoop( + oldValue: Schema.Json, + newValue: Schema.Json, + path: string, + patches: Array +): void { + if (Object.is(oldValue, newValue)) return if (Array.isArray(oldValue) && Array.isArray(newValue)) { const len1 = oldValue.length const len2 = newValue.length @@ -190,25 +197,20 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { // Compare shared prefix by index const shared = Math.min(len1, len2) for (let i = 0; i < shared; i++) { - const path = `/${i}` - const patch = get(oldValue[i], newValue[i]) - for (const op of patch) { - prefixPathInPlace(op, path) - patches.push(op) - } + getLoop(oldValue[i], newValue[i], `${path}/${i}`, patches) } // Remove from end to start so later indices do not shift. for (let i = len1 - 1; i >= len2; i--) { - patches.push({ op: "remove", path: `/${i}` }) + patches.push({ op: "remove", path: `${path}/${i}` }) } // Add from beginning to end. for (let i = len1; i < len2; i++) { - patches.push({ op: "add", path: `/${i}`, value: newValue[i] }) + patches.push({ op: "add", path: `${path}/${i}`, value: newValue[i] }) } - return patches + return } if (isJsonObject(oldValue) && isJsonObject(newValue)) { @@ -217,29 +219,23 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { const allKeys = Array.from(new Set([...keys1, ...keys2])).sort() for (const key of allKeys) { - const esc = escapeToken(key) - const path = `/${esc}` + const keyPath = `${path}/${escapeToken(key)}` const hasKey1 = Object.hasOwn(oldValue, key) const hasKey2 = Object.hasOwn(newValue, key) if (hasKey1 && hasKey2) { - const patch = get(oldValue[key], newValue[key]) - for (const op of patch) { - prefixPathInPlace(op, path) - patches.push(op) - } + getLoop(oldValue[key], newValue[key], keyPath, patches) } else if (!hasKey1 && hasKey2) { - patches.push({ op: "add", path, value: newValue[key] }) - } else if (hasKey1 && !hasKey2) { - patches.push({ op: "remove", path }) + patches.push({ op: "add", path: keyPath, value: newValue[key] }) + } else { + patches.push({ op: "remove", path: keyPath }) } } - return patches + return } - patches.push({ op: "replace", path: "", value: newValue }) - return patches + patches.push({ op: "replace", path, value: newValue }) } /** @@ -266,7 +262,7 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { * * **Example** (Applying a patch) * - * ```ts + * ```ts import.meta.vitest * import { JsonPatch } from "effect" * * const document = { items: [1, 2, 3], total: 6 } @@ -275,8 +271,7 @@ export function get(oldValue: Schema.Json, newValue: Schema.Json): JsonPatch { * { op: "replace", path: "/total", value: 10 } * ] * - * const result = JsonPatch.apply(patch, document) - * // { items: [1, 2, 3, 4], total: 10 } + * JsonPatch.apply(patch, document) // => { items: [1, 2, 3, 4], total: 10 } * ``` * * @see {@link get} to generate patches from value differences @@ -288,32 +283,14 @@ export function apply(patch: JsonPatch, oldValue: Schema.Json): Schema.Json { let doc = oldValue for (const op of patch) { - switch (op.op) { - case "replace": { - doc = op.path === "" ? op.value : setAt(doc, op.path, op.value, "replace") - break - } - case "add": { - doc = addAt(doc, op.path, op.value) - break - } - case "remove": { - doc = setAt(doc, op.path, undefined, "remove") - break - } - } + doc = applyOperation(doc, op) } return doc } -// Mutates op.path in place for perf; safe because child ops are freshly created and not shared. -function prefixPathInPlace(op: JsonPatchOperation, parent: string): void { - ;(op as any).path = op.path === "" ? parent : parent + op.path -} - function isJsonObject(value: unknown): value is Schema.JsonObject { - return Predicate.isObject(value) + return typeof value === "object" && value !== null && !Array.isArray(value) } /** @@ -325,85 +302,57 @@ function isJsonObject(value: unknown): value is Schema.JsonObject { function tokenize(pointer: string): Array { if (pointer === "") return [] if (pointer.charCodeAt(0) !== 47 /* "/" */) { - throw new Error(`Invalid JSON Pointer, it must start with "/": ${format(pointer)}`) + throw new Error(`Invalid JSON Pointer, it must start with "/": ${JSON.stringify(pointer)}`) } return pointer.split("/").slice(1).map(unescapeToken) } -/** Convert a reference token to a non-negative array index (rejects `-` and negatives). */ -function toIndex(token: string): number { +/** Converts a JSON Pointer reference token to a non-negative array index. */ +function toJsonPointerArrayIndex(token: string): number { if (!/^(0|[1-9]\d*)$/.test(token)) { throw new Error(`Invalid array index: "${token}"`) } return Number(token) } -function addAt(doc: Schema.Json, pointer: string, val: Schema.Json): Schema.Json { - if (pointer === "") return val - - const resolved = resolveParent(doc, pointer) - if (resolved === null) { - throw new Error(`Cannot add at "${pointer}" (parent not found or not a container).`) - } - - const { lastToken, parent, stack } = resolved - - if (Array.isArray(parent)) { - const idx = lastToken === "-" ? parent.length : toIndex(lastToken) - if (idx < 0 || idx > parent.length) throw new Error(`Array index out of bounds at "${pointer}".`) - const updated = parent.slice() - updated.splice(idx, 0, val) - return rebuildFromStack(stack, updated) - } - - if (isJsonObject(parent)) { - const updated = { ...parent } - updated[lastToken] = val - return rebuildFromStack(stack, updated) - } - - throw new Error(`Cannot add at "${pointer}" (parent not found or not a container).`) -} - -function setAt( - doc: Schema.Json, - pointer: string, - val: Schema.Json | undefined, - mode: "replace" | "remove" -): Schema.Json { - if (pointer === "") { - if (mode === "remove" || val === undefined) throw new Error("Unsupported operation at the root") - return val +function applyOperation(doc: Schema.Json, op: JsonPatchOperation): Schema.Json { + if (op.path === "") { + if (op.op === "remove") throw new Error("Unsupported operation at the root") + return op.value } - const resolved = resolveParent(doc, pointer) + const resolved = resolveParent(doc, op.path) if (resolved === null) { - throw new Error(`Cannot ${mode} at "${pointer}" (parent not found or not a container).`) + throw new Error(`Cannot ${op.op} at "${op.path}" (parent not found or not a container).`) } const { lastToken, parent, stack } = resolved if (Array.isArray(parent)) { - if (lastToken === "-") throw new Error(`"-" is not valid for ${mode} at "${pointer}".`) - const idx = toIndex(lastToken) - if (idx < 0 || idx >= parent.length) throw new Error(`Array index out of bounds at "${pointer}".`) + if (lastToken === "-" && op.op !== "add") { + throw new Error(`"-" is not valid for ${op.op} at "${op.path}".`) + } + const index = lastToken === "-" ? parent.length : toJsonPointerArrayIndex(lastToken) + const maxIndex = op.op === "add" ? parent.length : parent.length - 1 + if (index > maxIndex) throw new Error(`Array index out of bounds at "${op.path}".`) const updated = parent.slice() - if (mode === "remove") updated.splice(idx, 1) - else updated[idx] = val + if (op.op === "add") updated.splice(index, 0, op.value) + else if (op.op === "remove") updated.splice(index, 1) + else updated[index] = op.value return rebuildFromStack(stack, updated) } if (isJsonObject(parent)) { - if (!Object.hasOwn(parent, lastToken)) { - throw new Error(`Property "${lastToken}" does not exist at "${pointer}".`) + if (op.op !== "add" && !Object.hasOwn(parent, lastToken)) { + throw new Error(`Property "${lastToken}" does not exist at "${op.path}".`) } const updated = { ...parent } - if (mode === "remove") delete updated[lastToken] - else updated[lastToken] = val! + if (op.op === "remove") delete updated[lastToken] + else InternalRecord.assignProperty(updated, lastToken, op.value) return rebuildFromStack(stack, updated) } - throw new Error(`Cannot ${mode} at "${pointer}" (parent not found or not a container).`) + throw new Error(`Cannot ${op.op} at "${op.path}" (parent not found or not a container).`) } type StackEntry = { readonly container: unknown; readonly token: number | string } @@ -424,20 +373,18 @@ function resolveParent( for (let i = 0; i < tokens.length - 1; i++) { const token = tokens[i] - if (cur == null) return null - if (Array.isArray(cur)) { - const idx = toIndex(token) - if (idx < 0 || idx >= cur.length) return null + const idx = toJsonPointerArrayIndex(token) + if (idx >= cur.length) return null stack.push({ container: cur, token: idx }) cur = cur[idx] continue } - if (cur && typeof cur === "object") { + if (isJsonObject(cur)) { if (!Object.hasOwn(cur, token)) return null stack.push({ container: cur, token }) - cur = (cur as any)[token] + cur = cur[token] continue } @@ -460,7 +407,7 @@ function rebuildFromStack(stack: ReadonlyArray, newParent: Schema.Js acc = copy } else { const copy = { ...(container as Schema.JsonObject) } - copy[token as string] = acc + InternalRecord.assignProperty(copy, token as string, acc) acc = copy } } diff --git a/packages/effect/src/JsonPointer.ts b/packages/effect/src/JsonPointer.ts index a3d073381..78392b9da 100644 --- a/packages/effect/src/JsonPointer.ts +++ b/packages/effect/src/JsonPointer.ts @@ -1,8 +1,8 @@ /** - * Helpers for escaping and unescaping JSON Pointer path segments. JSON Pointer - * uses `/` to separate path tokens inside a JSON document, so token text must - * encode literal `~` and `/` characters. This module provides the two RFC 6901 - * token conversions used by JSON Patch and related path handling. + * Helpers for escaping JSON Pointer path segments and converting JSON Pointer + * URI fragments. JSON Pointer uses `/` to separate path tokens inside a JSON + * document, so token text must encode literal `~` and `/` characters. URI + * fragments additionally apply percent-encoding after JSON Pointer escaping. * * @since 4.0.0 */ @@ -27,12 +27,12 @@ * * **Example** (Escaping special characters) * - * ```ts + * ```ts import.meta.vitest * import { JsonPointer } from "effect" * - * JsonPointer.escapeToken("a/b") // "a~1b" - * JsonPointer.escapeToken("c~d") // "c~0d" - * JsonPointer.escapeToken("path/to~key") // "path~1to~0key" + * JsonPointer.escapeToken("a/b") // => "a~1b" + * JsonPointer.escapeToken("c~d") // => "c~0d" + * JsonPointer.escapeToken("path/to~key") // => "path~1to~0key" * ``` * * @see {@link unescapeToken} The inverse operation for decoding escaped tokens @@ -63,12 +63,12 @@ export function escapeToken(token: string): string { * * **Example** (Unescaping special characters) * - * ```ts + * ```ts import.meta.vitest * import { JsonPointer } from "effect" * - * JsonPointer.unescapeToken("a~1b") // "a/b" - * JsonPointer.unescapeToken("c~0d") // "c~d" - * JsonPointer.unescapeToken("path~1to~0key") // "path/to~key" + * JsonPointer.unescapeToken("a~1b") // => "a/b" + * JsonPointer.unescapeToken("c~0d") // => "c~d" + * JsonPointer.unescapeToken("path~1to~0key") // => "path/to~key" * ``` * * @see {@link escapeToken} The inverse operation for encoding tokens @@ -78,3 +78,96 @@ export function escapeToken(token: string): string { export function unescapeToken(token: string): string { return token.replace(/~1/g, "/").replace(/~0/g, "~") } + +/** @internal */ +export function formatUriFragmentToken(token: string): string { + return encodeURI(escapeToken(token)).replace(/#/g, "%23") +} + +/** @internal */ +export function decodeUriFragment(fragment: string): string | undefined { + if (fragment.length === 0 || fragment === "#") return "" + if (!fragment.startsWith("#")) return undefined + const encoded = fragment.slice(1) + try { + if (encodeURI(encoded).replace(/%25/g, "%").replace(/#/g, "%23") !== encoded) return undefined + const pointer = decodeURIComponent(encoded) + return pointer.startsWith("/") && !/~(?:[^01]|$)/.test(pointer) ? pointer : undefined + } catch { + return undefined + } +} + +/** + * Parses a JSON Pointer URI fragment into decoded path tokens. + * + * **When to use** + * + * Use when you need to resolve a URI fragment against a JSON document. + * + * **Details** + * + * Percent-encoding is decoded before the pointer is split into tokens, then + * each token is decoded with {@link unescapeToken}. The empty string and `#` + * both represent the document root. + * + * **Gotchas** + * + * Returns `undefined` when the input is not a URI fragment, contains characters + * that require percent-encoding, or contains an invalid JSON Pointer escape + * sequence. + * + * **Example** (Parsing URI fragments) + * + * ```ts import.meta.vitest + * import { JsonPointer } from "effect" + * + * JsonPointer.parseUriFragment("#/users/a~1b") // => ["users", "a/b"] + * JsonPointer.parseUriFragment("#/caf%C3%A9") // => ["café"] + * JsonPointer.parseUriFragment("#/%") // => undefined + * JsonPointer.parseUriFragment("#/a#b") // => undefined + * ``` + * + * @see {@link formatUriFragment} for the inverse operation + * @category decoding + * @since 4.0.0 + */ +export function parseUriFragment(fragment: string): ReadonlyArray | undefined { + const pointer = decodeUriFragment(fragment) + return pointer === undefined ? undefined : pointer.length === 0 ? [] : pointer.slice(1).split("/").map(unescapeToken) +} + +/** + * Formats path tokens as a JSON Pointer URI fragment. + * + * **When to use** + * + * Use when you need a URI fragment that identifies a value in a JSON document. + * + * **Details** + * + * Each token is encoded with {@link escapeToken} before URI percent-encoding + * is applied. An empty path is formatted as `#`. + * + * **Gotchas** + * + * Throws a `URIError` when a token contains an unpaired surrogate. + * + * **Example** (Formatting a URI fragment) + * + * ```ts import.meta.vitest + * import { JsonPointer } from "effect" + * + * JsonPointer.formatUriFragment(["users", "a/b", "Rate%"]) // => "#/users/a~1b/Rate%25" + * ``` + * + * @see {@link parseUriFragment} for the inverse operation + * @category encoding + * @since 4.0.0 + */ +export function formatUriFragment(path: ReadonlyArray): string { + return path.reduce( + (fragment, token) => `${fragment}/${formatUriFragmentToken(token)}`, + "#" + ) +} diff --git a/packages/effect/src/JsonSchema.ts b/packages/effect/src/JsonSchema.ts index 7def9d323..5cbd69837 100644 --- a/packages/effect/src/JsonSchema.ts +++ b/packages/effect/src/JsonSchema.ts @@ -2,16 +2,15 @@ * Helpers for normalizing and converting JSON Schema and OpenAPI schema * documents. Supported inputs include JSON Schema Draft-07, Draft 2020-12, * OpenAPI 3.0, and OpenAPI 3.1; conversions normalize through - * `Document<"draft-2020-12">` before emitting another dialect. The module also - * defines document types, meta-schema constants, OpenAPI component-key helpers, - * and `$ref` resolution utilities. + * `Document<"draft-2020-12">` before emitting another dialect, including + * JSON Schema Draft-04. The module also defines document types, meta-schema + * constants, and OpenAPI component-key helpers. * * @since 4.0.0 */ -import * as Arr from "./Array.ts" -import { unescapeToken } from "./JsonPointer.ts" +import * as InternalRecord from "./internal/record.ts" +import { formatUriFragment, parseUriFragment } from "./JsonPointer.ts" import * as Predicate from "./Predicate.ts" -import * as Rec from "./Record.ts" /** * A plain object representing a single JSON Schema node. @@ -42,9 +41,10 @@ export interface JsonSchema { * * **Details** * - * Supported values are `"draft-07"` for JSON Schema Draft-07, - * `"draft-2020-12"` for JSON Schema Draft 2020-12 and the canonical internal - * form, `"openapi-3.1"` for OpenAPI 3.1, and `"openapi-3.0"` for OpenAPI 3.0. + * Supported values are `"draft-04"` for JSON Schema Draft-04, `"draft-07"` + * for JSON Schema Draft-07, `"draft-2020-12"` for JSON Schema Draft 2020-12 + * and the canonical internal form, `"openapi-3.1"` for OpenAPI 3.1, and + * `"openapi-3.0"` for OpenAPI 3.0. * * @see {@link Document} for a single root schema tagged with a dialect * @see {@link MultiDocument} for multiple root schemas tagged with a dialect @@ -52,7 +52,7 @@ export interface JsonSchema { * @category models * @since 4.0.0 */ -export type Dialect = "draft-07" | "draft-2020-12" | "openapi-3.1" | "openapi-3.0" +export type Dialect = "draft-04" | "draft-07" | "draft-2020-12" | "openapi-3.1" | "openapi-3.0" /** * The JSON Schema primitive type names. @@ -81,8 +81,6 @@ export type Type = "string" | "number" | "boolean" | "array" | "object" | "null" * * @see {@link Document} for a single root schema with definitions * @see {@link MultiDocument} for multiple root schemas sharing definitions - * @see {@link resolve$ref} for resolving a `$ref` against definitions - * * @category models * @since 4.0.0 */ @@ -103,12 +101,12 @@ export interface Definitions extends Record {} * The `schema` field holds the root schema *without* the definitions * collection. Root definitions are stored separately in `definitions` and * referenced via `#/$defs/` for Draft-2020-12, `#/definitions/` - * for Draft-07, and `#/components/schemas/` for OpenAPI 3.1 and - * OpenAPI 3.0. + * for Draft-04 and Draft-07, and `#/components/schemas/` for OpenAPI 3.1 + * and OpenAPI 3.0. * * **Example** (Inspecting a parsed document) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -118,9 +116,9 @@ export interface Definitions extends Record {} * * const doc = JsonSchema.fromSchemaDraft2020_12(raw) * - * console.log(doc.dialect) // "draft-2020-12" - * console.log(doc.schema) // { type: "string" } - * console.log(doc.definitions) // { Trimmed: { type: "string", minLength: 1 } } + * doc.dialect // => "draft-2020-12" + * doc.schema // => { type: "string" } + * doc.definitions // => { Trimmed: { type: "string", minLength: 1 } } * ``` * * @see {@link MultiDocument} @@ -158,6 +156,20 @@ export interface MultiDocument { readonly definitions: Definitions } +/** + * Represents the `$schema` meta-schema URI for JSON Schema Draft-04. + * + * **When to use** + * + * Use when constructing a Draft-04 JSON Schema document and you need a stable + * value for the root `$schema` field. + * + * @see {@link META_SCHEMA_URI_DRAFT_07} for the Draft-07 `$schema` URI + * @category constants + * @since 4.0.0 + */ +export const META_SCHEMA_URI_DRAFT_04 = "http://json-schema.org/draft-04/schema#" + /** * Represents the `$schema` meta-schema URI for JSON Schema Draft-07. * @@ -169,14 +181,15 @@ export interface MultiDocument { * **Details** * * The exported value is the literal string - * `http://json-schema.org/draft-07/schema`. + * `http://json-schema.org/draft-07/schema#`. * + * @see {@link META_SCHEMA_URI_DRAFT_04} for the Draft-04 `$schema` URI * @see {@link META_SCHEMA_URI_DRAFT_2020_12} for the Draft 2020-12 `$schema` URI * * @category constants * @since 4.0.0 */ -export const META_SCHEMA_URI_DRAFT_07 = "http://json-schema.org/draft-07/schema" +export const META_SCHEMA_URI_DRAFT_07 = "http://json-schema.org/draft-07/schema#" /** * Represents the `$schema` meta-schema URI for JSON Schema Draft 2020-12. @@ -198,9 +211,20 @@ export const META_SCHEMA_URI_DRAFT_07 = "http://json-schema.org/draft-07/schema" */ export const META_SCHEMA_URI_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema" -const RE_DEFINITIONS = /^#\/definitions(?=\/|$)/ -const RE_DEFS = /^#\/\$defs(?=\/|$)/ -const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ +const META_SCHEMA_URI_OPEN_API_3_1 = "https://spec.openapis.org/oas/3.1/dialect/base" + +function isMetaSchemaUri(value: unknown, uri: string): boolean { + return value === uri || value === (uri.endsWith("#") ? uri.slice(0, -1) : `${uri}#`) +} + +function rewriteOpenApiComponentsReference(reference: string): string { + const path = reference.startsWith("#") ? parseUriFragment(reference) : undefined + return path !== undefined && path[0] === "components" && path[1] === "schemas" + ? formatUriFragment(["$defs", ...path.slice(2)]) + : reference +} + +const OPEN_API_31_TARGET_COLLISIONS = ["example", "discriminator", "xml", "externalDocs"] /** * Parses a raw Draft-07 JSON Schema into a `Document<"draft-2020-12">`. @@ -213,17 +237,25 @@ const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ * **Details** * * This converts Draft-07 tuple syntax (`items` as array plus - * `additionalItems`) to Draft-2020-12 form (`prefixItems` plus `items`), - * rewrites `#/definitions/...` refs to `#/$defs/...`, and extracts root-level - * `definitions` into the `definitions` field. + * `additionalItems`) to Draft-2020-12 form (`prefixItems` plus `items`), splits + * `dependencies` into `dependentRequired` and `dependentSchemas`, converts + * plain-name `$id` fragments to `$anchor`, and extracts root-level + * `definitions` into the `definitions` field. Local JSON Pointer refs are + * relocated when one of these structural conversions moves its target. * * **Gotchas** * - * Unsupported keywords, such as `if`/`then`/`else` and `$id`, are dropped. + * Unknown and custom keywords are copied as opaque values. Their contents are + * not treated as nested schemas. Draft-07 keywords such as `if` / `then` / + * `else` and `contains` are preserved and their subschemas are converted. + * Siblings of a valid Draft-07 `$ref` are ignored according to Draft-07 + * semantics. The conversion throws when a Draft-07 `$id` fragment cannot be + * represented as a Draft-2020-12 `$anchor`, or when an unknown Draft-07 + * keyword would become an active Draft-2020-12 keyword after copying. * * **Example** (Parsing a Draft-07 schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -237,8 +269,8 @@ const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ * } * * const doc = JsonSchema.fromSchemaDraft07(raw) - * console.log(doc.dialect) // "draft-2020-12" - * console.log(doc.schema.properties) // { tags: { type: "array", items: { type: "string" } } } + * doc.dialect // => "draft-2020-12" + * doc.schema.properties // => { tags: { type: "array", items: { type: "string" } } } * ``` * * @see {@link fromSchemaDraft2020_12} @@ -248,111 +280,7 @@ const RE_COMPONENTS_SCHEMAS = /^#\/components\/schemas(?=\/|$)/ * @since 4.0.0 */ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { - let definitions: Definitions | undefined - - const schema = walk(js, true) as JsonSchema - return { - dialect: "draft-2020-12", - schema, - definitions: definitions ?? {} - } - - function walk(node: unknown, isRoot: boolean): unknown { - if (Array.isArray(node)) return node.map((v) => walk(v, false)) - if (!Predicate.isObject(node)) return node - - const out: Record = {} - - let prefixItems: unknown = undefined - let additionalItems: unknown = undefined - - for (const k of Object.keys(node)) { - const v = node[k] - - switch (k) { - case "$ref": - out.$ref = typeof v === "string" ? v.replace(RE_DEFINITIONS, "#/$defs") : v - break - - case "definitions": { - const mapped = walk_object(v, walk) - if (isRoot) { - definitions = mapped as Definitions | undefined - } else { - out.definitions = mapped ?? v - } - break - } - - case "items": - prefixItems = v - break - case "additionalItems": - additionalItems = v - break - - case "properties": - case "patternProperties": { - const mapped = walk_object(v, walk) - out[k] = mapped ?? v - break - } - - case "additionalProperties": - case "propertyNames": - out[k] = walk(v, false) - break - - case "allOf": - case "anyOf": - case "oneOf": - out[k] = Array.isArray(v) ? v.map((x) => walk(x, false)) : v - break - - case "type": - case "required": - case "enum": - case "const": - case "title": - case "description": - case "default": - case "examples": - case "format": - case "readOnly": - case "writeOnly": - case "pattern": - case "minimum": - case "maximum": - case "exclusiveMinimum": - case "exclusiveMaximum": - case "minLength": - case "maxLength": - case "minItems": - case "maxItems": - case "minProperties": - case "maxProperties": - case "multipleOf": - case "uniqueItems": - out[k] = v - break - - default: - break - } - } - - // Draft-07 tuples -> 2020-12 tuples - if (prefixItems !== undefined) { - if (Array.isArray(prefixItems)) { - out.prefixItems = prefixItems.map((x) => walk(x, false)) - if (additionalItems !== undefined) out.items = walk(additionalItems, false) - } else { - out.items = walk(prefixItems, false) - } - } - - return out - } + return fromSchemaDraft2020_12(convertDraft07(js)) } /** @@ -369,7 +297,7 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { * * **Example** (Parsing a Draft-2020-12 schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -379,8 +307,8 @@ export function fromSchemaDraft07(js: JsonSchema): Document<"draft-2020-12"> { * } * * const doc = JsonSchema.fromSchemaDraft2020_12(raw) - * console.log(doc.schema) // { type: "number", minimum: 0 } - * console.log(doc.definitions) // { PositiveInt: { type: "integer", minimum: 1 } } + * doc.schema // => { type: "number", minimum: 0 } + * doc.definitions // => { PositiveInt: { type: "integer", minimum: 1 } } * ``` * * @see {@link fromSchemaDraft07} @@ -407,12 +335,21 @@ export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12" * * **Details** * - * This rewrites `#/components/schemas/...` refs to `#/$defs/...`, then delegates - * to {@link fromSchemaDraft2020_12}. + * This rewrites `#/components/schemas/...` refs to `#/$defs/...`, normalizes the + * OpenAPI base dialect URI to Draft 2020-12, converts the deprecated singular + * `example` field to `examples`, then delegates to + * {@link fromSchemaDraft2020_12}. + * + * **Gotchas** + * + * When both `example` and `examples` are present, the singular example is + * prepended to the array. Custom `$schema` dialect URIs and unknown keywords + * are copied opaquely. Component references inside a schema resource identified + * by `$id` are left unchanged because they are relative to that resource. * * **Example** (Parsing an OpenAPI 3.1 schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -423,8 +360,7 @@ export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12" * } * * const doc = JsonSchema.fromSchemaOpenApi3_1(raw) - * // $ref is rewritten to Draft-2020-12 form - * console.log(doc.schema.properties) // { user: { $ref: "#/$defs/User" } } + * doc.schema.properties // => { user: { $ref: "#/$defs/User" } } * ``` * * @see {@link fromSchemaOpenApi3_0} @@ -433,7 +369,20 @@ export function fromSchemaDraft2020_12(js: JsonSchema): Document<"draft-2020-12" * @since 4.0.0 */ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> { - const schema = rewrite_refs(js, (ref) => ref.replace(RE_COMPONENTS_SCHEMAS, "#/$defs")) as JsonSchema + const isRootResource = createsResource(js.$id) + const schema = transformSchema(js, (schema, inEmbeddedResource) => { + if (!isRootResource && !inEmbeddedResource) rewriteSchemaRef(schema, rewriteOpenApiComponentsReference) + if (isMetaSchemaUri(schema.$schema, META_SCHEMA_URI_OPEN_API_3_1)) { + InternalRecord.assignProperty(schema, "$schema", META_SCHEMA_URI_DRAFT_2020_12) + } + if (Object.hasOwn(schema, "example")) { + const examples = schema.examples + if (examples === undefined || Array.isArray(examples)) { + InternalRecord.assignProperty(schema, "examples", [schema.example, ...(examples ?? [])]) + delete schema.example + } + } + }) as JsonSchema return fromSchemaDraft2020_12(schema) } @@ -447,14 +396,23 @@ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> * * **Details** * - * This handles OpenAPI 3.0 extensions, including `nullable`, singular - * `example`, and boolean `exclusiveMinimum` or `exclusiveMaximum`. It - * normalizes the schema to Draft-07 first, then converts to Draft-2020-12 via - * {@link fromSchemaDraft07}. + * This directly converts OpenAPI 3.0 schema objects to Draft-2020-12. It + * handles `nullable`, singular `example`, boolean `exclusiveMinimum` and + * `exclusiveMaximum`, and OpenAPI component refs. Only values in OpenAPI + * schema positions are traversed as schemas. + * + * **Gotchas** + * + * OpenAPI 3.0 `nullable` is applied only when the same Schema Object has an + * explicit string `type`; other constraints such as `enum` are left + * unchanged. Unknown keywords, vendor extensions, and annotation values are + * copied opaquely unless their name would become active in Draft 2020-12 and + * change meaning, in which case conversion throws. Siblings of a valid + * OpenAPI 3.0 `$ref` are ignored. * * **Example** (Parsing an OpenAPI 3.0 nullable schema) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const raw: JsonSchema.JsonSchema = { @@ -463,8 +421,7 @@ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> * } * * const doc = JsonSchema.fromSchemaOpenApi3_0(raw) - * // nullable is expanded into a type array - * console.log(doc.schema.type) // ["string", "null"] + * doc.schema.type // => ["string", "null"] * ``` * * @see {@link fromSchemaOpenApi3_1} @@ -473,8 +430,7 @@ export function fromSchemaOpenApi3_1(js: JsonSchema): Document<"draft-2020-12"> * @since 4.0.0 */ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-12"> { - const normalized = normalize_OpenApi3_0_to_Draft07(schema) - return fromSchemaDraft07(normalized as JsonSchema) + return fromSchemaDraft2020_12(convertOpenApi30(schema)) } /** @@ -489,16 +445,24 @@ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-1 * * This rewrites `#/$defs/...` refs to `#/definitions/...`, converts * Draft-2020-12 tuple syntax (`prefixItems` plus `items`) to Draft-07 form - * (`items` as array plus `additionalItems`), and converts both the root schema - * and all definitions. + * (`items` as array plus `additionalItems`), merges `dependentRequired` and + * `dependentSchemas` into `dependencies`, and converts both the root schema + * and all definitions. Local JSON Pointer refs are relocated when structural + * keywords move. * * **Gotchas** * - * Unsupported Draft-2020-12 keywords are dropped. + * Unknown and custom keywords are copied as opaque values. Known keywords + * that Draft-07 cannot represent cause the conversion to throw + * instead of being dropped. These include dynamic references, + * `unevaluatedProperties`, `unevaluatedItems`, and non-default `minContains` + * or `maxContains` constraints. Conversion also throws when an opaque + * Draft-2020-12 keyword would collide with an active Draft-07 keyword, or when + * `$id` and `$anchor` occur together because Draft-07 cannot preserve both identifiers. * * **Example** (Converting to Draft-07) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const doc = JsonSchema.fromSchemaDraft2020_12({ @@ -508,12 +472,13 @@ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-1 * }) * * const draft07 = JsonSchema.toDocumentDraft07(doc) - * console.log(draft07.dialect) // "draft-07" - * console.log(draft07.schema.items) // [{ type: "string" }, { type: "number" }] - * console.log(draft07.schema.additionalItems) // { type: "boolean" } + * draft07.dialect // => "draft-07" + * draft07.schema.items // => [{ type: "string" }, { type: "number" }] + * draft07.schema.additionalItems // => { type: "boolean" } * ``` * * @see {@link fromSchemaDraft07} + * @see {@link toDocumentDraft04} for converting to Draft-04 * @see {@link toMultiDocumentOpenApi3_1} * @category encoding * @since 4.0.0 @@ -521,109 +486,61 @@ export function fromSchemaOpenApi3_0(schema: JsonSchema): Document<"draft-2020-1 export function toDocumentDraft07(document: Document<"draft-2020-12">): Document<"draft-07"> { return { dialect: "draft-07", - schema: toSchemaDraft07(document.schema), - definitions: Rec.map(document.definitions, toSchemaDraft07) + ...convertDocument(document, draft07Adapter) } } -function toSchemaDraft07(schema: JsonSchema): JsonSchema { - return rewrite(schema) - - function rewrite(node: unknown): JsonSchema { - return walk(rewrite_refs(node, (ref) => ref.replace(RE_DEFS, "#/definitions")), true) as JsonSchema - } - - function walk(node: unknown, _isRoot: boolean): unknown { - if (Array.isArray(node)) return node.map((v) => walk(v, false)) - if (!Predicate.isObject(node)) return node - - const src = node as Record - const out: Record = {} - - let prefixItems: unknown = undefined - let items: unknown = undefined - - for (const k of Object.keys(src)) { - const v = src[k] - - switch (k) { - // We already rewrote $ref via rewrite_refs, so just copy it through. - case "$ref": - case "type": - case "required": - case "enum": - case "const": - case "title": - case "description": - case "default": - case "examples": - case "format": - case "pattern": - case "minimum": - case "maximum": - case "exclusiveMinimum": - case "exclusiveMaximum": - case "minLength": - case "maxLength": - case "minItems": - case "maxItems": - case "minProperties": - case "maxProperties": - case "multipleOf": - case "uniqueItems": - out[k] = v - break - - // Schema maps - case "properties": - case "patternProperties": { - const mapped = walk_object(v, walk) - out[k] = mapped ?? v - break - } - - // Single subschemas - case "additionalProperties": - case "propertyNames": - out[k] = walk(v, false) - break - - // Schema arrays - case "allOf": - case "anyOf": - case "oneOf": - out[k] = Array.isArray(v) ? v.map((x) => walk(x, false)) : v - break - - // Tuple handling (2020-12 form) - case "prefixItems": - prefixItems = v - break - case "items": - items = v - break - - default: - // drop everything else (subset) - break - } - } - - // 2020-12 tuples -> Draft-07 tuples - if (prefixItems !== undefined) { - if (Array.isArray(prefixItems)) { - out.items = prefixItems.map((x) => walk(x, false)) - if (items !== undefined) out.additionalItems = walk(items, false) - } else { - // Non-standard, but keep a reasonable behavior - out.items = walk(prefixItems, false) - } - } else if (items !== undefined) { - // Regular items schema stays as items - out.items = walk(items, false) - } - - return out +/** + * Converts a `Document<"draft-2020-12">` to a `Document<"draft-04">`. + * + * **When to use** + * + * Use when you need to output a canonical JSON Schema document in Draft-04 + * format. + * + * **Details** + * + * This directly rewrites `#/$defs/...` refs to `#/definitions/...`, converts + * tuple syntax, merges canonical dependencies, lowers `const` to `enum`, + * converts numeric exclusive bounds to the Draft-04 boolean form, lowers + * conditionals and basic `contains` through boolean applicators, and converts + * both the root schema and all definitions. + * + * **Gotchas** + * + * Unknown and custom keywords are copied as opaque values. Newer annotation + * keywords are preserved as Draft-04 extensions. Known keywords + * without a Draft-04 equivalent, including `propertyNames`, non-default + * `contains` cardinality, dynamic references, and unevaluated constraints, + * cause the conversion to throw instead of being dropped. A conditional with + * both branches also throws when lowering it would duplicate a nested schema + * identifier. Conversion also throws when an opaque Draft-2020-12 keyword + * would collide with an active Draft-04 keyword, or when `$id` and `$anchor` + * occur together because Draft-04 cannot preserve both identifiers. + * + * **Example** (Converting exclusive bounds) + * + * ```ts import.meta.vitest + * import { JsonSchema } from "effect" + * + * const doc = JsonSchema.fromSchemaDraft2020_12({ + * type: "number", + * exclusiveMinimum: 0 + * }) + * + * JsonSchema.toDocumentDraft04(doc).schema // => { type: "number", minimum: 0, exclusiveMinimum: true } + * ``` + * + * @see {@link toDocumentDraft07} for converting to Draft-07 + * @category encoding + * @since 4.0.0 + */ +export function toDocumentDraft04(document: Document<"draft-2020-12">): Document<"draft-04"> { + return { + dialect: "draft-04", + ...convertDocument(document, draft04Adapter, { + booleanAdapter: (schema) => schema ? {} : { not: {} } + }) } } @@ -638,15 +555,26 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { * * **Details** * - * This rewrites `#/$defs/...` refs to `#/components/schemas/...`, sanitizes - * definition keys to match the OpenAPI component key pattern - * (`^[a-zA-Z0-9.\-_]+$`) by replacing invalid characters with `_`, updates all - * `$ref` pointers to use the sanitized keys, and converts all schemas and - * definitions in the multi-document. + * This rewrites local `#/$defs/...` refs to `#/components/schemas/...` and + * sanitizes definition keys to match the OpenAPI component key pattern + * (`^[a-zA-Z0-9.\-_]+$`) by replacing invalid characters with `_`. Valid keys + * are preserved. When sanitized keys collide, the converter appends the first + * available `_1`, `_2`, and subsequent suffix, with allocation independent of + * definition insertion order. All local refs are updated to use the allocated + * keys, including refs to paths within a definition. + * + * **Gotchas** + * + * External refs and local refs outside `#/$defs` are left unchanged. Conversion + * throws when a custom keyword would become an active OpenAPI keyword and + * therefore change meaning. References inside schema resources identified by + * `$id` are left unchanged. Conversion throws when an identified root schema + * references the detached shared definitions pool because OpenAPI cannot + * preserve that fragment reference. * * **Example** (Converting to OpenAPI 3.1) * - * ```ts + * ```ts import.meta.vitest * import { JsonSchema } from "effect" * * const multi: JsonSchema.MultiDocument<"draft-2020-12"> = { @@ -658,8 +586,8 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { * } * * const openapi = JsonSchema.toMultiDocumentOpenApi3_1(multi) - * console.log(openapi.dialect) // "openapi-3.1" - * console.log(openapi.schemas[0]) // { $ref: "#/components/schemas/User" } + * openapi.dialect // => "openapi-3.1" + * openapi.schemas[0] // => { $ref: "#/components/schemas/User" } * ``` * * @see {@link toDocumentDraft07} @@ -668,35 +596,74 @@ function toSchemaDraft07(schema: JsonSchema): JsonSchema { * @since 4.0.0 */ export function toMultiDocumentOpenApi3_1(multiDocument: MultiDocument<"draft-2020-12">): MultiDocument<"openapi-3.1"> { + const definitionKeys = Object.keys(multiDocument.definitions) const keyMap = new Map() - for (const key of Object.keys(multiDocument.definitions)) { - const sanitized = sanitizeOpenApiComponentsSchemasKey(key) - if (sanitized !== key) { - keyMap.set(key, sanitized) - } + const usedKeys = new Set(definitionKeys.filter((key) => VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(key))) + const invalidKeys = definitionKeys + .filter((key) => !VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(key)) + .sort() + .map((key) => [key, sanitizeOpenApiComponentsSchemasKey(key)] as const) + for (const [key, base] of invalidKeys) { + if (usedKeys.has(base)) continue + usedKeys.add(base) + keyMap.set(key, base) + } + for (const [key, base] of invalidKeys) { + if (keyMap.has(key)) continue + let candidate: string + let suffix = 0 + do candidate = `${base}_${++suffix}` + while (usedKeys.has(candidate)) + usedKeys.add(candidate) + keyMap.set(key, candidate) } - function rewrite(schema: JsonSchema): JsonSchema { - return rewrite_refs(schema, ($ref) => { - const tokens = $ref.split("/") - if (tokens.length > 0) { - const identifier = unescapeToken(tokens[tokens.length - 1]) - const sanitized = keyMap.get(identifier) - if (sanitized !== undefined) { - $ref = tokens.slice(0, -1).join("/") + "/" + sanitized + function rewrite( + schema: JsonSchema, + rejectSharedDefinitionRefs = false + ): JsonSchema { + const isRootResource = createsResource(schema.$id) + const localDefinitions = Predicate.isObject(schema.$defs) ? schema.$defs : undefined + return transformSchema(schema, (schema, inEmbeddedResource) => { + rejectKeywordCollisions(schema, OPEN_API_31_TARGET_COLLISIONS, "OpenAPI 3.1", "Draft 2020-12") + rewriteSchemaRef(schema, (reference, keyword) => { + const path = reference.startsWith("#") ? parseUriFragment(reference) : undefined + if (path === undefined || path[0] !== "$defs" || path.length < 2) return reference + const key = path[1] + if (isRootResource) { + if ( + rejectSharedDefinitionRefs && + !inEmbeddedResource && + Object.hasOwn(multiDocument.definitions, key) && + (localDefinitions === undefined || !Object.hasOwn(localDefinitions, key)) + ) { + unsupported(keyword, "OpenAPI 3.1", "a schema resource cannot reference the shared definitions pool") + } + return reference } - } - return $ref.replace(RE_DEFS, "#/components/schemas") + return inEmbeddedResource + ? reference + : formatUriFragment(["components", "schemas", keyMap.get(key) ?? key, ...path.slice(2)]) + }) }) as JsonSchema } + const schemas = multiDocument.schemas.map((schema) => rewrite(schema, true)) as unknown as MultiDocument< + "openapi-3.1" + >["schemas"] + const definitions: Definitions = {} + for (const key of definitionKeys) { + InternalRecord.assignProperty( + definitions, + keyMap.get(key) ?? key, + rewrite(multiDocument.definitions[key]) + ) + } + return { dialect: "openapi-3.1", - schemas: Arr.map(multiDocument.schemas, rewrite), - definitions: Rec.mapEntries( - multiDocument.definitions, - (definition, key) => [keyMap.get(key) ?? key, rewrite(definition)] - ) + schemas, + definitions } } @@ -710,239 +677,951 @@ export const VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP = /^[a-zA-Z0-9.\-_]+$/ * @internal */ export function sanitizeOpenApiComponentsSchemasKey(s: string): string { - if (s.length === 0) return "_" - if (VALID_OPEN_API_COMPONENTS_SCHEMAS_KEY_REGEXP.test(s)) return s - - const out: Array = [] - - for (const ch of s) { - const code = ch.codePointAt(0) - if ( - code !== undefined && - ((code >= 48 && code <= 57) || // 0-9 - (code >= 65 && code <= 90) || // A-Z - (code >= 97 && code <= 122) || // a-z - code === 46 || // . - code === 45 || // - - code === 95) // _ - ) { - out.push(ch) - } else { - out.push("_") + return s.length === 0 ? "_" : s.replace(/[^a-zA-Z0-9._-]/gu, "_") +} + +/** @internal */ +export function getReferenceKey($ref: string): string | undefined { + const path = $ref.startsWith("#") ? parseUriFragment($ref) : undefined + return path !== undefined && path.length === 2 && path[0] === "$defs" + ? path[1] + : undefined +} + +function transformSchema( + node: unknown, + transform: (schema: Record, inEmbeddedResource: boolean) => void +): unknown { + return walk(node, false, true) + + function walk(node: unknown, inheritedResource: boolean, isRoot = false): unknown { + if (!Predicate.isObject(node)) return node + const inEmbeddedResource = inheritedResource || (!isRoot && createsResource(node.$id)) + + const out: Record = {} + for (const key of Object.keys(node)) { + const value = node[key] + let transformed = value + switch (key) { + case "$defs": + case "properties": + case "patternProperties": + case "dependentSchemas": + transformed = mapObject(value, (value) => walk(value, inEmbeddedResource)) ?? value + break + case "allOf": + case "anyOf": + case "oneOf": + case "prefixItems": + transformed = Array.isArray(value) ? value.map((value) => walk(value, inEmbeddedResource)) : value + break + case "not": + case "additionalProperties": + case "propertyNames": + case "unevaluatedProperties": + case "items": + case "contains": + case "unevaluatedItems": + case "if": + case "then": + case "else": + case "contentSchema": + transformed = walk(value, inEmbeddedResource) + } + InternalRecord.assignProperty(out, key, transformed) } + transform(out, inEmbeddedResource) + return out } +} - return out.join("") +/** @internal */ +export function rewriteRefs(schema: JsonSchema, rewrite: ($ref: string) => string): JsonSchema { + return transformSchema(schema, (schema) => { + rewriteSchemaRef(schema, rewrite) + }) as JsonSchema } -function rewrite_refs(node: unknown, f: ($ref: string) => string): unknown { - if (Array.isArray(node)) return node.map((v) => rewrite_refs(v, f)) - if (!Predicate.isObject(node)) return node +function rewriteSchemaRef( + schema: Record, + rewrite: ($ref: string, keyword: "$ref" | "$dynamicRef") => string +): void { + if (typeof schema.$ref === "string") { + InternalRecord.assignProperty(schema, "$ref", rewrite(schema.$ref, "$ref")) + } + if (typeof schema.$dynamicRef === "string") { + InternalRecord.assignProperty(schema, "$dynamicRef", rewrite(schema.$dynamicRef, "$dynamicRef")) + } +} +function mapObject( + value: unknown, + f: (node: unknown, key: string) => unknown +): Record | undefined { + if (!Predicate.isObject(value)) return undefined const out: Record = {} + for (const key of Object.keys(value)) { + InternalRecord.assignProperty(out, key, f(value[key], key)) + } + return out +} - for (const k of Object.keys(node)) { - const v = node[k] +type Path = ReadonlyArray +type Convert = (root: unknown, sourcePath?: Path, targetPath?: Path) => unknown - if (k === "$ref") { - out[k] = typeof v === "string" ? f(v) : v - } else if (Array.isArray(v) || Predicate.isObject(v)) { - out[k] = rewrite_refs(v, f) - } else { - out[k] = v +interface Context { + readonly isDocumentRoot: boolean + readonly schema: (value: unknown, sourceKey: string, targetKey?: string) => unknown + readonly schemaAt: (value: unknown, sourcePath: Path, targetPath: Path) => unknown + readonly schemaArray: (value: unknown, sourceKey: string, targetKey?: string) => unknown + readonly schemaMap: (value: unknown, sourceKey: string, targetKey?: string) => unknown + readonly reference: (out: JsonSchema, value: unknown) => void +} + +type Adapter = (schema: JsonSchema, context: Context) => JsonSchema + +type PendingReference = readonly [out: JsonSchema, value: string, sourceResource: string] + +interface ResourceScope { + readonly parent?: ResourceScope + readonly sourceRoot: Path + readonly targetRoot: Path + readonly uri: string +} + +interface ConverterOptions { + readonly booleanAdapter?: (schema: boolean) => JsonSchema | boolean + readonly trackIds?: boolean + readonly ignoreRefSiblings?: boolean +} + +// Adapters decide which values are schemas. The kernel only handles recursion, +// resource scopes, and reference relocation between structural source/target paths. +function runConverter(adapter: Adapter, options: ConverterOptions | undefined, use: (convert: Convert) => A): A { + const locations = new Map() + const references: Array = [] + let rootUri = ROOT_URI + + function convert(root: unknown, sourcePath: Path = [], targetPath: Path = []): unknown { + if (sourcePath.length === 0 && options?.trackIds) { + const id = Predicate.isObject(root) ? getResourceId(root) : undefined + rootUri = resolveResourceUri(id, ROOT_URI) ?? ROOT_URI } + return loop(root, sourcePath, targetPath, { sourceRoot: [], targetRoot: [], uri: rootUri }) } + function finish(): void { + for (const [out, value, sourceResource] of references) { + let reference = value + const resolved = resolveUrl(value, sourceResource) + if (resolved !== undefined) { + const sourcePointer = parseUriFragment(resolved.hash) + resolved.hash = "" + if (sourcePointer !== undefined) { + const targetPath = locations.get(locationKey(resolved.href, sourcePointer)) + if (targetPath !== undefined) reference = relocateReference(value, targetPath) + } + } + InternalRecord.assignProperty(out, "$ref", reference) + } + } + + const out = use(convert) + finish() return out + + function loop( + node: unknown, + sourcePath: Path, + targetPath: Path, + resourceScope: ResourceScope + ): unknown { + if (typeof node === "boolean") { + recordLocations(sourcePath, targetPath, resourceScope) + return options?.booleanAdapter?.(node) ?? node + } + if (!Predicate.isObject(node)) return node + + let currentResourceScope = resourceScope + const id = getResourceId(node) + if (sourcePath.length > 0 && options?.trackIds && createsResource(id)) { + const uri = resolveResourceUri(id, resourceScope.uri) + if (uri !== undefined) { + currentResourceScope = { + parent: resourceScope, + sourceRoot: sourcePath, + targetRoot: targetPath, + uri + } + } + } + recordLocations(sourcePath, targetPath, currentResourceScope) + const currentResource = currentResourceScope.uri + + const context: Context = { + isDocumentRoot: sourcePath.length === 0, + schema(value, sourceKey, targetKey = sourceKey) { + return loop(value, [...sourcePath, sourceKey], [...targetPath, targetKey], currentResourceScope) + }, + schemaAt(value, sourceSuffix, targetSuffix) { + return loop(value, [...sourcePath, ...sourceSuffix], [...targetPath, ...targetSuffix], currentResourceScope) + }, + schemaArray(value, sourceKey, targetKey = sourceKey) { + return Array.isArray(value) + ? value.map((item, index) => + loop( + item, + [...sourcePath, sourceKey, String(index)], + [...targetPath, targetKey, String(index)], + currentResourceScope + ) + ) + : value + }, + schemaMap(value, sourceKey, targetKey = sourceKey) { + if (!Predicate.isObject(value)) return value + return mapObject(value, (item, key) => + loop( + item, + [...sourcePath, sourceKey, key], + [...targetPath, targetKey, key], + currentResourceScope + )) + }, + reference(out, value) { + if (typeof value === "string") { + references.push([out, value, currentResource]) + } else { + InternalRecord.assignProperty(out, "$ref", value) + } + } + } + return adapter(node, context) + } + + function getResourceId(schema: JsonSchema): unknown { + return options?.ignoreRefSiblings === true && typeof schema.$ref === "string" ? undefined : schema.$id + } + + function recordLocations(sourcePath: Path, targetPath: Path, scope: ResourceScope): void { + // A JSON Pointer can address an embedded schema from any containing resource. + if (scope.parent !== undefined) recordLocations(sourcePath, targetPath, scope.parent) + locations.set( + locationKey(scope.uri, sourcePath.slice(scope.sourceRoot.length)), + targetPath.slice(scope.targetRoot.length) + ) + } +} + +const ROOT_URI = "https://effect.invalid/.json-schema/" + +function resolveUrl(value: string, base: string): URL | undefined { + return URL.canParse(value, base) ? new URL(value, base) : undefined +} + +function resolveResourceUri(value: unknown, base: string): string | undefined { + if (typeof value !== "string") return undefined + const url = resolveUrl(value, base) + if (url === undefined) return undefined + url.hash = "" + return url.href +} + +function relocateReference(reference: string, targetPath: Path): string { + const index = reference.indexOf("#") + if (index === -1 && targetPath.length === 0) return reference + const uri = index === -1 ? reference : reference.slice(0, index) + return `${uri}${formatUriFragment(targetPath)}` +} + +function locationKey(resource: string, pointer: Path): string { + return `${resource}\u0000${JSON.stringify(pointer)}` } -function walk_object( +function createsResource(id: unknown): boolean { + return typeof id === "string" && id.length > 0 && id[0] !== "#" +} + +function convertSchema(root: JsonSchema, adapter: Adapter, options?: ConverterOptions): JsonSchema { + return runConverter(adapter, options, (convert) => convert(root) as JsonSchema) +} + +function convertDocument( + document: Document<"draft-2020-12">, + adapter: Adapter, + options?: ConverterOptions +): { readonly schema: JsonSchema; readonly definitions: Definitions } { + return runConverter(adapter, { ...options, trackIds: true }, (convert) => ({ + schema: convert(document.schema) as JsonSchema, + definitions: mapObject( + document.definitions, + (definition, key) => convert(definition, ["$defs", key], ["definitions", key]) as JsonSchema + ) as Definitions + })) +} + +const SCHEMA_MAP_KEYWORDS = new Set(["properties", "patternProperties"]) +const SCHEMA_ARRAY_KEYWORDS = new Set(["allOf", "anyOf", "oneOf"]) +const JSON_SCHEMA_SINGLE_KEYWORDS = new Set([ + "not", + "additionalProperties", + "propertyNames", + "contains", + "if", + "then", + "else", + "contentSchema" +]) +const OPEN_API_30_SCHEMA_MAP_KEYWORDS = new Set(["properties"]) +const OPEN_API_30_SCHEMA_SINGLE_KEYWORDS = new Set(["not", "items", "additionalProperties"]) +const DRAFT_04_SCHEMA_SINGLE_KEYWORDS = new Set(["not", "additionalProperties", "contentSchema"]) + +function convertSubschemaKeyword( + out: JsonSchema, + key: string, value: unknown, - walk: (node: unknown, isRoot: boolean) => unknown -): Record | undefined { - if (!Predicate.isObject(value)) return undefined - const out: Record = {} - for (const k of Object.keys(value)) out[k] = walk(value[k], false) - return out + context: Context, + singleKeywords: ReadonlySet, + mapKeywords: ReadonlySet = SCHEMA_MAP_KEYWORDS +): boolean { + let converted: unknown + if (mapKeywords.has(key)) converted = context.schemaMap(value, key) + else if (SCHEMA_ARRAY_KEYWORDS.has(key)) converted = context.schemaArray(value, key) + else if (singleKeywords.has(key)) converted = context.schema(value, key) + else return false + InternalRecord.assignProperty(out, key, converted) + return true } -function normalize_OpenApi3_0_to_Draft07(node: unknown): unknown { - if (Array.isArray(node)) return node.map(normalize_OpenApi3_0_to_Draft07) - if (!Predicate.isObject(node)) return node +const PRE_2020_TO_2020_COLLISIONS = [ + "$anchor", + "$defs", + "$dynamicAnchor", + "$dynamicRef", + "$vocabulary", + "contentSchema", + "dependentRequired", + "dependentSchemas", + "maxContains", + "minContains", + "prefixItems", + "unevaluatedItems", + "unevaluatedProperties" +] +const DRAFT_07_TO_2020_COLLISIONS = [...PRE_2020_TO_2020_COLLISIONS, "deprecated"] +const OPEN_API_30_TO_2020_COLLISIONS = [ + ...PRE_2020_TO_2020_COLLISIONS, + "$comment", + "$id", + "$schema", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "else", + "examples", + "if", + "patternProperties", + "propertyNames", + "then" +] +const ANCHOR_REGEXP = /^[A-Za-z_][-A-Za-z0-9._]*$/ +const LEGACY_ID_FRAGMENT_REGEXP = /^[A-Za-z][-A-Za-z0-9._:]*$/ + +function convertDraft07(root: JsonSchema): JsonSchema { + return convertSchema(root, (source, context) => { + const out: JsonSchema = {} + + if (typeof source.$ref === "string") { + context.reference(out, source.$ref) + if (Object.hasOwn(source, "definitions")) { + InternalRecord.assignProperty(out, "$defs", context.schemaMap(source.definitions, "definitions", "$defs")) + } + return out + } + rejectKeywordCollisions(source, DRAFT_07_TO_2020_COLLISIONS, "Draft 2020-12", "Draft-07") + + let items: unknown = undefined + let additionalItems: unknown = undefined - const src = node as Record - let out: Record = {} + for (const key of Object.keys(source)) { + const value = source[key] + if (convertSubschemaKeyword(out, key, value, context, JSON_SCHEMA_SINGLE_KEYWORDS)) continue + switch (key) { + case "$schema": + InternalRecord.assignProperty( + out, + key, + isMetaSchemaUri(value, META_SCHEMA_URI_DRAFT_07) ? META_SCHEMA_URI_DRAFT_2020_12 : value + ) + break + case "$id": + convertDraft07Id(out, value) + break + case "definitions": + InternalRecord.assignProperty(out, "$defs", context.schemaMap(value, key, "$defs")) + break + case "dependencies": { + if (!Predicate.isObject(value)) { + InternalRecord.assignProperty(out, key, value) + break + } + const dependentRequired: JsonSchema = {} + const dependentSchemas: JsonSchema = {} + for (const dependency of Object.keys(value)) { + const dependencyValue = value[dependency] + InternalRecord.assignProperty( + Array.isArray(dependencyValue) ? dependentRequired : dependentSchemas, + dependency, + Array.isArray(dependencyValue) + ? dependencyValue + : context.schemaAt( + dependencyValue, + ["dependencies", dependency], + ["dependentSchemas", dependency] + ) + ) + } + if (Object.keys(dependentRequired).length > 0) { + InternalRecord.assignProperty(out, "dependentRequired", dependentRequired) + } + if (Object.keys(dependentSchemas).length > 0) { + InternalRecord.assignProperty(out, "dependentSchemas", dependentSchemas) + } + break + } + case "items": + items = value + break + case "additionalItems": + additionalItems = value + break + default: + InternalRecord.assignProperty(out, key, value) + } + } - for (const k of Object.keys(src)) { - const v = src[k] - if (k === "$ref" && typeof v === "string") { - out[k] = v.replace(RE_COMPONENTS_SCHEMAS, "#/definitions") - } else if (k === "example") { - if (src.examples === undefined) { - out.examples = [v] + if (items !== undefined) { + if (Array.isArray(items)) { + InternalRecord.assignProperty(out, "prefixItems", context.schemaArray(items, "items", "prefixItems")) + if (additionalItems !== undefined) { + InternalRecord.assignProperty(out, "items", context.schema(additionalItems, "additionalItems", "items")) + } + } else { + InternalRecord.assignProperty(out, "items", context.schema(items, "items")) } - } else if (Array.isArray(v) || Predicate.isObject(v)) { - out[k] = normalize_OpenApi3_0_to_Draft07(v) - } else { - out[k] = v + } + + return out + }, { trackIds: true, ignoreRefSiblings: true }) +} + +function convertDraft07Id(out: JsonSchema, value: unknown): void { + if (typeof value !== "string" || !value.includes("#")) { + InternalRecord.assignProperty(out, "$id", value) + return + } + const fragmentIndex = value.indexOf("#") + const id = value.slice(0, fragmentIndex) + const anchor = value.slice(fragmentIndex + 1) + if (anchor.length === 0) { + if (id.length > 0) InternalRecord.assignProperty(out, "$id", id) + return + } + if (!ANCHOR_REGEXP.test(anchor)) { + unsupported("$id", "Draft 2020-12", `fragment "#${anchor}" is not a valid $anchor`) + } + if (id.length > 0) InternalRecord.assignProperty(out, "$id", id) + InternalRecord.assignProperty(out, "$anchor", anchor) +} + +function unsupported(keyword: string, dialect: string, details: string): never { + throw new Error(`Cannot convert JSON Schema keyword "${keyword}" to ${dialect}: ${details}`) +} + +function rejectKeywordCollisions( + source: JsonSchema, + keywords: ReadonlyArray, + targetDialect: string, + sourceDialect: string +): void { + for (const keyword of keywords) { + if (Object.hasOwn(source, keyword)) { + unsupported(keyword, targetDialect, `it is not active in ${sourceDialect} but would become active in the target`) + } + } +} + +const DRAFT_07_TARGET_COLLISIONS = ["additionalItems", "definitions", "dependencies"] + +function convertMetaSchemaKeyword( + out: JsonSchema, + value: unknown, + context: Context, + targetUri: string, + targetDialect: string +): void { + if (context.isDocumentRoot) { + InternalRecord.assignProperty( + out, + "$schema", + isMetaSchemaUri(value, META_SCHEMA_URI_DRAFT_2020_12) ? targetUri : value + ) + } else if (!isMetaSchemaUri(value, META_SCHEMA_URI_DRAFT_2020_12)) { + unsupported("$schema", targetDialect, "an embedded resource cannot declare a different dialect") + } +} + +function draft07Adapter(source: JsonSchema, context: Context): JsonSchema { + rejectKeywordCollisions(source, DRAFT_07_TARGET_COLLISIONS, "Draft-07", "Draft 2020-12") + const out: JsonSchema = {} + let reference: unknown = undefined + let prefixItems: unknown = undefined + let items: unknown = undefined + + for (const key of Object.keys(source)) { + const value = source[key] + if (convertSubschemaKeyword(out, key, value, context, JSON_SCHEMA_SINGLE_KEYWORDS)) continue + switch (key) { + case "$ref": + reference = value + break + case "$schema": + convertMetaSchemaKeyword(out, value, context, META_SCHEMA_URI_DRAFT_07, "Draft-07") + break + case "$id": + case "$anchor": + break + case "$defs": + InternalRecord.assignProperty(out, "definitions", context.schemaMap(value, key, "definitions")) + break + case "prefixItems": + prefixItems = value + break + case "items": + items = value + break + case "dependentRequired": + case "dependentSchemas": + case "minContains": + case "maxContains": + break + case "$dynamicRef": + case "$dynamicAnchor": + case "$vocabulary": + case "unevaluatedProperties": + case "unevaluatedItems": + unsupported(key, "Draft-07", "the target dialect has no equivalent") + case "required": + if (Array.isArray(value) && value.length === 0) break + InternalRecord.assignProperty(out, key, value) + break + default: + InternalRecord.assignProperty(out, key, value) } } - // Draft-04-style numeric exclusivity booleans - out = adjust_exclusivity(out) + convertTuple(out, prefixItems, items, context) - // OpenAPI 3.0 nullable - if (out.nullable === true) { - out = apply_nullable(out) + if (Object.hasOwn(source, "contains")) { + const minContains = source.minContains + const maxContains = source.maxContains + if ((minContains !== undefined && minContains !== 1) || maxContains !== undefined) { + unsupported("minContains/maxContains", "Draft-07", "contains cardinality cannot be represented") + } + if (Object.hasOwn(source, "minContains")) InternalRecord.assignProperty(out, "minContains", minContains) + } else { + if (Object.hasOwn(source, "minContains")) InternalRecord.assignProperty(out, "minContains", source.minContains) + if (Object.hasOwn(source, "maxContains")) InternalRecord.assignProperty(out, "maxContains", source.maxContains) } - delete out.nullable + + convertDependencies(source, out, context, "draft-07") + convertLegacyId(source, out, "$id", "Draft-07") + + convertReference(out, reference, context) return out } -function adjust_exclusivity(node: Record): Record { - let out = node +function convertTuple(out: JsonSchema, prefixItems: unknown, items: unknown, context: Context): void { + if (prefixItems === undefined) { + if (items !== undefined) InternalRecord.assignProperty(out, "items", context.schema(items, "items")) + return + } + InternalRecord.assignProperty(out, "items", context.schemaArray(prefixItems, "prefixItems", "items")) + if (items !== undefined) { + InternalRecord.assignProperty(out, "additionalItems", context.schema(items, "items", "additionalItems")) + } +} + +function convertReference(out: JsonSchema, reference: unknown, context: Context): void { + if (reference === undefined) return + if (typeof reference === "string" && Object.keys(out).length > 0) { + const referenceSchema: JsonSchema = {} + context.reference(referenceSchema, reference) + appendAllOf(out, referenceSchema) + } else { + context.reference(out, reference) + } +} - if (typeof out.exclusiveMinimum === "boolean") { - if (out.exclusiveMinimum === true && typeof out.minimum === "number") { - out = { ...out, exclusiveMinimum: out.minimum } - delete out.minimum +function convertDependencies( + source: JsonSchema, + out: JsonSchema, + context: Context, + targetDialect: "draft-04" | "draft-07" +): void { + const dependentRequired = Predicate.isObject(source.dependentRequired) ? source.dependentRequired : undefined + const dependentSchemas = Predicate.isObject(source.dependentSchemas) ? source.dependentSchemas : undefined + if (dependentRequired === undefined && dependentSchemas === undefined) return + + const dependencies: JsonSchema = {} + const keys = new Set([ + ...Object.keys(dependentRequired ?? {}), + ...Object.keys(dependentSchemas ?? {}) + ]) + for (const key of keys) { + const required = dependentRequired?.[key] + const dependency = dependentSchemas?.[key] + const omitRequired = targetDialect === "draft-04" && Array.isArray(required) && required.length === 0 + if (dependency === undefined) { + if (!omitRequired) InternalRecord.assignProperty(dependencies, key, required) + } else if (required === undefined || omitRequired) { + InternalRecord.assignProperty( + dependencies, + key, + context.schemaAt(dependency, ["dependentSchemas", key], ["dependencies", key]) + ) } else { - out = { ...out } - delete out.exclusiveMinimum + InternalRecord.assignProperty(dependencies, key, { + allOf: [ + context.schemaAt(dependency, ["dependentSchemas", key], ["dependencies", key, "allOf", "0"]), + { required } + ] + }) + } + } + if (Object.keys(dependencies).length > 0) InternalRecord.assignProperty(out, "dependencies", dependencies) +} + +function convertOpenApi30(root: JsonSchema): JsonSchema { + return convertSchema(root, (source, context) => { + const out: JsonSchema = {} + + if (typeof source.$ref === "string") { + context.reference(out, rewriteOpenApiComponentsReference(source.$ref)) + return out + } + rejectKeywordCollisions(source, OPEN_API_30_TO_2020_COLLISIONS, "Draft 2020-12", "OpenAPI 3.0") + + for (const key of Object.keys(source)) { + const value = source[key] + if ( + convertSubschemaKeyword( + out, + key, + value, + context, + OPEN_API_30_SCHEMA_SINGLE_KEYWORDS, + OPEN_API_30_SCHEMA_MAP_KEYWORDS + ) + ) { + continue + } + switch (key) { + case "example": + InternalRecord.assignProperty(out, "examples", [value]) + break + case "nullable": + case "exclusiveMinimum": + case "exclusiveMaximum": + break + default: + InternalRecord.assignProperty(out, key, value) + } + } + + convertOpenApiExclusiveBound(source, out, "minimum") + convertOpenApiExclusiveBound(source, out, "maximum") + + if (source.nullable === true && typeof source.type === "string") { + InternalRecord.assignProperty(out, "type", [source.type, "null"]) + } + + return out + }) +} + +function convertOpenApiExclusiveBound( + source: JsonSchema, + out: JsonSchema, + boundKey: "minimum" | "maximum" +): void { + const exclusiveKey = boundKey === "minimum" ? "exclusiveMinimum" : "exclusiveMaximum" + const exclusive = source[exclusiveKey] + if (typeof exclusive !== "boolean") { + if (exclusive !== undefined) InternalRecord.assignProperty(out, exclusiveKey, exclusive) + return + } + if (exclusive && typeof source[boundKey] === "number") { + InternalRecord.assignProperty(out, exclusiveKey, source[boundKey]) + delete out[boundKey] + } +} + +const DRAFT_04_TARGET_COLLISIONS = ["additionalItems", "definitions", "dependencies", "id"] + +function draft04Adapter(source: JsonSchema, context: Context): JsonSchema { + rejectKeywordCollisions(source, DRAFT_04_TARGET_COLLISIONS, "Draft-04", "Draft 2020-12") + const out: JsonSchema = {} + let reference: unknown = undefined + let prefixItems: unknown = undefined + let items: unknown = undefined + let constSchema: JsonSchema | undefined + + for (const key of Object.keys(source)) { + const value = source[key] + if (convertSubschemaKeyword(out, key, value, context, DRAFT_04_SCHEMA_SINGLE_KEYWORDS)) continue + switch (key) { + case "$ref": + reference = value + break + case "$schema": + convertMetaSchemaKeyword(out, value, context, META_SCHEMA_URI_DRAFT_04, "Draft-04") + break + case "$id": + case "$anchor": + break + case "$defs": + InternalRecord.assignProperty(out, "definitions", context.schemaMap(value, key, "definitions")) + break + case "prefixItems": + prefixItems = value + break + case "items": + items = value + break + case "$dynamicRef": + case "$dynamicAnchor": + case "$vocabulary": + case "unevaluatedProperties": + case "unevaluatedItems": + case "propertyNames": + unsupported(key, "Draft-04", "the target dialect has no equivalent") + case "dependentRequired": + case "dependentSchemas": + case "contains": + case "minContains": + case "maxContains": + case "if": + case "then": + case "else": + case "minimum": + case "maximum": + case "exclusiveMinimum": + case "exclusiveMaximum": + break + case "const": + constSchema = { enum: [value] } + break + case "required": + if (Array.isArray(value) && value.length === 0) break + InternalRecord.assignProperty(out, key, value) + break + default: + InternalRecord.assignProperty(out, key, value) } } - if (typeof out.exclusiveMaximum === "boolean") { - if (out.exclusiveMaximum === true && typeof out.maximum === "number") { - out = { ...out, exclusiveMaximum: out.maximum } - delete out.maximum + convertTuple(out, prefixItems, items, context) + + convertDraft04ExclusiveBound(source, out, "minimum") + convertDraft04ExclusiveBound(source, out, "maximum") + convertDependencies(source, out, context, "draft-04") + convertLegacyId(source, out, "id", "Draft-04") + convertDraft04Conditionals(source, out, context) + convertDraft04Contains(source, out, context) + + if (constSchema !== undefined) { + if (Object.hasOwn(source, "enum")) { + appendAllOf(out, constSchema) } else { - out = { ...out } - delete out.exclusiveMaximum + InternalRecord.assignProperty(out, "enum", constSchema.enum) } } + convertReference(out, reference, context) + return out } -function apply_nullable(node: Record): Record { - // enum widening - if (Array.isArray(node.enum)) { - return widen_type({ - ...node, - enum: node.enum.includes(null) ? node.enum : [...node.enum, null] - }) +function convertDraft04Conditionals(source: JsonSchema, out: JsonSchema, context: Context): void { + const hasIf = Object.hasOwn(source, "if") + const hasThen = Object.hasOwn(source, "then") + const hasElse = Object.hasOwn(source, "else") + if (!hasIf || (!hasThen && !hasElse)) { + if (hasIf) InternalRecord.assignProperty(out, "if", context.schema(source.if, "if")) + if (hasThen) InternalRecord.assignProperty(out, "then", context.schema(source.then, "then")) + if (hasElse) InternalRecord.assignProperty(out, "else", context.schema(source.else, "else")) + return + } + + const index = Array.isArray(out.allOf) ? out.allOf.length : 0 + const base = ["allOf", String(index), "anyOf"] as const + const convertBranch = (key: "if" | "then" | "else", targetPath: Path): unknown => + context.schemaAt(source[key], [key], [...base, ...targetPath]) + let conditional: JsonSchema + if (hasThen && hasElse) { + if (hasSchemaIdentifier(source.if)) { + unsupported("if", "Draft-04", "lowering both branches would duplicate a schema identifier") + } + // (if AND then) OR ((NOT if) AND else) + conditional = { + anyOf: [ + { + allOf: [ + convertBranch("if", ["0", "allOf", "0"]), + convertBranch("then", ["0", "allOf", "1"]) + ] + }, + { + allOf: [ + { not: convertBranch("if", ["1", "allOf", "0", "not"]) }, + convertBranch("else", ["1", "allOf", "1"]) + ] + } + ] + } + } else if (hasThen) { + // (NOT if) OR then + conditional = { + anyOf: [ + { not: convertBranch("if", ["0", "not"]) }, + convertBranch("then", ["1"]) + ] + } + } else { + // if OR else + conditional = { + anyOf: [ + convertBranch("if", ["0"]), + convertBranch("else", ["1"]) + ] + } + } + appendAllOf(out, conditional) +} + +function hasSchemaIdentifier(node: unknown): boolean { + if (!Predicate.isObject(node)) return false + if (Object.hasOwn(node, "$id") || Object.hasOwn(node, "$anchor")) return true + for (const key of Object.keys(node)) { + const value = node[key] + switch (key) { + case "$defs": + case "properties": + case "patternProperties": + case "dependentSchemas": + if (Predicate.isObject(value) && Object.values(value).some(hasSchemaIdentifier)) return true + break + case "allOf": + case "anyOf": + case "oneOf": + case "prefixItems": + if (Array.isArray(value) && value.some(hasSchemaIdentifier)) return true + break + case "not": + case "additionalProperties": + case "propertyNames": + case "unevaluatedProperties": + case "items": + case "contains": + case "unevaluatedItems": + case "if": + case "then": + case "else": + case "contentSchema": + if (hasSchemaIdentifier(value)) return true + } } + return false +} - // type widening - if (node.type !== undefined) return widen_type(node) +function convertDraft04Contains(source: JsonSchema, out: JsonSchema, context: Context): void { + if (!Object.hasOwn(source, "contains")) { + if (Object.hasOwn(source, "minContains")) InternalRecord.assignProperty(out, "minContains", source.minContains) + if (Object.hasOwn(source, "maxContains")) InternalRecord.assignProperty(out, "maxContains", source.maxContains) + return + } - // const === null - if (node.const === null) return node + const minContains = source.minContains + const maxContains = source.maxContains + if ((minContains !== undefined && minContains !== 1) || maxContains !== undefined) { + unsupported("minContains/maxContains", "Draft-04", "contains cardinality cannot be represented") + } - // fallback - return { anyOf: [node, { type: "null" }] } + const index = Array.isArray(out.allOf) ? out.allOf.length : 0 + const contains = context.schemaAt( + source.contains, + ["contains"], + ["allOf", String(index), "anyOf", "1", "not", "items", "not"] + ) + appendAllOf(out, { + anyOf: [ + { not: { type: "array" } }, + { not: { items: { not: contains } } } + ] + }) } -function widen_type(node: Record): Record { - const t = node.type - if (typeof t === "string") return t === "null" ? node : { ...node, type: [t, "null"] } - if (Array.isArray(t)) return t.includes("null") ? node : { ...node, type: [...t, "null"] } - return node +function appendAllOf(out: JsonSchema, schema: JsonSchema): void { + if (Array.isArray(out.allOf)) out.allOf.push(schema) + else InternalRecord.assignProperty(out, "allOf", [schema]) } -/** - * Resolves a `$ref` string by looking up the last path segment in a - * definitions map. - * - * **When to use** - * - * Use when you need to dereference a `$ref` pointer to get the JSON Schema - * object it points to. - * - * **Details** - * - * This only resolves the final segment of the ref path, such as `"User"` from - * `"#/$defs/User"`. It returns `undefined` if the definition is not found. - * - * **Gotchas** - * - * This function does not follow arbitrary JSON Pointer paths. - * - * **Example** (Resolving a $ref) - * - * ```ts - * import { JsonSchema } from "effect" - * - * const definitions: JsonSchema.Definitions = { - * User: { type: "object", properties: { name: { type: "string" } } } - * } - * - * const result = JsonSchema.resolve$ref("#/$defs/User", definitions) - * console.log(result) // { type: "object", properties: { name: { type: "string" } } } - * - * const missing = JsonSchema.resolve$ref("#/$defs/Unknown", definitions) - * console.log(missing) // undefined - * ``` - * - * @see {@link resolveTopLevel$ref} - * @see {@link Definitions} - * @category getters - * @since 4.0.0 - */ -export function resolve$ref($ref: string, definitions: Definitions): JsonSchema | undefined { - const tokens = $ref.split("/") - if (tokens.length > 0) { - const identifier = unescapeToken(tokens[tokens.length - 1]) - const definition = definitions[identifier] - if (definition !== undefined) { - return definition - } +function convertLegacyId( + source: JsonSchema, + out: JsonSchema, + targetKey: "$id" | "id", + dialect: string +): void { + const id = source.$id + const anchor = source.$anchor + if (anchor === undefined) { + if (id !== undefined) InternalRecord.assignProperty(out, targetKey, id) + return + } + if (typeof anchor !== "string" || !ANCHOR_REGEXP.test(anchor)) { + unsupported("$anchor", dialect, "the anchor is not valid") + } + if (!LEGACY_ID_FRAGMENT_REGEXP.test(anchor)) { + unsupported("$anchor", dialect, "the anchor cannot be represented as a plain-name fragment identifier") + } + if (id === undefined) { + InternalRecord.assignProperty(out, targetKey, `#${anchor}`) + } else { + unsupported("$anchor", dialect, "it cannot be combined with the schema $id") } } -/** - * Resolves a document whose root schema is a top-level `$ref`. - * - * **When to use** - * - * Use when you need to dereference a top-level `$ref` before inspecting the - * root JSON Schema object's properties directly. - * - * **Details** - * - * This returns the same object if no change is needed, or a shallow copy with - * the resolved schema. - * - * **Example** (Resolving a top-level $ref) - * - * ```ts - * import { JsonSchema } from "effect" - * - * const doc: JsonSchema.Document<"draft-2020-12"> = { - * dialect: "draft-2020-12", - * schema: { $ref: "#/$defs/User" }, - * definitions: { - * User: { type: "object", properties: { name: { type: "string" } } } - * } - * } - * - * const resolved = JsonSchema.resolveTopLevel$ref(doc) - * console.log(resolved.schema) // { type: "object", properties: { name: { type: "string" } } } - * ``` - * - * @see {@link resolve$ref} - * @see {@link Document} - * @category transforming - * @since 4.0.0 - */ -export function resolveTopLevel$ref(document: Document<"draft-2020-12">): Document<"draft-2020-12"> { - if (typeof document.schema.$ref === "string") { - const schema = resolve$ref(document.schema.$ref, document.definitions) - if (schema !== undefined) { - return { ...document, schema } +function convertDraft04ExclusiveBound( + source: JsonSchema, + out: JsonSchema, + boundKey: "minimum" | "maximum" +): void { + const exclusiveKey = boundKey === "minimum" ? "exclusiveMinimum" : "exclusiveMaximum" + const bound = source[boundKey] + const exclusive = source[exclusiveKey] + if (typeof exclusive === "number") { + const isBoundStricter = typeof bound === "number" && + (boundKey === "minimum" ? bound > exclusive : bound < exclusive) + if (isBoundStricter) { + InternalRecord.assignProperty(out, boundKey, bound) + } else { + InternalRecord.assignProperty(out, boundKey, exclusive) + InternalRecord.assignProperty(out, exclusiveKey, true) } + } else if (bound !== undefined) { + InternalRecord.assignProperty(out, boundKey, bound) } - return document } diff --git a/packages/effect/src/Latch.ts b/packages/effect/src/Latch.ts index 418c78e44..62e73d736 100644 --- a/packages/effect/src/Latch.ts +++ b/packages/effect/src/Latch.ts @@ -28,18 +28,18 @@ import * as internal from "./internal/effect.ts" * * **Example** (Coordinating fibers with a latch) * - * ```ts - * import { Effect, Latch } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" * * // Create and use a latch for coordination between fibers * const program = Effect.gen(function*() { * const latch = yield* Latch.make() - * - * // Wait for the latch to be opened - * yield* latch.await - * - * return "Latch was opened!" + * const waiter = yield* Effect.forkChild(latch.await.pipe(Effect.as("opened"))) + * yield* latch.open + * return yield* Fiber.join(waiter) * }) + * + * await Effect.runPromise(program) // => "opened" * ``` * * @see {@link make} for creating a latch inside Effect code @@ -138,24 +138,19 @@ export interface Latch { * * **Example** (Creating a latch unsafely) * - * ```ts - * import { Effect, Latch } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" * * const latch = Latch.makeUnsafe(false) + * const waiter = latch.await.pipe(Effect.as("opened")) * - * const waiter = Effect.gen(function*() { - * yield* Effect.log("Waiting for latch to open...") - * yield* latch.await - * yield* Effect.log("Latch opened! Continuing...") - * }) - * - * const opener = Effect.gen(function*() { - * yield* Effect.sleep("2 seconds") - * yield* Effect.log("Opening latch...") + * const program = Effect.gen(function*() { + * const fiber = yield* Effect.forkChild(waiter) * yield* latch.open + * return yield* Fiber.join(fiber) * }) * - * const program = Effect.all([waiter, opener]) + * await Effect.runPromise(program) // => "opened" * ``` * * @see {@link make} for creating a latch inside Effect code @@ -178,26 +173,19 @@ export const makeUnsafe: (open?: boolean | undefined) => Latch = internal.makeLa * * **Example** (Creating a latch) * - * ```ts - * import { Effect, Latch } from "effect" + * ```ts import.meta.vitest + * import { Effect, Fiber, Latch } from "effect" * * const program = Effect.gen(function*() { * const latch = yield* Latch.make(false) + * const waiter = latch.await.pipe(Effect.as("opened")) * - * const waiter = Effect.gen(function*() { - * yield* Effect.log("Waiting for latch to open...") - * yield* latch.await - * yield* Effect.log("Latch opened! Continuing...") - * }) - * - * const opener = Effect.gen(function*() { - * yield* Effect.sleep("2 seconds") - * yield* Effect.log("Opening latch...") - * yield* latch.open - * }) - * - * yield* Effect.all([waiter, opener]) + * const fiber = yield* Effect.forkChild(waiter) + * yield* latch.open + * return yield* Fiber.join(fiber) * }) + * + * await Effect.runPromise(program) // => "opened" * ``` * * @see {@link makeUnsafe} for synchronous allocation outside Effect code @@ -388,7 +376,7 @@ export const whenOpen: { * * Use to check the state of the latch without suspending or changing its state. * - * @category getters + * @category predicates * @since 4.0.0 */ export const isOpen = (self: Latch): boolean => self.isOpen() diff --git a/packages/effect/src/Layer.ts b/packages/effect/src/Layer.ts index 3b590a509..c5bd8f629 100644 --- a/packages/effect/src/Layer.ts +++ b/packages/effect/src/Layer.ts @@ -192,7 +192,7 @@ const MemoMapTypeId = "~effect/Layer/MemoMap" * * **Example** (Sharing layer construction with a memo map) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service "result" * ``` * * @category models @@ -251,7 +254,7 @@ const memoMapReuse = ( * * **Example** (Checking whether a value is a layer) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service( * }) * const notALayer = { someProperty: "value" } * - * console.log(Layer.isLayer(dbLayer)) // true - * console.log(Layer.isLayer(notALayer)) // false + * Layer.isLayer(dbLayer) // => true + * Layer.isLayer(notALayer) // => false * ``` * - * @category getters + * @category guards * @since 2.0.0 */ export const isLayer = (u: unknown): u is Layer => hasProperty(u, TypeId) @@ -305,7 +308,7 @@ const fromBuildUnsafe = ( * * **Example** (Constructing a layer from a build function) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service( * }) * ) * ) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "result" * ``` * * @category constructors @@ -349,7 +355,7 @@ export const fromBuild = ( * * **Example** (Memoizing layer construction) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service( * }) * ) * ) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "result" * ``` * * @category constructors @@ -438,11 +447,13 @@ class MemoMapImpl implements MemoMap { scope: Scope.Scope, build: (memoMap: MemoMap, scope: Scope.Scope) => Effect, E, RIn> ): Effect, E, RIn> { - const existing = this.get(layer, scope) - if (existing) { - return existing - } - return memoMapBuild(this, layer, scope, build) + return internalEffect.suspend(() => { + const existing = this.get(layer, scope) + if (existing) { + return existing + } + return memoMapBuild(this, layer, scope, build) + }) } } @@ -451,7 +462,7 @@ class MemoMapImpl implements MemoMap { * * **Example** (Creating a memo map unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service "result" * ``` * - * @category memo map + * @category constructors * @since 4.0.0 */ export const makeMemoMapUnsafe = (): MemoMap => new MemoMapImpl() @@ -491,7 +505,7 @@ export const makeMemoMapUnsafe = (): MemoMap => new MemoMapImpl() * @see {@link forkMemoMap} for allocating the child memo map inside `Effect` * @see {@link makeMemoMapUnsafe} for creating a root memo map without a parent * - * @category memo map + * @category constructors * @since 4.0.0 */ export const forkMemoMapUnsafe = (parent: MemoMap): MemoMap => new MemoMapImpl(parent) @@ -501,7 +515,7 @@ export const forkMemoMapUnsafe = (parent: MemoMap): MemoMap => new MemoMapImpl(p * * **Example** (Creating a memo map in an effect) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service new MemoMapImpl(p * * return Context.get(context, Database) * }) + * + * const database = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(database.query("SELECT 1")) // => "result" * ``` * - * @category memo map + * @category constructors * @since 2.0.0 */ export const makeMemoMap: Effect = internalEffect.sync(makeMemoMapUnsafe) @@ -541,7 +558,7 @@ export const makeMemoMap: Effect = internalEffect.sync(makeMemoMapUnsaf * @see {@link forkMemoMapUnsafe} for the synchronous constructor variant * @see {@link buildWithMemoMap} for building layers with an explicit memo map * - * @category memo map + * @category constructors * @since 4.0.0 */ export const forkMemoMap = (parent: MemoMap): Effect => internalEffect.sync(() => forkMemoMapUnsafe(parent)) @@ -561,14 +578,14 @@ export const forkMemoMap = (parent: MemoMap): Effect => internalEffect. * * @see {@link MemoMap} the memoization map type wrapped by this service * - * @category models + * @category services * @since 3.13.0 */ export class CurrentMemoMap extends Context.Service()("effect/Layer/CurrentMemoMap") { - static getOrCreate: (self: Context.Context) => MemoMap = Context.getOrElse( - this, - makeMemoMapUnsafe - ) + static forkOrCreate(self: Context.Context): MemoMap { + const current = Context.getOrUndefined(self, CurrentMemoMap) + return current ? forkMemoMapUnsafe(current) : makeMemoMapUnsafe() + } } /** @@ -577,7 +594,7 @@ export class CurrentMemoMap extends Context.Service()(" * * **Example** (Building layers with an explicit memo map) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service()(" * readonly log: (msg: string) => Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] + * * // Build layers with explicit memoization control * const program = Effect.gen(function*() { * const memoMap = yield* Layer.makeMemoMap @@ -601,7 +620,7 @@ export class CurrentMemoMap extends Context.Service()(" * * // Build logger layer with same memoization (reuses memo if same layer) * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg))) * }) * const loggerContext = yield* Layer.buildWithMemoMap( * loggerLayer, @@ -614,9 +633,13 @@ export class CurrentMemoMap extends Context.Service()(" * logger: Context.get(loggerContext, Logger) * } * }) + * + * const services = Effect.runSync(Effect.scoped(program)) + * Effect.runSync(services.logger.log("ready")) + * logs // => ["ready"] * ``` * - * @category memo map + * @category destructors * @since 2.0.0 */ export const buildWithMemoMap: { @@ -645,7 +668,7 @@ export const buildWithMemoMap: { * * **Example** (Building a layer into a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service "result" * ``` * * @category destructors @@ -677,7 +702,7 @@ export const build = ( core.withFiber((fiber) => buildWithMemoMap( self, - CurrentMemoMap.getOrCreate(fiber.context), + CurrentMemoMap.forkOrCreate(fiber.context), Context.getUnsafe(fiber.context, Scope.Scope) ) ) @@ -697,22 +722,24 @@ export const build = ( * * **Example** (Building a layer with an explicit scope) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, Scope } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * + * const logs: Array = [] + * * // Build a layer with explicit scope control * const program = Effect.gen(function*() { * const scope = yield* Effect.scope * * const dbLayer = Layer.effect(Database, Effect.gen(function*() { - * console.log("Initializing database...") + * logs.push("Initializing database...") * yield* Scope.addFinalizer( * scope, - * Effect.sync(() => console.log("Database closed")) + * Effect.sync(() => logs.push("Database closed")) * ) * return { query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) } * })) @@ -724,6 +751,9 @@ export const build = ( * return yield* database.query("SELECT * FROM users") * // Database will be closed when scope is closed * }) + * + * Effect.runSync(Effect.scoped(program)) // => "Result: SELECT * FROM users" + * logs // => ["Initializing database...", "Database closed"] * ``` * * @category destructors @@ -739,7 +769,7 @@ export const buildWithScope: { core.withFiber((fiber) => buildWithMemoMap( self, - CurrentMemoMap.getOrCreate(fiber.context), + CurrentMemoMap.forkOrCreate(fiber.context), scope ) )) @@ -755,16 +785,18 @@ export const buildWithScope: { * * **Example** (Creating a layer from a service implementation) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * - * const DatabaseLive = Layer.succeed(Database, { + * const DatabaseLayer = Layer.succeed(Database, { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`)) * }) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, DatabaseLayer)) // => "Query result: SELECT 1" * ``` * * @see {@link sync} for constructing layers from lazy values @@ -798,7 +830,7 @@ export const succeed: { * * **Example** (Providing multiple services from a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] * const context = Context.make(Database, { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) * }).pipe( * Context.add(Logger, { - * log: (msg: string) => Effect.sync(() => console.log(msg)) + * log: (msg: string) => Effect.sync(() => logs.push(msg)) * }) * ) * * const layer = Layer.succeedContext(context) + * const program = Logger.use((logger) => logger.log("ready")) + * Effect.runSync(Effect.provide(program, layer)) + * logs // => ["ready"] * ``` * * @see {@link succeed} for providing a single service from a value @@ -838,14 +874,12 @@ export const succeedContext = (context: Context.Context): Layer => * * **Example** (Disabling optional lifecycle work) * - * ```ts - * import { Console, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Option } from "effect" * - * declare const flag: boolean - * - * const StartupLogLive = flag - * ? Layer.effectDiscard(Console.log("application starting")) - * : Layer.empty + * const Service = Context.Service("Service") + * const context = Effect.runSync(Effect.scoped(Layer.build(Layer.empty))) + * Context.getOption(context, Service) // => Option.none() * ``` * * @see {@link effectDiscard} for running an effect while providing no services @@ -870,7 +904,7 @@ export const empty: Layer = succeedContext(Context.empty()) * * **Example** (Lazily providing a service) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service = succeedContext(Context.empty()) * const layer = Layer.sync(Database, () => ({ * query: (sql: string) => Effect.succeed(`Query: ${sql}`) * })) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link succeed} for constructing layers from static values @@ -912,7 +948,7 @@ export const sync: { * * **Example** (Lazily providing a context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`Query: ${sql}`) * }) * ) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link sync} for lazily providing a single service @@ -951,7 +989,7 @@ export const syncContext = (evaluate: LazyArg>): Layer * * **Example** (Creating a layer from an effect) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service(evaluate: LazyArg>): Layer * query: (sql: string) => Effect.succeed(`Query: ${sql}`) * })) * ) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link effectContext} for effectfully providing multiple services @@ -1008,7 +1048,7 @@ const effectImpl = ( * * **Example** (Creating a layer from an effectful context) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service< @@ -1021,6 +1061,8 @@ const effectImpl = ( * query: (sql: string) => Effect.succeed(`Query: ${sql}`) * })) * ) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, layer)) // => "Query: SELECT 1" * ``` * * @see {@link effect} for effectfully providing a single service @@ -1043,14 +1085,17 @@ export const effectContext = ( * * **Example** (Running an effect during layer construction) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Layer } from "effect" * + * const logs: Array = [] * const initLayer = Layer.effectDiscard( * Effect.sync(() => { - * console.log("Initializing application...") + * logs.push("Initializing application...") * }) * ) + * Effect.runSync(Effect.scoped(Layer.build(initLayer))) + * logs // => ["Initializing application..."] * ``` * * @see {@link empty} for a no-op layer that performs no construction work @@ -1071,8 +1116,8 @@ export const effectDiscard = (effect: Effect): Layer()("Config") {} * @@ -1083,6 +1128,7 @@ export const effectDiscard = (effect: Effect): Layer "https://api.example.com" * ``` * * @category constructors @@ -1091,6 +1137,8 @@ export const effectDiscard = (effect: Effect): Layer(evaluate: LazyArg>): Layer => fromBuildMemo((memoMap, scope) => internalEffect.suspend(() => evaluate().build(memoMap, scope))) +const unwrapKey = Context.Service>("effect/Layer/unwrap") + /** * Unwraps a `Layer` from an `Effect`, flattening the nested structure. * @@ -1106,7 +1154,7 @@ export const suspend = (evaluate: LazyArg>): Layer(evaluate: LazyArg>): Layer database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, unwrappedLayer)) // => "result" * ``` * * @category converting @@ -1125,10 +1175,7 @@ export const suspend = (evaluate: LazyArg>): Layer( self: Effect, E, R> -): Layer> => { - const service = Context.Service>("effect/Layer/unwrap") - return flatMap(effect(service)(self), Context.get(service)) -} +): Layer> => flatMap(effect(unwrapKey)(self), Context.get(unwrapKey)) const mergeAllEffect = , ...Array>]>( layers: Layers, @@ -1165,7 +1212,7 @@ const mergeAllEffect = , ...Array, ...Array Effect.succeed("result")) * }) + * const logs: Array = [] * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg))) * }) * * const mergedLayer = Layer.mergeAll(dbLayer, loggerLayer) + * const program = Logger.use((logger) => logger.log("ready")) + * Effect.runSync(Effect.provide(program, mergedLayer)) + * logs // => ["ready"] * ``` * * @see {@link merge} for merging one layer with another layer or array @@ -1216,7 +1267,7 @@ export const mergeAll = , ...Array, ...Array Effect.succeed("result")) * }) * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((_msg: string) => Effect.void) * }) * * const mergedLayer = Layer.merge(dbLayer, loggerLayer) + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, mergedLayer)) // => "result" * ``` * * @see {@link mergeAll} for merging several layers at once @@ -1310,7 +1363,7 @@ const provideWith = ( * * **Example** (Providing layer dependencies) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`DB: ${sql}`)) * }) * + * const logs: Array = [] * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`))) * }) * * // UserService depends on Database and Logger @@ -1365,6 +1419,8 @@ const provideWith = ( * }).pipe( * Effect.provide(userServiceWithDependencies) * ) + * Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" } + * logs // => ["[LOG] Looking up user 123"] * ``` * * @see {@link provideMerge} for retaining the dependency services @@ -1419,7 +1475,7 @@ export const provide: { * * **Example** (Providing dependencies while retaining services) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service Effect.succeed(`DB: ${sql}`)) * }) * + * const logs: Array = [] * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(`[LOG] ${msg}`))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`))) * }) * * // UserService depends on Database and Logger @@ -1480,6 +1537,8 @@ export const provide: { * }).pipe( * Effect.provide(allServicesLayer) * ) + * Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" } + * logs // => ["[LOG] Looking up user 123", "[LOG] Found user: DB: SELECT * FROM users WHERE id = 123"] * ``` * * @see {@link provide} for keeping dependency services private @@ -1529,7 +1588,7 @@ export const provideMerge: { * * **Example** (Creating services from layer output) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Config extends Context.Service Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] + * * // Base config layer * const configLayer = Layer.succeed(Config, { * dbUrl: "postgres://localhost:5432/mydb", @@ -1568,8 +1629,8 @@ export const provideMerge: { * const loggerLayer = Layer.succeed(Logger, { * log: Effect.fn("Logger.log")((msg: string) => * config.logLevel === "debug" - * ? Effect.sync(() => console.log(`[DEBUG] ${msg}`)) - * : Effect.sync(() => console.log(msg)) + * ? Effect.sync(() => logs.push(`[DEBUG] ${msg}`)) + * : Effect.sync(() => logs.push(msg)) * ) * }) * @@ -1590,6 +1651,8 @@ export const provideMerge: { * }).pipe( * Effect.provide(dynamicServiceLayer) * ) + * Effect.runSync(program) // => "Querying postgres://localhost:5432/mydb: SELECT * FROM users" + * logs // => ["[DEBUG] Starting database query"] * ``` * * @category sequencing @@ -1674,21 +1737,24 @@ export const tap: { * @since 2.0.0 */ export const tapError: { - ( - f: (e: XE) => Effect + ( + f: (e: Types.NoInfer) => Effect ): (self: Layer) => Layer> - ( + ( + f: (e: E) => Effect + ): (self: Layer) => Layer> + ( self: Layer, - f: (e: XE) => Effect + f: (e: Types.NoInfer) => Effect ): Layer> -} = dual(2, ( +} = dual(2, ( self: Layer, - f: (e: XE) => Effect + f: (e: E) => Effect ): Layer> => fromBuild((memoMap, scope) => internalEffect.catch_( self.build(memoMap, scope), - (error) => Scope.provide(internalEffect.andThen(f(error as XE), internalEffect.fail(error)), scope) + (error) => Scope.provide(internalEffect.andThen(f(error), internalEffect.fail(error)), scope) ) )) @@ -1714,22 +1780,24 @@ export const tapError: { * @since 4.0.0 */ export const tapCause: { - ( - f: (cause: Cause.Cause) => Effect + ( + f: (cause: Cause.Cause>) => Effect ): (self: Layer) => Layer> - ( + ( + f: (cause: Cause.Cause) => Effect + ): (self: Layer) => Layer> + ( self: Layer, - f: (cause: Cause.Cause) => Effect + f: (cause: Cause.Cause>) => Effect ): Layer> -} = dual(2, ( +} = dual(2, ( self: Layer, - f: (cause: Cause.Cause) => Effect + f: (cause: Cause.Cause) => Effect ): Layer> => fromBuild((memoMap, scope) => internalEffect.catchCause( self.build(memoMap, scope), - (cause) => - Scope.provide(internalEffect.andThen(f(cause as Cause.Cause), internalEffect.failCause(cause)), scope) + (cause) => Scope.provide(internalEffect.andThen(f(cause), internalEffect.failCause(cause)), scope) ) )) @@ -1744,8 +1812,8 @@ export const tapCause: { * * **Example** (Converting layer failures to defects) * - * ```ts - * import { Context, Data, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Data, Effect, Exit, Layer } from "effect" * * class DatabaseError extends Data.TaggedError("DatabaseError")<{ * message: string @@ -1756,10 +1824,11 @@ export const tapCause: { * }>()("Database") {} * * // Layer that can fail during construction - * const flakyDatabaseLayer = Layer.effect(Database, Effect.gen(function*() { - * console.log("connecting") - * return yield* new DatabaseError({ message: "Connection failed" }) - * })) + * const error = new DatabaseError({ message: "Connection failed" }) + * const flakyDatabaseLayer = Layer.effect( + * Database, + * Effect.fail(error) + * ) * * // Convert failures to fiber death - removes error from type * const reliableDatabaseLayer = flakyDatabaseLayer.pipe(Layer.orDie) @@ -1772,8 +1841,7 @@ export const tapCause: { * Effect.provide(reliableDatabaseLayer) * ) * - * // Running the program prints "connecting", then the DatabaseError is - * // converted into a fiber defect instead of remaining a typed error. + * Effect.runSync(Effect.exit(program)) // => Exit.die(error) * ``` * * @category error handling @@ -1828,7 +1896,7 @@ export { * * **Example** (Recovering from tagged layer errors) * - * ```ts + * ```ts import.meta.vitest * import { Context, Data, Effect, Layer } from "effect" * * class ConfigError extends Data.TaggedError("ConfigError") {} @@ -1844,6 +1912,8 @@ export { * const recovered = configLayer.pipe( * Layer.catchTag("ConfigError", () => fallbackLayer) * ) + * const program = Config.useSync((config) => config.apiUrl) + * Effect.runSync(Effect.provide(program, recovered)) // => "http://localhost" * ``` * * @see {@link catchCause} for recovering with access to the full cause @@ -1919,7 +1989,7 @@ export const catchTag: { * * **Example** (Recovering from layer failures by cause) * - * ```ts + * ```ts import.meta.vitest * import { Context, Data, Effect, Layer } from "effect" * * class DatabaseError extends Data.TaggedError("DatabaseError")<{ @@ -1944,14 +2014,12 @@ export const catchTag: { * * const program = Effect.gen(function*() { * const database = yield* Database - * const result = yield* database.query("SELECT * FROM users") - * console.log(result) + * return yield* database.query("SELECT * FROM users") * }).pipe( * Effect.provide(databaseWithFallback) * ) * - * Effect.runPromise(program) - * // Memory: SELECT * FROM users + * await Effect.runPromise(program) // => "Memory: SELECT * FROM users" * ``` * * @see {@link catchTag} for recovering from specific tagged errors @@ -2030,7 +2098,7 @@ export const updateService: { * * **Example** (Creating non-shared layer instances) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, Ref } from "effect" * * class Counter extends Context.Service n + 1) - * console.log("constructed Counter") * return { id } * })) * @@ -2075,7 +2142,7 @@ export const updateService: { * Layer.provide(rightLayer, counterLayer) * ) * - * yield* Effect.provide(showIds, shared) + * const sharedResult = yield* Effect.provide(compareIds, shared) * * const freshCounterLayer = Layer.fresh(counterLayer) * const fresh = Layer.merge( @@ -2083,15 +2150,12 @@ export const updateService: { * Layer.provide(rightLayer, freshCounterLayer) * ) * - * yield* Effect.provide(showIds, fresh) + * const freshResult = yield* Effect.provide(compareIds, fresh) + * + * return { shared: sharedResult, fresh: freshResult } * }) * - * Effect.runPromise(program) - * // constructed Counter - * // same Counter: true - * // constructed Counter - * // constructed Counter - * // same Counter: false + * await Effect.runPromise(program) // => { shared: true, fresh: false } * ``` * * @category layers @@ -2115,50 +2179,30 @@ export const fresh = (self: Layer): Layer => * * **Example** (Launching an application layer) * - * ```ts - * import { Console, Context, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Deferred, Effect, Fiber, Layer, Ref } from "effect" * * class HttpServer extends Context.Service Effect.Effect - * readonly stop: () => Effect.Effect + * readonly port: number * }>()("HttpServer") {} * - * class Logger extends Context.Service Effect.Effect - * }>()("Logger") {} - * - * // Server layer that starts an HTTP server - * const serverLayer = Layer.effect(HttpServer, Effect.gen(function*() { - * yield* Console.log("Starting HTTP server...") + * const program = Effect.gen(function*() { + * const events = yield* Ref.make>([]) + * const started = yield* Deferred.make() * - * return { - * start: Effect.fn("HttpServer.start")(function*() { - * yield* Console.log("Server listening on port 3000") - * return "Server started" - * }), - * stop: Effect.fn("HttpServer.stop")(function*() { - * yield* Console.log("Server stopped gracefully") - * return "Server stopped" - * }) - * } - * })) + * const serverLayer = Layer.effect(HttpServer, Effect.gen(function*() { + * yield* Ref.update(events, (events) => [...events, "Starting HTTP server..."]) + * yield* Deferred.succeed(started, undefined) + * return { port: 3000 } + * })) * - * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Console.log(`[LOG] ${msg}`)) + * const fiber = yield* Effect.forkChild(Layer.launch(serverLayer)) + * yield* Deferred.await(started) + * yield* Fiber.interrupt(fiber) + * return yield* Ref.get(events) * }) * - * // Application layer combining all services - * const appLayer = Layer.mergeAll(serverLayer, loggerLayer) - * - * // Launch the application - runs until interrupted - * const application = appLayer.pipe( - * Layer.launch, - * Effect.tapError((error) => Console.log(`Application failed: ${error}`)), - * Effect.tap(() => Console.log("Application completed")) - * ) - * - * // This will run forever until externally interrupted - * // Effect.runFork(application) + * await Effect.runPromise(program) // => ["Starting HTTP server..."] * ``` * * @category converting @@ -2219,7 +2263,7 @@ type AnyEffectOrStream = * * **Example** (Mocking services for tests) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class UserService extends Context.Service "Test User" * ``` * * @category testing @@ -2326,22 +2372,17 @@ const ChannelTypeId: Channel.TypeId = "~effect/Channel" * * **Example** (Constraining layer success types) * - * ```ts - * import { Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Layer } from "effect" * - * declare const FortyTwoLayer: Layer.Layer<42, never, never> - * declare const StringLayer: Layer.Layer + * const NumberService = Context.Service("Number") + * const numberLayer = Layer.succeed(NumberService, 42) * * // Define a constraint that the success type must be a number * const satisfiesNumber = Layer.satisfiesSuccessType() * * // This works - Layer<42, never, never> extends Layer - * const validLayer = satisfiesNumber(FortyTwoLayer) - * - * // This would cause a TypeScript compilation error: - * // const invalidLayer = satisfiesNumber(StringLayer) - * // ^^^^^^^^^^^ - * // Type 'string' is not assignable to type 'number' + * const validLayer = satisfiesNumber(numberLayer) * ``` * * @category utility types @@ -2360,23 +2401,16 @@ export const satisfiesSuccessType = * * **Example** (Constraining layer error types) * - * ```ts - * import { Layer } from "effect" + * ```ts import.meta.vitest + * import { Effect, Layer } from "effect" * - * declare const ErrorLayer: Layer.Layer - * declare const TypeErrorLayer: Layer.Layer - * declare const StringLayer: Layer.Layer + * const typeErrorLayer = Layer.effectDiscard(Effect.fail(new TypeError("boom"))) * * // Define a constraint that the error type must be an Error * const satisfiesError = Layer.satisfiesErrorType() * * // This works - Layer extends Layer - * const validLayer = satisfiesError(TypeErrorLayer) - * - * // This would cause a TypeScript compilation error: - * // const invalidLayer = satisfiesError(StringLayer) - * // ^^^^^^^^^^^ - * // Type 'string' is not assignable to type 'Error' + * const validLayer = satisfiesError(typeErrorLayer) * ``` * * @category utility types @@ -2395,22 +2429,17 @@ export const satisfiesErrorType = * * **Example** (Constraining layer service requirements) * - * ```ts - * import { Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" * - * declare const FortyTwoLayer: Layer.Layer - * declare const StringLayer: Layer.Layer + * const NumberService = Context.Service("Number") + * const numberLayer = Layer.effectDiscard(Effect.asVoid(NumberService)) * * // Define a constraint that the service requirements must be numbers * const satisfiesNumber = Layer.satisfiesServicesType() * * // This works - Layer extends Layer - * const validLayer = satisfiesNumber(FortyTwoLayer) - * - * // This would cause a TypeScript compilation error: - * // const invalidLayer = satisfiesNumber(StringLayer) - * // ^^^^^^^^^^^ - * // Type 'string' is not assignable to type 'number' + * const validLayer = satisfiesNumber(numberLayer) * ``` * * @category utility types @@ -2468,38 +2497,37 @@ export interface SpanOptions extends Tracer.SpanOptions { * * **Example** (Tracing layer construction with a span) * - * ```ts - * import { Console, Context, Effect, Layer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer } from "effect" * import type { Tracer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * + * const logs: Array = [] + * * // Create a traced layer - all operations performed during construction of * // the `Database` service are part of the "database-init" span * const databaseLayer = Layer.effect(Database, Effect.gen(function*() { * // These operations are traced under "database-init" span - * yield* Effect.log("Connecting to database") - * yield* Effect.sleep("100 millis") - * yield* Effect.log("Database connected") + * logs.push("Connecting to database") + * logs.push("Database connected") * * const parentSpan = yield* Effect.currentParentSpan - * yield* Console.log((parentSpan as Tracer.Span).name) // "database-init" + * logs.push((parentSpan as Tracer.Span).name) * * return { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) * } - * })).pipe(Layer.provide(Layer.span("database-init"))) - * - * // Can also use the `onEnd` callback to execute logic when the span ends - * const tracedLayer = Layer.span("service-initialization", { - * attributes: { version: "1.0.0" }, + * })).pipe(Layer.provide(Layer.span("database-init", { * onEnd: (span, exit) => - * Effect.sync(() => { - * console.log(`Span ${span.name} ended with:`, exit._tag) - * }) - * }) + * Effect.sync(() => logs.push(`Span ${span.name} ended with: ${exit._tag}`)) + * }))) + * + * const program = Database.use((database) => database.query("SELECT 1")) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => "Result: SELECT 1" + * logs // => ["Connecting to database", "Database connected", "database-init", "Span database-init ended with: Success"] * ``` * * @category tracing @@ -2532,10 +2560,11 @@ export const span = ( * * **Example** (Referencing an existing parent span) * - * ```ts - * import { Console, Context, Effect, Layer, Tracer } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer, Tracer } from "effect" * * class Database extends Context.Service Effect.Effect * }>()("Database") {} * @@ -2543,12 +2572,10 @@ export const span = ( * const databaseLayer = Layer.effect( * Database, * Effect.gen(function*() { - * yield* Effect.log("Initializing database") - * * const parentSpan = yield* Effect.currentParentSpan - * yield* Console.log(parentSpan.spanId) // "42" * * return { + * spanId: parentSpan.spanId, * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) * } * }) @@ -2556,6 +2583,9 @@ export const span = ( * spanId: "42", * traceId: "000" * })))) + * const program = Database.use((database) => + * Effect.map(database.query("SELECT 1"), (result) => ({ spanId: database.spanId, result }))) + * Effect.runSync(Effect.provide(program, databaseLayer)) // => { spanId: "42", result: "Result: SELECT 1" } * ``` * * @category tracing @@ -2576,7 +2606,7 @@ export const parentSpan = (span: Tracer.AnySpan): Layer => * * **Example** (Wrapping a layer with a span) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer } from "effect" * * class Database extends Context.Service => * readonly log: (msg: string) => Effect.Effect * }>()("Logger") {} * + * const logs: Array = [] + * * // Create layers with tracing * const databaseLayer = Layer.effect(Database, Effect.gen(function*() { - * yield* Effect.log("Connecting to database") - * yield* Effect.sleep("100 millis") * return { * query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result: ${sql}`)) * } @@ -2599,16 +2629,14 @@ export const parentSpan = (span: Tracer.AnySpan): Layer => * })) * * const loggerLayer = Layer.succeed(Logger, { - * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg))) + * log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(msg))) * }).pipe(Layer.withSpan("logger-initialization")) * * // Combine traced layers * const appLayer = Layer.mergeAll(databaseLayer, loggerLayer).pipe( * Layer.withSpan("app-initialization", { * onEnd: (span, exit) => - * Effect.sync(() => { - * console.log(`Application initialization completed: ${exit._tag}`) - * }) + * Effect.sync(() => logs.push(`Application initialization completed: ${exit._tag}`)) * }) * ) * @@ -2619,6 +2647,8 @@ export const parentSpan = (span: Tracer.AnySpan): Layer => * yield* logger.log("Application ready") * return yield* database.query("SELECT * FROM users") * }).pipe(Effect.provide(appLayer)) + * Effect.runSync(program) // => "Result: SELECT * FROM users" + * logs // => ["Application ready", "Application initialization completed: Success"] * ``` * * @category tracing @@ -2650,7 +2680,7 @@ export const withSpan: { (span) => internalEffect.addFinalizer((exit) => options.onEnd!(span, exit)) ) : internalEffect.makeSpanScoped(name, options), - (span) => withParentSpan(self, span) + (span) => withParentSpan(self, span, options) ) ) } @@ -2663,7 +2693,7 @@ export const withSpan: { (span) => internalEffect.addFinalizer((exit) => options.onEnd!(span, exit)) ) : internalEffect.makeSpanScoped(name, options), - (span) => withParentSpan(self, span) + (span) => withParentSpan(self, span, options) ) ) } as any @@ -2684,7 +2714,7 @@ export const withSpan: { * * **Example** (Attaching layers to an existing parent span) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, Tracer } from "effect" * * class Database extends Context.Service Effect.succeed(`DB: ${sql}`)) * } * })) * * const CacheLayer = Layer.effect(Cache, Effect.gen(function*() { - * yield* Effect.log("Connecting to cache") * return { * get: Effect.fn("Cache.get")((key: string) => Effect.succeed(`Cache: ${key}`)) * } @@ -2730,6 +2758,7 @@ export const withSpan: { * return { dbResult, cacheResult } * }) * ) + * Effect.runSync(Effect.scoped(program)) // => { dbResult: "DB: SELECT * FROM users", cacheResult: "Cache: user:123" } * ``` * * @category tracing diff --git a/packages/effect/src/LayerMap.ts b/packages/effect/src/LayerMap.ts index b4fc9800d..d9bc31fe0 100644 --- a/packages/effect/src/LayerMap.ts +++ b/packages/effect/src/LayerMap.ts @@ -15,6 +15,7 @@ import * as Effect from "./Effect.ts" import { identity } from "./Function.ts" import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" import * as Layer from "./Layer.ts" +import type * as Option from "./Option.ts" import * as RcMap from "./RcMap.ts" import * as Scope from "./Scope.ts" import type { Mutable, NoExcessProperties } from "./Types.ts" @@ -33,7 +34,7 @@ type IdleTimeToLiveInput = Duration.Input | ((key: K) => Duration.Input) * * **Example** (Managing keyed layers) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, LayerMap } from "effect" * * // Define a service key @@ -53,14 +54,22 @@ type IdleTimeToLiveInput = Duration.Input | ((key: K) => Duration.Input) * const layerMap = yield* createDatabaseLayerMap * * // Get a layer for a specific environment - * const devLayer = layerMap.get("development") + * const development = yield* Effect.provide( + * DatabaseService.use((database) => database.query("SELECT 1")), + * layerMap.get("development") + * ) * * // Get context directly - * const context = yield* layerMap.contextEffect("production") + * const productionContext = yield* layerMap.contextEffect("production") + * const production = yield* Context.get(productionContext, DatabaseService).query("SELECT 1") * * // Invalidate a cached layer * yield* layerMap.invalidate("development") + * + * return { development, production } * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => { development: "development: SELECT 1", production: "production: SELECT 1" } * ``` * * @category models @@ -84,6 +93,18 @@ export interface LayerMap { */ contextEffect(key: K): Effect.Effect, E, Scope.Scope> + /** + * Retains and returns the context for a key only when it is currently cached. + * + * **Details** + * + * `Option.none` means no entry is currently cached or the `LayerMap` is closed; + * no layer is built for a missing key. An existing in-flight entry is awaited. + * + * @since 4.0.0 + */ + contextEffectOption(key: K): Effect.Effect>, E, Scope.Scope> + /** * Invalidates the resource associated with the key. */ @@ -95,7 +116,7 @@ export interface LayerMap { * * **Example** (Creating a layer map) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, LayerMap } from "effect" * * // Define a service key @@ -117,16 +138,16 @@ export interface LayerMap { * const devLayer = layerMap.get("development") * * // Use the layer to provide the service - * const result = yield* Effect.provide( + * return yield* Effect.provide( * Effect.gen(function*() { * const db = yield* DatabaseService * return yield* db.query("SELECT * FROM users") * }), * devLayer * ) - * - * console.log(result) // "development: SELECT * FROM users" * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => "development: SELECT * FROM users" * ``` * * @category constructors @@ -150,10 +171,11 @@ export const make: < lookup: (key: K) => Layer.Layer, options?: { readonly idleTimeToLive?: IdleTimeToLiveInput | undefined + readonly preloadKeys?: Iterable | undefined } | undefined ) { const context = yield* Effect.context() - const memoMap = Layer.CurrentMemoMap.getOrCreate(context) + const memoMap = Layer.CurrentMemoMap.forkOrCreate(context) const rcMap = yield* RcMap.make({ lookup: (key: K) => @@ -163,11 +185,18 @@ export const make: < idleTimeToLive: options?.idleTimeToLive }) + if (options?.preloadKeys) { + for (const key of options.preloadKeys) { + yield* Effect.scoped(RcMap.get(rcMap, key)) + } + } + return identity>({ [TypeId]: TypeId, rcMap, get: (key) => Layer.effectContext(RcMap.get(rcMap, key)), contextEffect: (key) => RcMap.get(rcMap, key), + contextEffectOption: (key) => RcMap.getOption(rcMap, key), invalidate: (key) => RcMap.invalidate(rcMap, key) }) }) @@ -182,24 +211,20 @@ export const make: < * * **Example** (Creating a layer map from a record) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, LayerMap } from "effect" * - * // Define service keys - * const DevDatabase = Context.Service<{ - * readonly query: (sql: string) => Effect.Effect - * }>("DevDatabase") - * - * const ProdDatabase = Context.Service<{ + * // Define a service key + * const Database = Context.Service<{ * readonly query: (sql: string) => Effect.Effect - * }>("ProdDatabase") + * }>("Database") * * // Create predefined layers * const layers = { - * development: Layer.succeed(DevDatabase)({ + * development: Layer.succeed(Database)({ * query: Effect.fn("DevDatabase.query")((sql) => Effect.succeed(`DEV: ${sql}`)) * }), - * production: Layer.succeed(ProdDatabase)({ + * production: Layer.succeed(Database)({ * query: Effect.fn("ProdDatabase.query")((sql) => Effect.succeed(`PROD: ${sql}`)) * }) * } as const @@ -210,12 +235,19 @@ export const make: < * idleTimeToLive: "10 seconds" * }) * - * // Get layers by key - * const devLayer = layerMap.get("development") - * const prodLayer = layerMap.get("production") + * const development = yield* Effect.provide( + * Database.use((database) => database.query("SELECT 1")), + * layerMap.get("development") + * ) + * const production = yield* Effect.provide( + * Database.use((database) => database.query("SELECT 1")), + * layerMap.get("production") + * ) * - * console.log("LayerMap created from record") + * return { development, production } * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => { development: "DEV: SELECT 1", production: "PROD: SELECT 1" } * ``` * * @category constructors @@ -298,6 +330,20 @@ export interface TagClass< */ readonly contextEffect: (key: K) => Effect.Effect, E, Scope.Scope | Self> + /** + * Retains and returns the context for a key only when it is currently cached. + * + * **Details** + * + * `Option.none` means no entry is currently cached or the `LayerMap` is closed; + * no layer is built for a missing key. An existing in-flight entry is awaited. + * + * @since 4.0.0 + */ + readonly contextEffectOption: ( + key: K + ) => Effect.Effect>, E, Scope.Scope | Self> + /** * Invalidates the resource associated with the key. */ @@ -310,8 +356,8 @@ export interface TagClass< * * **Example** (Defining a layer map service) * - * ```ts - * import { Console, Context, Effect, Layer, LayerMap } from "effect" + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerMap } from "effect" * * // Define a service key * const Greeter = Context.Service<{ @@ -334,7 +380,7 @@ export interface TagClass< * const program = Effect.gen(function*() { * // Access and use the Greeter service * const greeter = yield* Greeter - * yield* Console.log(yield* greeter.greet) + * return yield* greeter.greet * }).pipe( * // Use the GreeterMap service to provide a variant of the Greeter service * Effect.provide(GreeterMap.get("John")) @@ -342,6 +388,8 @@ export interface TagClass< * // Provide the GreeterMap layer * Effect.provide(GreeterMap.layer) * ) + * + * await Effect.runPromise(program) // => "Hello, John!" * ``` * * @category services @@ -375,7 +423,7 @@ export const Service = () => : Options extends { readonly layers: infer Layers } ? keyof Layers : never, Service.Success, - Options extends { readonly preload: true } ? never : Service.Error, + Service.Error, Service.Services, Options extends { readonly preload: true } ? Service.Error : Options extends { readonly preloadKeys: Iterable } ? Service.Error @@ -383,11 +431,13 @@ export const Service = () => Options extends { readonly dependencies: ReadonlyArray> } ? Options["dependencies"][number] : never > => { - const Err = globalThis.Error as any const limit = getStackTraceLimit() - setStackTraceLimit(2) - const creationError = new Err() - setStackTraceLimit(limit) + let creationError: Error | undefined + if (limit !== 0) { + setStackTraceLimit(2) + creationError = new globalThis.Error() + setStackTraceLimit(limit) + } function TagClass() {} const TagClass_ = TagClass as any as Mutable> @@ -395,7 +445,7 @@ export const Service = () => TagClass.key = id Object.defineProperty(TagClass, "stack", { get() { - return creationError.stack + return creationError?.stack } }) @@ -410,6 +460,8 @@ export const Service = () => TagClass_.get = (key: string) => Layer.unwrap(Effect.map(TagClass_, (layerMap) => layerMap.get(key))) TagClass_.contextEffect = (key: string) => Effect.flatMap(TagClass_, (layerMap) => layerMap.contextEffect(key)) + TagClass_.contextEffectOption = (key: string) => + Effect.flatMap(TagClass_, (layerMap) => layerMap.contextEffectOption(key)) TagClass_.invalidate = (key: string) => Effect.flatMap(TagClass_, (layerMap) => layerMap.invalidate(key)) return TagClass as any @@ -424,7 +476,7 @@ export declare namespace Service { /** * Extracts the key type accepted by a `LayerMap.Service` definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Key = Options extends { readonly lookup: (key: infer K) => any } ? K @@ -434,7 +486,7 @@ export declare namespace Service { /** * Extracts the layer type produced by a `LayerMap.Service` definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Layers = Options extends { readonly lookup: (key: infer _K) => infer Layers } ? Layers @@ -445,7 +497,7 @@ export declare namespace Service { * Extracts the services provided by the layers in a `LayerMap.Service` * definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Success = Layers extends Layer.Layer ? _A : never @@ -453,7 +505,7 @@ export declare namespace Service { /** * Extracts the error type of the layers in a `LayerMap.Service` definition. * - * @category services + * @category utility types * @since 3.14.0 */ export type Error = Layers extends Layer.Layer ? _E : never @@ -462,7 +514,7 @@ export declare namespace Service { * Extracts the service requirements of the layers in a `LayerMap.Service` * definition. * - * @category services + * @category utility types * @since 4.0.0 */ export type Services = Layers extends Layer.Layer ? _R : never diff --git a/packages/effect/src/LayerRef.ts b/packages/effect/src/LayerRef.ts new file mode 100644 index 000000000..4d18aeb85 --- /dev/null +++ b/packages/effect/src/LayerRef.ts @@ -0,0 +1,383 @@ +/** + * Creates refreshable references to a single layer-built service context. + * + * A `LayerRef` builds one `Layer` into a cached `Context`, exposes it + * back as a layer or scoped effect, and supports invalidation so later users can + * acquire a fresh context. + * + * @since 4.0.0 + */ +import * as Context from "./Context.ts" +import type * as Duration from "./Duration.ts" +import * as Effect from "./Effect.ts" +import { identity } from "./Function.ts" +import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" +import * as Layer from "./Layer.ts" +import * as RcRef from "./RcRef.ts" +import type * as Schedule from "./Schedule.ts" +import * as Scope from "./Scope.ts" +import type { Mutable } from "./Types.ts" + +const TypeId = "~effect/LayerRef" + +/** + * A refreshable reference to a single layer-built service context. + * + * **When to use** + * + * Use when you want to share one scoped layer resource across many users while + * retaining the ability to invalidate it and rebuild it later. + * + * **Details** + * + * A `LayerRef` is the unkeyed counterpart to a layer cache: it lazily builds the + * layer on first use, reuses the resulting context while it is borrowed or kept + * idle, and can invalidate the cached context so the next use rebuilds it. + * + * @see {@link make} for constructing a `LayerRef` from a layer + * @see {@link Service} for defining a `LayerRef` as a service class + * + * @category models + * @since 4.0.0 + */ +export interface LayerRef { + readonly [TypeId]: typeof TypeId + + /** + * The underlying reference-counted cache that stores the built context. + */ + readonly rcRef: RcRef.RcRef, E> + + /** + * Layer that provides the currently cached context, acquiring it if needed. + */ + readonly get: Layer.Layer + + /** + * Scoped effect that retrieves the currently cached context, acquiring it if needed. + */ + readonly contextEffect: Effect.Effect, E, Scope.Scope> + + /** + * Invalidates the cached context so the next use rebuilds the layer. + */ + readonly invalidate: Effect.Effect + + /** + * Invalidates the cached context so the next use rebuilds the layer, and + * reacquires it. + */ + readonly refresh: Effect.Effect +} + +/** + * Creates a `LayerRef` from a `Layer`. + * + * **When to use** + * + * Use when you have one layer-built resource that should be shared, optionally + * kept alive while idle, and refreshed on demand. + * + * **Details** + * + * The layer is built lazily on first use unless `preload` is `true`. + * `idleTimeToLive` keeps the context cached after it stops being used, and + * `invalidationSchedule` can periodically invalidate it. When `preload` is + * `true`, scheduled invalidation also reacquires the context. + * + * **Gotchas** + * + * Invalidation does not revoke contexts already borrowed by active scopes; those + * contexts remain usable until their scopes close. + * + * **Example** (Sharing one layer-built service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerRef } from "effect" + * + * class Database extends Context.Service + * }>()("Database") {} + * + * const databaseLayer = Layer.succeed(Database, { + * query: Effect.succeed("result") + * }) + * + * const query = Effect.gen(function*() { + * const database = yield* Database + * return yield* database.query + * }) + * + * const program = Effect.scoped( + * Effect.gen(function*() { + * const ref = yield* LayerRef.make(databaseLayer, { + * idleTimeToLive: "5 seconds" + * }) + * + * const result = yield* Effect.provide(query, ref.get) + * + * yield* ref.invalidate + * + * return result + * }) + * ) + * + * await Effect.runPromise(program) // => "result" + * ``` + * + * @see {@link Service} for defining a reusable service class around a `LayerRef` + * + * @category constructors + * @since 4.0.0 + */ +export const make = Effect.fnUntraced( + function*( + layer: Layer.Layer, + options?: { + /** + * Duration to keep the resource alive after it is no longer used. + */ + readonly idleTimeToLive?: Duration.Input | undefined + /** + * Whether to acquire the resource during creation. + */ + readonly preload?: Preload | undefined + /** + * Schedule used to invalidate the cached resource. When `preload` is + * `true`, each scheduled invalidation also reacquires the resource. + */ + readonly invalidationSchedule?: Schedule.Schedule | undefined + } | undefined + ): Effect.fn.Return< + LayerRef, + [Preload] extends [true] ? E : never, + Scope.Scope | R | SR + > { + const context = yield* Effect.context() + const memoMap = Layer.CurrentMemoMap.forkOrCreate(context) + + const rcRef = yield* RcRef.make({ + acquire: Effect.contextWith((_: Context.Context) => + Layer.buildWithMemoMap(layer, memoMap, Context.get(_, Scope.Scope)) + ), + idleTimeToLive: options?.idleTimeToLive + }) + + const refresh = RcRef.invalidate(rcRef).pipe( + Effect.andThen(Effect.scoped(RcRef.get(rcRef))), + Effect.asVoid + ) + + if (options?.preload) { + yield* refresh as Effect.Effect + } + + if (options?.invalidationSchedule) { + const onRefresh = options.preload ? refresh : RcRef.invalidate(rcRef) + yield* onRefresh.pipe( + Effect.ignoreCause, + Effect.schedule(options.invalidationSchedule), + Effect.forkScoped + ) + } + + return identity>({ + [TypeId]: TypeId, + rcRef, + get: Layer.effectContext(RcRef.get(rcRef)), + contextEffect: RcRef.get(rcRef), + invalidate: RcRef.invalidate(rcRef), + refresh + }) + } +) + +/** + * Service class shape produced by `LayerRef.Service`. + * + * **When to use** + * + * Use as the public type for classes returned by `LayerRef.Service` when an API + * needs to accept, return, or alias the generated service class and its static + * helpers. + * + * **Details** + * + * It combines a `Context.Service` tag for the `LayerRef` with default layers and + * helper accessors for retrieving, using, and invalidating the cached resource. + * + * @see {@link Service} for creating concrete `LayerRef` service classes + * + * @category services + * @since 4.0.0 + */ +export interface TagClass< + in out Self, + in out Id extends string, + in out I, + in out E, + in out R, + in out LE, + in out Deps extends Layer.Layer +> extends Context.ServiceClass> { + /** + * Default layer for the `LayerRef` service, with dependencies applied. + */ + readonly layer: Layer.Layer< + Self, + (Deps extends Layer.Layer ? _E : never) | LE, + | Exclude ? _A : never)> + | (Deps extends Layer.Layer ? _R : never) + > + + /** + * Default layer for the `LayerRef` service without provided dependencies. + */ + readonly layerNoDeps: Layer.Layer + + /** + * Layer that provides the currently cached context, requiring this service. + */ + readonly get: Layer.Layer + + /** + * Scoped effect that retrieves the currently cached context through this service. + */ + readonly contextEffect: Effect.Effect, E, Scope.Scope | Self> + + /** + * Invalidates the cached context through this service. + */ + readonly invalidate: Effect.Effect + + /** + * Invalidates the cached context through this service, and reacquires it. + */ + readonly refresh: Effect.Effect +} + +/** + * Creates a service class for a `LayerRef`. + * + * **When to use** + * + * Use when you want to name a shared layer reference as an application service + * and expose static helpers for providing, retrieving, and invalidating it. + * + * **Details** + * + * The returned class is a `Context.Service` whose value is a `LayerRef`. It also + * includes `.layer`, `.layerNoDeps`, `.get`, `.contextEffect`, and `.invalidate` + * helpers so callers do not need to access the `LayerRef` value directly. + * + * **Example** (Defining a refreshable service) + * + * ```ts import.meta.vitest + * import { Context, Effect, Layer, LayerRef } from "effect" + * + * class Database extends Context.Service + * }>()("Database") {} + * + * const databaseLayer = Layer.succeed(Database, { + * query: Effect.succeed("result") + * }) + * + * class DatabaseRef extends LayerRef.Service()("DatabaseRef", { + * layer: databaseLayer, + * preload: true + * }) {} + * + * const program = Effect.gen(function*() { + * const database = yield* Database + * return yield* database.query + * }).pipe( + * Effect.provide(DatabaseRef.get), + * Effect.provide(DatabaseRef.layer) + * ) + * + * await Effect.runPromise(program) // => "result" + * ``` + * + * @see {@link make} for creating a `LayerRef` value without defining a service class + * + * @category services + * @since 4.0.0 + */ +export const Service = () => +< + const Id extends string, + I, + E, + R, + X, + const Deps extends ReadonlyArray> = [], + const Preload extends boolean = never, + SE = never, + SR = never +>( + id: Id, + options: { + /** + * Layer used to build the cached context. + */ + readonly layer: Layer.Layer + /** + * Layers used to satisfy dependencies of the `LayerRef` service layer. + */ + readonly dependencies?: Deps | undefined + /** + * Duration to keep the resource alive after it is no longer used. + */ + readonly idleTimeToLive?: Duration.Input | undefined + /** + * Whether to acquire the resource during creation. + */ + readonly preload?: Preload | undefined + /** + * Schedule used to invalidate the cached resource. When `preload` is + * `true`, each scheduled invalidation also reacquires the resource. + */ + readonly invalidationSchedule?: Schedule.Schedule | undefined + } +): TagClass< + Self, + Id, + I, + E, + R | SR, + [Preload] extends [true] ? E : never, + Deps[number] +> => { + const limit = getStackTraceLimit() + let creationError: Error | undefined + if (limit !== 0) { + setStackTraceLimit(2) + creationError = new globalThis.Error() + setStackTraceLimit(limit) + } + + function TagClass() {} + const TagClass_ = TagClass as any as Mutable> + Object.setPrototypeOf(TagClass, Object.getPrototypeOf(Context.Service(id))) + TagClass.key = id + Object.defineProperty(TagClass, "stack", { + get() { + return creationError?.stack + } + }) + + TagClass_.layerNoDeps = Layer.effect(TagClass_)( + make(options.layer, options) + ) + TagClass_.layer = options.dependencies && options.dependencies.length > 0 ? + Layer.provide(TagClass_.layerNoDeps, options.dependencies as any) : + TagClass_.layerNoDeps + + TagClass_.get = Layer.unwrap(TagClass_.useSync((ref) => ref.get)) + TagClass_.contextEffect = TagClass_.use((ref) => ref.contextEffect) + TagClass_.invalidate = TagClass_.use((ref) => ref.invalidate) + TagClass_.refresh = TagClass_.use((ref) => ref.refresh) + + return TagClass as any +} diff --git a/packages/effect/src/LogLevel.ts b/packages/effect/src/LogLevel.ts index f8002b848..837e1bd5a 100644 --- a/packages/effect/src/LogLevel.ts +++ b/packages/effect/src/LogLevel.ts @@ -36,8 +36,8 @@ import * as References from "./References.ts" * * **Example** (Using log levels) * - * ```ts - * import { Effect } from "effect" + * ```ts import.meta.vitest + * import { Effect, References } from "effect" * * // Using log levels with Effect logging * const program = Effect.gen(function*() { @@ -52,6 +52,13 @@ import * as References from "./References.ts" * // Type-safe log level variables * const errorLevel = "Error" // LogLevel * const debugLevel = "Debug" // LogLevel + * + * await Effect.runPromise( + * Effect.provideService(program, References.MinimumLogLevel, "None") + * ) + * + * const levels = [errorLevel, debugLevel] + * levels // => ["Error", "Debug"] * ``` * * @category models @@ -101,7 +108,7 @@ export type Severity = "Fatal" | "Error" | "Warn" | "Info" | "Debug" | "Trace" * @see {@link Severity} for the concrete message severity type that excludes `All` and `None` * @see {@link Order} for comparing these levels by severity order * - * @category models + * @category constants * @since 4.0.0 */ export const values: ReadonlyArray = ["All", "Fatal", "Error", "Warn", "Info", "Debug", "Trace", "None"] @@ -120,13 +127,12 @@ export const values: ReadonlyArray = ["All", "Fatal", "Error", "Warn", * * **Example** (Ordering log levels) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * // Compare log levels using Order - * console.log(LogLevel.Order("Error", "Info")) // 1 (Error > Info) - * console.log(LogLevel.Order("Debug", "Error")) // -1 (Debug < Error) - * console.log(LogLevel.Order("Info", "Info")) // 0 (Info == Info) + * LogLevel.Order("Error", "Info") // => 1 + * LogLevel.Order("Debug", "Error") // => -1 + * LogLevel.Order("Info", "Info") // => 0 * ``` * * @category ordering @@ -148,11 +154,11 @@ export const Order: Ord.Order = effect.LogLevelOrder * * **Example** (Comparing log levels) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * console.log(LogLevel.Equivalence("Error", "Error")) // true - * console.log(LogLevel.Equivalence("Error", "Info")) // false + * LogLevel.Equivalence("Error", "Error") // => true + * LogLevel.Equivalence("Error", "Info") // => false * ``` * * @see {@link Order} for severity ordering rather than exact level equality @@ -204,25 +210,24 @@ export const getOrdinal = (self: LogLevel): number => effect.logLevelToOrder(sel * * **Example** (Checking higher severity) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * // Check if Error is more severe than Info - * console.log(LogLevel.isGreaterThan("Error", "Info")) // true - * console.log(LogLevel.isGreaterThan("Debug", "Error")) // false + * LogLevel.isGreaterThan("Error", "Info") // => true + * LogLevel.isGreaterThan("Debug", "Error") // => false * * // Use with filtering * const isFatal = LogLevel.isGreaterThan("Fatal", "Warn") * const isError = LogLevel.isGreaterThan("Error", "Warn") * const isDebug = LogLevel.isGreaterThan("Debug", "Warn") - * console.log(isFatal) // true - * console.log(isError) // true - * console.log(isDebug) // false + * isFatal // => true + * isError // => true + * isDebug // => false * * // Curried usage * const isMoreSevereThanInfo = LogLevel.isGreaterThan("Info") - * console.log(isMoreSevereThanInfo("Error")) // true - * console.log(isMoreSevereThanInfo("Debug")) // false + * isMoreSevereThanInfo("Error") // => true + * isMoreSevereThanInfo("Debug") // => false * ``` * * @category ordering @@ -247,33 +252,15 @@ export const isGreaterThan: { * * **Example** (Filtering by minimum log level) * - * ```ts - * import { Logger, LogLevel } from "effect" - * - * // Check if level meets minimum threshold - * console.log(LogLevel.isGreaterThanOrEqualTo("Error", "Error")) // true - * console.log(LogLevel.isGreaterThanOrEqualTo("Error", "Info")) // true - * console.log(LogLevel.isGreaterThanOrEqualTo("Debug", "Info")) // false + * ```ts import.meta.vitest + * import { LogLevel } from "effect" * - * // Create a logger that only logs Info and above - * const infoLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Info")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * LogLevel.isGreaterThanOrEqualTo("Error", "Error") // => true + * LogLevel.isGreaterThanOrEqualTo("Error", "Info") // => true + * LogLevel.isGreaterThanOrEqualTo("Debug", "Info") // => false * - * // Production logger - only Error and Fatal - * const productionLogger = Logger.make((options) => { - * if (LogLevel.isGreaterThanOrEqualTo(options.logLevel, "Error")) { - * console.error( - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` - * ) - * } - * }) - * - * // Curried usage for filtering * const isInfoOrAbove = LogLevel.isGreaterThanOrEqualTo("Info") - * const shouldLog = isInfoOrAbove("Error") // true + * isInfoOrAbove("Error") // => true * ``` * * @category ordering @@ -297,25 +284,24 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking lower severity) * - * ```ts + * ```ts import.meta.vitest * import { LogLevel } from "effect" * - * // Check if Debug is less severe than Info - * console.log(LogLevel.isLessThan("Debug", "Info")) // true - * console.log(LogLevel.isLessThan("Error", "Info")) // false + * LogLevel.isLessThan("Debug", "Info") // => true + * LogLevel.isLessThan("Error", "Info") // => false * * // Filter out verbose logs * const isFatalVerbose = LogLevel.isLessThan("Fatal", "Info") * const isErrorVerbose = LogLevel.isLessThan("Error", "Info") * const isTraceVerbose = LogLevel.isLessThan("Trace", "Info") - * console.log(isFatalVerbose) // false (Fatal is not verbose) - * console.log(isErrorVerbose) // false (Error is not verbose) - * console.log(isTraceVerbose) // true (Trace is verbose) + * isFatalVerbose // => false + * isErrorVerbose // => false + * isTraceVerbose // => true * * // Curried usage * const isLessSevereThanError = LogLevel.isLessThan("Error") - * console.log(isLessSevereThanError("Info")) // true - * console.log(isLessSevereThanError("Fatal")) // false + * isLessSevereThanError("Info") // => true + * isLessSevereThanError("Fatal") // => false * ``` * * @category ordering @@ -340,31 +326,15 @@ export const isLessThan: { * * **Example** (Filtering by maximum log level) * - * ```ts - * import { Logger, LogLevel } from "effect" - * - * // Check if level is at or below threshold - * console.log(LogLevel.isLessThanOrEqualTo("Info", "Info")) // true - * console.log(LogLevel.isLessThanOrEqualTo("Debug", "Info")) // true - * console.log(LogLevel.isLessThanOrEqualTo("Error", "Info")) // false - * - * // Create a logger that suppresses verbose logs - * const quietLogger = Logger.make((options) => { - * if (LogLevel.isLessThanOrEqualTo(options.logLevel, "Info")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * ```ts import.meta.vitest + * import { LogLevel } from "effect" * - * // Development logger - suppress trace logs - * const devLogger = Logger.make((options) => { - * if (LogLevel.isLessThanOrEqualTo(options.logLevel, "Debug")) { - * console.log(`[${options.logLevel}] ${options.message}`) - * } - * }) + * LogLevel.isLessThanOrEqualTo("Info", "Info") // => true + * LogLevel.isLessThanOrEqualTo("Debug", "Info") // => true + * LogLevel.isLessThanOrEqualTo("Error", "Info") // => false * - * // Curried usage for filtering * const isInfoOrBelow = LogLevel.isLessThanOrEqualTo("Info") - * const shouldLog = isInfoOrBelow("Debug") // true + * isInfoOrBelow("Debug") // => true * ``` * * @category ordering @@ -390,22 +360,24 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking current fiber log level) * - * ```ts + * ```ts import.meta.vitest * import { Effect, LogLevel, References } from "effect" * * const program = Effect.gen(function*() { * const debugEnabled = yield* LogLevel.isEnabled("Debug") * const errorEnabled = yield* LogLevel.isEnabled("Error") * - * console.log({ debugEnabled, errorEnabled }) + * return { debugEnabled, errorEnabled } * }) * * const warnOnly = program.pipe( * Effect.provideService(References.MinimumLogLevel, "Warn") * ) + * + * await Effect.runPromise(warnOnly) // => { debugEnabled: false, errorEnabled: true } * ``` * - * @category filtering + * @category predicates * @since 4.0.0 */ export const isEnabled = (self: LogLevel): Effect.Effect => diff --git a/packages/effect/src/Logger.ts b/packages/effect/src/Logger.ts index 9c37114c1..280febf2c 100644 --- a/packages/effect/src/Logger.ts +++ b/packages/effect/src/Logger.ts @@ -20,6 +20,7 @@ import * as Formatter from "./Formatter.ts" import { dual } from "./Function.ts" import { isEffect, withFiber } from "./internal/core.ts" import * as effect from "./internal/effect.ts" +import * as InternalRecord from "./internal/record.ts" import * as Layer from "./Layer.ts" import type * as LogLevel from "./LogLevel.ts" import type { Pipeable } from "./Pipeable.ts" @@ -41,23 +42,20 @@ const TypeId = "~effect/Logger" * * **Example** (Creating custom loggers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Create a custom logger that accepts unknown messages and returns void + * const messages: Array = [] * const stringLogger = Logger.make((options) => { - * console.log(`[${options.logLevel}] ${options.message}`) + * messages.push(`[${options.logLevel}] ${options.message}`) * }) * - * // Create a logger that accepts any message type and returns a formatted string - * const formattedLogger = Logger.make((options) => - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` - * ) - * - * // Use the logger in an Effect program * const program = Effect.log("Hello World").pipe( * Effect.provide(Logger.layer([stringLogger])) * ) + * + * Effect.runSync(program) + * messages // => ["[Info] Hello World"] * ``` * * @category models @@ -77,24 +75,24 @@ export interface Logger extends Pipeable { * * **Example** (Accessing logger options) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Options interface provides all logging context + * const outputs: Array = [] * const detailedLogger = Logger.make((options) => { - * const output = { + * outputs.push({ * message: options.message, * level: options.logLevel, - * timestamp: options.date.toISOString(), - * fiberId: options.fiber.id, - * hasCause: options.cause !== undefined - * } - * console.log(JSON.stringify(output)) + * hasCause: options.cause.reasons.length > 0 + * }) * }) * * const program = Effect.log("Processing request").pipe( * Effect.provide(Logger.layer([detailedLogger])) * ) + * + * Effect.runSync(program) + * outputs // => [{ message: ["Processing request"], level: "Info", hasCause: false }] * ``` * * @category options @@ -113,16 +111,14 @@ export interface Options { * * **Example** (Checking logger values) * - * ```ts + * ```ts import.meta.vitest * import { Logger } from "effect" * - * const myLogger = Logger.make((options) => { - * console.log(options.message) - * }) + * const myLogger = Logger.make(() => undefined) * - * console.log(Logger.isLogger(myLogger)) // true - * console.log(Logger.isLogger("not a logger")) // false - * console.log(Logger.isLogger({ log: () => {} })) // false + * Logger.isLogger(myLogger) // => true + * Logger.isLogger("not a logger") // => false + * Logger.isLogger({ log: () => {} }) // => false * ``` * * @category guards @@ -141,26 +137,26 @@ export const isLogger = (u: unknown): u is Logger => Predicate * * **Example** (Accessing current loggers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Access current loggers from fiber context + * const messages: Array = [] + * const customLogger = Logger.make((options) => { + * messages.push(options.message) + * }) * const program = Effect.gen(function*() { * const currentLoggers = yield* Effect.service(Logger.CurrentLoggers) - * console.log(`Number of active loggers: ${currentLoggers.size}`) - * - * // Add a custom logger to the set - * const customLogger = Logger.make((options) => { - * console.log(`Custom: ${options.message}`) - * }) - * * yield* Effect.log("Hello from custom logger").pipe( * Effect.provide(Logger.layer([customLogger])) * ) + * return currentLoggers.has(Logger.defaultLogger) * }) + * + * Effect.runSync(program) // => true + * messages // => [["Hello from custom logger"]] * ``` * - * @category references + * @category services * @since 4.0.0 */ export const CurrentLoggers: Context.Reference>> = effect.CurrentLoggers @@ -183,7 +179,7 @@ export const CurrentLoggers: Context.Reference> * @see {@link consolePretty} for the TTY-mode pretty console logger affected by this reference * @see {@link withConsoleError} for routing a specific formatter logger to `console.error` * - * @category references + * @category services * @since 4.0.0 */ export const LogToStderr: Context.Reference = effect.LogToStderr @@ -198,27 +194,24 @@ export const LogToStderr: Context.Reference = effect.LogToStderr * * **Example** (Transforming logger output) * - * ```ts - * import { Logger } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" * - * // Create a logger that outputs objects + * const outputs: Array = [] * const structuredLogger = Logger.make((options) => ({ - * level: options.logLevel, - * message: options.message, - * timestamp: options.date.toISOString() + * message: options.message * })) * - * // Transform the output to JSON strings - * const jsonStringLogger = Logger.map( - * structuredLogger, - * (output) => JSON.stringify(output) - * ) - * * // Transform to uppercase messages * const uppercaseLogger = Logger.map( * structuredLogger, * (output) => ({ ...output, message: String(output.message).toUpperCase() }) * ) + * + * const collector = Logger.make((options) => outputs.push(uppercaseLogger.log(options))) + * const program = Effect.log("hello").pipe(Effect.provide(Logger.layer([collector]))) + * Effect.runSync(program) + * outputs // => [{ message: "HELLO" }] * ``` * * @category mapping @@ -247,20 +240,23 @@ export const map = dual< * * **Example** (Writing logger output with console.log) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * * // Create a custom formatter * const customFormatter = Logger.make((options) => - * `[${options.date.toISOString()}] ${options.logLevel}: ${options.message}` + * `${options.logLevel}: ${options.message}` * ) * - * // Route to console * const consoleLogger = Logger.withConsoleLog(customFormatter) * - * const program = Effect.log("Hello World").pipe( - * Effect.provide(Logger.layer([consoleLogger])) - * ) + * const program = Effect.gen(function*() { + * yield* Effect.log("Hello World").pipe(Effect.provide(Logger.layer([consoleLogger]))) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) // => ["Info: Hello World"] * ``` * * @category logging @@ -284,20 +280,23 @@ export const withConsoleLog = ( * * **Example** (Writing logger output with console.error) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * * // Create an error-specific formatter * const errorFormatter = Logger.make((options) => - * `ERROR [${options.date.toISOString()}]: ${options.message}` + * `ERROR: ${options.message}` * ) * - * // Route to console.error * const errorLogger = Logger.withConsoleError(errorFormatter) * - * const program = Effect.logError("Database connection failed").pipe( - * Effect.provide(Logger.layer([errorLogger])) - * ) + * const program = Effect.gen(function*() { + * yield* Effect.logError("Database connection failed").pipe(Effect.provide(Logger.layer([errorLogger]))) + * return yield* TestConsole.errorLines + * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) // => ["ERROR: Database connection failed"] * ``` * * @category logging @@ -325,9 +324,15 @@ export const withConsoleError = ( * * **Example** (Writing logs with level-based console methods) * - * ```ts - * import { Effect, Logger } from "effect" + * ```ts import.meta.vitest + * import { Console, Effect, Logger } from "effect" * + * const messages: Array> = [] + * const testConsole: Console.Console = Object.assign(Object.create(console), { + * info: (message: unknown) => messages.push(["info", message]), + * warn: (message: unknown) => messages.push(["warn", message]), + * error: (message: unknown) => messages.push(["error", message]) + * }) * const formatter = Logger.make((options) => * `[${options.logLevel}] ${options.message}` * ) @@ -338,10 +343,14 @@ export const withConsoleError = ( * yield* Effect.logInfo("Info message") // -> console.info * yield* Effect.logWarning("Warning") // -> console.warn * yield* Effect.logError("Error occurred") // -> console.error - * yield* Effect.logDebug("Debug info") // -> console.debug - * }).pipe( - * Effect.provide(Logger.layer([leveledLogger])) - * ) + * }).pipe(Effect.provide(Logger.layer([leveledLogger]))) + * Effect.runSync(Effect.provideService(program, Console.Console, testConsole)) + * const expected = [ + * ["info", "[Info] Info message"], + * ["warn", "[Warn] Warning"], + * ["error", "[Error] Error occurred"] + * ] + * messages // => expected * ``` * * @category logging @@ -404,7 +413,7 @@ const format = ( const append = (label: string, value: string): string => " " + format(label, value) let out = format("timestamp", date.toISOString()) - out += append("level", logLevel) + out += append("level", logLevel.toUpperCase()) out += append("fiber", formatFiberId(fiber.id)) const messages = Array.ensure(message) @@ -440,34 +449,20 @@ const format = ( * * **Example** (Creating loggers from functions) * - * ```ts - * import { Effect, Logger, References } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" * - * // Simple text logger + * const outputs: Array = [] * const textLogger = Logger.make((options) => - * `${options.date.toISOString()} [${options.logLevel}] ${options.message}` + * `${options.logLevel}: ${options.message}` * ) - * - * // Structured object logger - * const objectLogger = Logger.make((options) => ({ - * timestamp: options.date.toISOString(), - * level: options.logLevel, - * message: options.message, - * fiberId: options.fiber.id, - * annotations: options.fiber.getRef(References.CurrentLogAnnotations) - * })) - * - * // Custom filtering logger - * const filteredLogger = Logger.make((options) => { - * if (options.logLevel === "Debug") { - * return // Skip debug messages - * } - * return `${options.logLevel}: ${options.message}` - * }) + * const collector = Logger.make((options) => outputs.push(textLogger.log(options))) * * const program = Effect.log("Hello World").pipe( - * Effect.provide(Logger.layer([textLogger])) + * Effect.provide(Logger.layer([collector])) * ) + * Effect.runSync(program) + * outputs // => ["Info: Hello World"] * ``` * * @category constructors @@ -482,25 +477,10 @@ export const make: ( * * **Example** (Referencing the default logger) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the default logger (automatically used by Effect runtime) - * const program = Effect.gen(function*() { - * yield* Effect.log("This uses the default logger") - * yield* Effect.logInfo("Info message") - * yield* Effect.logError("Error message") - * }) - * - * // Explicitly use the default logger - * const withDefaultLogger = Effect.log("Explicit default").pipe( - * Effect.provide(Logger.layer([Logger.defaultLogger])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Compare with custom logger - * const customLogger = Logger.make((options) => { - * console.log(`CUSTOM: ${options.message}`) - * }) + * Logger.isLogger(Logger.defaultLogger) // => true * ``` * * @category constructors @@ -518,24 +498,24 @@ export const defaultLogger: Logger = effect.defaultLogger * * **Example** (Formatting logs as simple strings) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * * // Use the simple format logger - * const simpleLoggerProgram = Effect.log("Hello Simple Format").pipe( - * Effect.provide(Logger.layer([Logger.formatSimple])) + * const stableSimple = Logger.map(Logger.formatSimple, (output) => + * output + * .replace(/timestamp=\S+ /, "") + * .replace(/fiber=#\d+ /, "") * ) - * - * // Combine with console output - * const consoleSimpleLogger = Logger.withConsoleLog(Logger.formatSimple) - * * const program = Effect.gen(function*() { - * yield* Effect.log("Application started") - * yield* Effect.logInfo("Processing data") - * yield* Effect.logWarning("Memory usage high") - * }).pipe( - * Effect.provide(Logger.layer([consoleSimpleLogger])) - * ) + * yield* Effect.log("Application started").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableSimple)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) + * + * await Effect.runPromise(program) // => ["level=INFO message=\"Application started\""] * ``` * * @category constructors @@ -554,27 +534,23 @@ export const formatSimple = effect.loggerMake(format(escapeDoubleQuotes)) * * **Example** (Formatting logs as logfmt) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * - * // Use the logfmt format logger - * const logfmtLoggerProgram = Effect.log("Hello LogFmt Format").pipe( - * Effect.provide(Logger.layer([Logger.formatLogFmt])) - * ) - * - * // Perfect for structured logging systems - * const structuredProgram = Effect.gen(function*() { - * yield* Effect.log("User login", { userId: 123, method: "OAuth" }) - * yield* Effect.logInfo("Request processed", { - * duration: 45, - * status: "success" - * }) - * }).pipe( - * Effect.provide(Logger.layer([Logger.withConsoleLog(Logger.formatLogFmt)])) + * const stableLogFmt = Logger.map(Logger.formatLogFmt, (output) => + * output + * .replace(/timestamp=\S+ /, "") + * .replace(/fiber=#\d+ /, "") * ) + * const program = Effect.gen(function*() { + * yield* Effect.log("User login").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableLogFmt)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) * - * // Good for log aggregation systems like Splunk, ELK - * const productionLogger = Logger.formatLogFmt + * await Effect.runPromise(program) // => ["level=INFO message=\"User login\""] * ``` * * @category constructors @@ -594,30 +570,22 @@ export const formatLogFmt = effect.loggerMake(format(JSON.stringify, 0)) * * **Example** (Formatting logs as structured objects) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" + * import { TestConsole } from "effect/testing" * - * // Use the structured format logger - * const structuredLoggerProgram = Effect.log("Hello Structured Format").pipe( - * Effect.provide(Logger.layer([Logger.formatStructured])) - * ) - * - * // Perfect for JSON processing and analytics - * const analyticsProgram = Effect.gen(function*() { - * yield* Effect.log("User action", { action: "click", element: "button" }) - * yield* Effect.logInfo("API call", { endpoint: "/users", duration: 150 }) - * }).pipe( - * Effect.annotateLogs("sessionId", "abc123"), - * Effect.withLogSpan("request"), - * Effect.provide(Logger.layer([Logger.formatStructured])) - * ) + * const stableStructured = Logger.map(Logger.formatStructured, (output) => ({ + * message: output.message, + * level: output.level + * })) + * const program = Effect.gen(function*() { + * yield* Effect.log("User action").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableStructured)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) * - * // Process structured output - * const processingLogger = Logger.map(Logger.formatStructured, (output) => { - * // Process the structured object - * const enhanced = { ...output, processed: true } - * return enhanced - * }) + * await Effect.runPromise(program) // => [{ message: "User action", level: "INFO" }] * ``` * * @category constructors @@ -637,13 +605,13 @@ export const formatStructured: Logger `{"service":"api-server","entry":${jsonString}}` - * ) + * const stableJson = Logger.map(Logger.formatJson, (json) => { + * const output = JSON.parse(json) + * return Formatter.formatJson({ message: output.message, level: output.level }) + * }) + * const program = Effect.gen(function*() { + * yield* Effect.log("Server started").pipe( + * Effect.provide(Logger.layer([Logger.withConsoleLog(stableJson)])) + * ) + * return yield* TestConsole.logLines + * }).pipe(Effect.provide(TestConsole.layer)) * - * const envelopedConsoleLogger = Logger.withConsoleLog(envelopedJsonLogger) + * await Effect.runPromise(program) // => ["{\"message\":\"Server started\",\"level\":\"INFO\"}"] * ``` * * @category constructors @@ -719,43 +674,26 @@ export const formatJson = map(formatStructured, Formatter.formatJson) * * **Example** (Batching logger output) * - * ```ts - * import { Duration, Effect, Logger } from "effect" + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" * - * // Create a batched logger that flushes every 5 seconds - * const batchedLogger = Logger.batched(Logger.formatJson, { - * window: Duration.seconds(5), + * const flushed: Array> = [] + * const messageLogger = Logger.make((options) => String(options.message)) + * const batchedLogger = Logger.batched(messageLogger, { + * window: "1 hour", * flush: (messages) => * Effect.sync(() => { - * console.log(`Flushing ${messages.length} log entries:`) - * messages.forEach((msg, i) => console.log(`${i + 1}. ${msg}`)) + * flushed.push(messages) * }) * }) * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const logger = yield* batchedLogger - * - * yield* Effect.provide( - * Effect.all([ - * Effect.log("Event 1"), - * Effect.log("Event 2"), - * Effect.log("Event 3"), - * Effect.sleep(Duration.seconds(6)), // Trigger flush - * Effect.log("Event 4") - * ]), - * Logger.layer([logger]) - * ) - * }) - * - * // Remote batch logging example - * const remoteBatchLogger = Logger.batched(Logger.formatStructured, { - * window: Duration.seconds(10), - * flush: (entries) => - * Effect.sync(() => { - * // Send batch to remote logging service - * console.log(`Sending ${entries.length} log entries to remote service`) - * }) - * }) + * yield* Effect.log("Event 1").pipe(Effect.provide(Logger.layer([logger]))) + * yield* Effect.log("Event 2").pipe(Effect.provide(Logger.layer([logger]))) + * })) + * await Effect.runPromise(program) + * flushed // => [["Event 1", "Event 2"]] * ``` * * @category constructors @@ -814,7 +752,9 @@ export const batched = dual< /** * A `Logger` which outputs logs in a "pretty" format and writes them to the - * console. + * console. Chooses between tty and browser implementation. If the runtime + * platform is known and fixed, prefer {@link consolePrettyBrowser} or + * {@link consolePrettyTty}. * * **Details** * @@ -824,87 +764,151 @@ export const batched = dual< * * **Example** (Logging with pretty console output) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Use the pretty console logger with default settings - * const basicPretty = Effect.log("Hello Pretty Format").pipe( - * Effect.provide(Logger.layer([Logger.consolePretty()])) + * const prettyLogger = Logger.layer([Logger.consolePretty()]) + * + * Effect.log("hello").pipe( + * Effect.withLogSpan('label'), + * Effect.annotateLogs('key', 'value'), + * Effect.provide(prettyLogger), + * Effect.runSync * ) + * ``` * - * // Configure pretty logger options - * const customPretty = Logger.consolePretty({ - * colors: true, - * stderr: false, - * mode: "tty", - * formatDate: (date) => date.toLocaleTimeString() - * }) + * **Example** (Logging with console.error, when the environment has TTY) + * + * ```ts import.meta.vitest + * import { Effect, Layer, Logger } from "effect" * - * // Perfect for development environment - * const developmentProgram = Effect.gen(function*() { - * yield* Effect.log("Application starting") - * yield* Effect.logInfo("Database connected") - * yield* Effect.logWarning("High memory usage detected") - * }).pipe( - * Effect.annotateLogs("environment", "development"), - * Effect.withLogSpan("startup"), - * Effect.provide(Logger.layer([customPretty])) + * const prettyLoggerLayer = Layer.merge( + * Logger.layer([Logger.consolePretty()]), + * Layer.succeed(Logger.LogToStderr, true) * ) * - * // Disable colors for CI/CD environments - * const ciLogger = Logger.consolePretty({ colors: false }) + * Effect.log('hello').pipe( + * Effect.provide(prettyLoggerLayer), + * Effect.runSync + * ) * ``` * * @category constructors + * @see {@link consolePrettyBrowser} for browser-specific implementation + * @see {@link consolePrettyTty} for the TTY-mode implementation * @since 4.0.0 */ export const consolePretty: ( options?: { readonly colors?: "auto" | boolean | undefined - readonly stderr?: boolean | undefined readonly formatDate?: ((date: Date) => string) | undefined readonly mode?: "browser" | "tty" | "auto" | undefined } ) => Logger = effect.consolePretty /** - * A `Logger` which outputs logs using the [logfmt](https://brandur.org/logfmt) - * style and writes them to the console. + * A `Logger` which outputs logs in a "pretty" format and writes them to the + * console. Intended to be used on platforms with a browser console. * * **Details** * - * For example, a console logfmt entry is rendered as - * `timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=info`. + * For example, pretty output can render as + * `[09:37:17.579] INFO (#1) label=0ms: hello` followed by an annotation line + * such as `key: value`. * - * **Example** (Logging logfmt output to the console) + * **Example** (Logging with pretty console output) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Use the console logfmt logger - * const logfmtProgram = Effect.log("Hello LogFmt Console").pipe( - * Effect.provide(Logger.layer([Logger.consoleLogFmt])) + * const prettyLogger = Logger.layer([Logger.consolePrettyBrowser()]) + * + * Effect.log("hello").pipe( + * Effect.withLogSpan('label'), + * Effect.annotateLogs('key', 'value'), + * Effect.provide(prettyLogger), + * Effect.runSync * ) + * ``` * - * // Great for production environments - * const productionProgram = Effect.gen(function*() { - * yield* Effect.log("Server started", { port: 8080, version: "1.0.0" }) - * yield* Effect.logInfo("Request processed", { userId: 123, duration: 45 }) - * yield* Effect.logError("Validation failed", { - * field: "email", - * value: "invalid" - * }) - * }).pipe( - * Effect.annotateLogs("service", "api"), - * Effect.withLogSpan("request-handler"), - * Effect.provide(Logger.layer([Logger.consoleLogFmt])) + * @category constructors + * @see {@link consolePretty} for the platform-independent implementation + * @since 4.0.0 + */ +export const consolePrettyBrowser: ( + options?: { + readonly colors?: boolean | undefined + readonly formatDate?: ((date: Date) => string) | undefined + } +) => Logger = effect.prettyLoggerBrowser + +/** + * A `Logger` which outputs logs in a "pretty" format and writes them to the + * console. Intended to be used on platforms with tty console. + * + * **Details** + * + * For example, pretty output can render as + * `[09:37:17.579] INFO (#1) label=0ms: hello` followed by an annotation line + * such as `key: value`. + * + * **Example** (Logging with pretty console output) + * + * ```ts import.meta.vitest + * import { Effect, Logger } from "effect" + * + * const prettyLogger = Logger.layer([Logger.consolePrettyTty()]) + * + * Effect.log("hello").pipe( + * Effect.withLogSpan('label'), + * Effect.annotateLogs('key', 'value'), + * Effect.provide(prettyLogger), + * Effect.runSync + * ) + * ``` + * + * **Example** (Logging with console.error) + * + * ```ts import.meta.vitest + * import { Effect, Layer, Logger } from "effect" + * + * const prettyLoggerLayer = Layer.merge( + * Logger.layer([Logger.consolePrettyTty()]), + * Layer.succeed(Logger.LogToStderr, true) * ) * - * // Combine with other loggers - * const multiLoggerLive = Logger.layer([ - * Logger.consoleLogFmt, - * Logger.consolePretty() - * ]) + * Effect.log('hello').pipe( + * Effect.provide(prettyLoggerLayer), + * Effect.runSync + * ) + * ``` + * + * @category constructors + * @see {@link consolePretty} for the platform-independent implementation + * @since 4.0.0 + */ +export const consolePrettyTty: ( + options?: { + readonly colors?: boolean | undefined + readonly formatDate?: ((date: Date) => string) | undefined + } +) => Logger = effect.prettyLoggerTty + +/** + * A `Logger` which outputs logs using the [logfmt](https://brandur.org/logfmt) + * style and writes them to the console. + * + * **Details** + * + * For example, a console logfmt entry is rendered as + * `timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=info`. + * + * **Example** (Logging logfmt output to the console) + * + * ```ts import.meta.vitest + * import { Logger } from "effect" + * + * Logger.isLogger(Logger.consoleLogFmt) // => true * ``` * * @category constructors @@ -926,41 +930,10 @@ export const consoleLogFmt: Logger = withConsoleLog(formatLogFmt) * * **Example** (Logging structured output to the console) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the console structured logger - * const structuredProgram = Effect.log("Hello Structured Console").pipe( - * Effect.provide(Logger.layer([Logger.consoleStructured])) - * ) - * - * // Perfect for development debugging - * const debugProgram = Effect.gen(function*() { - * yield* Effect.log("User event", { - * userId: 123, - * action: "login", - * ip: "192.168.1.1" - * }) - * yield* Effect.logInfo("API call", { - * endpoint: "/users", - * method: "GET", - * duration: 120 - * }) - * }).pipe( - * Effect.annotateLogs("requestId", "req-123"), - * Effect.withLogSpan("authentication"), - * Effect.provide(Logger.layer([Logger.consoleStructured])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Easy to parse and inspect object structure - * const inspectionProgram = Effect.gen(function*() { - * yield* Effect.log("Complex data", { - * user: { id: 1, name: "John" }, - * metadata: { source: "api", version: 2 } - * }) - * }).pipe( - * Effect.provide(Logger.layer([Logger.consoleStructured])) - * ) + * Logger.isLogger(Logger.consoleStructured) // => true * ``` * * @category constructors @@ -980,46 +953,10 @@ export const consoleStructured: Logger = withConsoleLog(formatStr * * **Example** (Logging JSON output to the console) * - * ```ts - * import { Effect, Logger } from "effect" - * - * // Use the console JSON logger - * const jsonProgram = Effect.log("Hello JSON Console").pipe( - * Effect.provide(Logger.layer([Logger.consoleJson])) - * ) - * - * // Perfect for production logging and log aggregation - * const productionProgram = Effect.gen(function*() { - * yield* Effect.log("Server started", { port: 3000, env: "production" }) - * yield* Effect.logInfo("Request", { - * method: "POST", - * url: "/api/users", - * body: { name: "Alice" } - * }) - * yield* Effect.logError("Database error", { - * error: "Connection timeout", - * retryCount: 3 - * }) - * }).pipe( - * Effect.annotateLogs("service", "user-api"), - * Effect.annotateLogs("version", "1.2.3"), - * Effect.withLogSpan("request-processing"), - * Effect.provide(Logger.layer([Logger.consoleJson])) - * ) + * ```ts import.meta.vitest + * import { Logger } from "effect" * - * // Easy to pipe to log aggregation services - * const productionSetup = Logger.layer([ - * Logger.consoleJson, // For stdout JSON logs - * Logger.consolePretty() // For local debugging - * ]) - * - * // Ideal for containerized environments (Docker, Kubernetes) - * const containerProgram = Effect.log("Container ready", { - * containerId: "abc123", - * image: "myapp:latest" - * }).pipe( - * Effect.provide(Logger.layer([Logger.consoleJson])) - * ) + * Logger.isLogger(Logger.consoleJson) // => true * ``` * * @category constructors @@ -1042,40 +979,14 @@ export const consoleJson: Logger = withConsoleLog(formatJson) * * **Example** (Recording logs as trace span events) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Tracer logger is included by default - logs automatically become span events - * const defaultProgram = Effect.gen(function*() { - * yield* Effect.log("This automatically becomes a span event") - * yield* Effect.logInfo("Processing data") - * }) - * - * // Explicitly combine tracer logger with other loggers - * const observabilityProgram = Effect.gen(function*() { - * yield* Effect.log("Operation started") - * yield* Effect.logInfo("Processing data") - * yield* Effect.logError("Error occurred") - * }).pipe( - * Effect.withLogSpan("data-processing"), - * Effect.provide(Logger.layer([ - * Logger.tracerLogger, - * Logger.consoleJson - * ])) - * ) - * - * // Perfect for correlating logs with traces in distributed systems - * const distributedProgram = Effect.gen(function*() { - * yield* Effect.log("Step 1: Fetching user data") - * yield* Effect.sleep("100 millis") - * yield* Effect.log("Step 2: Processing payment") - * yield* Effect.sleep("200 millis") - * yield* Effect.log("Step 3: Sending confirmation") - * }).pipe( - * Effect.withLogSpan("payment-workflow"), - * Effect.annotateLogs("userId", "user-123"), + * const program = Effect.log("span event").pipe( + * Effect.withSpan("operation"), * Effect.provide(Logger.layer([Logger.tracerLogger])) * ) + * Effect.runSync(program) * ``` * * @category constructors @@ -1094,37 +1005,23 @@ export const tracerLogger: Logger = effect.tracerLogger * * **Example** (Providing logger layers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Logger } from "effect" * - * // Single logger layer - * const JsonLoggerLive = Logger.layer([Logger.consoleJson]) - * - * // Multiple loggers layer - * const MultiLoggerLive = Logger.layer([ - * Logger.consoleJson, - * Logger.consolePretty(), - * Logger.formatStructured - * ]) - * - * // Merge with existing loggers - * const AdditionalLoggerLive = Logger.layer( - * [Logger.consoleJson], - * { mergeWithExisting: true } - * ) - * - * // Using multiple logger formats - * const jsonLogger = Logger.consoleJson - * const prettyLogger = Logger.consolePretty() - * - * const CustomLoggerLive = Logger.layer([jsonLogger, prettyLogger]) + * const messages: Array = [] + * const customLogger = Logger.make((options) => { + * messages.push(options.message) + * }) + * const CustomLoggerLayer = Logger.layer([customLogger]) * * const program = Effect.log("Application started").pipe( - * Effect.provide(CustomLoggerLive) + * Effect.provide(CustomLoggerLayer) * ) + * Effect.runSync(program) + * messages // => [["Application started"]] * ``` * - * @category context + * @category layers * @since 4.0.0 */ export const layer = < @@ -1162,91 +1059,57 @@ export const layer = < * * **Example** (Writing JSON logs to a file) * - * ```ts - * import { Effect, Layer, Logger } from "effect" - * import { NodeFileSystem, NodeRuntime } from "@effect/platform-node" + * ```ts import.meta.vitest + * import { Effect, FileSystem, Logger } from "effect" * - * const fileLogger = Logger.formatJson.pipe( - * Logger.toFile("/tmp/log.txt") - * ) - * const LoggerLive = Logger.layer([fileLogger]).pipe( - * Layer.provide(NodeFileSystem.layer) - * ) - * - * Effect.log("a").pipe( - * Effect.andThen(Effect.log("b")), - * Effect.andThen(Effect.log("c")), - * Effect.provide(LoggerLive), - * NodeRuntime.runMain - * ) + * const writes: Array = [] + * const file = { + * writeAll: (buffer: Uint8Array) => Effect.sync(() => { + * writes.push(new TextDecoder().decode(buffer).trim()) + * }) + * } as unknown as FileSystem.File + * const fileSystem = FileSystem.makeNoop({ open: () => Effect.succeed(file) }) + * const messageLogger = Logger.make((options) => String(options.message)) + * + * const program = Effect.scoped(Effect.gen(function*() { + * const fileLogger = yield* Logger.toFile(messageLogger, "/tmp/log.txt") + * yield* Effect.log("a").pipe(Effect.provide(Logger.layer([fileLogger]))) + * yield* Effect.log("b").pipe(Effect.provide(Logger.layer([fileLogger]))) + * yield* Effect.log("c").pipe(Effect.provide(Logger.layer([fileLogger]))) + * })).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)) + * + * await Effect.runPromise(program) + * writes // => ["a\nb\nc"] * ``` * * **Example** (Writing logs to files) * - * ```ts - * import { Duration, Effect, Logger } from "effect" - * import { NodeFileSystem } from "@effect/platform-node" - * - * // Basic file logging. The scope keeps the file open while logs are emitted - * // and flushes pending entries when it closes. - * const basicFileLogger = Effect.scoped( - * Effect.gen(function*() { - * const fileLogger = yield* Logger.formatJson.pipe( - * Logger.toFile("/tmp/app.log") - * ) - * - * yield* Effect.log("Application started").pipe( - * Effect.provide(Logger.layer([fileLogger])) - * ) - * }) - * ).pipe( - * Effect.provide(NodeFileSystem.layer) - * ) + * ```ts import.meta.vitest + * import { Effect, FileSystem, Logger } from "effect" * - * // File logger with custom batch window - * const batchedFileLogger = Effect.scoped( - * Effect.gen(function*() { - * const fileLogger = yield* Logger.formatLogFmt.pipe( - * Logger.toFile("/var/log/myapp.log", { - * flag: "a", - * batchWindow: Duration.seconds(5) - * }) - * ) - * - * yield* Effect.all([ - * Effect.log("Event 1"), - * Effect.log("Event 2"), - * Effect.log("Event 3") - * ]).pipe( - * Effect.provide(Logger.layer([fileLogger])) - * ) + * const writes: Array = [] + * const file = { + * writeAll: (buffer: Uint8Array) => Effect.sync(() => { + * writes.push(new TextDecoder().decode(buffer).trim()) * }) - * ).pipe( - * Effect.provide(NodeFileSystem.layer) - * ) + * } as unknown as FileSystem.File + * const fileSystem = FileSystem.makeNoop({ open: () => Effect.succeed(file) }) + * const messageLogger = Logger.make((options) => String(options.message)) * - * // Multiple loggers: console + file - * const multiLogger = Effect.scoped( - * Effect.gen(function*() { - * const fileLogger = yield* Logger.formatJson.pipe( - * Logger.toFile("/tmp/production.log") - * ) - * - * const loggerLive = Logger.layer([ - * Logger.consolePretty(), - * fileLogger - * ]) - * - * yield* Effect.log("Production event").pipe( - * Effect.provide(loggerLive) - * ) + * const program = Effect.scoped(Effect.gen(function*() { + * const fileLogger = yield* Logger.toFile(messageLogger, "/tmp/app.log", { + * batchWindow: "1 hour" * }) - * ).pipe( - * Effect.provide(NodeFileSystem.layer) - * ) + * yield* Effect.log("Application started").pipe( + * Effect.provide(Logger.layer([fileLogger])) + * ) + * })).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)) + * + * await Effect.runPromise(program) + * writes // => ["Application started"] * ``` * - * @category file + * @category logging * @since 4.0.0 */ export const toFile = dual< @@ -1278,7 +1141,7 @@ export const toFile = dual< const encoder = new TextEncoder() return yield* batched(self, { window: options?.batchWindow ?? 1000, - flush: (output) => effect.ignore(logFile.write(encoder.encode(output.join("\n") + "\n"))) + flush: (output) => effect.ignore(logFile.writeAll(encoder.encode(output.join("\n") + "\n"))) }) }) ) diff --git a/packages/effect/src/ManagedRuntime.ts b/packages/effect/src/ManagedRuntime.ts index f3252b780..e0a99849d 100644 --- a/packages/effect/src/ManagedRuntime.ts +++ b/packages/effect/src/ManagedRuntime.ts @@ -207,6 +207,16 @@ export interface ManagedRuntime { */ readonly dispose: () => Promise + /** + * Dispose of the resources associated with the runtime. + * + * **When to use** + * + * Use with the `await using` syntax to automatically dispose the runtime + * when it goes out of scope. + */ + readonly [Symbol.asyncDispose]: () => Promise + /** * Dispose of the resources associated with the runtime. * @@ -239,15 +249,17 @@ export interface ManagedRuntime { * * **Example** (Creating a managed runtime) * - * ```ts + * ```ts import.meta.vitest * import { Context, Effect, Layer, ManagedRuntime } from "effect" * + * const notifications: Array = [] + * * class Notifications extends Context.Service Effect.Effect * }>()("Notifications") { * static readonly layer = Layer.succeed(this)({ * notify: Effect.fn("Notifications.notify")((message) => - * Effect.sync(() => console.log(message)) + * Effect.sync(() => notifications.push(message)) * ) * }) * } @@ -259,15 +271,15 @@ export interface ManagedRuntime { * (_) => _.notify("Hello, world!") * ).pipe(Effect.ensuring(runtime.disposeEffect)) * - * runtime.runPromise(program) - * // Hello, world! + * await runtime.runPromise(program) + * notifications // => ["Hello, world!"] * ``` * * @see {@link ManagedRuntime} for the returned runtime interface * @see {@link Layer.MemoMap} for shared layer memoization * @see {@link Layer.build} for lower-level scoped layer construction * - * @category runtime class + * @category constructors * @since 2.0.0 */ export const make = ( @@ -305,7 +317,7 @@ export const make = ( self.cachedContext = context }) ), - { ...defaultRunOptions, scheduler: fiber.currentScheduler } + { ...defaultRunOptions, scheduler: fiber.cache.scheduler } ) } return Effect.flatten(Fiber.await(buildFiber)) @@ -324,6 +336,9 @@ export const make = ( dispose(): Promise { return Effect.runPromise(self.disposeEffect) }, + [Symbol.asyncDispose](): Promise { + return self.dispose() + }, disposeEffect: Effect.suspend(() => { ;(self as Mutable>).contextEffect = Effect.die("ManagedRuntime disposed") self.cachedContext = undefined diff --git a/packages/effect/src/Match.ts b/packages/effect/src/Match.ts index 2dfe2af23..918c44d20 100644 --- a/packages/effect/src/Match.ts +++ b/packages/effect/src/Match.ts @@ -20,17 +20,44 @@ import type { Unify } from "./Unify.ts" const TypeId = internal.TypeId +// The conditional must stay deferred until P is inferred. Replacing it with an +// intersection loses contextual typing for nested generic calls (microsoft/TypeScript#52864). +type Contextual = internal.Contextual + +type TagHandlers = { + readonly [Tag in Types.Tags & string]: (_: Extract>) => Ret +} + +type PartialTagHandlers = { + readonly [Tag in Types.Tags & string]?: ((_: Extract>) => Ret) | undefined +} + +type ValueTagHandlers = { + readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any +} + +/** + * Marker used by `Matcher` to distinguish matchers created with `Match.value`. + * + * @category models + * @since 4.0.0 + */ +export type ValueFlavor = "value" + /** * Union type for matchers created by `Match.type` and `Match.value`. * * **Details** * * A `Matcher` carries the input type, accumulated filters, remaining cases, - * result type, and, for value matchers, the provided value being matched. + * result type, and a flavor distinguishing the two matcher variants: `never` + * for matchers created with `Match.type` and `ValueFlavor` for matchers created + * with `Match.value`. Because the flavor never depends on the input type, + * terminal combinators resolve even when the input contains type parameters. * * **Example** (Matching string and number values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Simulated dynamic input that can be a string or a number @@ -47,16 +74,23 @@ const TypeId = internal.TypeId * Match.exhaustive * ) * - * console.log(result) - * // Output: "string: some input" + * result // => "string: some input" * ``` * * @category models * @since 4.0.0 */ -export type Matcher = - | TypeMatcher - | ValueMatcher +export type Matcher< + Input, + Filters, + RemainingApplied, + Result, + Flavor, + Return = any, + Args extends Array = [] +> = + | TypeMatcher + | ValueMatcher /** * Represents a pattern matcher that operates on types rather than specific values. @@ -70,7 +104,7 @@ export type Matcher "String: hello" + * matcher(42) // => "Number: 42" * ``` * * @category models * @since 4.0.0 */ -export interface TypeMatcher extends Pipeable { +export interface TypeMatcher< + in Input, + out Filters, + out Remaining, + out Result, + out Return = any, + in Args extends Array = [] +> extends Pipeable { readonly _tag: "TypeMatcher" readonly [TypeId]: { readonly _input: T.Contravariant @@ -95,9 +136,11 @@ export interface TypeMatcher readonly _result: T.Covariant readonly _return: T.Covariant + readonly _args: T.Contravariant } readonly cases: ReadonlyArray - add(_case: Case): TypeMatcher + readonly select: (...args: Array) => unknown + add(_case: Case): TypeMatcher } /** @@ -107,11 +150,12 @@ export interface TypeMatcher "Unknown type") * ) * - * console.log(result) // "User: Alice" + * result // => "User: Alice" * ``` * * @category models * @since 4.0.0 */ -export interface ValueMatcher - extends Pipeable -{ +export interface ValueMatcher< + in Input, + Filters, + out Remaining, + out Result, + Provided, + out Return = any, + out Flavor = ValueFlavor +> extends Pipeable { readonly _tag: "ValueMatcher" readonly [TypeId]: { readonly _input: T.Contravariant readonly _filters: T.Covariant readonly _result: T.Covariant readonly _return: T.Covariant + readonly _flavor: T.Covariant } readonly provided: Provided readonly value: Result.Result - add(_case: Case): ValueMatcher + add(_case: Case): ValueMatcher } /** @@ -177,7 +228,7 @@ export type Case = When | Not * * **Example** (Creating positive match cases) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // When creates cases that match specific patterns @@ -187,8 +238,8 @@ export type Case = When | Not * Match.exhaustive * ) * - * console.log(stringMatcher("hello")) // "Got string: hello" - * console.log(stringMatcher(42)) // "Got number: 42" + * stringMatcher("hello") // => "Got string: hello" + * stringMatcher(42) // => "Got number: 42" * ``` * * @category models @@ -197,7 +248,7 @@ export type Case = When | Not export interface When { readonly _tag: "When" guard(u: unknown): boolean - evaluate(input: unknown): any + evaluate(input: unknown, ...args: Array): any } /** @@ -211,7 +262,7 @@ export interface When { * * **Example** (Creating negative match cases) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Not creates cases that exclude specific patterns @@ -221,8 +272,8 @@ export interface When { * Match.orElse(() => "This string is forbidden") * ) * - * console.log(matcher("hello")) // "Allowed: hello" - * console.log(matcher("forbidden")) // "This string is forbidden" + * matcher("hello") // => "Allowed: hello" + * matcher("forbidden") // => "This string is forbidden" * ``` * * @category models @@ -231,7 +282,7 @@ export interface When { export interface Not { readonly _tag: "Not" guard(u: unknown): boolean - evaluate(input: unknown): any + evaluate(input: unknown, ...args: Array): any } /** @@ -250,7 +301,7 @@ export interface Not { * * **Example** (Matching Numbers and Strings) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for values that are either strings or numbers @@ -266,11 +317,9 @@ export interface Not { * Match.exhaustive * ) * - * console.log(match(0)) - * // Output: "number: 0" + * match(0) // => "number: 0" * - * console.log(match("hello")) - * // Output: "string: hello" + * match("hello") // => "string: hello" * ``` * * @see {@link value} for creating a matcher from a specific value. @@ -280,6 +329,35 @@ export interface Not { */ export const type: () => Matcher, I, never, never> = internal.type +/** + * Creates a reusable matcher from a function that selects the value to match. + * + * **Details** + * + * The compiled matcher keeps the selector's original argument list. Case + * handlers receive the narrowed selected value followed by those arguments. + * + * **Example** (Creating a reusable matcher) + * + * ```ts import.meta.vitest + * import { Match } from "effect" + * + * const format = Match.fn((prefix: string, value: "a" | "b") => value).pipe( + * Match.when("a", (_value, prefix) => `${prefix}: A`), + * Match.when("b", (_value, prefix) => `${prefix}: B`), + * Match.exhaustive + * ) + * + * format("status", "a") // => "status: A" + * ``` + * + * @category constructors + * @since 4.0.0 + */ +export const fn: , I>( + select: (...args: Args) => I +) => Matcher, I, never, never, any, Args> = internal.fn + /** * Creates a matcher from a specific value. * @@ -299,7 +377,7 @@ export const type: () => Matcher, I, never, never> = * * **Example** (Matching an Object by Property) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const input = { name: "John", age: 30 } @@ -315,8 +393,7 @@ export const type: () => Matcher, I, never, never> = * Match.orElse(() => "Oh, not John") * ) * - * console.log(result) - * // Output: "John is 30 years old" + * result // => "John is 30 years old" * ``` * * @see {@link type} for creating a matcher from a specific type. @@ -326,7 +403,7 @@ export const type: () => Matcher, I, never, never> = */ export const value: ( i: I -) => Matcher, I, never, I> = internal.value +) => Matcher, I, never, ValueFlavor> = internal.value /** * Creates a match function for a specific value with discriminated union handling. @@ -339,7 +416,7 @@ export const value: ( * * **Example** (Matching value tags) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type Status = { readonly _tag: "Success"; readonly data: string } @@ -351,7 +428,7 @@ export const value: ( * Success: (result) => `Success: ${result.data}` * }) * - * console.log(message) // "Success: Hello" + * message // => "Success: Hello" * ``` * * @category constructors @@ -361,15 +438,20 @@ export const valueTags: { < const I, P extends - & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } + & ValueTagHandlers & { readonly [Tag in Exclude>]: never } - >(fields: P): (input: I) => Unify> + >( + fields: Contextual> + ): (input: I) => Unify> < const I, P extends - & { readonly [Tag in Types.Tags<"_tag", I> & string]: (_: Extract) => any } + & ValueTagHandlers & { readonly [Tag in Exclude>]: never } - >(input: I, fields: P): Unify> + >( + input: I, + fields: Contextual> + ): Unify> } = internal.valueTags /** @@ -383,7 +465,7 @@ export const valueTags: { * * **Example** (Matching type tags) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type Result = @@ -398,11 +480,9 @@ export const valueTags: { * Loading: () => "Loading..." * }) * - * console.log(formatResult({ _tag: "Success", data: "Hello World" })) - * // Output: "Data: Hello World" + * formatResult({ _tag: "Success", data: "Hello World" }) // => "Data: Hello World" * - * console.log(formatResult({ _tag: "Error", message: "Network failed" })) - * // Output: "Error: Network failed" + * formatResult({ _tag: "Error", message: "Network failed" }) // => "Error: Network failed" * * // Create a matcher with inferred return type * const processResult = Match.typeTags()({ @@ -411,8 +491,7 @@ export const valueTags: { * Loading: () => ({ type: "pending" }) * }) * - * console.log(processResult({ _tag: "Loading" })) - * // Output: { type: "pending" } + * processResult({ _tag: "Loading" }) // => { type: "pending" } * ``` * * @category constructors @@ -453,7 +532,7 @@ export const typeTags: { * * **Example** (Validating return type consistency) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const match = Match.type<{ a: number } | { b: string }>().pipe( @@ -471,9 +550,9 @@ export const typeTags: { * @category utility types * @since 4.0.0 */ -export const withReturnType: () => ( - self: Matcher -) => [Ret] extends [[A] extends [never] ? any : A] ? Matcher +export const withReturnType: () => >( + self: Matcher +) => [Ret] extends [[A] extends [never] ? any : A] ? Matcher : "withReturnType constraint does not extend Result type" = internal.withReturnType /** @@ -493,7 +572,7 @@ export const withReturnType: () => ( * * **Example** (Matching with values and predicates) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for objects with an "age" property @@ -509,14 +588,11 @@ export const withReturnType: () => ( * Match.orElse((user: { age: number }) => `${user.age} is too young`) * ) * - * console.log(match({ age: 20 })) - * // Output: "Age: 20" + * match({ age: 20 }) // => "Age: 20" * - * console.log(match({ age: 18 })) - * // Output: "You can vote" + * match({ age: 18 }) // => "You can vote" * - * console.log(match({ age: 4 })) - * // Output: "4 is too young" + * match({ age: 4 }) // => "4 is too young" * ``` * * @see {@link whenOr} for handling any one of several patterns with the same handler @@ -524,26 +600,28 @@ export const withReturnType: () => ( * @see {@link not} for handling inputs that do not match a pattern * @see {@link orElse} for providing a fallback when no pattern case matches * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const when: < R, const P extends Types.PatternPrimitive | Types.PatternBase, Ret, - Fn extends (_: Types.WhenMatch) => Ret + Args extends Array, + Fn extends (_: Types.WhenMatch, ...args: Args) => Ret >( pattern: P, f: Fn ) => ( - self: Matcher + self: Matcher ) => Matcher< I, Types.AddWithout>, Types.ApplyFilters>>, A | ReturnType, Pr, - Ret + Ret, + Args > = internal.when /** @@ -561,7 +639,7 @@ export const when: < * * **Example** (Matching one of several patterns) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type ErrorType = @@ -579,32 +657,32 @@ export const when: < * Match.exhaustive * ) * - * console.log(handleError({ _tag: "NetworkError", message: "No connection" })) - * // Output: "Retry the request" + * handleError({ _tag: "NetworkError", message: "No connection" }) // => "Retry the request" * - * console.log(handleError({ _tag: "ValidationError", field: "email" })) - * // Output: "Invalid field: email" + * handleError({ _tag: "ValidationError", field: "email" }) // => "Invalid field: email" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const whenOr: < R, const P extends ReadonlyArray | Types.PatternBase>, Ret, - Fn extends (_: Types.WhenMatch) => Ret + Args extends Array, + Fn extends (_: Types.WhenMatch, ...args: Args) => Ret >( ...args: [...patterns: P, f: Fn] ) => ( - self: Matcher + self: Matcher ) => Matcher< I, Types.AddWithout>, Types.ApplyFilters>>, A | ReturnType, Pr, - Ret + Ret, + Args > = internal.whenOr /** @@ -623,7 +701,7 @@ export const whenOr: < * * **Example** (Matching all provided patterns) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type User = { readonly age: number; readonly role: "admin" | "user" } @@ -637,31 +715,32 @@ export const whenOr: < * Match.orElse(() => "Access denied") * ) * - * console.log(checkUser({ age: 20, role: "admin" })) - * // Output: "Admin access granted" + * checkUser({ age: 20, role: "admin" }) // => "Admin access granted" * - * console.log(checkUser({ age: 20, role: "user" })) - * // Output: "Access denied" + * checkUser({ age: 20, role: "user" }) // => "Access denied" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const whenAnd: < R, const P extends ReadonlyArray | Types.PatternBase>, Ret, - Fn extends (_: Types.WhenMatch>) => Ret + Args extends Array, + Fn extends (_: Types.WhenMatch>, ...args: Args) => Ret >( ...args: [...patterns: P, f: Fn] ) => ( - self: Matcher + self: Matcher ) => Matcher< I, Types.AddWithout>>, Types.ApplyFilters>>>, A | ReturnType, - Pr + Pr, + Ret, + Args > = internal.whenAnd /** @@ -681,7 +760,7 @@ export const whenAnd: < * * **Example** (Matching on a discriminator field) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -695,12 +774,14 @@ export const whenAnd: < * Match.discriminator("type")("C", (_) => `C(${_.c})`), * Match.exhaustive * ) + * match({ type: "A", a: "ok" }) // => "A or B: A" + * match({ type: "C", c: true }) // => "C(true)" * ``` * * @see {@link discriminators} for defining several discriminator handlers at once * @see {@link discriminatorStartsWith} for matching string discriminator values by prefix * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminator: ( @@ -734,7 +815,7 @@ export const discriminator: ( * * **Example** (Matching discriminator prefixes) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -744,14 +825,14 @@ export const discriminator: ( * Match.orElse((_) => 3 as const) * ) * - * console.log(match({ type: "A" })) // 1 - * console.log(match({ type: "B" })) // 2 - * console.log(match({ type: "A.A" })) // 1 + * match({ type: "A" }) // => 1 + * match({ type: "B" }) // => 2 + * match({ type: "A.A" }) // => 1 * ``` * * @see {@link discriminator} for matching exact discriminator values * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminatorStartsWith: ( @@ -790,7 +871,7 @@ export const discriminatorStartsWith: ( * * **Example** (Mapping discriminator handlers) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -807,12 +888,14 @@ export const discriminatorStartsWith: ( * }), * Match.exhaustive * ) + * match({ type: "A", a: "ok" }) // => "ok" + * match({ type: "B", b: 42 }) // => 42 * ``` * * @see {@link discriminator} for adding one discriminator case to a matcher pipeline * @see {@link discriminatorsExhaustive} for handling every discriminator value and finalizing the matcher * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminators: ( @@ -821,10 +904,10 @@ export const discriminators: ( R, Ret, P extends - & { readonly [Tag in Types.Tags & string]?: ((_: Extract>) => Ret) | undefined } + & PartialTagHandlers & { readonly [Tag in Exclude>]: never } >( - fields: P + fields: Contextual> ) => ( self: Matcher ) => Matcher< @@ -854,7 +937,7 @@ export const discriminators: ( * * **Example** (Handling all discriminator cases) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -870,11 +953,12 @@ export const discriminators: ( * C: (c) => c.c * }) * ) + * match({ type: "C", c: true }) // => true * ``` * * @see {@link discriminators} for defining discriminator handlers without finalizing the matcher * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const discriminatorsExhaustive: ( @@ -883,10 +967,10 @@ export const discriminatorsExhaustive: ( R, Ret, P extends - & { readonly [Tag in Types.Tags & string]: (_: Extract>) => Ret } + & TagHandlers & { readonly [Tag in Exclude>]: never } >( - fields: P + fields: Contextual> ) => ( self: Matcher ) => [Pr] extends [never] ? (u: I) => Unify> : Unify> = @@ -907,7 +991,7 @@ export const discriminatorsExhaustive: ( * * **Example** (Matching a discriminated union by tag) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type Event = @@ -926,32 +1010,32 @@ export const discriminatorsExhaustive: ( * Match.exhaustive * ) * - * console.log(match({ _tag: "success", data: "Hello" })) - * // Output: "Ok!" + * match({ _tag: "success", data: "Hello" }) // => "Ok!" * - * console.log(match({ _tag: "error", error: new Error("Oops!") })) - * // Output: "Error: Oops!" + * match({ _tag: "error", error: new Error("Oops!") }) // => "Error: Oops!" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tag: < R, P extends Types.Tags<"_tag", R> & string, Ret, - Fn extends (_: Extract>) => Ret + Args extends Array, + Fn extends (_: Extract>, ...args: Args) => Ret >( ...pattern: [first: P, ...values: Array

, f: Fn] ) => ( - self: Matcher + self: Matcher ) => Matcher< I, Types.AddWithout>>, Types.ApplyFilters>>>, ReturnType | A, Pr, - Ret + Ret, + Args > = internal.tag /** @@ -966,7 +1050,7 @@ export const tag: < * * **Example** (Matching tag prefixes) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -976,12 +1060,12 @@ export const tag: < * Match.orElse((_) => 3 as const) * ) * - * console.log(match({ _tag: "A" })) // 1 - * console.log(match({ _tag: "B" })) // 2 - * console.log(match({ _tag: "A.A" })) // 1 + * match({ _tag: "A" }) // => 1 + * match({ _tag: "B" }) // => 2 + * match({ _tag: "A.A" }) // => 1 * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tagStartsWith: < @@ -1016,7 +1100,7 @@ export const tagStartsWith: < * * **Example** (Mapping tag handlers) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -1033,19 +1117,20 @@ export const tagStartsWith: < * }), * Match.exhaustive * ) + * match({ _tag: "A", a: "ok" }) // => "ok" * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tags: < R, Ret, P extends - & { readonly [Tag in Types.Tags<"_tag", R> & string]?: ((_: Extract>) => Ret) | undefined } + & PartialTagHandlers<"_tag", R, Ret> & { readonly [Tag in Exclude>]: never } >( - fields: P + fields: Contextual> ) => ( self: Matcher ) => Matcher< @@ -1071,7 +1156,7 @@ export const tags: < * * **Example** (Handling all tag cases) * - * ```ts + * ```ts import.meta.vitest * import { Match, pipe } from "effect" * * const match = pipe( @@ -1087,19 +1172,20 @@ export const tags: < * C: (c) => c.c * }) * ) + * match({ _tag: "B", b: 42 }) // => 42 * ``` * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const tagsExhaustive: < R, Ret, P extends - & { readonly [Tag in Types.Tags<"_tag", R> & string]: (_: Extract>) => Ret } + & TagHandlers<"_tag", R, Ret> & { readonly [Tag in Exclude>]: never } >( - fields: P + fields: Contextual> ) => ( self: Matcher ) => [Pr] extends [never] ? (u: I) => Unify> : Unify> = @@ -1120,7 +1206,7 @@ export const tagsExhaustive: < * * **Example** (Ignoring a specific value) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for string or number values @@ -1131,35 +1217,35 @@ export const tagsExhaustive: < * Match.orElse(() => "fallback") * ) * - * console.log(match("hello")) - * // Output: "ok" + * match("hello") // => "ok" * - * console.log(match("hi")) - * // Output: "fallback" + * match("hi") // => "fallback" * ``` * * @see {@link when} for adding a positive pattern case * - * @category Defining patterns + * @category defining patterns * @since 4.0.0 */ export const not: < R, const P extends Types.PatternPrimitive | Types.PatternBase, Ret, - Fn extends (_: Types.NotMatch) => Ret + Args extends Array, + Fn extends (_: Types.NotMatch, ...args: Args) => Ret >( pattern: P, f: Fn ) => ( - self: Matcher + self: Matcher ) => Matcher< I, Types.AddOnly>, Types.ApplyFilters>>, A | ReturnType, Pr, - Ret + Ret, + Args > = internal.not /** @@ -1176,7 +1262,7 @@ export const not: < * * **Example** (Matching non-empty strings) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processInput = Match.type() @@ -1185,19 +1271,16 @@ export const not: < * Match.orElse(() => "Input cannot be empty") * ) * - * console.log(processInput("hello")) - * // Output: "Valid input: hello" + * processInput("hello") // => "Valid input: hello" * - * console.log(processInput("")) - * // Output: "Input cannot be empty" + * processInput("") // => "Input cannot be empty" * - * console.log(processInput(" ")) - * // Output: "Valid input: " (whitespace-only strings are considered non-empty) + * processInput(" ") // => "Valid input: " * ``` * * @see {@link string} for matching any string * - * @category predicates + * @category guards * @since 4.0.0 */ export const nonEmptyString: SafeRefinement = internal.nonEmptyString @@ -1216,7 +1299,7 @@ export const nonEmptyString: SafeRefinement = internal.nonEmptySt * * **Example** (Matching literal values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const handleStatus = Match.type() @@ -1227,23 +1310,18 @@ export const nonEmptyString: SafeRefinement = internal.nonEmptySt * Match.orElse((value) => `Unknown status: ${value}`) * ) * - * console.log(handleStatus("success")) - * // Output: "Operation successful" + * handleStatus("success") // => "Operation successful" * - * console.log(handleStatus(200)) - * // Output: "Operation successful" + * handleStatus(200) // => "Operation successful" * - * console.log(handleStatus("failed")) - * // Output: "Operation failed" + * handleStatus("failed") // => "Operation failed" * - * console.log(handleStatus(0)) - * // Output: "Falsy value" + * handleStatus(0) // => "Falsy value" * - * console.log(handleStatus("pending")) - * // Output: "Unknown status: pending" + * handleStatus("pending") // => "Unknown status: pending" * ``` * - * @category predicates + * @category guards * @since 4.0.0 */ export const is: < @@ -1260,7 +1338,7 @@ export const is: < * * **Example** (Matching string values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processValue = Match.type().pipe( @@ -1270,12 +1348,12 @@ export const is: < * Match.exhaustive * ) * - * console.log(processValue("hello")) // "String: HELLO" - * console.log(processValue(42)) // "Number: 84" - * console.log(processValue(true)) // "Boolean: yes" + * processValue("hello") // => "String: HELLO" + * processValue(42) // => "Number: 84" + * processValue(true) // => "Boolean: yes" * ``` * - * @category predicates + * @category guards * @since 4.0.0 */ export const string: Predicate.Refinement = Predicate.isString @@ -1295,7 +1373,7 @@ export const string: Predicate.Refinement = Predicate.isString * * **Example** (Matching number values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const categorizeNumber = Match.type().pipe( @@ -1308,15 +1386,15 @@ export const string: Predicate.Refinement = Predicate.isString * Match.orElse(() => "Not a number type") * ) * - * console.log(categorizeNumber(42)) // "Integer: 42" - * console.log(categorizeNumber(3.14)) // "Float: 3.14" - * console.log(categorizeNumber(NaN)) // "Not a number" - * console.log(categorizeNumber("hello")) // "Not a number type" + * categorizeNumber(42) // => "Integer: 42" + * categorizeNumber(3.14) // => "Float: 3.14" + * categorizeNumber(NaN) // => "Not a number" + * categorizeNumber("hello") // => "Not a number type" * ``` * * @see {@link bigint} for matching primitive bigint values * - * @category predicates + * @category guards * @since 4.0.0 */ export const number: Predicate.Refinement = Predicate.isNumber @@ -1341,7 +1419,7 @@ export const number: Predicate.Refinement = Predicate.isNumber * * **Example** (Matching any remaining value) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const describeValue = Match.type() @@ -1353,23 +1431,19 @@ export const number: Predicate.Refinement = Predicate.isNumber * Match.exhaustive * ) * - * console.log(describeValue("hello")) - * // Output: "String: hello" + * describeValue("hello") // => "String: hello" * - * console.log(describeValue(42)) - * // Output: "Number: 42" + * describeValue(42) // => "Number: 42" * - * console.log(describeValue([1, 2, 3])) - * // Output: "Other: object" + * describeValue([1, 2, 3]) // => "Other: object" * - * console.log(describeValue(null)) - * // Output: "Other: object" + * describeValue(null) // => "Other: object" * ``` * * @see {@link defined} for matching only non-nullish values * @see {@link orElse} for providing a fallback after earlier cases * - * @category predicates + * @category guards * @since 4.0.0 */ export const any: SafeRefinement = internal.any @@ -1388,7 +1462,7 @@ export const any: SafeRefinement = internal.any * * **Example** (Matching defined values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processValue = Match.type() @@ -1397,28 +1471,22 @@ export const any: SafeRefinement = internal.any * Match.orElse(() => "Value is null or undefined") * ) * - * console.log(processValue("hello")) - * // Output: "Defined value: hello" + * processValue("hello") // => "Defined value: hello" * - * console.log(processValue(42)) - * // Output: "Defined value: 42" + * processValue(42) // => "Defined value: 42" * - * console.log(processValue(0)) - * // Output: "Defined value: 0" + * processValue(0) // => "Defined value: 0" * - * console.log(processValue("")) - * // Output: "Defined value: " + * processValue("") // => "Defined value: " * - * console.log(processValue(null)) - * // Output: "Value is null or undefined" + * processValue(null) // => "Value is null or undefined" * - * console.log(processValue(undefined)) - * // Output: "Value is null or undefined" + * processValue(undefined) // => "Value is null or undefined" * ``` * * @see {@link any} for matching every value without excluding nullish inputs * - * @category predicates + * @category guards * @since 4.0.0 */ export const defined: (u: A) => u is A & {} = internal.defined @@ -1437,7 +1505,7 @@ export const defined: (u: A) => u is A & {} = internal.defined * * **Example** (Matching boolean values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const describeTruthiness = Match.type().pipe( @@ -1451,15 +1519,15 @@ export const defined: (u: A) => u is A & {} = internal.defined * Match.orElse(() => "Some other truthy value") * ) * - * console.log(describeTruthiness(true)) // "Definitely true" - * console.log(describeTruthiness(false)) // "Definitely false" - * console.log(describeTruthiness(0)) // "Falsy number" - * console.log(describeTruthiness(1)) // "Some other truthy value" + * describeTruthiness(true) // => "Definitely true" + * describeTruthiness(false) // => "Definitely false" + * describeTruthiness(0) // => "Falsy number" + * describeTruthiness(1) // => "Some other truthy value" * ``` * * @see {@link is} for matching specific literal boolean values * - * @category predicates + * @category guards * @since 4.0.0 */ export const boolean: Predicate.Refinement = Predicate.isBoolean @@ -1481,7 +1549,7 @@ export { * @see {@link defined} for matching non-nullish values * @see {@link is} for matching literal values * - * @category predicates + * @category guards * @since 4.0.0 */ _undefined as undefined @@ -1504,7 +1572,7 @@ export { * @see {@link defined} for matching non-nullish values * @see {@link is} for matching literal values * - * @category predicates + * @category guards * @since 4.0.0 */ _null as null @@ -1524,7 +1592,7 @@ export { * * **Example** (Matching bigint values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processLargeNumber = Match.type().pipe( @@ -1538,15 +1606,15 @@ export { * Match.orElse(() => "Not a numeric type") * ) * - * console.log(processLargeNumber(123n)) // "BigInt: 123" - * console.log(processLargeNumber(9007199254740992n)) // "Large integer: 9007199254740992" - * console.log(processLargeNumber(123)) // "Regular number: 123" - * console.log(processLargeNumber("123")) // "Not a numeric type" + * processLargeNumber(123n) // => "BigInt: 123" + * processLargeNumber(9007199254740992n) // => "Large integer: 9007199254740992" + * processLargeNumber(123) // => "Regular number: 123" + * processLargeNumber("123") // => "Not a numeric type" * ``` * * @see {@link number} for matching primitive number values * - * @category predicates + * @category guards * @since 4.0.0 */ export const bigint: Predicate.Refinement = Predicate.isBigInt @@ -1562,7 +1630,7 @@ export const bigint: Predicate.Refinement = Predicate.isBigInt * * **Example** (Matching symbol values) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const mySymbol = Symbol("my-symbol") @@ -1579,12 +1647,12 @@ export const bigint: Predicate.Refinement = Predicate.isBigInt * Match.orElse(() => "Not a symbol") * ) * - * console.log(handleSymbol(mySymbol)) // "Symbol with description: my-symbol" - * console.log(handleSymbol(Symbol())) // "Symbol without description" - * console.log(handleSymbol("string")) // "Not a symbol" + * handleSymbol(mySymbol) // => "Symbol with description: my-symbol" + * handleSymbol(Symbol()) // => "Symbol without description" + * handleSymbol("string") // => "Not a symbol" * ``` * - * @category predicates + * @category guards * @since 4.0.0 */ export const symbol: Predicate.Refinement = Predicate.isSymbol @@ -1604,7 +1672,7 @@ export const symbol: Predicate.Refinement = Predicate.isSymbol * * **Example** (Matching Date instances) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const processDateValue = Match.type().pipe( @@ -1618,15 +1686,15 @@ export const symbol: Predicate.Refinement = Predicate.isSymbol * Match.orElse(() => "Not a date-related value") * ) * - * console.log(processDateValue(new Date("2024-01-01"))) // "Date: 2024-01-01" - * console.log(processDateValue(new Date("invalid"))) // "Invalid date" - * console.log(processDateValue("2024-01-01")) // "Date string: 2024-01-01" - * console.log(processDateValue(1704067200000)) // "Not a date-related value" + * processDateValue(new Date("2024-01-01")) // => "Date: 2024-01-01" + * processDateValue(new Date("invalid")) // => "Invalid date" + * processDateValue("2024-01-01") // => "Date string: 2024-01-01" + * processDateValue(1704067200000) // => "Not a date-related value" * ``` * * @see {@link instanceOf} for matching instances of any constructor * - * @category predicates + * @category guards * @since 4.0.0 */ export const date: Predicate.Refinement = Predicate.isDate @@ -1647,7 +1715,7 @@ export const date: Predicate.Refinement = Predicate.isDate * * **Example** (Matching record objects) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const analyzeValue = Match.type().pipe( @@ -1663,15 +1731,15 @@ export const date: Predicate.Refinement = Predicate.isDate * Match.orElse(() => "Not an object") * ) * - * console.log(analyzeValue({ name: "Alice", age: 30 })) // "Object with 2 properties: [name, age]" - * console.log(analyzeValue([1, 2, 3])) // "Array with 3 items" - * console.log(analyzeValue(null)) // "Not an object" - * console.log(analyzeValue("hello")) // "Not an object" + * analyzeValue({ name: "Alice", age: 30 }) // => "Object with 2 properties: [name, age]" + * analyzeValue([1, 2, 3]) // => "Array with 3 items" + * analyzeValue(null) // => "Not an object" + * analyzeValue("hello") // => "Not an object" * ``` * * @see {@link instanceOf} for matching a specific constructor * - * @category predicates + * @category guards * @since 4.0.0 */ export const record: Predicate.Refinement = Predicate.isObject @@ -1691,7 +1759,7 @@ export const record: Predicate.Refinement `Other: ${typeof value}`) * ) * - * console.log(handleValue(new CustomError("Failed", 404))) // "Custom error: Failed (code: 404)" - * console.log(handleValue(new Error("Generic error"))) // "Standard error: Generic error" - * console.log(handleValue([1, 2, 3])) // "Array with 3 items" - * console.log(handleValue(new Map([["count", 1]]))) // "Map with 1 entries" + * handleValue(new CustomError("Failed", 404)) // => "Custom error: Failed (code: 404)" + * handleValue(new Error("Generic error")) // => "Standard error: Generic error" + * handleValue([1, 2, 3]) // => "Array with 3 items" + * handleValue(new Map([["count", 1]])) // => "Map with 1 entries" * ``` * * @see {@link instanceOfUnsafe} for constructor matching without the same type-safety guarantee * @see {@link record} for matching broad non-null, non-array objects * - * @category predicates + * @category guards * @since 4.0.0 */ export const instanceOf: any>( @@ -1752,7 +1820,7 @@ export const instanceOf: any>( * * **Example** (Matching class instances unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * class CustomError extends Error { @@ -1770,11 +1838,12 @@ export const instanceOf: any>( * }), * Match.orElse(() => "Not a CustomError") * ) + * handleError(new CustomError("failed", 500)) // => "Custom error 500: failed" * ``` * * @see {@link instanceOf} for type-safe constructor matching * - * @category predicates + * @category guards * @since 4.0.0 */ export const instanceOfUnsafe: any>( @@ -1797,7 +1866,7 @@ export const instanceOfUnsafe: any>( * * **Example** (Providing a default value when no patterns match) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for string or number values @@ -1808,11 +1877,9 @@ export const instanceOfUnsafe: any>( * Match.orElse(() => "fallback") * ) * - * console.log(match("a")) - * // Output: "ok" + * match("a") // => "ok" * - * console.log(match("b")) - * // Output: "fallback" + * match("b") // => "fallback" * ``` * * @see {@link option} for finalizing unmatched input as `Option.none` @@ -1822,11 +1889,13 @@ export const instanceOfUnsafe: any>( * @category completion * @since 4.0.0 */ -export const orElse: Ret>( +export const orElse: , F extends (_: RA, ...args: Args) => Ret>( f: F ) => ( - self: Matcher -) => [Pr] extends [never] ? (input: I) => Unify | A> : Unify | A> = internal.orElse + self: Matcher +) => [Pr] extends [never] ? [Args] extends [[]] ? (input: I) => Unify | A> + : (...args: Args) => Unify | A> + : Unify | A> = internal.orElse // TODO(4.0): Rename to "orThrow"? Like Result.getOrThrow /** @@ -1849,7 +1918,7 @@ export const orElse: Ret>( * * **Example** (Throwing on unmatched input) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * const strictMatcher = Match.type<"a" | "b">().pipe( @@ -1859,8 +1928,8 @@ export const orElse: Ret>( * Match.orElseAbsurd * ) * - * console.log(strictMatcher("a")) // "Found A" - * console.log(strictMatcher("b")) // "Found B" + * strictMatcher("a") // => "Found A" + * strictMatcher("b") // => "Found B" * * // This would throw an error at runtime: * // strictMatcher("c" as any) // throws @@ -1872,9 +1941,10 @@ export const orElse: Ret>( * @category completion * @since 4.0.0 */ -export const orElseAbsurd: ( - self: Matcher -) => [Pr] extends [never] ? (input: I) => Unify : Unify = internal.orElseAbsurd +export const orElseAbsurd: >( + self: Matcher +) => [Pr] extends [never] ? [Args] extends [[]] ? (input: I) => Unify : (...args: Args) => Unify : Unify = + internal.orElseAbsurd /** * Wraps the match result in a `Result`, distinguishing matched and unmatched @@ -1893,7 +1963,7 @@ export const orElseAbsurd: ( * * **Example** (Extracting a user role with `Match.result`) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type User = { readonly role: "admin" | "editor" | "viewer" } @@ -1905,19 +1975,19 @@ export const orElseAbsurd: ( * Match.result // Wrap the result in an Result * ) * - * console.log(getRole({ role: "admin" })) - * // Output: { _id: 'Result', _tag: 'Ok', ok: 'Has full access' } + * getRole({ role: "admin" })._tag // => "Success" * - * console.log(getRole({ role: "viewer" })) - * // Output: { _id: 'Result', _tag: 'Err', err: { role: 'viewer' } } + * getRole({ role: "viewer" })._tag // => "Failure" * ``` * * @category completion * @since 4.0.0 */ -export const result: ( - self: Matcher -) => [Pr] extends [never] ? (input: I) => Result.Result, R> : Result.Result, R> = internal.result +export const result: >( + self: Matcher +) => [Pr] extends [never] ? [Args] extends [[]] ? (input: I) => Result.Result, R> + : (...args: Args) => Result.Result, R> + : Result.Result, R> = internal.result /** * Wraps the match result in an `Option`, representing an optional match. @@ -1939,7 +2009,7 @@ export const result: ( * * **Example** (Extracting a user role with `Match.option`) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type User = { readonly role: "admin" | "editor" | "viewer" } @@ -1951,11 +2021,9 @@ export const result: ( * Match.option // Wrap the result in an Option * ) * - * console.log(getRole({ role: "admin" })) - * // Output: { _id: 'Option', _tag: 'Some', value: 'Has full access' } + * getRole({ role: "admin" })._tag // => "Some" * - * console.log(getRole({ role: "viewer" })) - * // Output: { _id: 'Option', _tag: 'None' } + * getRole({ role: "viewer" })._tag // => "None" * ``` * * @see {@link result} for preserving unmatched input as a `Result` failure @@ -1964,9 +2032,11 @@ export const result: ( * @category completion * @since 4.0.0 */ -export const option: ( - self: Matcher -) => [Pr] extends [never] ? (input: I) => Option.Option> : Option.Option> = internal.option +export const option: >( + self: Matcher +) => [Pr] extends [never] ? [Args] extends [[]] ? (input: I) => Option.Option> + : (...args: Args) => Option.Option> + : Option.Option> = internal.option /** * Completes a matcher that handles every remaining input case. @@ -1983,7 +2053,7 @@ export const option: ( * * **Example** (Ensuring all cases are covered) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Create a matcher for string or number values @@ -2000,11 +2070,12 @@ export const option: ( * @category completion * @since 4.0.0 */ -export const exhaustive: ( - self: Matcher -) => [Pr] extends [never] ? (u: I) => Unify : Unify = internal.exhaustive +export const exhaustive: >( + self: Matcher +) => [Pr] extends [never] ? [Args] extends [[]] ? (u: I) => Unify : (...args: Args) => Unify : Unify = + internal.exhaustive -const SafeRefinementId = "~effect/match/Match/SafeRefinement" +const SafeRefinementId = "~effect/Match/SafeRefinement" /** * A safe refinement that narrows types without runtime errors. @@ -2017,7 +2088,7 @@ const SafeRefinementId = "~effect/match/Match/SafeRefinement" * * **Example** (Using safe refinements) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Built-in safe refinements @@ -2028,10 +2099,10 @@ const SafeRefinementId = "~effect/match/Match/SafeRefinement" * Match.orElse(() => "Undefined or null") * ) * - * console.log(processValue("hello")) // "HELLO" - * console.log(processValue(21)) // 42 - * console.log(processValue(true)) // "Defined: true" - * console.log(processValue(null)) // "Undefined or null" + * processValue("hello") // => "HELLO" + * processValue(21) // => 42 + * processValue(true) // => "Defined: true" + * processValue(null) // => "Undefined or null" * ``` * * @category models @@ -2068,7 +2139,7 @@ export declare namespace Types { * * **Example** (Computing matched types) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // WhenMatch computes the narrowed type after pattern matching @@ -2085,7 +2156,7 @@ export declare namespace Types { * // Result: { type: "user"; name: string } * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type WhenMatch = @@ -2113,7 +2184,7 @@ export declare namespace Types { * * **Example** (Computing unmatched types) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // NotMatch computes what remains after exclusion @@ -2127,7 +2198,7 @@ export declare namespace Types { * // Result: "b" | "c" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type NotMatch = Exclude>> @@ -2146,7 +2217,7 @@ export declare namespace Types { * * **Example** (Resolving match patterns) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // PForMatch resolves patterns to their matched types @@ -2157,7 +2228,7 @@ export declare namespace Types { * // Result: { name: string } * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type PForMatch

= [ResolvePred

] extends [infer X] ? X @@ -2174,7 +2245,7 @@ export declare namespace Types { * * **Example** (Computing excluded patterns) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // PForExclude computes what to exclude from type operations @@ -2185,7 +2256,7 @@ export declare namespace Types { * // Used internally to filter out admin objects * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type PForExclude

= [SafeRefinementR>] extends [infer X] ? X @@ -2239,7 +2310,7 @@ export declare namespace Types { * * **Example** (Describing complex object patterns) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // PatternBase enables complex object patterns @@ -2251,7 +2322,7 @@ export declare namespace Types { * // Allows: { name?: string | Predicate, age?: number | Predicate, ... } * * // Example usage: - * Match.value({ name: "Alice", age: 30, role: "admin" as const }).pipe( + * const result = Match.value({ name: "Alice", age: 30, role: "admin" as const }).pipe( * Match.when( * { age: (n: number) => n >= 18, role: "admin" }, * (user: { name: string; age: number; role: "admin" }) => @@ -2259,9 +2330,10 @@ export declare namespace Types { * ), * Match.orElse(() => "Not an adult admin") * ) + * result // => "Admin: Alice" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type PatternBase = A extends ReadonlyArray ? ReadonlyArray | PatternPrimitive @@ -2279,7 +2351,7 @@ export declare namespace Types { * literal values, and safe refinements. These are the atomic patterns that * can be composed into more complex matching logic. * - * @category types + * @category utility types * @since 4.0.0 */ export type PatternPrimitive = PredicateA | A | SafeRefinement @@ -2295,18 +2367,19 @@ export declare namespace Types { * * **Example** (Tracking excluded types) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Without is used internally when you write: - * Match.type().pipe( + * const match = Match.type().pipe( * Match.not(Match.string, (value) => `not string: ${value}`), * // At this point, type system uses Without to track exclusion * Match.orElse(() => "was a string") * ) + * match(42) // => "not string: 42" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export interface Without { @@ -2325,18 +2398,19 @@ export declare namespace Types { * * **Example** (Tracking included types) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // Only is used internally when you write: - * Match.type().pipe( + * const match = Match.type().pipe( * Match.when(Match.string, (s) => `string: ${s}`), * // At this point, type system uses Only for the match * Match.orElse((value) => `not string: ${value}`) * ) + * match("ok") // => "string: ok" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export interface Only { @@ -2355,19 +2429,20 @@ export declare namespace Types { * * **Example** (Accumulating excluded types) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // AddWithout is used when combining multiple exclusions: - * Match.type().pipe( + * const match = Match.type().pipe( * Match.not(Match.string, () => "not string"), * Match.not(Match.number, () => "not number"), * // Type system uses AddWithout to combine exclusions * Match.orElse(() => "was string or number") * ) + * match(true) // => "not string" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type AddWithout = [A] extends [Without] ? Without @@ -2385,18 +2460,19 @@ export declare namespace Types { * * **Example** (Refining included types) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * // AddOnly is used when refining positive matches: - * Match.type<{ type: "user" | "admin"; name: string }>().pipe( + * const match = Match.type<{ type: "user" | "admin"; name: string }>().pipe( * Match.when({ type: "admin" }, (admin) => admin.name), * // Type system uses AddOnly to refine the constraint * Match.orElse(() => "not admin") * ) + * match({ type: "admin", name: "Alice" }) // => "Alice" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type AddOnly = [A] extends [Without] ? [X] extends [WX] ? never @@ -2416,7 +2492,7 @@ export declare namespace Types { * * **Example** (Applying accumulated filters) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * // ApplyFilters computes the final narrowed type: @@ -2433,7 +2509,7 @@ export declare namespace Types { * // Result: number | boolean * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type ApplyFilters = A extends Only ? X @@ -2451,7 +2527,7 @@ export declare namespace Types { * * **Example** (Extracting discriminator tags) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * type Events = @@ -2470,7 +2546,7 @@ export declare namespace Types { * // Result: "user" | "admin" * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type Tags = P extends Record ? X : never @@ -2486,7 +2562,7 @@ export declare namespace Types { * * **Example** (Converting arrays to intersections) * - * ```ts + * ```ts import.meta.vitest * import type { Match } from "effect" * * type Combined = Match.Types.ArrayToIntersection<[ @@ -2502,7 +2578,7 @@ export declare namespace Types { * // for advanced pattern matching scenarios * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type ArrayToIntersection> = T.UnionToIntersection< @@ -2520,7 +2596,7 @@ export declare namespace Types { * * **Example** (Extracting matched types) * - * ```ts + * ```ts import.meta.vitest * import { Match } from "effect" * * type StringExtract = Match.Types.ExtractMatch< @@ -2540,7 +2616,7 @@ export declare namespace Types { * // ^^^ s is correctly typed as string * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type ExtractMatch = [ExtractAndNarrow] extends [infer EI] ? EI diff --git a/packages/effect/src/Metric.ts b/packages/effect/src/Metric.ts index d1a1cd4d2..14eb02edc 100644 --- a/packages/effect/src/Metric.ts +++ b/packages/effect/src/Metric.ts @@ -19,6 +19,7 @@ import type { Exit } from "./Exit.ts" import { constUndefined, dual } from "./Function.ts" import * as InternalEffect from "./internal/effect.ts" import * as InternalMetric from "./internal/metric.ts" +import * as InternalRecord from "./internal/record.ts" import * as Layer from "./Layer.ts" import * as Order from "./Order.ts" import type { Pipeable } from "./Pipeable.ts" @@ -48,12 +49,8 @@ import type { Contravariant, Covariant } from "./Types.ts" * * **Example** (Using multiple metric types) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricExample extends Data.TaggedError("MetricExample")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of metrics @@ -99,6 +96,13 @@ import type { Contravariant, Covariant } from "./Types.ts" * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [ + * result.counter.state.count, + * result.gauge.state.value, + * result.frequency.state.occurrences.get("200") + * ] // => [1, 128, 1] * ``` * * @category models @@ -127,12 +131,8 @@ export interface Metric extends Pipeable { * * **Example** (Using counter metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class CounterInterfaceError extends Data.TaggedError("CounterInterfaceError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of counters @@ -178,9 +178,12 @@ export interface Metric extends Pipeable { * bytes: { count: bytesState.count, incremental: bytesState.incremental } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [result.requests.count, result.bytes.count] // => [6, 1024n] * ``` * - * @category metrics + * @category models * @since 2.0.0 */ export interface Counter extends Metric> {} @@ -190,12 +193,8 @@ export interface Counter extends Metric {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of counters @@ -220,7 +219,6 @@ export interface Counter extends Metric = yield* Metric.value( * byteCounter * ) - * * // CounterState contains: * // - count: current count value (number or bigint based on counter type) * // - incremental: whether counter only allows increases @@ -240,9 +238,12 @@ export interface Counter extends Metric [3, 3, 1024000n] * ``` * - * @category Counter + * @category models * @since 4.0.0 */ export interface CounterState { @@ -261,32 +262,16 @@ export interface CounterState { * * **Example** (Using frequency metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class FrequencyInterfaceError - * extends Data.TaggedError("FrequencyInterfaceError")<{ - * readonly operation: string - * }> - * {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * // Function that accepts any Frequency metric - * const logFrequencyMetric = (freq: Metric.Frequency) => + * const analyzeFrequencyMetric = (freq: Metric.Frequency) => * Effect.gen(function*() { * const state = yield* Metric.value(freq) * - * yield* Effect.log(`Frequency Metric: ${freq.id}`) - * yield* Effect.log(`Description: ${freq.description ?? "No description"}`) - * yield* Effect.log(`Type: ${freq.type}`) // "Frequency" - * * // Access the frequency state * const occurrences: ReadonlyMap = state.occurrences - * yield* Effect.log(`Total unique values: ${occurrences.size}`) - * - * // Iterate through all occurrences - * for (const [value, count] of occurrences) { - * yield* Effect.log(` "${value}": ${count} occurrences`) - * } * * // Find most frequent value * let maxCount = 0 @@ -323,14 +308,16 @@ export interface CounterState { * yield* Metric.update(userActions, "login") * * // Use the function with different frequency metrics - * const statusAnalysis = yield* logFrequencyMetric(statusCodes) - * const actionAnalysis = yield* logFrequencyMetric(userActions) - * + * const statusAnalysis = yield* analyzeFrequencyMetric(statusCodes) + * const actionAnalysis = yield* analyzeFrequencyMetric(userActions) * return { statusAnalysis, actionAnalysis } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.statusAnalysis.mostFrequent, result.actionAnalysis.mostFrequent] // => ["200", "login"] * ``` * - * @category metrics + * @category models * @since 2.0.0 */ export interface Frequency extends Metric {} @@ -340,12 +327,8 @@ export interface Frequency extends Metric {} * * **Example** (Reading frequency state) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class FrequencyStateError extends Data.TaggedError("FrequencyStateError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create frequency metrics for different categories @@ -393,7 +376,6 @@ export interface Frequency extends Metric {} * * const topStatus = getMostFrequent(statusState.occurrences) * const topAction = getMostFrequent(actionState.occurrences) - * * return { * statusCodes: { * totalResponses: Array.from(statusState.occurrences.values()).reduce( @@ -413,9 +395,13 @@ export interface Frequency extends Metric {} * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const mostCommon = [result.statusCodes.mostCommon, result.userActions.mostCommon] + * mostCommon.map(({ key, count }) => [key, count]) // => [["200", 3], ["click", 3]] * ``` * - * @category metrics + * @category models * @since 4.0.0 */ export interface FrequencyState { @@ -432,12 +418,8 @@ export interface FrequencyState { * * **Example** (Using gauge metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class GaugeInterfaceError extends Data.TaggedError("GaugeInterfaceError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of gauges @@ -467,7 +449,6 @@ export interface FrequencyState { * const diskState: Metric.GaugeState = yield* Metric.value( * diskSpaceGauge * ) - * * // Gauge state contains: * // - value: current instantaneous value * @@ -476,9 +457,12 @@ export interface FrequencyState { * disk: { currentValue: diskState.value } // 5000000000n * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.memory.currentValue, result.disk.currentValue] // => [704, 5000000000n] * ``` * - * @category metrics + * @category models * @since 2.0.0 */ export interface Gauge extends Metric> {} @@ -488,12 +472,8 @@ export interface Gauge extends Metric {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of gauges @@ -529,7 +509,6 @@ export interface Gauge extends Metric = yield* Metric.value( * queueSizeGauge * ) - * * // GaugeState contains: * // - value: current instantaneous value (number or bigint based on gauge type) * @@ -545,9 +524,13 @@ export interface Gauge extends Metric [23.1, 5000000000n, 15] * ``` * - * @category metrics + * @category models * @since 4.0.0 */ export interface GaugeState { @@ -564,14 +547,8 @@ export interface GaugeState { * * **Example** (Using histogram metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class HistogramInterfaceError - * extends Data.TaggedError("HistogramInterfaceError")<{ - * readonly operation: string - * }> - * {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create histograms with different boundary strategies @@ -579,7 +556,7 @@ export interface GaugeState { * "http_response_time_ms", * { * description: "HTTP response time distribution in milliseconds", - * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 20 }) // 0, 50, 100, ..., 950 + * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 20 }) // 50, 100, ..., 900, Infinity * } * ) * @@ -612,7 +589,6 @@ export interface GaugeState { * const fileSizeState: Metric.HistogramState = yield* Metric.value( * fileSizeHistogram * ) - * * // Histogram state contains: * // - buckets: Array of [boundary, cumulativeCount] pairs * // - count: total number of observations @@ -636,9 +612,13 @@ export interface GaugeState { * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.responseTime.totalRequests, result.responseTime.totalTime, result.fileSize.totalBytes] + * values // => [4, 445, 118] * ``` * - * @category metrics + * @category models * @since 2.0.0 */ export interface Histogram extends Metric {} @@ -648,18 +628,14 @@ export interface Histogram extends Metric {} * * **Example** (Reading histogram state) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class HistogramStateError extends Data.TaggedError("HistogramStateError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create histogram with linear boundaries * const responseTimeHistogram = Metric.histogram("api_response_time_ms", { * description: "API response time distribution", - * boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 }) // 0, 100, 200, ..., 900 + * boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 }) // 100, 200, ..., 800, Infinity * }) * * // Record observations @@ -706,7 +682,6 @@ export interface Histogram extends Metric {} * } * * const bucketAnalysis = analyzeBuckets(state.buckets) - * * return { * responseTime: { * totalRequests: state.count, // 5 @@ -723,9 +698,14 @@ export interface Histogram extends Metric {} * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const stats = result.responseTime + * const values = [stats.totalRequests, stats.fastestResponse, stats.slowestResponse, stats.totalTime] + * values // => [5, 50, 750, 1295] * ``` * - * @category metrics + * @category models * @since 4.0.0 */ export interface HistogramState { @@ -747,12 +727,8 @@ export interface HistogramState { * * **Example** (Using summary metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class SummaryInterfaceError extends Data.TaggedError("SummaryInterfaceError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create summaries with different quantile configurations @@ -811,7 +787,6 @@ export interface HistogramState { * const median = getQuantileValue(responseTimeState.quantiles, 0.5) * const p95 = getQuantileValue(responseTimeState.quantiles, 0.95) * const p99 = getQuantileValue(responseTimeState.quantiles, 0.99) - * * return { * responseTime: { * totalRequests: responseTimeState.count, // 5 @@ -829,9 +804,13 @@ export interface HistogramState { * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [result.responseTime.totalRequests, result.responseTime.totalTime, result.requestSize.totalRequests] + * counts // => [5, 1461, 3] * ``` * - * @category metrics + * @category models * @since 2.0.0 */ export interface Summary extends Metric {} @@ -841,12 +820,8 @@ export interface Summary extends Metric {} * * **Example** (Reading summary state) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class SummaryStateError extends Data.TaggedError("SummaryStateError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create summary with specific quantiles @@ -889,7 +864,6 @@ export interface Summary extends Metric {} * } * * const quantileValues = extractQuantiles(state.quantiles) - * * return { * latencyAnalysis: { * totalRequests: state.count, // 7 @@ -911,9 +885,14 @@ export interface Summary extends Metric {} * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const analysis = result.latencyAnalysis + * const values = [analysis.totalRequests, analysis.fastestResponse, analysis.slowestResponse, analysis.totalLatency] + * values // => [7, 45, 890, 1879] * ``` * - * @category metrics + * @category models * @since 4.0.0 */ export interface SummaryState { @@ -930,12 +909,8 @@ export interface SummaryState { * * **Example** (Collecting application metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricsError extends Data.TaggedError("MetricsError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of metrics @@ -965,6 +940,9 @@ export interface SummaryState { * frequency: frequencyValue * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.counter.count, result.gauge.value] // => [1, 12] * ``` * * @since 2.0.0 @@ -975,78 +953,28 @@ export declare namespace Metric { * * **Example** (Inspecting metric types) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricTypeError extends Data.TaggedError("MetricTypeError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Metric } from "effect" * - * const program = Effect.gen(function*() { - * // Create different metric types - * const counter = Metric.counter("requests_total") - * const gauge = Metric.gauge("cpu_usage") - * const frequency = Metric.frequency("status_codes") - * const histogram = Metric.histogram("response_time", { + * const metrics: ReadonlyArray> = [ + * Metric.counter("requests_total"), + * Metric.gauge("cpu_usage"), + * Metric.frequency("status_codes"), + * Metric.histogram("response_time", { * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 10 }) - * }) - * const summary = Metric.summary("latency", { + * }), + * Metric.summary("latency", { * maxAge: "5 minutes", * maxSize: 1000, * quantiles: [0.5, 0.95, 0.99] * }) + * ] * - * // Function that checks metric type - * const getMetricInfo = (metric: Metric.Metric) => ({ - * name: metric.id, - * type: metric.type - * }) - * - * // Get type information for each metric - * const counterInfo = getMetricInfo(counter) // { name: "requests_total", type: "Counter" } - * const gaugeInfo = getMetricInfo(gauge) // { name: "cpu_usage", type: "Gauge" } - * const frequencyInfo = getMetricInfo(frequency) // { name: "status_codes", type: "Frequency" } - * const histogramInfo = getMetricInfo(histogram) // { name: "response_time", type: "Histogram" } - * const summaryInfo = getMetricInfo(summary) // { name: "latency", type: "Summary" } - * - * // Pattern match on metric type - * const describeMetric = (type: string): string => { - * switch (type) { - * case "Counter": - * return "Cumulative values that increase over time" - * case "Gauge": - * return "Instantaneous values that can go up or down" - * case "Frequency": - * return "Counts of discrete string occurrences" - * case "Histogram": - * return "Distribution of values across buckets" - * case "Summary": - * return "Quantile calculations over time windows" - * default: - * return "Unknown metric type" - * } - * } - * - * return { - * metrics: [ - * counterInfo, - * gaugeInfo, - * frequencyInfo, - * histogramInfo, - * summaryInfo - * ], - * descriptions: { - * Counter: describeMetric("Counter"), - * Gauge: describeMetric("Gauge"), - * Frequency: describeMetric("Frequency"), - * Histogram: describeMetric("Histogram"), - * Summary: describeMetric("Summary") - * } - * } - * }) + * const types: ReadonlyArray = metrics.map((metric) => metric.type) + * const actual = types // => ["Counter", "Gauge", "Frequency", "Histogram", "Summary"] * ``` * - * @category types + * @category models * @since 4.0.0 */ export type Type = "Counter" | "Frequency" | "Gauge" | "Histogram" | "Summary" @@ -1056,12 +984,8 @@ export declare namespace Metric { * * **Example** (Providing attributes in different formats) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class AttributesError extends Data.TaggedError("AttributesError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Different ways to specify attributes @@ -1110,19 +1034,22 @@ export declare namespace Metric { * // Both formats result in the same internal representation * const normalizedObject = normalizeAttributes(attributesAsObject) * const normalizedArray = normalizeAttributes(attributesAsArray) - * * return { * attributeFormats: { * object: normalizedObject, // { service: "api", environment: "production", version: "1.2.3" } - * array: normalizedArray, // { service: "api", environment: "production", version: "1.2.3" } - * areEqual: - * JSON.stringify(normalizedObject) === JSON.stringify(normalizedArray) // true + * array: normalizedArray // { service: "api", environment: "production", version: "1.2.3" } * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const objectAttributes = result.attributeFormats.object + * const arrayAttributes = result.attributeFormats.array + * const sameAttributes = [objectAttributes, arrayAttributes] + * sameAttributes // => [{ service: "api", environment: "production", version: "1.2.3" }, { service: "api", environment: "production", version: "1.2.3" }] * ``` * - * @category types + * @category models * @since 4.0.0 */ export type Attributes = AttributeSet | ReadonlyArray<[string, string]> @@ -1132,12 +1059,8 @@ export declare namespace Metric { * * **Example** (Combining metric attribute sets) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class AttributeSetError extends Data.TaggedError("AttributeSetError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Define attribute sets for different contexts @@ -1198,9 +1121,12 @@ export declare namespace Metric { * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const validation = [result.attributes.isValid, result.attributes.totalKeys] // => [true, 9] * ``` * - * @category types + * @category models * @since 4.0.0 */ export type AttributeSet = Readonly> @@ -1210,7 +1136,7 @@ export declare namespace Metric { * * **Example** (Extracting metric input types) * - * ```ts + * ```ts import.meta.vitest * import { Metric } from "effect" * * // Create various metric types @@ -1248,9 +1174,11 @@ export declare namespace Metric { * // Metric.update(numberCounter, "abc") // ✗ Type error * // Metric.update(stringFrequency, "ok") // ✓ Valid (string) * // Metric.update(stringFrequency, 404) // ✗ Type error + * const metricIds = metrics.map(({ id, type }) => `${id}:${type}`) + * metricIds // => ["requests:Counter", "bytes:Counter", "status_codes:Frequency", "cpu_usage:Gauge", "response_time:Histogram"] * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type Input = A extends Metric ? _Input @@ -1261,7 +1189,7 @@ export declare namespace Metric { * * **Example** (Extracting metric state types) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * // Create various metric types @@ -1299,7 +1227,6 @@ export declare namespace Metric { * const frequencyState = yield* Metric.value(statusFrequency) * const histogramState = yield* Metric.value(responseHistogram) * const summaryState = yield* Metric.value(latencySummary) - * * return { * counter: { count: counterState.count }, // { count: 10 } * gauge: { value: gaugeState.value }, // { value: 85.5 } @@ -1308,9 +1235,13 @@ export declare namespace Metric { * summary: { observations: summaryState.count } // { observations: 1 } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.counter.count, result.gauge.value, result.frequency.uniqueValues] + * values // => [10, 85.5, 1] * ``` * - * @category types + * @category utility types * @since 4.0.0 */ export type State = A extends Metric ? _State @@ -1321,12 +1252,8 @@ export declare namespace Metric { * * **Example** (Using metric hooks) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class HooksError extends Data.TaggedError("HooksError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create a counter metric @@ -1354,9 +1281,12 @@ export declare namespace Metric { * isIncremental: state.incremental // false * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const state = result // => { currentCount: 6, isIncremental: false } * ``` * - * @category interfaces + * @category models * @since 4.0.0 */ export interface Hooks { @@ -1370,12 +1300,8 @@ export declare namespace Metric { * * **Example** (Inspecting metric metadata) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetadataError extends Data.TaggedError("MetadataError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create metrics with different configurations @@ -1420,9 +1346,12 @@ export declare namespace Metric { * } * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const types = [result.counter.type, result.gauge.type, result.frequency.type] // => ["Counter", "Gauge", "Frequency"] * ``` * - * @category interfaces + * @category models * @since 4.0.0 */ export interface Metadata { @@ -1438,12 +1367,8 @@ export declare namespace Metric { * * **Example** (Inspecting metric snapshot protocols) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class SnapshotProtoError extends Data.TaggedError("SnapshotProtoError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create and update metrics @@ -1493,9 +1418,12 @@ export declare namespace Metric { * null * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [result.counter?.count, result.histogram?.observations] // => [25, 2] * ``` * - * @category interfaces + * @category models * @since 4.0.0 */ export interface SnapshotProto { @@ -1511,12 +1439,8 @@ export declare namespace Metric { * * **Example** (Analyzing metric snapshots) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class SnapshotError extends Data.TaggedError("SnapshotError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create different types of metrics @@ -1570,9 +1494,12 @@ export declare namespace Metric { * analysis * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const types = result.metricTypes // => ["Counter", "Gauge", "Frequency", "Histogram", "Summary"] * ``` * - * @category types + * @category models * @since 4.0.0 */ export type Snapshot = @@ -1588,12 +1515,8 @@ export declare namespace Metric { * * **Example** (Accessing the current metric attributes key) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class AttributesKeyError extends Data.TaggedError("AttributesKeyError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // The key is used internally by the Effect runtime to manage metric attributes @@ -1627,9 +1550,12 @@ export declare namespace Metric { * isConstant: key === "effect/Metric/CurrentMetricAttributes" // true * } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const key = result // => { keyValue: "effect/Metric/CurrentMetricAttributes", keyType: "string", isConstant: true } * ``` * - * @category references + * @category constants * @since 4.0.0 */ export const CurrentMetricAttributesKey = "effect/Metric/CurrentMetricAttributes" as const @@ -1651,17 +1577,12 @@ export const CurrentMetricAttributesKey = "effect/Metric/CurrentMetricAttributes * * **Example** (Providing current metric attributes) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class AttributesError extends Data.TaggedError("AttributesError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Access current metric attributes - * const attributes = yield* Metric.CurrentMetricAttributes - * console.log("Current attributes:", attributes) + * yield* Metric.CurrentMetricAttributes * * // Set new attributes context * const newAttributes = { service: "api", version: "1.0" } @@ -1676,16 +1597,27 @@ export const CurrentMetricAttributesKey = "effect/Metric/CurrentMetricAttributes * * return result * }) + * + * const attributes = await Effect.runPromise(program) + * const actual = attributes // => { service: "api", version: "1.0" } * ``` * - * @category references + * @category services * @since 4.0.0 */ export const CurrentMetricAttributes = Context.Reference(CurrentMetricAttributesKey, { defaultValue: () => ({}) }) -const MetricRegistryKey = "~effect/observability/Metric/MetricRegistryKey" +const MetricRegistryKey = "effect/Metric/MetricRegistry" + +/** + * The registry used to store metric metadata and hooks. + * + * @category services + * @since 4.0.0 + */ +export type MetricRegistry = Map> /** * Context reference for the metric registry in the current context. @@ -1710,15 +1642,15 @@ const MetricRegistryKey = "~effect/observability/Metric/MetricRegistryKey" * @see {@link snapshot} for reading all registered metrics from the current `Effect` context * @see {@link snapshotUnsafe} for reading all registered metrics from an explicit `Context` * - * @category references + * @category services * @since 4.0.0 */ -export const MetricRegistry = Context.Reference>>( +export const MetricRegistry: Context.Reference = Context.Reference( MetricRegistryKey, { defaultValue: () => new Map() } ) -const TypeId = "~effect/observability/Metric" +const TypeId = "~effect/Metric" abstract class Metric$ implements Metric { readonly [TypeId] = TypeId @@ -1728,8 +1660,7 @@ abstract class Metric$ implements Metric { declare readonly Input: Contravariant declare readonly State: Covariant - readonly #metadataCache = new WeakMap>() - #metadata: Metric.Metadata | undefined + readonly #metadata = new WeakMap>() readonly id: string readonly description: string | undefined @@ -1761,20 +1692,15 @@ abstract class Metric$ implements Metric { hook(context: Context.Context): Metric.Hooks { const extraAttributes = Context.get(context, CurrentMetricAttributes) - if (Object.keys(extraAttributes).length === 0) { - if (Predicate.isNotUndefined(this.#metadata)) { - return this.#metadata.hooks - } - this.#metadata = this.getOrCreate(context, this.attributes) - return this.#metadata.hooks + if (Object.keys(extraAttributes).length > 0) { + return this.getOrCreate(context, mergeAttributes(this.attributes, extraAttributes)).hooks } - const mergedAttributes = mergeAttributes(this.attributes, extraAttributes) - let metadata = this.#metadataCache.get(mergedAttributes) - if (Predicate.isNotUndefined(metadata)) { - return metadata.hooks + const registry = Context.get(context, MetricRegistry) + let metadata = this.#metadata.get(registry) + if (Predicate.isUndefined(metadata)) { + metadata = this.getOrCreate(context, this.attributes) + this.#metadata.set(registry, metadata) } - metadata = this.getOrCreate(context, mergedAttributes) - this.#metadataCache.set(mergedAttributes, metadata) return metadata.hooks } @@ -1912,7 +1838,7 @@ class HistogramMetric extends Metric$ { let count = 0 let sum = 0 let min = Number.MAX_VALUE - let max = Number.MIN_VALUE + let max = -Number.MAX_VALUE Arr.map(Arr.sort(bounds, Order.Number), (n, i) => { boundaries[i] = n @@ -1999,7 +1925,7 @@ class SummaryMetric extends Metric$ => { const builder: Array = [] @@ -2021,8 +1947,8 @@ class SummaryMetric extends Metric$ [q, undefined]) } // Compute the value of the quantile in terms of rank: - // > For a given quantile `q`, return the maximum value `v` such that at - // > most `q * n` values are less than or equal to `v`. + // For a given quantile `q`, return the maximum value `v` such that at + // most `q * n` values are less than or equal to `v`. return sortedQuantiles.map((q) => { if (q <= 0) return [q, samples[0]] if (q >= 1) return [q, samples[sampleSize - 1]] @@ -2094,24 +2020,18 @@ class MetricTransform extends Metric$ true + * Metric.isMetric({ name: "requests" }) // => false * ``` * * @category guards * @since 4.0.0 */ -export const isMetric = (u: unknown): u is Metric => - Predicate.hasProperty(u, "~effect/Metric") && u["~effect/Metric"] === "~effect/Metric" +export const isMetric = (u: unknown): u is Metric => + Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId /** * Represents a Counter metric that tracks cumulative numerical values over @@ -2127,12 +2047,8 @@ export const isMetric = (u: unknown): u is Metric => * * **Example** (Creating counter metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class CounterError extends Data.TaggedError("CounterError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create a basic counter for tracking requests @@ -2166,6 +2082,9 @@ export const isMetric = (u: unknown): u is Metric => * * return { requestValue, eventValue, bytesValue } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [result.requestValue.count, result.eventValue.count, result.bytesValue.count] // => [6, 1, 1024n] * ``` * * @category constructors @@ -2209,12 +2128,8 @@ export const counter: { * * **Example** (Creating gauge metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class GaugeError extends Data.TaggedError("GaugeError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create a gauge for tracking memory usage @@ -2253,6 +2168,9 @@ export const counter: { * * return { memoryValue, cpuValue, diskValue } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.memoryValue.value, result.cpuValue.value, result.diskValue.value] // => [800, 75, 1024000000n] * ``` * * @category constructors @@ -2288,12 +2206,8 @@ export const gauge: { * * **Example** (Creating frequency metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class FrequencyError extends Data.TaggedError("FrequencyError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create a frequency metric for HTTP status codes @@ -2343,6 +2257,14 @@ export const gauge: { * * return { statusCounts, actionCounts, errorCounts } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const counts = [ + * result.statusCounts.occurrences.get("200"), + * result.actionCounts.occurrences.get("login"), + * result.errorCounts.occurrences.get("ValidationError") + * ] + * counts // => [3, 2, 2] * ``` * * @category constructors @@ -2370,19 +2292,15 @@ export const frequency = (name: string, options?: { * * **Example** (Creating histogram metrics) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class HistogramError extends Data.TaggedError("HistogramError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create a histogram for API response times * const responseTimeHistogram = Metric.histogram("api_response_time", { * description: "Distribution of API response times in milliseconds", * boundaries: Metric.linearBoundaries({ start: 0, width: 50, count: 10 }) - * // Creates buckets: 0-50ms, 50-100ms, 100-150ms, ..., 400-450ms, 450ms+ + * // Creates buckets: 0-50ms, 50-100ms, 100-150ms, ..., 350-400ms, 400ms+ * }) * * // Create a histogram for request payload sizes @@ -2422,6 +2340,10 @@ export const frequency = (name: string, options?: { * * return { responseTimeState, payloadSizeState } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.responseTimeState.count, result.responseTimeState.sum, result.payloadSizeState.count] + * values // => [5, 500, 3] * ``` * * @category constructors @@ -2451,12 +2373,8 @@ export const histogram = (name: string, options: { * * **Example** (Creating summary metrics) * - * ```ts - * import { Data, Duration, Effect, Metric } from "effect" - * - * class SummaryError extends Data.TaggedError("SummaryError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Duration, Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create a summary for API response times @@ -2492,24 +2410,16 @@ export const histogram = (name: string, options: { * const responseStats = yield* Metric.value(responseTimeSummary) * const payloadStats = yield* Metric.value(payloadSizeSummary) * - * console.log({ - * count: responseStats.count, - * min: responseStats.min, - * max: responseStats.max, - * sum: responseStats.sum - * }) // { count: 8, min: 82, max: 240, sum: 1155 } - * - * console.log({ - * count: payloadStats.count, - * min: payloadStats.min, - * max: payloadStats.max, - * sum: payloadStats.sum - * }) // { count: 4, min: 1.2, max: 15.6, sum: 26 } - * * // Both summaries include quantile information for their configured windows. * * return { responseStats, payloadStats } * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const response = result.responseStats + * const payload = result.payloadStats + * const responseValues = [response.count, response.min, response.max, response.sum] // => [8, 82, 240, 1155] + * const payloadValues = [payload.count, payload.min, payload.max, payload.sum] // => [4, 1.2, 15.6, 26] * ``` * * @category constructors @@ -2549,7 +2459,7 @@ export const summary = (name: string, options: { * * **Example** (Creating summaries with explicit timestamps) * - * ```ts + * ```ts import.meta.vitest * import { Metric } from "effect" * * const responseTimesSummary = Metric.summaryWithTimestamp( @@ -2561,6 +2471,7 @@ export const summary = (name: string, options: { * quantiles: [0.5, 0.9, 0.99] // Calculate 50th, 90th, and 99th quantiles. * } * ) + * const metadata = [responseTimesSummary.id, responseTimesSummary.type] // => ["response_times_summary", "Summary"] * ``` * * @category constructors @@ -2588,12 +2499,8 @@ export const summaryWithTimestamp = (name: string, options: { * * **Example** (Recording durations with a timer) * - * ```ts - * import { Data, Duration, Effect, Metric } from "effect" - * - * class TimerError extends Data.TaggedError("TimerError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Duration, Effect, Metric } from "effect" * * // Create a timer metric to track API request durations * const apiRequestTimer = Metric.timer("api_request_duration", { @@ -2607,13 +2514,17 @@ export const summaryWithTimestamp = (name: string, options: { * yield* Metric.update(apiRequestTimer, duration) * * const state = yield* Metric.value(apiRequestTimer) - * console.log({ + * return { * count: state.count, * min: state.min, * max: state.max, * sum: state.sum - * }) // { count: 1, min: 120, max: 120, sum: 120 } + * } * }) + * + * await Effect.runPromise( + * Effect.provideService(apiOperation, Metric.MetricRegistry, new Map()) + * ) // => { count: 1, min: 120, max: 120, sum: 120 } * ``` * * @category constructors @@ -2646,10 +2557,10 @@ export const timer = (name: string, options?: { * * **Example** (Reading metric state) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * - * const requestCounter = Metric.counter("requests") + * const requestCounter = Metric.counter("modify_requests") * const responseTime = Metric.histogram("response_time", { * boundaries: [100, 500, 1000, 2000] * }) @@ -2661,16 +2572,19 @@ export const timer = (name: string, options?: { * * // Get current values * const counterState = yield* Metric.value(requestCounter) - * console.log(`Request count: ${counterState.count}`) - * * const histogramState = yield* Metric.value(responseTime) - * console.log(`Response time stats:`, { + * return { + * requestCount: counterState.count, * count: histogramState.count, * min: histogramState.min, * max: histogramState.max, * average: histogramState.sum / histogramState.count - * }) + * } * }) + * + * await Effect.runPromise( + * Effect.provideService(program, Metric.MetricRegistry, new Map()) + * ) // => { requestCount: 1, count: 1, min: 750, max: 750, average: 750 } * ``` * * @category getters @@ -2697,7 +2611,7 @@ export const value = ( * * **Example** (Modifying metric values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const temperatureGauge = Metric.gauge("temperature") @@ -2717,10 +2631,10 @@ export const value = ( * * const temp = yield* Metric.value(temperatureGauge) * const requests = yield* Metric.value(requestCounter) - * - * console.log(`Temperature: ${temp.value}°C`) // 22°C - * console.log(`Requests: ${requests.count}`) // 15 + * return [temp.value, requests.count] as const * }) + * + * await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => [22, 15] * ``` * * @category mutations @@ -2751,7 +2665,7 @@ export const modify: { * * **Example** (Updating metric values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const cpuUsage = Metric.gauge("cpu_usage_percent") @@ -2779,11 +2693,10 @@ export const modify: { * const cpu = yield* Metric.value(cpuUsage) * const statuses = yield* Metric.value(httpStatus) * const times = yield* Metric.value(responseTime) - * - * console.log(`CPU Usage: ${cpu.value}%`) - * console.log(`Status 200 count: ${statuses.occurrences.get("200")}`) // 2 - * console.log(`Response time samples: ${times.count}`) // 3 + * return [cpu.value, statuses.occurrences.get("200"), times.count] as const * }) + * + * await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => [67.8, 2, 3] * ``` * * @category mutations @@ -2808,36 +2721,30 @@ export const update: { * * **Example** (Mapping metric inputs) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricError extends Data.TaggedError("MetricError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * - * // Create a histogram that expects Duration values * const durationHistogram = Metric.histogram("request_duration_ms", { * description: "Request duration in milliseconds", * boundaries: Metric.linearBoundaries({ start: 0, width: 100, count: 10 }) * }) * - * // Transform to accept number values representing milliseconds - * const numberHistogram = Metric.mapInput( + * // Accept duration strings while recording numeric milliseconds + * const durationStringHistogram = Metric.mapInput( * durationHistogram, - * (ms: number) => ms // Direct mapping from number to expected input + * (input: string) => Number(input) * ) * * const program = Effect.gen(function*() { - * // Now we can update with a plain number - * yield* Metric.update(numberHistogram, 250) - * - * // Get metric value to see the recorded state - * const value = yield* Metric.value(numberHistogram) - * return value + * yield* Metric.update(durationStringHistogram, "250") + * return yield* Metric.value(durationStringHistogram) * }) + * + * const value = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [value.count, value.sum] // => [1, 250] * ``` * - * @category mapping + * @category annotations * @since 2.0.0 */ export const mapInput: { @@ -2874,12 +2781,8 @@ export const mapInput: { * * **Example** (Ignoring inputs with a constant value) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricError extends Data.TaggedError("MetricError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * // Create a counter that normally expects a number increment * const requestCounter = Metric.counter("total_requests", { @@ -2898,9 +2801,12 @@ export const mapInput: { * const value = yield* Metric.value(simpleRequestCounter) * return value // Counter state will show count: 3 * }) + * + * const value = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const count = value.count // => 3 * ``` * - * @category Input + * @category mapping * @since 2.0.0 */ export const withConstantInput: { @@ -2922,7 +2828,7 @@ export const withConstantInput: { * * **Example** (Applying metric attributes) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Metric } from "effect" * * const requestCounter = Metric.counter("http_requests_total", { @@ -2955,18 +2861,17 @@ export const withConstantInput: { * yield* Metric.update(taggedMetric, 1) // http_requests_total{service="user-api", version="v1"} * }) * - * // When taking snapshots, each attribute combination appears as a separate metric - * const viewMetrics = Effect.gen(function*() { - * const snapshots = yield* Metric.snapshot - * for (const metric of snapshots) { - * if (metric.id === "http_requests_total") { - * console.log(`${metric.id}`, metric.attributes, metric.state) - * } - * } + * const result = Effect.gen(function*() { + * yield* program + * const get = yield* Metric.value(getRequests) + * const post = yield* Metric.value(postRequests) + * return [get.count, post.count] as const * }) + * + * await Effect.runPromise(Effect.provideService(result, Metric.MetricRegistry, new Map())) // => [2, 1] * ``` * - * @category Attributes + * @category mapping * @since 4.0.0 */ export const withAttributes: { @@ -2998,12 +2903,8 @@ export const withAttributes: { * * **Example** (Capturing metric snapshots) * - * ```ts - * import { Console, Data, Effect, Metric } from "effect" - * - * class SnapshotError extends Data.TaggedError("SnapshotError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create and update some metrics @@ -3024,19 +2925,16 @@ export const withAttributes: { * // Take a snapshot of all metrics * const snapshots = yield* Metric.snapshot * - * // Examine the snapshots - * for (const snapshot of snapshots) { - * yield* Console.log(`Metric: ${snapshot.id}`) - * yield* Console.log(`Description: ${snapshot.description}`) - * yield* Console.log(`Type: ${snapshot.type}`) - * yield* Console.log(`State:`, snapshot.state) - * } - * * return snapshots * }) + * + * const snapshots = await Effect.runPromise( + * Effect.provideService(program, Metric.MetricRegistry, new Map()) + * ) + * const ids = snapshots.map((snapshot) => snapshot.id).sort() // => ["http_requests", "response_time_ms"] * ``` * - * @category Snapshotting + * @category snapshotting * @since 2.0.0 */ export const snapshot: Effect> = InternalEffect.map( @@ -3056,12 +2954,8 @@ export const snapshot: Effect> = InternalEffect.m * * **Example** (Dumping metrics as text) * - * ```ts - * import { Console, Data, Effect, Metric } from "effect" - * - * class DumpError extends Data.TaggedError("DumpError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Create and update some metrics for demonstration @@ -3085,20 +2979,21 @@ export const snapshot: Effect> = InternalEffect.m * * // Get formatted dump of all metrics * const metricsReport = yield* Metric.dump - * yield* Console.log("Current Metrics:") - * yield* Console.log(metricsReport) - * - * // Output will look like a formatted table: - * // Name Description Type State - * // http_requests_total Total HTTP requests Counter [count: 2] - * // response_time_ms Current response time in milliseconds Gauge [value: 125] - * // http_status_codes Frequency of HTTP status codes Frequency [occurrences: 200 -> 2, 404 -> 1] - * * return metricsReport * }) + * + * const report = await Effect.runPromise( + * Effect.provideService(program, Metric.MetricRegistry, new Map()) + * ) + * const included = [ + * report.includes("http_requests_total"), + * report.includes("response_time_ms"), + * report.includes("http_status_codes") + * ] + * included // => [true, true, true] * ``` * - * @category Debugging + * @category formatting * @since 4.0.0 */ export const dump: Effect = InternalEffect.flatMap(InternalEffect.context(), (context) => { @@ -3153,56 +3048,20 @@ export const dump: Effect = InternalEffect.flatMap(InternalEffect.contex * * **Example** (Capturing snapshots from a context) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class UnsafeSnapshotError extends Data.TaggedError("UnsafeSnapshotError")<{ - * readonly operation: string - * }> {} - * - * // Use unsafeSnapshot in performance-critical scenarios or internal implementations - * const performanceMetricsExporter = Effect.gen(function*() { - * // Create some metrics first - * const requestCounter = Metric.counter("http_requests", { - * description: "Total HTTP requests" - * }) - * const responseTime = Metric.gauge("response_time_ms", { - * description: "Current response time" - * }) + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * - * // Update metrics + * const requestCounter = Metric.counter("http_requests") + * const program = Effect.gen(function*() { * yield* Metric.update(requestCounter, 1) - * yield* Metric.update(responseTime, 150) - * - * // Get services context for unsafe operations - * const services = yield* Effect.context() - * - * // Use snapshotUnsafe for direct, synchronous access - * const snapshots = Metric.snapshotUnsafe(services) - * const exportBatchCreatedAt = 1_700_000_000_000 - * - * // Process snapshots immediately (useful for exporters, debugging tools) - * const exportData = snapshots.map((snapshot) => ({ - * name: snapshot.id, - * type: snapshot.type, - * value: snapshot.state, - * timestamp: exportBatchCreatedAt - * })) - * - * // This is synchronous and doesn't involve Effect overhead - * // Useful for performance-critical metric export operations - * return exportData + * const context = yield* Effect.context() + * return Metric.snapshotUnsafe(context).map((snapshot) => snapshot.id) * }) * - * // For normal application use, prefer the safe snapshot function: - * const safeSnapshotExample = Effect.gen(function*() { - * // This automatically handles the services context - * const snapshots = yield* Metric.snapshot - * return snapshots - * }) + * await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) // => ["http_requests"] * ``` * - * @category Snapshotting + * @category snapshotting * @since 4.0.0 */ export const snapshotUnsafe = (context: Context.Context): ReadonlyArray => { @@ -3285,60 +3144,13 @@ const attributesToString = (attributes: Metric.AttributeSet): string => { * * **Example** (Creating boundaries from values) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class BoundaryError extends Data.TaggedError("BoundaryError")<{ - * readonly operation: string - * }> {} - * - * // Create boundaries from an array of custom values - * const customBoundaries = Metric.boundariesFromIterable([ - * 10, - * 25, - * 50, - * 100, - * 250, - * 500, - * 1000 - * ]) - * console.log(customBoundaries) // [10, 25, 50, 100, 250, 500, 1000, Infinity] - * - * // Automatically removes duplicates and negative values - * const messyBoundaries = Metric.boundariesFromIterable([ - * -5, - * 0, - * 10, - * 10, - * 25, - * 25, - * 50, - * -1 - * ]) - * console.log(messyBoundaries) // [10, 25, 50, Infinity] - * - * // Works with any iterable (Set, generator functions, etc.) - * const setBoundaries = Metric.boundariesFromIterable( - * new Set([100, 200, 300, 200, 100]) - * ) - * console.log(setBoundaries) // [100, 200, 300, Infinity] - * - * // Use with histogram metric - * const responseTimeHistogram = Metric.histogram("response_times", { - * description: "API response time distribution", - * boundaries: customBoundaries - * }) - * - * const program = Effect.gen(function*() { - * yield* Metric.update(responseTimeHistogram, 75) // Goes in 50-100ms bucket - * yield* Metric.update(responseTimeHistogram, 150) // Goes in 100-250ms bucket + * ```ts import.meta.vitest + * import { Metric } from "effect" * - * const value = yield* Metric.value(responseTimeHistogram) - * return value - * }) + * Metric.boundariesFromIterable([-5, 0, 10, 10, 25, 50]) // => [10, 25, 50, Infinity] * ``` * - * @category boundaries + * @category constructors * @since 4.0.0 */ export const boundariesFromIterable = (iterable: Iterable): ReadonlyArray => @@ -3350,46 +3162,20 @@ export const boundariesFromIterable = (iterable: Iterable): ReadonlyArra * * **Details** * - * Generates `count - 1` finite boundaries using `start + width + index` for + * Generates `count - 1` candidate boundaries using `start + index * width` for * each zero-based index, then applies the same normalization as * `boundariesFromIterable`: non-positive values are removed, duplicates are * collapsed, and `Infinity` is appended. * * **Example** (Creating linear boundaries) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class BoundaryError extends Data.TaggedError("BoundaryError")<{ - * readonly operation: string - * }> {} - * - * // Create boundaries for response time histogram - * const responseBoundaries = Metric.linearBoundaries({ - * start: 0, // Starting point - * width: 100, // Offset used for the first boundary - * count: 5 // Creates 4 boundaries + infinity - * }) - * console.log(responseBoundaries) // [100, 101, 102, 103, Infinity] - * - * // Create a histogram using these boundaries - * const responseTimeHistogram = Metric.histogram("api_response_time", { - * description: "API response time distribution", - * boundaries: responseBoundaries - * }) - * - * const program = Effect.gen(function*() { - * // Record some response times - * yield* Metric.update(responseTimeHistogram, 85) - * yield* Metric.update(responseTimeHistogram, 101) - * yield* Metric.update(responseTimeHistogram, 450) + * ```ts import.meta.vitest + * import { Metric } from "effect" * - * const value = yield* Metric.value(responseTimeHistogram) - * return value - * }) + * Metric.linearBoundaries({ start: 10, width: 20, count: 5 }) // => [10, 30, 50, 70, Infinity] * ``` * - * @category boundaries + * @category constructors * @since 4.0.0 */ export const linearBoundaries = (options: { @@ -3397,7 +3183,7 @@ export const linearBoundaries = (options: { readonly width: number readonly count: number }): ReadonlyArray => - boundariesFromIterable(Arr.makeBy(options.count - 1, (n) => options.start + n + options.width)) + boundariesFromIterable(Arr.makeBy(options.count - 1, (n) => options.start + n * options.width)) /** * Creates histogram bucket boundaries with exponentially increasing values. @@ -3409,47 +3195,13 @@ export const linearBoundaries = (options: { * * **Example** (Creating exponential boundaries) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class BoundaryError extends Data.TaggedError("BoundaryError")<{ - * readonly operation: string - * }> {} - * - * // Create exponential boundaries for request size histogram - * // Buckets: 0-1KB, 1-2KB, 2-4KB, 4-8KB, 8KB+ - * const sizeBoundaries = Metric.exponentialBoundaries({ - * start: 1, // Starting at 1KB - * factor: 2, // Each boundary doubles the previous - * count: 5 // Creates 4 boundaries + infinity - * }) - * console.log(sizeBoundaries) // [1, 2, 4, 8, Infinity] - * - * // Create a histogram for tracking request payload sizes - * const requestSizeHistogram = Metric.histogram("request_size_kb", { - * description: "Request payload size distribution in KB", - * boundaries: sizeBoundaries - * }) - * - * // For very wide ranges, use larger factors - * const latencyBoundaries = Metric.exponentialBoundaries({ - * start: 0.1, // Start at 0.1ms - * factor: 10, // Each boundary is 10x larger - * count: 6 // Creates ranges: 0.1ms, 1ms, 10ms, 100ms, 1000ms+ - * }) - * - * const program = Effect.gen(function*() { - * // Record different request sizes - * yield* Metric.update(requestSizeHistogram, 1.5) // Goes in 1-2KB bucket - * yield* Metric.update(requestSizeHistogram, 3.2) // Goes in 2-4KB bucket - * yield* Metric.update(requestSizeHistogram, 12) // Goes in 8KB+ bucket + * ```ts import.meta.vitest + * import { Metric } from "effect" * - * const value = yield* Metric.value(requestSizeHistogram) - * return value - * }) + * Metric.exponentialBoundaries({ start: 1, factor: 2, count: 5 }) // => [1, 2, 4, 8, Infinity] * ``` * - * @category boundaries + * @category constructors * @since 4.0.0 */ export const exponentialBoundaries = (options: { @@ -3482,78 +3234,41 @@ const fiberFailures = counter("child_fiber_failures", { * * **Example** (Accessing the fiber runtime metrics key) * - * ```ts - * import { Data, Effect, Layer, Metric } from "effect" - * - * class MetricsError extends Data.TaggedError("MetricsError")<{ - * readonly operation: string - * }> {} - * - * const program = Effect.gen(function*() { - * // The key is used internally by the Effect runtime to manage fiber metrics - * const key = Metric.FiberRuntimeMetricsKey - * console.log("Fiber metrics key:", key) - * - * // Enable runtime metrics using the key - * const layer = Layer.succeed(Metric.FiberRuntimeMetrics)( - * Metric.FiberRuntimeMetricsImpl - * ) - * - * return yield* Effect.gen(function*() { - * // This Effect will have fiber metrics automatically collected - * yield* Effect.sleep("100 millis") + * ```ts import.meta.vitest + * import { Metric } from "effect" * - * // Create a test counter to demonstrate the key usage - * const testCounter = Metric.counter("test_counter") - * yield* Metric.update(testCounter, 1) - * return yield* Metric.value(testCounter) - * }).pipe(Effect.provide(layer)) - * }) + * Metric.FiberRuntimeMetricsKey // => "effect/Metric/FiberRuntimeMetrics" * ``` * - * @category metrics + * @category constants * @since 4.0.0 */ -export const FiberRuntimeMetricsKey: "effect/observability/Metric/FiberRuntimeMetricsKey" = - InternalMetric.FiberRuntimeMetricsKey +export const FiberRuntimeMetricsKey: "effect/Metric/FiberRuntimeMetrics" = InternalMetric.FiberRuntimeMetricsKey /** * Interface for the fiber runtime metrics service that tracks fiber lifecycle events. * * **Example** (Providing a custom fiber metrics service) * - * ```ts - * import { Data, Effect, Layer, Metric } from "effect" - * import type { Context, Exit } from "effect" + * ```ts import.meta.vitest + * import { Context, Exit, Metric } from "effect" * - * class MetricsError extends Data.TaggedError("MetricsError")<{ - * readonly operation: string - * }> {} - * - * // Custom implementation of the metrics service + * const events: Array = [] * const customMetricsService: Metric.FiberRuntimeMetricsService = { - * recordFiberStart: (context: Context.Context) => { - * console.log("Fiber started") - * // Custom logic for tracking fiber starts + * recordFiberStart: () => { + * events.push("start") * }, - * recordFiberEnd: ( - * context: Context.Context, - * exit: Exit.Exit - * ) => { - * console.log("Fiber completed with exit:", exit) - * // Custom logic for tracking fiber completion based on exit status + * recordFiberEnd: (_context, exit) => { + * events.push(Exit.isSuccess(exit) ? "success" : "failure") * } * } * - * const program = Effect.gen(function*() { - * // Use the custom metrics service - * const layer = Layer.succeed(Metric.FiberRuntimeMetrics)(customMetricsService) - * - * return yield* Effect.sleep("100 millis").pipe(Effect.provide(layer)) - * }) + * customMetricsService.recordFiberStart(Context.empty()) + * customMetricsService.recordFiberEnd(Context.empty(), Exit.succeed("ok")) + * events // => ["start", "success"] * ``` * - * @category metrics + * @category services * @since 4.0.0 */ export interface FiberRuntimeMetricsService { @@ -3578,50 +3293,26 @@ export interface FiberRuntimeMetricsService { * * **Example** (Accessing the fiber runtime metrics service) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricsError extends Data.TaggedError("MetricsError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { - * // Access the fiber runtime metrics service * const metricsService = yield* Metric.FiberRuntimeMetrics - * - * if (metricsService) { - * console.log("Runtime metrics are enabled") - * } else { - * console.log("Runtime metrics are disabled") - * } - * - * // Enable runtime metrics for the application - * const enabledLayer = Metric.enableRuntimeMetricsLayer - * - * return yield* Effect.gen(function*() { - * // Create some concurrent fibers to see metrics in action - * yield* Effect.all([ - * Effect.sleep("100 millis"), - * Effect.sleep("200 millis"), - * Effect.sleep("300 millis") - * ], { concurrency: "unbounded" }) - * - * // Create test metrics to demonstrate the service - * const testCounter = Metric.counter("test_counter") - * yield* Metric.update(testCounter, 5) - * const counterValue = yield* Metric.value(testCounter) - * - * return { counterValue, metricsEnabled: true } - * }).pipe(Effect.provide(enabledLayer)) + * return metricsService === Metric.FiberRuntimeMetricsImpl * }) + * + * const result = await Effect.runPromise( + * Effect.provideService(program, Metric.FiberRuntimeMetrics, Metric.FiberRuntimeMetricsImpl) + * ) + * const isDefault = result // => true * ``` * - * @category runtime metrics + * @category services * @since 4.0.0 */ export const FiberRuntimeMetrics = Context.Reference( InternalMetric.FiberRuntimeMetricsKey, - { defaultValue: constUndefined } + { fiberCached: true, defaultValue: constUndefined } ) /** @@ -3629,44 +3320,16 @@ export const FiberRuntimeMetrics = Context.Reference {} - * - * const program = Effect.gen(function*() { - * // Use the default metrics implementation - * const metrics = Metric.FiberRuntimeMetricsImpl - * console.log("Metrics implementation:", metrics) - * - * // Enable runtime metrics using the default implementation - * const layer = Layer.succeed(Metric.FiberRuntimeMetrics)(metrics) - * - * return yield* Effect.gen(function*() { - * // Run some Effects to trigger metric collection - * yield* Effect.forkChild(Effect.sleep("50 millis")) - * yield* Effect.forkChild(Effect.sleep("100 millis")) - * - * // Wait a bit and check the metrics - * yield* Effect.sleep("200 millis") - * - * // Create test metrics to demonstrate the implementation - * const testCounter = Metric.counter("test_counter") - * const testGauge = Metric.gauge("test_gauge") - * yield* Metric.update(testCounter, 3) - * yield* Metric.update(testGauge, 42) - * - * const counterValue = yield* Metric.value(testCounter) - * const gaugeValue = yield* Metric.value(testGauge) + * ```ts import.meta.vitest + * import { Metric } from "effect" * - * return { counter: counterValue, gauge: gaugeValue } - * }).pipe(Effect.provide(layer)) - * }) + * [ + * typeof Metric.FiberRuntimeMetricsImpl.recordFiberStart, + * typeof Metric.FiberRuntimeMetricsImpl.recordFiberEnd + * ] // => ["function", "function"] * ``` * - * @category metrics + * @category services * @since 4.0.0 */ export const FiberRuntimeMetricsImpl: FiberRuntimeMetricsService = { @@ -3695,91 +3358,18 @@ export const FiberRuntimeMetricsImpl: FiberRuntimeMetricsService = { * * **Example** (Enabling runtime metrics with a layer) * - * ```ts - * import { Console, Data, Effect, Layer, Metric } from "effect" - * - * class AppError extends Data.TaggedError("AppError")<{ - * readonly operation: string - * }> {} - * - * // Define your application logic - * const userService = Effect.gen(function*() { - * // Simulate user operations with concurrent processing - * const fetchUser = (id: number) => - * Effect.gen(function*() { - * yield* Effect.sleep(`${50 + id * 10} millis`) - * if (id % 7 === 0) { - * return yield* new AppError({ operation: `fetch-user-${id}` }) - * } - * return { id, name: `User ${id}`, email: `user${id}@example.com` } - * }) - * - * // Process multiple users concurrently (ignoring failures for demo) - * const userIds = Array.from({ length: 10 }, (_, i) => i + 1) - * const userTasks = userIds.map((id) => - * fetchUser(id).pipe(Effect.catchTag("AppError", () => Effect.succeed(null))) - * ) - * const allUsers = yield* Effect.all(userTasks, { concurrency: 4 }) - * const successfulUsers = allUsers.filter((user) => user !== null) - * return successfulUsers - * }) - * - * const analyticsService = Effect.gen(function*() { - * // Simulate analytics processing - * const tasks = Array.from({ length: 8 }, (_, i) => - * Effect.gen(function*() { - * yield* Effect.sleep(`${100 + i * 25} millis`) - * return `Analytics task ${i} completed` - * })) - * return yield* Effect.all(tasks, { concurrency: 3 }) - * }) - * - * // Main application that uses multiple services - * const application = Effect.gen(function*() { - * yield* Console.log("Starting application with runtime metrics...") - * - * // Run services concurrently - * const [users, analytics] = yield* Effect.all([ - * userService, - * analyticsService - * ], { concurrency: 2 }) - * - * yield* Console.log( - * `Processed ${users.length} users and ${analytics.length} analytics tasks` - * ) - * - * // Inspect the automatically collected runtime metrics - * const metrics = yield* Metric.snapshot - * const runtimeMetrics = metrics.filter((m) => m.id.startsWith("child_fiber")) - * - * yield* Console.log("Runtime Metrics Collected:") - * for (const metric of runtimeMetrics) { - * yield* Console.log(` ${metric.id}: ${JSON.stringify(metric.state)}`) - * } + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * - * return { users, analytics, metricsCount: runtimeMetrics.length } + * const program = Effect.gen(function*() { + * const service = yield* Metric.FiberRuntimeMetrics + * return service === Metric.FiberRuntimeMetricsImpl * }) * - * // Create the base application layer - * const AppLayer = Layer.empty // Add your application layers here (database, HTTP, etc.) - * - * // Add runtime metrics layer at the end - * const AppLayerWithMetrics = AppLayer.pipe( - * Layer.provide(Metric.enableRuntimeMetricsLayer) - * ) - * - * // Run the application with runtime metrics enabled - * const program = application.pipe( - * Effect.provide(AppLayerWithMetrics) - * ) - * - * // Alternative: Provide runtime metrics directly to the application - * const programWithDirectMetrics = application.pipe( - * Effect.provide(Metric.enableRuntimeMetricsLayer) - * ) + * await Effect.runPromise(Effect.provide(program, Metric.enableRuntimeMetricsLayer)) // => true * ``` * - * @category metrics + * @category layers * @since 4.0.0 */ export const enableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(FiberRuntimeMetricsImpl) @@ -3789,12 +3379,8 @@ export const enableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(Fibe * * **Example** (Disabling runtime metrics with a layer) * - * ```ts - * import { Data, Effect, Metric } from "effect" - * - * class MetricsError extends Data.TaggedError("MetricsError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { * // Disable runtime metrics collection @@ -3803,7 +3389,6 @@ export const enableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(Fibe * return yield* Effect.gen(function*() { * // Check that metrics service is disabled * const metricsService = yield* Metric.FiberRuntimeMetrics - * console.log("Metrics enabled:", metricsService !== undefined) // false * * // Run some Effects - no metrics will be collected * yield* Effect.forkChild(Effect.sleep("50 millis")) @@ -3818,9 +3403,12 @@ export const enableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(Fibe * return { counterValue, metricsEnabled: metricsService !== undefined } * }).pipe(Effect.provide(disabledLayer)) * }) + * + * const result = await Effect.runPromise(Effect.provideService(program, Metric.MetricRegistry, new Map())) + * const values = [result.counterValue.count, result.metricsEnabled] // => [1, false] * ``` * - * @category metrics + * @category layers * @since 4.0.0 */ export const disableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(undefined) @@ -3836,75 +3424,18 @@ export const disableRuntimeMetricsLayer = Layer.succeed(FiberRuntimeMetrics)(und * * **Example** (Enabling runtime metrics for an effect) * - * ```ts - * import { Console, Data, Effect, Layer, Metric } from "effect" - * - * class RuntimeMetricsError extends Data.TaggedError("RuntimeMetricsError")<{ - * readonly operation: string - * }> {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { - * // Create a concurrent workload to demonstrate fiber metrics - * const heavyWorkload = Effect.gen(function*() { - * // Simulate concurrent operations - * const tasks = Array.from({ length: 10 }, (_, i) => - * Effect.gen(function*() { - * yield* Effect.sleep(`${100 + i * 50} millis`) - * if (i % 4 === 0) { - * // Simulate some failures - * return yield* new RuntimeMetricsError({ operation: `task-${i}` }) - * } - * return `Task ${i} completed` - * }).pipe( - * Effect.catchTag("RuntimeMetricsError", () => - * Effect.succeed(`Task ${i} failed`)) - * )) - * - * // Run tasks concurrently - * const results = yield* Effect.all(tasks, { concurrency: 5 }) - * return results - * }) - * - * // Enable runtime metrics collection for our workload - * const workloadWithMetrics = Metric.enableRuntimeMetrics(heavyWorkload) - * - * // Execute the workload - * const results = yield* workloadWithMetrics - * - * // After execution, we can inspect the runtime metrics - * // The following metrics are automatically collected: - * // - child_fibers_active: Current number of active child fibers (Gauge) - * // - child_fibers_started: Total child fibers started (Counter, incremental) - * // - child_fiber_successes: Total successful child fibers (Counter, incremental) - * // - child_fiber_failures: Total failed child fibers (Counter, incremental) - * - * yield* Console.log(`Workload completed with ${results.length} results`) - * - * // Get all metrics including the runtime metrics - * const allMetrics = yield* Metric.snapshot - * const runtimeMetrics = allMetrics.filter((m) => - * m.id.startsWith("child_fiber") || m.id.includes("fiber") - * ) - * - * yield* Console.log("Runtime Metrics:") - * for (const metric of runtimeMetrics) { - * yield* Console.log(` ${metric.id}: ${JSON.stringify(metric.state)}`) - * } - * - * return results + * const service = yield* Metric.FiberRuntimeMetrics + * return service === Metric.FiberRuntimeMetricsImpl * }) * - * // Alternative: Use the layer version for broader application coverage - * const BaseAppLayer = Layer.empty // Your base application layers - * const AppLayerWithMetrics = BaseAppLayer.pipe( - * Layer.provide(Metric.enableRuntimeMetricsLayer) - * ) - * const programWithLayer = program.pipe( - * Effect.provide(AppLayerWithMetrics) - * ) + * await Effect.runPromise(Metric.enableRuntimeMetrics(program)) // => true * ``` * - * @category metrics + * @category providing services * @since 4.0.0 */ export const enableRuntimeMetrics: (self: Effect) => Effect = InternalEffect.provideService( @@ -3922,76 +3453,18 @@ export const enableRuntimeMetrics: (self: Effect) => Effect {} + * ```ts import.meta.vitest + * import { Effect, Metric } from "effect" * * const program = Effect.gen(function*() { - * // This section will have runtime metrics enabled - * const normalOperation = Effect.gen(function*() { - * const tasks = Array.from({ length: 5 }, (_, i) => - * Effect.gen(function*() { - * yield* Effect.sleep(`${100 + i * 20} millis`) - * return `Normal task ${i} completed` - * })) - * return yield* Effect.all(tasks, { concurrency: 3 }) - * }) - * - * // This section will have runtime metrics disabled for performance - * const highPerformanceOperation = Metric.disableRuntimeMetrics( - * Effect.gen(function*() { - * // Performance-critical code where metrics overhead should be avoided - * const hotPath = Array.from( - * { length: 1000 }, - * (_, i) => - * Effect.gen(function*() { - * // Simulate intensive computation - * const result = i * i + (i % 10) / 10 - * return result - * }) - * ) - * return yield* Effect.all(hotPath, { concurrency: 100 }) - * }) - * ) - * - * yield* Console.log("Running operations with selective metrics...") - * - * // Run both operations - * const [normalResults, performanceResults] = yield* Effect.all([ - * normalOperation, // Will generate fiber metrics - * highPerformanceOperation // Will NOT generate fiber metrics - * ]) - * - * // Check collected metrics - should only see metrics from normalOperation - * const metrics = yield* Metric.snapshot - * const runtimeMetrics = metrics.filter((m) => m.id.startsWith("child_fiber")) - * - * yield* Console.log(`Normal operation results: ${normalResults.length}`) - * yield* Console.log( - * `Performance operation results: ${performanceResults.length}` - * ) - * yield* Console.log(`Runtime metrics collected: ${runtimeMetrics.length}`) - * - * // The runtime metrics will only reflect the fibers from normalOperation - * // The highPerformanceOperation fibers were not tracked due to disableRuntimeMetrics - * - * return { normalResults, performanceResults, runtimeMetrics } + * const service = yield* Metric.FiberRuntimeMetrics + * return service === undefined * }) * - * // Enable runtime metrics globally, then selectively disable where needed - * const BaseAppLayer = Layer.empty // Your base application layers - * const AppLayerWithMetrics = BaseAppLayer.pipe( - * Layer.provide(Metric.enableRuntimeMetricsLayer) - * ) - * const finalProgram = program.pipe( - * Effect.provide(AppLayerWithMetrics) - * ) + * await Effect.runPromise(Metric.disableRuntimeMetrics(program)) // => true * ``` * - * @category metrics + * @category providing services * @since 4.0.0 */ export const disableRuntimeMetrics: (self: Effect) => Effect = InternalEffect.provideService( @@ -4024,11 +3497,9 @@ function makeHooks( } function serializeAttributes(attributes: Metric.Attributes): string { - return serializeEntries(Array.isArray(attributes) ? attributes : Object.entries(attributes)) -} - -function serializeEntries(entries: ReadonlyArray<[string, string]>): string { - return entries.map(([key, value]) => `${key}=${value}`).join(",") + const entries = Array.isArray(attributes) ? [...attributes] : Object.entries(attributes) + entries.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0) + return JSON.stringify(entries) } function mergeAttributes( @@ -4041,7 +3512,7 @@ function mergeAttributes( function attributesToRecord(attributes?: Metric.Attributes): Metric.AttributeSet | undefined { if (Predicate.isNotUndefined(attributes) && Array.isArray(attributes)) { return attributes.reduce((acc, [key, value]) => { - acc[key] = value + InternalRecord.assignProperty(acc, key, value) return acc }, {} as Metric.AttributeSet) } diff --git a/packages/effect/src/MutableHashMap.ts b/packages/effect/src/MutableHashMap.ts index 25aa08768..a9d97d747 100644 --- a/packages/effect/src/MutableHashMap.ts +++ b/packages/effect/src/MutableHashMap.ts @@ -20,7 +20,7 @@ import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" -const TypeId = "~effect/collections/MutableHashMap" +const TypeId = "~effect/MutableHashMap" /** * A mutable hash map that stores key-value pairs and supports both referential @@ -39,7 +39,7 @@ const TypeId = "~effect/collections/MutableHashMap" * * **Example** (Using a mutable hash map) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * // Create a mutable hash map with string keys and number values @@ -50,17 +50,7 @@ const TypeId = "~effect/collections/MutableHashMap" * MutableHashMap.set(map, "count", 42) * MutableHashMap.set(map, "total", 100) * - * // Use as iterable - * for (const [key, value] of map) { - * console.log(`${key}: ${value}`) - * } - * // Output: - * // count: 42 - * // total: 100 - * - * // Convert to array - * const entries = Array.from(map) - * console.log(entries) // [["count", 42], ["total", 100]] + * Array.from(map) // => [["count", 42], ["total", 100]] * ``` * * @see {@link empty} for creating an empty mutable hash map @@ -93,7 +83,7 @@ export interface MutableHashMap extends Iterable<[K, V]>, Pipeable * * @see {@link MutableHashMap} for the mutable hash map interface * - * @category refinements + * @category guards * @since 4.0.0 */ export const isMutableHashMap = (value: unknown): value is MutableHashMap => hasProperty(value, TypeId) @@ -133,7 +123,7 @@ const MutableHashMapProto: Omit, "backing" | "b * * **Example** (Creating an empty map) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.empty() @@ -142,7 +132,7 @@ const MutableHashMapProto: Omit, "backing" | "b * MutableHashMap.set(map, "key1", 42) * MutableHashMap.set(map, "key2", 100) * - * console.log(MutableHashMap.size(map)) // 2 + * MutableHashMap.size(map) // => 2 * ``` * * @see {@link make} for creating a map from explicit entries @@ -167,8 +157,8 @@ export const empty = (): MutableHashMap => { * * **Example** (Creating a map from entries) * - * ```ts - * import { MutableHashMap } from "effect" + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" * * const map = MutableHashMap.make( * ["key1", 42], @@ -176,8 +166,8 @@ export const empty = (): MutableHashMap => { * ["key3", 200] * ) * - * console.log(MutableHashMap.get(map, "key1")) // Some(42) - * console.log(MutableHashMap.size(map)) // 3 + * MutableHashMap.get(map, "key1") // => Option.some(42) + * MutableHashMap.size(map) // => 3 * ``` * * @see {@link empty} for creating an empty map @@ -202,8 +192,8 @@ export const make: >( * * **Example** (Creating a map from an iterable) * - * ```ts - * import { MutableHashMap } from "effect" + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" * * const entries = [ * ["apple", 1], @@ -213,12 +203,12 @@ export const make: >( * * const map = MutableHashMap.fromIterable(entries) * - * console.log(MutableHashMap.get(map, "banana")) // Some(2) - * console.log(MutableHashMap.size(map)) // 3 + * MutableHashMap.get(map, "banana") // => Option.some(2) + * MutableHashMap.size(map) // => 3 * * // Works with any iterable * const fromMap = MutableHashMap.fromIterable(new Map([["x", 10], ["y", 20]])) - * console.log(MutableHashMap.get(fromMap, "x")) // Some(10) + * MutableHashMap.get(fromMap, "x") // => Option.some(10) * ``` * * @see {@link make} for creating a map from explicit entries @@ -249,23 +239,22 @@ export const fromIterable = (entries: Iterable): MutableH * * **Example** (Getting a value) * - * ```ts - * import { MutableHashMap } from "effect" + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" * * const map = MutableHashMap.make(["key1", 42], ["key2", 100]) * - * console.log(MutableHashMap.get(map, "key1")) // Some(42) - * console.log(MutableHashMap.get(map, "key3")) // None + * MutableHashMap.get(map, "key1") // => Option.some(42) + * MutableHashMap.get(map, "key3") // => Option.none() * * // Pipe-able version - * const getValue = MutableHashMap.get("key1") - * console.log(getValue(map)) // Some(42) + * MutableHashMap.get("key1")(map) // => Option.some(42) * ``` * * @see {@link has} for checking only whether a key is present * @see {@link set} for inserting or replacing a value by key * - * @category elements + * @category getters * @since 2.0.0 */ export const get: { @@ -304,7 +293,7 @@ const isSimpleKey = (u: unknown): boolean => typeof u !== "object" && typeof u ! * * **Example** (Reading keys) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.make( @@ -313,17 +302,13 @@ const isSimpleKey = (u: unknown): boolean => typeof u !== "object" && typeof u ! * ["cherry", 3] * ) * - * const allKeys = Array.from(MutableHashMap.keys(map)) - * console.log(allKeys) // ["apple", "banana", "cherry"] - * - * // Useful for iteration or validation - * const hasRequiredKeys = allKeys.includes("apple") && allKeys.includes("banana") + * Array.from(MutableHashMap.keys(map)) // => ["apple", "banana", "cherry"] * ``` * * @see {@link values} for iterating over stored values * @see {@link has} for checking one key without iterating * - * @category elements + * @category getters * @since 3.8.0 */ export const keys = (self: MutableHashMap): Iterable => self.backing.keys() @@ -337,7 +322,7 @@ export const keys = (self: MutableHashMap): Iterable => self.back * * **Example** (Reading values) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.make( @@ -346,21 +331,18 @@ export const keys = (self: MutableHashMap): Iterable => self.back * ["cherry", 3] * ) * - * const allValues = Array.from(MutableHashMap.values(map)) - * console.log(allValues) // [1, 2, 3] + * const allValues = Array.from(MutableHashMap.values(map)) // => [1, 2, 3] * * // Useful for calculations - * const total = allValues.reduce((sum, value) => sum + value, 0) - * console.log(total) // 6 + * allValues.reduce((sum, value) => sum + value, 0) // => 6 * * // Filter values - * const largeValues = allValues.filter((value) => value > 1) - * console.log(largeValues) // [2, 3] + * allValues.filter((value) => value > 1) // => [2, 3] * ``` * * @see {@link keys} for iterating over stored keys * - * @category elements + * @category getters * @since 3.8.0 */ export const values = (self: MutableHashMap): Iterable => self.backing.values() @@ -390,22 +372,21 @@ const getFromBucket = ( * * **Example** (Checking for a key) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.make(["key1", 42], ["key2", 100]) * - * console.log(MutableHashMap.has(map, "key1")) // true - * console.log(MutableHashMap.has(map, "key3")) // false + * MutableHashMap.has(map, "key1") // => true + * MutableHashMap.has(map, "key3") // => false * * // Pipe-able version - * const hasKey = MutableHashMap.has("key1") - * console.log(hasKey(map)) // true + * MutableHashMap.has("key1")(map) // => true * ``` * * @see {@link get} for reading the value as an `Option` * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -427,8 +408,8 @@ export const has: { * * **Example** (Setting key-value pairs) * - * ```ts - * import { MutableHashMap } from "effect" + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" * * const map = MutableHashMap.empty() * @@ -436,17 +417,16 @@ export const has: { * MutableHashMap.set(map, "key1", 42) * MutableHashMap.set(map, "key2", 100) * - * console.log(MutableHashMap.get(map, "key1")) // Some(42) - * console.log(MutableHashMap.size(map)) // 2 + * MutableHashMap.get(map, "key1") // => Option.some(42) + * MutableHashMap.size(map) // => 2 * * // Update existing entry * MutableHashMap.set(map, "key1", 999) - * console.log(MutableHashMap.get(map, "key1")) // Some(999) + * MutableHashMap.get(map, "key1") // => Option.some(999) * * // Pipe-able version - * const setKey = MutableHashMap.set("key3", 300) - * setKey(map) - * console.log(MutableHashMap.size(map)) // 3 + * MutableHashMap.set("key3", 300)(map) + * MutableHashMap.size(map) // => 3 * ``` * * @see {@link modify} for updating an existing value with a function @@ -513,26 +493,26 @@ const getRefKey = ( * * **Example** (Modifying existing values) * - * ```ts - * import { MutableHashMap } from "effect" + * ```ts import.meta.vitest + * import { MutableHashMap, Option } from "effect" * * const map = MutableHashMap.make(["count", 5], ["total", 100]) * * // Increment existing value * MutableHashMap.modify(map, "count", (n) => n + 1) - * console.log(MutableHashMap.get(map, "count")) // Some(6) + * MutableHashMap.get(map, "count") // => Option.some(6) * * // Double existing value * MutableHashMap.modify(map, "total", (n) => n * 2) - * console.log(MutableHashMap.get(map, "total")) // Some(200) + * MutableHashMap.get(map, "total") // => Option.some(200) * * // Try to modify non-existent key (no effect) * MutableHashMap.modify(map, "missing", (n) => n + 1) - * console.log(MutableHashMap.has(map, "missing")) // false + * MutableHashMap.has(map, "missing") // => false * * // Pipe-able version - * const increment = MutableHashMap.modify("count", (n: number) => n + 1) - * increment(map) + * MutableHashMap.modify("count", (n: number) => n + 1)(map) + * MutableHashMap.get(map, "count") // => Option.some(7) * ``` * * @see {@link set} for inserting or replacing a value directly @@ -586,7 +566,7 @@ export const modify: { * * **Example** (Updating or removing a key) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap, Option } from "effect" * * const map = MutableHashMap.make(["count", 5]) @@ -597,7 +577,7 @@ export const modify: { * "count", * (option) => Option.map(option, (n) => n * 2) * ) - * console.log(MutableHashMap.get(map, "count")) // Some(10) + * MutableHashMap.get(map, "count") // => Option.some(10) * * // Add new key * MutableHashMap.modifyAt( @@ -605,11 +585,11 @@ export const modify: { * "new", * (option) => Option.isNone(option) ? Option.some(42) : option * ) - * console.log(MutableHashMap.get(map, "new")) // Some(42) + * MutableHashMap.get(map, "new") // => Option.some(42) * * // Remove key by returning None * MutableHashMap.modifyAt(map, "count", () => Option.none()) - * console.log(MutableHashMap.has(map, "count")) // false + * MutableHashMap.get(map, "count") // => Option.none() * * // Conditional update * MutableHashMap.modifyAt( @@ -617,7 +597,7 @@ export const modify: { * "new", * (option) => Option.filter(option, (n) => n > 50) // Remove if <= 50 * ) - * console.log(MutableHashMap.has(map, "new")) // false (42 <= 50) + * MutableHashMap.get(map, "new") // => Option.none() * ``` * * @see {@link modify} for updating only when the key already exists @@ -663,7 +643,7 @@ export const modifyAt: { * * **Example** (Removing a key) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.make( @@ -672,21 +652,20 @@ export const modifyAt: { * ["key3", 200] * ) * - * console.log(MutableHashMap.size(map)) // 3 + * MutableHashMap.size(map) // => 3 * * // Remove existing key * MutableHashMap.remove(map, "key2") - * console.log(MutableHashMap.size(map)) // 2 - * console.log(MutableHashMap.has(map, "key2")) // false + * MutableHashMap.size(map) // => 2 + * MutableHashMap.has(map, "key2") // => false * * // Remove non-existent key (no effect) * MutableHashMap.remove(map, "nonexistent") - * console.log(MutableHashMap.size(map)) // 2 + * MutableHashMap.size(map) // => 2 * * // Pipe-able version - * const removeKey = MutableHashMap.remove("key1") - * removeKey(map) - * console.log(MutableHashMap.size(map)) // 1 + * MutableHashMap.remove("key1")(map) + * MutableHashMap.size(map) // => 1 * ``` * * @see {@link clear} for removing all entries @@ -737,7 +716,7 @@ export const remove: { * * **Example** (Clearing all entries) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.make( @@ -746,17 +725,17 @@ export const remove: { * ["key3", 200] * ) * - * console.log(MutableHashMap.size(map)) // 3 + * MutableHashMap.size(map) // => 3 * * // Clear all entries * MutableHashMap.clear(map) * - * console.log(MutableHashMap.size(map)) // 0 - * console.log(MutableHashMap.has(map, "key1")) // false + * MutableHashMap.size(map) // => 0 + * MutableHashMap.has(map, "key1") // => false * * // Can still add new entries after clearing * MutableHashMap.set(map, "new", 999) - * console.log(MutableHashMap.size(map)) // 1 + * Array.from(map) // => [["new", 999]] * ``` * * @see {@link remove} for deleting one key @@ -780,26 +759,26 @@ export const clear = (self: MutableHashMap) => { * * **Example** (Checking map size) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashMap } from "effect" * * const map = MutableHashMap.empty() - * console.log(MutableHashMap.size(map)) // 0 + * MutableHashMap.size(map) // => 0 * * MutableHashMap.set(map, "key1", 42) * MutableHashMap.set(map, "key2", 100) - * console.log(MutableHashMap.size(map)) // 2 + * MutableHashMap.size(map) // => 2 * * MutableHashMap.remove(map, "key1") - * console.log(MutableHashMap.size(map)) // 1 + * MutableHashMap.size(map) // => 1 * * MutableHashMap.clear(map) - * console.log(MutableHashMap.size(map)) // 0 + * MutableHashMap.size(map) // => 0 * ``` * * @see {@link isEmpty} for checking whether the map has no entries * - * @category elements + * @category getters * @since 2.0.0 */ export const size = (self: MutableHashMap): number => self.backing.size diff --git a/packages/effect/src/MutableHashSet.ts b/packages/effect/src/MutableHashSet.ts index 1c5dc0a02..654f7c633 100644 --- a/packages/effect/src/MutableHashSet.ts +++ b/packages/effect/src/MutableHashSet.ts @@ -17,7 +17,7 @@ import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" -const TypeId = "~effect/collections/MutableHashSet" +const TypeId = "~effect/MutableHashSet" /** * A mutable hash set for storing unique values with Effect structural equality @@ -36,7 +36,7 @@ const TypeId = "~effect/collections/MutableHashSet" * * **Example** (Using a mutable hash set) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * // Create a mutable hash set @@ -49,16 +49,14 @@ const TypeId = "~effect/collections/MutableHashSet" * MutableHashSet.add(set, "cherry") * * // Check if elements exist - * console.log(MutableHashSet.has(set, "apple")) // true - * console.log(MutableHashSet.has(set, "grape")) // false + * MutableHashSet.has(set, "apple") // => true + * MutableHashSet.has(set, "grape") // => false * - * // Iterate over elements - * for (const value of set) { - * console.log(value) // "apple", "banana", "cherry" - * } + * // Collect the iterator values + * Array.from(set) // => ["apple", "banana", "cherry"] * * // Get size - * console.log(MutableHashSet.size(set)) // 3 + * MutableHashSet.size(set) // => 3 * ``` * * @category models @@ -86,7 +84,7 @@ export interface MutableHashSet extends Iterable, Pipeable, Inspectabl * * @see {@link MutableHashSet} for the mutable hash set interface * - * @category refinements + * @category guards * @since 4.0.0 */ export const isMutableHashSet = (value: unknown): value is MutableHashSet => hasProperty(value, TypeId) @@ -132,7 +130,7 @@ const fromHashMap = (keyMap: MutableHashMap.MutableHashMap): Muta * * **Example** (Creating an empty set) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.empty() @@ -142,8 +140,8 @@ const fromHashMap = (keyMap: MutableHashMap.MutableHashMap): Muta * MutableHashSet.add(set, "banana") * MutableHashSet.add(set, "apple") // Duplicate, no effect * - * console.log(MutableHashSet.size(set)) // 2 - * console.log(Array.from(set)) // ["apple", "banana"] + * MutableHashSet.size(set) // => 2 + * Array.from(set) // => ["apple", "banana"] * ``` * * @see {@link make} for creating a set from explicit values @@ -165,22 +163,20 @@ export const empty = (): MutableHashSet => fromHashMap(MutableHash * * **Example** (Creating a set from an iterable) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const values = ["apple", "banana", "apple", "cherry", "banana"] * const set = MutableHashSet.fromIterable(values) * - * console.log(MutableHashSet.size(set)) // 3 - * console.log(Array.from(set)) // ["apple", "banana", "cherry"] + * MutableHashSet.size(set) // => 3 + * Array.from(set) // => ["apple", "banana", "cherry"] * * // Works with any iterable - * const fromSet = MutableHashSet.fromIterable(new Set([1, 2, 3])) - * console.log(MutableHashSet.size(fromSet)) // 3 + * MutableHashSet.size(MutableHashSet.fromIterable(new Set([1, 2, 3]))) // => 3 * * // From string characters - * const fromString = MutableHashSet.fromIterable("hello") - * console.log(Array.from(fromString)) // ["h", "e", "l", "o"] + * Array.from(MutableHashSet.fromIterable("hello")) // => ["h", "e", "l", "o"] * ``` * * @category constructors @@ -199,22 +195,21 @@ export const fromIterable = (keys: Iterable): MutableHashSet => * * **Example** (Creating a set from values) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.make("apple", "banana", "apple", "cherry") * - * console.log(MutableHashSet.size(set)) // 3 - * console.log(Array.from(set)) // ["apple", "banana", "cherry"] + * MutableHashSet.size(set) // => 3 + * Array.from(set) // => ["apple", "banana", "cherry"] * * // With numbers * const numbers = MutableHashSet.make(1, 2, 3, 2, 1) - * console.log(MutableHashSet.size(numbers)) // 3 - * console.log(Array.from(numbers)) // [1, 2, 3] + * MutableHashSet.size(numbers) // => 3 + * Array.from(numbers) // => [1, 2, 3] * * // Mixed types - * const mixed = MutableHashSet.make("hello", 42, true, "hello") - * console.log(MutableHashSet.size(mixed)) // 3 + * MutableHashSet.size(MutableHashSet.make("hello", 42, true, "hello")) // => 3 * ``` * * @category constructors @@ -234,7 +229,7 @@ export const make = >( * * **Example** (Adding values) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.empty() @@ -243,17 +238,17 @@ export const make = >( * MutableHashSet.add(set, "apple") * MutableHashSet.add(set, "banana") * - * console.log(MutableHashSet.size(set)) // 2 - * console.log(MutableHashSet.has(set, "apple")) // true + * MutableHashSet.size(set) // => 2 + * MutableHashSet.has(set, "apple") // => true * * // Add duplicate (no effect) * MutableHashSet.add(set, "apple") - * console.log(MutableHashSet.size(set)) // 2 + * MutableHashSet.size(set) // => 2 * * // Pipe-able version * const addFruit = MutableHashSet.add("cherry") * addFruit(set) - * console.log(MutableHashSet.size(set)) // 3 + * MutableHashSet.size(set) // => 3 * ``` * * @category mutations @@ -281,27 +276,27 @@ export const add: { * * **Example** (Checking for a value) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.make("apple", "banana", "cherry") * - * console.log(MutableHashSet.has(set, "apple")) // true - * console.log(MutableHashSet.has(set, "grape")) // false + * MutableHashSet.has(set, "apple") // => true + * MutableHashSet.has(set, "grape") // => false * * // Pipe-able version * const hasApple = MutableHashSet.has("apple") - * console.log(hasApple(set)) // true + * hasApple(set) // => true * * // Check after adding * MutableHashSet.add(set, "grape") - * console.log(MutableHashSet.has(set, "grape")) // true + * MutableHashSet.has(set, "grape") // => true * ``` * * @see {@link add} for adding a value to the set * @see {@link remove} for removing a value from the set * - * @category elements + * @category predicates * @since 2.0.0 */ export const has: { @@ -322,26 +317,26 @@ export const has: { * * **Example** (Removing a value) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.make("apple", "banana", "cherry") * - * console.log(MutableHashSet.size(set)) // 3 + * MutableHashSet.size(set) // => 3 * * // Remove existing value * MutableHashSet.remove(set, "banana") - * console.log(MutableHashSet.size(set)) // 2 - * console.log(MutableHashSet.has(set, "banana")) // false + * MutableHashSet.size(set) // => 2 + * MutableHashSet.has(set, "banana") // => false * * // Remove non-existent value (no effect) * MutableHashSet.remove(set, "grape") - * console.log(MutableHashSet.size(set)) // 2 + * MutableHashSet.size(set) // => 2 * * // Pipe-able version * const removeFruit = MutableHashSet.remove("apple") * removeFruit(set) - * console.log(MutableHashSet.size(set)) // 1 + * MutableHashSet.size(set) // => 1 * ``` * * @category mutations @@ -364,25 +359,25 @@ export const remove: { * * **Example** (Checking set size) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.empty() - * console.log(MutableHashSet.size(set)) // 0 + * MutableHashSet.size(set) // => 0 * * MutableHashSet.add(set, "apple") * MutableHashSet.add(set, "banana") * MutableHashSet.add(set, "apple") // Duplicate - * console.log(MutableHashSet.size(set)) // 2 + * MutableHashSet.size(set) // => 2 * * MutableHashSet.remove(set, "apple") - * console.log(MutableHashSet.size(set)) // 1 + * MutableHashSet.size(set) // => 1 * * MutableHashSet.clear(set) - * console.log(MutableHashSet.size(set)) // 0 + * MutableHashSet.size(set) // => 0 * ``` * - * @category elements + * @category getters * @since 2.0.0 */ export const size = (self: MutableHashSet): number => MutableHashMap.size(self.keyMap) @@ -397,23 +392,23 @@ export const size = (self: MutableHashSet): number => MutableHashMap.size( * * **Example** (Clearing all values) * - * ```ts + * ```ts import.meta.vitest * import { MutableHashSet } from "effect" * * const set = MutableHashSet.make("apple", "banana", "cherry") * - * console.log(MutableHashSet.size(set)) // 3 + * MutableHashSet.size(set) // => 3 * * // Clear all values * MutableHashSet.clear(set) * - * console.log(MutableHashSet.size(set)) // 0 - * console.log(MutableHashSet.has(set, "apple")) // false - * console.log(Array.from(set)) // [] + * MutableHashSet.size(set) // => 0 + * MutableHashSet.has(set, "apple") // => false + * Array.from(set) // => [] * * // Can still add new values after clearing * MutableHashSet.add(set, "new") - * console.log(MutableHashSet.size(set)) // 1 + * MutableHashSet.size(set) // => 1 * ``` * * @category mutations diff --git a/packages/effect/src/MutableList.ts b/packages/effect/src/MutableList.ts index 7dbe56f27..3506009e8 100644 --- a/packages/effect/src/MutableList.ts +++ b/packages/effect/src/MutableList.ts @@ -9,6 +9,7 @@ * @since 4.0.0 */ import * as Arr from "./Array.ts" +import * as Count from "./internal/count.ts" /** * A mutable linked list data structure optimized for high-throughput operations. @@ -17,26 +18,16 @@ import * as Arr from "./Array.ts" * * **Example** (Creating and consuming a mutable list) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * - * // Create a mutable list * const list: MutableList.MutableList = MutableList.make() - * - * // Add elements * MutableList.append(list, 1) * MutableList.append(list, 2) * MutableList.prepend(list, 0) * - * // Access properties - * console.log(list.length) // 3 - * console.log(list.head?.array) // Contains elements from head bucket - * console.log(list.tail?.array) // Contains elements from tail bucket - * - * // Take elements - * console.log(MutableList.take(list)) // 0 - * console.log(MutableList.take(list)) // 1 - * console.log(MutableList.take(list)) // 2 + * MutableList.takeAll(list) // => [0, 1, 2] + * list.length // => 0 * ``` * * @category models @@ -52,36 +43,6 @@ export interface MutableList { * The MutableList namespace contains type definitions and utilities for working * with mutable linked lists. * - * **Example** (Typing queue processors) - * - * ```ts - * import { MutableList } from "effect" - * - * // Type annotation using the namespace - * const processQueue = (queue: MutableList.MutableList) => { - * while (queue.length > 0) { - * const item = MutableList.take(queue) - * if (item !== MutableList.Empty) { - * console.log("Processing:", item) - * } - * } - * } - * - * // Using the namespace for type definitions - * const createProcessor = (): { - * queue: MutableList.MutableList - * add: (item: T) => void - * process: () => Array - * } => { - * const queue = MutableList.make() - * return { - * queue, - * add: (item) => MutableList.append(queue, item), - * process: () => MutableList.takeAll(queue) - * } - * } - * ``` - * * @since 2.0.0 */ export declare namespace MutableList { @@ -97,27 +58,19 @@ export declare namespace MutableList { * * **Example** (Inspecting buckets) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.append(list, 1) * MutableList.append(list, 2) * - * // Access bucket information (for debugging or advanced usage) - * const inspectBucket = ( - * bucket: MutableList.MutableList.Bucket | undefined - * ) => { - * if (bucket) { - * console.log("Bucket array:", bucket.array) - * console.log("Bucket offset:", bucket.offset) - * console.log("Bucket mutable:", bucket.mutable) - * console.log("Has next bucket:", bucket.next !== undefined) - * } - * } + * const bucket: MutableList.MutableList.Bucket = list.head! * - * inspectBucket(list.head) - * inspectBucket(list.tail) + * bucket.array // => [1, 2] + * bucket.offset // => 0 + * bucket.mutable // => true + * bucket.next === undefined // => true * ``` * * @category models @@ -142,32 +95,12 @@ export declare namespace MutableList { * * **Example** (Checking for empty results) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * - * // Take from empty list returns Empty symbol - * const result = MutableList.take(list) - * console.log(result === MutableList.Empty) // true - * - * // Safe pattern for checking emptiness - * const processNext = (queue: MutableList.MutableList) => { - * const item = MutableList.take(queue) - * if (item === MutableList.Empty) { - * console.log("Queue is empty") - * return null - * } - * return item.toUpperCase() - * } - * - * // Compare with other empty results - * MutableList.append(list, "hello") - * const next = MutableList.take(list) - * console.log(next !== MutableList.Empty) // true, got "hello" - * - * const empty = MutableList.take(list) - * console.log(empty === MutableList.Empty) // true, list is empty + * MutableList.take(list) === MutableList.Empty // => true * ``` * * @category symbols @@ -181,43 +114,19 @@ export const Empty: unique symbol = Symbol.for("effect/MutableList/Empty") * * **Example** (Handling empty results type-safely) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * - * // Type-safe handling of empty results - * const takeAndDouble = ( - * queue: MutableList.MutableList - * ): number | null => { + * const takeAndDouble = (queue: MutableList.MutableList): number | null => { * const item: number | MutableList.Empty = MutableList.take(queue) - * - * if (item === MutableList.Empty) { - * return null - * } - * - * // TypeScript knows item is number here - * return item * 2 + * return item === MutableList.Empty ? null : item * 2 * } * - * console.log(takeAndDouble(list)) // null (empty list) - * + * takeAndDouble(list) // => null * MutableList.append(list, 5) - * console.log(takeAndDouble(list)) // 10 - * - * // Type guard function - * const isEmpty = ( - * result: number | MutableList.Empty - * ): result is MutableList.Empty => { - * return result === MutableList.Empty - * } - * - * const value = MutableList.take(list) - * if (isEmpty(value)) { - * console.log("List is empty") - * } else { - * console.log("Got value:", value) - * } + * takeAndDouble(list) // => 10 * ``` * * @category symbols @@ -230,22 +139,15 @@ export type Empty = typeof Empty * * **Example** (Creating an empty mutable list) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * - * // Add elements + * list.length // => 0 * MutableList.append(list, "first") - * MutableList.append(list, "second") - * MutableList.prepend(list, "beginning") - * - * console.log(list.length) // 3 - * - * // Take elements in FIFO order (from head) - * console.log(MutableList.take(list)) // "beginning" - * console.log(MutableList.take(list)) // "first" - * console.log(MutableList.take(list)) // "second" + * MutableList.take(list) // => "first" + * list.length // => 0 * ``` * * @category constructors @@ -270,27 +172,16 @@ const emptyBucket = (): MutableList.Bucket => ({ * * **Example** (Appending elements) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() - * - * // Append elements one by one * MutableList.append(list, 1) * MutableList.append(list, 2) * MutableList.append(list, 3) * - * console.log(list.length) // 3 - * - * // Elements are taken from head (FIFO) - * console.log(MutableList.take(list)) // 1 - * console.log(MutableList.take(list)) // 2 - * console.log(MutableList.take(list)) // 3 - * - * // High-throughput usage - * for (let i = 0; i < 10000; i++) { - * MutableList.append(list, i) - * } + * MutableList.toArray(list) // => [1, 2, 3] + * list.length // => 3 * ``` * * @category mutations @@ -313,27 +204,16 @@ export const append = (self: MutableList, message: A): void => { * * **Example** (Prepending elements) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() - * - * // Prepend elements (they'll be at the front) + * MutableList.append(list, "last") * MutableList.prepend(list, "third") * MutableList.prepend(list, "second") * MutableList.prepend(list, "first") * - * console.log(list.length) // 3 - * - * // Elements taken from head (most recently prepended first) - * console.log(MutableList.take(list)) // "first" - * console.log(MutableList.take(list)) // "second" - * console.log(MutableList.take(list)) // "third" - * - * // Use case: priority items or stack-like behavior - * MutableList.append(list, "normal") - * MutableList.prepend(list, "priority") // This will be taken first - * console.log(MutableList.take(list)) // "priority" + * MutableList.toArray(list) // => ["first", "second", "third", "last"] * ``` * * @category mutations @@ -346,6 +226,7 @@ export const prepend = (self: MutableList, message: A): void => { offset: 0, next: self.head } + if (!self.tail) self.tail = self.head self.length++ } @@ -356,25 +237,15 @@ export const prepend = (self: MutableList, message: A): void => { * * **Example** (Prepending multiple elements) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.append(list, 4) * MutableList.append(list, 5) - * - * // Prepend multiple elements * MutableList.prependAll(list, [1, 2, 3]) * - * console.log(list.length) // 5 - * - * // Elements are taken in order: [1, 2, 3, 4, 5] - * console.log(MutableList.takeAll(list)) // [1, 2, 3, 4, 5] - * - * // Works with any iterable - * const newList = MutableList.make() - * MutableList.prependAll(newList, "hello") // Prepends each character - * console.log(MutableList.takeAll(newList)) // ["h", "e", "l", "l", "o"] + * MutableList.toArray(list) // => [1, 2, 3, 4, 5] * ``` * * @category mutations @@ -390,32 +261,24 @@ export const prependAll = (self: MutableList, messages: Iterable): void * **When to use** * * Use when prepending a trusted array directly is worth the optimized path and - * you control whether the input may be reused. + * you can transfer ownership of the input when enabling mutation. * * **Gotchas** * - * When mutable=true, the input array may be modified internally. Only use - * mutable=true when you control the array lifecycle. + * When mutable=true, ownership of the input array transfers to the list. Do not + * read or modify the array afterward. * - * **Example** (Prepending arrays with optional mutation) + * **Example** (Transferring an array when prepending) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.append(list, 4) - * - * // Safe usage (default mutable=false) * const items = [1, 2, 3] - * MutableList.prependAllUnsafe(list, items) - * console.log(items) // [1, 2, 3] - unchanged + * MutableList.prependAllUnsafe(list, items, true) * - * // Unsafe but efficient usage (mutable=true) - * const mutableItems = [10, 20, 30] - * MutableList.prependAllUnsafe(list, mutableItems, true) - * // mutableItems may be modified internally for efficiency - * - * console.log(MutableList.takeAll(list)) // [10, 20, 30, 1, 2, 3, 4] + * MutableList.toArray(list) // => [1, 2, 3, 4] * ``` * * @category mutations @@ -428,6 +291,7 @@ export const prependAllUnsafe = (self: MutableList, messages: ReadonlyArra offset: 0, next: self.head } + if (!self.tail && messages.length > 0) self.tail = self.head self.length += self.head.array.length } @@ -437,33 +301,16 @@ export const prependAllUnsafe = (self: MutableList, messages: ReadonlyArra * * **Example** (Appending multiple elements) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.append(list, 1) * MutableList.append(list, 2) * - * // Append multiple elements - * const added = MutableList.appendAll(list, [3, 4, 5]) - * console.log(added) // 3 - * console.log(list.length) // 5 - * - * // Elements maintain order: [1, 2, 3, 4, 5] - * console.log(MutableList.takeAll(list)) // [1, 2, 3, 4, 5] - * - * // Works with any iterable - * const newList = MutableList.make() - * MutableList.appendAll(newList, new Set(["a", "b", "c"])) - * console.log(MutableList.takeAll(newList)) // ["a", "b", "c"] - * - * // Useful for bulk loading - * const bulkList = MutableList.make() - * const count = MutableList.appendAll( - * bulkList, - * Array.from({ length: 1000 }, (_, i) => i) - * ) - * console.log(count) // 1000 + * MutableList.appendAll(list, [3, 4, 5]) // => 3 + * MutableList.toArray(list) // => [1, 2, 3, 4, 5] + * list.length // => 5 * ``` * * @category mutations @@ -480,37 +327,24 @@ export const appendAll = (self: MutableList, messages: Iterable): numbe * **When to use** * * Use when appending a trusted array directly is worth the optimized path and - * you control whether the input may be reused. + * you can transfer ownership of the input when enabling mutation. * * **Gotchas** * - * When mutable=true, the input array may be modified internally. Only use - * mutable=true when you control the array lifecycle. + * When mutable=true, ownership of the input array transfers to the list. Do not + * read or modify the array afterward. * - * **Example** (Appending arrays with optional mutation) + * **Example** (Transferring an array when appending) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.append(list, 1) - * - * // Safe usage (default mutable=false) * const items = [2, 3, 4] - * const added = MutableList.appendAllUnsafe(list, items) - * console.log(added) // 3 - * console.log(items) // [2, 3, 4] - unchanged - * - * // Unsafe but efficient usage (mutable=true) - * const mutableItems = [5, 6, 7] - * MutableList.appendAllUnsafe(list, mutableItems, true) - * // mutableItems may be modified internally for efficiency - * - * console.log(MutableList.takeAll(list)) // [1, 2, 3, 4, 5, 6, 7] + * MutableList.appendAllUnsafe(list, items, true) // => 3 * - * // High-performance bulk operations - * const bigArray = new Array(10000).fill(0).map((_, i) => i) - * MutableList.appendAllUnsafe(list, bigArray, true) // Very efficient + * MutableList.toArray(list) // => [1, 2, 3, 4] * ``` * * @category mutations @@ -541,29 +375,17 @@ export const appendAllUnsafe = (self: MutableList, messages: ReadonlyArray * * **Example** (Clearing a mutable list) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.appendAll(list, [1, 2, 3, 4, 5]) * - * console.log(list.length) // 5 - * - * // Clear all elements * MutableList.clear(list) * - * console.log(list.length) // 0 - * console.log(MutableList.take(list)) // Empty - * - * // Can still use the list after clearing - * MutableList.append(list, 42) - * console.log(list.length) // 1 - * - * // Useful for resetting queues or buffers - * function resetBuffer(buffer: MutableList.MutableList) { - * MutableList.clear(buffer) - * console.log("Buffer cleared and ready for reuse") - * } + * MutableList.toArray(list) // => [] + * list.length // => 0 + * MutableList.take(list) === MutableList.Empty // => true * ``` * * @category mutations @@ -579,44 +401,29 @@ export const clear = (self: MutableList): void => { * The taken elements are removed from the list. This operation is optimized for performance * and includes zero-copy optimizations when possible. * + * **Details** + * + * Finite fractional values of `n` are rounded down. `NaN` and non-positive + * values leave the list unchanged and return an empty array. + * * **Example** (Taking batches) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.appendAll(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) * - * console.log(list.length) // 10 - * - * // Take first 3 elements - * const first3 = MutableList.takeN(list, 3) - * console.log(first3) // [1, 2, 3] - * console.log(list.length) // 7 - * - * // Take more than available - * const remaining = MutableList.takeN(list, 20) - * console.log(remaining) // [4, 5, 6, 7, 8, 9, 10] - * console.log(list.length) // 0 - * - * // Take from empty list - * const empty = MutableList.takeN(list, 5) - * console.log(empty) // [] - * - * // Batch processing pattern - * const queue = MutableList.make() - * MutableList.appendAll(queue, ["task1", "task2", "task3", "task4", "task5"]) - * - * while (queue.length > 0) { - * const batch = MutableList.takeN(queue, 2) // Process 2 at a time - * console.log("Processing batch:", batch) - * } + * MutableList.takeN(list, 3) // => [1, 2, 3] + * MutableList.toArray(list) // => [4, 5, 6, 7, 8, 9, 10] + * list.length // => 7 * ``` * - * @category elements + * @category mutations * @since 4.0.0 */ export const takeN = (self: MutableList, n: number): Array => { + n = Count.normalize(n) if (n <= 0 || !self.head) return [] n = Math.min(n, self.length) if (n === self.length && self.head?.offset === 0 && !self.head.next) { @@ -656,17 +463,19 @@ export const takeN = (self: MutableList, n: number): Array => { * * **Details** * - * If `n` is less than or equal to zero, or the list is empty, the list is left - * unchanged. If `n` is greater than or equal to the current length, the list is + * Finite fractional values of `n` are rounded down. If `n` is `NaN` or + * non-positive, or the list is empty, the list is left unchanged. If the + * normalized count is greater than or equal to the current length, the list is * cleared. * * @see {@link takeN} for removing up to `n` values and returning them as an array * @see {@link clear} for removing every value from the list * - * @category elements + * @category mutations * @since 4.0.0 */ export const takeNVoid = (self: MutableList, n: number): void => { + n = Count.normalize(n) if (n <= 0 || !self.head) return n = Math.min(n, self.length) if (n === self.length && self.head?.offset === 0 && !self.head.next) { @@ -696,40 +505,17 @@ export const takeNVoid = (self: MutableList, n: number): void => { * * **Example** (Draining all elements) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.appendAll(list, ["apple", "banana", "cherry"]) * - * console.log(list.length) // 3 - * - * // Take all elements - * const allItems = MutableList.takeAll(list) - * console.log(allItems) // ["apple", "banana", "cherry"] - * console.log(list.length) // 0 - * - * // Useful for converting to array and clearing - * const queue = MutableList.make() - * MutableList.appendAll(queue, [1, 2, 3, 4, 5]) - * - * const snapshot = MutableList.takeAll(queue) - * console.log("Queue contents:", snapshot) - * console.log("Queue is now empty:", queue.length === 0) - * - * // Drain pattern for processing - * function drainAndProcess( - * list: MutableList.MutableList, - * processor: (items: Array) => void - * ) { - * if (list.length > 0) { - * const items = MutableList.takeAll(list) - * processor(items) - * } - * } + * MutableList.takeAll(list) // => ["apple", "banana", "cherry"] + * list.length // => 0 * ``` * - * @category elements + * @category mutations * @since 4.0.0 */ export const takeAll = (self: MutableList): Array => takeN(self, self.length) @@ -741,46 +527,18 @@ export const takeAll = (self: MutableList): Array => takeN(self, self.l * * **Example** (Taking one element) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.appendAll(list, ["first", "second", "third"]) * - * // Take elements one by one - * console.log(MutableList.take(list)) // "first" - * console.log(list.length) // 2 - * - * console.log(MutableList.take(list)) // "second" - * console.log(MutableList.take(list)) // "third" - * console.log(list.length) // 0 - * - * // Take from empty list - * console.log(MutableList.take(list)) // Empty symbol - * - * // Check for empty using the Empty symbol - * const result = MutableList.take(list) - * if (result === MutableList.Empty) { - * console.log("List is empty") - * } else { - * console.log("Got element:", result) - * } - * - * // Consumer pattern - * function processNext( - * queue: MutableList.MutableList, - * processor: (item: T) => void - * ): boolean { - * const item = MutableList.take(queue) - * if (item !== MutableList.Empty) { - * processor(item) - * return true - * } - * return false - * } + * MutableList.take(list) // => "first" + * MutableList.toArray(list) // => ["second", "third"] + * list.length // => 2 * ``` * - * @category elements + * @category mutations * @since 4.0.0 */ export const take = (self: MutableList): Empty | A => { @@ -808,12 +566,19 @@ export const take = (self: MutableList): Empty | A => { * Use when you need to inspect or snapshot a bounded prefix of the list without * consuming it. * + * **Details** + * + * Finite fractional values of `n` are rounded down. `NaN` and non-positive + * values return an empty array. + * * @see {@link takeN} for removing up to `n` values and returning them as an array * - * @category elements + * @category converting * @since 4.0.0 */ export const toArrayN = (self: MutableList, n: number): Array => { + n = Count.normalize(n) + if (n <= 0) return [] const length = Math.min(n, self.length) const out = new Array(length) let index = 0 @@ -839,7 +604,7 @@ export const toArrayN = (self: MutableList, n: number): Array => { * * @see {@link takeAll} for converting all elements to an array and clearing the list * - * @category elements + * @category converting * @since 4.0.0 */ export const toArray = (self: MutableList): Array => toArrayN(self, self.length) @@ -850,39 +615,15 @@ export const toArray = (self: MutableList): Array => toArrayN(self, sel * * **Example** (Filtering in place) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.appendAll(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) * - * console.log(list.length) // 10 - * - * // Keep only even numbers * MutableList.filter(list, (n) => n % 2 === 0) * - * console.log(MutableList.takeAll(list)) // [2, 4, 6, 8, 10] - * - * // Filter with index - * const indexed = MutableList.make() - * MutableList.appendAll(indexed, ["a", "b", "c", "d", "e"]) - * - * // Keep elements at even indices - * MutableList.filter(indexed, (value, index) => index % 2 === 0) - * console.log(MutableList.takeAll(indexed)) // ["a", "c", "e"] - * - * // Real-world example: filtering a log queue - * const logs = MutableList.make<{ level: string; message: string }>() - * MutableList.appendAll(logs, [ - * { level: "INFO", message: "App started" }, - * { level: "ERROR", message: "Connection failed" }, - * { level: "DEBUG", message: "Cache hit" }, - * { level: "ERROR", message: "Timeout" } - * ]) - * - * // Keep only errors - * MutableList.filter(logs, (log) => log.level === "ERROR") - * console.log(MutableList.takeAll(logs).map((log) => log.message)) // ["Connection failed", "Timeout"] + * MutableList.toArray(list) // => [2, 4, 6, 8, 10] * ``` * * @category mutations @@ -899,6 +640,10 @@ export const filter = (self: MutableList, f: (value: A, i: number) => bool } chunk = chunk.next } + if (array.length === 0) { + clear(self) + return + } self.head = self.tail = { array, mutable: true, @@ -928,37 +673,15 @@ export const filter = (self: MutableList, f: (value: A, i: number) => bool * * **Example** (Removing matching values) * - * ```ts + * ```ts import.meta.vitest * import { MutableList } from "effect" * * const list = MutableList.make() * MutableList.appendAll(list, ["apple", "banana", "apple", "cherry", "apple"]) * - * console.log(list.length) // 5 - * - * // Remove all occurrences of "apple" * MutableList.remove(list, "apple") * - * console.log(MutableList.takeAll(list)) // ["banana", "cherry"] - * - * // Remove non-existent value (no effect) - * const colors = MutableList.make() - * MutableList.appendAll(colors, ["red", "blue"]) - * MutableList.remove(colors, "green") - * console.log(MutableList.takeAll(colors)) // ["red", "blue"] - * - * // Real-world example: removing completed tasks - * const tasks = MutableList.make<{ id: number; status: string }>() - * MutableList.appendAll(tasks, [ - * { id: 1, status: "pending" }, - * { id: 2, status: "completed" }, - * { id: 3, status: "pending" }, - * { id: 4, status: "completed" } - * ]) - * - * // Remove completed tasks by filtering status - * MutableList.filter(tasks, (task) => task.status !== "completed") - * console.log(MutableList.takeAll(tasks).map((task) => task.id)) // [1, 3] + * MutableList.toArray(list) // => ["banana", "cherry"] * ``` * * @category mutations diff --git a/packages/effect/src/MutableRef.ts b/packages/effect/src/MutableRef.ts index b8a042e84..acd270380 100644 --- a/packages/effect/src/MutableRef.ts +++ b/packages/effect/src/MutableRef.ts @@ -32,19 +32,20 @@ const TypeId = "~effect/MutableRef" * * **Example** (Creating and updating refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * // Create a mutable reference * const ref: MutableRef.MutableRef = MutableRef.make(42) * * // Read the current value - * console.log(ref.current) // 42 - * console.log(MutableRef.get(ref)) // 42 + * ref.current // => 42 + * MutableRef.get(ref) // => 42 * * // Update the value * ref.current = 100 - * console.log(MutableRef.get(ref)) // 100 + * + * MutableRef.get(ref) // => 100 * * // Use with complex types * interface Config { @@ -59,7 +60,8 @@ const TypeId = "~effect/MutableRef" * * // Update through the interface * config.current = { timeout: 10000, retries: 5 } - * console.log(config.current.timeout) // 10000 + * + * config.current // => { timeout: 10000, retries: 5 } * ``` * * @category models @@ -90,21 +92,24 @@ const MutableRefProto: Omit, "current"> = { * * **Example** (Creating mutable refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * // Create a counter reference * const counter = MutableRef.make(0) - * console.log(MutableRef.get(counter)) // 0 + * + * MutableRef.get(counter) // => 0 * * // Create a configuration reference * const config = MutableRef.make({ debug: false, timeout: 5000 }) - * console.log(MutableRef.get(config)) // { debug: false, timeout: 5000 } + * + * MutableRef.get(config) // => { debug: false, timeout: 5000 } * * // Create a string reference * const status = MutableRef.make("idle") * MutableRef.set(status, "running") - * console.log(MutableRef.get(status)) // "running" + * + * MutableRef.get(status) // => "running" * ``` * * @category constructors @@ -128,20 +133,22 @@ export const make = (value: T): MutableRef => { * * **Example** (Comparing and setting values) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const ref = MutableRef.make("initial") * * // Successful compare and set * const updated = MutableRef.compareAndSet(ref, "initial", "updated") - * console.log(updated) // true - * console.log(MutableRef.get(ref)) // "updated" + * + * updated // => true + * MutableRef.get(ref) // => "updated" * * // Failed compare and set (value doesn't match) * const failed = MutableRef.compareAndSet(ref, "initial", "failed") - * console.log(failed) // false - * console.log(MutableRef.get(ref)) // "updated" (unchanged) + * + * failed // => false + * MutableRef.get(ref) // => "updated" * * // Thread-safe counter increment * const counter = MutableRef.make(5) @@ -150,12 +157,16 @@ export const make = (value: T): MutableRef => { * current = MutableRef.get(counter) * } while (!MutableRef.compareAndSet(counter, current, current + 1)) * + * MutableRef.get(counter) // => 6 + * * // Pipe-able version * const casUpdate = MutableRef.compareAndSet("updated", "final") - * console.log(casUpdate(ref)) // true + * + * casUpdate(ref) // => true + * MutableRef.get(ref) // => "final" * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const compareAndSet: { @@ -182,29 +193,32 @@ export const compareAndSet: { * * **Example** (Decrementing numeric refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Decrement the counter * MutableRef.decrement(counter) - * console.log(MutableRef.get(counter)) // 4 + * + * MutableRef.get(counter) // => 4 * * // Chain operations * MutableRef.decrement(counter) * MutableRef.decrement(counter) - * console.log(MutableRef.get(counter)) // 2 + * + * MutableRef.get(counter) // => 2 * * // Useful for countdown scenarios * const countdown = MutableRef.make(10) * while (MutableRef.get(countdown) > 0) { - * console.log(MutableRef.get(countdown)) * MutableRef.decrement(countdown) * } + * + * MutableRef.get(countdown) // => 0 * ``` * - * @category numeric + * @category mutations * @since 2.0.0 */ export const decrement = (self: MutableRef): MutableRef => update(self, (n) => n - 1) @@ -219,29 +233,35 @@ export const decrement = (self: MutableRef): MutableRef => updat * * **Example** (Decrementing and reading refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Decrement and get the new value * const newValue = MutableRef.decrementAndGet(counter) - * console.log(newValue) // 4 - * console.log(MutableRef.get(counter)) // 4 + * + * newValue // => 4 + * MutableRef.get(counter) // => 4 * * // Use in expressions * const lives = MutableRef.make(3) - * console.log(`Lives remaining: ${MutableRef.decrementAndGet(lives)}`) // "Lives remaining: 2" + * const message = `Lives remaining: ${MutableRef.decrementAndGet(lives)}` + * + * message // => "Lives remaining: 2" * * // Conditional logic based on decremented value * const attempts = MutableRef.make(3) + * let retries = 0 * while (MutableRef.decrementAndGet(attempts) >= 0) { - * console.log("Retrying...") - * // retry logic + * retries += 1 * } + * + * retries // => 3 + * MutableRef.get(attempts) // => -1 * ``` * - * @category numeric + * @category mutations * @since 2.0.0 */ export const decrementAndGet = (self: MutableRef): number => updateAndGet(self, (n) => n - 1) @@ -255,27 +275,31 @@ export const decrementAndGet = (self: MutableRef): number => updateAndGe * * **Example** (Reading current values) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const ref = MutableRef.make("hello") - * console.log(MutableRef.get(ref)) // "hello" + * + * MutableRef.get(ref) // => "hello" * * MutableRef.set(ref, "world") - * console.log(MutableRef.get(ref)) // "world" + * + * MutableRef.get(ref) // => "world" * * // Reading complex objects * const config = MutableRef.make({ port: 3000, host: "localhost" }) * const currentConfig = MutableRef.get(config) - * console.log(currentConfig.port) // 3000 + * + * currentConfig // => { port: 3000, host: "localhost" } * * // Multiple reads return the same value * const value1 = MutableRef.get(ref) * const value2 = MutableRef.get(ref) - * console.log(value1 === value2) // true + * + * value1 === value2 // => true * ``` * - * @category general + * @category getters * @since 2.0.0 */ export const get = (self: MutableRef): T => self.current @@ -289,30 +313,38 @@ export const get = (self: MutableRef): T => self.current * * **Example** (Reading before decrementing) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Get current value and then decrement * const previousValue = MutableRef.getAndDecrement(counter) - * console.log(previousValue) // 5 - * console.log(MutableRef.get(counter)) // 4 + * + * previousValue // => 5 + * MutableRef.get(counter) // => 4 * * // Useful for processing where you need the original value * const itemsLeft = MutableRef.make(10) + * const processedItems: Array = [] * while (MutableRef.get(itemsLeft) > 0) { * const currentItem = MutableRef.getAndDecrement(itemsLeft) - * console.log(`Processing item ${currentItem}`) + * processedItems.push(currentItem) * } * + * processedItems // => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] + * MutableRef.get(itemsLeft) // => 0 + * * // Post-decrement semantics (like i-- in other languages) * const index = MutableRef.make(3) * const currentIndex = MutableRef.getAndDecrement(index) - * console.log(`Current: ${currentIndex}, Next: ${MutableRef.get(index)}`) // "Current: 3, Next: 2" + * const nextIndex = MutableRef.get(index) + * + * currentIndex // => 3 + * nextIndex // => 2 * ``` * - * @category numeric + * @category mutations * @since 2.0.0 */ export const getAndDecrement = (self: MutableRef): number => getAndUpdate(self, (n) => n - 1) @@ -326,38 +358,45 @@ export const getAndDecrement = (self: MutableRef): number => getAndUpdat * * **Example** (Reading before incrementing) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Get current value and then increment * const previousValue = MutableRef.getAndIncrement(counter) - * console.log(previousValue) // 5 - * console.log(MutableRef.get(counter)) // 6 + * + * previousValue // => 5 + * MutableRef.get(counter) // => 6 * * // Useful for ID generation * const idGenerator = MutableRef.make(0) * const getId = () => MutableRef.getAndIncrement(idGenerator) + * const ids = [getId(), getId(), getId()] * - * console.log(getId()) // 0 - * console.log(getId()) // 1 - * console.log(getId()) // 2 + * ids // => [0, 1, 2] * * // Post-increment semantics (like i++ in other languages) * const position = MutableRef.make(0) * const currentPos = MutableRef.getAndIncrement(position) - * console.log(`Was at: ${currentPos}, Now at: ${MutableRef.get(position)}`) // "Was at: 0, Now at: 1" + * const nextPos = MutableRef.get(position) + * + * currentPos // => 0 + * nextPos // => 1 * * // Useful for iteration counters * const iterations = MutableRef.make(0) + * const visited: Array = [] * while (MutableRef.get(iterations) < 5) { * const iteration = MutableRef.getAndIncrement(iterations) - * console.log(`Iteration ${iteration}`) + * visited.push(iteration) * } + * + * visited // => [0, 1, 2, 3, 4] + * MutableRef.get(iterations) // => 5 * ``` * - * @category numeric + * @category mutations * @since 2.0.0 */ export const getAndIncrement = (self: MutableRef): number => getAndUpdate(self, (n) => n + 1) @@ -372,34 +411,41 @@ export const getAndIncrement = (self: MutableRef): number => getAndUpdat * * **Example** (Reading before setting) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const ref = MutableRef.make("old") * * // Set new value and get the previous one * const previous = MutableRef.getAndSet(ref, "new") - * console.log(previous) // "old" - * console.log(MutableRef.get(ref)) // "new" + * + * previous // => "old" + * MutableRef.get(ref) // => "new" * * // Swapping values * const counter = MutableRef.make(5) * const oldValue = MutableRef.getAndSet(counter, 10) - * console.log(`Changed from ${oldValue} to ${MutableRef.get(counter)}`) // "Changed from 5 to 10" + * const newValue = MutableRef.get(counter) + * + * oldValue // => 5 + * newValue // => 10 * * // Pipe-able version * const setValue = MutableRef.getAndSet("final") * const previousValue = setValue(ref) - * console.log(previousValue) // "new" + * + * previousValue // => "new" + * MutableRef.get(ref) // => "final" * * // Useful for atomic swaps in algorithms * const buffer = MutableRef.make>(["a", "b", "c"]) * const oldBuffer = MutableRef.getAndSet(buffer, []) - * console.log(oldBuffer) // ["a", "b", "c"] - * console.log(MutableRef.get(buffer)) // [] + * + * oldBuffer // => ["a", "b", "c"] + * MutableRef.get(buffer) // => [] * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const getAndSet: { @@ -425,40 +471,46 @@ export const getAndSet: { * * **Example** (Reading before updating) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Increment and get the old value * const oldValue = MutableRef.getAndUpdate(counter, (n) => n + 1) - * console.log(oldValue) // 5 - * console.log(MutableRef.get(counter)) // 6 + * + * oldValue // => 5 + * MutableRef.get(counter) // => 6 * * // Double the value and get the previous one * const previous = MutableRef.getAndUpdate(counter, (n) => n * 2) - * console.log(previous) // 6 - * console.log(MutableRef.get(counter)) // 12 + * + * previous // => 6 + * MutableRef.get(counter) // => 12 * * // Transform string and get old value * const message = MutableRef.make("hello") * const oldMessage = MutableRef.getAndUpdate(message, (s) => s.toUpperCase()) - * console.log(oldMessage) // "hello" - * console.log(MutableRef.get(message)) // "HELLO" + * + * oldMessage // => "hello" + * MutableRef.get(message) // => "HELLO" * * // Pipe-able version * const addOne = MutableRef.getAndUpdate((n: number) => n + 1) * const result = addOne(counter) - * console.log(result) // Previous value before increment + * + * result // => 12 + * MutableRef.get(counter) // => 13 * * // Useful for implementing atomic operations * const list = MutableRef.make>([1, 2, 3]) * const oldList = MutableRef.getAndUpdate(list, (arr) => [...arr, 4]) - * console.log(oldList) // [1, 2, 3] - * console.log(MutableRef.get(list)) // [1, 2, 3, 4] + * + * oldList // => [1, 2, 3] + * MutableRef.get(list) // => [1, 2, 3, 4] * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const getAndUpdate: { @@ -479,32 +531,37 @@ export const getAndUpdate: { * * **Example** (Incrementing numeric refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Increment the counter * MutableRef.increment(counter) - * console.log(MutableRef.get(counter)) // 6 + * + * MutableRef.get(counter) // => 6 * * // Chain operations * MutableRef.increment(counter) * MutableRef.increment(counter) - * console.log(MutableRef.get(counter)) // 8 + * + * MutableRef.get(counter) // => 8 * * // Useful for simple counting * const visits = MutableRef.make(0) * MutableRef.increment(visits) // User visited * MutableRef.increment(visits) // Another visit - * console.log(MutableRef.get(visits)) // 2 + * + * MutableRef.get(visits) // => 2 * * // Returns the reference for chaining * const result = MutableRef.increment(counter) - * console.log(result === counter) // true + * + * result === counter // => true + * MutableRef.get(counter) // => 9 * ``` * - * @category numeric + * @category mutations * @since 2.0.0 */ export const increment = (self: MutableRef): MutableRef => update(self, (n) => n + 1) @@ -519,33 +576,38 @@ export const increment = (self: MutableRef): MutableRef => updat * * **Example** (Incrementing and reading refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Increment and get the new value * const newValue = MutableRef.incrementAndGet(counter) - * console.log(newValue) // 6 - * console.log(MutableRef.get(counter)) // 6 + * + * newValue // => 6 + * MutableRef.get(counter) // => 6 * * // Use in expressions * const score = MutableRef.make(100) - * console.log(`New score: ${MutableRef.incrementAndGet(score)}`) // "New score: 101" + * const message = `New score: ${MutableRef.incrementAndGet(score)}` + * + * message // => "New score: 101" * * // Pre-increment semantics (like ++i in other languages) * const level = MutableRef.make(0) * const nextLevel = MutableRef.incrementAndGet(level) - * console.log(`Reached level ${nextLevel}`) // "Reached level 1" + * + * nextLevel // => 1 * * // Conditional logic based on incremented value * const attempts = MutableRef.make(0) - * if (MutableRef.incrementAndGet(attempts) > 3) { - * console.log("Too many attempts") - * } + * const tooManyAttempts = MutableRef.incrementAndGet(attempts) > 3 + * + * tooManyAttempts // => false + * MutableRef.get(attempts) // => 1 * ``` * - * @category numeric + * @category mutations * @since 2.0.0 */ export const incrementAndGet = (self: MutableRef): number => updateAndGet(self, (n) => n + 1) @@ -560,38 +622,44 @@ export const incrementAndGet = (self: MutableRef): number => updateAndGe * * **Example** (Setting values) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const ref = MutableRef.make("initial") * * // Set a new value * MutableRef.set(ref, "updated") - * console.log(MutableRef.get(ref)) // "updated" + * + * MutableRef.get(ref) // => "updated" * * // Chain set operations (since it returns the ref) * const result = MutableRef.set(ref, "final") - * console.log(result === ref) // true (same reference) - * console.log(MutableRef.get(ref)) // "final" + * + * result === ref // => true + * MutableRef.get(ref) // => "final" * * // Set complex objects * const config = MutableRef.make({ debug: false, verbose: false }) * MutableRef.set(config, { debug: true, verbose: true }) - * console.log(MutableRef.get(config)) // { debug: true, verbose: true } + * + * MutableRef.get(config) // => { debug: true, verbose: true } * * // Pipe-able version * const setValue = MutableRef.set("new value") * setValue(ref) - * console.log(MutableRef.get(ref)) // "new value" + * + * MutableRef.get(ref) // => "new value" * * // Useful for state management * const state = MutableRef.make<"idle" | "loading" | "success" | "error">("idle") * MutableRef.set(state, "loading") * // ... perform async operation * MutableRef.set(state, "success") + * + * MutableRef.get(state) // => "success" * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const set: { @@ -615,34 +683,40 @@ export const set: { * * **Example** (Setting and reading values) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const ref = MutableRef.make("old") * * // Set and get the new value * const newValue = MutableRef.setAndGet(ref, "new") - * console.log(newValue) // "new" - * console.log(MutableRef.get(ref)) // "new" + * + * newValue // => "new" + * MutableRef.get(ref) // => "new" * * // Useful for assignments that need the value * const counter = MutableRef.make(0) * const currentValue = MutableRef.setAndGet(counter, 42) - * console.log(`Counter set to: ${currentValue}`) // "Counter set to: 42" + * + * currentValue // => 42 * * // Pipe-able version * const setValue = MutableRef.setAndGet("final") * const result = setValue(ref) - * console.log(result) // "final" + * + * result // => "final" * * // Difference from set: returns value instead of reference * const ref1 = MutableRef.make(1) * const returnedRef = MutableRef.set(ref1, 2) // Returns MutableRef * const returnedValue = MutableRef.setAndGet(ref1, 3) // Returns value - * console.log(returnedValue) // 3 + * + * returnedRef === ref1 // => true + * returnedValue // => 3 + * MutableRef.get(ref1) // => 3 * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const setAndGet: { @@ -667,42 +741,48 @@ export const setAndGet: { * * **Example** (Updating values) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Increment the counter * MutableRef.update(counter, (n) => n + 1) - * console.log(MutableRef.get(counter)) // 6 + * + * MutableRef.get(counter) // => 6 * * // Chain updates (since it returns the ref) * const result = MutableRef.update(counter, (n) => n * 2) - * console.log(result === counter) // true (same reference) - * console.log(MutableRef.get(counter)) // 12 + * + * result === counter // => true + * MutableRef.get(counter) // => 12 * * // Transform string * const message = MutableRef.make("hello") * MutableRef.update(message, (s) => s.toUpperCase()) - * console.log(MutableRef.get(message)) // "HELLO" + * + * MutableRef.get(message) // => "HELLO" * * // Update complex objects * const user = MutableRef.make({ name: "Alice", age: 30 }) * MutableRef.update(user, (u) => ({ ...u, age: u.age + 1 })) - * console.log(MutableRef.get(user)) // { name: "Alice", age: 31 } + * + * MutableRef.get(user) // => { name: "Alice", age: 31 } * * // Pipe-able version * const double = MutableRef.update((n: number) => n * 2) * double(counter) - * console.log(MutableRef.get(counter)) // 24 + * + * MutableRef.get(counter) // => 24 * * // Array operations * const list = MutableRef.make>([1, 2, 3]) * MutableRef.update(list, (arr) => [...arr, 4]) - * console.log(MutableRef.get(list)) // [1, 2, 3, 4] + * + * MutableRef.get(list) // => [1, 2, 3, 4] * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const update: { @@ -724,44 +804,50 @@ export const update: { * * **Example** (Updating and reading values) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const counter = MutableRef.make(5) * * // Increment and get the new value * const newValue = MutableRef.updateAndGet(counter, (n) => n + 1) - * console.log(newValue) // 6 - * console.log(MutableRef.get(counter)) // 6 + * + * newValue // => 6 + * MutableRef.get(counter) // => 6 * * // Double the value and get the result * const doubled = MutableRef.updateAndGet(counter, (n) => n * 2) - * console.log(doubled) // 12 + * + * doubled // => 12 * * // Transform string and get result * const message = MutableRef.make("hello") * const upperCase = MutableRef.updateAndGet(message, (s) => s.toUpperCase()) - * console.log(upperCase) // "HELLO" + * + * upperCase // => "HELLO" * * // Pipe-able version * const increment = MutableRef.updateAndGet((n: number) => n + 1) * const result = increment(counter) - * console.log(result) // 13 (new value) + * + * result // => 13 * * // Useful for calculations that need the result * const score = MutableRef.make(100) * const bonus = 50 * const newScore = MutableRef.updateAndGet(score, (s) => s + bonus) - * console.log(`New score: ${newScore}`) // "New score: 150" + * + * newScore // => 150 * * // Array transformations * const list = MutableRef.make>([1, 2, 3]) * const newList = MutableRef.updateAndGet(list, (arr) => arr.map((x) => x * 2)) - * console.log(newList) // [2, 4, 6] - * console.log(MutableRef.get(list)) // [2, 4, 6] + * + * newList // => [2, 4, 6] + * MutableRef.get(list) // => [2, 4, 6] * ``` * - * @category general + * @category mutations * @since 2.0.0 */ export const updateAndGet: { @@ -783,40 +869,45 @@ export const updateAndGet: { * * **Example** (Toggling boolean refs) * - * ```ts + * ```ts import.meta.vitest * import { MutableRef } from "effect" * * const flag = MutableRef.make(false) * * // Toggle the flag * MutableRef.toggle(flag) - * console.log(MutableRef.get(flag)) // true + * + * MutableRef.get(flag) // => true * * // Toggle again * MutableRef.toggle(flag) - * console.log(MutableRef.get(flag)) // false + * + * MutableRef.get(flag) // => false * * // Useful for state switches * const isVisible = MutableRef.make(true) * MutableRef.toggle(isVisible) // Hide - * console.log(MutableRef.get(isVisible)) // false + * + * MutableRef.get(isVisible) // => false * * // Toggle button implementation * const darkMode = MutableRef.make(false) * const toggleDarkMode = () => { * MutableRef.toggle(darkMode) - * console.log(`Dark mode: ${MutableRef.get(darkMode) ? "ON" : "OFF"}`) + * return MutableRef.get(darkMode) ? "ON" : "OFF" * } * - * toggleDarkMode() // "Dark mode: ON" - * toggleDarkMode() // "Dark mode: OFF" + * toggleDarkMode() // => "ON" + * toggleDarkMode() // => "OFF" * * // Returns the reference for chaining * const result = MutableRef.toggle(flag) - * console.log(result === flag) // true + * + * result === flag // => true + * MutableRef.get(flag) // => true * ``` * - * @category boolean + * @category mutations * @since 2.0.0 */ export const toggle = (self: MutableRef): MutableRef => update(self, (_) => !_) diff --git a/packages/effect/src/Newtype.ts b/packages/effect/src/Newtype.ts index ea64786d9..4df67075f 100644 --- a/packages/effect/src/Newtype.ts +++ b/packages/effect/src/Newtype.ts @@ -36,14 +36,19 @@ const TypeId = "~effect/Newtype" * * **Example** (Defining a newtype) * - * ```ts + * ```ts import.meta.vitest * import { Newtype } from "effect" * * interface UserId extends Newtype.Newtype<"UserId", number> {} * interface OrderId extends Newtype.Newtype<"OrderId", number> {} * + * const userId = Newtype.makeIso().set(1) * // UserId and OrderId are not assignable to each other * // even though both wrap `number`. + * // @ts-expect-error + * const orderId: OrderId = userId + * + * Newtype.value(userId) // => 1 * ``` * * @see {@link makeIso} — create an iso to wrap and unwrap @@ -125,7 +130,7 @@ export declare namespace Newtype { * * **Example** (Unwrapping a newtype) * - * ```ts + * ```ts import.meta.vitest * import { Newtype } from "effect" * * interface Label extends Newtype.Newtype<"Label", string> {} @@ -133,7 +138,8 @@ export declare namespace Newtype { * const iso = Newtype.makeIso(data: NonEmptyIterable.NonEmptyIterable): A { - * // Safe - guaranteed to have at least one element - * const [first] = NonEmptyIterable.unprepend(data) - * return first - * } - * - * // Works with any non-empty iterable - * const numbers = Array.make( - * 1, - * 2, - * 3 - * ) as unknown as NonEmptyIterable.NonEmptyIterable - * const firstNumber = getFirst(numbers) // 1 - * - * const chars = "hello" as unknown as NonEmptyIterable.NonEmptyIterable - * const firstChar = getFirst(chars) // "h" - * - * const entries = new Map([["a", 1], [ - * "b", - * 2 - * ]]) as unknown as NonEmptyIterable.NonEmptyIterable<[string, number]> - * const firstEntry = getFirst(entries) // ["a", 1] - * - * // Custom generator - * function* countdown(): Generator { - * yield 3 - * yield 2 - * yield 1 - * } - * const firstCount = getFirst( - * Chunk.fromIterable( - * countdown() - * ) as unknown as NonEmptyIterable.NonEmptyIterable - * ) // 3 - * ``` - * * @category models * @since 2.0.0 */ @@ -109,101 +67,13 @@ export interface NonEmptyIterable extends Iterable { * * **Example** (Extracting first and remaining elements) * - * ```ts - * import { Array, Chunk, NonEmptyIterable } from "effect" - * - * // Helper to make iterator iterable for Array.from - * const iteratorToIterable = (iterator: Iterator): Iterable => ({ - * [Symbol.iterator]() { - * return iterator - * } - * }) - * - * // With NonEmptyArray from Array.make (cast to NonEmptyIterable) - * const numbers = Array.make( - * 1, - * 2, - * 3, - * 4, - * 5 - * ) as unknown as NonEmptyIterable.NonEmptyIterable - * const [first, rest] = NonEmptyIterable.unprepend(numbers) - * console.log(first) // 1 - * console.log(globalThis.Array.from(iteratorToIterable(rest))) // [2, 3, 4, 5] - * - * // With strings (assert when known to be non-empty) - * const text = "hello" as unknown as NonEmptyIterable.NonEmptyIterable - * const [firstChar, restChars] = NonEmptyIterable.unprepend(text) - * console.log(firstChar) // "h" - * console.log(globalThis.Array.from(iteratorToIterable(restChars)).join("")) // "ello" - * - * // With Sets (assert when known to be non-empty) - * const uniqueNumbers = new Set([ - * 10, - * 20, - * 30 - * ]) as unknown as NonEmptyIterable.NonEmptyIterable - * const [firstUnique, restUnique] = NonEmptyIterable.unprepend(uniqueNumbers) - * console.log(firstUnique) // 10 (or any element, Set order is not guaranteed) - * console.log(globalThis.Array.from(iteratorToIterable(restUnique))) // [20, 30] (in some order) - * - * // With Maps (assert when known to be non-empty) - * const keyValuePairs = new Map([["a", 1], ["b", 2], [ - * "c", - * 3 - * ]]) as unknown as NonEmptyIterable.NonEmptyIterable<[string, number]> - * const [firstPair, restPairs] = NonEmptyIterable.unprepend(keyValuePairs) - * console.log(firstPair) // ["a", 1] - * console.log(globalThis.Array.from(iteratorToIterable(restPairs))) // [["b", 2], ["c", 3]] - * - * // With custom generators - * function* fibonacci(): Generator { - * let a = 1, b = 1 - * yield a - * for (let i = 0; i < 10; i++) { - * yield b - * const next = a + b - * a = b - * b = next - * } - * } - * - * const generator = Chunk.fromIterable( - * fibonacci() - * ) as unknown as NonEmptyIterable.NonEmptyIterable - * const [firstFib, restFib] = NonEmptyIterable.unprepend(generator) - * console.log(firstFib) // 1 - * console.log(globalThis.Array.from(iteratorToIterable(restFib))) // [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - * - * // Practical usage: implementing reduce for non-empty iterables - * function reduceNonEmpty( - * data: NonEmptyIterable.NonEmptyIterable, - * f: (acc: B, current: A) => B, - * initial: B - * ): B { - * const [first, rest] = NonEmptyIterable.unprepend(data) - * let result = f(initial, first) - * - * // Convert iterator to iterable for iteration - * const iterable = { - * [Symbol.iterator]() { - * return rest - * } - * } - * for (const item of iterable) { - * result = f(result, item) - * } - * - * return result - * } - * - * const data = Array.make( - * 1, - * 2, - * 3, - * 4 - * ) as unknown as NonEmptyIterable.NonEmptyIterable - * const sum = reduceNonEmpty(data, (acc, x) => acc + x, 0) // 10 + * ```ts import.meta.vitest + * import { Chunk, NonEmptyIterable } from "effect" + * + * const [first, rest] = NonEmptyIterable.unprepend(Chunk.make(1, 2, 3)) + * + * first // => 1 + * globalThis.Array.from({ [Symbol.iterator]: () => rest }) // => [2, 3] * ``` * * @category getters diff --git a/packages/effect/src/Number.ts b/packages/effect/src/Number.ts index ecae93f19..b42e1c07e 100644 --- a/packages/effect/src/Number.ts +++ b/packages/effect/src/Number.ts @@ -32,14 +32,11 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Coercing values to numbers) * - * ```ts + * ```ts import.meta.vitest * import { Number as N } from "effect" * - * const num = N.Number("42") - * console.log(num) // 42 - * - * const float = N.Number("3.14") - * console.log(float) // 3.14 + * N.Number("42") // => 42 + * N.Number("3.14") // => 3.14 * ``` * * @category constructors @@ -56,12 +53,11 @@ export const Number = globalThis.Number * * **Example** (Checking for numbers) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.isNumber(2), true) - * assert.deepStrictEqual(Number.isNumber("2"), false) + * Number.isNumber(2) // => true + * Number.isNumber("2") // => false * ``` * * @category guards @@ -78,11 +74,10 @@ export const isNumber: (input: unknown) => input is number = predicate.isNumber * * **Example** (Adding numbers) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.sum(2, 3), 5) + * Number.sum(2, 3) // => 5 * ``` * * @see {@link sumAll} for summing an iterable of numbers @@ -104,11 +99,10 @@ export const sum: { * * **Example** (Multiplying numbers) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.multiply(2, 3), 6) + * Number.multiply(2, 3) // => 6 * ``` * * @see {@link multiplyAll} for multiplying an iterable of numbers @@ -130,11 +124,10 @@ export const multiply: { * * **Example** (Subtracting numbers) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.subtract(2, 3), -1) + * Number.subtract(2, 3) // => -1 * ``` * * @category math @@ -154,11 +147,11 @@ export const subtract: { * * **Example** (Dividing numbers safely) * - * ```ts - * import { Number } from "effect" + * ```ts import.meta.vitest + * import { Number, Option } from "effect" * - * Number.divide(6, 3) // Option.some(2) - * Number.divide(6, 0) // Option.none() + * Number.divide(6, 3) // => Option.some(2) + * Number.divide(6, 0) // => Option.none() * ``` * * @see {@link divideUnsafe} for division that throws when the divisor is zero @@ -189,12 +182,16 @@ export const divide: { * * **Example** (Dividing numbers unsafely) * - * ```ts - * import { Number } from "effect" + * ```ts import.meta.vitest + * import { Number, Result } from "effect" * - * console.log(Number.divideUnsafe(6, 3)) // 2 + * Number.divideUnsafe(6, 3) // => 2 * - * // Passing 0 as the divisor throws a RangeError("Division by zero"). + * const failure = Result.try({ + * try: () => Number.divideUnsafe(6, 0), + * catch: (error) => (error as Error).message + * }) + * Result.merge(failure) // => "Division by zero" * ``` * * @see {@link divide} for division that returns `Option.none` when the divisor is zero @@ -220,11 +217,10 @@ export const divideUnsafe: { * * **Example** (Incrementing a number) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.increment(2), 3) + * Number.increment(2) // => 3 * ``` * * @category math @@ -241,11 +237,10 @@ export const increment = (n: number): number => n + 1 * * **Example** (Decrementing a number) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.decrement(3), 2) + * Number.decrement(3) // => 2 * ``` * * @category math @@ -263,12 +258,12 @@ export const decrement = (n: number): number => n - 1 * * **Example** (Comparing numbers) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" * - * console.log(Number.Order(1, 2)) // -1 - * console.log(Number.Order(2, 1)) // 1 - * console.log(Number.Order(1, 1)) // 0 + * Number.Order(1, 2) // => -1 + * Number.Order(2, 1) // => 1 + * Number.Order(1, 1) // => 0 * ``` * * @category instances @@ -286,12 +281,12 @@ export const Order: order.Order = order.Number * * **Example** (Comparing numbers for equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" * - * console.log(Number.Equivalence(1, 1)) // true - * console.log(Number.Equivalence(1, 2)) // false - * console.log(Number.Equivalence(NaN, NaN)) // true + * Number.Equivalence(1, 1) // => true + * Number.Equivalence(1, 2) // => false + * Number.Equivalence(NaN, NaN) // => true * ``` * * @category instances @@ -308,13 +303,12 @@ export const Equivalence: Equ.Equivalence = Equ.Number * * **Example** (Checking less-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.isLessThan(2, 3), true) - * assert.deepStrictEqual(Number.isLessThan(3, 3), false) - * assert.deepStrictEqual(Number.isLessThan(4, 3), false) + * Number.isLessThan(2, 3) // => true + * Number.isLessThan(3, 3) // => false + * Number.isLessThan(4, 3) // => false * ``` * * @category predicates @@ -334,13 +328,12 @@ export const isLessThan: { * * **Example** (Checking less-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.isLessThanOrEqualTo(2, 3), true) - * assert.deepStrictEqual(Number.isLessThanOrEqualTo(3, 3), true) - * assert.deepStrictEqual(Number.isLessThanOrEqualTo(4, 3), false) + * Number.isLessThanOrEqualTo(2, 3) // => true + * Number.isLessThanOrEqualTo(3, 3) // => true + * Number.isLessThanOrEqualTo(4, 3) // => false * ``` * * @category predicates @@ -360,13 +353,12 @@ export const isLessThanOrEqualTo: { * * **Example** (Checking greater-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.isGreaterThan(2, 3), false) - * assert.deepStrictEqual(Number.isGreaterThan(3, 3), false) - * assert.deepStrictEqual(Number.isGreaterThan(4, 3), true) + * Number.isGreaterThan(2, 3) // => false + * Number.isGreaterThan(3, 3) // => false + * Number.isGreaterThan(4, 3) // => true * ``` * * @category predicates @@ -386,13 +378,12 @@ export const isGreaterThan: { * * **Example** (Checking greater-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.isGreaterThanOrEqualTo(2, 3), false) - * assert.deepStrictEqual(Number.isGreaterThanOrEqualTo(3, 3), true) - * assert.deepStrictEqual(Number.isGreaterThanOrEqualTo(4, 3), true) + * Number.isGreaterThanOrEqualTo(2, 3) // => false + * Number.isGreaterThanOrEqualTo(3, 3) // => true + * Number.isGreaterThanOrEqualTo(4, 3) // => true * ``` * * @category predicates @@ -412,15 +403,14 @@ export const isGreaterThanOrEqualTo: { * * **Example** (Checking inclusive ranges) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * * const between = Number.between({ minimum: 0, maximum: 5 }) * - * assert.deepStrictEqual(between(3), true) - * assert.deepStrictEqual(between(-1), false) - * assert.deepStrictEqual(between(6), false) + * between(3) // => true + * between(-1) // => false + * between(6) // => false * ``` * * @see {@link clamp} for forcing a number into an inclusive range @@ -451,18 +441,19 @@ export const between: { * - If the `number` is less than the `minimum` value, the function returns the `minimum` value. * - If the `number` is greater than the `maximum` value, the function returns the `maximum` value. * - Otherwise, it returns the original `number`. + * - `NaN` is ordered below every non-`NaN` number by `Number.Order`, so it is + * clamped to `minimum`. * * **Example** (Clamping to a range) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * * const clamp = Number.clamp({ minimum: 1, maximum: 5 }) * - * assert.equal(clamp(3), 3) - * assert.equal(clamp(0), 1) - * assert.equal(clamp(6), 5) + * clamp(3) // => 3 + * clamp(0) // => 1 + * clamp(6) // => 5 * ``` * * @see {@link between} for checking whether a number is already inside a range @@ -490,11 +481,10 @@ export const clamp: { * * **Example** (Finding the minimum) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.min(2, 3), 2) + * Number.min(2, 3) // => 2 * ``` * * @see {@link max} for selecting the larger value @@ -516,11 +506,10 @@ export const min: { * * **Example** (Finding the maximum) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.max(2, 3), 3) + * Number.max(2, 3) // => 3 * ``` * * @see {@link min} for selecting the smaller value @@ -542,13 +531,12 @@ export const max: { * * **Example** (Determining the sign) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.sign(-5), -1) - * assert.deepStrictEqual(Number.sign(0), 0) - * assert.deepStrictEqual(Number.sign(5), 1) + * Number.sign(-5) // => -1 + * Number.sign(0) // => 0 + * Number.sign(5) // => 1 * ``` * * @category math @@ -565,11 +553,10 @@ export const sign = (n: number): Ordering => Order(n, 0) * * **Example** (Summing an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.sumAll([2, 3, 4]), 9) + * Number.sumAll([2, 3, 4]) // => 9 * ``` * * @see {@link sum} for adding two numbers @@ -595,11 +582,10 @@ export const sumAll = (collection: Iterable): number => { * * **Example** (Multiplying an iterable) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.multiplyAll([2, 3, 4]), 24) + * Number.multiplyAll([2, 3, 4]) // => 24 * ``` * * @see {@link multiply} for multiplying two numbers @@ -629,13 +615,12 @@ export const multiplyAll = (collection: Iterable): number => { * * **Example** (Calculating remainders) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.remainder(2, 2), 0) - * assert.deepStrictEqual(Number.remainder(3, 2), 1) - * assert.deepStrictEqual(Number.remainder(-4, 2), -0) + * Number.remainder(2, 2) // => 0 + * Number.remainder(3, 2) // => 1 + * Number.remainder(-4, 2) // => -0 * ``` * * @see {@link divide} for quotient calculation with division-by-zero represented as `Option.none` @@ -647,23 +632,42 @@ export const remainder: { (divisor: number): (self: number) => number (self: number, divisor: number): number } = dual(2, (self: number, divisor: number): number => { - const selfDecCount = decimalCount(self) - const divisorDecCount = decimalCount(divisor) + const selfString = self.toString() + const divisorString = divisor.toString() + if (selfString.includes("e") || divisorString.includes("e")) { + if (!globalThis.Number.isFinite(self) || !globalThis.Number.isFinite(divisor) || divisor === 0) { + return NaN + } + return remainderWithScientificNotation(self, divisor) + } + const selfDecCount = (selfString.split(".")[1] || "").length + const divisorDecCount = (divisorString.split(".")[1] || "").length const decCount = selfDecCount > divisorDecCount ? selfDecCount : divisorDecCount - const selfInt = parseInt(self.toFixed(decCount).replace(".", "")) + const selfInt = self === 0 ? self : parseInt(self.toFixed(decCount).replace(".", "")) const divisorInt = parseInt(divisor.toFixed(decCount).replace(".", "")) return (selfInt % divisorInt) / Math.pow(10, decCount) }) -function decimalCount(n: number): number { - const s = n.toString() - const eIndex = s.indexOf("e-") - if (eIndex !== -1) { - const exp = parseInt(s.slice(eIndex + 2)) - const mantissaDecimals = (s.slice(0, eIndex).split(".")[1] || "").length - return mantissaDecimals + exp +function remainderWithScientificNotation(self: number, divisor: number): number { + const [selfCoefficient, selfExponent] = toScientificInteger(self) + const [divisorCoefficient, divisorExponent] = toScientificInteger(divisor) + const exponent = Math.min(selfExponent, divisorExponent) + const selfInteger = selfCoefficient * BigInt(10) ** BigInt(selfExponent - exponent) + const divisorInteger = divisorCoefficient * BigInt(10) ** BigInt(divisorExponent - exponent) + const out = selfInteger % divisorInteger + if (out === BigInt(0)) { + return self < 0 || Object.is(self, -0) ? -0 : 0 } - return (s.split(".")[1] || "").length + const remainder = globalThis.Number(`${out}e${exponent}`) + return remainder === 0 ? Math.sign(self) * globalThis.Number.MIN_VALUE : remainder +} + +function toScientificInteger(n: number): readonly [coefficient: bigint, exponent: number] { + const scientific = Math.abs(n).toExponential() + const eIndex = scientific.indexOf("e") + const digits = scientific.slice(0, eIndex).replace(".", "") + const coefficient = BigInt(digits) * (n < 0 ? -BigInt(1) : BigInt(1)) + return [coefficient, globalThis.Number(scientific.slice(eIndex + 1)) - digits.length + 1] } /** @@ -675,12 +679,11 @@ function decimalCount(n: number): number { * * **Example** (Finding the next power of two) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.nextPow2(5), 8) - * assert.deepStrictEqual(Number.nextPow2(17), 32) + * Number.nextPow2(5) // => 8 + * Number.nextPow2(17) // => 32 * ``` * * @category math @@ -701,15 +704,15 @@ export const nextPow2 = (n: number): number => { * * **Example** (Parsing numbers from strings) * - * ```ts - * import { Number } from "effect" + * ```ts import.meta.vitest + * import { Number, Option } from "effect" * - * Number.parse("42") // Option.some(42) - * Number.parse("3.14") // Option.some(3.14) - * Number.parse("NaN") // Option.some(NaN) - * Number.parse("Infinity") // Option.some(Infinity) - * Number.parse("-Infinity") // Option.some(-Infinity) - * Number.parse("not a number") // Option.none() + * Number.parse("42") // => Option.some(42) + * Number.parse("3.14") // => Option.some(3.14) + * Number.parse("NaN") // => Option.some(NaN) + * Number.parse("Infinity") // => Option.some(Infinity) + * Number.parse("-Infinity") // => Option.some(-Infinity) + * Number.parse("not a number") // => Option.none() * ``` * * @see {@link Number} for native constructor coercion @@ -743,12 +746,11 @@ export const parse = (s: string): Option.Option => { * * **Example** (Rounding with precision) * - * ```ts + * ```ts import.meta.vitest * import { Number } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Number.round(1.1234, 2), 1.12) - * assert.deepStrictEqual(Number.round(1.567, 2), 1.57) + * Number.round(1.1234, 2) // => 1.12 + * Number.round(1.567, 2) // => 1.57 * ``` * * @category math diff --git a/packages/effect/src/Optic.ts b/packages/effect/src/Optic.ts index e9b3de8d6..c21149a6f 100644 --- a/packages/effect/src/Optic.ts +++ b/packages/effect/src/Optic.ts @@ -12,16 +12,17 @@ * @since 4.0.0 */ -import { format } from "./Formatter.ts" -import { identity, memoize } from "./Function.ts" +import * as Arr from "./Array.ts" +import { dual, identity } from "./Function.ts" +import * as InternalRecord from "./internal/record.ts" import * as Option from "./Option.ts" import * as Predicate from "./Predicate.ts" import * as Result from "./Result.ts" import type * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" -import type * as SchemaIssue from "./SchemaIssue.ts" +import * as SchemaIssue from "./SchemaIssue.ts" import * as Struct from "./Struct.ts" -import type { IsUnion } from "./Types.ts" +import type { IsUnion, NoInfer } from "./Types.ts" /** * A lossless, reversible conversion between types `S` and `A`. @@ -41,7 +42,7 @@ import type { IsUnion } from "./Types.ts" * * **Example** (Converting between Celsius and Fahrenheit) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * const fahrenheit = Optic.makeIso( @@ -49,18 +50,16 @@ import type { IsUnion } from "./Types.ts" * (f) => (f - 32) * 5 / 9 * ) * - * console.log(fahrenheit.get(100)) - * // Output: 212 + * fahrenheit.get(100) // => 212 * - * console.log(fahrenheit.set(32)) - * // Output: 0 + * fahrenheit.set(32) // => 0 * ``` * * @see {@link makeIso} — constructor * @see {@link Lens} — when you only need a one-directional focus into a whole * @see {@link Prism} — when the focus may not be present * - * @category Iso + * @category models * @since 4.0.0 */ export interface Iso extends Lens, Prism {} @@ -79,7 +78,7 @@ export interface Iso extends Lens, Prism {} * * **Example** (Wrapping and unwrapping a branded type) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type Meters = { readonly value: number } @@ -88,11 +87,9 @@ export interface Iso extends Lens, Prism {} * (n) => ({ value: n }) * ) * - * console.log(meters.get({ value: 100 })) - * // Output: 100 + * meters.get({ value: 100 }) // => 100 * - * console.log(meters.set(42)) - * // Output: { value: 42 } + * meters.set(42) // => { value: 42 } * ``` * * @see {@link Iso} — the type this function returns @@ -102,7 +99,7 @@ export interface Iso extends Lens, Prism {} * @since 4.0.0 */ export function makeIso(get: (s: S) => A, set: (a: A) => S): Iso { - return make(new IsoNode(get, set)) + return make(primitiveNode("Iso", get, set)) } /** @@ -123,22 +120,21 @@ export function makeIso(get: (s: S) => A, set: (a: A) => S): Iso { * * **Example** (Focusing on a struct field) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type Person = { readonly name: string; readonly age: number } * * const _name = Optic.id().key("name") * - * console.log(_name.get({ name: "Alice", age: 30 })) - * // Output: "Alice" + * _name.get({ name: "Alice", age: 30 }) // => "Alice" * ``` * * @see {@link makeLens} — constructor * @see {@link Iso} — when conversion is lossless in both directions * @see {@link Optional} — when reading can also fail * - * @category Lens + * @category models * @since 4.0.0 */ export interface Lens extends Optional { @@ -160,7 +156,7 @@ export interface Lens extends Optional { * * **Example** (Focusing on the first element of a pair) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * const _first = Optic.makeLens( @@ -168,11 +164,9 @@ export interface Lens extends Optional { * (s, pair) => [s, pair[1]] * ) * - * console.log(_first.get(["hello", 42])) - * // Output: "hello" + * _first.get(["hello", 42]) // => "hello" * - * console.log(_first.replace("world", ["hello", 42])) - * // Output: ["world", 42] + * _first.replace("world", ["hello", 42]) // => ["world", 42] * ``` * * @see {@link Lens} — the type this function returns @@ -182,7 +176,7 @@ export interface Lens extends Optional { * @since 4.0.0 */ export function makeLens(get: (s: S) => A, replace: (a: A, s: S) => S): Lens { - return make(new LensNode(get, replace)) + return make(primitiveNode("Lens", get, replace)) } /** @@ -198,7 +192,7 @@ export function makeLens(get: (s: S) => A, replace: (a: A, s: S) => S): Le * **Details** * * - `getResult(s)` returns `Result.Success` when the focus matches, or - * `Result.Failure` with an error message. + * `Result.Failure` with a structured issue. * - `set(a)` always succeeds and returns a new `S`. * - Extends {@link Optional}. * - Composing two Prisms produces a Prism; composing a Prism with a @@ -206,7 +200,7 @@ export function makeLens(get: (s: S) => A, replace: (a: A, s: S) => S): Le * * **Example** (Narrowing a tagged union) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * type Shape = @@ -215,18 +209,16 @@ export function makeLens(get: (s: S) => A, replace: (a: A, s: S) => S): Le * * const _circle = Optic.id().tag("Circle") * - * console.log(Result.isSuccess(_circle.getResult({ _tag: "Circle", radius: 5 }))) - * // Output: true + * _circle.getResult({ _tag: "Circle", radius: 5 }) // => Result.succeed({ _tag: "Circle", radius: 5 }) * - * console.log(Result.isFailure(_circle.getResult({ _tag: "Rect", width: 10 }))) - * // Output: true + * Result.isFailure(_circle.getResult({ _tag: "Rect", width: 10 })) // => true * ``` * * @see {@link makePrism} — constructor * @see {@link fromChecks} — build a Prism from schema checks * @see {@link Lens} — when reading always succeeds * - * @category Prism + * @category models * @since 4.0.0 */ export interface Prism extends Optional { @@ -243,26 +235,27 @@ export interface Prism extends Optional { * * **Details** * - * - `getResult` should return `Result.fail(message)` on mismatch. + * - `getResult` should return `Result.fail(issue)` on mismatch. + * - Issues are not formatted automatically; callers choose how to render them. * * **Example** (Parsing a string to a number) * - * ```ts - * import { Optic, Result } from "effect" + * ```ts import.meta.vitest + * import { Optic, Result, SchemaIssue } from "effect" * * const numeric = Optic.makePrism( * (s) => { * const n = Number(s) - * return Number.isNaN(n) ? Result.fail("not a number") : Result.succeed(n) + * return Number.isNaN(n) + * ? Result.fail(new SchemaIssue.InvalidValue({ message: "not a number" })) + * : Result.succeed(n) * }, * String * ) * - * console.log(Result.isSuccess(numeric.getResult("42"))) - * // Output: true + * numeric.getResult("42") // => Result.succeed(42) * - * console.log(numeric.set(42)) - * // Output: "42" + * numeric.set(42) // => "42" * ``` * * @see {@link Prism} — the type this function returns @@ -271,8 +264,11 @@ export interface Prism extends Optional { * @category constructors * @since 4.0.0 */ -export function makePrism(getResult: (s: S) => Result.Result, set: (a: A) => S): Prism { - return make(new PrismNode(getResult, set)) +export function makePrism( + getResult: (s: S) => Result.Result, + set: (a: A) => S +): Prism { + return make(primitiveNode("Prism", getResult, set)) } /** @@ -286,13 +282,13 @@ export function makePrism(getResult: (s: S) => Result.Result, s * * **Details** * - * - `getResult` runs all checks; fails with a combined error message when + * - `getResult` runs all checks and preserves their structured issues when * any check fails. * - `set` is identity — the value passes through unchanged. * * **Example** (Creating a positive integer prism) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result, Schema } from "effect" * * const posInt = Optic.fromChecks( @@ -300,11 +296,9 @@ export function makePrism(getResult: (s: S) => Result.Result, s * Schema.isInt() * ) * - * console.log(Result.isSuccess(posInt.getResult(3))) - * // Output: true + * posInt.getResult(3) // => Result.succeed(3) * - * console.log(Result.isFailure(posInt.getResult(-1))) - * // Output: true + * Result.isFailure(posInt.getResult(-1)) // => true * ``` * * @see {@link makePrism} — constructor with custom getter/setter @@ -314,142 +308,95 @@ export function makePrism(getResult: (s: S) => Result.Result, s * @since 4.0.0 */ export function fromChecks(...checks: readonly [SchemaAST.Check, ...Array>]): Prism { - return make(new CheckNode(checks)) -} - -type Node = - | IdentityNode - | IsoNode - | LensNode - | PrismNode - | OptionalNode - | PathNode - | CheckNode - | CompositionNode - -class IdentityNode { - readonly _tag = "IdentityNode" + return make([new CheckNode(checks)]) } -const identityNode = new IdentityNode() +type Kind = "Iso" | "Lens" | "Prism" | "Optional" -class CompositionNode { - readonly _tag = "CompositionNode" - readonly nodes: readonly [Node, ...Array] - - constructor(nodes: readonly [Node, ...Array]) { - this.nodes = nodes - } +type Operation = { + readonly kind: Kind + readonly get: (s: any) => any + readonly set: (a: any, s?: any) => any } -class IsoNode { - readonly _tag = "IsoNode" - readonly get: (s: S) => A - readonly set: (a: A) => S - - constructor(get: (s: S) => A, set: (a: A) => S) { - this.get = get - this.set = set - } +type PrimitiveStep = Operation & { + readonly _tag: "PrimitiveNode" } -class LensNode { - readonly _tag = "LensNode" - readonly get: (s: S) => A - readonly set: (a: A, s: S) => S - - constructor(get: (s: S) => A, set: (a: A, s: S) => S) { - this.get = get - this.set = set - } -} +type Step = PrimitiveStep | PathNode | CheckNode -class PrismNode { - readonly _tag = "PrismNode" - readonly get: (s: S) => Result.Result - readonly set: (a: A) => S +type Node = ReadonlyArray - constructor(get: (s: S) => Result.Result, set: (a: A) => S) { - this.get = get - this.set = set - } +function primitiveNode(kind: Kind, get: (s: any) => any, set: (a: any, s?: any) => any): Node { + return [{ _tag: "PrimitiveNode", kind, get, set }] } -class OptionalNode { - readonly _tag = "OptionalNode" - readonly get: (s: S) => Result.Result - readonly set: (a: A, s: S) => Result.Result - - constructor(get: (s: S) => Result.Result, set: (a: A, s: S) => Result.Result) { - this.get = get - this.set = set - } +const identityOperation: Operation = { + kind: "Iso", + get: identity, + set: identity } class PathNode { readonly _tag = "PathNode" + readonly kind = "Lens" readonly path: ReadonlyArray + readonly get: (s: any) => any + readonly set: (a: any, s?: any) => any constructor(path: ReadonlyArray) { this.path = path + this.get = (s) => { + let out = s + for (let i = 0; i < path.length; i++) { + out = out[path[i]] + } + return out + } + this.set = (a, s) => { + const out = cloneShallow(s) + let current = out + let i = 0 + for (; i < path.length - 1; i++) { + const key = path[i] + InternalRecord.assignProperty(current, key, cloneShallow(current[key])) + current = current[key] + } + InternalRecord.assignProperty(current, path[i], a) + return out + } } } class CheckNode { readonly _tag = "CheckNode" + readonly kind = "Prism" readonly checks: readonly [SchemaAST.Check, ...Array>] + readonly get: (s: T) => Result.Result + readonly set = identity constructor(checks: readonly [SchemaAST.Check, ...Array>]) { this.checks = checks + this.get = (s) => SchemaAST.runChecks(checks, s) } } -// Nodes that can appear in a normalized chain (no Identity/Composition) -type NormalizedNode = Exclude - -// Fuse with tail when possible, else push. -function pushNormalized(acc: Array, node: NormalizedNode): void { - const last = acc[acc.length - 1] - if (last) { +function compose(a: Node, b: Node): Node { + if (a.length === 0) return b + if (b.length === 0) return a + const nodes = a.slice() + for (let i = 0; i < b.length; i++) { + const node = b[i] + const last = nodes[nodes.length - 1] if (last._tag === "PathNode" && node._tag === "PathNode") { - // fuse Path - acc[acc.length - 1] = new PathNode([...last.path, ...node.path]) - return + nodes[nodes.length - 1] = new PathNode([...last.path, ...node.path]) + } else if (last._tag === "CheckNode" && node._tag === "CheckNode") { + nodes[nodes.length - 1] = new CheckNode([...last.checks, ...node.checks]) + } else { + nodes.push(node) } - if (last._tag === "CheckNode" && node._tag === "CheckNode") { - // fuse Checks - acc[acc.length - 1] = new CheckNode([...last.checks, ...node.checks]) - return - } - } - acc.push(node) -} - -// Collect nodes from a node into `acc`, flattening & normalizing on the fly. -function collect(node: Node, acc: Array): void { - if (node._tag === "IdentityNode") return - if (node._tag === "CompositionNode") { - // flatten without extra arrays - for (let i = 0; i < node.nodes.length; i++) collect(node.nodes[i], acc) - return - } - // primitive node - pushNormalized(acc, node) -} - -function compose(a: Node, b: Node): Node { - const nodes: Array = [] - collect(a, nodes) - collect(b, nodes) - - switch (nodes.length) { - case 0: - return identityNode - case 1: - return nodes[0] - default: - return new CompositionNode(nodes as [Node, ...Array]) } + return nodes } type ForbidUnion = IsUnion extends true ? [Message] : [] @@ -466,47 +413,43 @@ type ForbidUnion = IsUnion extends true ? [Message * * **Details** * - * - `getResult(s)` returns `Result.Success` or `Result.Failure`. + * - `getResult(s)` returns `Result.Success` or `Result.Failure`. * - `replaceResult(a, s)` returns `Result.Success` or - * `Result.Failure`. + * `Result.Failure`. * - `replace(a, s)` returns the original `s` on failure (never throws). * - `modify(f)` returns the original `s` on failure (never throws). * - All operations are pure; inputs are never mutated. * * **Example** (Focusing on an optional record key) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * type Env = { [key: string]: string } * const _home = Optic.id().at("HOME") * - * console.log(Result.isSuccess(_home.getResult({ HOME: "/root" }))) - * // Output: true + * _home.getResult({ HOME: "/root" }) // => Result.succeed("/root") * - * console.log(Result.isFailure(_home.getResult({ PATH: "/bin" }))) - * // Output: true + * Result.isFailure(_home.getResult({ PATH: "/bin" })) // => true * * // replace returns original on failure - * console.log(_home.replace("/new", { PATH: "/bin" })) - * // Output: { PATH: "/bin" } + * _home.replace("/new", { PATH: "/bin" }) // => { PATH: "/bin" } * ``` * * @see {@link makeOptional} — constructor * @see {@link Lens} — when reading always succeeds * @see {@link Prism} — when writing always succeeds * - * @category Optional + * @category models * @since 4.0.0 */ export interface Optional { - readonly node: Node /** * Attempts to read the focus `A` from the whole `S`. Returns * `Result.Success` when the focus exists, or - * `Result.Failure` with a descriptive error otherwise. + * `Result.Failure` with a structured issue otherwise. */ - readonly getResult: (s: S) => Result.Result + readonly getResult: (s: S) => Result.Result /** * Replaces the focus in `S` with a new `A`. Returns the original `s` * unchanged when the optic cannot focus (never throws). @@ -516,20 +459,21 @@ export interface Optional { * Like {@link replace}, but returns an explicit `Result` so callers can * detect and handle failure. */ - readonly replaceResult: (a: A, s: S) => Result.Result + readonly replaceResult: (a: A, s: S) => Result.Result /** * Composes this optic with another. The result type is the weakest of * the two: Iso + Iso = Iso, Lens + Prism = Optional, etc. * * **Example** (Composing a lens with a prism) * - * ```ts - * import { Optic, Option } from "effect" + * ```ts import.meta.vitest + * import { Optic, Option, Result } from "effect" * * type State = { value: Option.Option } * * const _inner = Optic.id().key("value").compose(Optic.some()) * // _inner is Optional + * _inner.getResult({ value: Option.some(1) }) // => Result.succeed(1) * ``` * * @see {@link id} — start a composition chain @@ -545,15 +489,14 @@ export interface Optional { * * **Example** (Incrementing a nested field) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type S = { readonly a: { readonly b: number } } * const _b = Optic.id().key("a").key("b") * * const inc = _b.modify((n) => n + 1) - * console.log(inc({ a: { b: 1 } })) - * // Output: { a: { b: 2 } } + * inc({ a: { b: 1 } }) // => { a: { b: 2 } } * ``` */ modify(f: (a: A) => A): (s: S) => S @@ -569,14 +512,13 @@ export interface Optional { * * **Example** (Drilling into nested structs) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type S = { readonly a: { readonly b: number } } * const _b = Optic.id().key("a").key("b") * - * console.log(_b.get({ a: { b: 42 } })) - * // Output: 42 + * _b.get({ a: { b: 42 } }) // => 42 * ``` */ key( @@ -601,17 +543,15 @@ export interface Optional { * * **Example** (Deleting an optional key) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type S = { readonly a?: number } * const _a = Optic.id().optionalKey("a") * - * console.log(_a.replace(undefined, { a: 1 })) - * // Output: {} + * _a.replace(undefined, { a: 1 }) // => {} * - * console.log(_a.replace(2, {})) - * // Output: { a: 2 } + * _a.replace(2, {}) // => { a: 2 } * ``` */ optionalKey( @@ -636,16 +576,14 @@ export interface Optional { * * **Example** (Focusing only on positive numbers) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result, Schema } from "effect" * * const _pos = Optic.id().check(Schema.isGreaterThan(0)) * - * console.log(Result.isSuccess(_pos.getResult(5))) - * // Output: true + * _pos.getResult(5) // => Result.succeed(5) * - * console.log(Result.isFailure(_pos.getResult(-1))) - * // Output: true + * Result.isFailure(_pos.getResult(-1)) // => true * ``` * * @see {@link fromChecks} — standalone prism from checks @@ -667,7 +605,7 @@ export interface Optional { * * **Example** (Narrowing a union) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * type B = { readonly _tag: "b"; readonly b: number } @@ -678,8 +616,7 @@ export interface Optional { * { expected: `"b" tag` } * ) * - * console.log(Result.isSuccess(_b.getResult({ _tag: "b", b: 1 }))) - * // Output: true + * _b.getResult({ _tag: "b", b: 1 }) // => Result.succeed({ _tag: "b", b: 1 }) * ``` * * @see `.tag()` — shorthand for narrowing by `_tag` @@ -704,10 +641,12 @@ export interface Optional { * - On a {@link Prism}, returns a Prism. * - On an {@link Optional}, returns an Optional. * - Shorthand for `.refine(s => s._tag === tag)`. + * - A non-matching value fails with {@link SchemaIssue.InvalidValue} whose + * `expected` annotation is `" tag"`. * * **Example** (Focusing a tagged variant) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * type Shape = @@ -716,11 +655,9 @@ export interface Optional { * * const _radius = Optic.id().tag("Circle").key("radius") * - * console.log(Result.isSuccess(_radius.getResult({ _tag: "Circle", radius: 5 }))) - * // Output: true + * _radius.getResult({ _tag: "Circle", radius: 5 }) // => Result.succeed(5) * - * console.log(Result.isFailure(_radius.getResult({ _tag: "Rect", width: 10 }))) - * // Output: true + * Result.isFailure(_radius.getResult({ _tag: "Rect", width: 10 })) // => true * ``` * * @see `.refine()` — for arbitrary type guards @@ -745,20 +682,21 @@ export interface Optional { * * - Always returns an {@link Optional}. * - Does **not** work on union types (compile error). + * - A missing key fails with a {@link SchemaIssue.Pointer} at that key whose + * inner issue is {@link SchemaIssue.MissingKey}, for both `getResult` and + * `replaceResult`. * * **Example** (Accessing records safely) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * type Env = { [key: string]: number } * const _x = Optic.id().at("x") * - * console.log(Result.isSuccess(_x.getResult({ x: 1 }))) - * // Output: true + * _x.getResult({ x: 1 }) // => Result.succeed(1) * - * console.log(Result.isFailure(_x.getResult({ y: 2 }))) - * // Output: true + * Result.isFailure(_x.getResult({ y: 2 })) // => true * ``` * * @see `.key()` — when the key is always present @@ -780,15 +718,14 @@ export interface Optional { * * **Example** (Picking keys) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type S = { readonly a: string; readonly b: number; readonly c: boolean } * * const _ac = Optic.id().pick(["a", "c"]) * - * console.log(_ac.get({ a: "hi", b: 1, c: true })) - * // Output: { a: "hi", c: true } + * _ac.get({ a: "hi", b: 1, c: true }) // => { a: "hi", c: true } * ``` * * @see `.omit()` — the inverse operation @@ -815,15 +752,14 @@ export interface Optional { * * **Example** (Omitting keys) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type S = { readonly a: string; readonly b: number; readonly c: boolean } * * const _ac = Optic.id().omit(["b"]) * - * console.log(_ac.get({ a: "hi", b: 1, c: true })) - * // Output: { a: "hi", c: true } + * _ac.get({ a: "hi", b: 1, c: true }) // => { a: "hi", c: true } * ``` * * @see `.pick()` — the inverse operation @@ -847,22 +783,20 @@ export interface Optional { * * **Example** (Filtering undefined values) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * const _defined = Optic.id().notUndefined() * - * console.log(Result.isSuccess(_defined.getResult(42))) - * // Output: true + * _defined.getResult(42) // => Result.succeed(42) * - * console.log(Result.isFailure(_defined.getResult(undefined))) - * // Output: true + * Result.isFailure(_defined.getResult(undefined)) // => true * ``` * * @since 4.0.0 */ - notUndefined(): Prism> - notUndefined(): Optional> + notUndefined(this: Prism): Prism> + notUndefined(this: Optional): Optional> /** * Focuses **all elements** of an array-like focus and optionally narrows @@ -877,11 +811,13 @@ export interface Optional { * element. Non-focusable elements are skipped. * - **replaceResult** expects exactly as many values as were collected by * `getResult` and writes them back in order. Fails with a - * length-mismatch error if counts differ. + * {@link SchemaIssue.InvalidValue} if counts differ. If an inner replacement + * fails, its issue is wrapped in a {@link SchemaIssue.Pointer} at the element + * index. * * **Example** (Incrementing liked posts) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Schema } from "effect" * * type Post = { title: string; likes: number } @@ -894,12 +830,10 @@ export interface Optional { * * const addLike = _likes.modifyAll((n) => n + 1) * - * console.log( - * addLike({ - * user: { posts: [{ title: "a", likes: 0 }, { title: "b", likes: 1 }] } - * }) - * ) - * // Output: { user: { posts: [{ title: "a", likes: 0 }, { title: "b", likes: 2 }] } } + * const result = addLike({ + * user: { posts: [{ title: "a", likes: 0 }, { title: "b", likes: 1 }] } + * }) + * result.user.posts // => [{ title: "a", likes: 0 }, { title: "b", likes: 2 }] * ``` * * @see {@link getAll} — extract all focused elements as an array @@ -920,7 +854,7 @@ export interface Optional { * * **Example** (Doubling all focused values) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Schema } from "effect" * * type S = { readonly items: ReadonlyArray } @@ -931,8 +865,7 @@ export interface Optional { * * const doubled = _positive.modifyAll((n) => n * 2) * - * console.log(doubled({ items: [1, -2, 3] })) - * // Output: { items: [2, -2, 6] } + * doubled({ items: [1, -2, 3] }) // => { items: [2, -2, 6] } * ``` * * @see `.forEach()` — create a sub-traversal @@ -951,29 +884,31 @@ export interface Optional { * * **Details** * - * - `getResult` should return `Result.fail(message)` on mismatch. - * - `set` should return `Result.fail(message)` when the update cannot be + * - `getResult` should return `Result.fail(issue)` on mismatch. + * - `set` should return `Result.fail(issue)` when the update cannot be * applied. + * - Issues are not formatted automatically; callers choose how to render them. * * **Example** (Accessing record keys safely) * - * ```ts - * import { Optic, Result } from "effect" + * ```ts import.meta.vitest + * import { Optic, Result, SchemaIssue } from "effect" * - * const atKey = (key: string) => - * Optic.makeOptional, number>( + * const atKey = (key: string) => { + * const issue = new SchemaIssue.Pointer([key], new SchemaIssue.MissingKey(undefined)) + * return Optic.makeOptional, number>( * (s) => * Object.hasOwn(s, key) * ? Result.succeed(s[key]) - * : Result.fail(`Key "${key}" not found`), + * : Result.fail(issue), * (a, s) => * Object.hasOwn(s, key) * ? Result.succeed({ ...s, [key]: a }) - * : Result.fail(`Key "${key}" not found`) + * : Result.fail(issue) * ) + * } * - * console.log(Result.isSuccess(atKey("x").getResult({ x: 1 }))) - * // Output: true + * atKey("x").getResult({ x: 1 }) // => Result.succeed(1) * ``` * * @see {@link Optional} — the type this function returns @@ -984,10 +919,10 @@ export interface Optional { * @since 4.0.0 */ export function makeOptional( - getResult: (s: S) => Result.Result, - set: (a: A, s: S) => Result.Result + getResult: (s: S) => Result.Result, + set: (a: A, s: S) => Result.Result ): Optional { - return make(new OptionalNode(getResult, set)) + return make(primitiveNode("Optional", getResult, set)) } /** @@ -1009,7 +944,7 @@ export function makeOptional( * * **Example** (Traversing array elements with a filter) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Schema } from "effect" * * type S = { readonly items: ReadonlyArray } @@ -1020,26 +955,26 @@ export function makeOptional( * * const getPositive = Optic.getAll(_positive) * - * console.log(getPositive({ items: [1, -2, 3] })) - * // Output: [1, 3] + * getPositive({ items: [1, -2, 3] }) // => [1, 3] * ``` * * @see {@link getAll} — extract focused elements * @see {@link Optional} — the base type * - * @category Traversal + * @category models * @since 4.0.0 */ export interface Traversal extends Optional> {} class OptionalImpl implements Optional { + /** @internal */ readonly node: Node - readonly getResult: (s: S) => Result.Result - readonly replaceResult: (a: A, s: S) => Result.Result + readonly getResult: (s: S) => Result.Result + readonly replaceResult: (a: A, s: S) => Result.Result constructor( node: Node, - getResult: (s: S) => Result.Result, - replaceResult: (a: A, s: S) => Result.Result + getResult: (s: S) => Result.Result, + replaceResult: (a: A, s: S) => Result.Result ) { this.node = node this.getResult = getResult @@ -1055,24 +990,28 @@ class OptionalImpl implements Optional { return make(compose(this.node, that.node)) } key(key: PropertyKey): any { - return make(compose(this.node, new PathNode([key]))) + return make(compose(this.node, [new PathNode([key])])) } optionalKey(key: PropertyKey): any { return make( compose( this.node, - new LensNode( + primitiveNode( + "Lens", (s) => s[key], (a, s) => { const copy = cloneShallow(s) if (a === undefined) { - if (Array.isArray(copy) && typeof key === "number") { - copy.splice(key, 1) + if ( + Array.isArray(copy) && + (typeof key === "number" || (typeof key === "string" && Arr.isCanonicalArrayIndex(key))) + ) { + copy.splice(Number(key), 1) } else { delete copy[key] } } else { - copy[key] = a + InternalRecord.assignProperty(copy, key, a) } return copy } @@ -1081,36 +1020,38 @@ class OptionalImpl implements Optional { ) } check(...checks: readonly [SchemaAST.Check, ...Array>]): any { - return make(compose(this.node, new CheckNode(checks))) + return make(compose(this.node, [new CheckNode(checks)])) } refine(refinement: (a: A) => a is B, annotations?: Schema.Annotations.Filter): any { - return make(compose(this.node, new CheckNode([SchemaAST.makeFilterByGuard(refinement, annotations)]))) + return make(compose(this.node, [new CheckNode([SchemaAST.makeFilterByGuard(refinement, annotations)])])) } tag(tag: string): any { + const err = Result.fail(new SchemaIssue.InvalidValue({ expected: `${JSON.stringify(tag)} tag` })) return make( compose( this.node, - new PrismNode( - (s) => - s._tag === tag - ? Result.succeed(s) - : Result.fail(`Expected ${format(tag)} tag, got ${format(s._tag)}`), + primitiveNode( + "Prism", + (s) => s._tag === tag ? Result.succeed(s) : err, identity ) ) ) } at(key: PropertyKey, ..._rest: Array): any { - const err = Result.fail(`Key ${format(key)} not found`) + const err = Result.fail( + new SchemaIssue.Pointer([key], new SchemaIssue.MissingKey(undefined)) + ) return make( compose( this.node, - new OptionalNode( + primitiveNode( + "Optional", (s) => Object.hasOwn(s, key) ? Result.succeed(s[key]) : err, (a, s) => { if (Object.hasOwn(s, key)) { const copy = cloneShallow(s) - copy[key] = a + InternalRecord.assignProperty(copy, key, a) return Result.succeed(copy) } else { return err @@ -1121,12 +1062,12 @@ class OptionalImpl implements Optional { ) } pick(keys: any) { - return this.compose(makeLens(Struct.pick(keys), (p, a) => ({ ...a, ...p }))) + return this.compose(makeLens(Struct.pick(keys), (p, a) => ({ ...Struct.omit(a, keys), ...p }))) } omit(keys: any) { - return this.compose(makeLens(Struct.omit(keys), (o, a) => ({ ...a, ...o }))) + return this.compose(makeLens(Struct.omit(keys), (o, a) => ({ ...Struct.pick(a, keys), ...o }))) } - notUndefined(): Prism> { + notUndefined(): any { return this.refine(Predicate.isNotUndefined, { expected: "a value other than `undefined`" }) } forEach(this: Traversal, f: (iso: Iso) => Optional): Traversal { @@ -1154,7 +1095,9 @@ class OptionalImpl implements Optional { // 2) arity check if (bs.length !== idxs.length) { return Result.fail( - `each: replacement length mismatch: ${bs.length} !== ${idxs.length}` + new SchemaIssue.InvalidValue({ + message: `each: replacement length mismatch: ${bs.length} !== ${idxs.length}` + }) ) } @@ -1164,7 +1107,7 @@ class OptionalImpl implements Optional { const i = idxs[k] const r = inner.replaceResult(bs[k], as[i]) if (Result.isFailure(r)) { - return Result.fail(`each: could not set element ${i}`) + return Result.fail(new SchemaIssue.Pointer([i], r.failure)) } out[i] = r.success } @@ -1211,7 +1154,7 @@ class LensImpl extends OptionalImpl implements Lens { class PrismImpl extends OptionalImpl implements Prism { readonly set: (a: A) => S - constructor(node: Node, getResult: (s: S) => Result.Result, set: (a: A) => S) { + constructor(node: Node, getResult: (s: S) => Result.Result, set: (a: A) => S) { super(node, getResult, (a, _) => Result.succeed(set(a))) this.set = set } @@ -1224,15 +1167,23 @@ class PrismImpl extends OptionalImpl implements Prism { } function make(node: Node): any { - const op = recur(node) - switch (op._tag) { - case "IsoNode": + let op: Operation = node[0] ?? identityOperation + if (node.length > 1) { + const kind = node.reduce((kind, step) => composeKind(kind, step.kind), "Iso") + op = { + kind, + get: compileGet(node, kind), + set: compileSet(node, kind) + } + } + switch (op.kind) { + case "Iso": return new IsoImpl(node, op.get, op.set) - case "LensNode": + case "Lens": return new LensImpl(node, op.get, op.set) - case "PrismNode": + case "Prism": return new PrismImpl(node, op.get, op.set) - case "OptionalNode": + case "Optional": return new OptionalImpl(node, op.get, op.set) } } @@ -1249,143 +1200,259 @@ function cloneShallow(pojo: T): T { return pojo } -type Op = { - readonly _tag: "IsoNode" | "LensNode" | "PrismNode" | "OptionalNode" - readonly get: (s: unknown) => any - readonly set: (a: unknown, s?: unknown) => any +function compileGet(nodes: Node, kind: Kind): (s: any) => any { + return (s) => { + for (let i = 0; i < nodes.length; i++) { + const op = nodes[i] + const result = op.get(s) + if (hasFailingGet(op.kind)) { + if (Result.isFailure(result)) { + return result + } + s = result.success + } else { + s = result + } + } + return hasFailingGet(kind) ? Result.succeed(s) : s + } } -const recur = memoize((node: Node): Op => { - switch (node._tag) { - case "IdentityNode": - return { _tag: "IsoNode", get: identity, set: identity } - case "IsoNode": - case "LensNode": - case "PrismNode": - case "OptionalNode": - return { _tag: node._tag, get: node.get, set: node.set } - case "PathNode": { - return { - _tag: "LensNode", - get: (s: any) => { - const path = node.path - let out: any = s - for (let i = 0, n = path.length; i < n; i++) { - out = out[path[i]] - } - return out - }, - set: (a: any, s: any) => { - const path = node.path - const out = cloneShallow(s) - - let current = out - let i = 0 - for (; i < path.length - 1; i++) { - const key = path[i] - current[key] = cloneShallow(current[key]) - current = current[key] - } - - const finalKey = path[i] - current[finalKey] = a - - return out - } +function compileSet(nodes: Node, kind: Kind): (a: any, s: any) => any { + if (hasSourceFreeSet(kind)) { + return (a) => { + for (let i = nodes.length - 1; i >= 0; i--) { + a = nodes[i].set(a) } + return a } - case "CheckNode": - return { - _tag: "PrismNode", - get: (s: any) => Result.mapError(SchemaAST.runChecks(node.checks, s), String), - set: identity + } + return (a, s) => { + const len = nodes.length + const sources = new Array(len) + for (let i = 0; i < len; i++) { + sources[i] = s + const op = nodes[i] + if (hasFailingGet(op.kind)) { + const result = op.get(s) + if (Result.isFailure(result)) { + return result + } + s = result.success + } else { + s = op.get(s) } - case "CompositionNode": { - const ops = node.nodes.map(recur) - const _tag = ops.reduce((tag, op) => getCompositionTag(tag, op._tag), "IsoNode") - return { - _tag, - get: (s: any) => { - for (let i = 0; i < ops.length; i++) { - const op = ops[i] - const result = op.get(s) - if (hasFailingGet(op._tag)) { - if (Result.isFailure(result)) { - return result - } - s = result.success - } else { - s = result - } - } - return hasFailingGet(_tag) ? Result.succeed(s) : s - }, - set: (a: any, s: any) => { - const source = s - const len = ops.length - const ss = new Array(len + 1) - ss[0] = s - for (let i = 0; i < len; i++) { - const op = ops[i] - if (hasFailingGet(op._tag)) { - const result = op.get(s) - if (Result.isFailure(result)) { - return _tag === "OptionalNode" ? result : source - } - s = result.success - } else { - s = op.get(s) - } - ss[i + 1] = s - } - for (let i = len - 1; i >= 0; i--) { - const op = ops[i] - if (hasSet(op._tag)) { - a = op.set(a) - } else if (op._tag === "LensNode") { - a = op.set(a, ss[i]) - } else { - const result = op.set(a, ss[i]) - if (Result.isFailure(result)) { - return result - } - a = result.success - } - } - return _tag === "OptionalNode" ? Result.succeed(a) : a + } + for (let i = len - 1; i >= 0; i--) { + const op = nodes[i] + if (hasSourceFreeSet(op.kind)) { + a = op.set(a) + } else if (op.kind === "Lens") { + a = op.set(a, sources[i]) + } else { + const result = op.set(a, sources[i]) + if (Result.isFailure(result)) { + return result } + a = result.success } } + return kind === "Optional" ? Result.succeed(a) : a } -}) +} -function hasFailingGet(tag: Op["_tag"]): boolean { - return tag === "PrismNode" || tag === "OptionalNode" +function hasFailingGet(kind: Kind): boolean { + return kind === "Prism" || kind === "Optional" } -function hasSet(tag: Op["_tag"]): boolean { - return tag === "IsoNode" || tag === "PrismNode" +function hasSourceFreeSet(kind: Kind): boolean { + return kind === "Iso" || kind === "Prism" } -function getCompositionTag(a: Op["_tag"], b: Op["_tag"]): Op["_tag"] { - switch (a) { - case "IsoNode": - return b - case "LensNode": - return hasFailingGet(b) ? "OptionalNode" : "LensNode" - case "PrismNode": - return hasSet(b) ? "PrismNode" : "OptionalNode" - case "OptionalNode": - return "OptionalNode" - } +function composeKind(a: Kind, b: Kind): Kind { + if (a === "Iso") return b + if (b === "Iso" || a === b) return a + return "Optional" } // --------------------------------------------- // Derived APIs // --------------------------------------------- /** - * Returns a function that extracts all elements focused by a - * {@link Traversal} as a plain mutable array. + * Reads the focused value from a `Lens`. + * + * **When to use** + * + * Use when the optic always focuses exactly one value. + * + * **Details** + * + * Supports both data-first and data-last forms. + * + * @see {@link getResult} for optics whose focus may be absent + * + * @category getters + * @since 4.0.0 + */ +export const get: { + (optic: Lens): (self: NoInfer) => A + (self: NoInfer, optic: Lens): A +} = dual< + (optic: Lens) => (self: NoInfer) => A, + (self: NoInfer, optic: Lens) => A +>(2, (self, optic) => optic.get(self)) + +/** + * Attempts to read the focused value from an `Optional`. + * + * **When to use** + * + * Use when the optic may not focus and you need the failure as a `Result`. + * + * **Details** + * + * Supports both data-first and data-last forms. + * + * @see {@link get} for optics that always focus + * + * @category getters + * @since 4.0.0 + */ +export const getResult: { + (optic: Optional): (self: NoInfer) => Result.Result + (self: NoInfer, optic: Optional): Result.Result +} = dual< + (optic: Optional) => (self: NoInfer) => Result.Result, + (self: NoInfer, optic: Optional) => Result.Result +>(2, (self, optic) => optic.getResult(self)) + +/** + * Builds a source value from a focused value using a `Prism`. + * + * **When to use** + * + * Use when the optic can construct the source without an existing source value. + * + * **Details** + * + * Supports both data-first and data-last forms. The focused value is `self`. + * + * @see {@link replace} for updates that use an existing source + * + * @category transforming + * @since 4.0.0 + */ +export const set: { + (optic: Prism): (self: NoInfer) => S + (self: NoInfer, optic: Prism): S +} = dual< + (optic: Prism) => (self: NoInfer) => S, + (self: NoInfer, optic: Prism) => S +>(2, (self, optic) => optic.set(self)) + +/** + * Replaces the focused value in a source. + * + * **When to use** + * + * Use when a failed focus should leave the source unchanged. + * + * **Details** + * + * Supports both data-first and data-last forms. + * + * @see {@link replaceResult} for an explicit replacement failure + * + * @category transforming + * @since 4.0.0 + */ +export const replace: { + (optic: Optional, value: NoInfer): (self: NoInfer) => S + (self: NoInfer, optic: Optional, value: NoInfer): S +} = dual< + (optic: Optional, value: NoInfer) => (self: NoInfer) => S, + (self: NoInfer, optic: Optional, value: NoInfer) => S +>(3, (self, optic, value) => optic.replace(value, self)) + +/** + * Attempts to replace the focused value in a source. + * + * **When to use** + * + * Use when you need an explicit `Result` for a replacement failure. + * + * **Details** + * + * Supports both data-first and data-last forms. + * + * @see {@link replace} for returning the original source on failure + * + * @category transforming + * @since 4.0.0 + */ +export const replaceResult: { + ( + optic: Optional, + value: NoInfer + ): (self: NoInfer) => Result.Result + ( + self: NoInfer, + optic: Optional, + value: NoInfer + ): Result.Result +} = dual< + ( + optic: Optional, + value: NoInfer + ) => (self: NoInfer) => Result.Result, + ( + self: NoInfer, + optic: Optional, + value: NoInfer + ) => Result.Result +>(3, (self, optic, value) => optic.replaceResult(value, self)) + +/** + * Transforms the focused value in a source. + * + * **When to use** + * + * Use when you want to update a focus with a function. + * + * **Details** + * + * Supports both data-first and data-last forms. A failed focus leaves the + * source unchanged. + * + * @see {@link modifyAll} for transforming every value in a traversal + * + * @category transforming + * @since 4.0.0 + */ +export const modify: { + ( + optic: Optional, + f: (value: NoInfer) => NoInfer + ): (self: NoInfer) => S + ( + self: NoInfer, + optic: Optional, + f: (value: NoInfer) => NoInfer + ): S +} = dual< + ( + optic: Optional, + f: (value: NoInfer) => NoInfer + ) => (self: NoInfer) => S, + ( + self: NoInfer, + optic: Optional, + f: (value: NoInfer) => NoInfer + ) => S +>(3, (self, optic, f) => optic.modify(f)(self)) + +/** + * Extracts all values focused by a `Traversal` as a plain mutable array. * * **When to use** * @@ -1396,10 +1463,11 @@ function getCompositionTag(a: Op["_tag"], b: Op["_tag"]): Op["_tag"] { * * - Returns an empty array when the traversal cannot focus. * - Always returns a fresh array (safe to mutate). + * - Supports both data-first and data-last forms. * * **Example** (Collecting positive numbers) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Schema } from "effect" * * type S = { readonly values: ReadonlyArray } @@ -1410,31 +1478,73 @@ function getCompositionTag(a: Op["_tag"], b: Op["_tag"]): Op["_tag"] { * * const getPositive = Optic.getAll(_pos) * - * console.log(getPositive({ values: [3, -1, 5] })) - * // Output: [3, 5] + * getPositive({ values: [3, -1, 5] }) // => [3, 5] * - * console.log(getPositive({ values: [-1, -2] })) - * // Output: [] + * getPositive({ values: [-1, -2] }) // => [] * ``` * * @see {@link Traversal} — the optic type this operates on * - * @category Traversal + * @category getters * @since 4.0.0 */ -export function getAll(traversal: Traversal): (s: S) => Array { - return (s) => - Result.match(traversal.getResult(s), { - onFailure: () => [], - onSuccess: (as) => [...as] - }) -} +export const getAll: { + (traversal: Traversal): (self: NoInfer) => Array + (self: NoInfer, traversal: Traversal): Array +} = dual< + (traversal: Traversal) => (self: NoInfer) => Array, + (self: NoInfer, traversal: Traversal) => Array +>(2, (self, traversal) => + Result.match(traversal.getResult(self), { + onFailure: () => [], + onSuccess: (as) => [...as] + })) + +/** + * Transforms every value focused by a `Traversal`. + * + * **When to use** + * + * Use when you want to update each value selected by a traversal. + * + * **Details** + * + * Supports both data-first and data-last forms. A failed traversal leaves the + * source unchanged. + * + * @see {@link modify} for transforming the focus as a whole + * @see {@link getAll} for reading every focused value + * + * @category transforming + * @since 4.0.0 + */ +export const modifyAll: { + ( + traversal: Traversal, + f: (value: NoInfer) => NoInfer + ): (self: NoInfer) => S + ( + self: NoInfer, + traversal: Traversal, + f: (value: NoInfer) => NoInfer + ): S +} = dual< + ( + traversal: Traversal, + f: (value: NoInfer) => NoInfer + ) => (self: NoInfer) => S, + ( + self: NoInfer, + traversal: Traversal, + f: (value: NoInfer) => NoInfer + ) => S +>(3, (self, traversal, f) => traversal.modifyAll(f)(self)) // --------------------------------------------- // Built-in Optics // --------------------------------------------- -const identityIso = make(identityNode) +const identityIso = make([]) /** * Iso that focuses on the whole value unchanged. @@ -1451,20 +1561,19 @@ const identityIso = make(identityNode) * * **Example** (Starting an optic chain) * - * ```ts + * ```ts import.meta.vitest * import { Optic } from "effect" * * type S = { readonly x: number } * * const _x = Optic.id().key("x") * - * console.log(_x.get({ x: 42 })) - * // Output: 42 + * _x.get({ x: 42 }) // => 42 * ``` * * @see {@link Iso} — the type this function returns * - * @category Iso + * @category constructors * @since 4.0.0 */ export function id(): Iso { @@ -1488,7 +1597,7 @@ export function id(): Iso { * * **Example** (Traversing record values) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Schema } from "effect" * * const _positiveValues = Optic.entries() @@ -1496,18 +1605,17 @@ export function id(): Iso { * * const inc = _positiveValues.modifyAll((n) => n + 1) * - * console.log(inc({ a: 0, b: 3, c: -1 })) - * // Output: { a: 0, b: 4, c: -1 } + * inc({ a: 0, b: 3, c: -1 }) // => { a: 0, b: 4, c: -1 } * ``` * * @see {@link Iso} — the type this function returns * @see {@link id} — identity iso * - * @category Iso + * @category constructors * @since 4.0.0 */ export function entries(): Iso, ReadonlyArray> { - return make(new IsoNode(Object.entries, Object.fromEntries)) + return make(primitiveNode("Iso", Object.entries, Object.fromEntries)) } /** @@ -1520,40 +1628,33 @@ export function entries(): Iso, ReadonlyArray>().compose(Optic.some()) * - * console.log(Result.isSuccess(_some.getResult(Option.some(42)))) - * // Output: true + * _some.getResult(Option.some(42)) // => Result.succeed(42) * - * console.log(Result.isFailure(_some.getResult(Option.none()))) - * // Output: true + * Result.isFailure(_some.getResult(Option.none())) // => true * - * console.log(_some.set(10)) - * // Output: { _tag: "Some", value: 10 } + * _some.set(10) // => Option.some(10) * ``` * * @see {@link none} — focuses on `None` instead * @see {@link Prism} — the type this function returns * - * @category Prism + * @category constructors * @since 4.0.0 */ export function some(): Prism, A> { const run = runRefinement(Option.isSome, { expected: "a Some value" }) return makePrism( - (s) => - Result.mapBoth(run(s), { - onFailure: String, - onSuccess: (s) => s.value - }), + (s) => Result.map(run(s), (s) => s.value), Option.some ) } @@ -1573,32 +1674,26 @@ export function some(): Prism, A> { * * **Example** (Matching None) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Option, Result } from "effect" * * const _none = Optic.id>().compose(Optic.none()) * - * console.log(Result.isSuccess(_none.getResult(Option.none()))) - * // Output: true + * _none.getResult(Option.none()) // => Result.succeed(undefined) * - * console.log(Result.isFailure(_none.getResult(Option.some(1)))) - * // Output: true + * Result.isFailure(_none.getResult(Option.some(1))) // => true * ``` * * @see {@link some} — focuses on `Some` instead * @see {@link Prism} — the type this function returns * - * @category Prism + * @category constructors * @since 4.0.0 */ export function none(): Prism, undefined> { const run = runRefinement(Option.isNone, { expected: "a None value" }) return makePrism( - (s) => - Result.mapBoth(run(s), { - onFailure: String, - onSuccess: () => undefined - }), + (s) => Result.map(run(s), () => undefined), () => Option.none() ) } @@ -1618,32 +1713,26 @@ export function none(): Prism, undefined> { * * **Example** (Accessing success) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * const _ok = Optic.id>().compose(Optic.success()) * - * console.log(Result.isSuccess(_ok.getResult(Result.succeed(42)))) - * // Output: true + * _ok.getResult(Result.succeed(42)) // => Result.succeed(42) * - * console.log(Result.isFailure(_ok.getResult(Result.fail("err")))) - * // Output: true + * Result.isFailure(_ok.getResult(Result.fail("err"))) // => true * ``` * * @see {@link failure} — focuses on the failure side * @see {@link Prism} — the type this function returns * - * @category Prism + * @category constructors * @since 4.0.0 */ export function success(): Prism, A> { const run = runRefinement(Result.isSuccess, { expected: "a Result.Success value" }) return makePrism( - (s) => - Result.mapBoth(run(s), { - onFailure: String, - onSuccess: (s) => s.success - }), + (s) => Result.map(run(s), (s) => s.success), Result.succeed ) } @@ -1663,32 +1752,26 @@ export function success(): Prism, A> { * * **Example** (Accessing failure) * - * ```ts + * ```ts import.meta.vitest * import { Optic, Result } from "effect" * * const _err = Optic.id>().compose(Optic.failure()) * - * console.log(Result.isSuccess(_err.getResult(Result.fail("oops")))) - * // Output: true + * _err.getResult(Result.fail("oops")) // => Result.succeed("oops") * - * console.log(Result.isFailure(_err.getResult(Result.succeed(42)))) - * // Output: true + * Result.isFailure(_err.getResult(Result.succeed(42))) // => true * ``` * * @see {@link success} — focuses on the success side * @see {@link Prism} — the type this function returns * - * @category Prism + * @category constructors * @since 4.0.0 */ export function failure(): Prism, E> { const run = runRefinement(Result.isFailure, { expected: "a Result.Failure value" }) return makePrism( - (s) => - Result.mapBoth(run(s), { - onFailure: String, - onSuccess: (s) => s.failure - }), + (s) => Result.map(run(s), (s) => s.failure), Result.fail ) } diff --git a/packages/effect/src/Option.ts b/packages/effect/src/Option.ts index e292f0f56..3e497eb10 100644 --- a/packages/effect/src/Option.ts +++ b/packages/effect/src/Option.ts @@ -20,6 +20,7 @@ import type { TypeLambda } from "./HKT.ts" import type { Inspectable } from "./Inspectable.ts" import * as doNotation from "./internal/doNotation.ts" import * as option from "./internal/option.ts" +import * as InternalRecord from "./internal/record.ts" import * as result from "./internal/result.ts" import type { Order } from "./Order.ts" import * as order from "./Order.ts" @@ -32,7 +33,7 @@ import type { Covariant, NoInfer, NotFunction } from "./Types.ts" import type * as Unify from "./Unify.ts" import type * as Gen from "./Utils.ts" -const TypeId = "~effect/data/Option" +const TypeId = "~effect/Option" /** * The `Option` data type represents optional values. An `Option` is either @@ -177,17 +178,16 @@ export declare namespace Option { * * **Example** (Extracting the value type) * - * ```ts - * import type { Option } from "effect" - * - * declare const myOption: Option.Option + * ```ts import.meta.vitest + * import { Option } from "effect" * - * // ┌─── string - * // ▼ + * const myOption: Option.Option = Option.some("value") * type MyType = Option.Option.Value + * + * const witness: MyType = "value" * ``` * - * @category Type-level Utils + * @category utility types * @since 2.0.0 */ export type Value> = [T] extends [Option] ? _A : never @@ -218,7 +218,7 @@ export interface OptionUnifyIgnore {} * Use when defining higher-kinded abstractions that must accept optional-value * types as one of their type-lambda inputs. * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface OptionTypeLambda extends TypeLambda { @@ -240,15 +240,12 @@ export interface OptionTypeLambda extends TypeLambda { * * **Example** (Creating an empty Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * // ┌─── Option * // ▼ - * const noValue = Option.none() - * - * console.log(noValue) - * // Output: { _id: 'Option', _tag: 'None' } + * const noValue = Option.none() // => Option.none() * ``` * * @see {@link some} for the opposite operation. @@ -273,15 +270,12 @@ export const none = (): Option => option.none * * **Example** (Wrapping a value) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * // ┌─── Option * // ▼ - * const value = Option.some(1) - * - * console.log(value) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } + * const value = Option.some(1) // => Option.some(1) * ``` * * @see {@link none} for the opposite operation. @@ -306,17 +300,12 @@ export const some: (value: A) => Option = option.some * * **Example** (Checking if a value is an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.isOption(Option.some(1))) - * // Output: true - * - * console.log(Option.isOption(Option.none())) - * // Output: true - * - * console.log(Option.isOption({})) - * // Output: false + * Option.isOption(Option.some(1)) // => true + * Option.isOption(Option.none()) // => true + * Option.isOption({}) // => false * ``` * * @see {@link isNone} to check for `None` specifically @@ -340,14 +329,11 @@ export const isOption: (input: unknown) => input is Option = option.isO * * **Example** (Checking for None) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.isNone(Option.some(1))) - * // Output: false - * - * console.log(Option.isNone(Option.none())) - * // Output: true + * Option.isNone(Option.some(1)) // => false + * Option.isNone(Option.none()) // => true * ``` * * @see {@link isSome} for the opposite check. @@ -370,14 +356,11 @@ export const isNone: (self: Option) => self is None = option.isNone * * **Example** (Checking for Some) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.isSome(Option.some(1))) - * // Output: true - * - * console.log(Option.isSome(Option.none())) - * // Output: false + * Option.isSome(Option.some(1)) // => true + * Option.isSome(Option.none()) // => false * ``` * * @see {@link isNone} for the opposite check. @@ -403,16 +386,13 @@ export const isSome: (self: Option) => self is Some = option.isSome * * **Example** (Matching on an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * const message = Option.match(Option.some(1), { + * Option.match(Option.some(1), { * onNone: () => "Option is empty", * onSome: (value) => `Option has a value: ${value}` - * }) - * - * console.log(message) - * // Output: "Option has a value: 1" + * }) // => "Option has a value: 1" * ``` * * @see {@link getOrElse} for unwrapping with a default @@ -453,7 +433,7 @@ export const match: { * * **Example** (Converting a parser to a type guard) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * type MyData = string | number @@ -465,11 +445,8 @@ export const match: { * // ▼ * const isString = Option.toRefinement(parseString) * - * console.log(isString("a")) - * // Output: true - * - * console.log(isString(1)) - * // Output: false + * isString("a") // => true + * isString(1) // => false * ``` * * @see {@link liftPredicate} for the reverse direction @@ -495,14 +472,11 @@ export const toRefinement = (f: (a: A) => Option): (a: A) => * * **Example** (Getting the first element) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.fromIterable([1, 2, 3])) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } - * - * console.log(Option.fromIterable([])) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.fromIterable([1, 2, 3]) // => Option.some(1) + * Option.fromIterable([]) // => Option.none() * ``` * * @see {@link toArray} for the inverse direction @@ -532,14 +506,11 @@ export const fromIterable = (collection: Iterable): Option => { * * **Example** (Extracting the success side) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Option.getSuccess(Result.succeed("ok"))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'ok' } - * - * console.log(Option.getSuccess(Result.fail("err"))) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.getSuccess(Result.succeed("ok")) // => Option.some("ok") + * Option.getSuccess(Result.fail("err")) // => Option.none() * ``` * * @see {@link getFailure} for the opposite operation. @@ -564,14 +535,11 @@ export const getSuccess: (self: Result) => Option = result.getSuc * * **Example** (Extracting the failure side) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Option.getFailure(Result.succeed("ok"))) - * // Output: { _id: 'Option', _tag: 'None' } - * - * console.log(Option.getFailure(Result.fail("err"))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'err' } + * Option.getFailure(Result.succeed("ok")) // => Option.none() + * Option.getFailure(Result.fail("err")) // => Option.some("err") * ``` * * @see {@link getSuccess} for the opposite operation. @@ -597,14 +565,11 @@ export const getFailure: (self: Result) => Option = result.getFai * * **Example** (Unwrapping with a fallback) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.some(1).pipe(Option.getOrElse(() => 0))) - * // Output: 1 - * - * console.log(Option.none().pipe(Option.getOrElse(() => 0))) - * // Output: 0 + * Option.some(1).pipe(Option.getOrElse(() => 0)) // => 1 + * Option.none().pipe(Option.getOrElse(() => 0)) // => 0 * ``` * * @see {@link getOrNull} to fall back to `null` @@ -638,14 +603,11 @@ export const getOrElse: { * * **Example** (Providing a fallback Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.none().pipe(Option.orElse(() => Option.some("b")))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'b' } - * - * console.log(Option.some("a").pipe(Option.orElse(() => Option.some("b")))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'a' } + * Option.none().pipe(Option.orElse(() => Option.some("b"))) // => Option.some("b") + * Option.some("a").pipe(Option.orElse(() => Option.some("b"))) // => Option.some("a") * ``` * * @see {@link orElseSome} to wrap the fallback value in `Some` automatically @@ -677,14 +639,11 @@ export const orElse: { * * **Example** (Providing a fallback value) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.none().pipe(Option.orElseSome(() => "b"))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'b' } - * - * console.log(Option.some("a").pipe(Option.orElseSome(() => "b"))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'a' } + * Option.none().pipe(Option.orElseSome(() => "b")) // => Option.some("b") + * Option.some("a").pipe(Option.orElseSome(() => "b")) // => Option.some("a") * ``` * * @see {@link orElse} when the fallback is itself an `Option` @@ -716,14 +675,13 @@ export const orElseSome: { * * **Example** (Tracking value source) * - * ```ts - * import { Option } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * console.log(Option.orElseResult(Option.some("primary"), () => Option.some("fallback"))) - * // Output: { _id: 'Option', _tag: 'Some', value: { _tag: 'Failure', value: 'primary' } } + * const fallback = () => Option.some("fallback") * - * console.log(Option.orElseResult(Option.none(), () => Option.some("fallback"))) - * // Output: { _id: 'Option', _tag: 'Some', value: { _tag: 'Success', value: 'fallback' } } + * Option.orElseResult(Option.some("primary"), fallback) // => Option.some(Result.fail("primary")) + * Option.orElseResult(Option.none(), fallback) // => Option.some(Result.succeed("fallback")) * ``` * * @see {@link orElse} for the simpler variant without source tracking @@ -755,15 +713,14 @@ export const orElseResult: { * * **Example** (Finding the first Some) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.firstSomeOf([ + * Option.firstSomeOf([ * Option.none(), * Option.some(1), * Option.some(2) - * ])) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } + * ]) // => Option.some(1) * ``` * * @see {@link orElse} for a two-option fallback @@ -798,17 +755,12 @@ export const firstSomeOf = > = Iterable Option.none() + * Option.fromNullishOr(null) // => Option.none() + * Option.fromNullishOr(1) // => Option.some(1) * ``` * * @see {@link fromNullOr} to only treat `null` as absent @@ -838,17 +790,12 @@ export const fromNullishOr = ( * * **Example** (Converting possibly undefined values to an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.fromUndefinedOr(undefined)) - * // Output: { _id: 'Option', _tag: 'None' } - * - * console.log(Option.fromUndefinedOr(null)) - * // Output: { _id: 'Option', _tag: 'Some', value: null } - * - * console.log(Option.fromUndefinedOr(42)) - * // Output: { _id: 'Option', _tag: 'Some', value: 42 } + * Option.fromUndefinedOr(undefined) // => Option.none() + * Option.fromUndefinedOr(null) // => Option.some(null) + * Option.fromUndefinedOr(42) // => Option.some(42) * ``` * * @see {@link fromNullishOr} to treat both `null` and `undefined` as absent @@ -877,17 +824,12 @@ export const fromUndefinedOr = ( * * **Example** (Converting possibly null values to an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.fromNullOr(null)) - * // Output: { _id: 'Option', _tag: 'None' } - * - * console.log(Option.fromNullOr(undefined)) - * // Output: { _id: 'Option', _tag: 'Some', value: undefined } - * - * console.log(Option.fromNullOr(42)) - * // Output: { _id: 'Option', _tag: 'Some', value: 42 } + * Option.fromNullOr(null) // => Option.none() + * Option.fromNullOr(undefined) // => Option.some(undefined) + * Option.fromNullOr(42) // => Option.some(42) * ``` * * @see {@link fromNullishOr} to treat both `null` and `undefined` as absent @@ -915,7 +857,7 @@ export const fromNullOr = ( * * **Example** (Lifting a parser) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const parse = (s: string): number | undefined => { @@ -925,11 +867,8 @@ export const fromNullOr = ( * * const parseOption = Option.liftNullishOr(parse) * - * console.log(parseOption("1")) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } - * - * console.log(parseOption("not a number")) - * // Output: { _id: 'Option', _tag: 'None' } + * parseOption("1") // => Option.some(1) + * parseOption("not a number") // => Option.none() * ``` * * @see {@link fromNullishOr} for converting a single value @@ -957,14 +896,11 @@ export const liftNullishOr = , B>( * * **Example** (Unwrapping to null) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.getOrNull(Option.some(1))) - * // Output: 1 - * - * console.log(Option.getOrNull(Option.none())) - * // Output: null + * Option.getOrNull(Option.some(1)) // => 1 + * Option.getOrNull(Option.none()) // => null * ``` * * @see {@link getOrUndefined} to return `undefined` instead @@ -990,14 +926,11 @@ export const getOrNull: (self: Option) => A | null = getOrElse(constNull) * * **Example** (Unwrapping to undefined) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.getOrUndefined(Option.some(1))) - * // Output: 1 - * - * console.log(Option.getOrUndefined(Option.none())) - * // Output: undefined + * Option.getOrUndefined(Option.some(1)) // => 1 + * Option.getOrUndefined(Option.none()) // => undefined * ``` * * @see {@link getOrNull} to return `null` instead @@ -1022,16 +955,13 @@ export const getOrUndefined: (self: Option) => A | undefined = getOrElse(c * * **Example** (Lifting JSON.parse) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const parse = Option.liftThrowable(JSON.parse) * - * console.log(parse("1")) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } - * - * console.log(parse("")) - * // Output: { _id: 'Option', _tag: 'None' } + * parse("1") // => Option.some(1) + * parse("") // => Option.none() * ``` * * @see {@link liftNullishOr} for nullable-returning functions @@ -1065,14 +995,16 @@ export const liftThrowable = , B>( * * **Example** (Throwing a custom error) * - * ```ts - * import { Option } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * console.log(Option.getOrThrowWith(Option.some(1), () => new Error("missing"))) - * // Output: 1 + * Option.getOrThrowWith(Option.some(1), () => new Error("missing")) // => 1 * - * Option.getOrThrowWith(Option.none(), () => new Error("missing")) - * // throws Error: missing + * const failure = Result.try({ + * try: () => Option.getOrThrowWith(Option.none(), () => new Error("missing")), + * catch: (error) => (error as Error).message + * }) + * Result.getFailure(failure).pipe(Option.getOrElse(() => "no error")) // => "missing" * ``` * * @see {@link getOrThrow} for a version with a default error @@ -1106,14 +1038,16 @@ export const getOrThrowWith: { * * **Example** (Throwing a default error) * - * ```ts - * import { Option } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * console.log(Option.getOrThrow(Option.some(1))) - * // Output: 1 + * Option.getOrThrow(Option.some(1)) // => 1 * - * Option.getOrThrow(Option.none()) - * // throws Error: getOrThrow called on a None + * const failure = Result.try({ + * try: () => Option.getOrThrow(Option.none()), + * catch: (error) => (error as Error).message + * }) + * Result.getFailure(failure).pipe(Option.getOrElse(() => "no error")) // => "getOrThrow called on a None" * ``` * * @see {@link getOrThrowWith} for a custom error @@ -1140,14 +1074,11 @@ export const getOrThrow: (self: Option) => A = getOrThrowWith(() => new Er * * **Example** (Mapping over an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.map(Option.some(2), (n) => n * 2)) - * // Output: { _id: 'Option', _tag: 'Some', value: 4 } - * - * console.log(Option.map(Option.none(), (n: number) => n * 2)) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.map(Option.some(2), (n) => n * 2) // => Option.some(4) + * Option.map(Option.none(), (n: number) => n * 2) // => Option.none() * ``` * * @see {@link flatMap} when `f` returns an `Option` @@ -1174,14 +1105,11 @@ export const map: { * * **Example** (Replacing a value) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.as(Option.some(42), "new value")) - * // Output: { _id: 'Option', _tag: 'Some', value: 'new value' } - * - * console.log(Option.as(Option.none(), "new value")) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.as(Option.some(42), "new value") // => Option.some("new value") + * Option.as(Option.none(), "new value") // => Option.none() * ``` * * @see {@link asVoid} to replace with `undefined` @@ -1206,14 +1134,11 @@ export const as: { * * **Example** (Voiding the value) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.asVoid(Option.some(42))) - * // Output: { _id: 'Option', _tag: 'Some', value: undefined } - * - * console.log(Option.asVoid(Option.none())) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.asVoid(Option.some(42)) // => Option.some(undefined) + * Option.asVoid(Option.none()) // => Option.none() * ``` * * @see {@link as} to replace with a specific constant @@ -1234,11 +1159,10 @@ export { * * **Example** (Referencing Option.void) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.void) - * // Output: { _id: 'Option', _tag: 'Some', value: undefined } + * Option.void // => Option.some(undefined) * ``` * * @see {@link asVoid} to convert an existing `Option` to `Option` @@ -1266,7 +1190,7 @@ export { * * **Example** (Chaining optional lookups) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * interface User { @@ -1279,12 +1203,9 @@ export { * address: Option.some({ street: Option.some("123 Main St") }) * } * - * const street = user.address.pipe( + * user.address.pipe( * Option.flatMap((addr) => addr.street) - * ) - * - * console.log(street) - * // Output: { _id: 'Option', _tag: 'Some', value: '123 Main St' } + * ) // => Option.some("123 Main St") * ``` * * @see {@link map} when `f` returns a plain value @@ -1320,20 +1241,17 @@ export const flatMap: { * * **Example** (Chaining with andThen) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * // Chain with a function returning Option - * console.log(Option.andThen(Option.some(5), (x) => Option.some(x * 2))) - * // Output: { _id: 'Option', _tag: 'Some', value: 10 } + * Option.andThen(Option.some(5), (x) => Option.some(x * 2)) // => Option.some(10) * * // Chain with a static value - * console.log(Option.andThen(Option.some(5), "hello")) - * // Output: { _id: 'Option', _tag: 'Some', value: "hello" } + * Option.andThen(Option.some(5), "hello") // => Option.some("hello") * * // Chain with None - skips - * console.log(Option.andThen(Option.none(), (x) => Option.some(x * 2))) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.andThen(Option.none(), (x) => Option.some(x * 2)) // => Option.none() * ``` * * @see {@link flatMap} for the standard monadic bind @@ -1376,7 +1294,7 @@ export const andThen: { * * **Example** (Navigating optional properties) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * interface Employee { @@ -1387,12 +1305,9 @@ export const andThen: { * company: { address: { street: { name: "high street" } } } * } * - * console.log( - * Option.some(emp).pipe( - * Option.flatMapNullishOr((e) => e.company?.address?.street?.name) - * ) - * ) - * // Output: { _id: 'Option', _tag: 'Some', value: 'high street' } + * Option.some(emp).pipe( + * Option.flatMapNullishOr((e) => e.company?.address?.street?.name) + * ) // => Option.some("high street") * ``` * * @see {@link flatMap} when the function already returns `Option` @@ -1425,14 +1340,11 @@ export const flatMapNullishOr: { * * **Example** (Flattening nested Options) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.flatten(Option.some(Option.some("value")))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'value' } - * - * console.log(Option.flatten(Option.some(Option.none()))) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.flatten(Option.some(Option.some("value"))) // => Option.some("value") + * Option.flatten(Option.some(Option.none())) // => Option.none() * ``` * * @see {@link flatMap} which is `map` + `flatten` @@ -1457,14 +1369,11 @@ export const flatten: (self: Option>) => Option = flatMap(identi * * **Example** (Keeping the second value) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.zipRight(Option.some(1), Option.some("hello"))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'hello' } - * - * console.log(Option.zipRight(Option.none(), Option.some("hello"))) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.zipRight(Option.some(1), Option.some("hello")) // => Option.some("hello") + * Option.zipRight(Option.none(), Option.some("hello")) // => Option.none() * ``` * * @see {@link zipLeft} to keep the first value instead @@ -1493,14 +1402,11 @@ export const zipRight: { * * **Example** (Keeping the first value) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.zipLeft(Option.some("hello"), Option.some(1))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'hello' } - * - * console.log(Option.zipLeft(Option.some("hello"), Option.none())) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.zipLeft(Option.some("hello"), Option.some(1)) // => Option.some("hello") + * Option.zipLeft(Option.some("hello"), Option.none()) // => Option.none() * ``` * * @see {@link zipRight} to keep the second value instead @@ -1530,7 +1436,7 @@ export const zipLeft: { * * **Example** (Composing parsers) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const parse = (s: string): Option.Option => @@ -1541,11 +1447,8 @@ export const zipLeft: { * * const parseAndDouble = Option.composeK(parse, double) * - * console.log(parseAndDouble("42")) - * // Output: { _id: 'Option', _tag: 'Some', value: 84 } - * - * console.log(parseAndDouble("not a number")) - * // Output: { _id: 'Option', _tag: 'None' } + * parseAndDouble("42") // => Option.some(84) + * parseAndDouble("not a number") // => Option.none() * ``` * * @see {@link flatMap} for single-step chaining @@ -1575,17 +1478,14 @@ export const composeK: { * * **Example** (Validating without transforming) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const getInteger = (n: number) => * Number.isInteger(n) ? Option.some(n) : Option.none() * - * console.log(Option.tap(Option.some(1), getInteger)) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } - * - * console.log(Option.tap(Option.some(1.14), getInteger)) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.tap(Option.some(1), getInteger) // => Option.some(1) + * Option.tap(Option.some(1.14), getInteger) // => Option.none() * ``` * * @see {@link flatMap} when you want to transform the value @@ -1615,14 +1515,11 @@ export const tap: { * * **Example** (Pairing two Options) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.product(Option.some("hello"), Option.some(42))) - * // Output: { _id: 'Option', _tag: 'Some', value: ['hello', 42] } - * - * console.log(Option.product(Option.none(), Option.some(42))) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.product(Option.some("hello"), Option.some(42)) // => Option.some(["hello", 42]) + * Option.product(Option.none(), Option.some(42)) // => Option.none() * ``` * * @see {@link zipWith} to combine with a function instead of a tuple @@ -1650,17 +1547,14 @@ export const product = (self: Option, that: Option): Option<[A, B]> * * **Example** (Combining many Options) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const first = Option.some(1) * const rest = [Option.some(2), Option.some(3)] * - * console.log(Option.productMany(first, rest)) - * // Output: { _id: 'Option', _tag: 'Some', value: [1, 2, 3] } - * - * console.log(Option.productMany(first, [Option.some(2), Option.none()])) - * // Output: { _id: 'Option', _tag: 'None' } + * Option.productMany(first, rest) // => Option.some([1, 2, 3]) + * Option.productMany(first, [Option.some(2), Option.none()]) // => Option.none() * ``` * * @see {@link product} for combining exactly two @@ -1704,7 +1598,7 @@ export const productMany = ( * * **Example** (Combining a tuple and a struct) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const maybeName: Option.Option = Option.some("John") @@ -1712,17 +1606,11 @@ export const productMany = ( * * // ┌─── Option<[string, number]> * // ▼ - * const tuple = Option.all([maybeName, maybeAge]) - * console.log(tuple) - * // Output: - * // { _id: 'Option', _tag: 'Some', value: [ 'John', 25 ] } + * const tuple = Option.all([maybeName, maybeAge]) // => Option.some(["John", 25]) * * // ┌─── Option<{ name: string; age: number; }> * // ▼ - * const struct = Option.all({ name: maybeName, age: maybeAge }) - * console.log(struct) - * // Output: - * // { _id: 'Option', _tag: 'Some', value: { name: 'John', age: 25 } } + * const struct = Option.all({ name: maybeName, age: maybeAge }) // => Option.some({ name: "John", age: 25 }) * ``` * * @see {@link product} for combining exactly two @@ -1758,7 +1646,7 @@ export const all: > | Record> | Record ({ name: name.toUpperCase(), age }) - * ) - * - * console.log(person) - * // Output: - * // { _id: 'Option', _tag: 'Some', value: { name: 'JOHN', age: 25 } } + * ) // => Option.some({ name: "JOHN", age: 25 }) * ``` * * @see {@link product} to combine into a tuple instead @@ -1823,16 +1707,15 @@ export const zipWith: { * * **Example** (Summing present values) * - * ```ts + * ```ts import.meta.vitest * import { Option, pipe } from "effect" * * const items = [Option.some(1), Option.none(), Option.some(2), Option.none()] * - * console.log(pipe(items, Option.reduceCompact(0, (b, a) => b + a))) - * // Output: 3 + * pipe(items, Option.reduceCompact(0, (b, a) => b + a)) // => 3 * ``` * - * @category reducing + * @category folding * @since 2.0.0 */ export const reduceCompact: { @@ -1866,14 +1749,11 @@ export const reduceCompact: { * * **Example** (Converting to an array) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.toArray(Option.some(1))) - * // Output: [1] - * - * console.log(Option.toArray(Option.none())) - * // Output: [] + * Option.toArray(Option.some(1)) // => [1] + * Option.toArray(Option.none()) // => [] * ``` * * @see {@link fromIterable} for the inverse direction @@ -1899,7 +1779,7 @@ export const toArray = (self: Option): Array => isNone(self) ? [] : [se * * **Example** (Partitioning by Result) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * * const parseNumber = (s: string): Result.Result => { @@ -1907,14 +1787,9 @@ export const toArray = (self: Option): Array => isNone(self) ? [] : [se * return isNaN(n) ? Result.fail("Not a number") : Result.succeed(n) * } * - * console.log(Option.partitionMap(Option.some("42"), parseNumber)) - * // Output: [{ _id: 'Option', _tag: 'None' }, { _id: 'Option', _tag: 'Some', value: 42 }] - * - * console.log(Option.partitionMap(Option.some("abc"), parseNumber)) - * // Output: [{ _id: 'Option', _tag: 'Some', value: 'Not a number' }, { _id: 'Option', _tag: 'None' }] - * - * console.log(Option.partitionMap(Option.none(), parseNumber)) - * // Output: [{ _id: 'Option', _tag: 'None' }, { _id: 'Option', _tag: 'None' }] + * Option.partitionMap(Option.some("42"), parseNumber) // => [Option.none(), Option.some(42)] + * Option.partitionMap(Option.some("abc"), parseNumber) // => [Option.some("Not a number"), Option.none()] + * Option.partitionMap(Option.none(), parseNumber) // => [Option.none(), Option.none()] * ``` * * @see {@link filter} for simple predicate-based filtering @@ -1951,14 +1826,13 @@ export const partitionMap: { * * **Example** (Filtering and transforming) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Option.filterMap( + * Option.filterMap( * Option.some(2), * (n) => (n % 2 === 0 ? Result.succeed(`Even: ${n}`) : Result.failVoid) - * )) - * // Output: { _id: 'Option', _tag: 'Some', value: 'Even: 2' } + * ) // => Option.some("Even: 2") * ``` * * @see {@link filter} for predicate-based filtering @@ -1995,20 +1869,15 @@ export const filterMap: { * * **Example** (Filtering with a predicate) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const removeEmpty = (input: Option.Option) => * Option.filter(input, (value) => value !== "") * - * console.log(removeEmpty(Option.some("hello"))) - * // Output: { _id: 'Option', _tag: 'Some', value: 'hello' } - * - * console.log(removeEmpty(Option.some(""))) - * // Output: { _id: 'Option', _tag: 'None' } - * - * console.log(removeEmpty(Option.none())) - * // Output: { _id: 'Option', _tag: 'None' } + * removeEmpty(Option.some("hello")) // => Option.some("hello") + * removeEmpty(Option.some("")) // => Option.none() + * removeEmpty(Option.none()) // => Option.none() * ``` * * @see {@link filterMap} to transform and filter simultaneously @@ -2044,19 +1913,14 @@ export const filter: { * * **Example** (Comparing Options) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Option } from "effect" * * const eq = Option.makeEquivalence(Equivalence.strictEqual()) * - * console.log(eq(Option.some(1), Option.some(1))) - * // Output: true - * - * console.log(eq(Option.some(1), Option.some(2))) - * // Output: false - * - * console.log(eq(Option.none(), Option.none())) - * // Output: true + * eq(Option.some(1), Option.some(1)) // => true + * eq(Option.some(1), Option.some(2)) // => false + * eq(Option.none(), Option.none()) // => true * ``` * * @category instances @@ -2082,19 +1946,14 @@ export const makeEquivalence = (isEquivalent: Equivalence.Equivalence): Eq * * **Example** (Ordering Options) * - * ```ts + * ```ts import.meta.vitest * import { Number as N, Option } from "effect" * * const ord = Option.makeOrder(N.Order) * - * console.log(ord(Option.none(), Option.some(1))) - * // Output: -1 - * - * console.log(ord(Option.some(1), Option.none())) - * // Output: 1 - * - * console.log(ord(Option.some(1), Option.some(2))) - * // Output: -1 + * ord(Option.none(), Option.some(1)) // => -1 + * ord(Option.some(1), Option.none()) // => 1 + * ord(Option.some(1), Option.some(2)) // => -1 * ``` * * @category sorting @@ -2118,16 +1977,13 @@ export const makeOrder = (O: Order): Order> => * * **Example** (Lifting addition) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const addOptions = Option.lift2((a: number, b: number) => a + b) * - * console.log(addOptions(Option.some(2), Option.some(3))) - * // Output: { _id: 'Option', _tag: 'Some', value: 5 } - * - * console.log(addOptions(Option.some(2), Option.none())) - * // Output: { _id: 'Option', _tag: 'None' } + * addOptions(Option.some(2), Option.some(3)) // => Option.some(5) + * addOptions(Option.some(2), Option.none()) // => Option.none() * ``` * * @see {@link zipWith} for a non-lifted variant @@ -2157,16 +2013,13 @@ export const lift2 = (f: (a: A, b: B) => C): { * * **Example** (Validating positive numbers) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const parsePositive = Option.liftPredicate((n: number) => n > 0) * - * console.log(parsePositive(1)) - * // Output: { _id: 'Option', _tag: 'Some', value: 1 } - * - * console.log(parsePositive(-1)) - * // Output: { _id: 'Option', _tag: 'None' } + * parsePositive(1) // => Option.some(1) + * parsePositive(-1) // => Option.none() * ``` * * @see {@link filter} to apply a predicate to an existing `Option` @@ -2207,24 +2060,19 @@ export const liftPredicate: { // Note: I intentionally avoid using the NoInfer p * * **Example** (Checking with custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Option } from "effect" * * const check = Option.containsWith(Equivalence.strictEqual()) * - * console.log(Option.some(2).pipe(check(2))) - * // Output: true - * - * console.log(Option.some(1).pipe(check(2))) - * // Output: false - * - * console.log(Option.none().pipe(check(2))) - * // Output: false + * Option.some(2).pipe(check(2)) // => true + * Option.some(1).pipe(check(2)) // => false + * Option.none().pipe(check(2)) // => false * ``` * * @see {@link contains} for a version using default equality * - * @category elements + * @category predicates * @since 2.0.0 */ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { @@ -2248,23 +2096,18 @@ export const containsWith = (isEquivalent: (self: A, that: A) => boolean): { * * **Example** (Checking containment) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * - * console.log(Option.some(2).pipe(Option.contains(2))) - * // Output: true - * - * console.log(Option.some(1).pipe(Option.contains(2))) - * // Output: false - * - * console.log(Option.none().pipe(Option.contains(2))) - * // Output: false + * Option.some(2).pipe(Option.contains(2)) // => true + * Option.some(1).pipe(Option.contains(2)) // => false + * Option.none().pipe(Option.contains(2)) // => false * ``` * * @see {@link containsWith} for custom equality * @see {@link exists} to test with a predicate * - * @category elements + * @category predicates * @since 2.0.0 */ export const contains: { @@ -2288,25 +2131,20 @@ export const contains: { * * **Example** (Testing a condition) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const isEven = (n: number) => n % 2 === 0 * - * console.log(Option.some(2).pipe(Option.exists(isEven))) - * // Output: true - * - * console.log(Option.some(1).pipe(Option.exists(isEven))) - * // Output: false - * - * console.log(Option.none().pipe(Option.exists(isEven))) - * // Output: false + * Option.some(2).pipe(Option.exists(isEven)) // => true + * Option.some(1).pipe(Option.exists(isEven)) // => false + * Option.none().pipe(Option.exists(isEven)) // => false * ``` * * @see {@link filter} to keep or discard based on a predicate * @see {@link contains} to test for a specific value * - * @category elements + * @category predicates * @since 2.0.0 */ export const exists: { @@ -2335,24 +2173,22 @@ export const exists: { * * **Example** (Starting do notation) * - * ```ts + * ```ts import.meta.vitest * import { Option, pipe } from "effect" - * import * as assert from "node:assert" * - * const result = pipe( + * pipe( * Option.some(2), * Option.bindTo("x"), * Option.bind("y", () => Option.some(3)), * Option.let("sum", ({ x, y }) => x + y) - * ) - * assert.deepStrictEqual(result, Option.some({ x: 2, y: 3, sum: 5 })) + * ) // => Option.some({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link Do} for starting with an empty record * @see {@link bind} to add `Option` values * @see {@link let_ let} to add plain values * - * @category do notation + * @category mapping * @since 2.0.0 */ export const bindTo: { @@ -2383,24 +2219,22 @@ export { * * **Example** (Adding a computed value) * - * ```ts + * ```ts import.meta.vitest * import { Option, pipe } from "effect" - * import * as assert from "node:assert" * - * const result = pipe( + * pipe( * Option.Do, * Option.bind("x", () => Option.some(2)), * Option.bind("y", () => Option.some(3)), * Option.let("sum", ({ x, y }) => x + y) - * ) - * assert.deepStrictEqual(result, Option.some({ x: 2, y: 3, sum: 5 })) + * ) // => Option.some({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link Do} for starting the chain * @see {@link bind} to add `Option` values * @see {@link bindTo} to start by naming an existing `Option` * - * @category do notation + * @category mapping * @since 2.0.0 */ let_ as let @@ -2416,25 +2250,23 @@ export { * * **Example** (Binding Option values) * - * ```ts + * ```ts import.meta.vitest * import { Option, pipe } from "effect" - * import * as assert from "node:assert" * - * const result = pipe( + * pipe( * Option.Do, * Option.bind("x", () => Option.some(2)), * Option.bind("y", () => Option.some(3)), * Option.let("sum", ({ x, y }) => x + y), * Option.filter(({ x, y }) => x * y > 5) - * ) - * assert.deepStrictEqual(result, Option.some({ x: 2, y: 3, sum: 5 })) + * ) // => Option.some({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link Do} for starting the chain * @see {@link let_ let} to add plain values * @see {@link bindTo} to start by naming an existing `Option` * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bind: { @@ -2460,25 +2292,23 @@ export const bind: { * * **Example** (Building Option pipelines with do notation) * - * ```ts + * ```ts import.meta.vitest * import { Option, pipe } from "effect" - * import * as assert from "node:assert" * - * const result = pipe( + * pipe( * Option.Do, * Option.bind("x", () => Option.some(2)), * Option.bind("y", () => Option.some(3)), * Option.let("sum", ({ x, y }) => x + y), * Option.filter(({ x, y }) => x * y > 5) - * ) - * assert.deepStrictEqual(result, Option.some({ x: 2, y: 3, sum: 5 })) + * ) // => Option.some({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link bind} to add `Option` values * @see {@link let_ let} to add plain values * @see {@link bindTo} to start by naming an existing `Option` * - * @category do notation + * @category constructors * @since 2.0.0 */ export const Do: Option<{}> = some({}) @@ -2500,21 +2330,17 @@ export const Do: Option<{}> = some({}) * * **Example** (Sequencing Option computations with generator syntax) * - * ```ts + * ```ts import.meta.vitest * import { Option } from "effect" * * const maybeName: Option.Option = Option.some("John") * const maybeAge: Option.Option = Option.some(25) * - * const person = Option.gen(function*() { + * Option.gen(function*() { * const name = (yield* maybeName).toUpperCase() * const age = yield* maybeAge * return { name, age } - * }) - * - * console.log(person) - * // Output: - * // { _id: 'Option', _tag: 'Some', value: { name: 'JOHN', age: 25 } } + * }) // => Option.some({ name: "JOHN", age: 25 }) * ``` * * @see {@link Do} / {@link bind} for the do notation alternative @@ -2555,17 +2381,16 @@ export const gen: Gen.Gen = (...args) => { * * **Example** (Reducing with first-wins semantics) * - * ```ts + * ```ts import.meta.vitest * import { Number, Option } from "effect" * * const reducer = Option.makeReducer(Number.ReducerSum) - * console.log(reducer.combineAll([Option.some(1), Option.none(), Option.some(2)])) - * // Output: { _id: 'Option', _tag: 'Some', value: 3 } + * reducer.combineAll([Option.some(1), Option.none(), Option.some(2)]) // => Option.some(3) * ``` * * @see {@link makeReducerFailFast} for fail-fast semantics * - * @category Reducer + * @category constructors * @since 4.0.0 */ export function makeReducer(combiner: Combiner.Combiner): Reducer.Reducer> { @@ -2593,20 +2418,17 @@ export function makeReducer(combiner: Combiner.Combiner): Reducer.Reducer< * * **Example** (Fail-fast combining) * - * ```ts + * ```ts import.meta.vitest * import { Number, Option } from "effect" * * const combiner = Option.makeCombinerFailFast(Number.ReducerSum) - * console.log(combiner.combine(Option.some(1), Option.some(2))) - * // Output: { _id: 'Option', _tag: 'Some', value: 3 } - * - * console.log(combiner.combine(Option.some(1), Option.none())) - * // Output: { _id: 'Option', _tag: 'None' } + * combiner.combine(Option.some(1), Option.some(2)) // => Option.some(3) + * combiner.combine(Option.some(1), Option.none()) // => Option.none() * ``` * * @see {@link makeReducerFailFast} to get a full `Reducer` * - * @category Combiner + * @category constructors * @since 4.0.0 */ export function makeCombinerFailFast(combiner: Combiner.Combiner): Combiner.Combiner> { @@ -2633,21 +2455,18 @@ export function makeCombinerFailFast(combiner: Combiner.Combiner): Combine * * **Example** (Fail-fast reducing) * - * ```ts + * ```ts import.meta.vitest * import { Number, Option } from "effect" * * const reducer = Option.makeReducerFailFast(Number.ReducerSum) - * console.log(reducer.combineAll([Option.some(1), Option.some(2)])) - * // Output: { _id: 'Option', _tag: 'Some', value: 3 } - * - * console.log(reducer.combineAll([Option.some(1), Option.none()])) - * // Output: { _id: 'Option', _tag: 'None' } + * reducer.combineAll([Option.some(1), Option.some(2)]) // => Option.some(3) + * reducer.combineAll([Option.some(1), Option.none()]) // => Option.none() * ``` * * @see {@link makeCombinerFailFast} for just the combiner * @see {@link makeReducer} for non-fail-fast semantics * - * @category Reducer + * @category constructors * @since 4.0.0 */ export function makeReducerFailFast(reducer: Reducer.Reducer): Reducer.Reducer> { diff --git a/packages/effect/src/Order.ts b/packages/effect/src/Order.ts index dcc7bf42c..211b44bbd 100644 --- a/packages/effect/src/Order.ts +++ b/packages/effect/src/Order.ts @@ -31,7 +31,7 @@ import * as Reducer from "./Reducer.ts" * * **Example** (Defining a custom Order) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const byAge: Order.Order<{ name: string; age: number }> = (self, that) => { @@ -42,12 +42,12 @@ import * as Reducer from "./Reducer.ts" * * const person1 = { name: "Alice", age: 30 } * const person2 = { name: "Bob", age: 25 } - * console.log(byAge(person1, person2)) // 1 + * byAge(person1, person2) // => 1 * ``` * * @see {@link make} to create an order from a comparison function * @see {@link Ordering} for the result type of comparisons - * @category type class + * @category models * @since 2.0.0 */ export interface Order { @@ -66,7 +66,7 @@ export interface Order { * This is type-level only, has no runtime representation, and is used * internally by the Effect type system. * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface OrderTypeLambda extends TypeLambda { @@ -90,7 +90,7 @@ export interface OrderTypeLambda extends TypeLambda { * * **Example** (Creating an Order) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const byAge = Order.make<{ name: string; age: number }>((self, that) => { @@ -99,8 +99,8 @@ export interface OrderTypeLambda extends TypeLambda { * return 0 * }) * - * console.log(byAge({ name: "Alice", age: 30 }, { name: "Bob", age: 25 })) // 1 - * console.log(byAge({ name: "Alice", age: 25 }, { name: "Bob", age: 30 })) // -1 + * byAge({ name: "Alice", age: 30 }, { name: "Bob", age: 25 }) // => 1 + * byAge({ name: "Alice", age: 25 }, { name: "Bob", age: 30 }) // => -1 * ``` * * @see {@link mapInput} to transform an order by mapping the input type @@ -128,12 +128,12 @@ export function make( * * **Example** (Ordering strings) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * - * console.log(Order.String("apple", "banana")) // -1 - * console.log(Order.String("banana", "apple")) // 1 - * console.log(Order.String("apple", "apple")) // 0 + * Order.String("apple", "banana") // => -1 + * Order.String("banana", "apple") // => 1 + * Order.String("apple", "apple") // => 0 * ``` * * @see {@link mapInput} to compare objects by a string property @@ -158,15 +158,15 @@ export const String: Order = make((self, that) => self < that ? -1 : 1) * * **Example** (Ordering numbers) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * - * console.log(Order.Number(1, 1)) // 0 - * console.log(Order.Number(1, 2)) // -1 - * console.log(Order.Number(2, 1)) // 1 + * Order.Number(1, 1) // => 0 + * Order.Number(1, 2) // => -1 + * Order.Number(2, 1) // => 1 * - * console.log(Order.Number(0, -0)) // 0 - * console.log(Order.Number(NaN, 1)) // -1 + * Order.Number(0, -0) // => 0 + * Order.Number(NaN, 1) // => -1 * ``` * * @see {@link mapInput} to compare objects by a number property @@ -194,12 +194,12 @@ export const Number: Order = make((self, that) => { * * **Example** (Ordering booleans) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * - * console.log(Order.Boolean(false, true)) // -1 - * console.log(Order.Boolean(true, false)) // 1 - * console.log(Order.Boolean(true, true)) // 0 + * Order.Boolean(false, true) // => -1 + * Order.Boolean(true, false) // => 1 + * Order.Boolean(true, true) // => 0 * ``` * * @see {@link mapInput} to compare objects by a boolean property @@ -222,12 +222,12 @@ export const Boolean: Order = make((self, that) => self < that ? -1 : 1 * * **Example** (Ordering BigInts) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * - * console.log(Order.BigInt(1n, 2n)) // -1 - * console.log(Order.BigInt(2n, 1n)) // 1 - * console.log(Order.BigInt(1n, 1n)) // 0 + * Order.BigInt(1n, 2n) // => -1 + * Order.BigInt(2n, 1n) // => 1 + * Order.BigInt(1n, 1n) // => 0 * ``` * * @see {@link Number} for regular number comparisons @@ -252,14 +252,14 @@ export const BigInt: Order = make((self, that) => self < that ? -1 : 1) * * **Example** (Reversing an Order) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const flip = Order.flip(Order.Number) * - * console.log(flip(1, 2)) // 1 - * console.log(flip(2, 1)) // -1 - * console.log(flip(1, 1)) // 0 + * flip(1, 2) // => 1 + * flip(2, 1) // => -1 + * flip(1, 1) // => 0 * ``` * * @see {@link combine} to combine orders for multi-criteria comparison @@ -286,7 +286,7 @@ export function flip(O: Order): Order { * * **Example** (Combining two Orders) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const byAge = Order.mapInput( @@ -303,8 +303,8 @@ export function flip(O: Order): Order { * const person2 = { name: "Bob", age: 30 } * const person3 = { name: "Charlie", age: 25 } * - * console.log(byAgeAndName(person1, person2)) // -1 (Same age, Alice < Bob) - * console.log(byAgeAndName(person1, person3)) // 1 (Alice (30) > Charlie (25)) + * byAgeAndName(person1, person2) // => -1 + * byAgeAndName(person1, person3) // => 1 * ``` * * @see {@link combineAll} to combine multiple orders from a collection @@ -338,14 +338,14 @@ export const combine: { * * **Example** (Ordering with an always-equal Order) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const alwaysEqualOrder = Order.alwaysEqual() * - * console.log(alwaysEqualOrder(1, 2)) // 0 - * console.log(alwaysEqualOrder(2, 1)) // 0 - * console.log(alwaysEqualOrder(1, 1)) // 0 + * alwaysEqualOrder(1, 2) // => 0 + * alwaysEqualOrder(2, 1) // => 0 + * alwaysEqualOrder(1, 1) // => 0 * ``` * * @see {@link combine} to combine with other orders @@ -368,10 +368,11 @@ export function alwaysEqual(): Order { * * Applies orders in iteration order and short-circuits on the first non-zero * result. It returns `0` only if all orders return `0`. + * The collection is materialized when the order is created, so it must be finite. * * **Example** (Combining multiple Orders) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const byAge = Order.mapInput( @@ -388,7 +389,7 @@ export function alwaysEqual(): Order { * const person1 = { name: "Alice", age: 30 } * const person2 = { name: "Bob", age: 30 } * - * console.log(combinedOrder(person1, person2)) // -1 (Same age, Alice < Bob) + * combinedOrder(person1, person2) // => -1 * ``` * * @see {@link combine} to combine two orders @@ -397,9 +398,10 @@ export function alwaysEqual(): Order { * @since 2.0.0 */ export function combineAll(collection: Iterable>): Order { + const orders = Array.from(collection) return make((a1, a2) => { let out: Ordering = 0 - for (const O of collection) { + for (const O of orders) { out = O(a1, a2) if (out !== 0) { return out @@ -426,14 +428,14 @@ export function combineAll(collection: Iterable>): Order { * * **Example** (Mapping Input) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const byLength = Order.mapInput(Order.Number, (s: string) => s.length) * - * console.log(byLength("a", "bb")) // -1 - * console.log(byLength("bb", "a")) // 1 - * console.log(byLength("aa", "bb")) // 0 + * byLength("a", "bb") // => -1 + * byLength("bb", "a") // => 1 + * byLength("aa", "bb") // => 0 * ``` * * @see {@link combine} to combine mapped orders for multi-criteria comparison @@ -464,15 +466,15 @@ export const mapInput: { * * **Example** (Ordering Dates) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const date1 = new Date("2023-01-01") * const date2 = new Date("2023-01-02") * - * console.log(Order.Date(date1, date2)) // -1 - * console.log(Order.Date(date2, date1)) // 1 - * console.log(Order.Date(date1, date1)) // 0 + * Order.Date(date1, date2) // => -1 + * Order.Date(date2, date1) // => 1 + * Order.Date(date1, date1) // => 0 * ``` * * @see {@link mapInput} to compare objects by a date property @@ -496,14 +498,14 @@ export const Date: Order = mapInput(Number, (date) => date.getTime()) * * **Example** (Ordering tuples) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const tupleOrder = Order.Tuple([Order.Number, Order.String]) * - * console.log(tupleOrder([1, "a"], [2, "b"])) // -1 - * console.log(tupleOrder([1, "b"], [1, "a"])) // 1 - * console.log(tupleOrder([1, "a"], [1, "a"])) // 0 + * tupleOrder([1, "a"], [2, "b"]) // => -1 + * tupleOrder([1, "b"], [1, "a"]) // => 1 + * tupleOrder([1, "a"], [1, "a"]) // => 0 * ``` * * @see {@link Array} to compare arrays with length consideration @@ -560,15 +562,15 @@ export { * * **Example** (Ordering array elements) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const arrayOrder = Order.Array(Order.Number) * - * console.log(arrayOrder([1, 2], [1, 3])) // -1 - * console.log(arrayOrder([1, 2], [1, 2, 3])) // -1 (shorter array is less) - * console.log(arrayOrder([1, 2, 3], [1, 2])) // 1 (longer array is greater) - * console.log(arrayOrder([1, 2], [1, 2])) // 0 + * arrayOrder([1, 2], [1, 3]) // => -1 + * arrayOrder([1, 2], [1, 2, 3]) // => -1 + * arrayOrder([1, 2, 3], [1, 2]) // => 1 + * arrayOrder([1, 2], [1, 2]) // => 0 * ``` * * @see {@link Tuple} for type-safe tuple ordering @@ -593,7 +595,7 @@ export { * * **Example** (Ordering structs) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const personOrder = Order.Struct({ @@ -605,9 +607,9 @@ export { * const person2 = { name: "Bob", age: 25 } * const person3 = { name: "Alice", age: 25 } * - * console.log(personOrder(person1, person2)) // -1 (Alice < Bob) - * console.log(personOrder(person1, person3)) // 1 (same name, 30 > 25) - * console.log(personOrder(person1, person1)) // 0 + * personOrder(person1, person2) // => -1 + * personOrder(person1, person3) // => 1 + * personOrder(person1, person1) // => 0 * ``` * * @see {@link combine} to combine orders manually @@ -644,14 +646,14 @@ export function Struct }>( * * **Example** (Checking less-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const isLessThanNumber = Order.isLessThan(Order.Number) * - * console.log(isLessThanNumber(1, 2)) // true - * console.log(isLessThanNumber(2, 1)) // false - * console.log(isLessThanNumber(1, 1)) // false + * isLessThanNumber(1, 2) // => true + * isLessThanNumber(2, 1) // => false + * isLessThanNumber(1, 1) // => false * ``` * * @see {@link isLessThanOrEqualTo} for non-strict less than or equal @@ -678,14 +680,14 @@ export const isLessThan = (O: Order): { * * **Example** (Checking greater-than comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const isGreaterThanNumber = Order.isGreaterThan(Order.Number) * - * console.log(isGreaterThanNumber(2, 1)) // true - * console.log(isGreaterThanNumber(1, 2)) // false - * console.log(isGreaterThanNumber(1, 1)) // false + * isGreaterThanNumber(2, 1) // => true + * isGreaterThanNumber(1, 2) // => false + * isGreaterThanNumber(1, 1) // => false * ``` * * @see {@link isGreaterThanOrEqualTo} for non-strict greater than or equal @@ -712,14 +714,14 @@ export const isGreaterThan = (O: Order): { * * **Example** (Checking less-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const isLessThanOrEqualToNumber = Order.isLessThanOrEqualTo(Order.Number) * - * console.log(isLessThanOrEqualToNumber(1, 2)) // true - * console.log(isLessThanOrEqualToNumber(1, 1)) // true - * console.log(isLessThanOrEqualToNumber(2, 1)) // false + * isLessThanOrEqualToNumber(1, 2) // => true + * isLessThanOrEqualToNumber(1, 1) // => true + * isLessThanOrEqualToNumber(2, 1) // => false * ``` * * @see {@link isLessThan} for strict less than @@ -747,14 +749,14 @@ export const isLessThanOrEqualTo = (O: Order): { * * **Example** (Checking greater-than-or-equal comparisons) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const isGreaterThanOrEqualToNumber = Order.isGreaterThanOrEqualTo(Order.Number) * - * console.log(isGreaterThanOrEqualToNumber(2, 1)) // true - * console.log(isGreaterThanOrEqualToNumber(1, 1)) // true - * console.log(isGreaterThanOrEqualToNumber(1, 2)) // false + * isGreaterThanOrEqualToNumber(2, 1) // => true + * isGreaterThanOrEqualToNumber(1, 1) // => true + * isGreaterThanOrEqualToNumber(1, 2) // => false * ``` * * @see {@link isGreaterThan} for strict greater than @@ -782,14 +784,14 @@ export const isGreaterThanOrEqualTo = (O: Order): { * * **Example** (Selecting the minimum value) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const minNumber = Order.min(Order.Number) * - * console.log(minNumber(1, 2)) // 1 - * console.log(minNumber(2, 1)) // 1 - * console.log(minNumber(1, 1)) // 1 + * minNumber(1, 2) // => 1 + * minNumber(2, 1) // => 1 + * minNumber(1, 1) // => 1 * ``` * * @see {@link max} for the maximum of two values @@ -817,14 +819,14 @@ export const min = (O: Order): { * * **Example** (Selecting the maximum value) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const maxNumber = Order.max(Order.Number) * - * console.log(maxNumber(1, 2)) // 2 - * console.log(maxNumber(2, 1)) // 2 - * console.log(maxNumber(1, 1)) // 1 + * maxNumber(1, 2) // => 2 + * maxNumber(2, 1) // => 2 + * maxNumber(1, 1) // => 1 * ``` * * @see {@link min} for the minimum of two values @@ -854,14 +856,14 @@ export const max = (O: Order): { * * **Example** (Clamping values) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const clamp = Order.clamp(Order.Number)({ minimum: 1, maximum: 5 }) * - * console.log(clamp(3)) // 3 - * console.log(clamp(0)) // 1 - * console.log(clamp(6)) // 5 + * clamp(3) // => 3 + * clamp(0) // => 1 + * clamp(6) // => 5 * ``` * * @see {@link min} for the minimum of two values @@ -905,16 +907,16 @@ export const clamp = (O: Order): { * * **Example** (Checking ranges) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const betweenNumber = Order.isBetween(Order.Number) * - * console.log(betweenNumber(5, { minimum: 1, maximum: 10 })) // true - * console.log(betweenNumber(1, { minimum: 1, maximum: 10 })) // true - * console.log(betweenNumber(10, { minimum: 1, maximum: 10 })) // true - * console.log(betweenNumber(0, { minimum: 1, maximum: 10 })) // false - * console.log(betweenNumber(11, { minimum: 1, maximum: 10 })) // false + * betweenNumber(5, { minimum: 1, maximum: 10 }) // => true + * betweenNumber(1, { minimum: 1, maximum: 10 }) // => true + * betweenNumber(10, { minimum: 1, maximum: 10 }) // => true + * betweenNumber(0, { minimum: 1, maximum: 10 }) // => false + * betweenNumber(11, { minimum: 1, maximum: 10 }) // => false * ``` * * @see {@link clamp} to clamp a value to a range @@ -957,14 +959,14 @@ export const isBetween = (O: Order): { * * **Example** (Creating a Reducer) * - * ```ts + * ```ts import.meta.vitest * import { Order } from "effect" * * const reducer = Order.makeReducer() * const orders = [Order.Number, Order.flip(Order.Number)] * * const combined = reducer.combineAll(orders) - * console.log(combined(1, 2)) // -1 (uses first order) + * combined(1, 2) // => -1 * ``` * * @see {@link combine} to combine two orders diff --git a/packages/effect/src/Ordering.ts b/packages/effect/src/Ordering.ts index 8afcdb4ed..ff93ffca7 100644 --- a/packages/effect/src/Ordering.ts +++ b/packages/effect/src/Ordering.ts @@ -27,7 +27,7 @@ import * as Reducer_ from "./Reducer.ts" * * **Example** (Defining comparison results) * - * ```ts + * ```ts import.meta.vitest * import type { Ordering } from "effect" * * // Custom comparison function @@ -37,9 +37,9 @@ import * as Reducer_ from "./Reducer.ts" * return 0 * } * - * console.log(compareNumbers(5, 10)) // -1 (5 < 10) - * console.log(compareNumbers(10, 5)) // 1 (10 > 5) - * console.log(compareNumbers(5, 5)) // 0 (5 == 5) + * compareNumbers(5, 10) // => -1 + * compareNumbers(10, 5) // => 1 + * compareNumbers(5, 5) // => 0 * * // Using with string comparison * const compareStrings = (a: string, b: string): Ordering.Ordering => { @@ -63,13 +63,13 @@ export type Ordering = -1 | 0 | 1 * * **Example** (Reversing comparison order) * - * ```ts + * ```ts import.meta.vitest * import { Ordering } from "effect" * * // Basic reversal - * console.log(Ordering.reverse(1)) // -1 (greater becomes less) - * console.log(Ordering.reverse(-1)) // 1 (less becomes greater) - * console.log(Ordering.reverse(0)) // 0 (equal stays equal) + * Ordering.reverse(1) // => -1 + * Ordering.reverse(-1) // => 1 + * Ordering.reverse(0) // => 0 * * // Creating descending sort from ascending comparison * const compareNumbers = (a: number, b: number): Ordering.Ordering => @@ -103,9 +103,8 @@ export const reverse = (o: Ordering): Ordering => (o === -1 ? 1 : o === 1 ? -1 : * * **Example** (Pattern matching on orderings) * - * ```ts + * ```ts import.meta.vitest * import { Function, Ordering } from "effect" - * import * as assert from "node:assert" * * const toMessage = Ordering.match({ * onLessThan: Function.constant("less than"), @@ -113,9 +112,9 @@ export const reverse = (o: Ordering): Ordering => (o === -1 ? 1 : o === 1 ? -1 : * onGreaterThan: Function.constant("greater than") * }) * - * assert.deepStrictEqual(toMessage(-1), "less than") - * assert.deepStrictEqual(toMessage(0), "equal") - * assert.deepStrictEqual(toMessage(1), "greater than") + * toMessage(-1) // => "less than" + * toMessage(0) // => "equal" + * toMessage(1) // => "greater than" * ``` * * @category pattern matching diff --git a/packages/effect/src/PartitionedSemaphore.ts b/packages/effect/src/PartitionedSemaphore.ts index b5c2e8ce8..07aaabfb6 100644 --- a/packages/effect/src/PartitionedSemaphore.ts +++ b/packages/effect/src/PartitionedSemaphore.ts @@ -193,7 +193,6 @@ export const makeUnsafe = (options: { } const needed = permits - totalPermits - const taken = permits - needed if (totalPermits > 0) { totalPermits = 0 } @@ -217,8 +216,7 @@ export const makeUnsafe = (options: { } const cleanup = () => { - waiters.delete(entry) - if (waiters.size === 0) { + if (waiters.delete(entry) && waiters.size === 0) { MutableHashMap.remove(partitions, key) } } @@ -228,9 +226,7 @@ export const makeUnsafe = (options: { return Effect.sync(() => { cleanup() waitingPermits -= entry.permits - if (taken > 0) { - releaseUnsafe(taken) - } + releaseUnsafe(permits - entry.permits) }) }) } diff --git a/packages/effect/src/Path.ts b/packages/effect/src/Path.ts index 8acb1fdbe..946b18f0a 100644 --- a/packages/effect/src/Path.ts +++ b/packages/effect/src/Path.ts @@ -29,7 +29,7 @@ import { BadArgument } from "./PlatformError.ts" * @category type IDs * @since 4.0.0 */ -export const TypeId = "~effect/platform/Path" +export const TypeId = "~effect/Path" /** * Defines the service interface for platform-specific path manipulation. @@ -47,38 +47,38 @@ export const TypeId = "~effect/platform/Path" * * **Example** (Using path operations) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Path } from "effect" * * const program = Effect.gen(function*() { * const path = yield* Path.Path * - * // Use various path operations - * const joined = path.join("home", "user", "documents") - * const normalized = path.normalize("./path/../to/file.txt") - * const basename = path.basename("/path/to/file.txt") - * const dirname = path.dirname("/path/to/file.txt") - * const extname = path.extname("file.txt") - * const isAbs = path.isAbsolute("/absolute/path") - * const parsed = path.parse("/path/to/file.txt") - * const relative = path.relative("/from/path", "/to/path") - * const resolved = path.resolve("relative", "path") - * - * console.log({ - * joined, - * normalized, - * basename, - * dirname, - * extname, - * isAbs, - * parsed, - * relative, - * resolved - * }) + * return { + * joined: path.join("home", "user", "documents"), + * normalized: path.normalize("./path/../to/file.txt"), + * basename: path.basename("/path/to/file.txt"), + * dirname: path.dirname("/path/to/file.txt"), + * extname: path.extname("file.txt"), + * isAbsolute: path.isAbsolute("/absolute/path"), + * name: path.parse("/path/to/file.txt").name, + * relative: path.relative("/from/path", "/to/path"), + * resolved: path.resolve("/base", "relative", "path") + * } * }) + * + * const result = Effect.runSync(Effect.provide(program, Path.layer)) + * result.joined // => "home/user/documents" + * result.normalized // => "to/file.txt" + * result.basename // => "file.txt" + * result.dirname // => "/path/to" + * result.extname // => ".txt" + * result.isAbsolute // => true + * result.name // => "file" + * result.relative // => "../../to/path" + * result.resolved // => "/base/relative/path" * ``` * - * @category models + * @category services * @since 4.0.0 */ export interface Path { @@ -108,7 +108,7 @@ export interface Path { * * **Example** (Working with parsed paths) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Path } from "effect" * * // Access types and utilities in the Path namespace @@ -127,8 +127,10 @@ export interface Path { * name: "file" * } * - * console.log(parsed, exampleParsed) + * return [parsed.base, exampleParsed.base] * }) + * + * Effect.runSync(Effect.provide(program, Path.layer)) // => ["file.txt", "file.txt"] * ``` * * @since 4.0.0 @@ -150,7 +152,7 @@ export declare namespace Path { * * **Example** (Parsing and formatting paths) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Path } from "effect" * * const program = Effect.gen(function*() { @@ -158,23 +160,19 @@ export declare namespace Path { * * // Parse a path into its components * const parsed = path.parse("/home/user/documents/file.txt") - * console.log(parsed) - * // { - * // root: "/", - * // dir: "/home/user/documents", - * // base: "file.txt", - * // ext: ".txt", - * // name: "file" - * // } - * * // Format a path from its components * const formatted = path.format({ * dir: "/home/user", * name: "newfile", * ext: ".ts" * }) - * console.log(formatted) // "/home/user/newfile.ts" + * return { dir: parsed.dir, base: parsed.base, formatted } * }) + * + * const result = Effect.runSync(Effect.provide(program, Path.layer)) + * result.dir // => "/home/user/documents" + * result.base // => "file.txt" + * result.formatted // => "/home/user/newfile.ts" * ``` * * @category models @@ -198,7 +196,7 @@ export declare namespace Path { * * **Example** (Providing a custom Path service) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Layer, Path } from "effect" * * // Create a custom path implementation @@ -244,12 +242,11 @@ export declare namespace Path { * * const program = Effect.gen(function*() { * const path = yield* Path.Path - * const joined = path.join("home", "user", "file.txt") - * console.log(joined) // "home/user/file.txt" + * return path.join("home", "user", "file.txt") * }) * * // Run with custom path implementation - * const result = Effect.provide(program, customPathLayer) + * Effect.runSync(Effect.provide(program, customPathLayer)) // => "home/user/file.txt" * ``` * * @category services diff --git a/packages/effect/src/Pipeable.ts b/packages/effect/src/Pipeable.ts index e58dfac8a..6f2697e74 100644 --- a/packages/effect/src/Pipeable.ts +++ b/packages/effect/src/Pipeable.ts @@ -26,15 +26,16 @@ * * **Example** (Chaining operations with pipe) * - * ```ts + * ```ts import.meta.vitest * import { Effect } from "effect" * * // The Pipeable interface allows Effect values to be chained using the pipe method * const program = Effect.succeed(1).pipe( * Effect.map((x) => x + 1), - * Effect.flatMap((x) => Effect.succeed(x * 2)), - * Effect.tap((x) => Effect.log(`Result: ${x}`)) + * Effect.flatMap((x) => Effect.succeed(x * 2)) * ) + * + * Effect.runSync(program) // => 4 * ``` * * @category models @@ -539,7 +540,7 @@ export interface Pipeable { * * **Example** (Implementing a pipe method) * - * ```ts + * ```ts import.meta.vitest * import { Pipeable } from "effect" * * class NumberBox { @@ -554,7 +555,7 @@ export interface Pipeable { * (n) => n + 2, * (n) => n * 3 * ) - * console.log(result) // 21 + * result // => 21 * ``` * * @category combinators diff --git a/packages/effect/src/PlatformError.ts b/packages/effect/src/PlatformError.ts index 8b3dd7cea..b2fca10c2 100644 --- a/packages/effect/src/PlatformError.ts +++ b/packages/effect/src/PlatformError.ts @@ -11,7 +11,7 @@ */ import * as Data from "./Data.ts" -const TypeId = "~effect/platform/PlatformError" +const TypeId = "~effect/PlatformError" /** * Error data for an invalid argument passed to a platform API. @@ -30,7 +30,7 @@ const TypeId = "~effect/platform/PlatformError" * @see {@link SystemError} for failures reported by the host platform or operating system * @see {@link PlatformError} for the wrapper used by most platform APIs * - * @category models + * @category errors * @since 4.0.0 */ export class BadArgument extends Data.TaggedError("BadArgument")<{ @@ -69,7 +69,7 @@ export class BadArgument extends Data.TaggedError("BadArgument")<{ * @see {@link SystemError} for the error data that carries this tag on its `_tag` field * @see {@link systemError} for creating a `PlatformError` from a system failure with one of these tags * - * @category models + * @category errors * @since 4.0.0 */ export type SystemErrorTag = @@ -103,7 +103,7 @@ export type SystemErrorTag = * @see {@link BadArgument} for platform API failures caused by rejected caller input before an operation runs * @see {@link SystemErrorTag} for the normalized tag values stored in `_tag` * - * @category models + * @category errors * @since 4.0.0 */ export class SystemError extends Data.Error<{ @@ -151,7 +151,7 @@ export class SystemError extends Data.Error<{ * @see {@link badArgument} for creating this wrapper from rejected caller input * @see {@link systemError} for creating this wrapper from a host or operating-system failure * - * @category models + * @category errors * @since 4.0.0 */ export class PlatformError extends Data.TaggedError("PlatformError")<{ diff --git a/packages/effect/src/Pool.ts b/packages/effect/src/Pool.ts index 66d034fc3..104c0224f 100644 --- a/packages/effect/src/Pool.ts +++ b/packages/effect/src/Pool.ts @@ -16,9 +16,10 @@ import * as Duration from "./Duration.ts" import * as Effect from "./Effect.ts" import type * as Exit from "./Exit.ts" import * as Fiber from "./Fiber.ts" -import { dual, identity } from "./Function.ts" +import { constant, dual, identity } from "./Function.ts" +import * as core from "./internal/core.ts" +import * as internal from "./internal/effect.ts" import * as Iterable from "./Iterable.ts" -import * as Latch from "./Latch.ts" import { type Pipeable, pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" import * as Queue from "./Queue.ts" @@ -28,6 +29,14 @@ import * as Semaphore from "./Semaphore.ts" const TypeId = "~effect/Pool" +const Acquire = Symbol() +const AcquireContext = Symbol() + +interface PoolImpl extends Pool { + readonly [Acquire]: Effect.Effect + readonly [AcquireContext]: Context.Context +} + /** * A `Pool` is a pool of items of type `A`, each of which may be * associated with the acquisition and release of resources. An attempt to get @@ -76,6 +85,7 @@ export interface Pool extends Pipeable { export interface Config { readonly acquire: Effect.Effect readonly concurrency: number + readonly isFixed: boolean readonly minSize: number readonly maxSize: number readonly strategy: Strategy @@ -93,10 +103,8 @@ export interface Config { * * **Details** * - * This state tracks the pool scope, active and available items, invalidated - * items, semaphores, waiters, and shutdown status. It is exposed for - * inspection and implementation support; user code should prefer the - * high-level pool operations. + * This state is exposed for inspection and implementation support. User code + * should prefer the high-level pool operations. * * @see {@link Pool} for the pool value exposing this state * @see {@link PoolItem} for the entries stored in the runtime item sets @@ -109,13 +117,13 @@ export interface Config { export interface State { readonly scope: Scope.Scope isShuttingDown: boolean - readonly semaphore: Semaphore.Semaphore + usage: number readonly resizeSemaphore: Semaphore.Semaphore readonly items: Set> - readonly available: Set> - readonly availableLatch: Latch.Latch + availableHead: PoolItem | undefined + availableTail: PoolItem | undefined readonly invalidated: Set> - waiters: number + readonly waiters: Set<() => void> } /** @@ -144,6 +152,10 @@ export interface PoolItem { finalizer: Effect.Effect refCount: number disableReclaim: boolean + isAvailable: boolean + availablePrevious: PoolItem | undefined + availableNext: PoolItem | undefined + release: (exit: Exit.Exit) => Effect.Effect } /** @@ -184,7 +196,7 @@ export interface Strategy { * * This predicate narrows the input to `Pool`. * - * @category refinements + * @category guards * @since 2.0.0 */ export const isPool = (u: unknown): u is Pool => hasProperty(u, TypeId) @@ -223,7 +235,7 @@ export const make = (options: { readonly concurrency?: number | undefined readonly targetUtilization?: number | undefined }): Effect.Effect, never, R | Scope.Scope> => - makeWithStrategy({ ...options, min: options.size, max: options.size, strategy: strategyNoop() }) + makeWithStrategy({ ...options, min: options.size, max: options.size, strategy: strategyNoop }) /** * Creates a scoped pool with minimum and maximum sizes and a time-to-live @@ -250,7 +262,7 @@ export const make = (options: { * * **Example** (Creating a connection pool) * - * ```ts + * ```ts import.meta.vitest * import { Duration, Effect, Pool } from "effect" * * interface Connection { @@ -277,6 +289,8 @@ export const make = (options: { * (pool) => Effect.flatMap(Pool.get(pool), (connection) => connection.execute("select 1")) * ) * ) + * + * await Effect.runPromise(program) // => ["executed: select 1"] * ``` * * @category constructors @@ -338,6 +352,7 @@ export const makeWithStrategy = (options: { const config: Config = { acquire, concurrency, + isFixed: options.min === options.max, minSize: options.min, maxSize: options.max, strategy: options.strategy, @@ -346,16 +361,18 @@ export const makeWithStrategy = (options: { const state: State = { scope, isShuttingDown: false, - semaphore: Semaphore.makeUnsafe(concurrency * options.max), + usage: 0, resizeSemaphore: Semaphore.makeUnsafe(1), items: new Set(), - available: new Set(), - availableLatch: Latch.makeUnsafe(false), + availableHead: undefined, + availableTail: undefined, invalidated: new Set(), - waiters: 0 + waiters: new Set() } - const self: Pool = { + const self: PoolImpl = { [TypeId]: TypeId, + [Acquire]: options.acquire as Effect.Effect, + [AcquireContext]: services as Context.Context, config, state, pipe() { @@ -363,14 +380,18 @@ export const makeWithStrategy = (options: { } } yield* Scope.addFinalizer(scope, shutdown(self)) - yield* Effect.tap( - Effect.forkDetach(restore(resize(self))), - (fiber) => Scope.addFinalizer(scope, Fiber.interrupt(fiber)) - ) - yield* Effect.tap( - Effect.forkDetach(restore(options.strategy.run(self))), - (fiber) => Scope.addFinalizer(scope, Fiber.interrupt(fiber)) - ) + if (config.minSize > 0) { + yield* Effect.tap( + Effect.forkDetach(restore(resize(self)), { startImmediately: true }), + (fiber) => Scope.addFinalizer(scope, Fiber.interrupt(fiber)) + ) + } + if (options.strategy !== strategyNoop) { + yield* Effect.tap( + Effect.forkDetach(restore(options.strategy.run(self))), + (fiber) => Scope.addFinalizer(scope, Fiber.interrupt(fiber)) + ) + } return self })) @@ -386,13 +407,17 @@ const shutdown = Effect.fnUntraced(function*(self: Pool) { yield* semaphore.take(1) } else { self.state.items.delete(item) - self.state.available.delete(item) + removeAvailable(self, item) self.state.invalidated.delete(item) yield* item.finalizer } } yield* semaphore.releaseAll - self.state.availableLatch.openUnsafe() + if (self.state.waiters.size > 0) { + const waiters = Array.from(self.state.waiters) + self.state.waiters.clear() + for (const notify of waiters) notify() + } yield* semaphore.take(size) }) @@ -419,73 +444,294 @@ const shutdown = Effect.fnUntraced(function*(self: Pool) { * @since 2.0.0 */ export const get = (self: Pool): Effect.Effect => - Effect.suspend(() => { - if (self.state.isShuttingDown) { - return Effect.interrupt + core.withFiber((fiber) => { + const state = self.state + if (state.isShuttingDown) return internal.interrupt + if (state.availableHead !== undefined) { + state.usage++ + if (self.config.isFixed || targetSize(self) <= activeSize(self)) { + return leaseItem(self, state.availableHead, fiber) + } + state.usage-- } - return Effect.flatMap(getPoolItem(self), (item) => item.exit) + return getSlowWith(self, leaseItemWith) }) -const getPoolItem = (self: Pool): Effect.Effect, never, Scope.Scope> => - Effect.uninterruptibleMask((restore) => - restore(self.state.semaphore.take(1)).pipe( - Effect.flatMap(() => Effect.scope), - Effect.flatMap((scope) => - getPoolItemInner(self).pipe( - Effect.ensuring(Effect.sync(() => self.state.waiters--)), - Effect.tap((item) => { - if (item.exit._tag === "Failure") { - self.state.items.delete(item) - self.state.invalidated.delete(item) - self.state.available.delete(item) - return self.state.semaphore.release(1) - } - item.refCount++ - self.state.available.delete(item) - if (item.refCount < self.config.concurrency) { - self.state.available.add(item) - } - return Scope.addFinalizerExit(scope, () => - Effect.flatMap( - Effect.suspend(() => { - item.refCount-- - if (self.state.invalidated.has(item)) { - return invalidatePoolItem(self, item) - } - self.state.available.add(item) - return Effect.void - }), - () => self.state.semaphore.release(1) - )) - }), - Effect.onInterrupt(() => self.state.semaphore.release(1)) - ) - ) - ) - ) +/** + * Borrows an item while an effect runs and returns it when the effect exits. + * + * **When to use** + * + * Use when an item is needed by one effect. Unlike `Effect.scoped` with + * {@link get}, this avoids allocating a scope and registering a finalizer. + * + * **Example** (Running a single operation with a pooled item) + * + * ```ts import.meta.vitest + * import { Effect, Pool } from "effect" + * + * const program = Effect.scoped( + * Effect.flatMap( + * Pool.make({ acquire: Effect.succeed("resource"), size: 2 }), + * (pool) => Pool.use(pool, (item) => Effect.succeed(item.length)) + * ) + * ) + * + * await Effect.runPromise(program) // => 8 + * ``` + * + * @see {@link get} for borrowing an item for the lifetime of a scope + * + * @category combinators + * @since 4.0.0 + */ +export const use: { + ( + f: (item: A) => Effect.Effect + ): (self: Pool) => Effect.Effect + ( + self: Pool, + f: (item: A) => Effect.Effect + ): Effect.Effect +} = dual(2, ( + self: Pool, + f: (item: A) => Effect.Effect +): Effect.Effect => + internal.suspend(() => { + const state = self.state + if (state.isShuttingDown) return internal.interrupt + if (state.availableHead !== undefined) { + state.usage++ + if (self.config.isFixed || targetSize(self) <= activeSize(self)) { + return useItem(self, state.availableHead, f) + } + state.usage-- + } + return getSlowWith(self, (self, item, _fiber, restore) => useItem(self, item, f, restore)) + })) -const getPoolItemInner = Effect.fnUntraced(function*( - self: Pool -) { - self.state.waiters++ - if (self.state.isShuttingDown) { - return yield* Effect.interrupt - } else if (targetSize(self) > activeSize(self)) { - while (true) { - yield* self.state.resizeSemaphore.withPermitsIfAvailable(1)( - Effect.forkIn(Effect.interruptible(resize(self)), self.state.scope) - ) - if (self.state.isShuttingDown) { - return yield* Effect.interrupt - } else if (self.state.available.size > 0) { - return Iterable.headUnsafe(self.state.available) +const useItem = ( + self: Pool, + item: PoolItem, + f: (item: A) => Effect.Effect, + restore?: (effect: Effect.Effect) => Effect.Effect +): Effect.Effect => { + if (!leaseItemBookkeeping(self, item)) { + return item.exit as Exit.Exit + } + let body: Effect.Effect + try { + body = f((item.exit as Exit.Success).value) + } catch (defect) { + return internal.flatMap(item.release(item.exit), () => core.exitDie(defect)) + } + return internal.onExitPrimitive(restore !== undefined ? restore(body) : body, item.release) +} + +const getSlowWith = ( + self: Pool, + lease: ( + self: Pool, + item: PoolItem, + fiber: Fiber.Fiber, + restore: (effect: Effect.Effect) => Effect.Effect + ) => Effect.Effect +): Effect.Effect => + internal.uninterruptibleMask((restore) => { + const state = self.state + state.usage++ + const wait: Effect.Effect = internal.flatMap( + internal.onInterrupt( + restore(waitForItem(self)), + () => + internal.sync(() => { + state.usage-- + }) + ), + () => loop + ) + const step: Effect.Effect = core.withFiber((fiber) => { + if (state.isShuttingDown) { + state.usage-- + return internal.interrupt } - self.state.availableLatch.closeUnsafe() - yield* self.state.availableLatch.await + if (state.availableHead !== undefined) { + return lease(self, state.availableHead, fiber, restore) + } + return wait + }) + const loop: Effect.Effect = internal.suspend(() => { + if (state.isShuttingDown) { + state.usage-- + return internal.interrupt + } + return targetSize(self) > activeSize(self) + ? internal.flatMap( + state.resizeSemaphore.withPermitsIfAvailable(1)( + Effect.forkIn(Effect.interruptible(resize(self)), state.scope) + ), + () => step + ) + : step + }) + return loop + }) + +const leaseItemBookkeeping = (self: Pool, item: PoolItem): boolean => { + const state = self.state + if (item.exit._tag === "Failure") { + state.usage-- + state.items.delete(item) + state.invalidated.delete(item) + removeAvailable(self, item) + return false + } + item.refCount++ + if (item.refCount >= self.config.concurrency) { + removeAvailable(self, item) + } + return true +} + +const leaseItem = ( + self: Pool, + item: PoolItem, + fiber: Fiber.Fiber +): Effect.Effect => { + if (!leaseItemBookkeeping(self, item)) { + return item.exit + } + const scope = Context.getUnsafe(fiber.context, Scope.Scope) + if (scope.state._tag === "Closed") { + return internal.flatMap(item.release(item.exit), () => item.exit) + } + internal.scopeAddFinalizerUnsafe(scope, {}, item.release) + return item.exit +} + +const leaseItemWith = ( + self: Pool, + item: PoolItem, + fiber: Fiber.Fiber +): Effect.Effect => leaseItem(self, item, fiber) + +const releaseItem = (self: Pool, item: PoolItem): Effect.Effect => + core.withFiber((fiber) => { + const state = self.state + item.refCount-- + state.usage-- + if (state.invalidated.has(item)) { + return invalidatePoolItem(self, item) + } + // Every release frees one slot, so it can admit one waiter. Reacting only + // to the saturated-to-unsaturated transition strands the rest: several + // leases returning at once would wake a single waiter and leave the others + // asleep against an item that has capacity for them. `addAvailable` is + // idempotent, so re-adding an available item is free. + if (item.refCount < self.config.concurrency) { + addAvailableFront(self, item) + wakeWaiters(self, fiber, 1) } + return internal.void + }) + +const waitForItem = (self: Pool): Effect.Effect => + internal.callback((resume) => { + const state = self.state + if (state.availableHead !== undefined || state.isShuttingDown) { + return resume(internal.void) + } + const observer = () => { + state.waiters.delete(observer) + resume(internal.void) + } + state.waiters.add(observer) + return internal.sync(() => { + state.waiters.delete(observer) + }) + }) + +const wakeWaiters = (self: Pool, fiber: Fiber.Fiber, count: number) => { + const waiters = self.state.waiters + if (waiters.size === 0) return + fiber.currentDispatcher.scheduleTask(() => { + let remaining = count + const toWake: Array<() => void> = [] + for (const notify of waiters) { + if (remaining-- <= 0) break + toWake.push(notify) + } + for (let i = 0; i < toWake.length; i++) { + toWake[i]() + } + }, 0) +} + +const wakeAll = (self: Pool): Effect.Effect => + core.withFiber((fiber) => { + wakeWaiters(self, fiber, Number.POSITIVE_INFINITY) + return internal.void + }) + +// Reservations prevent reuse without extending the lifetime of borrowed items. +const reservations = new WeakMap, number>() + +/** Adds a freshly acquired item, which has no use behind it, at the back. */ +const addAvailable = (self: Pool, item: PoolItem): void => { + if (item.isAvailable || reservations.has(item)) return + item.isAvailable = true + item.availablePrevious = self.state.availableTail + item.availableNext = undefined + if (self.state.availableTail !== undefined) { + self.state.availableTail.availableNext = item + } else { + self.state.availableHead = item } - return Iterable.headUnsafe(self.state.available) -}) + self.state.availableTail = item +} + +/** + * Returns a released item at the front, so the next borrow gets the one used + * most recently. Borrowers take from the front, so the list runs warmest + * first. + * + * Sending it to the back instead spreads a sequence of borrows evenly over + * every item the pool has open. For a pool of connections that means none of + * them is ever the hot one - each borrow lands on a peer that has been sitting + * idle, losing whatever warmth it had - and it means `timeToLive` never + * reclaims anything, because a pool that grew for one burst keeps every item + * equally fresh forever. Under saturation the two orders agree, since every + * item is checked out either way. + */ +const addAvailableFront = (self: Pool, item: PoolItem): void => { + if (item.isAvailable || reservations.has(item)) return + item.isAvailable = true + item.availablePrevious = undefined + item.availableNext = self.state.availableHead + if (self.state.availableHead !== undefined) { + self.state.availableHead.availablePrevious = item + } else { + self.state.availableTail = item + } + self.state.availableHead = item +} + +const removeAvailable = (self: Pool, item: PoolItem): void => { + if (!item.isAvailable) return + item.isAvailable = false + if (item.availablePrevious !== undefined) { + item.availablePrevious.availableNext = item.availableNext + } else { + self.state.availableHead = item.availableNext + } + if (item.availableNext !== undefined) { + item.availableNext.availablePrevious = item.availablePrevious + } else { + self.state.availableTail = item.availablePrevious + } + item.availablePrevious = undefined + item.availableNext = undefined +} /** * Invalidates the specified item so the pool can remove it and reallocate the @@ -507,9 +753,9 @@ const getPoolItemInner = Effect.fnUntraced(function*( * @since 2.0.0 */ export const invalidate: { - (item: A): (self: Pool) => Effect.Effect - (self: Pool, item: A): Effect.Effect -} = dual(2, (self: Pool, item: A): Effect.Effect => + (item: A): (self: Pool) => Effect.Effect + (self: Pool, item: A): Effect.Effect +} = dual(2, (self: Pool, item: A): Effect.Effect => Effect.suspend(() => { if (self.state.isShuttingDown) return Effect.void for (const poolItem of self.state.items) { @@ -521,22 +767,82 @@ export const invalidate: { return Effect.void })) +/** + * Reserves a leased item for exclusive use until the scope closes. This + * removes the item's remaining capacity from the pool but does not wait + * for existing leases to finish. It has no effect when per-item concurrency is + * `1` or the pool does not contain the item. + * + * @see {@link get} for acquiring an item + * + * @category combinators + * @since 4.0.0 + */ +export const reserve: { + (item: A): (self: Pool) => Effect.Effect + (self: Pool, item: A): Effect.Effect +} = dual( + 2, + (self: Pool, item: A): Effect.Effect => + Effect.asVoid(Effect.acquireRelease( + Effect.sync(() => { + if (self.config.concurrency === 1) return undefined + for (const poolItem of self.state.items) { + if (poolItem.exit._tag !== "Success" || poolItem.exit.value !== item) continue + const existing = reservations.get(poolItem) + if (existing === undefined) self.state.usage += self.config.concurrency - 1 + reservations.set(poolItem, (existing ?? 0) + 1) + removeAvailable(self, poolItem) + return poolItem + } + return undefined + }), + (poolItem) => + core.withFiber((fiber) => { + if (poolItem === undefined) return internal.void + const remaining = (reservations.get(poolItem) ?? 1) - 1 + if (remaining > 0) { + reservations.set(poolItem, remaining) + return internal.void + } + reservations.delete(poolItem) + self.state.usage -= self.config.concurrency - 1 + if ( + !self.state.isShuttingDown && + self.state.items.has(poolItem) && + !self.state.invalidated.has(poolItem) && + poolItem.refCount < self.config.concurrency + ) { + addAvailableFront(self, poolItem) + wakeWaiters(self, fiber, self.config.concurrency - poolItem.refCount) + } + return internal.void + }) + )) +) + const invalidatePoolItem = (self: Pool, poolItem: PoolItem): Effect.Effect => Effect.suspend(() => { if (!self.state.items.has(poolItem)) { return Effect.void } else if (poolItem.refCount === 0) { self.state.items.delete(poolItem) - self.state.available.delete(poolItem) + removeAvailable(self, poolItem) self.state.invalidated.delete(poolItem) return Effect.asVoid(Effect.flatMap( poolItem.finalizer, - () => Effect.forkIn(Effect.interruptible(resize(self)), self.state.scope) + () => Effect.forkIn(Effect.interruptible(resize(self)), self.state.scope, { startImmediately: true }) )) } self.state.invalidated.add(poolItem) - self.state.available.delete(poolItem) - return Effect.void + removeAvailable(self, poolItem) + // An invalidated item stops counting towards the pool's active size, so the + // pool is now below target and has to top itself back up. Waiting for the + // last lease to be returned would strand anybody already queued: the item + // they are waiting for is never coming back. + return Effect.asVoid( + Effect.forkIn(Effect.interruptible(resize(self)), self.state.scope, { startImmediately: true }) + ) }) const resize = (self: Pool): Effect.Effect => @@ -550,52 +856,76 @@ const resizeLoop = (self: Pool): Effect.Effect => return Effect.void } const toAcquire = target - active - return self.config.strategy.reclaim(self).pipe( - Effect.flatMap((item) => item ? Effect.succeed(item) : allocate(self)), + const acquireOne = self.config.strategy === strategyNoop + ? allocate(self) + : Effect.flatMap( + self.config.strategy.reclaim(self), + (item) => item ? Effect.succeed(item) : allocate(self) + ) + if (toAcquire === 1) { + const acquired = Effect.tap(acquireOne, wakeAll(self)) + return self.config.isFixed + ? Effect.asVoid(acquired) + : Effect.flatMap(acquired, (item) => item.exit._tag === "Failure" ? Effect.void : resizeLoop(self)) + } + const acquired = acquireOne.pipe( Effect.replicateEffect(toAcquire, { concurrency: toAcquire }), - Effect.tap(self.state.availableLatch.open), - Effect.flatMap((items) => items.some((_) => _.exit._tag === "Failure") ? Effect.void : resizeLoop(self)) + Effect.tap(wakeAll(self)) ) + return self.config.isFixed + ? Effect.asVoid(acquired) + : Effect.flatMap( + acquired, + (items) => items.some((_) => _.exit._tag === "Failure") ? Effect.void : resizeLoop(self) + ) }) const allocate = (self: Pool): Effect.Effect> => - Effect.acquireUseRelease( - Scope.make(), - (scope) => - self.config.acquire.pipe( - Scope.provide(scope), - Effect.exit, - Effect.flatMap((exit) => { - const item: PoolItem = { - exit, - finalizer: Effect.catchCause(Scope.close(scope, exit), reportUnhandledError), - refCount: 0, - disableReclaim: false - } - self.state.items.add(item) - self.state.available.add(item) - return Effect.as( - exit._tag === "Success" - ? self.config.strategy.onAcquire(item) - : Effect.flatMap(item.finalizer, () => self.config.strategy.onAcquire(item)), - item - ) - }) - ), - (scope, exit) => exit._tag === "Failure" ? Scope.close(scope, exit) : Effect.void + internal.uninterruptibleMask((restore) => + core.withFiber((fiber) => { + const impl = self as PoolImpl + const scope = internal.scopeMakeUnsafe() + const previousContext = fiber.context + fiber.setContext(Context.add(impl[AcquireContext], Scope.Scope, scope)) + const use = Effect.flatMap(Effect.exit(impl[Acquire]), (exit) => { + const item: PoolItem = { + exit, + finalizer: Effect.catchCause(Scope.close(scope, exit), reportUnhandledError), + refCount: 0, + disableReclaim: false, + isAvailable: false, + availablePrevious: undefined, + availableNext: undefined, + release: undefined as any + } + item.release = constant(releaseItem(self, item)) + self.state.items.add(item) + addAvailable(self, item) + if (self.config.strategy === strategyNoop) { + return exit._tag === "Success" ? Effect.succeed(item) : Effect.as(item.finalizer, item) + } + return Effect.as( + exit._tag === "Success" + ? self.config.strategy.onAcquire(item) + : Effect.flatMap(item.finalizer, () => self.config.strategy.onAcquire(item)), + item + ) + }) + return internal.onExitPrimitive( + restore(use) as Effect.Effect>, + (exit) => { + fiber.setContext(previousContext) + return exit._tag === "Failure" ? internal.scopeCloseUnsafe(scope, exit) : undefined + }, + true + ) + }) ) -const currentUsage = (self: Pool) => { - let count = self.state.waiters - for (const item of self.state.items) { - count += item.refCount - } - return count -} - const targetSize = (self: Pool) => { if (self.state.isShuttingDown) return 0 - const utilization = currentUsage(self) / self.config.targetUtilization + if (self.config.isFixed) return self.config.minSize + const utilization = self.state.usage / self.config.targetUtilization const target = Math.ceil(utilization / self.config.concurrency) return Math.min(Math.max(self.config.minSize, target), self.config.maxSize) } @@ -608,11 +938,11 @@ const activeSize = (self: Pool) => { // Strategy // ----------------------------------------------------------------------------- -const strategyNoop = (): Strategy => ({ +const strategyNoop: Strategy = { run: (_) => Effect.void, onAcquire: (_) => Effect.void, reclaim: (_) => Effect.undefined -}) +} const strategyCreationTTL = Effect.fnUntraced(function*(ttl: Duration.Input) { const clock = yield* Clock @@ -671,14 +1001,14 @@ const strategyUsageTTL = Effect.fnUntraced(function*(ttl: Duration.Input) return Effect.undefined } const item = Iterable.head( - Iterable.filter(pool.state.invalidated, (item) => !item.disableReclaim) + Iterable.filter(pool.state.invalidated, (item) => !item.disableReclaim && !reservations.has(item)) ) if (item._tag === "None") { return Effect.undefined } pool.state.invalidated.delete(item.value) if (item.value.refCount < pool.config.concurrency) { - pool.state.available.add(item.value) + addAvailable(pool, item.value) } return Effect.as(Queue.offer(queue, item.value), item.value) }) diff --git a/packages/effect/src/Predicate.ts b/packages/effect/src/Predicate.ts index b5ac5040c..f66458aaf 100644 --- a/packages/effect/src/Predicate.ts +++ b/packages/effect/src/Predicate.ts @@ -29,12 +29,12 @@ import type { TupleOf, TupleOfAtLeast } from "./Types.ts" * * **Example** (Defining a predicate) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isPositive: Predicate.Predicate = (n) => n > 0 * - * console.log(isPositive(1)) + * isPositive(1) // => true * ``` * * @see {@link Refinement} @@ -62,15 +62,18 @@ export interface Predicate { * * **Example** (Type-level usage) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type P = Predicate.Predicate * type TL = Predicate.PredicateTypeLambda + * + * const witness: P = (value) => value > 0 + * witness(1) // => true * ``` * * @see {@link Predicate} - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface PredicateTypeLambda extends TypeLambda { @@ -93,14 +96,14 @@ export interface PredicateTypeLambda extends TypeLambda { * * **Example** (Narrowing unknown values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isString: Predicate.Refinement = (u): u is string => typeof u === "string" * * const data: unknown = "hello" * if (isString(data)) { - * console.log(data.toUpperCase()) + * data.toUpperCase() // => "HELLO" * } * ``` * @@ -129,11 +132,13 @@ export interface Refinement { * * **Example** (Extracting predicate input) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type IsString = Predicate.Predicate * type Input = Predicate.Predicate.In + * + * const input: Input = "value" * ``` * * @see {@link Predicate} @@ -156,11 +161,13 @@ export declare namespace Predicate { * * **Example** (Inferring the input type) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type P = Predicate.Predicate * type Input = Predicate.Predicate.In

+ * + * const input: Input = 1 * ``` * * @see {@link Predicate.Any} @@ -183,10 +190,13 @@ export declare namespace Predicate { * * **Example** (Using generic constraints) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type AnyPredicate = Predicate.Predicate.Any + * + * const witness: AnyPredicate = () => true + * witness("value") // => true * ``` * * @see {@link Predicate.In} @@ -211,12 +221,14 @@ export declare namespace Predicate { * * **Example** (Extracting refinement types) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type IsString = Predicate.Refinement * type Input = Predicate.Refinement.In * type Output = Predicate.Refinement.Out + * + * const output: Output = "value" * ``` * * @see {@link Refinement} @@ -238,11 +250,13 @@ export declare namespace Refinement { * * **Example** (Inferring the input type) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type R = Predicate.Refinement * type Input = Predicate.Refinement.In + * + * const input: Input = "value" * ``` * * @see {@link Refinement.Out} @@ -267,11 +281,13 @@ export declare namespace Refinement { * * **Example** (Inferring the output type) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type R = Predicate.Refinement * type Output = Predicate.Refinement.Out + * + * const output: Output = "value" * ``` * * @see {@link Refinement.In} @@ -293,10 +309,13 @@ export declare namespace Refinement { * * **Example** (Using generic constraints) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * type AnyRefinement = Predicate.Refinement.Any + * + * const witness: AnyRefinement = (_): _ is string => true + * witness("value") // => true * ``` * * @see {@link Refinement.In} @@ -322,14 +341,14 @@ export declare namespace Refinement { * * **Example** (Checking string length) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isLongerThan2 = Predicate.mapInput((s: string) => s.length)( * (n: number) => n > 2 * ) * - * console.log(isLongerThan2("hello")) + * isLongerThan2("hello") // => true * ``` * * @see {@link Predicate} @@ -358,12 +377,12 @@ export const mapInput: { * * **Example** (Checking exact length) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isPair = Predicate.isTupleOf(2) * - * console.log(isPair([1, 2])) + * isPair([1, 2]) // => true * ``` * * @see {@link isTupleOfAtLeast} @@ -391,12 +410,12 @@ export const isTupleOf: { * * **Example** (Checking minimum length) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const hasAtLeast2 = Predicate.isTupleOfAtLeast(2) * - * console.log(hasAtLeast2([1, 2, 3])) + * hasAtLeast2([1, 2, 3]) // => true * ``` * * @see {@link isTupleOf} @@ -424,18 +443,16 @@ export const isTupleOfAtLeast: { * * **Example** (Filtering truthy values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const values = [0, 1, "", "ok", false] - * const truthy = values.filter(Predicate.isTruthy) - * - * console.log(truthy) + * const truthy = values.filter(Predicate.isTruthy) // => [1, "ok"] * ``` * * @see {@link isNullish} * @see {@link isNotNullish} - * @category guards + * @category predicates * @since 2.0.0 */ export function isTruthy(input: unknown): boolean { @@ -455,13 +472,13 @@ export function isTruthy(input: unknown): boolean { * * **Example** (Guarding a Set) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = new Set([1, 2]) * * if (Predicate.isSet(data)) { - * console.log(data.size) + * data.size // => 2 * } * ``` * @@ -487,13 +504,13 @@ export function isSet(input: unknown): input is Set { * * **Example** (Guarding a Map) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = new Map([["a", 1]]) * * if (Predicate.isMap(data)) { - * console.log(data.size) + * data.size // => 1 * } * ``` * @@ -520,13 +537,13 @@ export function isMap(input: unknown): input is Map { * * **Example** (Guarding strings) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = "hi" * * if (Predicate.isString(data)) { - * console.log(data.toUpperCase()) + * data.toUpperCase() // => "HI" * } * ``` * @@ -554,13 +571,13 @@ export function isString(input: unknown): input is string { * * **Example** (Guarding numbers) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = 42 * * if (Predicate.isNumber(data)) { - * console.log(data + 1) + * data + 1 // => 43 * } * ``` * @@ -587,13 +604,13 @@ export function isNumber(input: unknown): input is number { * * **Example** (Guarding booleans) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = true * * if (Predicate.isBoolean(data)) { - * console.log(data ? "yes" : "no") + * data ? "yes" : "no" // => "yes" * } * ``` * @@ -620,13 +637,13 @@ export function isBoolean(input: unknown): input is boolean { * * **Example** (Guarding bigints) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = 1n * * if (Predicate.isBigInt(data)) { - * console.log(data + 2n) + * data + 2n // => 3n * } * ``` * @@ -652,13 +669,13 @@ export function isBigInt(input: unknown): input is bigint { * * **Example** (Guarding symbols) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = Symbol.for("id") * * if (Predicate.isSymbol(data)) { - * console.log(data.description) + * data.description // => "id" * } * ``` * @@ -684,14 +701,14 @@ export function isSymbol(input: unknown): input is symbol { * * **Example** (Guarding property keys) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const key: unknown = "name" * const obj: Record = { name: "Ada" } * * if (Predicate.isPropertyKey(key) && key in obj) { - * console.log(obj[key]) + * obj[key] // => "Ada" * } * ``` * @@ -719,13 +736,13 @@ export function isPropertyKey(u: unknown): u is PropertyKey { * * **Example** (Guarding functions) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = () => 1 * * if (Predicate.isFunction(data)) { - * console.log(data()) + * data() // => 1 * } * ``` * @@ -751,12 +768,12 @@ export function isFunction(input: unknown): input is Function { * * **Example** (Guarding undefined values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = undefined * - * console.log(Predicate.isUndefined(data)) + * Predicate.isUndefined(data) // => true * ``` * * @see {@link isNotUndefined} @@ -782,13 +799,11 @@ export function isUndefined(input: unknown): input is undefined { * * **Example** (Filtering undefined values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const values = [1, undefined, 2] - * const defined = values.filter(Predicate.isNotUndefined) - * - * console.log(defined) + * const defined = values.filter(Predicate.isNotUndefined) // => [1, 2] * ``` * * @see {@link isUndefined} @@ -813,12 +828,12 @@ export function isNotUndefined(input: A): input is Exclude { * * **Example** (Guarding null values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = null * - * console.log(Predicate.isNull(data)) + * Predicate.isNull(data) // => true * ``` * * @see {@link isNotNull} @@ -844,13 +859,11 @@ export function isNull(input: unknown): input is null { * * **Example** (Filtering null values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const values = [1, null, 2] - * const nonNull = values.filter(Predicate.isNotNull) - * - * console.log(nonNull) + * const nonNull = values.filter(Predicate.isNotNull) // => [1, 2] * ``` * * @see {@link isNull} @@ -875,13 +888,11 @@ export function isNotNull(input: A): input is Exclude { * * **Example** (Guarding nullish values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const values = [0, null, "", undefined] - * const nullish = values.filter(Predicate.isNullish) - * - * console.log(nullish) + * const nullish = values.filter(Predicate.isNullish) // => [null, undefined] * ``` * * @see {@link isNotNullish} @@ -908,13 +919,11 @@ export function isNullish(input: A): input is A & (null | undefined) { * * **Example** (Filtering non-nullish values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const values = [0, null, "", undefined] - * const present = values.filter(Predicate.isNotNullish) - * - * console.log(present) + * const present = values.filter(Predicate.isNotNullish) // => [0, ""] * ``` * * @see {@link isNullish} @@ -936,10 +945,10 @@ export function isNotNullish(input: A): input is NonNullable { * * **Example** (Matching no values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * - * console.log(Predicate.isNever("anything")) + * Predicate.isNever("anything") // => false * ``` * * @see {@link isUnknown} @@ -959,10 +968,10 @@ export function isNever(_: unknown): _ is never { * * **Example** (Matching every value) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * - * console.log(Predicate.isUnknown(123)) + * Predicate.isUnknown(123) // => true * ``` * * @see {@link isNever} @@ -987,10 +996,10 @@ export function isUnknown(_: unknown): _ is unknown { * * **Example** (Checking objects or arrays) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * - * console.log(Predicate.isObjectOrArray([])) + * Predicate.isObjectOrArray([]) // => true * ``` * * @see {@link isObject} @@ -1018,11 +1027,11 @@ export function isObjectOrArray(input: unknown): input is { [x: PropertyKey]: un * * **Example** (Guarding objects) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * - * console.log(Predicate.isObject({ a: 1 })) - * console.log(Predicate.isObject([1, 2])) + * Predicate.isObject({ a: 1 }) // => true + * Predicate.isObject([1, 2]) // => false * ``` * * @see {@link isObjectOrArray} @@ -1051,12 +1060,12 @@ export function isObject(input: unknown): input is { [x: PropertyKey]: unknown } * * **Example** (Checking readonly objects) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = { a: 1 } * - * console.log(Predicate.isReadonlyObject(data)) + * Predicate.isReadonlyObject(data) // => true * ``` * * @see {@link isObject} @@ -1081,11 +1090,11 @@ export function isReadonlyObject(input: unknown): input is { readonly [x: Proper * * **Example** (Checking object keywords) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * - * console.log(Predicate.isObjectKeyword(() => 1)) - * console.log(Predicate.isObjectKeyword(null)) + * Predicate.isObjectKeyword(() => 1) // => true + * Predicate.isObjectKeyword(null) // => false * ``` * * @see {@link isObject} @@ -1112,14 +1121,14 @@ export function isObjectKeyword(input: unknown): input is object { * * **Example** (Guarding object properties) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const hasName = Predicate.hasProperty("name") * const data: unknown = { name: "Ada" } * * if (hasName(data)) { - * console.log(data.name) + * data.name // => "Ada" * } * ``` * @@ -1151,12 +1160,12 @@ export const hasProperty: { * * **Example** (Guarding tagged values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isOk = Predicate.isTagged("Ok") * - * console.log(isOk({ _tag: "Ok", value: 1 })) + * isOk({ _tag: "Ok", value: 1 }) // => true * ``` * * @see {@link hasProperty} @@ -1184,12 +1193,12 @@ export const isTagged: { * * **Example** (Guarding errors) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = new Error("boom") * - * console.log(Predicate.isError(data)) + * Predicate.isError(data) // => true * ``` * * @see {@link isUnknown} @@ -1213,12 +1222,12 @@ export function isError(input: unknown): input is Error { * * **Example** (Guarding Uint8Array values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = new Uint8Array([1, 2]) * - * console.log(Predicate.isUint8Array(data)) + * Predicate.isUint8Array(data) // => true * ``` * * @see {@link isIterable} @@ -1243,12 +1252,12 @@ export function isUint8Array(input: unknown): input is Uint8Array { * * **Example** (Guarding Date values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = new Date() * - * console.log(Predicate.isDate(data)) + * Predicate.isDate(data) // => true * ``` * * @see {@link isRegExp} @@ -1272,12 +1281,12 @@ export function isDate(input: unknown): input is Date { * * **Example** (Guarding iterables) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = [1, 2, 3] * - * console.log(Predicate.isIterable(data)) + * Predicate.isIterable(data) // => true * ``` * * @see {@link isSet} @@ -1302,12 +1311,12 @@ export function isIterable(input: unknown): input is Iterable { * * **Example** (Guarding promises) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = Promise.resolve(1) * - * console.log(Predicate.isPromise(data)) + * Predicate.isPromise(data) // => true * ``` * * @see {@link isPromiseLike} @@ -1332,12 +1341,12 @@ export function isPromise(input: unknown): input is Promise { * * **Example** (Guarding promise-like values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = { then: () => {} } * - * console.log(Predicate.isPromiseLike(data)) + * Predicate.isPromiseLike(data) // => true * ``` * * @see {@link isPromise} @@ -1361,12 +1370,12 @@ export function isPromiseLike(input: unknown): input is PromiseLike { * * **Example** (Guarding RegExp values) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const data: unknown = /abc/ * - * console.log(Predicate.isRegExp(data)) + * Predicate.isRegExp(data) // => true * ``` * * @see {@link isDate} @@ -1392,7 +1401,7 @@ export function isRegExp(input: unknown): input is RegExp { * * **Example** (Composing refinements) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isNumber: Predicate.Refinement = (u): u is number => typeof u === "number" @@ -1400,7 +1409,7 @@ export function isRegExp(input: unknown): input is RegExp { * * const isIntegerNumber = Predicate.compose(isNumber, isInteger) * - * console.log(isIntegerNumber(1)) + * isIntegerNumber(1) // => true * ``` * * @see {@link and} @@ -1434,12 +1443,12 @@ export const compose: { * * **Example** (Checking tuples) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const tupleCheck = Predicate.Tuple([(n: number) => n > 0, Predicate.isString]) * - * console.log(tupleCheck([1, "ok"])) + * tupleCheck([1, "ok"]) // => true * ``` * * @see {@link Struct} @@ -1480,7 +1489,7 @@ export function Tuple>( * * **Example** (Checking structs) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const userCheck = Predicate.Struct({ @@ -1488,7 +1497,7 @@ export function Tuple>( * name: Predicate.isString * }) * - * console.log(userCheck({ id: 1, name: "Ada" })) + * userCheck({ id: 1, name: "Ada" }) // => true * ``` * * @see {@link Tuple} @@ -1528,12 +1537,12 @@ export function Struct>( * * **Example** (Negating a predicate) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isNotString = Predicate.not(Predicate.isString) * - * console.log(isNotString(1)) + * isNotString(1) // => true * ``` * * @see {@link and} @@ -1561,12 +1570,12 @@ export function not(self: Predicate): Predicate { * * **Example** (Checking either condition) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isStringOrNumber = Predicate.or(Predicate.isString, Predicate.isNumber) * - * console.log(isStringOrNumber("a")) + * isStringOrNumber("a") // => true * ``` * * @see {@link and} @@ -1597,7 +1606,7 @@ export const or: { * * **Example** (Checking both conditions) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const hasAAndB = Predicate.and( @@ -1610,6 +1619,8 @@ export const or: { * // input has both properties at this point * const a = input.a * const b = input.b + * + * const values = [a, b] // => [1, "ok"] * } * ``` * @@ -1638,14 +1649,14 @@ export const and: { * * **Example** (Checking exclusive-or conditions) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isEven = (n: number) => n % 2 === 0 * const isPositive = (n: number) => n > 0 * const either = Predicate.xor(isEven, isPositive) * - * console.log(either(-2)) + * either(-2) // => true * ``` * * @see {@link or} @@ -1671,13 +1682,13 @@ export const xor: { * * **Example** (Defining equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isEven = (n: number) => n % 2 === 0 * const same = Predicate.eqv(isEven, isEven) * - * console.log(same(3)) + * same(3) // => true * ``` * * @see {@link xor} @@ -1704,14 +1715,14 @@ export const eqv: { * * **Example** (Checking implication) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const isAdult = (age: number) => age >= 18 * const canVote = (age: number) => age >= 18 * const implies = Predicate.implies(isAdult, canVote) * - * console.log(implies(16)) + * implies(16) // => true * ``` * * @see {@link and} @@ -1740,12 +1751,12 @@ export const implies: { * * **Example** (Checking NOR conditions) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const neither = Predicate.nor(Predicate.isString, Predicate.isNumber) * - * console.log(neither(true)) + * neither(true) // => true * ``` * * @see {@link or} @@ -1774,12 +1785,12 @@ export const nor: { * * **Example** (Checking NAND conditions) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const notBoth = Predicate.nand(Predicate.isString, Predicate.isNumber) * - * console.log(notBoth("a")) + * notBoth("a") // => true * ``` * * @see {@link and} @@ -1809,17 +1820,17 @@ export const nand: { * * **Example** (Checking all predicates) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const allChecks = Predicate.every([Predicate.isNumber, (n: number) => n > 0]) * - * console.log(allChecks(2)) + * allChecks(2) // => true * ``` * * @see {@link some} * @see {@link and} - * @category elements + * @category combining * @since 2.0.0 */ export function every(collection: Iterable>): Predicate { @@ -1847,17 +1858,17 @@ export function every(collection: Iterable>): Predicate { * * **Example** (Checking any predicate) * - * ```ts + * ```ts import.meta.vitest * import { Predicate } from "effect" * * const anyCheck = Predicate.some([Predicate.isString, Predicate.isNumber]) * - * console.log(anyCheck("ok")) + * anyCheck("ok") // => true * ``` * * @see {@link every} * @see {@link or} - * @category elements + * @category combining * @since 2.0.0 */ export function some(collection: Iterable>): Predicate { diff --git a/packages/effect/src/PrimaryKey.ts b/packages/effect/src/PrimaryKey.ts index 8353ad9df..f7ddfa656 100644 --- a/packages/effect/src/PrimaryKey.ts +++ b/packages/effect/src/PrimaryKey.ts @@ -24,7 +24,7 @@ import { hasProperty } from "./Predicate.ts" * @category symbols * @since 2.0.0 */ -export const symbol = "~effect/interfaces/PrimaryKey" +export const symbol = "~effect/PrimaryKey" /** * An interface for objects that can provide a string-based primary key. @@ -41,7 +41,7 @@ export const symbol = "~effect/interfaces/PrimaryKey" * * **Example** (Implementing a primary key) * - * ```ts + * ```ts import.meta.vitest * import { PrimaryKey } from "effect" * * class ProductId implements PrimaryKey.PrimaryKey { @@ -53,7 +53,7 @@ export const symbol = "~effect/interfaces/PrimaryKey" * } * * const productId = new ProductId("electronics", 42) - * console.log(PrimaryKey.value(productId)) // "electronics-42" + * PrimaryKey.value(productId) // => "electronics-42" * ``` * * @category models @@ -81,7 +81,7 @@ export interface PrimaryKey { * @see {@link PrimaryKey} for the protocol being checked * @see {@link value} for extracting the string value after narrowing * - * @category models + * @category guards * @since 4.0.0 */ export const isPrimaryKey = (u: unknown): u is PrimaryKey => hasProperty(u, symbol) @@ -96,7 +96,7 @@ export const isPrimaryKey = (u: unknown): u is PrimaryKey => hasProperty(u, symb * * **Example** (Reading primary key values) * - * ```ts + * ```ts import.meta.vitest * import { PrimaryKey } from "effect" * * class OrderId implements PrimaryKey.PrimaryKey { @@ -108,16 +108,16 @@ export const isPrimaryKey = (u: unknown): u is PrimaryKey => hasProperty(u, symb * } * * const orderId = new OrderId(1640995200000, 1) - * console.log(PrimaryKey.value(orderId)) // "order_1640995200000_1" + * PrimaryKey.value(orderId) // => "order_1640995200000_1" * * // Can also be used with simple string-based implementations * const simpleKey = { * [PrimaryKey.symbol]: () => "simple-key-123" * } - * console.log(PrimaryKey.value(simpleKey)) // "simple-key-123" + * PrimaryKey.value(simpleKey) // => "simple-key-123" * ``` * - * @category accessors + * @category getters * @since 2.0.0 */ export const value = (self: PrimaryKey): string => self[symbol]() diff --git a/packages/effect/src/PubSub.ts b/packages/effect/src/PubSub.ts index dcbfbf154..6861798fd 100644 --- a/packages/effect/src/PubSub.ts +++ b/packages/effect/src/PubSub.ts @@ -17,6 +17,7 @@ import * as Effect from "./Effect.ts" import * as Exit from "./Exit.ts" import type { LazyArg } from "./Function.ts" import { dual, identity } from "./Function.ts" +import * as Count from "./internal/count.ts" import * as Latch from "./Latch.ts" import * as MutableList from "./MutableList.ts" import * as MutableRef from "./MutableRef.ts" @@ -35,26 +36,27 @@ const TypeId = "~effect/PubSub" * * **Example** (Publishing and subscribing to messages) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * // Create a bounded PubSub with capacity 10 * const pubsub = yield* PubSub.bounded(10) * * // Subscribe and consume messages - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish messages - * yield* PubSub.publish(pubsub, "Hello") - * yield* PubSub.publish(pubsub, "World") + * // Publish messages + * yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.publish(pubsub, "World") * - * const message1 = yield* PubSub.take(subscription) - * const message2 = yield* PubSub.take(subscription) - * console.log(message1, message2) // "Hello", "World" - * })) - * }) + * const message1 = yield* PubSub.take(subscription) + * const message2 = yield* PubSub.take(subscription) + * return [message1, message2] + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["Hello", "World"] * ``` * * @category models @@ -139,6 +141,7 @@ export declare namespace PubSub { take(): A | undefined takeN(n: number): Array takeAll(): Array + close(): void readonly remaining: number } @@ -204,31 +207,28 @@ const SubscriptionTypeId = "~effect/PubSub/Subscription" * * **Example** (Taking messages from a subscription) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * * // Subscribe within a scope for automatic cleanup - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription: PubSub.Subscription = yield* PubSub.subscribe( - * pubsub - * ) - * - * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) - * - * // Take individual messages - * const message = yield* PubSub.take(subscription) - * console.log(message) // "msg1" - * - * // Take multiple messages - * const messages = yield* PubSub.takeUpTo(subscription, 1) - * console.log(messages) // ["msg2"] - * const allMessages = yield* PubSub.takeAll(subscription) - * console.log(allMessages) // ["msg3"] - * })) - * }) + * const subscription: PubSub.Subscription = yield* PubSub.subscribe(pubsub) + * + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) + * + * // Take individual messages + * const message = yield* PubSub.take(subscription) + * + * // Take multiple messages + * const messages = yield* PubSub.takeUpTo(subscription, 1) + * const allMessages = yield* PubSub.takeAll(subscription) + * return { message, messages, allMessages } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { message: "msg1", messages: ["msg2"], allMessages: ["msg3"] } * ``` * * @category models @@ -253,7 +253,7 @@ export interface Subscription extends Pipeable { * * **Example** (Creating a PubSub with a custom strategy) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * * const program = Effect.gen(function*() { @@ -264,8 +264,13 @@ export interface Subscription extends Pipeable { * }) * * // Use the created PubSub - * yield* PubSub.publish(pubsub, "Hello") + * const published = yield* PubSub.publish(pubsub, "Hello") + * yield* PubSub.shutdown(pubsub) + * return published * }) + * + * const actual = await Effect.runPromise(program) + * actual // => true * ``` * * @category constructors @@ -301,7 +306,7 @@ export const make = ( * * **Example** (Creating a bounded PubSub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * * const program = Effect.gen(function*() { @@ -313,7 +318,15 @@ export const make = ( * capacity: 100, * replay: 10 // Last 10 messages replayed to new subscribers * }) + * + * const capacities = [PubSub.capacity(pubsub), PubSub.capacity(pubsubWithReplay)] + * yield* PubSub.shutdown(pubsub) + * yield* PubSub.shutdown(pubsubWithReplay) + * return capacities * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [100, 100] * ``` * * @category constructors @@ -340,33 +353,27 @@ export const bounded = ( * * **Example** (Dropping messages when full) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * // Create dropping PubSub that drops new messages when full * const pubsub = yield* PubSub.dropping(3) * - * // With replay buffer for late subscribers - * const pubsubWithReplay = yield* PubSub.dropping({ - * capacity: 3, - * replay: 5 - * }) + * const subscription = yield* PubSub.subscribe(pubsub) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * // Fill the PubSub and see dropping behavior + * yield* PubSub.publish(pubsub, "msg1") // succeeds + * yield* PubSub.publish(pubsub, "msg2") // succeeds + * yield* PubSub.publish(pubsub, "msg3") // succeeds + * const dropped = yield* PubSub.publish(pubsub, "msg4") // returns false (dropped) * - * // Fill the PubSub and see dropping behavior - * yield* PubSub.publish(pubsub, "msg1") // succeeds - * yield* PubSub.publish(pubsub, "msg2") // succeeds - * yield* PubSub.publish(pubsub, "msg3") // succeeds - * const dropped = yield* PubSub.publish(pubsub, "msg4") // returns false (dropped) - * console.log("Message dropped:", !dropped) // true + * const messages = yield* PubSub.takeAll(subscription) + * return { dropped: !dropped, messages } + * })) * - * const messages = yield* PubSub.takeAll(subscription) - * console.log(messages) // ["msg1", "msg2", "msg3"] - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => { dropped: true, messages: ["msg1", "msg2", "msg3"] } * ``` * * @category constructors @@ -393,32 +400,26 @@ export const dropping = ( * * **Example** (Sliding old messages when full) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * // Create sliding PubSub that evicts old messages when full * const pubsub = yield* PubSub.sliding(3) * - * // With replay buffer - * const pubsubWithReplay = yield* PubSub.sliding({ - * capacity: 3, - * replay: 2 - * }) + * const subscription = yield* PubSub.subscribe(pubsub) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * // Fill and overflow the PubSub + * yield* PubSub.publish(pubsub, "msg1") + * yield* PubSub.publish(pubsub, "msg2") + * yield* PubSub.publish(pubsub, "msg3") + * yield* PubSub.publish(pubsub, "msg4") // "msg1" is evicted * - * // Fill and overflow the PubSub - * yield* PubSub.publish(pubsub, "msg1") - * yield* PubSub.publish(pubsub, "msg2") - * yield* PubSub.publish(pubsub, "msg3") - * yield* PubSub.publish(pubsub, "msg4") // "msg1" is evicted + * return yield* PubSub.takeAll(subscription) + * })) * - * const messages = yield* PubSub.takeAll(subscription) - * console.log(messages) // ["msg2", "msg3", "msg4"] - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => ["msg2", "msg3", "msg4"] * ``` * * @category constructors @@ -440,30 +441,25 @@ export const sliding = ( * * **Example** (Creating an unbounded PubSub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * // Create unbounded PubSub * const pubsub = yield* PubSub.unbounded() * - * // With replay buffer for late subscribers - * const pubsubWithReplay = yield* PubSub.unbounded({ - * replay: 10 - * }) + * const subscription = yield* PubSub.subscribe(pubsub) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * // Can publish unlimited messages + * for (let i = 0; i < 3; i++) { + * yield* PubSub.publish(pubsub, `message-${i}`) + * } * - * // Can publish unlimited messages - * for (let i = 0; i < 3; i++) { - * yield* PubSub.publish(pubsub, `message-${i}`) - * } + * return yield* PubSub.takeAll(subscription) + * })) * - * const message = yield* PubSub.take(subscription) - * console.log("First message:", message) // "message-0" - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => ["message-0", "message-1", "message-2"] * ``` * * @category constructors @@ -545,25 +541,29 @@ export const makeAtomicBounded = ( */ export const makeAtomicUnbounded = (options?: { readonly replay?: number | undefined -}): PubSub.Atomic => new UnboundedPubSub(options?.replay ? new ReplayBuffer(options.replay) : undefined) +}): PubSub.Atomic => { + const replay = options?.replay + return new UnboundedPubSub( + replay && replay > 0 ? new ReplayBuffer(Math.ceil(replay)) : undefined + ) +} /** * Returns the number of elements the queue can hold. * * **Example** (Getting PubSub capacity) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * * const program = Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(100) - * const cap = PubSub.capacity(pubsub) - * console.log("PubSub capacity:", cap) // 100 - * * const unboundedPubsub = yield* PubSub.unbounded() - * const unboundedCap = PubSub.capacity(unboundedPubsub) - * console.log("Unbounded capacity:", unboundedCap) // Number.MAX_SAFE_INTEGER + * return [PubSub.capacity(pubsub), PubSub.capacity(unboundedPubsub)] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [100, Number.MAX_SAFE_INTEGER] * ``` * * @category getters @@ -582,29 +582,28 @@ export const capacity = (self: PubSub): number => self.pubsub.capacity * * **Example** (Getting PubSub size) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * * // Initially empty * const initialSize = yield* PubSub.size(pubsub) - * console.log("Initial size:", initialSize) // 0 * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish some messages for the active subscription - * yield* PubSub.publish(pubsub, "msg1") - * yield* PubSub.publish(pubsub, "msg2") + * // Publish some messages for the active subscription + * yield* PubSub.publish(pubsub, "msg1") + * yield* PubSub.publish(pubsub, "msg2") * - * const afterPublish = yield* PubSub.size(pubsub) - * console.log("After publishing:", afterPublish) // 2 + * const afterPublish = yield* PubSub.size(pubsub) + * const messages = yield* PubSub.takeAll(subscription) + * return { initialSize, afterPublish, messages } + * })) * - * yield* PubSub.takeAll(subscription) - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => { initialSize: 0, afterPublish: 2, messages: ["msg1", "msg2"] } * ``` * * @category getters @@ -627,14 +626,16 @@ export const size = (self: PubSub): Effect.Effect => Effect.sync(( * * **Example** (Reading size synchronously) * - * ```ts - * import { PubSub } from "effect" + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" * - * // Unsafe synchronous size check - * declare const pubsub: PubSub.PubSub + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * return PubSub.sizeUnsafe(pubsub) + * }) * - * const size = PubSub.sizeUnsafe(pubsub) - * console.log("Current size:", size) + * const actual = await Effect.runPromise(program) + * actual // => 0 * ``` * * @category getters @@ -656,29 +657,28 @@ export const sizeUnsafe = (self: PubSub): number => { * * **Example** (Checking whether a PubSub is full) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(2) * * // Initially not full * const initiallyFull = yield* PubSub.isFull(pubsub) - * console.log("Initially full:", initiallyFull) // false * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Fill the PubSub for the active subscription - * yield* PubSub.publish(pubsub, "msg1") - * yield* PubSub.publish(pubsub, "msg2") + * // Fill the PubSub for the active subscription + * yield* PubSub.publish(pubsub, "msg1") + * yield* PubSub.publish(pubsub, "msg2") * - * const nowFull = yield* PubSub.isFull(pubsub) - * console.log("Now full:", nowFull) // true + * const nowFull = yield* PubSub.isFull(pubsub) + * const messages = yield* PubSub.takeAll(subscription) + * return { initiallyFull, nowFull, messages } + * })) * - * yield* PubSub.takeAll(subscription) - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => { initiallyFull: false, nowFull: true, messages: ["msg1", "msg2"] } * ``` * * @category predicates @@ -692,28 +692,27 @@ export const isFull = (self: PubSub): Effect.Effect => * * **Example** (Checking whether a PubSub is empty) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * * // Initially empty * const initiallyEmpty = yield* PubSub.isEmpty(pubsub) - * console.log("Initially empty:", initiallyEmpty) // true * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish a message for the active subscription - * yield* PubSub.publish(pubsub, "Hello") + * // Publish a message for the active subscription + * yield* PubSub.publish(pubsub, "Hello") * - * const nowEmpty = yield* PubSub.isEmpty(pubsub) - * console.log("Now empty:", nowEmpty) // false + * const nowEmpty = yield* PubSub.isEmpty(pubsub) + * const message = yield* PubSub.take(subscription) + * return { initiallyEmpty, nowEmpty, message } + * })) * - * yield* PubSub.take(subscription) - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => { initiallyEmpty: true, nowEmpty: false, message: "Hello" } * ``` * * @category predicates @@ -733,7 +732,7 @@ export const isEmpty = (self: PubSub): Effect.Effect => Effect.ma * * **Example** (Shutting down a PubSub) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * * const program = Effect.gen(function*() { @@ -743,12 +742,14 @@ export const isEmpty = (self: PubSub): Effect.Effect => Effect.ma * yield* PubSub.shutdown(pubsub) * * const isShutdown = yield* PubSub.isShutdown(pubsub) - * console.log("Is shutdown:", isShutdown) // true * * // Publishing after shutdown returns false * const published = yield* PubSub.publish(pubsub, "msg1") - * console.log("Published after shutdown:", published) // false + * return { isShutdown, published } * }) + * + * const actual = await Effect.runPromise(program) + * actual // => { isShutdown: true, published: false } * ``` * * @category lifecycle @@ -770,7 +771,7 @@ export const shutdown = (self: PubSub): Effect.Effect => * * **Example** (Checking whether a PubSub is shut down) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * * const program = Effect.gen(function*() { @@ -778,14 +779,16 @@ export const shutdown = (self: PubSub): Effect.Effect => * * // Initially not shutdown * const initiallyShutdown = yield* PubSub.isShutdown(pubsub) - * console.log("Initially shutdown:", initiallyShutdown) // false * * // Shutdown the PubSub * yield* PubSub.shutdown(pubsub) * * const nowShutdown = yield* PubSub.isShutdown(pubsub) - * console.log("Now shutdown:", nowShutdown) // true + * return [initiallyShutdown, nowShutdown] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [false, true] * ``` * * @category predicates @@ -804,18 +807,18 @@ export const isShutdown = (self: PubSub): Effect.Effect => Effect * * **Example** (Checking shutdown synchronously) * - * ```ts - * import { PubSub } from "effect" + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" * - * declare const pubsub: PubSub.PubSub + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * const initiallyShutdown = PubSub.isShutdownUnsafe(pubsub) + * yield* PubSub.shutdown(pubsub) + * return [initiallyShutdown, PubSub.isShutdownUnsafe(pubsub)] + * }) * - * // Unsafe synchronous shutdown check - * const isDown = PubSub.isShutdownUnsafe(pubsub) - * if (isDown) { - * console.log("PubSub is shutdown, cannot publish") - * } else { - * console.log("PubSub is active") - * } + * const actual = await Effect.runPromise(program) + * actual // => [false, true] * ``` * * @category predicates @@ -830,7 +833,7 @@ export const isShutdownUnsafe = (self: PubSub): boolean => self.shutdownFl * * **Example** (Waiting for shutdown) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, PubSub } from "effect" * * const program = Effect.gen(function*() { @@ -840,19 +843,19 @@ export const isShutdownUnsafe = (self: PubSub): boolean => self.shutdownFl * const waiterFiber = yield* Effect.forkChild( * Effect.gen(function*() { * yield* PubSub.awaitShutdown(pubsub) - * console.log("PubSub has been shutdown!") + * return "PubSub has been shutdown!" * }) * ) * - * // Do some work... - * yield* Effect.sleep("100 millis") - * * // Shutdown the PubSub * yield* PubSub.shutdown(pubsub) * * // The waiter will now complete - * yield* Fiber.join(waiterFiber) + * return yield* Fiber.join(waiterFiber) * }) + * + * const actual = await Effect.runPromise(program) + * actual // => "PubSub has been shutdown!" * ``` * * @category lifecycle @@ -877,24 +880,24 @@ export const awaitShutdown = (self: PubSub): Effect.Effect => self.s * * **Example** (Publishing a message) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * * // Publish a message * const published = yield* PubSub.publish(pubsub, "Hello World") - * console.log("Message published:", published) // true * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * yield* PubSub.publish(pubsub, "Hello") - * const message = yield* PubSub.take(subscription) - * console.log("Received:", message) // "Hello" - * })) - * }) + * yield* PubSub.publish(pubsub, "Hello") + * const message = yield* PubSub.take(subscription) + * return { published, message } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { published: true, message: "Hello" } * ``` * * @see {@link publishUnsafe} for a synchronous non-blocking attempt that does not run effectful surplus handling @@ -941,24 +944,16 @@ export const publish: { * * **Example** (Publishing without suspending) * - * ```ts - * import { PubSub } from "effect" - * - * declare const pubsub: PubSub.PubSub + * ```ts import.meta.vitest + * import { Effect, PubSub } from "effect" * - * // Unsafe synchronous publish (non-blocking) - * const published = PubSub.publishUnsafe(pubsub, "Hello") - * if (published) { - * console.log("Message published successfully") - * } else { - * console.log("Message dropped (PubSub full or shutdown)") - * } + * const program = Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * return PubSub.publishUnsafe(pubsub, "Hello") + * }) * - * // Useful for scenarios where you don't want to suspend - * const messages = ["msg1", "msg2", "msg3"] - * const publishedCount = - * messages.filter((msg) => PubSub.publishUnsafe(pubsub, msg)).length - * console.log(`Published ${publishedCount} out of ${messages.length} messages`) + * const actual = await Effect.runPromise(program) + * actual // => true * ``` * * @see {@link publish} for effectful publishing that honors the configured surplus strategy @@ -984,37 +979,30 @@ export const publishUnsafe: { * * **Example** (Publishing multiple messages) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * * // Publish multiple messages at once - * const messages = ["Hello", "World", "from", "Effect"] - * const allPublished = yield* PubSub.publishAll(pubsub, messages) - * console.log("All messages published:", allPublished) // true + * const allPublished = yield* PubSub.publishAll(pubsub, ["Hello", "World", "from", "Effect"]) * * // With a smaller capacity and an active subscription * const smallPubsub = yield* PubSub.bounded(2) - * const manyMessages = ["msg1", "msg2", "msg3", "msg4"] - * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(smallPubsub) + * const subscription = yield* PubSub.subscribe(smallPubsub) * - * // Will suspend until space becomes available for all messages - * const fiber = yield* Effect.forkChild(PubSub.publishAll(smallPubsub, manyMessages)) + * // Will suspend until space becomes available for all messages + * const fiber = yield* Effect.forkChild(PubSub.publishAll(smallPubsub, ["msg1", "msg2", "msg3", "msg4"])) * - * const firstBatch = yield* PubSub.takeBetween(subscription, 2, 2) - * console.log("First batch:", firstBatch) // ["msg1", "msg2"] + * const firstBatch = yield* PubSub.takeBetween(subscription, 2, 2) + * const result = yield* Fiber.join(fiber) + * const secondBatch = yield* PubSub.takeAll(subscription) + * return { allPublished, firstBatch, result, secondBatch } + * })) * - * const result = yield* Fiber.join(fiber) - * console.log("All messages eventually published:", result) // true - * - * const secondBatch = yield* PubSub.takeAll(subscription) - * console.log("Second batch:", secondBatch) // ["msg3", "msg4"] - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => { allPublished: true, firstBatch: ["msg1", "msg2"], result: true, secondBatch: ["msg3", "msg4"] } * ``` * * @category publishing @@ -1048,14 +1036,14 @@ export const publishAll: { * * **Example** (Subscribing to messages) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * * const program = Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * * // Subscribe within a scope for automatic cleanup - * yield* Effect.scoped(Effect.gen(function*() { + * const first = yield* Effect.scoped(Effect.gen(function*() { * const subscription = yield* PubSub.subscribe(pubsub) * * // Publish some messages @@ -1065,25 +1053,28 @@ export const publishAll: { * // Take messages one by one * const msg1 = yield* PubSub.take(subscription) * const msg2 = yield* PubSub.take(subscription) - * console.log(msg1, msg2) // "Hello", "World" * * // Subscription is automatically cleaned up when scope exits + * return [msg1, msg2] * })) * - * yield* Effect.scoped(Effect.gen(function*() { + * const second = yield* Effect.scoped(Effect.gen(function*() { * const sub1 = yield* PubSub.subscribe(pubsub) * const sub2 = yield* PubSub.subscribe(pubsub) * * // Multiple subscribers can receive the same messages * yield* PubSub.publish(pubsub, "Broadcast") * - * const [msg1, msg2] = yield* Effect.all([ + * return yield* Effect.all([ * PubSub.take(sub1), * PubSub.take(sub2) * ]) - * console.log("Both received:", msg1, msg2) // "Broadcast", "Broadcast" * })) + * return [first, second] * }) + * + * const actual = await Effect.runPromise(program) + * actual // => [["Hello", "World"], ["Broadcast", "Broadcast"]] * ``` * * @category subscriptions @@ -1115,6 +1106,7 @@ const unsubscribe = (self: Subscription): Effect.Effect => Effect.sync(() => { self.subscribers.delete(self.subscription) self.subscription.unsubscribe() + self.replayWindow.close() self.strategy.onPubSubEmptySpaceUnsafe(self.pubsub, self.subscribers) }) ), @@ -1130,28 +1122,26 @@ const unsubscribe = (self: Subscription): Effect.Effect => * * **Example** (Taking a message) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Start a fiber to take a message (will suspend) - * const takeFiber = yield* Effect.forkChild( - * PubSub.take(subscription) - * ) + * // Start a fiber to take a message (will suspend) + * const takeFiber = yield* Effect.forkChild(PubSub.take(subscription)) * - * // Publish a message - * yield* PubSub.publish(pubsub, "Hello") + * // Publish a message + * yield* PubSub.publish(pubsub, "Hello") * - * // The take will now complete - * const message = yield* Fiber.join(takeFiber) - * console.log("Received:", message) // "Hello" - * })) - * }) + * // The take will now complete + * return yield* Fiber.join(takeFiber) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => "Hello" * ``` * * @category subscriptions @@ -1183,23 +1173,23 @@ export const take = (self: Subscription): Effect.Effect => * * **Example** (Taking all available messages) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish multiple messages - * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) + * // Publish multiple messages + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) * - * // Take all available messages at once - * const allMessages = yield* PubSub.takeAll(subscription) - * console.log("All messages:", allMessages) // ["msg1", "msg2", "msg3"] - * })) - * }) + * // Take all available messages at once + * return yield* PubSub.takeAll(subscription) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["msg1", "msg2", "msg3"] * ``` * * @category subscriptions @@ -1250,35 +1240,36 @@ const pollForItem = (self: Subscription) => { } /** - * Takes up to the specified number of messages from the subscription without suspending. + * Takes up to the specified number of messages from the subscription without + * suspending. Finite fractional values are rounded down, while `NaN` and + * non-positive values are treated as `0`. * * **Example** (Taking up to a maximum number of messages) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish multiple messages - * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3", "msg4", "msg5"]) + * // Publish multiple messages + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3", "msg4", "msg5"]) * - * // Take up to 3 messages - * const upTo3 = yield* PubSub.takeUpTo(subscription, 3) - * console.log("Up to 3:", upTo3) // ["msg1", "msg2", "msg3"] + * // Take up to 3 messages + * const upTo3 = yield* PubSub.takeUpTo(subscription, 3) * - * // Take up to 5 more (only 2 remaining) - * const upTo5 = yield* PubSub.takeUpTo(subscription, 5) - * console.log("Up to 5:", upTo5) // ["msg4", "msg5"] + * // Take up to 5 more (only 2 remaining) + * const upTo5 = yield* PubSub.takeUpTo(subscription, 5) * - * // No more messages available - * const noMore = yield* PubSub.takeUpTo(subscription, 10) - * console.log("No more:", noMore) // [] - * })) - * }) + * // No more messages available + * const noMore = yield* PubSub.takeUpTo(subscription, 10) + * return [upTo3, upTo5, noMore] + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => [["msg1", "msg2", "msg3"], ["msg4", "msg5"], []] * ``` * * @category subscriptions @@ -1290,6 +1281,7 @@ export const takeUpTo: { } = dual(2, (self: Subscription, max: number): Effect.Effect> => Effect.suspend(() => { if (self.shutdownFlag.current) return Effect.interrupt + max = Count.normalize(max) let replay: Array | undefined = undefined if (self.replayWindow.remaining >= max) { return Effect.succeed(self.replayWindow.takeN(max)) @@ -1305,33 +1297,33 @@ export const takeUpTo: { })) /** - * Takes between the specified minimum and maximum number of messages from the subscription. - * Will suspend if the minimum number is not immediately available. + * Takes between the specified minimum and maximum number of messages from the + * subscription. Finite fractional bounds are rounded down, while `NaN` and + * non-positive bounds are treated as `0`. Will suspend if the normalized + * minimum number is not immediately available. * * **Example** (Taking between a minimum and maximum) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Fiber, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Start taking between 2 and 5 messages (will suspend) - * const takeFiber = yield* Effect.forkChild( - * PubSub.takeBetween(subscription, 2, 5) - * ) + * // Start taking between 2 and 5 messages (will suspend) + * const takeFiber = yield* Effect.forkChild(PubSub.takeBetween(subscription, 2, 5)) * - * // Publish 3 messages - * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) + * // Publish 3 messages + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) * - * // Now the take will complete with 3 messages - * const messages = yield* Fiber.join(takeFiber) - * console.log("Between 2-5:", messages) // ["msg1", "msg2", "msg3"] - * })) - * }) + * // Now the take will complete with 3 messages + * return yield* Fiber.join(takeFiber) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["msg1", "msg2", "msg3"] * ``` * * @category subscriptions @@ -1343,7 +1335,7 @@ export const takeBetween: { } = dual( 3, (self: Subscription, min: number, max: number): Effect.Effect> => - Effect.suspend(() => takeRemainderLoop(self, min, max, [])) + Effect.suspend(() => takeRemainderLoop(self, Count.normalize(min), Count.normalize(max), [])) ) const takeRemainderLoop = ( @@ -1395,29 +1387,29 @@ const takeRemainderLoop = ( * * **Example** (Checking remaining messages) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { + * const program = Effect.scoped(Effect.gen(function*() { * const pubsub = yield* PubSub.bounded(10) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish some messages - * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) + * // Publish some messages + * yield* PubSub.publishAll(pubsub, ["msg1", "msg2", "msg3"]) * - * // Check how many messages are available - * const count = yield* PubSub.remaining(subscription) - * console.log("Messages available:", count) // 3 + * // Check how many messages are available + * const count = yield* PubSub.remaining(subscription) * - * // Take one message - * yield* PubSub.take(subscription) + * // Take one message + * const message = yield* PubSub.take(subscription) * - * const remaining = yield* PubSub.remaining(subscription) - * console.log("Messages remaining:", remaining) // 2 - * })) - * }) + * const remaining = yield* PubSub.remaining(subscription) + * return { count, message, remaining } + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => { count: 3, message: "msg1", remaining: 2 } * ``` * * @see {@link remainingUnsafe} for a synchronous check that reports shutdown as `Option.none()` @@ -1443,23 +1435,17 @@ export const remaining = (self: Subscription): Effect.Effect => * * **Example** (Checking remaining messages synchronously) * - * ```ts - * import { PubSub } from "effect" - * - * declare const subscription: PubSub.Subscription + * ```ts import.meta.vitest + * import { Effect, Option, PubSub } from "effect" * - * // Unsafe synchronous check for remaining messages - * const remainingOption = PubSub.remainingUnsafe(subscription) - * if (remainingOption._tag === "Some") { - * console.log("Messages available:", remainingOption.value) - * } else { - * console.log("Subscription is shutdown") - * } + * const program = Effect.scoped(Effect.gen(function*() { + * const pubsub = yield* PubSub.bounded(2) + * const subscription = yield* PubSub.subscribe(pubsub) + * return PubSub.remainingUnsafe(subscription) + * })) * - * // Useful for polling or batching scenarios - * if (remainingOption._tag === "Some" && remainingOption.value > 10) { - * // Process messages in batch - * } + * const actual = await Effect.runPromise(program) + * actual // => Option.some(0) * ``` * * @see {@link remaining} for the effectful variant that interrupts on shutdown @@ -1526,6 +1512,7 @@ const makeSubscriptionUnsafe = ( class BoundedPubSubArb implements PubSub.Atomic { array: Array + replayIndices: Array publisherIndex = 0 subscribers: Array subscriberCount = 0 @@ -1538,6 +1525,7 @@ class BoundedPubSubArb implements PubSub.Atomic { this.capacity = capacity this.replayBuffer = replayBuffer this.array = Array.from({ length: capacity }) + this.replayIndices = replayBuffer ? Array.from({ length: capacity }) : [] this.subscribers = Array.from({ length: capacity }) } @@ -1561,15 +1549,16 @@ class BoundedPubSubArb implements PubSub.Atomic { if (this.isFull()) { return false } + const replayIndex = this.replayBuffer?.offer(value) if (this.subscriberCount !== 0) { const index = this.publisherIndex % this.capacity this.array[index] = value + if (replayIndex !== undefined) { + this.replayIndices[index] = replayIndex + } this.subscribers[index] = this.subscriberCount this.publisherIndex += 1 } - if (this.replayBuffer) { - this.replayBuffer.offer(value) - } return true } @@ -1594,11 +1583,12 @@ class BoundedPubSubArb implements PubSub.Atomic { const a = chunk[iteratorIndex++] const index = this.publisherIndex % this.capacity this.array[index] = a + const replayIndex = this.replayBuffer?.offer(a) + if (replayIndex !== undefined) { + this.replayIndices[index] = replayIndex + } this.subscribers[index] = this.subscriberCount this.publisherIndex += 1 - if (this.replayBuffer) { - this.replayBuffer.offer(a) - } } return chunk.slice(iteratorIndex) } @@ -1606,12 +1596,11 @@ class BoundedPubSubArb implements PubSub.Atomic { slide(): void { if (this.subscribersIndex !== this.publisherIndex) { const index = this.subscribersIndex % this.capacity + const value = this.array[index] this.array[index] = AbsentValue as unknown as A this.subscribers[index] = 0 this.subscribersIndex += 1 - } - if (this.replayBuffer) { - this.replayBuffer.slide() + this.replayBuffer?.slide(value, this.replayIndices[index]) } } @@ -1671,6 +1660,7 @@ class BoundedPubSubArbSubscription implements PubSub.BackingSubscripti } pollUpTo(n: number): Array { + n = Count.normalize(n) if (this.unsubscribed) { return [] } @@ -1717,6 +1707,7 @@ class BoundedPubSubArbSubscription implements PubSub.BackingSubscripti class BoundedPubSubPow2 implements PubSub.Atomic { array: Array + replayIndices: Array mask: number publisherIndex = 0 subscribers: Array @@ -1730,6 +1721,7 @@ class BoundedPubSubPow2 implements PubSub.Atomic { this.capacity = capacity this.replayBuffer = replayBuffer this.array = Array.from({ length: capacity }) + this.replayIndices = replayBuffer ? Array.from({ length: capacity }) : [] this.mask = capacity - 1 this.subscribers = Array.from({ length: capacity }) } @@ -1754,15 +1746,16 @@ class BoundedPubSubPow2 implements PubSub.Atomic { if (this.isFull()) { return false } + const replayIndex = this.replayBuffer?.offer(value) if (this.subscriberCount !== 0) { const index = this.publisherIndex & this.mask this.array[index] = value + if (replayIndex !== undefined) { + this.replayIndices[index] = replayIndex + } this.subscribers[index] = this.subscriberCount this.publisherIndex += 1 } - if (this.replayBuffer) { - this.replayBuffer.offer(value) - } return true } @@ -1787,11 +1780,12 @@ class BoundedPubSubPow2 implements PubSub.Atomic { const elem = chunk[iteratorIndex++] const index = this.publisherIndex & this.mask this.array[index] = elem + const replayIndex = this.replayBuffer?.offer(elem) + if (replayIndex !== undefined) { + this.replayIndices[index] = replayIndex + } this.subscribers[index] = this.subscriberCount this.publisherIndex += 1 - if (this.replayBuffer) { - this.replayBuffer.offer(elem) - } } return chunk.slice(iteratorIndex) } @@ -1799,12 +1793,11 @@ class BoundedPubSubPow2 implements PubSub.Atomic { slide(): void { if (this.subscribersIndex !== this.publisherIndex) { const index = this.subscribersIndex & this.mask + const value = this.array[index] this.array[index] = AbsentValue as unknown as A this.subscribers[index] = 0 this.subscribersIndex += 1 - } - if (this.replayBuffer) { - this.replayBuffer.slide() + this.replayBuffer?.slide(value, this.replayIndices[index]) } } @@ -1864,6 +1857,7 @@ class BoundedPubSubPow2Subscription implements PubSub.BackingSubscript } pollUpTo(n: number): Array { + n = Count.normalize(n) if (this.unsubscribed) { return [] } @@ -1912,6 +1906,7 @@ class BoundedPubSubSingle implements PubSub.Atomic { subscriberCount = 0 subscribers = 0 value: A = AbsentValue as unknown as A + replayIndex = 0 readonly capacity = 1 readonly replayBuffer: ReplayBuffer | undefined @@ -1944,14 +1939,15 @@ class BoundedPubSubSingle implements PubSub.Atomic { if (this.isFull()) { return false } + const replayIndex = this.replayBuffer?.offer(value) if (this.subscriberCount !== 0) { this.value = value + if (replayIndex !== undefined) { + this.replayIndex = replayIndex + } this.subscribers = this.subscriberCount this.publisherIndex += 1 } - if (this.replayBuffer) { - this.replayBuffer.offer(value) - } return true } @@ -1975,11 +1971,10 @@ class BoundedPubSubSingle implements PubSub.Atomic { slide(): void { if (this.isFull()) { + const value = this.value this.subscribers = 0 this.value = AbsentValue as unknown as A - } - if (this.replayBuffer) { - this.replayBuffer.slide() + this.replayBuffer?.slide(value, this.replayIndex) } } @@ -2025,28 +2020,22 @@ class BoundedPubSubSingleSubscription implements PubSub.BackingSubscri if (this.self.subscribers === 0) { this.self.value = AbsentValue as unknown as A } - this.subscriberIndex += 1 + this.subscriberIndex = this.self.publisherIndex return elem } pollUpTo(n: number): Array { - if (this.isEmpty() || n < 1) { + if (Count.normalize(n) < 1 || this.isEmpty()) { return [] } - const a = this.self.value - this.self.subscribers -= 1 - if (this.self.subscribers === 0) { - this.self.value = AbsentValue as unknown as A - } - this.subscriberIndex += 1 - return [a] + return [this.poll() as A] } unsubscribe(): void { if (!this.unsubscribed) { this.unsubscribed = true this.self.subscriberCount -= 1 - if (this.subscriberIndex !== this.self.publisherIndex) { + if (this.self.subscribers !== 0 && this.subscriberIndex !== this.self.publisherIndex) { this.self.subscribers -= 1 if (this.self.subscribers === 0) { this.self.value = AbsentValue as unknown as A @@ -2058,6 +2047,7 @@ class BoundedPubSubSingleSubscription implements PubSub.BackingSubscri interface Node { value: A | AbsentValue + replayIndex: number | undefined subscribers: number next: Node | null } @@ -2065,6 +2055,7 @@ interface Node { class UnboundedPubSub implements PubSub.Atomic { publisherHead: Node = { value: AbsentValue, + replayIndex: undefined, subscribers: 0, next: null } @@ -2096,19 +2087,19 @@ class UnboundedPubSub implements PubSub.Atomic { } publish(value: A): boolean { + const replayIndex = this.replayBuffer?.offer(value) const subscribers = this.publisherTail.subscribers if (subscribers !== 0) { - this.publisherTail.next = { + const node: Node = { value, + replayIndex, subscribers, next: null } + this.publisherTail.next = node this.publisherTail = this.publisherTail.next this.publisherIndex += 1 } - if (this.replayBuffer) { - this.replayBuffer.offer(value) - } return true } @@ -2125,12 +2116,12 @@ class UnboundedPubSub implements PubSub.Atomic { slide(): void { if (this.publisherHead !== this.publisherTail) { + const node = this.publisherHead.next! + const value = node.value as A this.publisherHead = this.publisherHead.next! this.publisherHead.value = AbsentValue this.subscribersIndex += 1 - } - if (this.replayBuffer) { - this.replayBuffer.slide() + this.replayBuffer?.slide(value, node.replayIndex!) } } @@ -2221,6 +2212,7 @@ class UnboundedPubSubSubscription implements PubSub.BackingSubscriptio } pollUpTo(n: number): Array { + n = Count.normalize(n) const builder: Array = [] let i = 0 while (i !== n) { @@ -2481,34 +2473,30 @@ export class BackPressureStrategy implements PubSub.Strategy { * * **Example** (Applying a dropping strategy) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { - * // Create PubSub with dropping strategy - * const pubsub = yield* PubSub.dropping(2) - * - * // Or explicitly create with dropping strategy - * const customPubsub = yield* PubSub.make({ + * const program = Effect.scoped(Effect.gen(function*() { + * // Explicitly create a PubSub with a dropping strategy + * const pubsub = yield* PubSub.make({ * atomicPubSub: () => PubSub.makeAtomicBounded(2), * strategy: () => new PubSub.DroppingStrategy() * }) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Fill the PubSub - * const pub1 = yield* PubSub.publish(pubsub, "msg1") // true - * const pub2 = yield* PubSub.publish(pubsub, "msg2") // true - * const pub3 = yield* PubSub.publish(pubsub, "msg3") // false (dropped) + * // Fill the PubSub + * const pub1 = yield* PubSub.publish(pubsub, "msg1") // true + * const pub2 = yield* PubSub.publish(pubsub, "msg2") // true + * const pub3 = yield* PubSub.publish(pubsub, "msg3") // false (dropped) * - * console.log("Publication results:", [pub1, pub2, pub3]) // [true, true, false] + * // Subscribers will only see the first two messages + * const messages = yield* PubSub.takeAll(subscription) + * return { published: [pub1, pub2, pub3], messages } + * })) * - * // Subscribers will only see the first two messages - * const messages = yield* PubSub.takeAll(subscription) - * console.log("Received messages:", messages) // ["msg1", "msg2"] - * })) - * }) + * const actual = await Effect.runPromise(program) + * actual // => { published: [true, true, false], messages: ["msg1", "msg2"] } * ``` * * @category models @@ -2568,33 +2556,30 @@ export class DroppingStrategy implements PubSub.Strategy { * * **Example** (Applying a sliding strategy) * - * ```ts + * ```ts import.meta.vitest * import { Effect, PubSub } from "effect" * - * const program = Effect.gen(function*() { - * // Create PubSub with sliding strategy - * const pubsub = yield* PubSub.sliding(2) - * - * // Or explicitly create with sliding strategy - * const customPubsub = yield* PubSub.make({ + * const program = Effect.scoped(Effect.gen(function*() { + * // Explicitly create a PubSub with a sliding strategy + * const pubsub = yield* PubSub.make({ * atomicPubSub: () => PubSub.makeAtomicBounded(2), * strategy: () => new PubSub.SlidingStrategy() * }) * - * yield* Effect.scoped(Effect.gen(function*() { - * const subscription = yield* PubSub.subscribe(pubsub) + * const subscription = yield* PubSub.subscribe(pubsub) * - * // Publish messages that exceed capacity - * yield* PubSub.publish(pubsub, "msg1") // stored - * yield* PubSub.publish(pubsub, "msg2") // stored - * yield* PubSub.publish(pubsub, "msg3") // "msg1" evicted, "msg3" stored - * yield* PubSub.publish(pubsub, "msg4") // "msg2" evicted, "msg4" stored + * // Publish messages that exceed capacity + * yield* PubSub.publish(pubsub, "msg1") // stored + * yield* PubSub.publish(pubsub, "msg2") // stored + * yield* PubSub.publish(pubsub, "msg3") // "msg1" evicted, "msg3" stored + * yield* PubSub.publish(pubsub, "msg4") // "msg2" evicted, "msg4" stored * - * // Subscribers will see the most recent messages - * const messages = yield* PubSub.takeAll(subscription) - * console.log("Recent messages:", messages) // ["msg3", "msg4"] - * })) - * }) + * // Subscribers will see the most recent messages + * return yield* PubSub.takeAll(subscription) + * })) + * + * const actual = await Effect.runPromise(program) + * actual // => ["msg3", "msg4"] * ``` * * @category models @@ -2702,27 +2687,40 @@ const strategyCompleteSubscribersUnsafe = ( interface ReplayNode { value: A | AbsentValue + index: number next: ReplayNode | null } class ReplayBuffer { readonly capacity: number - head: ReplayNode = { value: AbsentValue, next: null } + head: ReplayNode = { value: AbsentValue, index: 0, next: null } tail: ReplayNode = this.head + readonly slideValues: Array<{ + readonly value: A + readonly index: number + }> = [] size = 0 index = 0 + publisherIndex = 0 constructor(capacity: number) { this.capacity = capacity } - slide() { + slide(value: A, publisherIndex: number): void { + this.slideValues[this.index % this.capacity] = { + value, + index: publisherIndex + } this.index++ } - offer(a: A): void { + offer(a: A): number { + const index = this.publisherIndex++ this.tail.value = a + this.tail.index = index this.tail.next = { value: AbsentValue, + index: 0, next: null } this.tail = this.tail.next @@ -2731,6 +2729,7 @@ class ReplayBuffer { } else { this.size += 1 } + return index } offerAll(as: Iterable): void { for (const a of as) { @@ -2740,48 +2739,67 @@ class ReplayBuffer { } class ReplayWindowImpl implements PubSub.ReplayWindow { - head: ReplayNode - index: number - remaining: number readonly buffer: ReplayBuffer + readonly values: Array + index = 0 + remaining: number + slideIndex: number + newestIndex = -1 constructor(buffer: ReplayBuffer) { this.buffer = buffer - this.index = buffer.index this.remaining = buffer.size - this.head = buffer.head - } - fastForward() { - while (this.index < this.buffer.index) { - this.head = this.head.next! - this.index++ + this.slideIndex = buffer.index + this.values = new Array(this.remaining) + let node = buffer.head + for (let i = 0; i < this.remaining; i++) { + this.values[i] = node.value as A + this.newestIndex = node.index + node = node.next! + } + } + close(): void { + this.values.length = 0 + this.remaining = 0 + } + sync(): void { + const slides = this.buffer.index - this.slideIndex + if (slides === 0 || this.remaining === 0) { + return + } + const count = Math.min(slides, this.buffer.capacity) + const start = this.buffer.index - count + for (let i = 0; i < count; i++) { + const entry = this.buffer.slideValues[(start + i) % this.buffer.capacity] + if (entry.index > this.newestIndex) { + this.index = (this.index + 1) % this.values.length + this.values[(this.index + this.remaining - 1) % this.values.length] = entry.value + this.newestIndex = entry.index + } } + this.slideIndex = this.buffer.index } take(): A | undefined { if (this.remaining === 0) { return undefined - } else if (this.index < this.buffer.index) { - this.fastForward() } + this.sync() + const value = this.values[this.index] + this.values[this.index] = AbsentValue as unknown as A + this.index = (this.index + 1) % this.values.length this.remaining-- - const value = this.head.value - this.head = this.head.next! + if (this.remaining === 0) { + this.close() + } return value as A } takeN(n: number): Array { - if (this.remaining === 0) { - return [] - } else if (this.index < this.buffer.index) { - this.fastForward() - } + n = Count.normalize(n) const len = Math.min(n, this.remaining) const items = new Array(len) for (let i = 0; i < len; i++) { - const value = this.head.value as A - this.head = this.head.next! - items[i] = value + items[i] = this.take()! } - this.remaining -= len return items } takeAll(): Array { @@ -2793,5 +2811,6 @@ const emptyReplayWindow: PubSub.ReplayWindow = { remaining: 0, take: () => undefined, takeN: () => [], - takeAll: () => [] + takeAll: () => [], + close: () => void 0 } diff --git a/packages/effect/src/Pull.ts b/packages/effect/src/Pull.ts index 54092190a..514b66e02 100644 --- a/packages/effect/src/Pull.ts +++ b/packages/effect/src/Pull.ts @@ -53,7 +53,7 @@ export interface Pull * @see {@link Leftover} for extracting the completion leftover type * @see {@link Services} for extracting the required services type instead * - * @category type extractors + * @category utility types * @since 4.0.0 */ export type Success

= P extends Effect ? _A : never @@ -71,7 +71,7 @@ export type Success

= P extends Effect ? _A : n * @see {@link Services} for extracting the required services type instead * @see {@link ExcludeDone} for excluding `Cause.Done` from an error union * - * @category type extractors + * @category utility types * @since 4.0.0 */ export type Error

= P extends Effect ? _E extends Cause.Done ? never : _E @@ -90,7 +90,7 @@ export type Error

= P extends Effect ? _E exten * @see {@link Error} for extracting the ordinary failure type, excluding `Cause.Done` * @see {@link Services} for extracting the required services type instead * - * @category type extractors + * @category utility types * @since 4.0.0 */ export type Leftover

= P extends Effect ? _E extends Cause.Done ? _L : never @@ -108,7 +108,7 @@ export type Leftover

= P extends Effect ? _E ex * @see {@link Error} for extracting the ordinary failure type * @see {@link Leftover} for extracting the completion leftover type * - * @category type extractors + * @category utility types * @since 4.0.0 */ export type Services

= P extends Effect ? _R : never @@ -124,7 +124,7 @@ export type Services

= P extends Effect ? _R : * @see {@link Error} for extracting ordinary failures from a `Pull` * @see {@link Leftover} for extracting the completion leftover type * - * @category type extractors + * @category utility types * @since 4.0.0 */ export type ExcludeDone = Exclude> @@ -151,7 +151,7 @@ export type ExcludeDone = Exclude> * @see {@link matchEffect} for handling success, ordinary failure, and done outcomes explicitly * @see {@link filterDoneLeftover} for extracting a done leftover from an existing `Cause` * - * @category Done + * @category error handling * @since 4.0.0 */ export const catchDone: { @@ -180,7 +180,7 @@ export const catchDone: { * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause` * @see {@link filterNoDone} for selecting causes with no done failures * - * @category Done + * @category predicates * @since 4.0.0 */ export const isDoneCause = (cause: Cause.Cause): boolean => cause.reasons.some(isDoneFailure) @@ -197,7 +197,7 @@ export const isDoneCause = (cause: Cause.Cause): boolean => cause.reasons. * @see {@link isDoneCause} for checking an entire `Cause` for any done reason * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause` * - * @category Done + * @category guards * @since 4.0.0 */ export const isDoneFailure = ( @@ -214,19 +214,35 @@ export const isDoneFailure = ( * * **Details** * - * Returns a successful `Result` with the `Cause.Done` value when one is - * present, otherwise returns a failed `Result` containing the non-done cause. + * Returns a successful `Result` with the `Cause.Done` value when the cause + * contains a done signal and no other failures besides interruptions. When the + * done signal was merged with a real failure (for example a failing + * finalizer), the `Result` fails with the remaining cause, stripped of the + * done signal. Without a done signal the `Result` fails with the original + * cause. * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterDone: ( input: Cause.Cause -) => Result.Result, Cause.Cause>> = Filter - .composePassthrough( - Cause.findError, - (e) => Cause.isDone(e) ? Result.succeed(e) : Result.fail(e) - ) as any +) => Result.Result, Cause.Cause>> = ( + cause: Cause.Cause +): Result.Result => { + let done: Cause.Done | undefined + let hasFailure = false + for (const reason of cause.reasons) { + if (isDoneFailure(reason)) { + done ??= reason.error + } else if (reason._tag !== "Interrupt") { + hasFailure = true + } + } + if (done === undefined) return Result.fail(cause) + return hasFailure + ? Result.fail(Cause.fromReasons(cause.reasons.filter((reason) => !isDoneFailure(reason)))) + : Result.succeed(done) +} /** * Finds a `Cause.Done` failure in a cause whose done value is not used. @@ -238,22 +254,19 @@ export const filterDone: ( * * **Details** * - * Returns a successful `Result` with the done marker when present, otherwise - * returns a failed `Result` with the non-done cause. + * Returns a successful `Result` with the done marker when it is the only + * failure, otherwise returns a failed `Result` with the non-done cause. * * @see {@link filterDone} for preserving the typed `Cause.Done` value when the done payload matters * @see {@link filterDoneLeftover} for extracting only the done leftover value * @see {@link filterNoDone} for the inverse filter that succeeds only when no done failure is present * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterDoneVoid: ( input: Cause.Cause -) => Result.Result>> = Filter.composePassthrough( - Cause.findError, - (e) => Cause.isDone(e) ? Result.succeed(e) : Result.fail(e) -) as any +) => Result.Result>> = filterDone as any /** * Keeps a `Cause` only when it contains no `Cause.Done` failures. @@ -271,7 +284,7 @@ export const filterDoneVoid: ( * @see {@link filterDone} for the inverse typed done filter * @see {@link filterDoneVoid} for done detection when the payload is not needed * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterNoDone: ( @@ -291,15 +304,15 @@ export const filterNoDone: ( * Use to extract only the leftover value carried by a `Cause.Done` completion * signal. * - * @category Done + * @category filtering * @since 4.0.0 */ export const filterDoneLeftover: ( cause: Cause.Cause -) => Result.Result, Cause.Cause>> = Filter.composePassthrough( - Cause.findError, - (e) => Cause.isDone(e) ? Result.succeed(e.value) : Result.fail(e) -) as any +) => Result.Result, Cause.Cause>> = ((cause: Cause.Cause) => { + const done = filterDone(cause) + return Result.isFailure(done) ? done : Result.succeed(done.success.value) +}) as any /** * Converts a `Cause` into an `Exit`, treating `Cause.Done` as successful @@ -313,13 +326,14 @@ export const filterDoneLeftover: ( * * **Details** * - * If the cause contains a done value, that leftover becomes the successful - * value. Otherwise the non-done cause becomes the failure cause. + * If the done signal is the only failure in the cause, its leftover becomes + * the successful value. Otherwise the non-done cause becomes the failure + * cause. * * @see {@link filterDone} for extracting the done signal without converting the cause to an `Exit` * @see {@link matchEffect} for handling `Pull` success, failure, and done outcomes directly * - * @category Done + * @category converting * @since 4.0.0 */ export const doneExitFromCause = (cause: Cause.Cause): Exit.Exit, ExcludeDone> => { @@ -336,7 +350,7 @@ export const doneExitFromCause = (cause: Cause.Cause): Exit.Exit(cause: Cause.Cause): Exit.Exit Effect.succeed(`Got error: ${cause}`), * onDone: (leftover) => Effect.succeed(`Stream halted with: ${leftover}`) * }) + * + * await Effect.runPromise(result) // => "Stream halted with: stream ended" * ``` * * @category pattern matching diff --git a/packages/effect/src/Queue.ts b/packages/effect/src/Queue.ts index bd0b4a2e9..91a8004ee 100644 --- a/packages/effect/src/Queue.ts +++ b/packages/effect/src/Queue.ts @@ -16,6 +16,7 @@ import { constant, constTrue, dual, identity } from "./Function.ts" import type { Inspectable } from "./Inspectable.ts" import * as core from "./internal/core.ts" import { PipeInspectableProto } from "./internal/core.ts" +import * as Count from "./internal/count.ts" import * as internalEffect from "./internal/effect.ts" import * as MutableList from "./MutableList.ts" import * as Option from "./Option.ts" @@ -142,7 +143,7 @@ export const asDequeue: (self: Queue) => Dequeue = identity * * **Example** (Offering through enqueue handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * // Function that only needs write access to a queue @@ -155,7 +156,10 @@ export const asDequeue: (self: Queue) => Dequeue = identity * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * yield* producer(queue) + * return yield* Queue.takeAll(queue) * }) + * + * await Effect.runPromise(program) // => ["hello", "world", "!"] * ``` * * @category models @@ -206,7 +210,7 @@ export declare namespace Enqueue { * * **Example** (Taking through dequeue handles) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -220,8 +224,10 @@ export declare namespace Enqueue { * * // Take elements using dequeue interface * const item = yield* Queue.take(dequeue) - * console.log(item) // "a" + * return item * }) + * + * await Effect.runPromise(program) // => "a" * ``` * * @category models @@ -270,7 +276,7 @@ export declare namespace Dequeue { * * **Example** (Offering and taking queue values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -286,8 +292,10 @@ export declare namespace Dequeue { * const item2 = yield* Queue.take(queue) * const item3 = yield* Queue.take(queue) * - * console.log([item1, item2, item3]) // ["hello", "world", "!"] + * return [item1, item2, item3] * }) + * + * await Effect.runPromise(program) // => ["hello", "world", "!"] * ``` * * @category models @@ -408,10 +416,10 @@ const QueueProto = { * * **Example** (Creating queues) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const queue = yield* Queue.make() * * // add messages to the queue @@ -421,18 +429,18 @@ const QueueProto = { * * // take messages from the queue * const messages = yield* Queue.takeAll(queue) - * console.log(messages) // [1, 2, 3, 4, 5] * * // signal that the queue is done * yield* Queue.end(queue) * const done = yield* Effect.flip(Queue.take(queue)) - * console.log(Cause.isDone(done)) // true * * // signal that another queue has failed * const failedQueue = yield* Queue.make() * const failed = yield* Queue.fail(failedQueue, "boom") - * console.log(failed) // true + * return { messages, done, failed } * }) + * + * await Effect.runPromise(program) // => { messages: [1, 2, 3, 4, 5], done: Cause.Done(), failed: true } * ``` * * @category constructors @@ -470,7 +478,7 @@ export const make = ( * * **Example** (Creating bounded queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -481,8 +489,10 @@ export const make = ( * yield* Queue.offer(queue, "second") * * const size = yield* Queue.size(queue) - * console.log(size) // 2 + * return size * }) + * + * await Effect.runPromise(program) // => 2 * ``` * * @category constructors @@ -501,7 +511,7 @@ export const bounded = (capacity: number): Effect> => * * **Example** (Creating sliding queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -516,8 +526,10 @@ export const bounded = (capacity: number): Effect> => * yield* Queue.offer(queue, 4) * * const all = yield* Queue.takeAll(queue) - * console.log(all) // [2, 3, 4] - oldest element (1) was dropped + * return all * }) + * + * await Effect.runPromise(program) // => [2, 3, 4] * ``` * * @category constructors @@ -536,7 +548,7 @@ export const sliding = (capacity: number): Effect> => * * **Example** (Creating dropping queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -545,15 +557,15 @@ export const sliding = (capacity: number): Effect> => * // Fill the queue to capacity * const success1 = yield* Queue.offer(queue, 1) * const success2 = yield* Queue.offer(queue, 2) - * console.log(success1, success2) // true, true * * // This will be dropped * const success3 = yield* Queue.offer(queue, 3) - * console.log(success3) // false * * const all = yield* Queue.takeAll(queue) - * console.log(all) // [1, 2] - element 3 was dropped + * return [success1, success2, success3, all] * }) + * + * await Effect.runPromise(program) // => [true, true, false, [1, 2]] * ``` * * @category constructors @@ -572,7 +584,7 @@ export const dropping = (capacity: number): Effect> => * * **Example** (Creating unbounded queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -585,12 +597,13 @@ export const dropping = (capacity: number): Effect> => * * // Check current size * const size = yield* Queue.size(queue) - * console.log(size) // 5 * * // Take all messages * const messages = yield* Queue.takeAll(queue) - * console.log(messages) // ["message1", "message2", "message3", "message4", "message5"] + * return { size, messages } * }) + * + * await Effect.runPromise(program) // => { size: 5, messages: ["message1", "message2", "message3", "message4", "message5"] } * ``` * * @category constructors @@ -609,7 +622,7 @@ export const unbounded = (): Effect> => make() * * **Example** (Offering a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -618,15 +631,16 @@ export const unbounded = (): Effect> => make() * // Successfully add messages to queue * const success1 = yield* Queue.offer(queue, 1) * const success2 = yield* Queue.offer(queue, 2) - * console.log(success1, success2) // true, true * * // Queue state * const size = yield* Queue.size(queue) - * console.log(size) // 2 + * return { offered: [success1, success2], size } * }) + * + * await Effect.runPromise(program) // => { offered: [true, true], size: 2 } * ``` * - * @category Offering + * @category offering * @since 2.0.0 */ export const offer = (self: Enqueue, message: Types.NoInfer): Effect => @@ -670,8 +684,8 @@ export const offer = (self: Enqueue, message: Types.NoInfer): Eff * * **Example** (Offering a value synchronously) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" * * // Create a queue effect and extract the queue for unsafe operations * const program = Effect.gen(function*() { @@ -680,15 +694,16 @@ export const offer = (self: Enqueue, message: Types.NoInfer): Eff * // Add messages synchronously using unsafe API * const success1 = Queue.offerUnsafe(queue, 1) * const success2 = Queue.offerUnsafe(queue, 2) - * console.log(success1, success2) // true, true * * // Check current size * const size = Queue.sizeUnsafe(queue) - * console.log(size) // 2 + * return { offered: [success1, success2], size } * }) + * + * await Effect.runPromise(program) // => { offered: [true, true], size: 2 } * ``` * - * @category Offering + * @category offering * @since 4.0.0 */ export const offerUnsafe = (self: Enqueue, message: Types.NoInfer): boolean => { @@ -728,7 +743,7 @@ export const offerUnsafe = (self: Enqueue, message: Types.NoInfer * * **Example** (Offering multiple values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -736,11 +751,13 @@ export const offerUnsafe = (self: Enqueue, message: Types.NoInfer * * // Try to add more messages than capacity without suspending * const remaining1 = yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) - * console.log(remaining1) // [4, 5] - couldn't fit the last 2 + * return remaining1 * }) + * + * await Effect.runPromise(program) // => [4, 5] * ``` * - * @category Offering + * @category offering * @since 2.0.0 */ export const offerAll = (self: Enqueue, messages: Iterable): Effect> => @@ -772,8 +789,8 @@ export const offerAll = (self: Enqueue, messages: Iterable): Effe * * **Example** (Offering multiple values synchronously) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" * * // Create a bounded queue and use unsafe API * const program = Effect.gen(function*() { @@ -781,15 +798,16 @@ export const offerAll = (self: Enqueue, messages: Iterable): Effe * * // Try to add 5 messages to capacity-3 queue using unsafe API * const remaining = Queue.offerAllUnsafe(queue, [1, 2, 3, 4, 5]) - * console.log(remaining) // [4, 5] - couldn't fit the last 2 * * // Check what's in the queue * const size = Queue.sizeUnsafe(queue) - * console.log(size) // 3 + * return { remaining, size } * }) + * + * await Effect.runPromise(program) // => { remaining: [4, 5], size: 3 } * ``` * - * @category Offering + * @category offering * @since 4.0.0 */ export const offerAllUnsafe = (self: Enqueue, messages: Iterable): Array => { @@ -832,20 +850,21 @@ export const offerAllUnsafe = (self: Enqueue, messages: Iterable) * * **Example** (Failing queues with an error) * - * ```ts - * import { Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Fail the queue with an error * const failed = yield* Queue.fail(queue, "Something went wrong") - * console.log(failed) // true * * // Taking from the failed queue fails with the error - * const error = yield* Effect.flip(Queue.take(queue)) - * console.log(error) // "Something went wrong" + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [failed, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail("Something went wrong")] * ``` * * @category completion @@ -859,8 +878,8 @@ export const fail = (self: Enqueue, error: E) => failCause(self, cor * * **Example** (Failing queues with a cause) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) @@ -868,11 +887,13 @@ export const fail = (self: Enqueue, error: E) => failCause(self, cor * // Create a cause and fail the queue * const cause = Cause.fail("Queue processing failed") * const failed = yield* Queue.failCause(queue, cause) - * console.log(failed) // true * * // The queue is now done with the specified failure cause - * console.log(queue.state._tag) // "Done" + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [failed, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Queue processing failed"))] * ``` * * @category completion @@ -902,8 +923,8 @@ export const failCause: { * * **Example** (Failing queues with a cause synchronously) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) @@ -911,11 +932,13 @@ export const failCause: { * // Create a cause and fail the queue synchronously * const cause = Cause.fail("Processing error") * const failed = Queue.failCauseUnsafe(queue, cause) - * console.log(failed) // true * * // The queue is now done with the specified failure cause - * console.log(queue.state._tag) // "Done" + * const exit = Queue.takeUnsafe(queue) + * return [failed, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.failCause(Cause.fail("Processing error"))] * ``` * * @category completion @@ -952,7 +975,7 @@ export const failCauseUnsafe = (self: Enqueue, cause: Cause): boo * * **Example** (Ending queues) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -964,16 +987,16 @@ export const failCauseUnsafe = (self: Enqueue, cause: Cause): boo * * // Signal completion - no more messages will be accepted * const ended = yield* Queue.end(queue) - * console.log(ended) // true * * // Trying to offer more messages will return false * const offerResult = yield* Queue.offer(queue, 3) - * console.log(offerResult) // false * * // But we can still take existing messages * const message = yield* Queue.take(queue) - * console.log(message) // 1 + * return [ended, offerResult, message] * }) + * + * await Effect.runPromise(program) // => [true, false, 1] * ``` * * @category completion @@ -999,7 +1022,7 @@ export const end = (self: Enqueue): Effect => failCa * * **Example** (Ending queues synchronously) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * // Create a queue and use unsafe operations @@ -1012,17 +1035,19 @@ export const end = (self: Enqueue): Effect => failCa * * // End the queue synchronously * const ended = Queue.endUnsafe(queue) - * console.log(ended) // true * * // Existing messages can still be consumed while the queue is closing - * console.log(queue.state._tag) // "Closing" + * const states = [queue.state._tag] * * Queue.takeUnsafe(queue) * Queue.takeUnsafe(queue) * * // After buffered messages are consumed, the queue is done - * console.log(queue.state._tag) // "Done" + * states.push(queue.state._tag) + * return { ended, states } * }) + * + * await Effect.runPromise(program) // => { ended: true, states: ["Closing", "Done"] } * ``` * * @category completion @@ -1040,7 +1065,7 @@ export const endUnsafe = (self: Enqueue) => failCauseUnsafe(s * * **Example** (Interrupting queues gracefully) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1052,23 +1077,21 @@ export const endUnsafe = (self: Enqueue) => failCauseUnsafe(s * * // Interrupt gracefully - no more offers accepted, but messages can be consumed * const interrupted = yield* Queue.interrupt(queue) - * console.log(interrupted) // true * * // Trying to offer more messages will return false * const offerResult = yield* Queue.offer(queue, 3) - * console.log(offerResult) // false * * // But we can still take existing messages * const message1 = yield* Queue.take(queue) - * console.log(message1) // 1 * * const message2 = yield* Queue.take(queue) - * console.log(message2) // 2 * * // After all messages are consumed, queue is done * const isDone = queue.state._tag === "Done" - * console.log(isDone) // true + * return { interrupted, offerResult, messages: [message1, message2], isDone } * }) + * + * await Effect.runPromise(program) // => { interrupted: true, offerResult: false, messages: [1, 2], isDone: true } * ``` * * @category completion @@ -1088,7 +1111,7 @@ export const interrupt = (self: Enqueue): Effect => * * **Example** (Shutting down queues) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1100,12 +1123,13 @@ export const interrupt = (self: Enqueue): Effect => * * // Shutdown clears buffered messages and prevents further offers * const wasShutdown = yield* Queue.shutdown(queue) - * console.log(wasShutdown) // true * * // Queue is now done and cleared * const size = yield* Queue.size(queue) - * console.log(size) // 0 + * return { wasShutdown, size } * }) + * + * await Effect.runPromise(program) // => { wasShutdown: true, size: 0 } * ``` * * @category completion @@ -1142,8 +1166,8 @@ export const shutdown = (self: Enqueue): Effect => * * **Example** (Clearing queued values) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) @@ -1153,16 +1177,16 @@ export const shutdown = (self: Enqueue): Effect => * * // Clear all messages from the queue * const messages = yield* Queue.clear(queue) - * console.log(messages) // [1, 2, 3, 4, 5] * * // Queue is now empty * const size = yield* Queue.size(queue) - * console.log(size) // 0 * * // Clearing empty queue returns empty array * const empty = yield* Queue.clear(queue) - * console.log(empty) // [] + * return { messages, size, empty } * }) + * + * await Effect.runPromise(program) // => { messages: [1, 2, 3, 4, 5], size: 0, empty: [] } * ``` * * @category taking @@ -1197,7 +1221,7 @@ export const clear = (self: Dequeue): Effect, Pull.ExcludeD * * **Example** (Taking all available values) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1208,8 +1232,10 @@ export const clear = (self: Dequeue): Effect, Pull.ExcludeD * * // Take all available messages * const messages1 = yield* Queue.takeAll(queue) - * console.log(messages1) // [1, 2, 3, 4, 5] + * return messages1 * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category taking @@ -1223,7 +1249,7 @@ export const takeAll = (self: Dequeue): Effect, * * **Example** (Collecting values until completion) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1231,13 +1257,13 @@ export const takeAll = (self: Dequeue): Effect, * * // Add several messages * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) - * // Some time later, end the queue - * yield* Effect.forkChild(Queue.end(queue)) + * yield* Queue.end(queue) * * // Collect all available messages - * const messages = yield* Queue.collect(queue) - * console.log(messages) // [1, 2, 3, 4, 5] + * return yield* Queue.collect(queue) * }) + * + * await Effect.runPromise(program) // => [1, 2, 3, 4, 5] * ``` * * @category taking @@ -1269,13 +1295,14 @@ export const collect = (self: Dequeue): Effect, Pull * **Details** * * The operation may wait until enough messages are available to satisfy the - * queue's batching rules. If `n` is less than or equal to zero, it succeeds - * with an empty array. If the queue completes or fails before messages can be - * taken, the effect fails with the queue's terminal error. + * queue's batching rules. Finite fractional values of `n` are rounded down. + * If `n` is `NaN` or non-positive, it succeeds with an empty array. If the + * queue completes or fails before messages can be taken, the effect fails with + * the queue's terminal error. * * **Example** (Taking a fixed number of values) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1286,16 +1313,16 @@ export const collect = (self: Dequeue): Effect, Pull * * // Take exactly 3 messages * const first3 = yield* Queue.takeN(queue, 3) - * console.log(first3) // [1, 2, 3] * * // Take exactly 2 more messages * const next2 = yield* Queue.takeN(queue, 2) - * console.log(next2) // [4, 5] * * // Take remaining messages * const remaining = yield* Queue.takeN(queue, 2) - * console.log(remaining) // [6, 7] + * return [first3, next2, remaining] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3], [4, 5], [6, 7]] * ``` * * @category taking @@ -1312,13 +1339,14 @@ export const takeN = ( * **Details** * * The operation waits when fewer than the required minimum messages are - * available. It returns at most `max` messages. If the queue completes or fails - * before the minimum can be satisfied, the effect fails with the queue's - * terminal error. + * available. It returns at most `max` messages. Finite fractional bounds are + * rounded down, while `NaN` and non-positive bounds are treated as `0`. If the + * queue completes or fails before the minimum can be satisfied, the effect + * fails with the queue's terminal error. * * **Example** (Taking a bounded batch of values) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1329,15 +1357,16 @@ export const takeN = ( * * // Take between 2 and 5 messages * const batch1 = yield* Queue.takeBetween(queue, 2, 5) - * console.log(batch1) // [1, 2, 3, 4, 5] - took 5 (up to max) * * // Take between 1 and 10 messages (but only 3 remain) * const batch2 = yield* Queue.takeBetween(queue, 1, 10) - * console.log(batch2) // [6, 7, 8] - took 3 (all remaining) * * // No more messages available, will wait or return done * // const batch3 = yield* Queue.takeBetween(queue, 1, 3) + * return [batch1, batch2] * }) + * + * await Effect.runPromise(program) // => [[1, 2, 3, 4, 5], [6, 7, 8]] * ``` * * @category taking @@ -1347,10 +1376,13 @@ export const takeBetween = ( self: Dequeue, min: number, max: number -): Effect, E> => - internalEffect.suspend(() => +): Effect, E> => { + min = Count.normalize(min) + max = Count.normalize(max) + return internalEffect.suspend(() => takeBetweenUnsafe(self, min, max) ?? internalEffect.andThen(awaitTake(self), takeBetween(self, 1, max)) ) +} /** * Takes a single message from the queue, or wait for a message to be @@ -1363,8 +1395,8 @@ export const takeBetween = ( * * **Example** (Taking one value) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(3) @@ -1376,18 +1408,16 @@ export const takeBetween = ( * // Take messages one by one * const msg1 = yield* Queue.take(queue) * const msg2 = yield* Queue.take(queue) - * console.log(msg1, msg2) // "first", "second" * * // End the queue * yield* Queue.end(queue) * * // Taking from an ended queue fails with Done - * const result = yield* Effect.match(Queue.take(queue), { - * onFailure: (error: Cause.Done) => true, - * onSuccess: (value: string) => false - * }) - * console.log("Queue ended:", result) // true + * const result = yield* Effect.exit(Queue.take(queue)) + * return [[msg1, msg2], result] * }) + * + * await Effect.runPromise(program) // => [["first", "second"], Exit.fail(Cause.Done())] * ``` * * @category taking @@ -1409,7 +1439,7 @@ export const take = (self: Dequeue): Effect => * * **Example** (Polling without blocking) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1417,15 +1447,16 @@ export const take = (self: Dequeue): Effect => * * // Poll returns Option.none if empty * const maybe1 = yield* Queue.poll(queue) - * console.log(Option.isNone(maybe1)) // true * * // Add an item * yield* Queue.offer(queue, 42) * * // Poll returns Option.some with the item * const maybe2 = yield* Queue.poll(queue) - * console.log(Option.getOrNull(maybe2)) // 42 + * return [maybe1, maybe2] * }) + * + * await Effect.runPromise(program) // => [Option.none(), Option.some(42)] * ``` * * @category taking @@ -1452,7 +1483,7 @@ export const poll = (self: Dequeue): Effect> => * * **Example** (Peeking at the next value) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { @@ -1461,8 +1492,10 @@ export const poll = (self: Dequeue): Effect> => * * // Peek at the next item without removing it * const item = yield* Queue.peek(queue) - * console.log(item) // 42 + * return item * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @category taking @@ -1495,8 +1528,8 @@ export const peek = (self: Dequeue): Effect => * * **Example** (Taking one value synchronously) * - * ```ts - * import { Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Queue } from "effect" * * // Create a queue and use unsafe operations * const program = Effect.gen(function*() { @@ -1508,15 +1541,15 @@ export const peek = (self: Dequeue): Effect => * * // Take a message synchronously * const result1 = Queue.takeUnsafe(queue) - * console.log(result1) // Success(1) or Exit containing value 1 * * const result2 = Queue.takeUnsafe(queue) - * console.log(result2) // Success(2) * * // No more messages - returns undefined * const result3 = Queue.takeUnsafe(queue) - * console.log(result3) // undefined + * return [result1, result2, result3] * }) + * + * await Effect.runPromise(program) // => [Exit.succeed(1), Exit.succeed(2), undefined] * ``` * * @category taking @@ -1531,28 +1564,99 @@ export const takeUnsafe = (self: Dequeue): Exit | undefined => releaseCapacity(self) return core.exitSucceed(message) } else if (self.capacity <= 0 && self.state.offers.size > 0) { - self.capacity = 1 - releaseCapacity(self) - self.capacity = 0 - const message = MutableList.take(self.messages)! + const message = takeOfferUnsafe(self.state.offers) releaseCapacity(self) return core.exitSucceed(message) } return undefined } +/** + * Manually releases current queue takers synchronously. + * + * **When to use** + * + * Use when synchronous offers should release waiting consumers immediately + * instead of waiting for the scheduled release task. + * + * **Details** + * + * This immediately runs the queue's taker-release pass instead of waiting for + * its scheduled task. It does not complete the queue or resume fibers waiting + * on `Queue.await`. + * + * **Example** (Releasing a waiting taker synchronously) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.unbounded() + * const taker = yield* Queue.take(queue).pipe(Effect.forkChild) + * yield* Effect.yieldNow + * + * Queue.offerUnsafe(queue, 1) + * Queue.flushUnsafe(queue) + * + * return yield* Fiber.join(taker) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category offering + * @since 4.0.0 + */ +export const flushUnsafe = (self: Enqueue): void => releaseTakers(self) + +/** + * Manually releases current queue takers. + * + * **When to use** + * + * Use when synchronous offers should release waiting consumers through an + * `Effect` instead of waiting for the scheduled release task. + * + * **Details** + * + * This immediately runs the queue's taker-release pass instead of waiting for + * its scheduled task. It does not complete the queue or resume fibers waiting + * on `Queue.await`. + * + * **Example** (Releasing a waiting taker) + * + * ```ts import.meta.vitest + * import { Effect, Fiber, Queue } from "effect" + * + * const program = Effect.gen(function*() { + * const queue = yield* Queue.unbounded() + * const taker = yield* Queue.take(queue).pipe(Effect.forkChild) + * yield* Effect.yieldNow + * + * Queue.offerUnsafe(queue, 1) + * yield* Queue.flush(queue) + * + * return yield* Fiber.join(taker) + * }) + * + * await Effect.runPromise(program) // => 1 + * ``` + * + * @category offering + * @since 4.0.0 + */ +export const flush = (self: Enqueue): Effect => internalEffect.sync(() => flushUnsafe(self)) + const await_ = (self: Dequeue): Effect> => internalEffect.callback>((resume) => { + const awaiter = (effect: Effect) => resume(Pull.catchDone(effect, () => internalEffect.exitVoid)) if (self.state._tag === "Done") { - if (Pull.isDoneCause(self.state.exit.cause)) { - return resume(internalEffect.exitVoid) - } - return resume(self.state.exit) + return awaiter(self.state.exit) } - self.state.awaiters.add(resume) + self.state.awaiters.add(awaiter) return internalEffect.sync(() => { if (self.state._tag !== "Done") { - self.state.awaiters.delete(resume) + self.state.awaiters.delete(awaiter) } }) }) @@ -1594,34 +1698,36 @@ export { * * **Details** * - * Completed queues report a size of `0`. + * After `end`, a queue remains `Closing` while buffered messages are drained, + * and its size continues to include those messages. A `Done` queue reports a + * size of `0`. * * **Example** (Checking queue size) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Check size of empty queue * const size1 = yield* Queue.size(queue) - * console.log(size1) // 0 * * // Add some messages * yield* Queue.offerAll(queue, [1, 2, 3, 4, 5]) * * // Check size after adding messages * const size2 = yield* Queue.size(queue) - * console.log(size2) // 5 * * // End the queue * yield* Queue.end(queue) * - * // Size of ended queue is 0 + * // Ending retains the buffered size while the queue is Closing * const size3 = yield* Queue.size(queue) - * console.log(size3) // 0 + * return [size1, size2, size3] * }) + * + * await Effect.runPromise(program) // => [0, 5, 5] * ``` * * @category sizes @@ -1634,22 +1740,25 @@ export const size = (self: Dequeue): Effect => internalEffec * * **Example** (Checking if queues are full) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(3) * - * console.log(yield* Queue.isFull(queue)) // false + * const before = yield* Queue.isFull(queue) * * // Add some messages * yield* Queue.offerAll(queue, [1, 2, 3]) * - * console.log(yield* Queue.isFull(queue)) // true + * const after = yield* Queue.isFull(queue) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category sizes + * @category predicates * @since 2.0.0 */ export const isFull = (self: Dequeue): Effect => internalEffect.sync(() => isFullUnsafe(self)) @@ -1664,20 +1773,21 @@ export const isFull = (self: Dequeue): Effect => internalEf * * **Details** * - * Completed queues report a size of `0`. This unsafe operation reads the queue - * state directly without Effect wrapping. + * After `endUnsafe`, a queue remains `Closing` while buffered messages are + * drained, and its size continues to include those messages. A `Done` queue + * reports a size of `0`. This unsafe operation reads the queue state directly + * without Effect wrapping. * * **Example** (Checking queue size synchronously) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Check size of empty queue * const size1 = Queue.sizeUnsafe(queue) - * console.log(size1) // 0 * * // Add some messages * Queue.offerUnsafe(queue, 1) @@ -1686,15 +1796,16 @@ export const isFull = (self: Dequeue): Effect => internalEf * * // Check size after adding messages * const size2 = Queue.sizeUnsafe(queue) - * console.log(size2) // 3 * * // End the queue * Queue.endUnsafe(queue) * - * // Size of ended queue is 0 + * // Ending retains the buffered size while the queue is Closing * const size3 = Queue.sizeUnsafe(queue) - * console.log(size3) // 0 + * return [size1, size2, size3] * }) + * + * await Effect.runPromise(program) // => [0, 3, 3] * ``` * * @category sizes @@ -1712,22 +1823,25 @@ export const sizeUnsafe = (self: Dequeue): number => self.state._tag * * **Example** (Checking fullness synchronously) * - * ```ts - * import { Cause, Effect, Option, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(3) * - * console.log(Queue.isFullUnsafe(queue)) // false + * const before = Queue.isFullUnsafe(queue) * * // Add some messages * yield* Queue.offerAll(queue, [1, 2, 3]) * - * console.log(Queue.isFullUnsafe(queue)) // true + * const after = Queue.isFullUnsafe(queue) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [false, true] * ``` * - * @category sizes + * @category predicates * @since 4.0.0 */ export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(self) === self.capacity @@ -1738,15 +1852,15 @@ export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(s * * **Example** (Running effects into queues) * - * ```ts - * import { Cause, Effect, Queue } from "effect" + * ```ts import.meta.vitest + * import { Cause, Effect, Exit, Queue } from "effect" * * const program = Effect.gen(function*() { * const queue = yield* Queue.bounded(10) * * // Create an effect that succeeds * const dataProcessing = Effect.gen(function*() { - * yield* Effect.sleep("100 millis") + * yield* Effect.yieldNow * return "Processing completed successfully" * }) * @@ -1756,11 +1870,11 @@ export const isFullUnsafe = (self: Dequeue): boolean => sizeUnsafe(s * const effectIntoQueue = Queue.into(queue)(dataProcessing) * * const wasCompleted = yield* effectIntoQueue - * console.log("Queue operation completed:", wasCompleted) // true - * - * // Queue state now reflects the effect's outcome - * console.log("Queue state:", queue.state._tag) // "Done" + * const exit = yield* Effect.exit(Queue.take(queue)) + * return [wasCompleted, exit] * }) + * + * await Effect.runPromise(program) // => [true, Exit.fail(Cause.Done())] * ``` * * @category completion @@ -1801,7 +1915,6 @@ const exitFailDone = core.exitFail(core.Done()) as Failure const exitInterrupt = internalEffect.exitInterrupt() as Failure const releaseTakers = (self: Enqueue) => { - self.scheduleRunning = false if (self.state._tag === "Done" || self.state.takers.size === 0) { return } @@ -1819,7 +1932,10 @@ const scheduleReleaseTaker = (self: Enqueue) => { return } self.scheduleRunning = true - self.dispatcher.scheduleTask(() => releaseTakers(self), 0) + self.dispatcher.scheduleTask(() => { + self.scheduleRunning = false + releaseTakers(self) + }, 0) } const takeBetweenUnsafe = ( @@ -1831,11 +1947,8 @@ const takeBetweenUnsafe = ( return self.state.exit } else if (max <= 0 || min <= 0) { return core.exitSucceed([]) - } else if (self.capacity <= 0 && self.state.offers.size > 0) { - self.capacity = 1 - releaseCapacity(self) - self.capacity = 0 - const messages = [MutableList.take(self.messages)!] + } else if (self.capacity <= 0 && self.messages.length === 0 && self.state.offers.size > 0) { + const messages = [takeOfferUnsafe(self.state.offers)] releaseCapacity(self) return core.exitSucceed(messages) } @@ -1882,6 +1995,22 @@ const offerRemainingArray = (self: Enqueue, remaining: Array) => }) } +// Reserve a pending message for the consumer before the producer can reenter. +const takeOfferUnsafe = (offers: Set>): A => { + const entry = offers.values().next().value! + if (entry._tag === "Single") { + offers.delete(entry) + entry.resume(exitTrue) + return entry.message + } + const message = entry.remaining[entry.offset++] + if (entry.offset === entry.remaining.length) { + offers.delete(entry) + entry.resume(core.exitSucceed([])) + } + return message +} + const releaseCapacity = (self: Dequeue): boolean => { if (self.state._tag === "Done") { return Pull.isDoneCause(self.state.exit.cause) @@ -1895,22 +2024,22 @@ const releaseCapacity = (self: Dequeue): boolean => { } return false } - let n = self.capacity - self.messages.length + // Resuming a producer can synchronously take, offer, or shut down this queue. for (const entry of self.state.offers) { - if (n === 0) break + let n = self.capacity - self.messages.length + if (n <= 0) break else if (entry._tag === "Single") { MutableList.append(self.messages, entry.message) - n-- - entry.resume(exitTrue) self.state.offers.delete(entry) + entry.resume(exitTrue) } else { for (; entry.offset < entry.remaining.length; entry.offset++) { if (n === 0) return false MutableList.append(self.messages, entry.remaining[entry.offset]) n-- } - entry.resume(core.exitSucceed([])) self.state.offers.delete(entry) + entry.resume(core.exitSucceed([])) } } return false @@ -1935,10 +2064,7 @@ const takeAllUnsafe = (self: Dequeue) => { releaseCapacity(self) return messages } else if (self.state._tag !== "Done" && self.state.offers.size > 0) { - self.capacity = 1 - releaseCapacity(self) - self.capacity = 0 - const messages = [MutableList.take(self.messages)!] + const messages = [takeOfferUnsafe(self.state.offers)] releaseCapacity(self) return messages } diff --git a/packages/effect/src/Random.ts b/packages/effect/src/Random.ts index 53078f1d2..67b2c5781 100644 --- a/packages/effect/src/Random.ts +++ b/packages/effect/src/Random.ts @@ -17,6 +17,23 @@ import * as random from "./internal/random.ts" import type * as NonEmptyIterable from "./NonEmptyIterable.ts" import * as Predicate from "./Predicate.ts" +/** + * The service used to generate pseudo-random numbers. + * + * @category services + * @since 4.0.0 + */ +export interface Random { + /** + * Generates a random safe integer. + */ + nextIntUnsafe(): number + /** + * Generates a random number between 0 (inclusive) and 1 (exclusive). + */ + nextDoubleUnsafe(): number +} + /** * Represents a service for generating pseudo-random numbers. * @@ -33,29 +50,25 @@ import * as Predicate from "./Predicate.ts" * * **Example** (Accessing the random service) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * * const program = Effect.gen(function*() { * const float = yield* Random.next * const integer = yield* Random.nextInt * const inRange = yield* Random.nextIntBetween(1, 100) - * - * console.log("Float:", float) - * console.log("Integer:", integer) - * console.log("In range:", inRange) + * return [float, integer, inRange] as const * }) + * + * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [0.1633802591287037, 3434461687501127, 1] * ``` * - * @category Random Number Generators + * @category services * @since 2.0.0 */ -export const Random: Context.Reference<{ - nextIntUnsafe(): number - nextDoubleUnsafe(): number -}> = random.Random +export const Random: Context.Reference = random.Random -const randomWith = (f: (random: typeof Random["Service"]) => A): Effect.Effect => +const randomWith = (f: (random: Random) => A): Effect.Effect => Effect.withFiber((fiber) => Effect.succeed(f(fiber.getRef(Random)))) /** @@ -68,16 +81,13 @@ const randomWith = (f: (random: typeof Random["Service"]) => A): Effect.Effec * * **Example** (Generating a random number) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const randomDouble = yield* Random.next - * console.log("Random double:", randomDouble) - * }) + * await Effect.runPromise(Random.next.pipe(Random.withSeed("example"))) // => 0.1633802591287037 * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const next: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe()) @@ -91,16 +101,13 @@ export const next: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe( * * **Example** (Generating a random boolean) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const value = yield* Random.nextBoolean - * console.log("Random boolean:", value) - * }) + * await Effect.runPromise(Random.nextBoolean.pipe(Random.withSeed("example"))) // => false * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const nextBoolean: Effect.Effect = randomWith((r) => r.nextDoubleUnsafe() > 0.5) @@ -116,16 +123,13 @@ export const nextBoolean: Effect.Effect = randomWith((r) => r.nextDoubl * * **Example** (Generating a random integer) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const randomInt = yield* Random.nextInt - * console.log("Random integer:", randomInt) - * }) + * await Effect.runPromise(Random.nextInt.pipe(Random.withSeed("example"))) // => -6064002158214091 * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const nextInt: Effect.Effect = randomWith((r) => r.nextIntUnsafe()) @@ -139,20 +143,17 @@ export const nextInt: Effect.Effect = randomWith((r) => r.nextIntUnsafe( * * **Example** (Generating a bounded random number) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const randomDouble = yield* Random.nextBetween(0, 1) - * console.log("Random double: ", randomDouble) - * }) + * await Effect.runPromise(Random.nextBetween(0, 1).pipe(Random.withSeed("example"))) // => 0.1633802591287037 * ``` * - * @category Random Number Generators + * @category generators * @since 4.0.0 */ export const nextBetween = (min: number, max: number): Effect.Effect => - randomWith((r) => r.nextDoubleUnsafe() * (max - min) + min) + randomWith((r) => random.nextBetween(min, max, r.nextDoubleUnsafe())) /** * Generates a random integer between `min` and `max`. @@ -169,7 +170,7 @@ export const nextBetween = (min: number, max: number): Effect.Effect => * * **Example** (Generating a bounded random integer) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * * const program = Effect.gen(function*() { @@ -178,10 +179,13 @@ export const nextBetween = (min: number, max: number): Effect.Effect => * halfOpen: true * }) * const diceRoll3 = yield* Random.nextIntBetween(0, 10) + * return [diceRoll1, diceRoll2, diceRoll3] * }) + * + * await Effect.runPromise(program.pipe(Random.withSeed("example"))) // => [1, 4, 0] * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const nextIntBetween = (min: number, max: number, options?: { @@ -204,16 +208,13 @@ export const nextIntBetween = (min: number, max: number, options?: { * * **Example** (Shuffling values) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const values = yield* Random.shuffle([1, 2, 3, 4, 5]) - * console.log(values) - * }) + * await Effect.runPromise(Random.shuffle([1, 2, 3, 4, 5]).pipe(Random.withSeed("example"))) // => [4, 2, 5, 3, 1] * ``` * - * @category Random Number Generators + * @category generators * @since 2.0.0 */ export const shuffle = (elements: Iterable): Effect.Effect> => @@ -243,16 +244,13 @@ export const shuffle = (elements: Iterable): Effect.Effect> => * * **Example** (Choosing a random value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * - * const program = Effect.gen(function*() { - * const value = yield* Random.choice(["red", "green", "blue"] as const) - * console.log(value) - * }) + * await Effect.runPromise(Random.choice(["red", "green", "blue"] as const).pipe(Random.withSeed("example"))) // => "red" * ``` * - * @category Random Number Generators + * @category generators * @since 3.6.0 */ export const choice: >( @@ -285,25 +283,22 @@ export const choice: >( * * **Example** (Seeding random generation) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Random } from "effect" * * const program = Effect.gen(function*() { * const value1 = yield* Random.next * const value2 = yield* Random.next - * console.log(value1, value2) + * return [value1, value2] * }) * - * // Same seed produces same sequence - * const seeded1 = program.pipe(Random.withSeed("my-seed")) - * const seeded2 = program.pipe(Random.withSeed("my-seed")) - * - * // Both will output identical values - * Effect.runPromise(seeded1) - * Effect.runPromise(seeded2) + * await Effect.runPromise(Effect.all([ + * program.pipe(Random.withSeed("my-seed")), + * program.pipe(Random.withSeed("my-seed")) + * ])) // => [[0.018368576514773527, 0.4010840628128671], [0.018368576514773527, 0.4010840628128671]] * ``` * - * @category Seeding + * @category providing services * @since 4.0.0 */ export const withSeed: { diff --git a/packages/effect/src/RcMap.ts b/packages/effect/src/RcMap.ts index 18892480c..e484c4fd2 100644 --- a/packages/effect/src/RcMap.ts +++ b/packages/effect/src/RcMap.ts @@ -21,6 +21,7 @@ import * as Exit from "./Exit.ts" import * as Fiber from "./Fiber.ts" import { constant, dual, flow } from "./Function.ts" import * as MutableHashMap from "./MutableHashMap.ts" +import type * as Option from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" import * as Scope from "./Scope.ts" @@ -39,17 +40,14 @@ const TypeId = "~effect/RcMap" * * **Example** (Inspecting a reference-counted map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * // Create an RcMap that manages database connections * const dbConnectionMap = yield* RcMap.make({ * lookup: (dbName: string) => - * Effect.acquireRelease( - * Effect.succeed(`Connection to ${dbName}`), - * (conn) => Effect.log(`Closing ${conn}`) - * ), + * Effect.acquireRelease(Effect.succeed(`Connection to ${dbName}`), () => Effect.void), * capacity: 10, * idleTimeToLive: "5 minutes" * }) @@ -60,8 +58,10 @@ const TypeId = "~effect/RcMap" * // - idleTimeToLive: Time before idle resources are released * // - state: Current state of the map * - * console.log(`Capacity: ${dbConnectionMap.capacity}`) - * }).pipe(Effect.scoped) + * return dbConnectionMap.capacity + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => 10 * ``` * * @see {@link make} for creating an `RcMap` @@ -205,15 +205,17 @@ const makeUnsafe = (options: { * * **Example** (Creating a reference-counted map) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`acquired ${key}`), - * () => Effect.log(`releasing ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ) * }) * @@ -224,12 +226,15 @@ const makeUnsafe = (options: { * Effect.scoped * ) * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released foo"] * ``` * * @see {@link get} for acquiring or retaining a resource by key * @see {@link invalidate} for removing a resource from the map * - * @category models + * @category constructors * @since 3.5.0 */ export const make: { @@ -269,7 +274,7 @@ export const make: { self.state = { _tag: "Closed" } return Effect.forEach( map, - ([, entry]) => Effect.exit(Scope.close(entry.scope, Exit.void)) + ([, entry]) => Effect.exit(closeEntry(entry)) ).pipe( Effect.tap(() => Effect.sync(() => { @@ -299,22 +304,26 @@ export const make: { * * **Example** (Acquiring a resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`Resource: ${key}`), - * () => Effect.log(`Released ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ) * }) * * // Get a resource - it will be acquired on first access * const resource = yield* RcMap.get(map, "database") - * console.log(resource) // "Resource: database" - * }).pipe(Effect.scoped) + * return [resource, events] as const + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => ["Resource: database", ["released database"]] * ``` * * @see {@link make} for creating the reference-counted map @@ -361,7 +370,7 @@ export const get: { context.set(key, value) }) context.set(Scope.Scope.key, entry.scope) - self.lookup(key).pipe( + Effect.suspend(() => self.lookup(key)).pipe( Effect.runForkWith(Context.makeUnsafe(context)), Fiber.runIn(entry.scope) ).addObserver((exit) => Deferred.doneUnsafe(entry.deferred, exit)) @@ -373,20 +382,93 @@ export const get: { }) ) +/** + * Retains and returns an existing resource without invoking the map's lookup + * function when the key is missing. + * + * **When to use** + * + * Use when you only want to acquire a reference to a resource that is currently + * cached. + * + * **Details** + * + * Returns `Option.none` when the key is not currently stored or the map is + * closed. If an entry exists, its reference count is incremented for the current + * `Scope` before awaiting its result. A successful entry returns + * `Option.some(value)`, while an in-flight or cached failure fails with the same + * error as `get`. + * + * **Example** (Retaining only cached resources) + * + * ```ts import.meta.vitest + * import { Effect, Option, RcMap } from "effect" + * + * const program = Effect.gen(function*() { + * const map = yield* RcMap.make({ + * lookup: (key: string) => Effect.succeed(`Resource: ${key}`), + * idleTimeToLive: "1 minute" + * }) + * + * const missing = yield* RcMap.getOption(map, "database") + * yield* Effect.scoped(RcMap.get(map, "database")) + * const cached = yield* Effect.scoped(RcMap.getOption("database")(map)) + * + * return [missing, cached] as const + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [Option.none(), Option.some("Resource: database")] + * ``` + * + * @see {@link get} for acquiring a resource when the key is missing + * @see {@link has} for checking presence without retaining or awaiting the entry + * + * @category combinators + * @since 4.0.0 + */ +export const getOption: { + (key: K): (self: RcMap) => Effect.Effect, E, Scope.Scope> + (self: RcMap, key: K): Effect.Effect, E, Scope.Scope> +} = dual( + 2, + (self: RcMap, key: K): Effect.Effect, E, Scope.Scope> => + Effect.uninterruptibleMask((restore) => { + if (self.state._tag === "Closed") { + return Effect.succeedNone + } + const o = MutableHashMap.get(self.state.map, key) + if (o._tag === "None") { + return Effect.succeedNone + } + const entry = o.value + entry.refCount++ + const scope = Context.getUnsafe(Fiber.getCurrent()!.context, Scope.Scope) + return Scope.addFinalizer(scope, entry.finalizer).pipe( + Effect.andThen(Effect.asSome(restore(Deferred.await(entry.deferred)))) + ) + }) +) + +const closeEntry = (entry: State.Entry) => + entry.fiber + ? Fiber.interrupt(entry.fiber).pipe(Effect.andThen(Scope.close(entry.scope, Exit.void))) + : Scope.close(entry.scope, Exit.void) + const release = (self: RcMap, key: K, entry: State.Entry) => Effect.withFiber((fiber) => { entry.refCount-- if (entry.refCount > 0) { return Effect.void - } else if ( - self.state._tag === "Closed" - || !MutableHashMap.has(self.state.map, key) - || Duration.isZero(entry.idleTimeToLive) - ) { - if (self.state._tag === "Open") { - MutableHashMap.remove(self.state.map, key) - } - return Scope.close(entry.scope, Exit.void) + } else if (self.state._tag === "Closed") { + return closeEntry(entry) + } + + const o = MutableHashMap.get(self.state.map, key) + if (o._tag === "None" || o.value !== entry) { + return closeEntry(entry) + } else if (Duration.isZero(entry.idleTimeToLive)) { + MutableHashMap.remove(self.state.map, key) + return closeEntry(entry) } else if (!Duration.isFinite(entry.idleTimeToLive)) { return Effect.void } @@ -400,6 +482,8 @@ const release = (self: RcMap, key: K, entry: State.Entry const remaining = entry.expiresAt - now if (remaining <= 0) { if (self.state._tag === "Closed" || entry.refCount > 0) return Effect.void + const o = MutableHashMap.get(self.state.map, key) + if (o._tag === "None" || o.value !== entry) return Effect.void MutableHashMap.remove(self.state.map, key) return restore(Scope.close(entry.scope, Exit.void)) } @@ -427,10 +511,10 @@ const release = (self: RcMap, key: K, entry: State.Entry * * **Example** (Listing keys) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => Effect.succeed(`value-${key}`) * }) @@ -442,8 +526,10 @@ const release = (self: RcMap, key: K, entry: State.Entry * * // Get all keys currently in the map * const allKeys = yield* RcMap.keys(map) - * console.log(allKeys) // ["foo", "bar", "baz"] - * }).pipe(Effect.scoped) + * return Array.from(allKeys) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => ["foo", "bar", "baz"] * ``` * * @see {@link has} for checking one key without enumerating all keys @@ -467,15 +553,17 @@ export const keys = (self: RcMap): Effect.Effect> * * **Example** (Invalidating a resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`Resource: ${key}`), - * () => Effect.log(`Released ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ) * }) * @@ -488,7 +576,10 @@ export const keys = (self: RcMap): Effect.Effect> * * // Next access will create a new resource * yield* RcMap.get(map, "cache") - * }).pipe(Effect.scoped) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released cache", "released cache"] * ``` * * @see {@link get} for acquiring or retaining the resource for a key @@ -509,8 +600,7 @@ export const invalidate: { const entry = o.value MutableHashMap.remove(self.state.map, key) if (entry.refCount > 0) return - if (entry.fiber) yield* Fiber.interrupt(entry.fiber) - yield* Scope.close(entry.scope, Exit.void) + yield* closeEntry(entry) }, Effect.uninterruptible) ) @@ -561,15 +651,17 @@ export const has: { * * **Example** (Extending resource idle time) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcMap } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const map = yield* RcMap.make({ * lookup: (key: string) => * Effect.acquireRelease( * Effect.succeed(`Resource: ${key}`), - * () => Effect.log(`Released ${key}`) + * () => Effect.sync(() => events.push(`released ${key}`)) * ), * idleTimeToLive: "10 seconds" * }) @@ -583,7 +675,10 @@ export const has: { * * // The resource will now live for another 10 seconds * // from the time it was touched - * }).pipe(Effect.scoped) + * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released session"] * ``` * * @see {@link invalidate} for removing the resource instead of extending it diff --git a/packages/effect/src/RcRef.ts b/packages/effect/src/RcRef.ts index fab9307ec..3d64d5890 100644 --- a/packages/effect/src/RcRef.ts +++ b/packages/effect/src/RcRef.ts @@ -33,15 +33,17 @@ const TypeId = "~effect/RcRef" * * **Example** (Sharing a lazily acquired resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcRef } from "effect" * + * const events: Array = [] + * * // Create an RcRef for a database connection * const createConnectionRef = (connectionString: string) => * RcRef.make({ * acquire: Effect.acquireRelease( * Effect.succeed(`Connected to ${connectionString}`), - * (connection) => Effect.log(`Closing connection: ${connection}`) + * (connection) => Effect.sync(() => events.push(`closed ${connection}`)) * ) * }) * @@ -53,8 +55,10 @@ const TypeId = "~effect/RcRef" * const connection1 = yield* RcRef.get(connectionRef) * const connection2 = yield* RcRef.get(connectionRef) * - * return [connection1, connection2] + * return [connection1 === connection2, events] as const * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [true, ["closed Connected to postgres://localhost"]] * ``` * * @category models @@ -69,12 +73,13 @@ export interface RcRef extends Pipeable { * * **Example** (Referencing namespace types) * - * ```ts + * ```ts import.meta.vitest * import type { RcRef } from "effect" * * // Use RcRef namespace types * type MyRcRef = RcRef.RcRef * type MyVariance = RcRef.RcRef.Variance + * * ``` * * @since 3.5.0 @@ -121,14 +126,16 @@ export declare namespace RcRef { * * **Example** (Creating a reference-counted resource) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcRef } from "effect" * - * Effect.gen(function*() { + * const events: Array = [] + * + * const program = Effect.gen(function*() { * const ref = yield* RcRef.make({ * acquire: Effect.acquireRelease( * Effect.succeed("foo"), - * () => Effect.log("release foo") + * () => Effect.sync(() => events.push("released foo")) * ) * }) * @@ -139,6 +146,9 @@ export declare namespace RcRef { * Effect.scoped * ) * }) + * + * await Effect.runPromise(Effect.scoped(program)) + * events // => ["released foo"] * ``` * * @category constructors @@ -172,15 +182,17 @@ export const make: ( * * **Example** (Sharing one acquired value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, RcRef } from "effect" * + * const events: Array = [] + * * const program = Effect.gen(function*() { * // Create an RcRef with a resource * const ref = yield* RcRef.make({ * acquire: Effect.acquireRelease( * Effect.succeed("shared resource"), - * (resource) => Effect.log(`Releasing ${resource}`) + * (resource) => Effect.sync(() => events.push(`released ${resource}`)) * ) * }) * @@ -188,11 +200,10 @@ export const make: ( * const value1 = yield* RcRef.get(ref) * const value2 = yield* RcRef.get(ref) * - * // Both values are the same instance - * console.log(value1 === value2) // true - * - * return value1 + * return [value1 === value2, events] as const * }) + * + * await Effect.runPromise(Effect.scoped(program)) // => [true, ["released shared resource"]] * ``` * * @category combinators diff --git a/packages/effect/src/Record.ts b/packages/effect/src/Record.ts index 99d006ce2..ec47fe303 100644 --- a/packages/effect/src/Record.ts +++ b/packages/effect/src/Record.ts @@ -15,6 +15,7 @@ import * as Equal from "./Equal.ts" import type { Equivalence } from "./Equivalence.ts" import { dual, identity } from "./Function.ts" import type { TypeLambda } from "./HKT.ts" +import * as InternalRecord from "./internal/record.ts" import * as Option from "./Option.ts" import * as Reducer from "./Reducer.ts" import type { Result } from "./Result.ts" @@ -27,7 +28,7 @@ import type { NoInfer } from "./Types.ts" * * **Example** (Defining a readonly record type) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // Creating a readonly record type @@ -37,6 +38,7 @@ import type { NoInfer } from "./Types.ts" * name: "John", * age: 30 * } + * user // => { name: "John", age: 30 } * ``` * * @category models @@ -52,7 +54,7 @@ export type ReadonlyRecord = { * * **Example** (Using readonly record helper types) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // Using NonLiteralKey to convert literal keys to generic types @@ -60,6 +62,9 @@ export type ReadonlyRecord = { * * // Using IntersectKeys to find common keys between record types * type CommonKeys = Record.ReadonlyRecord.IntersectKeys<"a" | "b", "b" | "c"> // "b" + * + * "key" satisfies GenericKey + * "b" satisfies CommonKeys * ``` * * @since 2.0.0 @@ -76,7 +81,7 @@ export declare namespace ReadonlyRecord { * * **Example** (Converting literal keys to non-literal keys) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // For literal string keys, this becomes 'string' @@ -84,6 +89,10 @@ export declare namespace ReadonlyRecord { * * // For symbol keys, this becomes 'symbol' * type Example2 = Record.ReadonlyRecord.NonLiteralKey // symbol + * + * const symbol: Example2 = Symbol.for("key") + * "key" satisfies Example1 + * symbol * ``` * * @category models @@ -98,7 +107,7 @@ export declare namespace ReadonlyRecord { * * **Example** (Intersecting record keys) * - * ```ts + * ```ts import.meta.vitest * import type { Record } from "effect" * * // Intersection of literal keys @@ -106,6 +115,9 @@ export declare namespace ReadonlyRecord { * * // Intersection with generic string * type Example2 = Record.ReadonlyRecord.IntersectKeys // string + * + * "b" satisfies Example1 + * "a" satisfies Example2 * ``` * * @category models @@ -114,6 +126,21 @@ export declare namespace ReadonlyRecord { export type IntersectKeys = [string] extends [K1 | K2] ? NonLiteralKey & NonLiteralKey : K1 & K2 + + /** + * Represents a record produced by grouping values under keys of type `K`. + * + * **Details** + * + * Finite string or symbol key types become optional because an input may not + * produce every possible key. Open `string` and `symbol` key types retain their + * record index signatures. + * + * @category utility types + * @since 4.0.0 + */ + export type GroupByResult = [NonLiteralKey] extends [K] ? Record + : Partial> } /** @@ -122,7 +149,7 @@ export declare namespace ReadonlyRecord { * * **Example** (Applying a readonly record type lambda) * - * ```ts + * ```ts import.meta.vitest * import type { HKT, Record } from "effect" * * type Settings = HKT.Kind< @@ -137,9 +164,10 @@ export declare namespace ReadonlyRecord { * port: 3000, * retries: 3 * } + * defaults // => { port: 3000, retries: 3 } * ``` * - * @category type lambdas + * @category utility types * @since 2.0.0 */ export interface ReadonlyRecordTypeLambda extends TypeLambda { @@ -151,16 +179,15 @@ export interface ReadonlyRecordTypeLambda extends Typ * * **Example** (Creating an empty record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" * * // Create an empty record * const emptyRecord = Record.empty() - * console.log(emptyRecord) // {} + * emptyRecord // => {} * * // The type ensures type safety for future operations - * const withValue = Record.set(emptyRecord, "count", 42) - * console.log(withValue) // { count: 42 } + * Record.set(emptyRecord, "count", 42) // => { count: 42 } * ``` * * @category constructors @@ -176,12 +203,11 @@ export const empty = (): Record< * * **Example** (Checking for an empty record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.isEmptyRecord({}), true) - * assert.deepStrictEqual(Record.isEmptyRecord({ a: 3 }), false) + * Record.isEmptyRecord({}) // => true + * Record.isEmptyRecord({ a: 3 }) // => false * ``` * * @category guards @@ -195,12 +221,11 @@ export const isEmptyRecord = (self: Record): self is * * **Example** (Checking for an empty readonly record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.isEmptyReadonlyRecord({}), true) - * assert.deepStrictEqual(Record.isEmptyReadonlyRecord({ a: 3 }), false) + * Record.isEmptyReadonlyRecord({}) // => true + * Record.isEmptyReadonlyRecord({ a: 3 }) // => false * ``` * * @category guards @@ -216,16 +241,10 @@ export const isEmptyReadonlyRecord: ( * * **Example** (Building a record from mapped iterable values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" - * - * const input = [1, 2, 3, 4] * - * assert.deepStrictEqual( - * Record.fromIterableWith(input, (a) => [String(a), a * 2]), - * { "1": 2, "2": 4, "3": 6, "4": 8 } - * ) + * Record.fromIterableWith([1, 2, 3, 4], (a) => [String(a), a * 2]) // => { "1": 2, "2": 4, "3": 6, "4": 8 } * ``` * * @category constructors @@ -248,7 +267,7 @@ export const fromIterableWith: { const out: Record = empty() for (const a of self) { const [k, b] = f(a) - out[k] = b + InternalRecord.assignProperty(out, k, b) } return out } @@ -259,31 +278,38 @@ export const fromIterableWith: { * * **Example** (Building a record keyed by iterable values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const users = [ * { id: "2", name: "name2" }, * { id: "1", name: "name1" } * ] * - * assert.deepStrictEqual( - * Record.fromIterableBy(users, (user) => user.id), - * { - * "2": { id: "2", name: "name2" }, - * "1": { id: "1", name: "name1" } - * } - * ) + * Record.fromIterableBy( + * users, + * (user) => user.id + * ) // => { "1": { id: "1", name: "name1" }, "2": { id: "2", name: "name2" } } * ``` * * @category constructors * @since 2.0.0 */ -export const fromIterableBy = ( - items: Iterable, - f: (a: A) => K -): Record, A> => fromIterableWith(items, (a) => [f(a), a]) +export const fromIterableBy: { + ( + f: (a: A) => K + ): (items: Iterable) => Record, A> + ( + items: Iterable, + f: (a: A) => K + ): Record, A> +} = dual( + 2, + ( + items: Iterable, + f: (a: A) => K + ): Record, A> => fromIterableWith(items, (a) => [f(a), a]) +) /** * Builds a record from an iterable of key-value pairs. @@ -295,13 +321,10 @@ export const fromIterableBy = ( * * **Example** (Building a record from entries) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * const input: Array<[string, number]> = [["a", 1], ["b", 2]] - * - * assert.deepStrictEqual(Record.fromEntries(input), { a: 1, b: 2 }) + * Record.fromEntries([["a", 1], ["b", 2]]) // => { a: 1, b: 2 } * ``` * * @category constructors @@ -316,15 +339,11 @@ export const fromEntries: ( * * **Example** (Collecting mapped record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(Record.collect(x, (key, n) => [key, n]), [["a", 1], [ - * "b", - * 2 - * ], ["c", 3]]) + * Record.collect(x, (key, n) => [key, n]) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @category converting @@ -349,12 +368,11 @@ export const collect: { * * **Example** (Converting a record to entries) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } - * assert.deepStrictEqual(Record.toEntries(x), [["a", 1], ["b", 2], ["c", 3]]) + * Record.toEntries(x) // => [["a", 1], ["b", 2], ["c", 3]] * ``` * * @category converting @@ -370,11 +388,10 @@ export const toEntries: (self: ReadonlyRecord) => Arr * * **Example** (Getting the record size) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.size({ a: "a", b: 1, c: true }), 3) + * Record.size({ a: "a", b: 1, c: true }) // => 3 * ``` * * @category getters @@ -387,15 +404,14 @@ export const size = (self: ReadonlyRecord): number => * * **Example** (Checking key membership) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.has({ a: 1, b: 2 }, "a"), true) - * assert.deepStrictEqual(Record.has(Record.empty(), "c"), false) + * Record.has({ a: 1, b: 2 }, "a") // => true + * Record.has(Record.empty(), "c") // => false * ``` * - * @category guards + * @category predicates * @since 2.0.0 */ export const has: { @@ -419,14 +435,13 @@ export const has: { * * **Example** (Getting a value as an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Record as R } from "effect" - * import * as assert from "node:assert" * * const person: Record = { name: "John Doe", age: 35 } * - * assert.deepStrictEqual(R.get(person, "name"), Option.some("John Doe")) - * assert.deepStrictEqual(R.get(person, "email"), Option.none()) + * R.get(person, "name") // => Option.some("John Doe") + * R.get(person, "email") // => Option.none() * ``` * * @category getters @@ -438,7 +453,7 @@ export const get: { } = dual( 2, (self: ReadonlyRecord, key: NoInfer): Option.Option => - has(self, key) ? Option.some(self[key]) : Option.none() + Object.hasOwn(self, key) ? Option.some(self[key]) : Option.none() ) /** @@ -447,15 +462,15 @@ export const get: { * * **Example** (Modifying a value at a key) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * * const f = (x: number) => x * 2 * * const input: Record = { a: 3 } * - * Record.modify(input, "a", f) // Option.some({ a: 6 }) - * Record.modify(input, "b", f) // Option.none() + * Record.modify(input, "a", f) // => Option.some({ a: 6 }) + * Record.modify(input, "b", f) // => Option.none() * ``` * * @category mutations @@ -494,11 +509,11 @@ export const modify: { * * **Example** (Replacing a value at a key) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * - * Record.replace({ a: 1, b: 2, c: 3 }, "a", 10) // Option.some({ a: 10, b: 2, c: 3 }) - * Record.replace(Record.empty(), "a", 10) // Option.none() + * Record.replace({ a: 1, b: 2, c: 3 }, "a", 10) // => Option.some({ a: 10, b: 2, c: 3 }) + * Record.replace(Record.empty(), "a", 10) // => Option.none() * ``` * * @category mutations @@ -537,11 +552,10 @@ export const replace: { * * **Example** (Removing a key) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.remove({ a: 1, b: 2 }, "a"), { b: 2 }) + * Record.remove({ a: 1, b: 2 }, "a") // => { b: 2 } * ``` * * @category mutations @@ -569,13 +583,13 @@ export const remove: { * * **Example** (Popping a value and removing its key) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * * const input: Record = { a: 1, b: 2 } * - * Record.pop(input, "a") // Option.some([1, { b: 2 }]) - * Record.pop(input, "c") // Option.none() + * Record.pop(input, "a") // => Option.some([1, { b: 2 }]) + * Record.pop(input, "c") // => Option.none() * ``` * * @category mutations @@ -600,17 +614,16 @@ export const pop: { * * **Example** (Mapping record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const f = (n: number) => `-${n}` * - * assert.deepStrictEqual(Record.map({ a: 3, b: 5 }, f), { a: "-3", b: "-5" }) + * Record.map({ a: 3, b: 5 }, f) // => { a: "-3", b: "-5" } * * const g = (n: number, key: string) => `${key.toUpperCase()}-${n}` * - * assert.deepStrictEqual(Record.map({ a: 3, b: 5 }, g), { a: "A-3", b: "B-5" }) + * Record.map({ a: 3, b: 5 }, g) // => { a: "A-3", b: "B-5" } * ``` * * @category mapping @@ -624,7 +637,7 @@ export const map: { (self: ReadonlyRecord, f: (a: A, key: NoInfer) => B): Record => { const out: Record = { ...self } as any for (const key of keys(self)) { - out[key] = f(self[key], key) + InternalRecord.assignProperty(out, key, f(self[key], key)) } return out } @@ -635,14 +648,10 @@ export const map: { * * **Example** (Mapping record keys) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()), - * { A: 3, B: 5 } - * ) + * Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()) // => { A: 3, B: 5 } * ``` * * @category mapping @@ -665,7 +674,7 @@ export const mapKeys: { const out: Record = {} as any for (const key of keys(self)) { const a = self[key] - out[f(key, a)] = a + InternalRecord.assignProperty(out, f(key, a), a) } return out } @@ -676,14 +685,10 @@ export const mapKeys: { * * **Example** (Mapping record entries) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]), - * { A: 4, B: 6 } - * ) + * Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]) // => { A: 4, B: 6 } * ``` * * @category mapping @@ -706,7 +711,7 @@ export const mapEntries: { const out = {} as Record for (const key of keys(self)) { const [k, b] = f(self[key], key) - out[k] = b + InternalRecord.assignProperty(out, k, b) } return out } @@ -718,13 +723,12 @@ export const mapEntries: { * * **Example** (Filtering and mapping with Result) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } * const f = (a: number, key: string) => a > 2 ? Result.succeed(a * 2) : Result.failVoid - * assert.deepStrictEqual(Record.filterMap(x, f), { c: 6 }) + * Record.filterMap(x, f) // => { c: 6 } * ``` * * @category filtering @@ -748,7 +752,7 @@ export const filterMap: { for (const key of keys(self)) { const result = f(self[key], key) if (R.isSuccess(result)) { - out[key] = result.success + InternalRecord.assignProperty(out, key, result.success) } } return out @@ -760,12 +764,11 @@ export const filterMap: { * * **Example** (Filtering record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3, d: 4 } - * assert.deepStrictEqual(Record.filter(x, (n) => n > 2), { c: 3, d: 4 }) + * Record.filter(x, (n) => n > 2) // => { c: 3, d: 4 } * ``` * * @category filtering @@ -795,7 +798,7 @@ export const filter: { const out: Record = empty() for (const key of keys(self)) { if (predicate(self[key], key)) { - out[key] = self[key] + InternalRecord.assignProperty(out, key, self[key]) } } return out @@ -808,14 +811,10 @@ export const filter: { * * **Example** (Extracting Some values) * - * ```ts + * ```ts import.meta.vitest * import { Option, Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }), - * { a: 1, c: 2 } - * ) + * Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }) // => { a: 1, c: 2 } * ``` * * @category filtering @@ -830,7 +829,7 @@ export const getSomes: ( for (const key of keys(self)) { const option = self[key] if (Option.isSome(option)) { - out[key] = option.value + InternalRecord.assignProperty(out, key, option.value) } } return out @@ -842,18 +841,14 @@ export const getSomes: ( * * **Example** (Extracting Result failures) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.getFailures({ + * Record.getFailures({ * a: Result.succeed(1), * b: Result.fail("err"), * c: Result.succeed(2) - * }), - * { b: "err" } - * ) + * }) // => { b: "err" } * ``` * * @category filtering @@ -866,7 +861,7 @@ export const getFailures = ( for (const key of keys(self)) { const value = self[key] if (R.isFailure(value)) { - out[key] = value.failure + InternalRecord.assignProperty(out, key, value.failure) } } @@ -879,18 +874,14 @@ export const getFailures = ( * * **Example** (Extracting Result successes) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.getSuccesses({ + * Record.getSuccesses({ * a: Result.succeed(1), * b: Result.fail("err"), * c: Result.succeed(2) - * }), - * { a: 1, c: 2 } - * ) + * }) // => { a: 1, c: 2 } * ``` * * @category filtering @@ -903,7 +894,7 @@ export const getSuccesses = ( for (const key of keys(self)) { const value = self[key] if (R.isSuccess(value)) { - out[key] = value.success + InternalRecord.assignProperty(out, key, value.success) } } @@ -921,13 +912,12 @@ export const getSuccesses = ( * * **Example** (Partitioning with Result) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * * const x = { a: 1, b: 2, c: 3 } * const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n)) - * assert.deepStrictEqual(Record.partition(x, f), [{ a: 1, c: 3 }, { b: 2 }]) + * Record.partition(x, f) // => [{ a: 1, c: 3 }, { b: 2 }] * ``` * * @category filtering @@ -954,9 +944,9 @@ export const partition: { for (const key of keys(self)) { const e = f(self[key], key) if (R.isFailure(e)) { - left[key] = e.failure + InternalRecord.assignProperty(left, key, e.failure) } else { - right[key] = e.success + InternalRecord.assignProperty(right, key, e.success) } } return [left, right] @@ -969,14 +959,10 @@ export const partition: { * * **Example** (Separating Result values) * - * ```ts + * ```ts import.meta.vitest * import { Record, Result } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }), - * [{ a: "e" }, { b: 1 }] - * ) + * Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }) // => [{ a: "e" }, { b: 1 }] * ``` * * @category filtering @@ -991,11 +977,10 @@ export const separate: ( * * **Example** (Getting record keys) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.keys({ a: 1, b: 2, c: 3 }), ["a", "b", "c"]) + * Record.keys({ a: 1, b: 2, c: 3 }) // => ["a", "b", "c"] * ``` * * @category getters @@ -1009,11 +994,10 @@ export const keys = (self: ReadonlyRecord): * * **Example** (Getting record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.values({ a: 1, b: 2, c: 3 }), [1, 2, 3]) + * Record.values({ a: 1, b: 2, c: 3 }) // => [1, 2, 3] * ``` * * @category getters @@ -1026,12 +1010,11 @@ export const values = (self: ReadonlyRecord): Array { a: 5, b: 2 } + * Record.set("c", 5)({ a: 1, b: 2 }) // => { a: 1, b: 2, c: 5 } * ``` * * @category mutations @@ -1058,13 +1041,50 @@ export const set: { } ) +/** + * Mutates a record by assigning a value to a property. + * + * **When to use** + * + * Use when incrementally constructing a new record and copying it for every + * property would be unnecessary. + * + * **Gotchas** + * + * This function mutates `self`. When `key` is `"__proto__"`, it creates an + * own data property instead of changing the object's prototype. + * + * **Example** (Assigning an external key safely) + * + * ```ts import.meta.vitest + * import { Record } from "effect" + * + * const key: string = "__proto__" // Assume this comes from external input + * const value = { polluted: true } + * + * const unsafe: Record = {} + * unsafe[key] = value + * Object.getPrototypeOf(unsafe) === value // => true + * + * const safe: Record = {} + * Record.assignProperty(safe, key, value) + * Object.getPrototypeOf(safe) === Object.prototype // => true + * safe[key] === value // => true + * ``` + * + * @see {@link set} for an immutable update + * @category mutations + * @since 4.0.0 + */ +export const assignProperty: (self: object, key: PropertyKey, value: unknown) => void = InternalRecord.assignProperty + /** * Checks whether all the keys and values in one record are also found in another record. * Uses the provided equivalence function to compare values. * * **Example** (Checking subrecords with a custom equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Record } from "effect" * * const isSubrecord = Record.isSubrecordBy( @@ -1077,15 +1097,9 @@ export const set: { * status: "active" * } * - * console.log( - * isSubrecord(required, available) - * ) // true - * console.log( - * isSubrecord({ role: "Admin", status: "inactive" }, available) - * ) // false - * console.log( - * isSubrecord(required, { role: "editor", status: "active" }) - * ) // false + * isSubrecord(required, available) // => true + * isSubrecord({ role: "Admin", status: "inactive" }, available) // => false + * isSubrecord(required, { role: "editor", status: "active" }) // => false * ``` * * @category predicates @@ -1114,18 +1128,11 @@ export const isSubrecordBy = (equivalence: Equivalence): { * * **Example** (Checking subrecords) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.isSubrecord({ a: 1 } as Record, { a: 1, b: 2 }), - * true - * ) - * assert.deepStrictEqual( - * Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record), - * false - * ) + * Record.isSubrecord({ a: 1 } as Record, { a: 1, b: 2 }) // => true + * Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record) // => false * ``` * * @category predicates @@ -1141,14 +1148,10 @@ export const isSubrecord: { * * **Example** (Reducing record values) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value, key) => acc + value), - * 6 - * ) + * Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value) => acc + value) // => 6 * ``` * * @category folding @@ -1180,15 +1183,14 @@ export const reduce: { * * **Example** (Checking every record value) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.every({ a: 1, b: 2 }, (n) => n > 0), true) - * assert.deepStrictEqual(Record.every({ a: 1, b: -1 }, (n) => n > 0), false) + * Record.every({ a: 1, b: 2 }, (n) => n > 0) // => true + * Record.every({ a: 1, b: -1 }, (n) => n > 0) // => false * ``` * - * @category predicates + * @category guards * @since 2.0.0 */ export const every: { @@ -1221,12 +1223,11 @@ export const every: { * * **Example** (Checking for any matching value) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.some({ a: 1, b: 2 }, (n) => n > 1), true) - * assert.deepStrictEqual(Record.some({ a: 1, b: 2 }, (n) => n > 2), false) + * Record.some({ a: 1, b: 2 }, (n) => n > 1) // => true + * Record.some({ a: 1, b: 2 }, (n) => n > 2) // => false * ``` * * @category predicates @@ -1253,14 +1254,10 @@ export const some: { * * **Example** (Merging records with union) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b), - * { a: 1, b: 5, c: 4 } - * ) + * Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { a: 1, b: 5, c: 4 } * ``` * * @category combining @@ -1292,14 +1289,14 @@ export const union: { const out: Record = empty() for (const key of keys(self)) { if (has(that, key as any)) { - out[key] = combine(self[key], that[key as unknown as K1]) + InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) } else { - out[key] = self[key] + InternalRecord.assignProperty(out, key, self[key]) } } for (const key of keys(that)) { if (!has(out, key)) { - out[key] = that[key] + InternalRecord.assignProperty(out, key, that[key]) } } return out @@ -1312,14 +1309,10 @@ export const union: { * * **Example** (Merging intersecting keys) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b), - * { b: 5 } - * ) + * Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b) // => { b: 5 } * ``` * * @category combining @@ -1348,7 +1341,7 @@ export const intersection: { } for (const key of keys(self)) { if (has(that, key as any)) { - out[key] = combine(self[key], that[key as unknown as K1]) + InternalRecord.assignProperty(out, key, combine(self[key], that[key as unknown as K1])) } } return out @@ -1361,14 +1354,10 @@ export const intersection: { * * **Example** (Keeping keys unique to each record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual( - * Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 }), - * { a: 1, c: 4 } - * ) + * Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 }) // => { a: 1, c: 4 } * ``` * * @category combining @@ -1395,12 +1384,12 @@ export const difference: { const out = {} as Record for (const key of keys(self)) { if (!has(that, key as any)) { - out[key] = self[key] + InternalRecord.assignProperty(out, key, self[key]) } } for (const key of keys(that)) { if (!has(self, key as any)) { - out[key] = that[key] + InternalRecord.assignProperty(out, key, that[key]) } } return out @@ -1412,14 +1401,13 @@ export const difference: { * * **Example** (Comparing records with a value equivalence) * - * ```ts + * ```ts import.meta.vitest * import { Equal, Record } from "effect" - * import * as assert from "node:assert" * * const recordEquivalence = Record.makeEquivalence(Equal.asEquivalence()) * - * assert.deepStrictEqual(recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 2 }), true) - * assert.deepStrictEqual(recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 3 }), false) + * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 2 }) // => true + * recordEquivalence({ a: 1, b: 2 }, { a: 1, b: 3 }) // => false * ``` * * @category instances @@ -1437,11 +1425,10 @@ export const makeEquivalence = ( * * **Example** (Creating a singleton record) * - * ```ts + * ```ts import.meta.vitest * import { Record } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(Record.singleton("a", 1), { a: 1 }) + * Record.singleton("a", 1) // => { a: 1 } * ``` * * @category constructors @@ -1516,18 +1503,17 @@ export function makeReducerIntersection( * * **Example** (Finding the first matching entry) * - * ```ts - * import { Record } from "effect" + * ```ts import.meta.vitest + * import { Option, Record } from "effect" * * const record = { a: 1, b: 2, c: 3 } - * const result = Record.findFirst( + * Record.findFirst( * record, * (value, key) => value > 1 && key !== "b" - * ) - * console.log(result) // Option.Some(["c", 3]) + * ) // => Option.some(["c", 3]) * ``` * - * @category elements + * @category searching * @since 3.14.0 */ export const findFirst: { diff --git a/packages/effect/src/Redactable.ts b/packages/effect/src/Redactable.ts index 23aeb2605..b02eb30f2 100644 --- a/packages/effect/src/Redactable.ts +++ b/packages/effect/src/Redactable.ts @@ -29,7 +29,7 @@ import { hasProperty } from "./Predicate.ts" * * **Example** (Masking an API key) * - * ```ts + * ```ts import.meta.vitest * import { Context, Redactable } from "effect" * * class ApiKey { @@ -39,6 +39,8 @@ import { hasProperty } from "./Predicate.ts" * return this.raw.slice(0, 4) + "..." * } * } + * + * Redactable.redact(new ApiKey("secret-key")) // => "secr..." * ``` * * @see {@link Redactable} for the interface this symbol belongs to @@ -63,7 +65,7 @@ export const symbolRedactable: unique symbol = Symbol.for("~effect/Redactable") * * **Example** (Masking an API key) * - * ```ts + * ```ts import.meta.vitest * import { Context, Redactable } from "effect" * * class ApiKey { @@ -73,6 +75,8 @@ export const symbolRedactable: unique symbol = Symbol.for("~effect/Redactable") * return this.raw.slice(0, 4) + "..." * } * } + * + * Redactable.redact(new ApiKey("secret-key")) // => "secr..." * ``` * * @see {@link symbolRedactable} for the symbol key to implement @@ -159,9 +163,12 @@ export function getRedacted(redactable: Redactable): unknown { /** @internal */ export const currentFiberTypeId = "~effect/Fiber/currentFiber" +const emptyMap = new Map() const emptyContext: Context.Context = { "~effect/Context": {} as any, - mapUnsafe: new Map(), + base: emptyMap, + depth: 0, + mapUnsafe: emptyMap, pipe() { return pipeArguments(this, arguments) } diff --git a/packages/effect/src/Redacted.ts b/packages/effect/src/Redacted.ts index afb1eb698..be67a9e65 100644 --- a/packages/effect/src/Redacted.ts +++ b/packages/effect/src/Redacted.ts @@ -19,7 +19,7 @@ import type { Pipeable } from "./Pipeable.ts" import { hasProperty, isString } from "./Predicate.ts" import type { Covariant } from "./Types.ts" -const TypeId = "~effect/data/Redacted" +const TypeId = "~effect/Redacted" /** * A wrapper for sensitive values whose string, JSON, and inspection output is @@ -39,7 +39,7 @@ const TypeId = "~effect/data/Redacted" * * **Example** (Creating redacted values) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * // Create a redacted value to protect sensitive information @@ -47,6 +47,7 @@ const TypeId = "~effect/data/Redacted" * const userPassword = Redacted.make("user-password") * * // TypeScript will infer the types as Redacted + * Array.of(String(apiKey), String(userPassword)) // => ["", ""] * ``` * * @category models @@ -65,14 +66,14 @@ export interface Redacted extends Redacted.Variance, Equal.Eq * * **Example** (Using namespace utilities) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * // Use the Redacted namespace for type-level operations * const secret = Redacted.make("my-secret") * * // The namespace contains utilities for working with Redacted values - * const isRedacted = Redacted.isRedacted(secret) // true + * Redacted.isRedacted(secret) // => true * ``` * * @since 3.3.0 @@ -109,7 +110,7 @@ export declare namespace Redacted { * * **Example** (Extracting the redacted value type) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * type ApiKey = Redacted.Redacted<{ readonly token: string }> @@ -119,7 +120,7 @@ export declare namespace Redacted { * token: `${value.token}:rotated` * }) * - * console.log(rotate({ token: "secret" })) // { token: "secret:rotated" } + * rotate({ token: "secret" }) // => { token: "secret:rotated" } * ``` * * @category utility types @@ -142,17 +143,17 @@ export declare namespace Redacted { * * **Example** (Checking for redacted values) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * const secret = Redacted.make("my-secret") * const plainString = "not-secret" * - * console.log(Redacted.isRedacted(secret)) // true - * console.log(Redacted.isRedacted(plainString)) // false + * Redacted.isRedacted(secret) // => true + * Redacted.isRedacted(plainString) // => false * ``` * - * @category refinements + * @category guards * @since 3.3.0 */ export const isRedacted = (u: unknown): u is Redacted => hasProperty(u, TypeId) @@ -173,10 +174,11 @@ export const isRedacted = (u: unknown): u is Redacted => hasProperty(u, * * **Example** (Creating a redacted value) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" * * const API_KEY = Redacted.make("1234567890") + * String(API_KEY) // => "" * ``` * * @category constructors @@ -229,13 +231,12 @@ const Proto = { * * **Example** (Retrieving a redacted value) * - * ```ts + * ```ts import.meta.vitest * import { Redacted } from "effect" - * import * as assert from "node:assert" * * const API_KEY = Redacted.make("1234567890") * - * assert.equal(Redacted.value(API_KEY), "1234567890") + * Redacted.value(API_KEY) // => "1234567890" * ``` * * @category getters @@ -260,20 +261,20 @@ export const value: (self: Redacted) => T = redacted.value * * **Example** (Wiping a redacted value) * - * ```ts - * import { Redacted } from "effect" - * import * as assert from "node:assert" + * ```ts import.meta.vitest + * import { Redacted, Result } from "effect" * * const API_KEY = Redacted.make("1234567890") * - * assert.equal(Redacted.value(API_KEY), "1234567890") + * Redacted.value(API_KEY) // => "1234567890" * * Redacted.wipeUnsafe(API_KEY) * - * assert.throws( - * () => Redacted.value(API_KEY), - * new Error("Unable to get redacted value") - * ) + * const failure = Result.try({ + * try: () => Redacted.value(API_KEY), + * catch: (error) => (error as Error).message + * }) + * failure // => Result.fail("Unable to get redacted value") * ``` * * @category unsafe @@ -293,9 +294,8 @@ export const wipeUnsafe = (self: Redacted): boolean => redacted.redactedRe * * **Example** (Comparing redacted values) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Redacted } from "effect" - * import * as assert from "node:assert" * * const API_KEY1 = Redacted.make("1234567890") * const API_KEY2 = Redacted.make("1-34567890") @@ -303,8 +303,8 @@ export const wipeUnsafe = (self: Redacted): boolean => redacted.redactedRe * * const equivalence = Redacted.makeEquivalence(Equivalence.strictEqual()) * - * assert.equal(equivalence(API_KEY1, API_KEY2), false) - * assert.equal(equivalence(API_KEY1, API_KEY3), true) + * equivalence(API_KEY1, API_KEY2) // => false + * equivalence(API_KEY1, API_KEY3) // => true * ``` * * @category instances diff --git a/packages/effect/src/Reducer.ts b/packages/effect/src/Reducer.ts index cff3de051..6f1a6e42b 100644 --- a/packages/effect/src/Reducer.ts +++ b/packages/effect/src/Reducer.ts @@ -37,13 +37,12 @@ import type * as Combiner from "./Combiner.ts" * * **Example** (String concatenation reducer) * - * ```ts + * ```ts import.meta.vitest * import { Reducer } from "effect" * * const Concat = Reducer.make((a, b) => a + b, "") * - * console.log(Concat.combineAll(["hello", " ", "world"])) - * // Output: "hello world" + * Concat.combineAll(["hello", " ", "world"]) // => "hello world" * ``` * * @see {@link make} – create a `Reducer` from a function and initial value @@ -89,7 +88,7 @@ export interface Reducer extends Combiner.Combiner { * * **Example** (Multiplying with short-circuit) * - * ```ts + * ```ts import.meta.vitest * import { Reducer } from "effect" * * const Product = Reducer.make( @@ -105,11 +104,8 @@ export interface Reducer extends Combiner.Combiner { * } * ) * - * console.log(Product.combineAll([2, 3, 4])) - * // Output: 24 - * - * console.log(Product.combineAll([2, 0, 4])) - * // Output: 0 + * Product.combineAll([2, 3, 4]) // => 24 + * Product.combineAll([2, 0, 4]) // => 0 * ``` * * @see {@link Reducer} – the interface this creates @@ -155,16 +151,13 @@ export function make( * * **Example** (Reversing string concatenation) * - * ```ts + * ```ts import.meta.vitest * import { Reducer, String } from "effect" * * const Prepend = Reducer.flip(String.ReducerConcat) * - * console.log(Prepend.combine("a", "b")) - * // Output: "ba" - * - * console.log(Prepend.combineAll(["a", "b", "c"])) - * // Output: "cba" + * Prepend.combine("a", "b") // => "ba" + * Prepend.combineAll(["a", "b", "c"]) // => "cba" * ``` * * @see {@link make} diff --git a/packages/effect/src/Ref.ts b/packages/effect/src/Ref.ts index d3569010c..a5c783fca 100644 --- a/packages/effect/src/Ref.ts +++ b/packages/effect/src/Ref.ts @@ -35,24 +35,18 @@ const TypeId = "~effect/Ref" * * **Example** (Reading and updating a ref) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { - * // Create a ref with initial value * const counter = yield* Ref.make(0) - * - * // Read the current value * const value = yield* Ref.get(counter) - * console.log(value) // 0 - * - * // Update the value atomically * yield* Ref.update(counter, (n) => n + 1) - * - * // Read the updated value * const newValue = yield* Ref.get(counter) - * console.log(newValue) // 1 + * return [value, newValue] * }) + * + * await Effect.runPromise(program) // => [0, 1] * ``` * * @see {@link make} for creating a `Ref` @@ -85,18 +79,17 @@ export declare namespace Ref { * * **Example** (Using invariant refs) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * - * // This interface defines the invariant nature of Ref's type parameter - * // A Ref is both a producer and consumer of A * const program = Effect.gen(function*() { * const ref = yield* Ref.make(42) - * - * // Ref is invariant - it can both produce and consume numbers - * const value = yield* Ref.get(ref) // produces number - * yield* Ref.set(ref, value + 1) // consumes number + * const value = yield* Ref.get(ref) + * yield* Ref.set(ref, value + 1) + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 43 * ``` * * @category models @@ -136,18 +129,11 @@ const RefProto = { * * **Example** (Creating a ref unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Ref } from "effect" * - * // Create a ref directly without Effect * const counter = Ref.makeUnsafe(0) - * - * // Get the current value - * const value = Ref.getUnsafe(counter) - * console.log(value) // 0 - * - * // Note: This is unsafe and should be used carefully - * // Prefer Ref.make for Effect-wrapped creation + * Ref.getUnsafe(counter) // => 0 * ``` * * @category constructors @@ -168,14 +154,15 @@ export const makeUnsafe = (value: A): Ref => { * * **Example** (Creating a ref) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(42) - * const value = yield* Ref.get(ref) - * console.log(value) // 42 + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link makeUnsafe} for synchronous construction outside Effect code @@ -194,14 +181,15 @@ export const make = (value: A): Effect.Effect> => Effect.sync(() => ma * * **Example** (Getting the current value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(42) - * const value = yield* Ref.get(ref) - * console.log(value) // 42 + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 * ``` * * @see {@link set} for replacing the current value @@ -220,29 +208,29 @@ export const get = (self: Ref) => Effect.sync(() => self.ref.current) * * **Example** (Setting a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(0) * yield* Ref.set(ref, 42) - * const value = yield* Ref.get(ref) - * console.log(value) // 42 + * return yield* Ref.get(ref) * }) * - * // Using multiple operations * const program2 = Effect.gen(function*() { * const ref = yield* Ref.make(0) * yield* Ref.set(ref, 100) - * const value = yield* Ref.get(ref) - * console.log(value) // 100 + * return yield* Ref.get(ref) * }) + * + * await Effect.runPromise(program) // => 42 + * await Effect.runPromise(program2) // => 100 * ``` * * @see {@link getAndSet} for setting while returning the previous value * @see {@link setAndGet} for setting while returning the new value * - * @category setters + * @category mutations * @since 2.0.0 */ export const set = dual< @@ -259,19 +247,18 @@ export const set = dual< * * **Example** (Replacing a value atomically) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make("initial") * - * // Get current value and set new value atomically * const previous = yield* Ref.getAndSet(ref, "updated") - * console.log(previous) // "initial" - * * const current = yield* Ref.get(ref) - * console.log(current) // "updated" + * return [previous, current] * }) + * + * await Effect.runPromise(program) // => ["initial", "updated"] * ``` * * @see {@link set} for setting without returning the previous value @@ -299,19 +286,18 @@ export const getAndSet = dual< * * **Example** (Updating and returning the previous value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(10) * - * // Get current value and update it atomically * const previous = yield* Ref.getAndUpdate(counter, (n) => n * 2) - * console.log(previous) // 10 - * * const current = yield* Ref.get(counter) - * console.log(current) // 20 + * return [previous, current] * }) + * + * await Effect.runPromise(program) // => [10, 20] * ``` * * @see {@link update} for updating without returning the previous value @@ -345,32 +331,26 @@ export const getAndUpdate = dual< * * **Example** (Conditionally updating and returning the previous value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Only update if value is greater than 3 * const previous1 = yield* Ref.getAndUpdateSome( * counter, * (n) => n > 3 ? Option.some(n * 2) : Option.none() * ) - * console.log(previous1) // 5 - * * const current1 = yield* Ref.get(counter) - * console.log(current1) // 10 - * - * // Try to update again (won't update since 10 > 3 is true but let's say condition is n < 3) * const previous2 = yield* Ref.getAndUpdateSome( * counter, * (n) => n < 3 ? Option.some(n * 2) : Option.none() * ) - * console.log(previous2) // 10 - * * const current2 = yield* Ref.get(counter) - * console.log(current2) // 10 (unchanged) + * return [previous1, current1, previous2, current2] * }) + * + * await Effect.runPromise(program) // => [5, 10, 10, 10] * ``` * * @see {@link getAndUpdate} for always applying an update @@ -402,28 +382,24 @@ export const getAndUpdateSome = dual< * * **Example** (Setting and returning the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const ref = yield* Ref.make(10) * - * // Set new value and get it back in one operation * const newValue = yield* Ref.setAndGet(ref, 42) - * console.log(newValue) // 42 - * - * // Verify the ref contains the new value * const current = yield* Ref.get(ref) - * console.log(current) // 42 + * return [newValue, current] * }) * - * // Useful for sequential operations * const program2 = Effect.gen(function*() { * const counter = yield* Ref.make(0) - * - * const newValue = yield* Ref.setAndGet(counter, 20) - * console.log(newValue) // 20 + * return yield* Ref.setAndGet(counter, 20) * }) + * + * await Effect.runPromise(program) // => [42, 42] + * await Effect.runPromise(program2) // => 20 * ``` * * @category mutations @@ -450,41 +426,36 @@ export const setAndGet = dual< * * **Example** (Modifying a value atomically) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(10) * - * // Modify the ref and return some computation result * const result = yield* Ref.modify(counter, (n) => [ - * `Previous value was ${n}`, // Return value - * n * 2 // New ref value + * `Previous value was ${n}`, + * n * 2 * ]) - * - * console.log(result) // "Previous value was 10" - * * const current = yield* Ref.get(counter) - * console.log(current) // 20 + * return [result, current] * }) * - * // Example with more complex computation * const program2 = Effect.gen(function*() { * const state = yield* Ref.make({ count: 0, total: 0 }) - * - * const incremented = yield* Ref.modify(state, (s) => [ - * s.count, // Return previous count - * { count: s.count + 1, total: s.total + s.count + 1 } // New state + * return yield* Ref.modify(state, (s) => [ + * s.count, + * { count: s.count + 1, total: s.total + s.count + 1 } * ]) - * - * console.log(incremented) // 0 * }) + * + * await Effect.runPromise(program) // => ["Previous value was 10", 20] + * await Effect.runPromise(program2) // => 0 * ``` * * @see {@link updateAndGet} for returning the new stored value * @see {@link modifySome} for optionally updating while returning a separate result * - * @category setters + * @category mutations * @since 2.0.0 */ export const modify = dual< @@ -513,13 +484,12 @@ export const modify = dual< * * **Example** (Conditionally modifying a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Only modify if value is greater than 3 * const result1 = yield* Ref.modifySome( * counter, * (n) => @@ -527,13 +497,7 @@ export const modify = dual< * ? [`incremented ${n}`, Option.some(n + 10)] * : ["no change", Option.none()] * ) - * - * console.log(result1) // "incremented 5" - * * const current1 = yield* Ref.get(counter) - * console.log(current1) // 15 - * - * // Try to modify with a condition that fails * const result2 = yield* Ref.modifySome( * counter, * (n) => @@ -541,18 +505,17 @@ export const modify = dual< * ? [`decremented ${n}`, Option.some(n - 5)] * : ["no change", Option.none()] * ) - * - * console.log(result2) // "no change" - * * const current2 = yield* Ref.get(counter) - * console.log(current2) // 15 (unchanged) + * return [result1, current1, result2, current2] * }) + * + * await Effect.runPromise(program) // => ["incremented 5", 15, "no change", 15] * ``` * * @see {@link modify} for always storing a new value * @see {@link updateSome} for optional updates without a separate return value * - * @category setters + * @category mutations * @since 2.0.0 */ export const modifySome: { @@ -581,32 +544,30 @@ export const modifySome: { * * **Example** (Updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Update the value * yield* Ref.update(counter, (n) => n * 2) - * - * const value = yield* Ref.get(counter) - * console.log(value) // 10 + * return yield* Ref.get(counter) * }) * - * // Using multiple operations * const program2 = Effect.gen(function*() { * const counter = yield* Ref.make(5) * yield* Ref.update(counter, (n: number) => n + 10) - * const value = yield* Ref.get(counter) - * console.log(value) // 15 + * return yield* Ref.get(counter) * }) + * + * await Effect.runPromise(program) // => 10 + * await Effect.runPromise(program2) // => 15 * ``` * * @see {@link updateAndGet} for returning the new value * @see {@link getAndUpdate} for returning the previous value * - * @category setters + * @category mutations * @since 2.0.0 */ export const update = dual< @@ -626,20 +587,18 @@ export const update = dual< * * **Example** (Updating and returning the new value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Update and get the new value in one operation * const newValue = yield* Ref.updateAndGet(counter, (n) => n * 3) - * console.log(newValue) // 15 - * - * // Verify the ref contains the new value * const current = yield* Ref.get(counter) - * console.log(current) // 15 + * return [newValue, current] * }) + * + * await Effect.runPromise(program) // => [15, 15] * ``` * * @see {@link update} for updating without returning the new value @@ -667,37 +626,33 @@ export const updateAndGet = dual< * * **Example** (Conditionally updating a value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(5) * - * // Only update if value is even * yield* Ref.updateSome( * counter, * (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none() * ) - * - * let current = yield* Ref.get(counter) - * console.log(current) // 5 (unchanged because 5 is odd) - * - * // Set to even number and try again + * const before = yield* Ref.get(counter) * yield* Ref.set(counter, 6) * yield* Ref.updateSome( * counter, * (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none() * ) - * - * current = yield* Ref.get(counter) - * console.log(current) // 12 (updated because 6 is even) + * const after = yield* Ref.get(counter) + * return [before, after] * }) + * + * await Effect.runPromise(program) // => [5, 12] * ``` * * @see {@link update} for always applying an update * @see {@link updateSomeAndGet} for returning the resulting current value * - * @category setters + * @category mutations * @since 2.0.0 */ export const updateSome = dual< @@ -727,26 +682,24 @@ export const updateSome = dual< * * **Example** (Conditionally updating and returning the current value) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Option, Ref } from "effect" * * const program = Effect.gen(function*() { * const counter = yield* Ref.make(10) * - * // Only update if value is greater than 5 * const result1 = yield* Ref.updateSomeAndGet( * counter, * (n) => n > 5 ? Option.some(n / 2) : Option.none() * ) - * console.log(result1) // 5 (updated and returned) - * - * // Try to update again with same condition * const result2 = yield* Ref.updateSomeAndGet( * counter, * (n) => n > 5 ? Option.some(n / 2) : Option.none() * ) - * console.log(result2) // 5 (unchanged because 5 is not > 5) + * return [result1, result2] * }) + * + * await Effect.runPromise(program) // => [5, 5] * ``` * * @see {@link updateSome} for conditional updates without returning a value @@ -781,18 +734,11 @@ export const updateSomeAndGet = dual< * * **Example** (Reading a ref unsafely) * - * ```ts + * ```ts import.meta.vitest * import { Ref } from "effect" * - * // Create a ref directly * const counter = Ref.makeUnsafe(42) - * - * // Get the value synchronously - * const value = Ref.getUnsafe(counter) - * console.log(value) // 42 - * - * // Note: This is unsafe and should be used carefully - * // Prefer Ref.get for Effect-wrapped access + * Ref.getUnsafe(counter) // => 42 * ``` * * @category getters diff --git a/packages/effect/src/References.ts b/packages/effect/src/References.ts index bc7926e3e..5d5e51dd9 100644 --- a/packages/effect/src/References.ts +++ b/packages/effect/src/References.ts @@ -124,52 +124,6 @@ export { Tracer } -/** - * Context reference for controlling the current concurrency limit. Can be set to "unbounded" - * for unlimited concurrency or a specific number to limit concurrent operations. - * - * **When to use** - * - * Use to configure the default concurrency limit for operations that read - * concurrency from the current context. - * - * **Example** (Setting current concurrency) - * - * ```ts - * import { Effect, References } from "effect" - * - * const limitConcurrency = Effect.gen(function*() { - * // Get current setting - * const current = yield* References.CurrentConcurrency - * console.log(current) // "unbounded" (default) - * - * // Run with limited concurrency - * yield* Effect.provideService( - * Effect.gen(function*() { - * const limited = yield* References.CurrentConcurrency - * console.log(limited) // 10 - * }), - * References.CurrentConcurrency, - * 10 - * ) - * - * // Run with unlimited concurrency - * yield* Effect.provideService( - * Effect.gen(function*() { - * const unlimited = yield* References.CurrentConcurrency - * console.log(unlimited) // "unbounded" - * }), - * References.CurrentConcurrency, - * "unbounded" - * ) - * }) - * ``` - * - * @category references - * @since 4.0.0 - */ -export const CurrentConcurrency: Context.Reference = references.CurrentConcurrency - /** * Context reference for managing log annotations that are automatically added to all log entries. * These annotations provide contextual metadata that appears in every log message. @@ -181,23 +135,19 @@ export const CurrentConcurrency: Context.Reference = refer * * **Example** (Managing log annotations) * - * ```ts - * import { Console, Effect, References } from "effect" + * ```ts import.meta.vitest + * import { Effect, References } from "effect" * * const logAnnotationExample = Effect.gen(function*() { * // Get current annotations (empty by default) * const current = yield* References.CurrentLogAnnotations - * console.log(current) // {} + * const defaultCount = Object.keys(current).length * * // Run with custom log annotations - * yield* Effect.provideService( + * const custom = yield* Effect.provideService( * Effect.gen(function*() { * const annotations = yield* References.CurrentLogAnnotations - * console.log(annotations) // { requestId: "req-123", userId: "user-456", version: "1.0.0" } - * - * // All log entries will include these annotations - * yield* Console.log("Starting operation") - * yield* Console.info("Processing data") + * return [annotations.requestId, annotations.userId, annotations.version] * }), * References.CurrentLogAnnotations, * { @@ -208,12 +158,10 @@ export const CurrentConcurrency: Context.Reference = refer * ) * * // Run with extended annotations - * yield* Effect.provideService( + * const extended = yield* Effect.provideService( * Effect.gen(function*() { - * const extended = yield* References.CurrentLogAnnotations - * console.log(extended) // { requestId: "req-123", userId: "user-456", version: "1.0.0", operation: "data-sync", timestamp: 1234567890 } - * - * yield* Console.log("Operation completed with extended context") + * const annotations = yield* References.CurrentLogAnnotations + * return [annotations.operation, annotations.timestamp] * }), * References.CurrentLogAnnotations, * { @@ -224,7 +172,11 @@ export const CurrentConcurrency: Context.Reference = refer * timestamp: 1234567890 * } * ) + * + * return [defaultCount, custom, extended] * }) + * + * await Effect.runPromise(logAnnotationExample) // => [0, ["req-123", "user-456", "1.0.0"], ["data-sync", 1234567890]] * ``` * * @category references @@ -246,39 +198,25 @@ export const CurrentLogAnnotations: Context.Reference = [] + * const logger = Logger.make(({ logLevel }) => { + * levels.push(logLevel) + * }) * - * // Change to Error level to reduce noise - * yield* Effect.provideService( - * Effect.gen(function*() { - * const level = yield* References.CurrentLogLevel - * console.log(level) // "Error" - * yield* Console.info("This info message will be filtered out") - * yield* Console.error("This error message will be shown") - * }), - * References.CurrentLogLevel, - * "Error" + * const program = Effect.gen(function*() { + * yield* Effect.log("uses the default level") + * yield* Effect.log("uses the provided level").pipe( + * Effect.provideService(References.CurrentLogLevel, "Error") * ) * }) + * + * await Effect.runPromise(program.pipe(Effect.provide(Logger.layer([logger])))) + * levels // => ["Info", "Error"] * ``` * * @category references @@ -297,24 +235,20 @@ export const CurrentLogLevel: Context.Reference = references.CurrentLo * * **Example** (Tracking log spans) * - * ```ts - * import { Console, Effect, References } from "effect" + * ```ts import.meta.vitest + * import { Effect, References } from "effect" * * const logSpanExample = Effect.gen(function*() { * // Get current spans (empty by default) * const current = yield* References.CurrentLogSpans - * console.log(current.length) // 0 + * const defaultCount = current.length * * // Add a log span manually * const databaseConnectionStartedAt = 0 - * yield* Effect.provideService( + * const database = yield* Effect.provideService( * Effect.gen(function*() { - * // Simulate some work - * yield* Effect.sleep("100 millis") - * yield* Console.log("Database operation in progress") - * * const spans = yield* References.CurrentLogSpans - * console.log("Active spans:", spans.map(([label]) => label)) // ["database-connection"] + * return spans.map(([label]) => label) * }), * References.CurrentLogSpans, * [["database-connection", databaseConnectionStartedAt]] @@ -322,12 +256,10 @@ export const CurrentLogLevel: Context.Reference = references.CurrentLo * * // Add another span * const dataProcessingStartedAt = 100 - * yield* Effect.provideService( + * const processing = yield* Effect.provideService( * Effect.gen(function*() { * const spans = yield* References.CurrentLogSpans - * console.log("Active spans:", spans.map(([label]) => label)) // ["database-connection", "data-processing"] - * - * yield* Console.log("Multiple operations in progress") + * return spans.map(([label]) => label) * }), * References.CurrentLogSpans, * [ @@ -337,15 +269,19 @@ export const CurrentLogLevel: Context.Reference = references.CurrentLo * ) * * // Clear spans when operations complete - * yield* Effect.provideService( + * const cleared = yield* Effect.provideService( * Effect.gen(function*() { * const spans = yield* References.CurrentLogSpans - * console.log("Active spans:", spans.length) // 0 + * return spans.length * }), * References.CurrentLogSpans, * [] * ) + * + * return [defaultCount, database, processing, cleared] * }) + * + * await Effect.runPromise(logSpanExample) // => [0, ["database-connection"], ["database-connection", "data-processing"], 0] * ``` * * @category references @@ -384,47 +320,27 @@ export const CurrentStackFrame: Context.Reference = refe * * Use to filter out log entries below a severity threshold. * - * **Example** (Setting the minimum log level) - * - * ```ts - * import { Console, Effect, References } from "effect" - * - * const configureMinimumLogging = Effect.gen(function*() { - * // Get current minimum level (default is "Info") - * const current = yield* References.MinimumLogLevel - * console.log(current) // "Info" + * **Example** (Filtering logs below the minimum level) * - * // Set minimum level to Warn - Debug and Info will be filtered - * yield* Effect.provideService( - * Effect.gen(function*() { - * const minLevel = yield* References.MinimumLogLevel - * console.log(minLevel) // "Warn" - * - * // These won't be processed at all - * yield* Console.debug("Debug message") // Filtered out - * yield* Console.info("Info message") // Filtered out + * ```ts import.meta.vitest + * import { Effect, Logger, References } from "effect" * - * // These will be processed - * yield* Console.warn("Warning message") // Shown - * yield* Console.error("Error message") // Shown - * }), - * References.MinimumLogLevel, - * "Warn" - * ) - * - * // Reset to default Info level - * yield* Effect.provideService( - * Effect.gen(function*() { - * const minLevel = yield* References.MinimumLogLevel - * console.log(minLevel) // "Info" + * const levels: Array = [] + * const logger = Logger.make(({ logLevel }) => { + * levels.push(logLevel) + * }) * - * // Now info messages will be processed - * yield* Console.info("Info message") // Shown - * }), - * References.MinimumLogLevel, - * "Info" - * ) + * const program = Effect.gen(function*() { + * yield* Effect.logInfo("filtered out") + * yield* Effect.logWarning("included at the threshold") + * yield* Effect.logError("included above the threshold") * }) + * + * await Effect.runPromise(program.pipe( + * Effect.provideService(References.MinimumLogLevel, "Warn"), + * Effect.provide(Logger.layer([logger])) + * )) + * levels // => ["Warn", "Error"] * ``` * * @category references @@ -442,40 +358,31 @@ export const MinimumLogLevel: Context.Reference = references.MinimumLo * * **Example** (Toggling tracing) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References } from "effect" * * const tracingControl = Effect.gen(function*() { * // Check if tracing is enabled (default is true) * const current = yield* References.TracerEnabled - * console.log(current) // true * * // Disable tracing globally - * yield* Effect.provideService( - * Effect.gen(function*() { - * const isEnabled = yield* References.TracerEnabled - * console.log(isEnabled) // false - * - * // Spans will not be traced in this context - * yield* Effect.log("This will not be traced") - * }), + * const disabled = yield* Effect.provideService( + * References.TracerEnabled, * References.TracerEnabled, * false * ) * * // Re-enable tracing - * yield* Effect.provideService( - * Effect.gen(function*() { - * const isEnabled = yield* References.TracerEnabled - * console.log(isEnabled) // true - * - * // All subsequent spans will be traced - * yield* Effect.log("This will be traced") - * }), + * const enabled = yield* Effect.provideService( + * References.TracerEnabled, * References.TracerEnabled, * true * ) + * + * return [current, disabled, enabled] * }) + * + * await Effect.runPromise(tracingControl) // => [true, false, true] * ``` * * @category references @@ -493,27 +400,20 @@ export const TracerEnabled: Context.Reference = references.TracerEnable * * **Example** (Managing span annotations) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References } from "effect" * * const spanAnnotationExample = Effect.gen(function*() { * // Get current annotations (empty by default) * const current = yield* References.TracerSpanAnnotations - * console.log(current) // {} + * const defaultCount = Object.keys(current).length * * // Set global span annotations - * yield* Effect.provideService( + * const configured = yield* Effect.provideService( * Effect.gen(function*() { * // Get current annotations * const annotations = yield* References.TracerSpanAnnotations - * console.log(annotations) // { service: "user-service", version: "1.2.3", environment: "production" } - * - * // All spans created will include these annotations - * yield* Effect.gen(function*() { - * // Add more specific annotations for this span - * yield* Effect.annotateCurrentSpan("userId", "123") - * yield* Effect.log("Processing user") - * }) + * return [annotations.service, annotations.version, annotations.environment] * }), * References.TracerSpanAnnotations, * { @@ -524,15 +424,19 @@ export const TracerEnabled: Context.Reference = references.TracerEnable * ) * * // Clear annotations - * yield* Effect.provideService( + * const cleared = yield* Effect.provideService( * Effect.gen(function*() { * const annotations = yield* References.TracerSpanAnnotations - * console.log(annotations) // {} + * return Object.keys(annotations).length * }), * References.TracerSpanAnnotations, * {} * ) + * + * return [defaultCount, configured, cleared] * }) + * + * await Effect.runPromise(spanAnnotationExample) // => [0, ["user-service", "1.2.3", "production"], 0] * ``` * * @category references @@ -551,13 +455,13 @@ export const TracerSpanAnnotations: Context.Reference links.length), * References.TracerSpanLinks, * [spanLink] * ) * * // Clear links - * yield* Effect.provideService( - * Effect.gen(function*() { - * const links = yield* References.TracerSpanLinks - * console.log(links.length) // 0 - * }), + * const clearedCount = yield* Effect.provideService( + * Effect.map(References.TracerSpanLinks, (links) => links.length), * References.TracerSpanLinks, * [] * ) + * + * return [defaultCount, configuredCount, clearedCount] * }) + * + * await Effect.runPromise(spanLinksExample) // => [0, 1, 0] * ``` * * @category references @@ -619,36 +514,31 @@ export const TracerSpanLinks: Context.Reference> = refer * * **Example** (Toggling trace timing) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References } from "effect" * * const tracingControl = Effect.gen(function*() { * // Check if trace timing is enabled (default is true) * const current = yield* References.TracerTimingEnabled - * console.log(current) // true * * // Disable trace timing globally - * yield* Effect.provideService( - * Effect.gen(function*() { - * // Spans will not having timing information in this context - * const isEnabled = yield* References.TracerTimingEnabled - * console.log(isEnabled) // false - * }), + * const disabled = yield* Effect.provideService( + * References.TracerTimingEnabled, * References.TracerTimingEnabled, * false * ) * * // Re-enable trace timing - * yield* Effect.provideService( - * Effect.gen(function*() { - * // Spans will have timing information in this context - * const isEnabled = yield* References.TracerTimingEnabled - * console.log(isEnabled) // true - * }), + * const enabled = yield* Effect.provideService( + * References.TracerTimingEnabled, * References.TracerTimingEnabled, * true * ) + * + * return [current, disabled, enabled] * }) + * + * await Effect.runPromise(tracingControl) // => [true, false, true] * ``` * * @category references @@ -756,27 +646,25 @@ export { * * **Example** (Providing a custom scheduler) * - * ```ts + * ```ts import.meta.vitest * import { Effect, References, Scheduler } from "effect" * * const customScheduling = Effect.gen(function*() { * // Get current scheduler (default is MixedScheduler) * const current = yield* References.Scheduler - * console.log(current) // MixedScheduler instance + * const isDefaultMixed = current instanceof Scheduler.MixedScheduler * * // Use a custom scheduler - * yield* Effect.provideService( - * Effect.gen(function*() { - * const scheduler = yield* References.Scheduler - * console.log(scheduler) // Custom scheduler instance - * - * // Effects will use the custom scheduler in this context - * yield* Effect.log("Using custom scheduler") - * }), + * const isCustomMixed = yield* Effect.provideService( + * Effect.map(References.Scheduler, (scheduler) => scheduler instanceof Scheduler.MixedScheduler), * References.Scheduler, * new Scheduler.MixedScheduler() * ) + * + * return [isDefaultMixed, isCustomMixed] * }) + * + * await Effect.runPromise(customScheduling) // => [true, true] * ``` * * @category references diff --git a/packages/effect/src/RegExp.ts b/packages/effect/src/RegExp.ts index 0123914f8..c1c97ea3e 100644 --- a/packages/effect/src/RegExp.ts +++ b/packages/effect/src/RegExp.ts @@ -22,15 +22,13 @@ import * as predicate from "./Predicate.ts" * * **Example** (Creating a regular expression) * - * ```ts + * ```ts import.meta.vitest * import { RegExp } from "effect" * - * // Create a regular expression using Effect's RegExp constructor * const pattern = new RegExp.RegExp("hello", "i") - * - * // Test the pattern - * console.log(pattern.test("Hello World")) // true - * console.log(pattern.test("goodbye")) // false + * pattern // => /hello/i + * pattern.test("Hello World") // => true + * pattern.test("goodbye") // => false * ``` * * @category constructors @@ -47,12 +45,11 @@ export const RegExp = globalThis.RegExp * * **Example** (Checking for regular expressions) * - * ```ts + * ```ts import.meta.vitest * import { RegExp } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(RegExp.isRegExp(/a/), true) - * assert.deepStrictEqual(RegExp.isRegExp("a"), false) + * RegExp.isRegExp(/a/) // => true + * RegExp.isRegExp("a") // => false * ``` * * @category guards @@ -69,14 +66,13 @@ export const isRegExp: (input: unknown) => input is RegExp = predicate.isRegExp * * **Example** (Escaping a pattern string) * - * ```ts + * ```ts import.meta.vitest * import { RegExp } from "effect" - * import * as assert from "node:assert" * - * assert.deepStrictEqual(RegExp.escape("a*b"), "a\\*b") + * RegExp.escape("a*b") // => "a\\*b" * ``` * - * @category RegExp + * @category transforming * @since 2.0.0 */ export const escape = (string: string): string => string.replace(/[/\\^$*+?.()|[\]{}]/g, "\\$&") diff --git a/packages/effect/src/Request.ts b/packages/effect/src/Request.ts index 5eb39ffb0..5ccb1032e 100644 --- a/packages/effect/src/Request.ts +++ b/packages/effect/src/Request.ts @@ -17,6 +17,7 @@ import type * as Exit from "./Exit.ts" import { dual } from "./Function.ts" import * as core from "./internal/core.ts" import * as internalEffect from "./internal/effect.ts" +import * as InternalRecord from "./internal/record.ts" import { hasProperty } from "./Predicate.ts" import type * as Types from "./Types.ts" @@ -28,7 +29,7 @@ const TypeId = "~effect/Request" * * **Example** (Defining typed requests) * - * ```ts + * ```ts import.meta.vitest * import type { Request } from "effect" * * // Define a request that fetches a user by ID @@ -41,6 +42,7 @@ const TypeId = "~effect/Request" * interface GetAllUsers extends Request.Request, Error> { * readonly _tag: "GetAllUsers" * } + * * ``` * * @category models @@ -63,7 +65,7 @@ export interface Request extends Variance @@ -98,7 +100,7 @@ export interface Variance { * * **Example** (Using generated request constructors) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * interface GetUser extends Request.Request { @@ -108,7 +110,10 @@ export interface Variance { * * // Constructor type is used internally by Request.of() and Request.tagged() * const GetUser = Request.tagged("GetUser") - * const userRequest = GetUser({ id: 123 }) + * const request = GetUser({ id: 123 }) + * + * request._tag // => "GetUser" + * request.id // => 123 * ``` * * @category models @@ -123,7 +128,7 @@ export interface Constructor, T extends keyof R * * **Example** (Extracting a request error type) * - * ```ts + * ```ts import.meta.vitest * import type { Request } from "effect" * * interface GetUser extends Request.Request { @@ -132,6 +137,7 @@ export interface Constructor, T extends keyof R * * // Extract the error type from a Request using the utility * type UserError = Request.Error // Error + * * ``` * * @category utility types @@ -144,7 +150,7 @@ export type Error> = [T] extends [Request { @@ -154,6 +160,7 @@ export type Error> = [T] extends [Request // string + * * ``` * * @category utility types @@ -176,7 +183,7 @@ export type Services> = [T] extends [Request { @@ -186,6 +193,7 @@ export type Services> = [T] extends [Request // Exit.Exit + * * ``` * * @category utility types @@ -226,7 +234,7 @@ export const RequestPrototype: Request = { * * **Example** (Checking request values) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * declare const User: unique symbol @@ -241,8 +249,8 @@ export const RequestPrototype: Request = { * const GetUser = Request.tagged("GetUser") * * const request = GetUser({ id: "123" }) - * console.log(Request.isRequest(request)) // true - * console.log(Request.isRequest("not a request")) // false + * Request.isRequest(request) // => true + * Request.isRequest("not a request") // => false * ``` * * @category guards @@ -255,7 +263,7 @@ export const isRequest = (u: unknown): u is Request = * * **Example** (Creating untagged request constructors) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * declare const UserProfile: unique symbol @@ -274,13 +282,16 @@ export const isRequest = (u: unknown): u is Request = * id: "user-123", * includeSettings: true * }) + * + * request.id // => "user-123" + * request.includeSettings // => true * ``` * * @category constructors * @since 2.0.0 */ export const of = >(): Constructor => (args) => - Object.assign(Object.create(RequestPrototype), args) + Object.setPrototypeOf({ ...(args as R) }, RequestPrototype) /** * Creates a constructor function for a tagged Request type. The tag is automatically @@ -288,7 +299,7 @@ export const of = >(): Constructor => (args) * * **Example** (Creating tagged request constructors) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * declare const User: unique symbol @@ -317,8 +328,7 @@ export const of = >(): Constructor => (args) * const postRequest = GetPost({ id: "post-456" }) * * // _tag is automatically set - * console.log(userRequest._tag) // "GetUser" - * console.log(postRequest._tag) // "GetPost" + * Array.of(userRequest._tag, postRequest._tag) // => ["GetUser", "GetPost"] * ``` * * @category constructors @@ -328,10 +338,7 @@ export const tagged = & { _tag: string }>( tag: R["_tag"] ): Constructor => (args) => { - const request = Object.create(RequestPrototype) - if (args) Object.assign(request, args) - request._tag = tag - return request + return Object.setPrototypeOf({ ...(args as R), _tag: tag }, RequestPrototype) } /** @@ -344,7 +351,7 @@ export const tagged = & { _tag: string }>( * * **Example** (Defining request classes) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * class GetUser extends Request.Class<{ id: number }, string, Error> { @@ -354,7 +361,7 @@ export const tagged = & { _tag: string }>( * } * * const getUserRequest = new GetUser(123) - * console.log(getUserRequest.id) // 123 + * getUserRequest.id // => 123 * ``` * * @category constructors @@ -364,9 +371,9 @@ export const Class: new, Success, Error = never, C args: Types.Equals>, {}> extends true ? void : { readonly [P in keyof A as P extends keyof Request ? never : P]: A[P] } ) => Request & Readonly = (function() { - function Class(this: any, args: any) { + function Class(this: object, args: object | undefined) { if (args) { - Object.assign(this, args) + InternalRecord.assignProperties(this, args) } } Class.prototype = RequestPrototype @@ -383,7 +390,7 @@ export const Class: new, Success, Error = never, C * * **Example** (Defining tagged request classes) * - * ```ts + * ```ts import.meta.vitest * import { Request } from "effect" * * class GetUserById @@ -391,8 +398,9 @@ export const Class: new, Success, Error = never, C * {} * * const request = new GetUserById({ id: 123 }) - * console.log(request._tag) // "GetUserById" - * console.log(request.id) // 123 + * + * request._tag // => "GetUserById" + * request.id // => 123 * ``` * * @category constructors @@ -554,7 +562,7 @@ export const succeed: { * an `uninterruptible` flag used by batching and caching internals, and the * `completeUnsafe` callback used by resolvers to supply the final `Exit`. * - * @category entry + * @category models * @since 2.0.0 */ export interface Entry { @@ -580,7 +588,7 @@ export interface Entry { * most application code receives entries from a `RequestResolver` instead of * constructing them directly. * - * @category entry + * @category constructors * @since 2.0.0 */ export const makeEntry = (options: { diff --git a/packages/effect/src/RequestResolver.ts b/packages/effect/src/RequestResolver.ts index 33cce7167..951c5f715 100644 --- a/packages/effect/src/RequestResolver.ts +++ b/packages/effect/src/RequestResolver.ts @@ -17,7 +17,8 @@ import type * as Duration from "./Duration.ts" import * as Effect from "./Effect.ts" import * as Exit from "./Exit.ts" import { constTrue, dual, identity } from "./Function.ts" -import { exitFail, exitSucceed } from "./internal/core.ts" +import { exitSucceed } from "./internal/core.ts" +import * as Count from "./internal/count.ts" import * as effect from "./internal/effect.ts" import * as internal from "./internal/request.ts" import * as Iterable from "./Iterable.ts" @@ -52,14 +53,14 @@ const TypeId = "~effect/RequestResolver" * * **Example** (Defining a request resolver) * - * ```ts - * import { Effect, Exit, RequestResolver } from "effect" - * import type { Request } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly id: number * } + * const GetUserRequest = Request.tagged("GetUserRequest") * * // In practice, you would typically use RequestResolver.make() instead * const resolver = RequestResolver.make((entries) => @@ -69,6 +70,9 @@ const TypeId = "~effect/RequestResolver" * } * }) * ) + * + * const program = Effect.request(GetUserRequest({ id: 1 }), resolver) + * await Effect.runPromise(program) // => "User 1" * ``` * * @category models @@ -202,7 +206,7 @@ const defaultKey = (_request: unknown): unknown => defaultKeyObject * * **Example** (Creating a request resolver) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * // Define a request type @@ -224,6 +228,7 @@ const defaultKey = (_request: unknown): unknown => defaultKeyObject * * // Use the resolver to handle requests * const getUserEffect = Effect.request(GetUserRequest({ id: 123 }), UserResolver) + * await Effect.runPromise(getUserEffect) // => "User 123" * ``` * * @category constructors @@ -248,7 +253,7 @@ export const make = ( * * **Example** (Grouping requests by key) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserByRole extends Request.Request { @@ -258,12 +263,14 @@ export const make = ( * } * const GetUserByRole = Request.tagged("GetUserByRole") * + * const batches: Array<[role: string, size: number]> = [] + * * // Group requests by role for efficient batch processing * const UserByRoleResolver = RequestResolver.makeGrouped({ * key: ({ request }) => request.role, * resolver: (entries, role) => * Effect.sync(() => { - * console.log(`Processing ${entries.length} requests for role: ${role}`) + * batches.push([role, entries.length]) * for (const entry of entries) { * entry.completeUnsafe( * Exit.succeed(`User ${entry.request.id} with role ${role}`) @@ -271,6 +278,15 @@ export const make = ( * } * }) * }) + * + * const program = Effect.all([ + * Effect.request(GetUserByRole({ role: "admin", id: 1 }), UserByRoleResolver), + * Effect.request(GetUserByRole({ role: "admin", id: 2 }), UserByRoleResolver) + * ] as const, { concurrency: "unbounded" }) + * const result = await Effect.runPromise(program) + * + * batches // => [["admin", 2]] + * result // => ["User 1 with role admin", "User 2 with role admin"] * ``` * * @category constructors @@ -305,7 +321,7 @@ const hashGroupKey = (get: (entry: Request.Entry) => K) => { * * **Example** (Creating a resolver from a pure function) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetSquareRequest extends Request.Request { @@ -324,7 +340,7 @@ const hashGroupKey = (get: (entry: Request.Entry) => K) => { * GetSquareRequest({ value: 5 }), * SquareResolver * ) - * // Will resolve to 25 + * await Effect.runPromise(getSquareEffect) // => 25 * ``` * * @category constructors @@ -350,7 +366,7 @@ export const fromFunction = ( * * **Example** (Batching pure request handling) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetDoubleRequest extends Request.Request { @@ -368,7 +384,8 @@ export const fromFunction = ( * const effects = [1, 2, 3].map((value) => * Effect.request(GetDoubleRequest({ value }), DoubleResolver) * ) - * const batchedEffect = Effect.all(effects) // [2, 4, 6] + * const batchedEffect = Effect.all(effects) + * await Effect.runPromise(batchedEffect) // => [2, 4, 6] * ``` * * @category constructors @@ -393,7 +410,7 @@ export const fromFunctionBatched = ( * * **Example** (Creating a resolver from an effectful function) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetUserFromAPIRequest extends Request.Request { @@ -406,13 +423,7 @@ export const fromFunctionBatched = ( * * // Create a resolver that uses effects (like HTTP calls) * const UserAPIResolver = RequestResolver.fromEffect( - * (entry) => - * Effect.gen(function*() { - * // Simulate an API call - * yield* Effect.sleep("100 millis") - * // Just return the result without error handling for simplicity - * return `User ${entry.request.id} from API` - * }) + * (entry) => Effect.succeed(`User ${entry.request.id} from API`) * ) * * // Usage @@ -420,6 +431,7 @@ export const fromFunctionBatched = ( * GetUserFromAPIRequest({ id: 123 }), * UserAPIResolver * ) + * await Effect.runPromise(getUserEffect) // => "User 123 from API" * ``` * * @category constructors @@ -456,9 +468,8 @@ export const fromEffect = ( * * **Example** (Handling tagged request batches) * - * ```ts - * import { Effect, RequestResolver } from "effect" - * import type { Request } from "effect" + * ```ts import.meta.vitest + * import { Effect, Request, RequestResolver } from "effect" * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" @@ -471,6 +482,8 @@ export const fromEffect = ( * } * * type MyRequest = GetUser | GetPost + * const GetUser = Request.tagged("GetUser") + * const GetPost = Request.tagged("GetPost") * * // Create a resolver that handles different request types * const MyResolver = RequestResolver.fromEffectTagged()({ @@ -479,6 +492,12 @@ export const fromEffect = ( * GetPost: (requests) => * Effect.succeed(requests.map((req) => `Post ${req.request.id}`)) * }) + * + * const program = Effect.all([ + * Effect.request(GetUser({ id: 1 }), MyResolver), + * Effect.request(GetPost({ id: 2 }), MyResolver) + * ] as const) + * await Effect.runPromise(program) // => ["User 1", "Post 2"] * ``` * * @category constructors @@ -510,17 +529,18 @@ export const fromEffectTagged = - Effect.matchCause((fns[tag] as any)(requests) as Effect.Effect, unknown, unknown>, { + Effect.matchCause((fns[tag] as any)(requests) as Effect.Effect, unknown, unknown>, { onFailure: (cause) => { for (let i = 0; i < requests.length; i++) { const entry = requests[i] - entry.completeUnsafe(exitFail(cause) as any) + entry.completeUnsafe(Exit.failCause(cause) as any) } }, onSuccess: (res) => { - for (let i = 0; i < res.length; i++) { - const entry = requests[i] - entry.completeUnsafe(exitSucceed(res[i]) as any) + let i = 0 + for (const result of res) { + const entry = requests[i++] + entry.completeUnsafe(exitSucceed(result) as any) } } }), @@ -534,7 +554,7 @@ export const fromEffectTagged = { @@ -550,17 +570,21 @@ export const fromEffectTagged = { + * delayRan = true * }) * ) + * + * await Effect.runPromise(resolverWithCustomDelay.delay) + * Array.of(delayRan, RequestResolver.isRequestResolver(resolverWithCustomDelay)) // => [true, true] * ``` * - * @category delay + * @category delays & timeouts * @since 4.0.0 */ export const setDelayEffect: { @@ -580,7 +604,7 @@ export const setDelayEffect: { * * **Example** (Setting a batch delay) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -599,11 +623,11 @@ export const setDelayEffect: { * // Add a 100ms delay to batch requests together * const delayedResolver = RequestResolver.setDelay(resolver, "100 millis") * - * // Can also use number for milliseconds - * const delayedResolver2 = RequestResolver.setDelay(resolver, 100) + * const program = Effect.request(GetDataRequest(), delayedResolver) + * await Effect.runPromise(program) // => "data" * ``` * - * @category delay + * @category delays & timeouts * @since 4.0.0 */ export const setDelay: { @@ -623,7 +647,7 @@ export const setDelay: { * * **Example** (Running effects around request resolution) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -631,6 +655,8 @@ export const setDelay: { * } * const GetDataRequest = Request.tagged("GetDataRequest") * + * const events: Array = [] + * * const resolver = RequestResolver.make((entries) => * Effect.sync(() => { * for (const entry of entries) { @@ -644,16 +670,20 @@ export const setDelay: { * resolver, * (entries) => * Effect.gen(function*() { - * yield* Effect.log(`Starting batch of ${entries.length} requests`) + * events.push(`Starting batch of ${entries.length} requests`) * return entries.length * }), * (entries, initialSize) => - * Effect.gen(function*() { - * yield* Effect.log( - * `Batch completed with ${entries.length} requests (started with ${initialSize})` - * ) + * Effect.sync(() => { + * events.push(`Batch completed with ${entries.length} requests (started with ${initialSize})`) * }) * ) + * + * const program = Effect.request(GetDataRequest(), resolverWithAround) + * const result = await Effect.runPromise(program) + * + * events // => ["Starting batch of 1 requests", "Batch completed with 1 requests (started with 1)"] + * result // => "data" * ``` * * @category combinators @@ -712,11 +742,12 @@ export const never: RequestResolver = make(() => Effect.never) * * When more than `n` requests are waiting for the same resolver and batch key, * the current batch is run and additional requests are collected into later - * batches. + * batches. Finite fractional values of `n` are rounded down. `NaN` and + * non-positive values are treated as `1` so that every batch remains non-empty. * * **Example** (Limiting parallel request batches) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -725,9 +756,11 @@ export const never: RequestResolver = make(() => Effect.never) * } * const GetDataRequest = Request.tagged("GetDataRequest") * + * const batchSizes: Array = [] + * * const resolver = RequestResolver.make((entries) => * Effect.sync(() => { - * console.log(`Processing batch of ${entries.length} requests`) + * batchSizes.push(entries.length) * for (const entry of entries) { * entry.completeUnsafe(Exit.succeed(`data-${entry.request.id}`)) * } @@ -742,6 +775,13 @@ export const never: RequestResolver = make(() => Effect.never) * { length: 12 }, * (_, i) => Effect.request(GetDataRequest({ id: i }), limitedResolver) * ) + * + * const result = await Effect.runPromise(Effect.all(requests, { concurrency: "unbounded" })) + * batchSizes // => [5, 5, 2] + * + * result.length // => 12 + * + * Array.of(result[0], result[11]) // => ["data-0", "data-11"] * ``` * * @category combinators @@ -750,11 +790,13 @@ export const never: RequestResolver = make(() => Effect.never) export const batchN: { (n: number): (self: RequestResolver) => RequestResolver (self: RequestResolver, n: number): RequestResolver -} = dual(2, (self: RequestResolver, n: number): RequestResolver => - makeWith({ +} = dual(2, (self: RequestResolver, n: number): RequestResolver => { + const size = Count.normalizeNonEmpty(n) + return makeWith({ ...self, - collectWhile: (requests) => requests.size < n - })) + collectWhile: (requests) => requests.size < size + }) +}) /** * Transforms a request resolver by grouping requests using the specified key @@ -762,7 +804,7 @@ export const batchN: { * * **Example** (Grouping resolver requests) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { @@ -772,9 +814,11 @@ export const batchN: { * } * const GetUserRequest = Request.tagged("GetUserRequest") * + * const batchSizes: Array = [] + * * const resolver = RequestResolver.make((entries) => * Effect.sync(() => { - * console.log(`Processing ${entries.length} users`) + * batchSizes.push(entries.length) * for (const entry of entries) { * entry.completeUnsafe(Exit.succeed(`User ${entry.request.userId}`)) * } @@ -802,6 +846,13 @@ export const batchN: { * groupedResolver * ) * ] + * + * const result = await Effect.runPromise(Effect.all(requests, { concurrency: "unbounded" })) + * batchSizes.sort() + * + * batchSizes // => [1, 2] + * + * result // => ["User 1", "User 2", "User 3"] * ``` * * @category combinators @@ -830,7 +881,7 @@ export const grouped: { * * **Example** (Racing request resolvers) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -861,6 +912,8 @@ export const grouped: { * * // Race resolvers - will use whichever completes first * const racingResolver = RequestResolver.race(fastResolver, slowResolver) + * const program = Effect.request(GetDataRequest({ id: 1 }), racingResolver) + * await Effect.runPromise(program) // => "fast-1" * ``` * * @category combinators @@ -888,7 +941,7 @@ export const race: { * * **Example** (Adding a tracing span) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { @@ -919,6 +972,7 @@ export const race: { * * // Spans will automatically include batch size and request links * const effect = Effect.request(GetDataRequest({ id: 123 }), tracedResolver) + * await Effect.runPromise(effect) // => "data-123" * ``` * * @category combinators @@ -1065,8 +1119,9 @@ export const asCache: { * * **Gotchas** * - * Entries do not expire by time, and completed failures are cached the same as - * successes. Request equality controls cache hits. + * Entries do not expire by time, and completed failures without interruptions + * are cached the same as successes. Results containing interruptions are not + * cached. Request equality controls cache hits. * * @see {@link asCache} for exposing the resolver as a `Cache` with time-to-live and service lookup controls * @see {@link persisted} for backing persistable requests with the configured persistence store @@ -1114,7 +1169,16 @@ export const withCache: { MutableHashMap.set(cache, entry.request, cached) const prevComplete = entry.completeUnsafe entry.completeUnsafe = function(exit) { - cached.exit = exit as any + if (Exit.hasInterrupts(exit)) { + if (cached.exit === undefined) { + const current = MutableHashMap.get(cache, entry.request) + if (current._tag === "Some" && current.value === cached) { + MutableHashMap.remove(cache, entry.request) + } + } + } else { + cached.exit = exit as any + } prevComplete(exit) } return true @@ -1159,7 +1223,7 @@ export const withCache: { * @see {@link withCache} for in-memory resolver caching that does not require persistable request values or a persistence store * @see {@link asCache} for exposing resolver results through a `Cache` instead of returning another resolver * - * @category Persistence + * @category caching * @since 4.0.0 */ export const persisted: { @@ -1212,6 +1276,7 @@ export const persisted: { >) const leftover: Array> = [] const toPersist = new Map>() + const completed = new Set>() for (let i = 0; i < results.length; i++) { const entry = entries[i] const exit = results[i] @@ -1221,6 +1286,7 @@ export const persisted: { ) { const prevComplete = entry.completeUnsafe entry.completeUnsafe = function(exit) { + completed.add(entry) toPersist.set(entry.request, exit as any) prevComplete(exit) } @@ -1232,10 +1298,10 @@ export const persisted: { if (!Arr.isArrayNonEmpty(leftover)) { return } - yield* Effect.catchCause(self.runAll(leftover, key), (cause) => { + yield* Effect.catchCause(Effect.suspend(() => self.runAll(leftover, key)), (cause) => { for (let i = 0; i < leftover.length; i++) { const entry = leftover[i] - if (!toPersist.has(entry.request)) continue + if (completed.has(entry)) continue entry.completeUnsafe(Exit.failCause(cause) as any) } return Effect.void diff --git a/packages/effect/src/Result.ts b/packages/effect/src/Result.ts index c1c105247..262a9493a 100644 --- a/packages/effect/src/Result.ts +++ b/packages/effect/src/Result.ts @@ -17,6 +17,7 @@ import type { TypeLambda } from "./HKT.ts" import type { Inspectable } from "./Inspectable.ts" import * as doNotation from "./internal/doNotation.ts" import * as option_ from "./internal/option.ts" +import * as InternalRecord from "./internal/record.ts" import * as result from "./internal/result.ts" import type { Option } from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" @@ -26,7 +27,7 @@ import type { Covariant, NoInfer, NotFunction } from "./Types.ts" import type * as Unify from "./Unify.ts" import type * as Gen from "./Utils.ts" -const TypeId = "~effect/data/Result" +const TypeId = "~effect/Result" /** * A value that is either `Success` or `Failure`. @@ -46,18 +47,13 @@ const TypeId = "~effect/data/Result" * * **Example** (Creating and matching a Result) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const success = Result.succeed(42) - * const failure = Result.fail("something went wrong") - * - * const message = Result.match(success, { + * Result.match(Result.succeed(42), { * onSuccess: (value) => `Success: ${value}`, * onFailure: (error) => `Error: ${error}` - * }) - * console.log(message) - * // Output: "Success: 42" + * }) // => "Success: 42" * ``` * * @see {@link succeed} / {@link fail} to create values @@ -80,14 +76,13 @@ export type Result = Success | Failure * * **Example** (Accessing the failure value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const failure = Result.fail("Network error") * * if (Result.isFailure(failure)) { - * console.log(failure.failure) - * // Output: "Network error" + * failure.failure // => "Network error" * } * ``` * @@ -143,14 +138,13 @@ export interface ResultIterator> { * * **Example** (Accessing the success value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const success = Result.succeed(42) * * if (Result.isSuccess(success)) { - * console.log(success.success) - * // Output: 42 + * success.success // => 42 * } * ``` * @@ -212,7 +206,7 @@ export interface ResultUnifyIgnore {} * (e.g., `map`, `flatMap` abstractions). You typically do not need to * reference this directly. * - * @category type lambdas + * @category utility types * @since 4.0.0 */ export interface ResultTypeLambda extends TypeLambda { @@ -225,8 +219,8 @@ export interface ResultTypeLambda extends TypeLambda { * * **Example** (Extracting inner types) * - * ```ts - * import type { Result } from "effect" + * ```ts import.meta.vitest + * import { Result } from "effect" * * type R = Result.Result * @@ -235,6 +229,9 @@ export interface ResultTypeLambda extends TypeLambda { * * // string * type E = Result.Result.Failure + * + * const success: A = 42 + * const failure: E = "error" * ``` * * @since 4.0.0 @@ -243,14 +240,14 @@ export declare namespace Result { /** * Extracts the failure type `E` from `Result`. * - * @category Type Level + * @category utility types * @since 4.0.0 */ export type Failure> = [T] extends [Result] ? _E : never /** * Extracts the success type `A` from `Result`. * - * @category Type Level + * @category utility types * @since 4.0.0 */ export type Success> = [T] extends [Result] ? _A : never @@ -266,13 +263,10 @@ export declare namespace Result { * * **Example** (Wrapping a value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.succeed(42) - * - * console.log(Result.isSuccess(result)) - * // Output: true + * Result.succeed(42) // => Result.succeed(42) * ``` * * @see {@link fail} to create a Failure @@ -296,13 +290,10 @@ export const succeed: (right: A) => Result = result.succeed * * **Example** (Creating a failure) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.fail("Something went wrong") - * - * console.log(Result.isFailure(result)) - * // Output: true + * Result.fail("Something went wrong") // => Result.fail("Something went wrong") * ``` * * @see {@link succeed} to create a Success @@ -330,13 +321,10 @@ export { * * **Example** (Referencing void results) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result: Result.Result = Result.void - * - * console.log(Result.isSuccess(result)) - * // Output: true + * const result: Result.Result = Result.void // => Result.succeed(undefined) * ``` * * @see {@link succeed} to create a Success with a specific value @@ -363,13 +351,10 @@ export { * * **Example** (Failing without a payload) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.failVoid - * - * console.log(Result.isFailure(result)) - * // Output: true + * Result.failVoid // => Result.fail(undefined) * ``` * * @see {@link fail} to create a Failure with a specific value @@ -396,14 +381,12 @@ export const failVoid: Result = fail(void 0) * * **Example** (Handling nullable values) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.fromNullishOr(1, () => "fallback")) - * // Output: { _tag: "Success", success: 1, ... } + * Result.fromNullishOr(1, () => "fallback") // => Result.succeed(1) * - * console.log(Result.fromNullishOr(null, () => "fallback")) - * // Output: { _tag: "Failure", failure: "fallback", ... } + * Result.fromNullishOr(null, () => "fallback") // => Result.fail("fallback") * ``` * * @see {@link fromOption} to convert from an Option @@ -437,16 +420,12 @@ export const fromNullishOr: { * * **Example** (Converting an Option to a Result) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * const some = Result.fromOption(Option.some(1), () => "missing") - * console.log(some) - * // Output: { _tag: "Success", success: 1, ... } + * Result.fromOption(Option.some(1), () => "missing") // => Result.succeed(1) * - * const none = Result.fromOption(Option.none(), () => "missing") - * console.log(none) - * // Output: { _tag: "Failure", failure: "missing", ... } + * Result.fromOption(Option.none(), () => "missing") // => Result.fail("missing") * ``` * * @see {@link getSuccess} to extract the success value as an Option @@ -503,19 +482,16 @@ export { * * **Example** (Catching JSON parse errors) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const ok = Result.try(() => JSON.parse('{"name": "Alice"}')) - * console.log(ok) - * // Output: { _tag: "Success", success: { name: "Alice" }, ... } + * Result.try(() => JSON.parse('{"name": "Alice"}')) // => Result.succeed({ name: "Alice" }) * * const err = Result.try({ * try: () => JSON.parse("not json"), * catch: (e) => `Parse failed: ${e}` * }) - * console.log(Result.isFailure(err)) - * // Output: true + * Result.isFailure(err) // => true * ``` * * @see {@link succeed} / {@link fail} for direct construction @@ -541,14 +517,12 @@ export { * * **Example** (Checking if a value is a Result) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.isResult(Result.succeed(1))) - * // Output: true + * Result.isResult(Result.succeed(1)) // => true * - * console.log(Result.isResult({ value: 1 })) - * // Output: false + * Result.isResult({ value: 1 }) // => false * ``` * * @see {@link isSuccess} / {@link isFailure} to narrow to a specific variant @@ -572,14 +546,13 @@ export const isResult: (input: unknown) => input is Result = r * * **Example** (Narrowing to failure) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const result = Result.fail("oops") * * if (Result.isFailure(result)) { - * console.log(result.failure) - * // Output: "oops" + * result.failure // => "oops" * } * ``` * @@ -605,14 +578,13 @@ export const isFailure: (self: Result) => self is Failure = re * * **Example** (Narrowing to success) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * const result = Result.succeed(42) * * if (Result.isSuccess(result)) { - * console.log(result.success) - * // Output: 42 + * result.success // => 42 * } * ``` * @@ -639,14 +611,12 @@ export const isSuccess: (self: Result) => self is Success = re * * **Example** (Extracting the success as an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Result.getSuccess(Result.succeed("ok"))) - * // Output: { _tag: "Some", value: "ok" } + * Result.getSuccess(Result.succeed("ok")) // => Option.some("ok") * - * console.log(Result.getSuccess(Result.fail("err"))) - * // Output: { _tag: "None" } + * Result.getSuccess(Result.fail("err")) // => Option.none() * ``` * * @see {@link getFailure} to extract the error instead @@ -672,14 +642,12 @@ export const getSuccess: (self: Result) => Option = result.getSuc * * **Example** (Extracting the failure as an Option) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * console.log(Result.getFailure(Result.succeed("ok"))) - * // Output: { _tag: "None" } + * Result.getFailure(Result.succeed("ok")) // => Option.none() * - * console.log(Result.getFailure(Result.fail("err"))) - * // Output: { _tag: "Some", value: "err" } + * Result.getFailure(Result.fail("err")) // => Option.some("err") * ``` * * @see {@link getSuccess} to extract the success instead @@ -701,7 +669,7 @@ export const getFailure: (self: Result) => Option = result.getFai * * **Example** (Comparing Results for equality) * - * ```ts + * ```ts import.meta.vitest * import { Equivalence, Result } from "effect" * * const eq = Result.makeEquivalence( @@ -709,11 +677,9 @@ export const getFailure: (self: Result) => Option = result.getFai * Equivalence.strictEqual() * ) * - * console.log(eq(Result.succeed(1), Result.succeed(1))) - * // Output: true + * eq(Result.succeed(1), Result.succeed(1)) // => true * - * console.log(eq(Result.succeed(1), Result.fail("x"))) - * // Output: false + * eq(Result.succeed(1), Result.fail("x")) // => false * ``` * * @category instances @@ -744,18 +710,16 @@ export const makeEquivalence = ( * * **Example** (Mapping both channels) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(1), * Result.mapBoth({ * onSuccess: (n) => n + 1, * onFailure: (e) => `Error: ${e}` * }) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 2, ... } + * ) // => Result.succeed(2) * ``` * * @see {@link map} to transform only the success value @@ -796,15 +760,13 @@ export const mapBoth: { * * **Example** (Adding context to an error) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.fail("not found"), * Result.mapError((e) => `Error: ${e}`) - * ) - * console.log(result) - * // Output: { _tag: "Failure", failure: "Error: not found", ... } + * ) // => Result.fail("Error: not found") * ``` * * @see {@link map} to transform only the success value @@ -819,7 +781,7 @@ export const mapError: { } = dual( 2, (self: Result, f: (err: E) => E2): Result => - isFailure(self) ? fail(f(self.failure)) : succeed(self.success) + isFailure(self) ? fail(f(self.failure)) : self as unknown as Result ) /** @@ -838,15 +800,13 @@ export const mapError: { * * **Example** (Doubling the success value) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(3), * Result.map((n) => n * 2) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 6, ... } + * ) // => Result.succeed(6) * ``` * * @see {@link mapError} to transform only the error value @@ -862,7 +822,7 @@ export const map: { } = dual( 2, (self: Result, f: (ok: A) => A2): Result => - isSuccess(self) ? succeed(f(self.success)) : fail(self.failure) + isSuccess(self) ? succeed(f(self.success)) : self as unknown as Result ) /** @@ -881,7 +841,7 @@ export const map: { * * **Example** (Folding to a string) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * * const format = Result.match({ @@ -889,11 +849,9 @@ export const map: { * onFailure: (e: string) => `Err: ${e}` * }) * - * console.log(format(Result.succeed(42))) - * // Output: "Got 42" + * format(Result.succeed(42)) // => "Got 42" * - * console.log(format(Result.fail("timeout"))) - * // Output: "Err: timeout" + * format(Result.fail("timeout")) // => "Err: timeout" * ``` * * @see {@link merge} to extract `A | E` without mapping @@ -936,18 +894,16 @@ export const match: { * * **Example** (Validating a number) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const ensurePositive = pipe( + * pipe( * 5, * Result.liftPredicate( * (n: number) => n > 0, * (n) => `${n} is not positive` * ) - * ) - * console.log(ensurePositive) - * // Output: { _tag: "Success", success: 5, ... } + * ) // => Result.succeed(5) * ``` * * @see {@link filterOrFail} to validate a value that is already in a `Result` @@ -997,18 +953,16 @@ export const liftPredicate: { * * **Example** (Filtering a success value) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(0), * Result.filterOrFail( * (n) => n > 0, * (n) => `${n} is not positive` * ) - * ) - * console.log(result) - * // Output: { _tag: "Failure", failure: "0 is not positive", ... } + * ) // => Result.fail("0 is not positive") * ``` * * @see {@link liftPredicate} to create a `Result` from a raw value with a predicate @@ -1050,14 +1004,12 @@ export const filterOrFail: { * * **Example** (Extracting the inner value) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.merge(Result.succeed(42))) - * // Output: 42 + * Result.merge(Result.succeed(42)) // => 42 * - * console.log(Result.merge(Result.fail("error"))) - * // Output: "error" + * Result.merge(Result.fail("error")) // => "error" * ``` * * @see {@link match} to map each branch to a common type @@ -1084,14 +1036,12 @@ export const merge: (self: Result) => E | A = match({ onFailure: ide * * **Example** (Providing a fallback) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrElse(Result.succeed(1), () => 0)) - * // Output: 1 + * Result.getOrElse(Result.succeed(1), () => 0) // => 1 * - * console.log(Result.getOrElse(Result.fail("err"), () => 0)) - * // Output: 0 + * Result.getOrElse(Result.fail("err"), () => 0) // => 0 * ``` * * @see {@link getOrNull} / {@link getOrUndefined} for simpler fallbacks @@ -1126,14 +1076,12 @@ export const getOrElse: { * * **Example** (Unwrapping to nullable) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrNull(Result.succeed(1))) - * // Output: 1 + * Result.getOrNull(Result.succeed(1)) // => 1 * - * console.log(Result.getOrNull(Result.fail("err"))) - * // Output: null + * Result.getOrNull(Result.fail("err")) // => null * ``` * * @see {@link getOrUndefined} to return `undefined` instead @@ -1159,14 +1107,12 @@ export const getOrNull: (self: Result) => A | null = getOrElse(const * * **Example** (Unwrapping to optional) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrUndefined(Result.succeed(1))) - * // Output: 1 + * Result.getOrUndefined(Result.succeed(1)) // => 1 * - * console.log(Result.getOrUndefined(Result.fail("err"))) - * // Output: undefined + * Result.getOrUndefined(Result.fail("err")) // => undefined * ``` * * @see {@link getOrNull} to return `null` instead @@ -1192,19 +1138,19 @@ export const getOrUndefined: (self: Result) => A | undefined = getOr * * **Example** (Throwing a custom error) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log( - * Result.getOrThrowWith(Result.succeed(1), () => new Error("fail")) - * ) - * // Output: 1 + * Result.getOrThrowWith(Result.succeed(1), () => new Error("fail")) // => 1 * - * // This would throw: new Error("Unexpected: oops") - * // Result.getOrThrowWith( - * // Result.fail("oops"), - * // (err) => new Error(`Unexpected: ${err}`) - * // ) + * const failure = Result.try({ + * try: () => Result.getOrThrowWith( + * Result.fail("oops"), + * (error) => new Error(`Unexpected: ${error}`) + * ), + * catch: (error) => (error as Error).message + * }) + * Result.merge(failure) // => "Unexpected: oops" * ``` * * @see {@link getOrThrow} to throw the raw failure value @@ -1238,14 +1184,13 @@ export const getOrThrowWith: { * * **Example** (Unwrapping or throwing) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * console.log(Result.getOrThrow(Result.succeed(1))) - * // Output: 1 + * Result.getOrThrow(Result.succeed(1)) // => 1 * - * // This would throw the string "error": - * // Result.getOrThrow(Result.fail("error")) + * const failure = Result.try(() => Result.getOrThrow(Result.fail("error"))) + * Result.merge(failure) // => "error" * ``` * * @see {@link getOrThrowWith} for custom error mapping @@ -1272,15 +1217,13 @@ export const getOrThrow: (self: Result) => A = getOrThrowWith(identi * * **Example** (Recovering from a failure) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.fail("primary failed"), * Result.orElse(() => Result.succeed(99)) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 99, ... } + * ) // => Result.succeed(99) * ``` * * @see {@link getOrElse} to unwrap with a fallback value (not a Result) @@ -1315,17 +1258,15 @@ export const orElse: { * * **Example** (Validating sequentially) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(5), * Result.flatMap((n) => * n > 0 ? Result.succeed(n * 2) : Result.fail("not positive") * ) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: 10, ... } + * ) // => Result.succeed(10) * ``` * * @see {@link andThen} for a more flexible variant that also accepts plain values @@ -1363,25 +1304,23 @@ export const flatMap: { * * **Example** (Chaining Result values with different argument types) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * * // With a function returning a Result * const a = pipe( * Result.succeed(1), * Result.andThen((n) => Result.succeed(n + 1)) - * ) + * ) // => Result.succeed(2) * * // With a plain mapping function * const b = pipe( * Result.succeed(1), * Result.andThen((n) => n + 1) - * ) + * ) // => Result.succeed(2) * * // With a constant value - * const c = pipe(Result.succeed(1), Result.andThen("done")) - * - * console.log(a, b, c) + * const c = pipe(Result.succeed(1), Result.andThen("done")) // => Result.succeed("done") * ``` * * @see {@link flatMap} for the stricter variant (function returning Result only) @@ -1430,18 +1369,14 @@ export const andThen: { * * **Example** (Collecting a tuple and a struct) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * * // Tuple - * const tuple = Result.all([Result.succeed(1), Result.succeed("two")]) - * console.log(tuple) - * // Output: { _tag: "Success", success: [1, "two"], ... } + * Result.all([Result.succeed(1), Result.succeed("two")]) // => Result.succeed([1, "two"]) * * // Struct - * const struct = Result.all({ x: Result.succeed(1), y: Result.fail("err") }) - * console.log(struct) - * // Output: { _tag: "Failure", failure: "err", ... } + * Result.all({ x: Result.succeed(1), y: Result.fail("err") }) // => Result.fail("err") * ``` * * @see {@link flatMap} for chaining two Results sequentially @@ -1481,7 +1416,7 @@ export const all: > | Record> | Record Result.fail(42) * - * console.log(Result.flip(Result.fail("error"))) - * // Output: { _tag: "Success", success: "error", ... } + * Result.flip(Result.fail("error")) // => Result.succeed("error") * ``` * * @see {@link mapError} to transform the error without swapping @@ -1538,17 +1471,14 @@ export const flip = (self: Result): Result => * * **Example** (Composing multiple Results) * - * ```ts + * ```ts import.meta.vitest * import { Result } from "effect" * - * const result = Result.gen(function*() { + * Result.gen(function*() { * const a = yield* Result.succeed(1) * const b = yield* Result.succeed(2) * return a + b - * }) - * - * console.log(result) - * // Output: { _tag: "Success", success: 3, ... } + * }) // => Result.succeed(3) * ``` * * @see {@link flatMap} for point-free sequential composition @@ -1592,17 +1522,15 @@ export const gen: Gen.Gen = (...args) => { * * **Example** (Building an object step by step) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.Do, * Result.bind("x", () => Result.succeed(2)), * Result.bind("y", () => Result.succeed(3)), * Result.let("sum", ({ x, y }) => x + y) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { x: 2, y: 3, sum: 5 }, ... } + * ) // => Result.succeed({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link bind} to add Result-producing fields @@ -1610,7 +1538,7 @@ export const gen: Gen.Gen = (...args) => { * @see {@link gen} for an alternative generator-based syntax * @see {@link bindTo} for starting a do-notation chain from an existing Result * - * @category do notation + * @category constructors * @since 2.0.0 */ export const Do: Result<{}> = succeed({}) @@ -1633,23 +1561,21 @@ export const Do: Result<{}> = succeed({}) * * **Example** (Binding Result values) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.Do, * Result.bind("x", () => Result.succeed(2)), * Result.bind("y", ({ x }) => Result.succeed(x + 3)) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { x: 2, y: 5 }, ... } + * ) // => Result.succeed({ x: 2, y: 5 }) * ``` * * @see {@link Do} to start the do-notation chain * @see {@link let_ let} for pure computed fields * @see {@link bindTo} to wrap an initial Result into a named field * - * @category do notation + * @category sequencing * @since 2.0.0 */ export const bind: { @@ -1680,21 +1606,19 @@ export const bind: { * * **Example** (Wrapping a value into a named field) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.succeed(42), * Result.bindTo("answer") - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { answer: 42 }, ... } + * ) // => Result.succeed({ answer: 42 }) * ``` * * @see {@link Do} to start from an empty object * @see {@link bind} to add more fields * - * @category do notation + * @category mapping * @since 2.0.0 */ export const bindTo: { @@ -1731,23 +1655,21 @@ export { * * **Example** (Adding a computed field) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * - * const result = pipe( + * pipe( * Result.Do, * Result.bind("x", () => Result.succeed(2)), * Result.bind("y", () => Result.succeed(3)), * Result.let("sum", ({ x, y }) => x + y) - * ) - * console.log(result) - * // Output: { _tag: "Success", success: { x: 2, y: 3, sum: 5 }, ... } + * ) // => Result.succeed({ x: 2, y: 3, sum: 5 }) * ``` * * @see {@link Do} to start the do-notation chain * @see {@link bind} for Result-producing fields * - * @category do notation + * @category mapping * @since 2.0.0 */ let_ as let @@ -1769,21 +1691,17 @@ export { * * **Example** (Transposing an Option of a Result) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * - * const some = Option.some(Result.succeed(42)) - * console.log(Result.transposeOption(some)) - * // Output: { _tag: "Success", success: { _tag: "Some", value: 42 }, ... } + * Result.transposeOption(Option.some(Result.succeed(42))) // => Result.succeed(Option.some(42)) * - * const none = Option.none>() - * console.log(Result.transposeOption(none)) - * // Output: { _tag: "Success", success: { _tag: "None" }, ... } + * Result.transposeOption(Option.none>()) // => Result.succeed(Option.none()) * ``` * * @see {@link transposeMapOption} to map and transpose in one step * - * @category Transposing + * @category transposing * @since 3.14.0 */ export const transposeOption = ( @@ -1809,7 +1727,7 @@ export const transposeOption = ( * * **Example** (Mapping and transposing in one step) * - * ```ts + * ```ts import.meta.vitest * import { Option, Result } from "effect" * * const parse = (s: string) => @@ -1817,16 +1735,14 @@ export const transposeOption = ( * ? Result.fail("not a number" as const) * : Result.succeed(Number(s)) * - * console.log(Result.transposeMapOption(Option.some("42"), parse)) - * // Output: { _tag: "Success", success: { _tag: "Some", value: 42 }, ... } + * Result.transposeMapOption(Option.some("42"), parse) // => Result.succeed(Option.some(42)) * - * console.log(Result.transposeMapOption(Option.none(), parse)) - * // Output: { _tag: "Success", success: { _tag: "None" }, ... } + * Result.transposeMapOption(Option.none(), parse) // => Result.succeed(Option.none()) * ``` * * @see {@link transposeOption} when the Option already contains a Result * - * @category Transposing + * @category transposing * @since 3.15.0 */ export const transposeMapOption = dual< @@ -1854,11 +1770,10 @@ export const transposeMapOption = dual< * * **Example** (Succeeding with None) * - * ```ts - * import { Result } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * console.log(Result.isSuccess(Result.succeedNone)) - * // Output: true + * Result.succeedNone // => Result.succeed(Option.none()) * ``` * * @see {@link succeedSome} for the `Some` counterpart @@ -1880,12 +1795,10 @@ export const succeedNone = succeed(option_.none) * * **Example** (Wrapping a value in Some inside a Result) * - * ```ts - * import { Result } from "effect" + * ```ts import.meta.vitest + * import { Option, Result } from "effect" * - * const result = Result.succeedSome(42) - * console.log(result) - * // Output: { _tag: "Success", success: { _tag: "Some", value: 42 }, ... } + * Result.succeedSome(42) // => Result.succeed(Option.some(42)) * ``` * * @see {@link succeedNone} for the `None` counterpart @@ -1907,17 +1820,17 @@ export const succeedSome = (a: A): Result, E> => succeed * * **Example** (Logging a success value) * - * ```ts + * ```ts import.meta.vitest * import { pipe, Result } from "effect" * + * const values: Array = [] * const result = pipe( * Result.succeed(42), - * Result.tap((n) => console.log("Got:", n)) + * Result.tap((n) => values.push(n)) * ) - * // Output: "Got: 42" * - * console.log(Result.isSuccess(result)) - * // Output: true + * values // => [42] + * result // => Result.succeed(42) * ``` * * @see {@link map} to transform the success value diff --git a/packages/effect/src/Runtime.ts b/packages/effect/src/Runtime.ts index 001c81a72..ab7ad6809 100644 --- a/packages/effect/src/Runtime.ts +++ b/packages/effect/src/Runtime.ts @@ -31,31 +31,27 @@ import type * as Fiber from "./Fiber.ts" * * **Example** (Customizing teardown behavior) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Exit, Runtime } from "effect" * - * // Custom teardown that logs completion status + * // Custom teardown that maps completion status to an exit code * const customTeardown: Runtime.Teardown = (exit, onExit) => { - * if (Exit.isSuccess(exit)) { - * console.log("Program completed successfully with value:", exit.value) - * onExit(0) - * } else { - * console.log("Program failed with cause:", exit.cause) - * onExit(1) - * } + * onExit(Exit.isSuccess(exit) ? 0 : 1) * } * + * const completed = new Promise, number]>((resolve) => { * // Use with makeRunMain - * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { - * fiber.addObserver((exit) => { - * teardown(exit, (code) => { - * console.log(`Exiting with code: ${code}`) + * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { + * fiber.addObserver((exit) => { + * teardown(exit, (code) => resolve([exit, code])) * }) * }) + * + * const program = Effect.succeed("Hello, World!") + * runMain(program, { teardown: customTeardown }) * }) * - * const program = Effect.succeed("Hello, World!") - * runMain(program, { teardown: customTeardown }) + * await completed // => [Exit.succeed("Hello, World!"), 0] * ``` * * @category models @@ -90,23 +86,18 @@ export interface Teardown { * * **Example** (Referencing default teardown) * - * ```ts + * ```ts import.meta.vitest * import { Exit, Runtime } from "effect" * - * const logExitCode = (exit: Exit.Exit) => { - * Runtime.defaultTeardown(exit, (code) => { - * console.log(`Exit code: ${code}`) - * }) - * } - * - * logExitCode(Exit.succeed(42)) - * // Output: Exit code: 0 + * const exitCodes: Array = [] + * const collectExitCode = (exit: Exit.Exit) => + * Runtime.defaultTeardown(exit, (code) => exitCodes.push(code)) * - * logExitCode(Exit.fail("error")) - * // Output: Exit code: 1 + * collectExitCode(Exit.succeed(42)) + * collectExitCode(Exit.fail("error")) + * collectExitCode(Exit.interrupt(123)) * - * logExitCode(Exit.interrupt(123)) - * // Output: Exit code: 130 + * exitCodes // => [0, 1, 130] * ``` * * @see {@link errorExitCode} for customizing failure exit codes @@ -152,47 +143,36 @@ export const defaultTeardown: Teardown = ( * * **Example** (Creating platform runners) * - * ```ts - * import { Effect, Fiber, Runtime } from "effect" + * ```ts import.meta.vitest + * import { Effect, Exit, Runtime } from "effect" * + * const events: Array = [] + * const completed = new Promise, number]>((resolve) => { * // Create a simple runner for a hypothetical platform - * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { - * // Set up signal handling - * const handleSignal = () => { - * Effect.runSync(Fiber.interrupt(fiber)) - * } - * - * // Add signal listeners (platform-specific) - * // process.on('SIGINT', handleSignal) - * // process.on('SIGTERM', handleSignal) - * - * // Handle fiber completion - * fiber.addObserver((exit) => { - * teardown(exit, (code) => { - * console.log(`Program finished with exit code: ${code}`) - * // process.exit(code) + * const runMain = Runtime.makeRunMain(({ fiber, teardown }) => { + * // Handle fiber completion + * fiber.addObserver((exit) => { + * teardown(exit, (code) => resolve([exit, code])) * }) * }) - * }) * - * // Use the runner - * const program = Effect.gen(function*() { - * yield* Effect.log("Starting program") - * yield* Effect.sleep(1000) - * yield* Effect.log("Program completed") - * return "success" - * }) - * - * // Run with default options - * runMain(program) + * // Use the runner + * const program = Effect.sync(() => { + * events.push("Starting program", "Program completed") + * return "success" + * }) * - * // Run with custom teardown - * runMain(program, { - * teardown: (exit, onExit) => { - * console.log("Custom teardown logic") - * Runtime.defaultTeardown(exit, onExit) - * } + * runMain(program, { + * teardown: (exit, onExit) => { + * events.push("Custom teardown logic") + * Runtime.defaultTeardown(exit, onExit) + * } + * }) * }) + * + * const result = await completed + * result // => [Exit.succeed("success"), 0] + * events // => ["Starting program", "Program completed", "Custom teardown logic"] * ``` * * @category running @@ -285,16 +265,14 @@ export type errorExitCode = "~effect/Runtime/errorExitCode" * * **Example** (Setting a process exit code) * - * ```ts - * import { Data, Effect, Runtime } from "effect" - * import { NodeRuntime } from "@effect/platform-node" + * ```ts import.meta.vitest + * import { Data, Runtime } from "effect" * * class MyError extends Data.TaggedError("MyError") { * readonly [Runtime.errorExitCode] = 42 * } * - * // If the program fails with MyError, the process will exit with code 42 - * NodeRuntime.runMain(Effect.fail(new MyError())) + * Runtime.getErrorExitCode(new MyError()) // => 42 * ``` * * @see {@link errorReported} for controlling automatic error logging @@ -327,7 +305,7 @@ export const errorExitCode: errorExitCode = "~effect/Runtime/errorExitCode" * * @see {@link errorExitCode} for the marker read by this function * - * @category accessors + * @category getters * @since 4.0.0 */ export const getErrorExitCode = (u: unknown): number => { @@ -377,17 +355,14 @@ export type errorReported = "~effect/Runtime/errorReported" * * **Example** (Suppressing error reporting) * - * ```ts - * import { Data, Effect, Runtime } from "effect" - * import { NodeRuntime } from "@effect/platform-node" + * ```ts import.meta.vitest + * import { Data, Runtime } from "effect" * * class MyError extends Data.TaggedError("MyError") { * readonly [Runtime.errorReported] = false * } * - * // If the program fails with MyError, the process will exit with code 1 but - * // no error will be logged. - * NodeRuntime.runMain(Effect.fail(new MyError())) + * Runtime.getErrorReported(new MyError()) // => false * ``` * * @see {@link errorExitCode} for controlling failure exit codes @@ -418,7 +393,7 @@ export const errorReported: errorReported = "~effect/Runtime/errorReported" * * @see {@link errorReported} for the marker read by this function * - * @category accessors + * @category getters * @since 4.0.0 */ export const getErrorReported = (u: unknown): boolean => { diff --git a/packages/effect/src/Schedule.ts b/packages/effect/src/Schedule.ts index 8e62d3afa..a9d4816da 100644 --- a/packages/effect/src/Schedule.ts +++ b/packages/effect/src/Schedule.ts @@ -8,13 +8,13 @@ * * @since 2.0.0 */ +import type { NonEmptyReadonlyArray } from "./Array.ts" import * as Cause from "./Cause.ts" import * as Context from "./Context.ts" import * as Cron from "./Cron.ts" import type * as DateTime from "./DateTime.ts" import * as Duration from "./Duration.ts" import type { Effect } from "./Effect.ts" -import type { LazyArg } from "./Function.ts" import { constant, dual, identity } from "./Function.ts" import { isEffect } from "./internal/core.ts" import * as effect from "./internal/effect.ts" @@ -23,7 +23,7 @@ import { type Pipeable, pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" import * as Pull from "./Pull.ts" import * as Result from "./Result.ts" -import type { Contravariant, Covariant, Mutable } from "./Types.ts" +import type { Contravariant, Covariant, Mutable, UnionToIntersection } from "./Types.ts" const TypeId = "~effect/Schedule" @@ -34,41 +34,17 @@ const randomNext: Effect = random.Random.useSync((random) => random.next * * **Example** (Defining retry and repeat schedules) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class NetworkError extends Data.TaggedError("NetworkError")<{ - * readonly attempt: number - * }> {} + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Basic retry schedule - retry up to 3 times with exponential backoff - * const retrySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.both(Schedule.recurs(3)) + * const executions: Array = [] + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(Schedule.recurs(2)), + * Effect.as(executions) * ) * - * // Basic repeat schedule - repeat every 30 seconds forever - * const repeatSchedule: Schedule.Schedule = Schedule - * .spaced("30 seconds") - * - * const program = Effect.gen(function*() { - * let attempts = 0 - * - * const result1 = yield* Effect.retry( - * Effect.gen(function*() { - * attempts++ - * if (attempts < 3) { - * return yield* Effect.fail(new NetworkError({ attempt: attempts })) - * } - * return "Success" - * }), - * retrySchedule - * ) - * console.log(result1) // "Success" - * - * yield* Console.log("heartbeat").pipe( - * Effect.repeat(repeatSchedule.pipe(Schedule.take(5))) - * ) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] * ``` * * @category models @@ -81,26 +57,6 @@ export interface Schedule - * Effect.succeed(metadata.attempt <= 5 && metadata.elapsed < 10000) - * ) - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Task execution"), - * metadataAwareSchedule - * ) - * }) - * ``` - * * @category metadata * @since 4.0.0 */ @@ -116,34 +72,6 @@ export interface InputMetadata { /** * Extended metadata that includes both input metadata and the output value from the schedule. * - * **Example** (Logging schedule output metadata) - * - * ```ts - * import { Console, Duration, Effect, Schedule } from "effect" - * - * // Custom schedule that logs metadata and output for each recurrence - * const loggingSchedule = Schedule.unfold(0, (n) => Effect.succeed(n + 1)).pipe( - * Schedule.addDelay(() => Effect.succeed(Duration.millis(100))), - * Schedule.collectWhile((metadata) => - * Console.log( - * `Output: ${metadata.output}, attempt: ${metadata.attempt}, elapsed: ${metadata.elapsed}ms` - * ).pipe(Effect.as(metadata.attempt <= 3)) - * ) - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.succeed("task completed"), - * loggingSchedule.pipe(Schedule.take(3)) - * ) - * }) - * - * // Output logs will show: - * // Output: 0, attempt: 1, elapsed: 0ms - * // Output: 1, attempt: 2, elapsed: 100ms - * // Output: 2, attempt: 3, elapsed: 200ms - * ``` - * * @category metadata * @since 4.0.0 */ @@ -162,7 +90,7 @@ export interface Metadata extends InputMetada * input and output values, zero duration, and zeroed timing fields before any * schedule step has produced metadata. * - * @category metadata + * @category services * @since 4.0.0 */ export const CurrentMetadata = Context.Reference("effect/Schedule/CurrentMetadata", { @@ -181,34 +109,6 @@ export const CurrentMetadata = Context.Reference("effect/Schedule/Curr /** * The Schedule namespace contains types and utilities for working with schedules. * - * **Example** (Creating custom schedules with the namespace) - * - * ```ts - * import { Duration, Effect, Schedule } from "effect" - * - * // Usage of the Schedule namespace for creating schedules - * - * // Create custom schedule with metadata - * const customSchedule = Schedule.unfold(0, (n) => Effect.succeed(n + 1)).pipe( - * Schedule.addDelay((n) => Effect.succeed(Duration.millis(n * 100))) - * ) - * - * const program = Effect.gen(function*() { - * let attempt = 0 - * - * yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 3) { - * return yield* Effect.fail(`Attempt ${attempt} failed`) - * } - * return `Success on attempt ${attempt}` - * }), - * customSchedule.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * * @since 2.0.0 */ export declare namespace Schedule { @@ -217,27 +117,11 @@ export declare namespace Schedule { * * **Example** (Understanding schedule variance) * - * ```ts - * import { Effect, Schedule } from "effect" - * - * // Understanding Schedule variance: - * // - Output: covariant (can be a subtype) - * // - Input: contravariant (can accept supertypes) - * // - Error: covariant (can be a subtype) - * // - Env: covariant (can be a subtype) - * - * // Schedule that produces strings, accepts any input - * const stringSchedule = Schedule.spaced("1 second").pipe( - * Schedule.map(() => Effect.succeed("tick")) - * ) - * - * // Schedule that only accepts Error inputs - * const errorSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(5) - * ) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * // Schedule requiring a service environment - * const serviceSchedule = Schedule.spaced("5 seconds") + * const schedule: Schedule.Schedule = Schedule.recurs(2) + * Schedule.isSchedule(schedule) // => true * ``` * * @category models @@ -267,6 +151,38 @@ export declare namespace Schedule { } } +/** + * Extracts the output type from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Output = S extends Schedule ? Output : never + +/** + * Extracts the input type from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Input = S extends Schedule ? Input : never + +/** + * Extracts the error type from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Error = S extends Schedule ? Error : never + +/** + * Extracts the service requirements from a `Schedule`. + * + * @category utility types + * @since 4.0.0 + */ +export type Env = S extends Schedule ? Env : never + const ScheduleProto = { [TypeId]: { _Out: identity, @@ -283,16 +199,16 @@ const ScheduleProto = { * * **Example** (Checking for schedules) * - * ```ts + * ```ts import.meta.vitest * import { Schedule } from "effect" * * const schedule = Schedule.exponential("100 millis") * const notSchedule = { foo: "bar" } * - * console.log(Schedule.isSchedule(schedule)) // true - * console.log(Schedule.isSchedule(notSchedule)) // false - * console.log(Schedule.isSchedule(null)) // false - * console.log(Schedule.isSchedule(undefined)) // false + * Schedule.isSchedule(schedule) // => true + * Schedule.isSchedule(notSchedule) // => false + * Schedule.isSchedule(null) // => false + * Schedule.isSchedule(undefined) // => false * ``` * * @category guards @@ -305,7 +221,7 @@ export const isSchedule = (u: unknown): u is Schedule { @@ -318,6 +234,14 @@ export const isSchedule = (u: unknown): u is Schedule 0 * ``` * * @category constructors @@ -353,7 +277,7 @@ const metadataFn = () => { * * **Example** (Creating a metadata-aware schedule) * - * ```ts + * ```ts import.meta.vitest * import { Cause, Duration, Effect, Schedule } from "effect" * * const firstThreeInputs = Schedule.fromStepWithMetadata(Effect.succeed((metadata: Schedule.InputMetadata) => { @@ -366,6 +290,14 @@ const metadataFn = () => { * Duration.millis(250) * ] as [string, Duration.Duration]) * })) + * + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(firstThreeInputs) + * const [output] = yield* step(0, "input") + * return output + * }) + * + * await Effect.runPromise(program) // => "attempt 1: input" * ``` * * @category constructors @@ -388,11 +320,11 @@ export const fromStepWithMetadata = ( * * **Example** (Extracting a schedule step function) * - * ```ts - * import { Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * // Extract step function from an existing schedule - * const schedule = Schedule.exponential("100 millis").pipe(Schedule.take(3)) + * const schedule = Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 })) * * const program = Effect.gen(function*() { * const stepFn = yield* Schedule.toStep(schedule) @@ -400,10 +332,10 @@ export const fromStepWithMetadata = ( * // Use the step function directly for custom logic. The timestamp is * // supplied by the caller, so tests can pass a deterministic value. * const now = 0 - * const result = yield* stepFn(now, "input") - * - * console.log(`Step result: ${result}`) + * return yield* stepFn(now, "input") * }) + * + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] * ``` * * @category destructors @@ -476,26 +408,19 @@ export const toStepWithMetadata = ( * * **Example** (Extracting a sleeping step function) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Convert schedule to step function with automatic sleeping - * const schedule = Schedule.spaced("1 second").pipe(Schedule.take(3)) + * const schedule = Schedule.recurs(3) * * const program = Effect.gen(function*() { * const stepWithSleep = yield* Schedule.toStepWithSleep(schedule) * - * // Each call will automatically sleep for the scheduled delay - * console.log("Starting...") - * const result1 = yield* stepWithSleep("first") - * console.log(`First result: ${result1}`) - * - * const result2 = yield* stepWithSleep("second") - * console.log(`Second result: ${result2}`) - * - * const result3 = yield* stepWithSleep("third") - * console.log(`Third result: ${result3}`) + * return [yield* stepWithSleep("first"), yield* stepWithSleep("second")] * }) + * + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [0, 1] * ``` * * @category destructors @@ -519,117 +444,41 @@ export const toStepWithSleep = ( * * **Example** (Adding extra delay to a schedule) * - * ```ts - * import { Console, Data, Duration, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Add a deterministic extra delay based on the schedule output - * const delayedSchedule = Schedule.addDelay( - * Schedule.exponential("100 millis").pipe(Schedule.take(5)), - * (output) => - * Effect.succeed(Duration.millis(Duration.toMillis(output) * 0.25)) - * ) - * - * const repeatProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.succeed("delayed task"), - * delayedSchedule.pipe( - * Schedule.tapOutput((delay) => - * Console.log(`Base delay: ${delay}`) - * ) - * ) - * ) - * }) - * - * // Add adaptive delay based on execution count - * const adaptiveSchedule = Schedule.addDelay( - * Schedule.recurs(6), - * (executionCount) => - * // Increase delay as execution count grows - * Effect.succeed(Duration.millis(executionCount * 200)) - * ) - * - * const adaptiveProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Adaptive delay task") - * return "adaptive" - * }), - * adaptiveSchedule.pipe( - * Schedule.tapOutput((count) => - * Console.log(`Execution ${count + 1} with adaptive delay`) - * ) - * ) - * ) - * }) - * - * // Add effectful delay computation from deterministic service data - * const loadByExecution = [1, 3, 2, 4] as const + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * const dynamicSchedule = Schedule.addDelay( - * Schedule.spaced("1 second").pipe(Schedule.take(4)), - * (executionNumber) => { - * const load = loadByExecution[executionNumber] ?? 1 - * return Effect.succeed(Duration.millis(load * 100)) - * } + * const schedule = Schedule.recurs(1).pipe( + * Schedule.addDelay(() => Effect.succeed("25 millis")) * ) - * - * const dynamicProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Dynamic delay task") - * return "dynamic" - * }), - * dynamicSchedule - * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(schedule) + * const [, delay] = yield* step(0, undefined) + * return delay * }) * - * // Combine with retry for progressive backoff - * const progressiveRetrySchedule = Schedule.addDelay( - * Schedule.exponential("50 millis").pipe(Schedule.take(4)), - * () => Effect.succeed(Duration.millis(100)) // Fixed additional delay - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 5) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * return `Success on attempt ${attempt}` - * }), - * progressiveRetrySchedule - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) + * await Effect.runPromise(program) // => Duration.millis(25) * ``` * * @category delays & timeouts * @since 2.0.0 */ export const addDelay: { - ( - f: (output: Output) => Effect - ): ( + ( + f: (metadata: Metadata) => Effect + ): ( self: Schedule ) => Schedule ( self: Schedule, - f: (output: Output) => Effect + f: (metadata: Metadata) => Effect ): Schedule } = dual(2, ( self: Schedule, - f: (output: Output) => Effect + f: (metadata: Metadata) => Effect ): Schedule => modifyDelay( self, - (output, delay) => - effect.map(f(output), (d) => Duration.sum(Duration.fromInputUnsafe(d), Duration.fromInputUnsafe(delay))) + (metadata) => effect.map(f(metadata), (d) => Duration.sum(Duration.fromInputUnsafe(d), metadata.duration)) )) /** @@ -638,41 +487,17 @@ export const addDelay: { * * **Example** (Sequencing quick and slow retries) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // First retry 3 times quickly, then switch to slower retries - * const quickRetries = Schedule.exponential("100 millis").pipe( - * Schedule.take(3) - * ) - * const slowRetries = Schedule.exponential("1 second").pipe( - * Schedule.take(2) - * ) - * - * const combinedRetries = Schedule.andThen(quickRetries, slowRetries) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * let attempt = 0 - * yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) - * if (attempt < 6) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Failure ${attempt}` })) - * } - * return `Success on attempt ${attempt}` - * }), - * combinedRetries - * ) - * }) + * const schedule = Schedule.concat(Schedule.recurs(1), Schedule.recurs(2)) + * Schedule.isSchedule(schedule) // => true * ``` * * @category sequencing * @since 2.0.0 */ -export const andThen: { +export const concat: { ( other: Schedule ): ( @@ -686,7 +511,7 @@ export const andThen: { self: Schedule, other: Schedule ): Schedule => - map(andThenResult(self, other), (result) => effect.succeed(Result.merge(result)))) + map(concatResult(self, other), ({ output }) => effect.succeed(Result.merge(output)))) /** * Returns a schedule that runs `self` to completion, then runs `other`, and @@ -700,37 +525,17 @@ export const andThen: { * * **Example** (Tracking sequential schedule phases) * - * ```ts - * import { Console, Effect, Result, Schedule } from "effect" - * - * // Track which phase of the schedule we're in - * const phaseTracker = Schedule.andThenResult( - * Schedule.exponential("100 millis").pipe(Schedule.take(2)), - * Schedule.spaced("500 millis").pipe(Schedule.take(2)) - * ) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * phaseTracker.pipe( - * Schedule.tapOutput((result) => - * Result.match(result, { - * onFailure: (phase1Output) => Console.log(`Phase 1: ${phase1Output}`), - * onSuccess: (phase2Output) => Console.log(`Phase 2: ${phase2Output}`) - * }) - * ) - * ) - * ) - * }) + * const schedule = Schedule.concatResult(Schedule.recurs(1), Schedule.recurs(2)) + * Schedule.isSchedule(schedule) // => true * ``` * * @category sequencing * @since 4.0.0 */ -export const andThenResult: { +export const concatResult: { ( other: Schedule ): ( @@ -743,31 +548,31 @@ export const andThenResult: { } = dual(2, ( self: Schedule, other: Schedule -): Schedule, Input & Input2, Error | Error2, Env | Env2> => +): Schedule, Input & Input2, Error | Error2, Env | Env2> => fromStep(effect.sync(() => { let currentSide = 0 let currentStep: | undefined | ((now: number, input: Input & Input2) => Pull.Pull< - [Result.Result, Duration.Duration], + [Result.Result, Duration.Duration], Error | Error2, - Result.Result, + Result.Result, Env | Env2 >) - const left = map(self, Result.succeed) - const right = map(other, Result.fail) + const left = map(self, ({ output }) => Result.fail(output)) + const right = map(other, ({ output }) => Result.succeed(output)) return function recur( now, input ): Pull.Pull< - [Result.Result, Duration.Duration], + [Result.Result, Duration.Duration], Error | Error2, - Result.Result, + Result.Result, Env | Env2 > { if (currentStep) return currentStep(now, input) return toStep< - Result.Result, + Result.Result, Input & Input2, Error | Error2, Env | Env2 @@ -790,1223 +595,253 @@ export const andThenResult: { }))) /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting a tuple of the outputs of both schedules. + * Combines schedules by recurring while all schedules want to recur, using the + * maximum delay between recurrences and outputting that maximum delay. * * **When to use** * - * Use when the combined schedule should continue only while both schedules still recur. - * - * **Example** (Combining time and attempt limits) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Both schedules must want to continue for the combined schedule to continue - * const timeLimit = Schedule.spaced("1 second").pipe(Schedule.take(5)) // max 5 times - * const attemptLimit = Schedule.recurs(3) // max 3 attempts - * - * // Continues only while BOTH schedules want to continue (intersection/AND logic) - * const bothSchedule = Schedule.both(timeLimit, attemptLimit) - * // Outputs: [time_result, attempt_count] tuple - * - * const program = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task completed" - * }), - * bothSchedule.pipe( - * Schedule.tapOutput(([timeResult, attemptResult]) => - * Console.log(`Time: ${timeResult}, Attempts: ${attemptResult}`) - * ) - * ) - * ) - * - * yield* Console.log("Completed all executions") - * }) + * Use when a combined policy should continue only while every schedule still + * recurs, and should wait for the slowest schedule between recurrences. * - * // Both with different delay strategies - uses maximum delay - * const fastSchedule = Schedule.fixed("500 millis").pipe(Schedule.take(4)) - * const slowSchedule = Schedule.spaced("2 seconds").pipe(Schedule.take(6)) + * **Example** (Combining retry schedules by their maximum delay) * - * // Will use the slower (maximum) delay and stop when first schedule exhausts - * const conservativeSchedule = Schedule.both(fastSchedule, slowSchedule) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) + * const schedule = Schedule.max([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")]) + * Schedule.isSchedule(schedule) // => true + * ``` * - * if (attempt < 3) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } + * @category combining + * @since 4.0.0 + */ +export const max = < + const Schedules extends NonEmptyReadonlyArray< + Schedule + > +>( + schedules: Schedules +): Schedule< + Duration.Duration, + UnionToIntersection< + Input + >, + Error, + Env +> => + fromStep(effect.map( + effect.all(schedules.map(toStep)), + (steps) => (now, input) => + effect.flatMap( + effect.forEach(steps, (step) => + Pull.matchEffect(step(now, input as never), { + onSuccess: (result) => effect.succeed(result[1]), + onDone: () => effect.undefined, + onFailure: effect.failCause + })), + (results) => { + const duration = maxDuration(results) + if (duration === undefined) { + return Cause.done(Duration.zero) + } + return effect.succeed([duration, duration] as [Duration.Duration, Duration.Duration]) + } + ) + )) + +const maxDuration = (results: ReadonlyArray): Duration.Duration | undefined => { + let max = results[0] + for (let i = 1; i < results.length; i++) { + max = results[i] && max && Duration.max(max, results[i]!) + if (max === undefined) break + } + + return max +} + +/** + * Returns a new `Schedule` that recurs on the specified `Cron` schedule and + * outputs the duration between recurrences. * - * return `Success on attempt ${attempt}` - * }), - * conservativeSchedule - * ) + * **Example** (Scheduling work with cron expressions) * - * yield* Console.log(`Final result: ${result}`) - * }) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * // Both provides intersection semantics (AND logic) - * // Compare with either which provides union semantics (OR logic) + * const everyMinute = Schedule.cron("* * * * *") + * Schedule.isSchedule(everyMinute) // => true * ``` * - * @see {@link either} for continuing while either schedule still recurs - * - * @category combining + * @category constructors * @since 2.0.0 */ -export const both: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> - ( - self: Schedule, - other: Schedule - ): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> => - bothWith(self, other, (left, right) => [left, right])) +export const cron: { + (expression: Cron.Cron): Schedule + (expression: string, tz?: string | DateTime.TimeZone): Schedule +} = (expression: string | Cron.Cron, tz?: string | DateTime.TimeZone) => { + const parsed = Cron.isCron(expression) ? Result.succeed(expression) : Cron.parse(expression, tz) + return fromStep(effect.map(effect.fromResult(parsed), (cron) => (now, _) => { + if (now === Number.POSITIVE_INFINITY) { + return Cause.done(Duration.zero) + } + return effect.sync(() => { + const next = Cron.next(cron, now).getTime() + const duration = Duration.millis(next - now) + return [duration, duration] + }) + })) +} /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting the result of the left schedule (i.e. `self`). + * Returns a schedule that recurs once after the specified duration. * * **When to use** * - * Use when two schedules must both allow recurrence and only the left schedule's - * output is needed. + * Use when you need a schedule that recurs once after a fixed delay. * - * **Example** (Combining schedules and keeping the left output) + * **Details** * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * The schedule outputs the configured duration for its first recurrence and + * then completes. * - * // Combine two schedules, keeping left output - * const leftSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("left-result")) - * ) - * const rightSchedule = Schedule.spaced("50 millis") + * **Example** (Recurring once after a duration) * - * const combined = Schedule.bothLeft(leftSchedule, rightSchedule) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(3)) - * ) - * }) + * Schedule.isSchedule(Schedule.duration("1 second")) // => true * ``` * - * @category combining + * @see {@link during} for recurring until a duration has elapsed + * + * @category constructors * @since 2.0.0 */ -export const bothLeft: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => bothWith(self, other, (output) => output)) +export const duration = (durationInput: Duration.Input): Schedule => { + const duration = Duration.fromInputUnsafe(durationInput) + return fromStepWithMetadata(effect.succeed((meta) => + meta.attempt === 1 + ? effect.succeed([duration, duration]) + : Cause.done(Duration.zero) + )) +} /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting the result of the right schedule (i.e. `other`). + * Returns a new `Schedule` that will always recur, but only during the + * specified `duration` of time. * * **When to use** * - * Use when two schedules must both allow recurrence and only the right - * schedule's output is needed. - * - * **Example** (Combining schedules and keeping the right output) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * Use to bound a repeating or retrying schedule by elapsed time. * - * // Combine two schedules, keeping right output - * const leftSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("left-result")) - * ) - * const rightSchedule = Schedule.spaced("50 millis").pipe( - * Schedule.map(() => Effect.succeed("right-result")) - * ) + * **Example** (Repeating work during a duration) * - * const combined = Schedule.bothRight(leftSchedule, rightSchedule) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(3)) - * ) - * }) + * Schedule.isSchedule(Schedule.during("5 seconds")) // => true * ``` * - * @category combining - * @since 2.0.0 + * @see {@link duration} for one delayed recurrence + * + * @category constructors + * @since 4.0.0 */ -export const bothRight: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => bothWith(self, other, (_, output) => output)) +export const during = (duration: Duration.Input): Schedule => { + const durationMillis = Duration.toMillis(duration) + return fromStepWithMetadata( + effect.succeed((meta) => { + const elapsed = Duration.millis(meta.elapsed) + return meta.elapsed > durationMillis + ? Cause.done(elapsed) + : effect.succeed([elapsed, Duration.zero]) + }) + ) +} /** - * Combines two `Schedule`s by recurring if both of the two schedules want - * to recur, using the maximum of the two durations between recurrences and - * outputting the result of the combination of both schedule outputs using the - * specified `combine` function. + * Combines schedules by recurring while at least one schedule wants to recur, + * using the minimum delay between recurrences and outputting that minimum delay. * * **When to use** * - * Use when two schedules must both allow recurrence and their outputs should be - * combined into a custom value. - * - * **Example** (Combining schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * Use when a combined policy should continue while any schedule still recurs, + * and should wait for the fastest schedule between recurrences. * - * // Combine two schedules with custom output combination - * const leftSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("left")) - * ) - * const rightSchedule = Schedule.spaced("50 millis").pipe( - * Schedule.map(() => Effect.succeed("right")) - * ) + * **Example** (Combining retry schedules by their minimum delay) * - * const combined = Schedule.bothWith( - * leftSchedule, - * rightSchedule, - * (left, right) => `${left}-${right}` - * ) + * ```ts import.meta.vitest + * import { Schedule } from "effect" * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * combined.pipe(Schedule.take(3)) - * ) - * }) + * const schedule = Schedule.min([Schedule.fixed("5 seconds"), Schedule.spaced("10 seconds")]) + * Schedule.isSchedule(schedule) // => true * ``` * * @category combining - * @since 2.0.0 + * @since 4.0.0 */ -export const bothWith: { - ( - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): Schedule -} = dual(3, ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 -): Schedule => +export const min = < + const Schedules extends NonEmptyReadonlyArray< + Schedule + > +>( + schedules: Schedules +): Schedule< + Duration.Duration, + UnionToIntersection< + Input + >, + Error, + Env +> => fromStep(effect.map( - effect.zip(toStep(self), toStep(other)), - ([stepLeft, stepRight]) => (now, input) => - Pull.matchEffect(stepLeft(now, input as Input), { - onSuccess: (leftResult) => - stepRight(now, input as Input2).pipe( - effect.map((rightResult) => - [ - combine(leftResult[0], rightResult[0]), - Duration.max(leftResult[1], rightResult[1]) - ] as [Output3, Duration.Duration] - ), - Pull.catchDone((rightDone) => Cause.done(combine(leftResult[0], rightDone as Output2))) - ), - onDone: (leftDone) => - stepRight(now, input as Input2).pipe( - effect.flatMap((rightResult) => Cause.done(combine(leftDone, rightResult[0]))), - Pull.catchDone((rightDone) => Cause.done(combine(leftDone, rightDone as Output2))) - ), - onFailure: effect.failCause - }) - ))) + effect.all(schedules.map(toStep)), + (steps) => (now, input) => + effect.flatMap( + effect.forEach(steps, (step) => + Pull.matchEffect(step(now, input as never), { + onSuccess: (result) => effect.succeed(result[1]), + onDone: () => effect.undefined, + onFailure: effect.failCause + })), + (results) => { + const duration = minDuration(results) + if (duration === undefined) { + return Cause.done(Duration.zero) + } + return effect.succeed([duration, duration] as [Duration.Duration, Duration.Duration]) + } + ) + )) + +const minDuration = (results: ReadonlyArray): Duration.Duration | undefined => { + let min: Duration.Duration | undefined = undefined + for (let i = 0; i < results.length; i++) { + const duration = results[i] + if (duration !== undefined) { + min = min === undefined ? duration : Duration.min(min, duration) + } + } + + return min +} /** - * Returns a new `Schedule` that follows `self` and outputs the inputs seen so - * far as an array. + * Schedule that always recurs, but will wait a certain amount between + * repetitions, given by `base * factor.pow(n)`, where `n` is the number of + * repetitions so far. Returns the current duration between recurrences. * - * **Details** + * **Example** (Retrying with exponential backoff) * - * This does not make the schedule run forever. The collected schedule stops - * when `self` stops and fails when `self` fails. + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * **Example** (Collecting schedule inputs) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.exponential("100 millis")) + * return yield* step(0, undefined) + * }) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Collect all inputs passed to the schedule - * const inputCollector = Schedule.collectInputs( - * Schedule.spaced("100 millis") - * ) - * - * const program = Effect.gen(function*() { - * let counter = 0 - * yield* Effect.repeat( - * Effect.gen(function*() { - * counter++ - * yield* Console.log(`Iteration ${counter}`) - * return `result-${counter}` - * }), - * inputCollector.pipe(Schedule.take(4)) - * ) - * }) - * ``` - * - * @category collecting - * @since 4.0.0 - */ -export const collectInputs = ( - self: Schedule -): Schedule, Input, Error, Env> => collectWhile(passthrough(self), () => effect.succeed(true)) - -/** - * Returns a new `Schedule` that follows `self` and outputs the schedule outputs - * seen so far as an array. - * - * **Details** - * - * This does not make the schedule run forever. The collected schedule stops - * when `self` stops and fails when `self` fails. - * - * **Example** (Collecting schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Collect all outputs from the schedule - * const outputCollector = Schedule.collectOutputs( - * Schedule.recurs(4) - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * outputCollector.pipe(Schedule.take(4)) - * ) - * }) - * ``` - * - * @category collecting - * @since 4.0.0 - */ -export const collectOutputs = ( - self: Schedule -): Schedule, Input, Error, Env> => collectWhile(self, () => effect.succeed(true)) - -/** - * Returns a new `Schedule` that recurs as long as the specified `predicate` - * returns `true`, collecting all outputs of the schedule into an array. - * - * **Example** (Collecting outputs while a condition holds) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Collect outputs while condition is met - * const collectWhileSmall = Schedule.collectWhile( - * Schedule.exponential("100 millis"), - * (metadata) => - * Effect.succeed(metadata.attempt <= 5 && metadata.elapsed < 2000) - * ) - * - * const conditionalProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const attempts = yield* Effect.repeat( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) - * return `attempt-${attempt}` - * }), - * collectWhileSmall - * ) - * - * yield* Console.log(`Collected attempts: [${attempts.join(", ")}]`) - * }) - * - * // Collect with effectful predicate - * const collectWithCheck = Schedule.collectWhile( - * Schedule.fixed("1 second"), - * (metadata) => - * Effect.gen(function*() { - * const shouldContinue = metadata.attempt < 5 - * yield* Console.log( - * `Check ${metadata.attempt}: continue = ${shouldContinue}` - * ) - * return shouldContinue - * }) - * ) - * - * const effectfulProgram = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.succeed("checked"), - * collectWithCheck - * ) - * - * yield* Console.log(`Final collection: ${results.length} items`) - * }) - * - * // Collect samples with condition - * const samples = [12, 18, 24, 30, 36] - * - * const collectSamples = Schedule.collectWhile( - * Schedule.spaced("200 millis"), - * (metadata) => - * Effect.succeed(metadata.attempt <= 5 && metadata.elapsed < 2000) - * ) - * - * const samplingProgram = Effect.gen(function*() { - * let index = 0 - * const collected = yield* Effect.repeat( - * Effect.gen(function*() { - * const sample = samples[index++] - * yield* Console.log(`Sample: ${sample}`) - * return sample - * }), - * collectSamples - * ) - * - * const average = collected.reduce((sum, s) => sum + s, 0) / collected.length - * yield* Console.log( - * `Collected ${collected.length} samples, average: ${average.toFixed(1)}` - * ) - * }) - * ``` - * - * @category collecting - * @since 2.0.0 - */ -export const collectWhile: { - ( - predicate: ( - metadata: Metadata - ) => boolean | Effect - ): ( - self: Schedule - ) => Schedule, Input, Error | Error2, Env | Env2> - ( - self: Schedule, - predicate: ( - metadata: Metadata - ) => boolean | Effect - ): Schedule, Input, Error | Error2, Env | Env2> -} = dual(2, ( - self: Schedule, - predicate: ( - metadata: Metadata - ) => boolean | Effect -): Schedule, Input, Error | Error2, Env | Env2> => - reduce(while_(self, predicate), () => [] as Array, (outputs, output) => { - outputs.push(output) - return outputs - })) - -/** - * Returns a new `Schedule` that recurs on the specified `Cron` schedule and - * outputs the duration between recurrences. - * - * **Example** (Scheduling work with cron expressions) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class ScheduledTaskError extends Data.TaggedError("ScheduledTaskError")<{ readonly message: string }> {} - * - * // Run every minute - * const everyMinute = Schedule.cron("* * * * *") - * - * const minutelyProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Running minutely task") - * return "minute" - * }), - * everyMinute.pipe( - * Schedule.take(3), // Run only 3 times for demo - * Schedule.tapOutput((duration) => - * Console.log(`Next execution in: ${duration}`) - * ) - * ) - * ) - * }) - * - * // Run every day at 2:30 AM - * const dailyBackup = Schedule.cron("30 2 * * *") - * - * const backupProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Running daily backup...") - * // Simulate backup process - * yield* Effect.sleep("2 seconds") - * yield* Console.log("Backup completed") - * return "backup-done" - * }), - * dailyBackup.pipe( - * Schedule.take(2) // Run 2 times for demo - * ) - * ) - * }) - * - * // Run every Monday at 9:00 AM with timezone - * const weeklyReport = Schedule.cron("0 9 * * 1", "America/New_York") - * - * const reportProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Generating weekly report...") - * const report = { - * week: 42, - * status: "ready" as const - * } - * yield* Console.log(`Report generated: ${JSON.stringify(report)}`) - * return report - * }), - * weeklyReport.pipe(Schedule.take(1)) - * ) - * }) - * - * // Run every 15 minutes during business hours (9 AM - 5 PM) - * const businessHoursCheck = Schedule.cron("0,15,30,45 9-17 * * 1-5") - * - * const businessProgram = Effect.gen(function*() { - * const statuses = ["healthy", "healthy", "degraded", "healthy"] as const - * let index = 0 - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Business hours health check...") - * const status = statuses[index++] - * yield* Console.log(`System status: ${status}`) - * return status - * }), - * businessHoursCheck.pipe( - * Schedule.take(4) // Demo with 4 checks - * ) - * ) - * }) - * - * // Run on specific days of the month - * const monthlyInvoice = Schedule.cron("0 10 1,15 * *") // 1st and 15th at 10 AM - * - * const invoiceProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Processing monthly invoices...") - * const invoiceCount = 72 - * yield* Console.log(`Processed ${invoiceCount} invoices`) - * return { count: invoiceCount, batch: "2024-01-a" } - * }), - * monthlyInvoice.pipe(Schedule.take(1)) - * ) - * }) - * - * // Complex cron with error handling - * const complexCron = Schedule.cron("0 2,4,6 * * *").pipe( - * Schedule.tapOutput((duration) => - * Console.log(`Scheduled to run again in ${duration}`) - * ) - * ) - * - * const robustProgram = Effect.gen(function*() { - * let attempt = 0 - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log("Complex scheduled task...") - * if (attempt === 1) { - * return yield* Effect.fail(new ScheduledTaskError({ message: "Scheduled task failed" })) - * } - * return "success" - * }), - * complexCron.pipe(Schedule.take(3)) - * ).pipe( - * Effect.catch((error: unknown) => - * Console.log(`Cron task error: ${String(error)}`) - * ) - * ) - * }) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const cron: { - (expression: Cron.Cron): Schedule - (expression: string, tz?: string | DateTime.TimeZone): Schedule -} = (expression: string | Cron.Cron, tz?: string | DateTime.TimeZone) => { - const parsed = Cron.isCron(expression) ? Result.succeed(expression) : Cron.parse(expression, tz) - return fromStep(effect.map(effect.fromResult(parsed), (cron) => (now, _) => - effect.sync(() => { - const next = Cron.next(cron, now).getTime() - const duration = Duration.millis(next - now) - return [duration, duration] - }))) -} - -/** - * Returns a new schedule that outputs the delay between each occurrence. - * - * **Example** (Extracting schedule delays) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Extract delays from an exponential backoff schedule - * const exponentialDelays = Schedule.delays( - * Schedule.exponential("100 millis").pipe(Schedule.take(5)) - * ) - * - * const delayProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task result" - * }), - * exponentialDelays.pipe( - * Schedule.tapOutput((delay) => - * Console.log(`Waiting ${delay} before next execution`) - * ) - * ) - * ) - * }) - * - * // Monitor delays from a Fibonacci schedule - * const fibonacciDelays = Schedule.delays( - * Schedule.fibonacci("200 millis").pipe(Schedule.take(8)) - * ) - * - * const fibDelayProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Fibonacci task"), - * fibonacciDelays.pipe( - * Schedule.tapOutput((delay) => Console.log(`Fibonacci delay: ${delay}`)) - * ) - * ) - * }) - * - * // Extract delays for analysis or logging - * const analyzeDelays = Schedule.delays( - * Schedule.spaced("1 second").pipe(Schedule.take(3)) - * ).pipe( - * Schedule.tapOutput((delay) => - * Effect.gen(function*() { - * yield* Console.log(`Recorded delay: ${delay}`) - * // In real applications, might send to metrics system - * }) - * ) - * ) - * - * // Combine delays with other schedules for complex timing - * const adaptiveSchedule = Schedule.unfold(100, (delay) => Effect.succeed(delay * 1.5)).pipe( - * Schedule.take(6) - * ) - * - * const adaptiveDelays = Schedule.delays(adaptiveSchedule) - * - * const adaptiveProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Adaptive task execution") - * return "completed" - * }), - * adaptiveDelays.pipe( - * Schedule.tapOutput((delay) => Console.log(`Adaptive delay: ${delay}`)) - * ) - * ) - * }) - * - * // Use delays to implement custom timing logic - * const customTimingSchedule = Schedule.delays( - * Schedule.exponential("50 millis").pipe(Schedule.take(4)) - * ).pipe( - * Schedule.map((delay) => Effect.succeed(`Next execution in ${delay}`)), - * Schedule.tapOutput((message) => Console.log(message)) - * ) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const delays = (self: Schedule): Schedule => - fromStep( - effect.map( - toStep(self), - (step) => (now, input) => - Pull.catchDone( - effect.map(step(now, input), ([_, duration]) => [duration, duration]), - (_) => Cause.done(Duration.zero) - ) - ) - ) - -/** - * Returns a schedule that recurs once after the specified duration. - * - * **When to use** - * - * Use when you need a schedule that recurs once after a fixed delay. - * - * **Details** - * - * The schedule outputs the configured duration for its first recurrence and - * then completes. - * - * **Example** (Recurring once after a duration) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * const program = Effect.repeat( - * Console.log("runs again after one second"), - * Schedule.duration("1 second") - * ) - * ``` - * - * @see {@link during} for recurring until a duration has elapsed - * - * @category constructors - * @since 2.0.0 - */ -export const duration = (durationInput: Duration.Input): Schedule => { - const duration = Duration.fromInputUnsafe(durationInput) - return fromStepWithMetadata(effect.succeed((meta) => - meta.attempt === 1 - ? effect.succeed([duration, duration]) - : Cause.done(Duration.zero) - )) -} - -/** - * Returns a new `Schedule` that will always recur, but only during the - * specified `duration` of time. - * - * **When to use** - * - * Use to bound a repeating or retrying schedule by elapsed time. - * - * **Example** (Repeating work during a duration) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Run a task for exactly 5 seconds, regardless of how many iterations - * const fiveSecondSchedule = Schedule.during("5 seconds") - * - * const timedProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed inside the time window") - * yield* Effect.sleep("500 millis") // Each task takes 500ms - * return "task done" - * }), - * fiveSecondSchedule.pipe( - * Schedule.tapOutput((elapsedDuration) => - * Console.log(`Total elapsed: ${elapsedDuration}`) - * ) - * ) - * ) - * - * yield* Console.log("Time limit reached!") - * }) - * - * // Combine with other schedules for time-bounded execution - * const timeAndCountLimited = Schedule.spaced("1 second").pipe( - * Schedule.both(Schedule.during("10 seconds")), // Stop after 10 seconds OR - * Schedule.both(Schedule.recurs(15)) // 15 attempts, whichever comes first - * ) - * - * // Burst execution within time window - * const burstWindow = Schedule.during("3 seconds") - * - * const burstProgram = Effect.gen(function*() { - * yield* Console.log("Starting burst execution...") - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Burst task") - * return "burst" - * }), - * burstWindow - * ) - * - * yield* Console.log("Burst window completed") - * }) - * - * // Timed retry window - retry for up to 30 seconds - * const timedRetry = Schedule.exponential("200 millis").pipe( - * Schedule.both(Schedule.during("30 seconds")) - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) - * - * if (attempt < 4) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * timedRetry - * ) - * - * yield* Console.log(`Result: ${result}`) - * }).pipe( - * Effect.catch((error: unknown) => Console.log(`Timed out: ${String(error)}`)) - * ) - * ``` - * - * @see {@link duration} for one delayed recurrence - * - * @category constructors - * @since 4.0.0 - */ -export const during = (duration: Duration.Input): Schedule => - while_( - elapsed, - ({ output }) => effect.succeed(Duration.isLessThanOrEqualTo(output, Duration.fromInputUnsafe(duration))) - ) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting a tuple of the outputs of both schedules. - * - * **When to use** - * - * Use when the combined schedule should continue while at least one schedule still recurs. - * - * **Example** (Combining schedules with either semantics) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Either continues as long as at least one schedule wants to continue - * const timeBasedSchedule = Schedule.spaced("2 seconds").pipe(Schedule.take(3)) - * const countBasedSchedule = Schedule.recurs(5) - * - * // Continues until both schedules are exhausted (either still wants to recur) - * const eitherSchedule = Schedule.either(timeBasedSchedule, countBasedSchedule) - * // Outputs: [time_result, count_result] tuple - * - * const program = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task completed" - * }), - * eitherSchedule.pipe( - * Schedule.tapOutput(([timeResult, countResult]) => - * Console.log(`Time: ${timeResult}, Count: ${countResult}`) - * ) - * ) - * ) - * - * yield* Console.log(`Total executions: ${results.length}`) - * }) - * - * // Either with different delay strategies - * const aggressiveRetry = Schedule.exponential("100 millis").pipe( - * Schedule.take(3) - * ) - * const fallbackRetry = Schedule.fixed("5 seconds").pipe(Schedule.take(2)) - * - * // Will use the more aggressive retry until it's exhausted, then fallback - * const combinedRetry = Schedule.either(aggressiveRetry, fallbackRetry) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Retry attempt ${attempt}`) - * - * if (attempt < 6) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * combinedRetry - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Either provides union semantics (OR logic) - * // Compare with both, which provides intersection semantics (AND logic) - * ``` - * - * @see {@link both} for continuing only while both schedules still recur - * - * @category combining - * @since 2.0.0 - */ -export const either: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> - ( - self: Schedule, - other: Schedule - ): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule<[Output, Output2], Input & Input2, Error | Error2, Env | Env2> => - eitherWith(self, other, (left, right) => [left, right])) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting the result of the left schedule (i.e. `self`). - * - * **When to use** - * - * Use when either schedule may keep recurrence going and only the left - * schedule's output is needed. - * - * **Example** (Combining either schedules and keeping the left output) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Combine two schedules with either semantics, keeping left output - * const primarySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("primary-result")), - * Schedule.take(2) - * ) - * const backupSchedule = Schedule.spaced("500 millis").pipe( - * Schedule.map(() => Effect.succeed("backup-result")) - * ) - * - * const combined = Schedule.eitherLeft(primarySchedule, backupSchedule) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * - * @category combining - * @since 4.0.0 - */ -export const eitherLeft: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => eitherWith(self, other, (output) => output)) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting the result of the right schedule (i.e. `other`). - * - * **When to use** - * - * Use when either schedule may keep recurrence going and only the right - * schedule's output is needed. - * - * **Example** (Combining either schedules and keeping the right output) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Combine two schedules with either semantics, keeping right output - * const primarySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("primary-result")), - * Schedule.take(2) - * ) - * const backupSchedule = Schedule.spaced("500 millis").pipe( - * Schedule.map(() => Effect.succeed("backup-result")) - * ) - * - * const combined = Schedule.eitherRight(primarySchedule, backupSchedule) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-done" - * }), - * combined.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * - * @category combining - * @since 4.0.0 - */ -export const eitherRight: { - ( - other: Schedule - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule - ): Schedule -} = dual(2, ( - self: Schedule, - other: Schedule -): Schedule => eitherWith(self, other, (_, output) => output)) - -/** - * Combines two `Schedule`s by recurring if either of the two schedules wants - * to recur, using the minimum of the two durations between recurrences and - * outputting the result of the combination of both schedule outputs using the - * specified `combine` function. - * - * **When to use** - * - * Use when either schedule may keep recurrence going and their outputs should be - * combined into a custom value. - * - * **Example** (Combining either schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Combine schedules with either semantics and custom combination - * const primarySchedule = Schedule.exponential("100 millis").pipe( - * Schedule.map(() => Effect.succeed("primary")), - * Schedule.take(2) - * ) - * const fallbackSchedule = Schedule.spaced("500 millis").pipe( - * Schedule.map(() => Effect.succeed("fallback")) - * ) - * - * const combined = Schedule.eitherWith( - * primarySchedule, - * fallbackSchedule, - * (primary, fallback) => `${primary}+${fallback}` - * ) - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task-result" - * }), - * combined.pipe(Schedule.take(5)) - * ) - * }) - * ``` - * - * @category combining - * @since 2.0.0 - */ -export const eitherWith: { - ( - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 - ): Schedule -} = dual(3, ( - self: Schedule, - other: Schedule, - combine: (selfOutput: Output, otherOutput: Output2) => Output3 -): Schedule => - fromStep(effect.map( - effect.zip(toStep(self), toStep(other)), - ([stepLeft, stepRight]) => (now, input) => - Pull.matchEffect(stepLeft(now, input as Input), { - onSuccess: (leftResult) => - stepRight(now, input as Input2).pipe( - effect.map((rightResult) => - [combine(leftResult[0], rightResult[0]), Duration.min(leftResult[1], rightResult[1])] as [ - Output3, - Duration.Duration - ] - ), - Pull.catchDone((rightDone) => - effect.succeed<[Output3, Duration.Duration]>([ - combine(leftResult[0], rightDone as Output2), - leftResult[1] - ]) - ) - ), - onFailure: effect.failCause, - onDone: (leftDone) => - stepRight(now, input as Input2).pipe( - effect.map((rightResult) => - [combine(leftDone, rightResult[0]), rightResult[1]] as [ - Output3, - Duration.Duration - ] - ), - Pull.catchDone((rightDone) => Cause.done(combine(leftDone, rightDone as Output2))) - ) - }) - ))) - -/** - * Schedule that always recurs and returns the total elapsed duration since the - * first recurrence. - * - * **Details** - * - * This schedule never stops and outputs the cumulative time that has passed since the schedule - * started executing. Useful for tracking execution time or implementing time-based logic. - * - * **Example** (Measuring elapsed schedule time) - * - * ```ts - * import { Console, Duration, Effect, Schedule } from "effect" - * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Running task..."), - * Schedule.spaced("1 second").pipe( - * Schedule.both(Schedule.elapsed), - * Schedule.tapOutput(([count, duration]) => - * Console.log(`Run ${count}, elapsed: ${Duration.toMillis(duration)}ms`) - * ), - * Schedule.take(5) - * ) - * ) - * }) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const elapsed: Schedule = fromStepWithMetadata( - effect.succeed((meta) => effect.succeed([Duration.millis(meta.elapsed), Duration.zero] as const)) -) - -/** - * Schedule that always recurs, but will wait a certain amount between - * repetitions, given by `base * factor.pow(n)`, where `n` is the number of - * repetitions so far. Returns the current duration between recurrences. - * - * **Example** (Retrying with exponential backoff) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryFailure extends Data.TaggedError("RetryFailure")<{ readonly message: string }> {} - * - * // Basic exponential backoff with default factor of 2 - * const basicExponential = Schedule.exponential("100 millis") - * // Delays: 100ms, 200ms, 400ms, 800ms, 1600ms, ... - * - * // Custom exponential backoff with factor 1.5 - * const gentleExponential = Schedule.exponential("200 millis", 1.5) - * // Delays: 200ms, 300ms, 450ms, 675ms, 1012ms, ... - * - * // Retry with exponential backoff (limited to 5 attempts) - * const retryPolicy = Schedule.exponential("50 millis").pipe( - * Schedule.both(Schedule.recurs(5)) - * ) - * - * const program = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 4) { - * yield* Console.log(`Attempt ${attempt} failed, retrying...`) - * return yield* Effect.fail(new RetryFailure({ message: `Failure ${attempt}` })) - * } - * return `Success on attempt ${attempt}` - * }), - * retryPolicy - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Will retry with delays: 50ms, 100ms, 200ms before success + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] * ``` * * @category constructors @@ -2030,61 +865,15 @@ export const exponential = ( * * **Example** (Retrying with Fibonacci backoff) * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Basic Fibonacci schedule starting with 100ms - * const fibSchedule = Schedule.fibonacci("100 millis") - * // Delays: 100ms, 100ms, 200ms, 300ms, 500ms, 800ms, 1300ms, ... - * - * // Retry with Fibonacci backoff for gradual increase - * const retryWithFib = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) - * - * if (attempt < 5) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * Schedule.fibonacci("50 millis").pipe( - * Schedule.both(Schedule.recurs(6)), // Maximum 6 retries - * Schedule.tapOutput((delay) => Console.log(`Next retry in ${delay}`)) - * ) - * ) + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * yield* Console.log(`Final result: ${result}`) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(Schedule.fibonacci("100 millis")) + * return yield* step(0, undefined) * }) * - * // Heartbeat with Fibonacci intervals (starts fast, gets slower) - * const adaptiveHeartbeat = Effect.gen(function*() { - * yield* Console.log("Heartbeat") - * return "pulse" - * }).pipe( - * Effect.repeat( - * Schedule.fibonacci("200 millis").pipe( - * Schedule.take(8) // First 8 heartbeats - * ) - * ) - * ) - * - * // Fibonacci vs exponential comparison - * const compareSchedules = Effect.gen(function*() { - * yield* Console.log("=== Fibonacci Delays ===") - * // 100ms, 100ms, 200ms, 300ms, 500ms, 800ms - * - * yield* Console.log("=== Exponential Delays ===") - * // 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms - * - * // Fibonacci grows more slowly than exponential - * }) + * await Effect.runPromise(program) // => [Duration.millis(100), Duration.millis(100)] * ``` * * @category constructors @@ -2125,50 +914,15 @@ export const fibonacci = (one: Duration.Input): Schedule => { * * **Example** (Repeating on fixed intervals) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Fixed interval schedule - recurs on a one-second cadence - * const everySecond = Schedule.fixed("1 second") - * - * // Health check that runs at fixed intervals - * const healthCheck = Effect.gen(function*() { - * yield* Console.log("Health check") - * yield* Effect.sleep("200 millis") // simulate health check work - * return "healthy" - * }).pipe( - * Effect.repeat(Schedule.fixed("2 seconds").pipe(Schedule.take(5))) - * ) - * - * // Difference between fixed and spaced: - * // - fixed: maintains constant rate regardless of action duration - * // - spaced: waits for the duration AFTER each action completes - * - * const longRunningTask = Effect.gen(function*() { - * yield* Console.log("Task started") - * yield* Effect.sleep("1.5 seconds") // Longer than interval - * yield* Console.log("Task completed") - * return "done" - * }) - * - * // Fixed schedule: if task takes 1.5s but interval is 1s, - * // next execution happens immediately (no pile-up) - * const fixedSchedule = longRunningTask.pipe( - * Effect.repeat(Schedule.fixed("1 second").pipe(Schedule.take(3))) - * ) - * - * // Comparing with spaced (waits 1s AFTER each task) - * const spacedSchedule = longRunningTask.pipe( - * Effect.repeat(Schedule.spaced("1 second").pipe(Schedule.take(3))) - * ) + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * const program = Effect.gen(function*() { - * yield* Console.log("=== Fixed Schedule Demo ===") - * yield* fixedSchedule - * - * yield* Console.log("=== Spaced Schedule Demo ===") - * yield* spacedSchedule + * const step = yield* Schedule.toStep(Schedule.fixed("1 second")) + * return yield* step(0, undefined) * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(1)] * ``` * * @see {@link spaced} for delaying after each action completes @@ -2201,158 +955,123 @@ export const fixed = (interval: Duration.Input): Schedule => { } /** - * Returns a new `Schedule` that maps the output of this schedule using the - * specified function. + * Returns a new `Schedule` that maps each schedule decision to a new output + * using the full schedule metadata. * - * **Example** (Mapping schedule outputs) + * **Details** * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * The callback receives the schedule input, output, selected delay duration, + * current attempt, and elapsed timing information. Return either a plain value + * or an `Effect` that produces the new output. * - * // Transform schedule output from number to string - * const countSchedule = Schedule.recurs(5).pipe( - * Schedule.map((count) => Effect.succeed(`Execution #${count + 1}`)) - * ) + * **Example** (Mapping schedule outputs) * - * // Map schedule delays to human-readable format - * const readableDelays = Schedule.exponential("100 millis").pipe( - * Schedule.map((duration) => Effect.succeed(`Next retry in ${duration}`)) - * ) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" * - * // Transform numeric output to structured data - * const structuredSchedule = Schedule.spaced("1 second").pipe( - * Schedule.map((recurrence) => Effect.succeed({ - * iteration: recurrence + 1, - * phase: recurrence < 5 ? "warmup" as const : "steady" as const - * })) + * const countSchedule = Schedule.recurs(5).pipe( + * Schedule.map(({ output: count }) => Effect.succeed(`Execution #${count + 1}`)) * ) - * * const program = Effect.gen(function*() { - * const results = yield* Effect.repeat( - * Effect.succeed("task completed"), - * structuredSchedule.pipe( - * Schedule.take(8), - * Schedule.tapOutput((info) => - * Console.log( - * `${info.phase} phase - iteration ${info.iteration}` - * ) - * ) - * ) - * ) - * - * yield* Console.log(`Completed iterations`) + * const step = yield* Schedule.toStep(countSchedule) + * const [output] = yield* step(0, undefined) + * return output * }) * - * // Map with effectful transformation - * const effectfulMap = Schedule.fixed("2 seconds").pipe( - * Schedule.map((count) => - * Effect.gen(function*() { - * yield* Console.log(`Processing count: ${count}`) - * return count * 10 - * }) - * ) - * ) - * - * // Combine mapping with other schedule operations - * const complexSchedule = Schedule.fibonacci("100 millis").pipe( - * Schedule.map((delay) => Effect.succeed(`Delay: ${delay}`)) - * ) + * await Effect.runPromise(program) // => "Execution #1" * ``` * * @category mapping * @since 2.0.0 */ export const map: { - ( - f: (output: Output) => Output2 | Effect - ): ( + ( + f: (metadata: Metadata) => Output2 | Effect + ): ( self: Schedule ) => Schedule ( self: Schedule, - f: (output: Output) => Output2 | Effect + f: (metadata: Metadata) => Output2 | Effect ): Schedule } = dual(2, ( self: Schedule, - f: (output: Output) => Output2 | Effect -): Schedule => { - const handle = Pull.matchEffect({ - onSuccess: ([output, duration]: [Output, Duration.Duration]) => { - const result = f(output) - if (!isEffect(result)) return effect.succeed([result, duration] as [Output2, Duration.Duration]) - return effect.map(result, (output) => [output, duration] as [Output2, Duration.Duration]) - }, - onFailure: effect.failCause, - onDone: (output: Output) => { - const result = f(output) - if (!isEffect(result)) return Cause.done(result as Output2) - return effect.flatMap(result, Cause.done) - } - }) - return fromStep(effect.map(toStep(self), (step) => (now, input) => handle(step(now, input)))) -}) + f: (metadata: Metadata) => Output2 | Effect +): Schedule => + fromStep(effect.map(toStep(self), (step) => { + const meta = metadataFn() + return (now, input) => + Pull.matchEffect(step(now, input), { + onSuccess: ([output, duration]) => { + const result = f({ ...meta(now, input), output, duration }) + if (!isEffect(result)) return effect.succeed([result, duration] as [Output2, Duration.Duration]) + return effect.map(result, (output) => [output, duration] as [Output2, Duration.Duration]) + }, + onFailure: effect.failCause, + onDone: (output) => { + const result = f({ ...meta(now, input), output, duration: Duration.zero }) + if (!isEffect(result)) return Cause.done(result as Output2) + return effect.flatMap(result, Cause.done) + } + }) + }))) /** * Returns a new `Schedule` that modifies the delay of the next recurrence * of the schedule using the specified effectful function. * - * **Example** (Modifying delays from schedule output) + * **Example** (Modifying delays from schedule metadata) * - * ```ts - * import { Console, Duration, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * - * // Modify delays based on output - increase delay on high iteration counts - * const adaptiveDelay = Schedule.recurs(10).pipe( - * Schedule.modifyDelay((output, delay) => { - * // Double the delay if we're seeing high iteration counts - * return Effect.succeed(output > 5 ? Duration.times(delay, 2) : delay) - * }) + * const schedule = Schedule.spaced("10 millis").pipe( + * Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.times(duration, 2))) * ) - * * const program = Effect.gen(function*() { - * let counter = 0 - * yield* Effect.repeat( - * Effect.gen(function*() { - * counter++ - * yield* Console.log(`Attempt ${counter}`) - * return counter - * }), - * adaptiveDelay.pipe(Schedule.take(8)) - * ) + * const step = yield* Schedule.toStep(schedule) + * const [, delay] = yield* step(0, undefined) + * return delay * }) + * + * await Effect.runPromise(program) // => Duration.millis(20) * ``` * * @category delays & timeouts * @since 2.0.0 */ export const modifyDelay: { - ( + ( f: ( - output: Output, - delay: Duration.Duration + metadata: Metadata ) => Effect - ): ( + ): ( self: Schedule ) => Schedule ( self: Schedule, f: ( - output: Output, - delay: Duration.Input + metadata: Metadata ) => Effect ): Schedule } = dual(2, ( self: Schedule, f: ( - output: Output, - delay: Duration.Input + metadata: Metadata ) => Effect ): Schedule => - fromStep(effect.map(toStep(self), (step) => (now, input) => - effect.flatMap( - step(now, input), - ([output, delay]) => effect.map(f(output, delay), (delay) => [output, Duration.fromInputUnsafe(delay)]) - )))) + fromStep(effect.map(toStep(self), (step) => { + const meta = metadataFn() + return (now, input) => + effect.flatMap( + step(now, input), + ([output, duration]) => + effect.map(f({ ...meta(now, input), output, duration }), (replacement) => [ + output, + Duration.fromInputUnsafe(replacement) + ]) + ) + }))) /** * Returns a new `Schedule` that randomly adjusts each recurrence delay. @@ -2374,9 +1093,9 @@ export const modifyDelay: { export const jittered = ( self: Schedule ): Schedule => - modifyDelay(self, (_, delay) => + modifyDelay(self, ({ duration }) => effect.map(randomNext, (random) => { - const millis = Duration.toMillis(Duration.fromInputUnsafe(delay)) + const millis = Duration.toMillis(duration) return Duration.millis(millis * 0.8 * (1 - random) + millis * 1.2 * random) })) @@ -2385,25 +1104,19 @@ export const jittered = ( * * **Example** (Passing inputs through as outputs) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" * - * // Create a schedule that outputs the inputs instead of original outputs * const inputSchedule = Schedule.passthrough( - * Schedule.exponential("100 millis").pipe(Schedule.take(3)) + * Schedule.exponential("100 millis").pipe(Schedule.upTo({ times: 3 })) * ) - * * const program = Effect.gen(function*() { - * let counter = 0 - * yield* Effect.repeat( - * Effect.gen(function*() { - * counter++ - * yield* Console.log(`Task ${counter} executed`) - * return `result-${counter}` - * }), - * inputSchedule - * ) + * const step = yield* Schedule.toStep(inputSchedule) + * const [output] = yield* step(0, "input") + * return output * }) + * + * await Effect.runPromise(program) // => "input" * ``` * * @category mapping @@ -2423,225 +1136,38 @@ export const passthrough = ( * Returns a `Schedule` which can only be stepped the specified number of * `times` before it terminates. * - * **When to use** - * - * Use when you need a counter schedule with no additional delay. - * - * **Gotchas** - * - * `recurs(n)` counts schedule recurrences, not the first evaluation of the - * effect being repeated or retried. For retrying, this means one initial - * attempt plus at most `n` retries. - * - * **Example** (Limiting recurrences) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Basic recurs - retry at most 3 times - * const maxThreeAttempts = Schedule.recurs(3) - * - * // Retry a failing operation at most 5 times - * const program = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) - * - * if (attempt < 4) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * - * return `Success on attempt ${attempt}` - * }), - * Schedule.recurs(5) // Will retry up to 5 times - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Combining recurs with other schedules for sophisticated retry logic - * const complexRetry = Schedule.exponential("100 millis").pipe( - * Schedule.both(Schedule.recurs(3)) // At most 3 retries - * ) - * - * // Allow ten recurrences after the initial run - * const tenRecurrences = Effect.gen(function*() { - * yield* Console.log("Executing task...") - * return "completed" - * }).pipe( - * Effect.repeat(Schedule.recurs(10)) - * ) - * - * // The schedule outputs the current recurrence count (0-based) - * const countingSchedule = Schedule.recurs(3).pipe( - * Schedule.tapOutput((count) => Console.log(`Execution #${count + 1}`)) - * ) - * ``` - * - * @see {@link take} for limiting an existing schedule - * - * @category constructors - * @since 2.0.0 - */ -export const recurs = (times: number): Schedule => - while_(forever, ({ attempt }) => effect.succeed(attempt <= times)) - -/** - * Returns a new `Schedule` that combines the outputs of the provided schedule - * using the specified effectful `combine` function and starting from the - * specified `initial` state. - * - * **Example** (Reducing schedule outputs) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Sum up execution counts from a counter schedule - * const sumSchedule = Schedule.reduce( - * Schedule.recurs(5), - * () => 0, // Initial sum - * (sum, count) => Effect.succeed(sum + count) // Add each count to the sum - * ) + * **When to use** * - * const sumProgram = Effect.gen(function*() { - * const finalSum = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "task" - * }), - * sumSchedule.pipe( - * Schedule.tapOutput((sum) => Console.log(`Running sum: ${sum}`)) - * ) - * ) - * - * yield* Console.log(`Final sum: ${finalSum}`) - * }) + * Use when you need a counter schedule with no additional delay. * - * // Build a history of execution counts - * const historySchedule = Schedule.reduce( - * Schedule.spaced("1 second").pipe(Schedule.take(4)), - * () => [] as Array, // Initial empty array - * (history, executionNumber) => Effect.succeed([...history, executionNumber]) - * ) + * **Gotchas** * - * const historyProgram = Effect.gen(function*() { - * const timeline = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Recording execution...") - * return "recorded" - * }), - * historySchedule - * ) - * - * yield* Console.log( - * `Execution timeline: ${timeline.join(", ")}` - * ) - * }) + * `recurs(n)` counts schedule recurrences, not the first evaluation of the + * effect being repeated or retried. For retrying, this means one initial + * attempt plus at most `n` retries. * - * // Accumulate metrics with effectful combination - * const metricsAccumulator = Schedule.reduce( - * Schedule.recurs(6), - * () => ({ total: 0, count: 0, max: 0 }), - * (metrics, executionCount) => Effect.succeed({ - * total: metrics.total + executionCount + 1, - * count: metrics.count + 1, - * max: Math.max(metrics.max, executionCount + 1) - * }) - * ) + * **Example** (Limiting recurrences) * - * const metricsProgram = Effect.gen(function*() { - * const finalMetrics = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Processing...") - * return "processed" - * }), - * metricsAccumulator - * ) - * - * const average = finalMetrics.total / finalMetrics.count - * yield* Console.log(`Final metrics: ${finalMetrics.count} executions`) - * yield* Console.log( - * `Average delay: ${average.toFixed(1)}ms, Max delay: ${finalMetrics.max}ms` - * ) - * }) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Build configuration state over time - * const configBuilder = Schedule.reduce( - * Schedule.fixed("500 millis").pipe(Schedule.take(3)), - * () => ({ retries: 1, timeout: 1000, backoff: 100 }), - * (config, executionNumber) => Effect.succeed({ - * retries: config.retries + 1, - * timeout: config.timeout * 1.5, - * backoff: Math.min(config.backoff * 2, 5000) - * }) + * const executions: Array = [] + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(Schedule.recurs(3)), + * Effect.as(executions) * ) * - * const configProgram = Effect.gen(function*() { - * const finalConfig = yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Updating configuration...") - * return "updated" - * }), - * configBuilder.pipe( - * Schedule.tapOutput((config) => - * Console.log( - * `Config: retries=${config.retries}, timeout=${config.timeout}ms` - * ) - * ) - * ) - * ) - * - * yield* Console.log(`Final config: ${JSON.stringify(finalConfig)}`) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3, 4] * ``` * - * @category folding + * @see {@link upTo} for limiting an existing schedule + * + * @category constructors * @since 2.0.0 */ -export const reduce: { - ( - initial: LazyArg, - combine: (state: State, output: Output) => State | Effect - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - initial: LazyArg, - combine: (state: State, output: Output) => State | Effect - ): Schedule -} = dual(3, ( - self: Schedule, - initial: LazyArg, - combine: (state: State, output: Output) => State | Effect -): Schedule => - fromStep(effect.map(toStep(self), (step) => { - let state = initial() - return (now, input) => - Pull.matchEffect(step(now, input), { - onSuccess([output, delay]) { - const next = combine(state, output) - if (!isEffect(next)) { - state = next - return effect.succeed([next, delay] as [State, Duration.Duration]) - } - return effect.map(next, (nextState) => { - state = nextState - return [nextState, delay] - }) - }, - onFailure: effect.failCause, - onDone(output) { - const next = combine(state, output) - return isEffect(next) ? effect.flatMap(next, Cause.done) : Cause.done(next) - } - }) - }))) +export const recurs = (times: number): Schedule => + while_(forever, ({ attempt }) => effect.succeed(attempt <= times)) /** * Returns a schedule that recurs continuously, each repetition spaced the @@ -2653,45 +1179,15 @@ export const reduce: { * * **Example** (Repeating with fixed spacing) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Basic spaced schedule - runs every 2 seconds - * const everyTwoSeconds = Schedule.spaced("2 seconds") - * - * // Heartbeat that runs indefinitely with fixed spacing - * const heartbeat = Effect.gen(function*() { - * yield* Console.log("Heartbeat") - * }).pipe( - * Effect.repeat(everyTwoSeconds) - * ) - * - * // Limited repeat - run only 5 times with 1-second spacing - * const limitedTask = Effect.gen(function*() { - * yield* Console.log("Executing scheduled task...") - * yield* Effect.sleep("500 millis") // simulate work - * return "Task completed" - * }).pipe( - * Effect.repeat( - * Schedule.spaced("1 second").pipe(Schedule.take(5)) - * ) - * ) - * - * // Simple spaced schedule with limited repetitions - * const limitedSpaced = Schedule.spaced("100 millis").pipe( - * Schedule.both(Schedule.recurs(5)) // at most 5 times - * ) + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * const program = Effect.gen(function*() { - * yield* Console.log("Starting spaced execution...") - * - * yield* Effect.repeat( - * Effect.succeed("work item"), - * limitedSpaced - * ) - * - * yield* Console.log("Completed executions") + * const step = yield* Schedule.toStep(Schedule.spaced("2 seconds")) + * return yield* step(0, undefined) * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(2)] * ``` * * @see {@link fixed} for recurrence aligned to a regular cadence @@ -2716,23 +1212,20 @@ export const spaced = (duration: Duration.Input): Schedule => { * * **Example** (Tapping schedule metadata) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * const monitoredSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(5), - * Schedule.tap((metadata) => - * Console.log( - * `Attempt ${metadata.attempt} produced ${metadata.output} ` + - * `after ${metadata.elapsed}ms; next delay is ${metadata.duration}` - * ) - * ) - * ) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" * - * const program = Effect.retry( - * Effect.fail("transient error"), - * monitoredSchedule + * const attempts: Array = [] + * const monitoredSchedule = Schedule.recurs(2).pipe( + * Schedule.tap((metadata) => Effect.sync(() => attempts.push(metadata.attempt))) * ) + * const program = Effect.gen(function*() { + * const step = yield* Schedule.toStep(monitoredSchedule) + * const [output] = yield* step(0, undefined) + * return { attempts, output } + * }) + * + * await Effect.runPromise(program) // => { attempts: [1], output: 0 } * ``` * * @category sequencing @@ -2759,474 +1252,82 @@ export const tap: { }))) /** - * Returns a new `Schedule` that allows execution of an effectful function for - * every input to the schedule, but does not alter the inputs and outputs of - * the schedule. - * - * **Example** (Tapping retry inputs) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryError extends Data.TaggedError("RetryError")<{ readonly message: string }> {} - * - * // Log retry errors for debugging - * const errorLoggingSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(3), - * Schedule.tapInput((error: RetryError) => - * Console.log(`Retry triggered by error: ${String(error)}`) - * ) - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 4) { - * return yield* Effect.fail(new RetryError({ message: `Network timeout on attempt ${attempt}` })) - * } - * return `Success on attempt ${attempt}` - * }), - * errorLoggingSchedule - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Monitor input frequency for metrics - * const inputMonitoringSchedule = Schedule.spaced("1 second").pipe( - * Schedule.take(5), - * Schedule.tapInput((input: unknown) => - * Effect.gen(function*() { - * yield* Console.log(`Input type: ${typeof input}`) - * // In real applications, might send metrics to monitoring system - * }) - * ) - * ) - * - * // Input validation with side effects - * const validatingSchedule = Schedule.fixed("500 millis").pipe( - * Schedule.take(4), - * Schedule.tapInput((input: any) => - * Effect.gen(function*() { - * if (typeof input === "object" && input !== null) { - * yield* Console.log(`Valid object input: ${JSON.stringify(input)}`) - * } else { - * yield* Console.log(`Warning: Non-object input received: ${input}`) - * } - * }) - * ) - * ) - * - * const validationProgram = Effect.gen(function*() { - * let count = 0 - * - * yield* Effect.repeat( - * Effect.gen(function*() { - * count++ - * yield* Console.log("Task with validation") - * return { data: `sample-${count}` } - * }), - * validatingSchedule - * ) - * }) - * - * // Conditional alerting based on input - * const alertingSchedule = Schedule.exponential("200 millis").pipe( - * Schedule.take(6), - * Schedule.tapInput((error: RetryError) => - * Effect.gen(function*() { - * if (String(error).includes("critical")) { - * yield* Console.log(`Critical error: ${String(error)}`) - * // In real applications, might trigger alerts or notifications - * } else { - * yield* Console.log(`Regular error: ${String(error)}`) - * } - * }) - * ) - * ) - * - * const alertProgram = Effect.gen(function*() { - * let attempt = 0 - * - * yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * const isCritical = attempt === 3 - * const errorType = isCritical - * ? "critical database failure" - * : "temporary network issue" - * return yield* Effect.fail(new RetryError({ message: errorType })) - * }), - * alertingSchedule - * ).pipe( - * Effect.catch((error: unknown) => - * Console.log(`All retries exhausted: ${String(error)}`) - * ) - * ) - * }) - * - * // Chain multiple input taps for different purposes - * const comprehensiveSchedule = Schedule.fibonacci("100 millis").pipe( - * Schedule.take(5), - * Schedule.tapInput((error: RetryError) => - * Console.log(`Error occurred: ${error._tag}`) - * ), - * Schedule.tapInput((error: RetryError) => - * String(error).length > 20 - * ? Console.log("Long error message detected") - * : Effect.void - * ) - * ) - * ``` - * - * @category sequencing - * @since 2.0.0 - */ -export const tapInput: { - ( - f: (input: Input) => Effect - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - f: (input: Input) => Effect - ): Schedule -} = dual(2, ( - self: Schedule, - f: (input: Input) => Effect -): Schedule => - fromStep(effect.map( - toStep(self), - (step) => (now, input) => effect.andThen(f(input), step(now, input)) - ))) - -/** - * Returns a new `Schedule` that allows execution of an effectful function for - * every output of the schedule, but does not alter the inputs and outputs of - * the schedule. - * - * **Example** (Tapping schedule outputs) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Log schedule outputs for debugging/monitoring - * const monitoredSchedule = Schedule.exponential("100 millis").pipe( - * Schedule.take(5), - * Schedule.tapOutput((delay) => Console.log(`Next delay will be: ${delay}`)) - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 - * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * if (attempt < 4) { - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } - * return `Success on attempt ${attempt}` - * }), - * monitoredSchedule - * ) - * - * yield* Console.log(`Final result: ${result}`) - * }) - * - * // Tap output for metrics collection - * const metricsSchedule = Schedule.spaced("1 second").pipe( - * Schedule.take(10), - * Schedule.tapOutput((executionCount) => - * Effect.gen(function*() { - * // Simulate metrics collection - * yield* Console.log(`Recording metric: execution_count=${executionCount}`) - * // In real code, this might send to monitoring system - * }) - * ) - * ) - * - * // Tap output with conditional side effects - * const alertingSchedule = Schedule.fibonacci("200 millis").pipe( - * Schedule.take(8), - * Schedule.tapOutput((delay) => - * Effect.gen(function*() { - * const delayMs = delay.toString() - * if (delayMs.includes("1000")) { // Alert on delays >= 1 second - * yield* Console.log(`High delay detected: ${delay}`) - * } - * }) - * ) - * ) - * - * const healthCheckProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Performing health check...") - * return "healthy" - * }), - * alertingSchedule - * ) - * }) - * - * // Chain multiple taps for different purposes - * const comprehensiveSchedule = Schedule.fixed("500 millis").pipe( - * Schedule.take(6), - * Schedule.tapOutput((count) => Console.log(`Execution ${count + 1}`)), - * Schedule.tapOutput((count) => - * count % 3 === 0 - * ? Console.log("Checkpoint reached") - * : Effect.void - * ) - * ) - * ``` - * - * @category sequencing - * @since 2.0.0 - */ -export const tapOutput: { - ( - f: (output: Output) => Effect - ): ( - self: Schedule - ) => Schedule - ( - self: Schedule, - f: (output: Output) => Effect - ): Schedule -} = dual(2, ( - self: Schedule, - f: (output: Output) => Effect -): Schedule => - fromStep(effect.map( - toStep(self), - (step) => (now, input) => effect.tap(step(now, input), ([output]) => f(output)) - ))) - -/** - * Returns a new `Schedule` that takes at most the specified number of outputs - * from the schedule. Once the specified number of outputs is reached, the - * schedule will stop. + * Returns a new `Schedule` that limits an existing schedule by elapsed + * duration, number of outputs, or both. * * **When to use** * - * Use to limit an existing schedule while preserving its output and delay behavior. + * Use to bound an existing schedule while preserving its output and delay + * behavior. When both `duration` and `times` are specified, the schedule + * stops as soon as either limit is reached. * * **Gotchas** * - * `take(n)` limits schedule outputs. When used with repeat or retry, the - * effect is evaluated once before the schedule is stepped, so the total number - * of evaluations can be one greater than the number of outputs taken. - * - * **Example** (Taking a limited number of recurrences) - * - * ```ts - * import { Console, Data, Effect, Schedule } from "effect" - * - * class RetryAttemptError extends Data.TaggedError("RetryAttemptError")<{ readonly message: string }> {} - * - * // Limit an infinite schedule to five recurrences - * const limitedHeartbeat = Schedule.spaced("1 second").pipe( - * Schedule.take(5) // Will stop after 5 schedule outputs - * ) - * - * const heartbeatProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Heartbeat") - * return "pulse" - * }), - * limitedHeartbeat - * ) - * - * yield* Console.log("Heartbeat sequence completed") - * }) - * - * // Limit retry attempts to a specific number - * const limitedRetry = Schedule.exponential("100 millis").pipe( - * Schedule.take(3) // At most 3 retry attempts - * ) - * - * const retryProgram = Effect.gen(function*() { - * let attempt = 0 + * The `times` option limits schedule outputs. When used with repeat or retry, + * the effect is evaluated once before the schedule is stepped, so the total + * number of evaluations can be one greater than the configured number of + * outputs. * - * const result = yield* Effect.retry( - * Effect.gen(function*() { - * attempt++ - * yield* Console.log(`Attempt ${attempt}`) + * The `duration` option is based on the elapsed time observed by the schedule + * step. Long-running effects can cause the duration limit to be detected on the + * following schedule step. * - * if (attempt < 5) { // Will fail more than 3 times - * return yield* Effect.fail(new RetryAttemptError({ message: `Attempt ${attempt} failed` })) - * } + * **Example** (Limiting by duration and recurrence count) * - * return `Success on attempt ${attempt}` - * }), - * limitedRetry - * ) - * - * yield* Console.log(`Result: ${result}`) - * }).pipe( - * Effect.catch((error: unknown) => - * Console.log(`Failed after limited retries: ${String(error)}`) - * ) - * ) + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // Combine take with other schedule operations - * const samplingSchedule = Schedule.fixed("500 millis").pipe( - * Schedule.take(10), // Take at most 10 schedule outputs - * Schedule.map((count) => Effect.succeed(`Sample #${count + 1}`)) + * const executions: Array = [] + * const schedule = Schedule.forever.pipe(Schedule.upTo({ times: 2 })) + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(schedule), + * Effect.as(executions) * ) * - * const samplingProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * const value = "sample" - * yield* Console.log(`Sampled value: ${value}`) - * return value - * }), - * samplingSchedule.pipe( - * Schedule.tapOutput((label) => Console.log(`Completed: ${label}`)) - * ) - * ) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] * ``` * - * @see {@link recurs} for creating a count-limited schedule - * - * @category taking + * @category filtering * @since 4.0.0 */ -export const take: { - (n: number): ( +export const upTo: { + (options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + }): ( self: Schedule ) => Schedule ( self: Schedule, - n: number + options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + } ): Schedule } = dual(2, ( self: Schedule, - n: number -): Schedule => while_(self, ({ attempt }) => effect.succeed(attempt <= n))) - -/** - * Creates a schedule that unfolds a state by repeatedly applying a function, - * outputting the current state and computing the next state. - * - * **Example** (Unfolding schedule state) - * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Counter schedule that increments by 1 each time - * const counterSchedule = Schedule.unfold(0, (n) => Effect.succeed(n + 1)) - * // Outputs: 0, 1, 2, 3, 4, 5, ... - * - * const countingProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Task executed") - * return "done" - * }), - * counterSchedule.pipe( - * Schedule.take(5), - * Schedule.tapOutput((count) => Console.log(`Count: ${count}`)) - * ) - * ) - * }) - * - * // Fibonacci sequence schedule - * const fibonacciSchedule = Schedule.unfold( - * [0, 1] as [number, number], - * ([a, b]) => Effect.succeed([b, a + b] as [number, number]) - * ) - * // Outputs: [0,1], [1,1], [1,2], [2,3], [3,5], [5,8], ... - * - * const fibProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Console.log("Fibonacci step"), - * fibonacciSchedule.pipe( - * Schedule.take(8), - * Schedule.tapOutput(([a, b]) => Console.log(`Fib: ${a}, next: ${b}`)) - * ) - * ) - * }) - * - * // Effectful unfold - exponential backoff with state - * const exponentialState = Schedule.unfold( - * 100, - * (delayMs) => - * Effect.gen(function*() { - * yield* Console.log(`Current delay: ${delayMs}ms`) - * return Math.min(delayMs * 2, 5000) // Cap at 5 seconds - * }) - * ) - * - * // Deterministic delay adjustment schedule - * const adjustedDelaySchedule = Schedule.unfold( - * { delay: 1000, adjustment: 100 }, - * ({ delay, adjustment }) => - * Effect.gen(function*() { - * const nextDelay = Math.max(100, delay + adjustment) - * yield* Console.log(`Adjusted delay: ${nextDelay}ms`) - * return { delay: nextDelay, adjustment: adjustment * -1 } - * }) - * ) - * - * // State machine schedule - * type State = "init" | "warming" | "active" | "cooling" - * const stateMachineSchedule = Schedule.unfold("init" as State, (state) => { - * switch (state) { - * case "init": - * return Effect.succeed("warming" as State) - * case "warming": - * return Effect.succeed("active" as State) - * case "active": - * return Effect.succeed("cooling" as State) - * case "cooling": - * return Effect.succeed("active" as State) - * } - * }) - * - * const stateMachineProgram = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("State machine step") - * return "step" - * }), - * stateMachineSchedule.pipe( - * Schedule.take(10), - * Schedule.tapOutput((state) => Console.log(`State: ${state}`)) - * ) - * ) - * }) - * ``` - * - * @category constructors - * @since 2.0.0 - */ -export const unfold = ( - initial: State, - next: (state: State) => Effect -): Schedule => - fromStep(effect.sync(() => { - let state = initial - return constant(effect.map( - effect.suspend(() => next(state)), - (nextState) => { - const prev = state - state = nextState - return [prev, Duration.zero] as const - } + options: { + readonly duration?: Duration.Input | undefined + readonly times?: number | undefined + } +): Schedule => { + const duration = options.duration === undefined ? undefined : Duration.fromInputUnsafe(options.duration) + return while_(self, ({ attempt, elapsed }) => + effect.succeed( + (options.times === undefined || attempt <= options.times) && + (duration === undefined || Duration.isLessThanOrEqualTo(Duration.millis(elapsed), duration)) )) - })) +}) const while_: { + >( + predicate: ( + metadata: Metadata + ) => metadata is Meta + ): ( + self: Schedule + ) => Schedule ( predicate: ( metadata: Metadata @@ -3234,6 +1335,12 @@ const while_: { ): ( self: Schedule ) => Schedule + >( + self: Schedule, + predicate: ( + metadata: Metadata + ) => metadata is Meta + ): Schedule ( self: Schedule, predicate: ( @@ -3274,8 +1381,7 @@ export { * `Effect`, preserves the output and delay when it returns * `true`, and stops the schedule when it returns `false`. * - * @see {@link collectWhile} for collecting outputs while using the same predicate - * @see {@link take} for stopping after a fixed number of schedule outputs + * @see {@link upTo} for stopping after a fixed number of schedule outputs * * @category filtering * @since 4.0.0 @@ -3300,21 +1406,15 @@ export { * * **Example** (Repeating on aligned windows) * - * ```ts - * import { Console, Effect, Schedule } from "effect" - * - * // Execute tasks at regular intervals aligned to window boundaries - * const windowSchedule = Schedule.windowed("5 seconds") + * ```ts import.meta.vitest + * import { Duration, Effect, Schedule } from "effect" * * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Window task executed") - * return "window-task" - * }), - * windowSchedule.pipe(Schedule.take(4)) - * ) + * const step = yield* Schedule.toStep(Schedule.windowed("5 seconds")) + * return yield* step(0, undefined) * }) + * + * await Effect.runPromise(program) // => [0, Duration.seconds(5)] * ``` * * @category constructors @@ -3340,21 +1440,18 @@ export const windowed = (interval: Duration.Input): Schedule => { * * **Example** (Repeating forever) * - * ```ts - * import { Console, Effect, Schedule } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schedule } from "effect" + * import { TestClock } from "effect/testing" * - * // A schedule that runs forever with no delay - * const infiniteSchedule = Schedule.forever + * const executions: Array = [] + * const schedule = Schedule.forever.pipe(Schedule.upTo({ times: 2 })) + * const program = Effect.sync(() => executions.push(executions.length + 1)).pipe( + * Effect.repeat(schedule), + * Effect.as(executions) + * ) * - * const program = Effect.gen(function*() { - * yield* Effect.repeat( - * Effect.gen(function*() { - * yield* Console.log("Running forever...") - * return "continuous-task" - * }), - * infiniteSchedule.pipe(Schedule.take(5)) // Limit for demo - * ) - * }) + * await Effect.runPromise(Effect.provide(program, TestClock.layer())) // => [1, 2, 3] * ``` * * @category constructors @@ -3390,41 +1487,6 @@ export { identity_ as identity } -/** - * Ensures that a schedule's input type extends a given type `T`. - * - * **When to use** - * - * Use when you need a generic helper to prove that an existing schedule can - * consume a required input type without changing runtime behavior. - * - * **Example** (Constraining schedule input types) - * - * ```ts - * import { Schedule } from "effect" - * - * declare const StringInputSchedule: Schedule.Schedule - * declare const NumberInputSchedule: Schedule.Schedule - * - * const satisfiesStringInput = Schedule.satisfiesInputType() - * - * // This works because the schedule input type is string. - * const validSchedule = satisfiesStringInput(StringInputSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesStringInput(NumberInputSchedule) - * ``` - * - * @see {@link setInputType} for adapting an input-agnostic schedule - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesInputType = () => -( - self: Schedule -): Schedule => self - /** * Sets the input type of the provided schedule without altering its behavior. * @@ -3439,126 +1501,17 @@ export const satisfiesInputType = () => * * **Example** (Setting a schedule input type) * - * ```ts + * ```ts import.meta.vitest * import { Schedule } from "effect" * * const schedule = Schedule.recurs(3).pipe( * Schedule.setInputType() * ) + * Schedule.isSchedule(schedule) // => true * ``` * - * @see {@link satisfiesInputType} for checking an existing input type - * * @category utility types * @since 4.0.0 */ export const setInputType = () => (self: Schedule): Schedule => self - -/** - * Ensures that a schedule's output type extends a given type `T`. - * - * **Details** - * - * This helper is checked at compile time and does not change the schedule's - * runtime behavior. - * - * **Example** (Constraining schedule output types) - * - * ```ts - * import { Schedule } from "effect" - * - * declare const StringOutputSchedule: Schedule.Schedule - * declare const NumberOutputSchedule: Schedule.Schedule - * - * const satisfiesStringOutput = Schedule.satisfiesOutputType() - * - * // This works because the schedule output type is string. - * const validSchedule = satisfiesStringOutput(StringOutputSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesStringOutput(NumberOutputSchedule) - * ``` - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesOutputType = () => -( - self: Schedule -): Schedule => self - -/** - * Ensures that a schedule's error type extends a given type `T`. - * - * **Details** - * - * This helper is checked at compile time and does not change the schedule's - * runtime behavior. - * - * **Example** (Constraining schedule error types) - * - * ```ts - * import { Data, Schedule } from "effect" - * - * // Create a custom error using Data.TaggedError - * class CustomError extends Data.TaggedError("CustomError")<{ - * message: string - * }> {} - * - * declare const CustomErrorSchedule: Schedule.Schedule - * declare const StringErrorSchedule: Schedule.Schedule - * - * const satisfiesCustomError = Schedule.satisfiesErrorType() - * - * // This works because the schedule error type is CustomError. - * const validSchedule = satisfiesCustomError(CustomErrorSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesCustomError(StringErrorSchedule) - * ``` - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesErrorType = () => -( - self: Schedule -): Schedule => self - -/** - * Ensures that a schedule's context type extends a given type `T`. - * - * **Details** - * - * This helper is checked at compile time and does not change the schedule's - * runtime behavior. - * - * **Example** (Constraining schedule service types) - * - * ```ts - * import { Schedule } from "effect" - * - * interface Logger { - * readonly log: (message: string) => void - * } - * - * declare const LoggerSchedule: Schedule.Schedule - * declare const NumberSchedule: Schedule.Schedule - * - * const satisfiesLogger = Schedule.satisfiesServicesType() - * - * // This works because the schedule context type is Logger. - * const validSchedule = satisfiesLogger(LoggerSchedule) - * - * // This would cause a TypeScript compilation error: - * // const invalidSchedule = satisfiesLogger(NumberSchedule) - * ``` - * - * @category utility types - * @since 4.0.0 - */ -export const satisfiesServicesType = () => -( - self: Schedule -): Schedule => self diff --git a/packages/effect/src/Scheduler.ts b/packages/effect/src/Scheduler.ts index 4e076de55..a60495eb0 100644 --- a/packages/effect/src/Scheduler.ts +++ b/packages/effect/src/Scheduler.ts @@ -26,7 +26,7 @@ import type * as Fiber from "./Fiber.ts" * priorities, and decides when fibers should yield control after consuming * their operation budget. * - * @category models + * @category services * @since 2.0.0 */ export interface Scheduler { @@ -72,25 +72,46 @@ export interface SchedulerDispatcher { * The default value creates a `MixedScheduler`. Provide this service to * customize execution mode, task dispatching, or yield behavior. * - * @category references + * @category services * @since 2.0.0 */ export const Scheduler: Context.Reference = Context.Reference("effect/Scheduler", { + fiberCached: true, defaultValue: () => new MixedScheduler() }) -const setImmediate = "setImmediate" in globalThis - ? (f: () => void) => { +const setMicrotask = (f: () => void) => { + let cancelled = false + Promise.resolve().then(() => { + if (!cancelled) f() + }) + return (): void => { + cancelled = true + } +} + +const setTimer: (f: () => void) => () => void = "setImmediate" in globalThis + ? (f) => { // @ts-ignore const timer = globalThis.setImmediate(f) // @ts-ignore return (): void => globalThis.clearImmediate(timer) } - : (f: () => void) => { + : (f) => { const timer = setTimeout(f, 0) return (): void => clearTimeout(timer) } +// Some runtimes (e.g. Cloudflare Workers) throw when a timer is set in global +// scope. Fall back to a microtask so effects can still yield at module load. +const setImmediate = (f: () => void) => { + try { + return setTimer(f) + } catch { + return setMicrotask(f) + } +} + class PriorityBuckets { buckets: Array<[priority: number, tasks: Array<() => void>]> = [] @@ -135,7 +156,7 @@ class PriorityBuckets { * operation counts to decide when fibers should yield, and is the default * scheduler implementation. * - * @category schedulers + * @category models * @since 2.0.0 */ export class MixedScheduler implements Scheduler { @@ -144,10 +165,10 @@ export class MixedScheduler implements Scheduler { constructor( executionMode: "sync" | "async" = "async", - setImmediateFn: (f: () => void) => () => void = setImmediate + setImmediateFn?: (f: () => void) => () => void ) { this.executionMode = executionMode - this.setImmediate = setImmediateFn + this.setImmediate = setImmediateFn ?? (executionMode === "sync" ? setMicrotask : setImmediate) } /** @@ -161,7 +182,7 @@ export class MixedScheduler implements Scheduler { * @since 2.0.0 */ shouldYield(fiber: Fiber.Fiber) { - return fiber.currentOpCount >= fiber.maxOpsBeforeYield + return fiber.currentOpCount >= fiber.cache.maxOpsBeforeYield } /** @@ -252,10 +273,11 @@ class MixedSchedulerDispatcher implements SchedulerDispatcher { * * @see {@link PreventSchedulerYield} for bypassing scheduler yield checks entirely rather than tuning the operation budget * - * @category references + * @category services * @since 4.0.0 */ export const MaxOpsBeforeYield = Context.Reference("effect/Scheduler/MaxOpsBeforeYield", { + fiberCached: true, defaultValue: () => 2048 }) @@ -277,9 +299,10 @@ export const MaxOpsBeforeYield = Context.Reference("effect/Scheduler/Max * @see {@link MaxOpsBeforeYield} for tuning yield frequency without disabling yield checks * @see {@link Scheduler} for providing custom scheduler yield behavior * - * @category references + * @category services * @since 4.0.0 */ export const PreventSchedulerYield = Context.Reference("effect/Scheduler/PreventSchedulerYield", { + fiberCached: true, defaultValue: () => false }) diff --git a/packages/effect/src/Schema.ts b/packages/effect/src/Schema.ts index ac49d9a2c..ab871a45e 100644 --- a/packages/effect/src/Schema.ts +++ b/packages/effect/src/Schema.ts @@ -12,14 +12,12 @@ * @since 4.0.0 */ -/** @effect-diagnostics schemaStructWithTag:skip-file */ -import type { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec" import * as Arr from "./Array.ts" import * as BigDecimal_ from "./BigDecimal.ts" import type * as Brand from "./Brand.ts" +import * as ByteSize_ from "./ByteSize.ts" import * as Cause_ from "./Cause.ts" import * as Chunk_ from "./Chunk.ts" -import type * as Combiner from "./Combiner.ts" import * as Data from "./Data.ts" import * as DateTime from "./DateTime.ts" import type { Differ } from "./Differ.ts" @@ -27,46 +25,62 @@ import * as Duration_ from "./Duration.ts" import * as Effect from "./Effect.ts" import * as Encoding from "./Encoding.ts" import * as Equal from "./Equal.ts" -import * as Equivalence from "./Equivalence.ts" +import type * as Equivalence from "./Equivalence.ts" import * as Exit_ from "./Exit.ts" import type { Formatter } from "./Formatter.ts" import { format, formatPropertyKey } from "./Formatter.ts" -import { identity, memoize } from "./Function.ts" +import { identity } from "./Function.ts" +import type * as Graph_ from "./Graph.ts" import * as HashMap_ from "./HashMap.ts" import * as HashSet_ from "./HashSet.ts" import * as core from "./internal/core.ts" +import { effectIsExit } from "./internal/effect.ts" +import * as InternalGraph from "./internal/graph.ts" +import * as InternalRecord from "./internal/record.ts" import * as InternalAnnotations from "./internal/schema/annotations.ts" -import * as InternalArbitrary from "./internal/schema/arbitrary.ts" -import * as InternalEquivalence from "./internal/schema/equivalence.ts" -import * as InternalStandard from "./internal/schema/representation.ts" -import * as InternalSchema from "./internal/schema/schema.ts" -import { SchemaError } from "./internal/schema/schema.ts" -import * as JsonPatch from "./JsonPatch.ts" -import * as JsonSchema from "./JsonSchema.ts" +import * as InternalMake from "./internal/schema/make.ts" +import * as InternalStandardSchema from "./internal/schema/standardSchema.ts" +import * as InternalToCodec from "./internal/schema/toCodec.ts" +import * as InternalToDifferJsonPatch from "./internal/schema/toDifferJsonPatch.ts" +import * as InternalToEncoderXml from "./internal/schema/toEncoderXml.ts" +import * as InternalEquivalence from "./internal/schema/toEquivalence.ts" +import * as InternalToFormatter from "./internal/schema/toFormatter.ts" +import * as InternalToIso from "./internal/schema/toIso.ts" +import * as InternalToJsonSchemaDocument from "./internal/schema/toJsonSchemaDocument.ts" +import * as InternalToRepresentation from "./internal/schema/toRepresentation.ts" +import { isSchemaError as isSchemaErrorInternal, SchemaErrorTypeId } from "./internal/schemaError.ts" +import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" +import type * as JsonPatch from "./JsonPatch.ts" +import type * as JsonSchema from "./JsonSchema.ts" import { remainder } from "./Number.ts" -import * as Optic_ from "./Optic.ts" +import type * as Optic_ from "./Optic.ts" import * as Option_ from "./Option.ts" import * as Order from "./Order.ts" import * as Pipeable from "./Pipeable.ts" import * as Predicate from "./Predicate.ts" import * as Record_ from "./Record.ts" import * as Redacted_ from "./Redacted.ts" +import * as RegExp_ from "./RegExp.ts" import * as Result_ from "./Result.ts" -import * as Scheduler from "./Scheduler.ts" import * as SchemaAST from "./SchemaAST.ts" import * as SchemaGetter from "./SchemaGetter.ts" import * as SchemaIssue from "./SchemaIssue.ts" import * as SchemaParser from "./SchemaParser.ts" import type * as SchemaRepresentation from "./SchemaRepresentation.ts" import * as SchemaTransformation from "./SchemaTransformation.ts" +import type { StandardJSONSchemaV1, StandardSchemaV1 } from "./StandardSchema.ts" import type { Assign, Lambda, Mutable, Simplify } from "./Struct.ts" import * as Struct_ from "./Struct.ts" -import * as FastCheck from "./testing/FastCheck.ts" import type { RequiredKeys, UnionToIntersection } from "./Types.ts" import type { Unify } from "./Unify.ts" +import * as Cookies_ from "./unstable/http/Cookies.ts" +import * as Headers_ from "./unstable/http/Headers.ts" +import * as UrlParams_ from "./unstable/http/UrlParams.ts" +import * as IpInterface_ from "./unstable/net/IpInterface.ts" +import * as IpNetwork_ from "./unstable/net/IpNetwork.ts" +import * as NetAddress_ from "./unstable/net/NetAddress.ts" -const TypeId = InternalSchema.TypeId - +const TypeId = InternalMake.TypeId /** * Whether a schema field is required or optional within a struct. * @@ -77,7 +91,6 @@ const TypeId = InternalSchema.TypeId * @since 4.0.0 */ export type Optionality = "required" | "optional" - /** * Whether a schema field is readonly or mutable within a struct. * @@ -87,7 +100,6 @@ export type Optionality = "required" | "optional" * @since 4.0.0 */ export type Mutability = "readonly" | "mutable" - /** * Whether a schema field has a constructor default value. * @@ -98,7 +110,6 @@ export type Mutability = "readonly" | "mutable" * @since 4.0.0 */ export type ConstructorDefault = "no-default" | "with-default" - /** * Options for `makeEffect`, `make`, and Class constructors. * @@ -107,8 +118,8 @@ export type ConstructorDefault = "no-default" | "with-default" * Use when passing `disableChecks: true` to skip validation when you trust the data. * - Pass `parseOptions` to control error reporting behavior. * - * @see {@link Bottom.makeEffect} - * @see {@link Bottom.make} + * @see {@link BottomWithoutNew.makeEffect} + * @see {@link BottomWithoutNew.make} * * @category options * @since 3.13.4 @@ -122,22 +133,27 @@ export interface MakeOptions { * Whether to disable validation for the schema. */ readonly disableChecks?: boolean | undefined -} + /** @internal */ + readonly "~payload"?: { + readonly token: unknown + readonly value: unknown + } +} /** - * The fully-parameterized base interface for all schemas. Exposes all 14 type - * parameters controlling type inference, mutability, optionality, services, and - * transformation behavior. + * The fully-parameterized schema interface without a construct signature. + * Exposes all 14 type parameters controlling type inference, mutability, + * optionality, services, and transformation behavior. * * **When to use** * - * Use when you are writing advanced generic schema utilities or performing - * schema introspection. + * Use as the base for schema interfaces that provide a specialized construct + * signature. * * @category models * @since 4.0.0 */ -export interface Bottom< +export interface BottomWithoutNew< out T, out E, out RD, @@ -155,26 +171,21 @@ export interface Bottom< out EncodedOptionality extends Optionality = "required" > extends Pipeable.Pipeable { readonly [TypeId]: typeof TypeId - readonly "ast": Ast readonly "Rebuild": Rebuild readonly "~type.parameters": TypeParameters - readonly "Type": T readonly "Encoded": E readonly "DecodingServices": RD readonly "EncodingServices": RE - readonly "~type.make.in": TypeMakeIn - readonly "~type.make": TypeMake // useful to type the `refine` interface + readonly "~type.make": TypeMake readonly "~type.constructor.default": TypeConstructorDefault readonly "Iso": Iso - readonly "~type.mutability": TypeMutability readonly "~type.optionality": TypeOptionality readonly "~encoded.mutability": EncodedMutability readonly "~encoded.optionality": EncodedOptionality - annotate(annotations: Annotations.Bottom): this["Rebuild"] annotateKey(annotations: Annotations.Key): this["Rebuild"] check(...checks: readonly [SchemaAST.Check, ...Array>]): this["Rebuild"] @@ -195,12 +206,14 @@ export interface Bottom< * **Gotchas** * * Throws an `Error` with the schema issue in its `cause` when validation - * fails. + * fails. Schema validation failures use the generic message + * `"Schema validation failed"`; format the `cause` explicitly with + * `SchemaIssue.makeFormatterDefault()` when human-readable details are needed. * Causes that contain defects, interruptions, or other non-schema reasons * throw with the underlying `Cause` attached instead. * - * @see {@link Bottom.makeOption} — construct synchronously and discard validation details - * @see {@link Bottom.makeEffect} — construct through `Effect` when validation failure should stay in the error channel + * @see {@link BottomWithoutNew.makeOption} — construct synchronously and discard validation details + * @see {@link BottomWithoutNew.makeEffect} — construct through `Effect` when validation failure should stay in the error channel */ make(input: this["~type.make.in"], options?: MakeOptions): this["Type"] /** @@ -223,8 +236,8 @@ export interface Bottom< * that contain defects, interruptions, or other non-schema reasons throw * instead. * - * @see {@link Bottom.make} — construct synchronously when validation failure should throw - * @see {@link Bottom.makeEffect} — construct through `Effect` when validation details should stay in the error channel + * @see {@link BottomWithoutNew.make} — construct synchronously when validation failure should throw + * @see {@link BottomWithoutNew.makeEffect} — construct through `Effect` when validation details should stay in the error channel */ makeOption(input: this["~type.make.in"], options?: MakeOptions): Option_.Option /** @@ -236,38 +249,98 @@ export interface Bottom< * Use when constructor input may fail validation and you want to * compose that failure with other `Effect` operations instead of throwing. * - * @see {@link Bottom.make} — construct synchronously when validation failure should throw - * @see {@link Bottom.makeOption} — construct synchronously and discard validation details + * **Details** + * + * Validation failures are returned directly as `SchemaIssue.Issue` values + * and are not wrapped in `SchemaError`. + * + * @see {@link BottomWithoutNew.make} — construct synchronously when validation failure should throw + * @see {@link BottomWithoutNew.makeOption} — construct synchronously and discard validation details */ - makeEffect(input: this["~type.make.in"], options?: MakeOptions): Effect.Effect + makeEffect(input: this["~type.make.in"], options?: MakeOptions): Effect.Effect +} +/** + * Fully-parameterized base interface for schemas that can be extended directly + * by TypeScript classes. + * + * **When to use** + * + * Use as the base for concrete schema interfaces whose runtime values support + * `class ... extends schema`. + * + * **Details** + * + * Extends {@link BottomWithoutNew} with a construct signature that accepts `never`. The + * signature enables class extension without making ordinary schemas directly + * constructible. + * + * @see {@link BottomWithoutNew} for the schema protocol without a construct signature + * + * @category utility types + * @since 4.0.0 + */ +export interface Bottom< + out T, + out E, + out RD, + out RE, + out Ast extends SchemaAST.AST, + out Rebuild extends Top, + out TypeMakeIn = T, + out Iso = T, + in out TypeParameters extends ReadonlyArray = readonly [], + out TypeMake = TypeMakeIn, + out TypeMutability extends Mutability = "readonly", + out TypeOptionality extends Optionality = "required", + out TypeConstructorDefault extends ConstructorDefault = "no-default", + out EncodedMutability extends Mutability = "readonly", + out EncodedOptionality extends Optionality = "required" +> extends + BottomWithoutNew< + T, + E, + RD, + RE, + Ast, + Rebuild, + TypeMakeIn, + Iso, + TypeParameters, + TypeMake, + TypeMutability, + TypeOptionality, + TypeConstructorDefault, + EncodedMutability, + EncodedOptionality + > +{ + new(_: never): {} } - /** - * Lazy `Bottom` variant for schema implementations that compute their public - * views on demand. + * Lazy `BottomWithoutNew` variant for schema implementations that + * compute their public views on demand. * * **When to use** * - * Use as an implementation base for schema interfaces that must expose - * `Bottom` behavior without forcing TypeScript to eagerly evaluate expensive - * `Type`, `Encoded`, or service views. + * Use as the base for lazy schema interfaces that provide a specialized + * construct signature. * * **Details** * * The laziness is purely type-level; runtime behavior is unchanged. - * `BottomLazy` keeps the structural operations inherited from `Bottom`, but - * erases the expensive schema views to `unknown`. Concrete schema interfaces can - * then redeclare the precise views they expose. This keeps wide schemas such as - * `Struct` and `Union` cheaper when generic code reads a single view, while - * preserving their exact public types. + * `BottomLazyWithoutNew` keeps the structural operations inherited from + * `BottomWithoutNew`, but erases the expensive schema views to + * `unknown`. Concrete schema interfaces can then redeclare the precise views + * they expose. This keeps wide schemas such as `Struct` and `Union` cheaper when + * generic code reads a single view, while preserving their exact public types. * - * @see {@link Bottom} for the fully parameterized schema interface when every + * @see {@link BottomWithoutNew} for the fully parameterized schema interface when every * view must be supplied directly. * * @category utility types * @since 4.0.0 */ -export interface BottomLazy< +export interface BottomLazyWithoutNew< out Ast extends SchemaAST.AST, out Rebuild extends Top, in out TypeParameters extends ReadonlyArray = readonly [], @@ -277,7 +350,7 @@ export interface BottomLazy< out EncodedMutability extends Mutability = "readonly", out EncodedOptionality extends Optionality = "required" > extends - Bottom< + BottomWithoutNew< unknown, unknown, unknown, @@ -295,7 +368,49 @@ export interface BottomLazy< EncodedOptionality > {} - +/** + * Lazy `Bottom` variant for schemas that can be extended directly by TypeScript + * classes. + * + * **When to use** + * + * Use as the base for concrete lazy schema interfaces whose runtime values + * support `class ... extends schema`. + * + * **Details** + * + * Extends {@link BottomLazyWithoutNew} with a construct signature that accepts `never`. + * The signature enables class extension without making ordinary schemas + * directly constructible. + * + * @see {@link BottomLazyWithoutNew} for the lazy schema protocol without a construct signature + * + * @category utility types + * @since 4.0.0 + */ +export interface BottomLazy< + out Ast extends SchemaAST.AST, + out Rebuild extends Top, + in out TypeParameters extends ReadonlyArray = readonly [], + out TypeMutability extends Mutability = "readonly", + out TypeOptionality extends Optionality = "required", + out TypeConstructorDefault extends ConstructorDefault = "no-default", + out EncodedMutability extends Mutability = "readonly", + out EncodedOptionality extends Optionality = "required" +> extends + BottomLazyWithoutNew< + Ast, + Rebuild, + TypeParameters, + TypeMutability, + TypeOptionality, + TypeConstructorDefault, + EncodedMutability, + EncodedOptionality + > +{ + new(_: never): {} +} /** * Type-level representation returned by {@link declareConstructor}. * @@ -315,7 +430,6 @@ export interface declareConstructor {} - /** * Creates a schema for a **parametric** type (a generic container such as * `Array`, `Option`, etc.) by accepting a list of type-parameter schemas @@ -339,8 +453,8 @@ export interface declareConstructor` type) * - * ```ts - * import { Effect, Option, Schema, SchemaIssue as Issue, SchemaParser } from "effect" + * ```ts import.meta.vitest + * import { Effect, Schema, SchemaIssue, SchemaParser } from "effect" * * interface Box { * readonly value: A @@ -355,7 +469,7 @@ export interface declareConstructor * (u, ast, options) => { * if (!isBox(u)) { - * return Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(u))) + * return Effect.fail(new SchemaIssue.InvalidType(ast, u, options)) * } * return Effect.map( * SchemaParser.decodeUnknownEffect(itemCodec)(u.value, options), @@ -365,6 +479,7 @@ export interface declareConstructor { value: 1 } * ``` * * @category constructors @@ -393,7 +508,6 @@ export function declareConstructor() { ) } } - /** * Type-level representation returned by {@link declare}. * @@ -403,7 +517,6 @@ export function declareConstructor() { export interface declare extends declareConstructor { readonly "Rebuild": declare } - /** * Creates a schema for a **non-parametric** opaque type using a type-guard * function. The schema accepts any unknown value and succeeds when `is` returns @@ -416,7 +529,7 @@ export interface declare extends declareConstructor extends declareConstructor "user_123" * ``` * * @see {@link declareConstructor} for creating schemas for parametric types. @@ -441,14 +555,13 @@ export function declare( ): declare { return declareConstructor()( [], - () => (input, ast) => + () => (input, ast, options) => is(input) ? Effect.succeed(input) : - Effect.fail(new SchemaIssue.InvalidType(ast, Option_.some(input))), + Effect.fail(new SchemaIssue.InvalidType(ast, input, options)), annotations ) } - /** * Returns a schema widened to the fully-parameterized {@link Bottom} interface, * making all 14 type parameters visible to TypeScript. @@ -461,7 +574,7 @@ export function declare( * * **Example** (Inspecting all type parameters of a schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String @@ -499,7 +612,6 @@ export function revealBottom( > { return bottom } - /** * Adds metadata annotations to a schema without changing its runtime behavior. * This is the pipeable (curried) counterpart of the `.annotate` method. @@ -512,15 +624,16 @@ export function revealBottom( * * **Example** (Adding a title and description) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * const Age = Schema.Number.pipe( + * const Age = Schema.Natural.pipe( * Schema.annotate({ * title: "Age", * description: "A non-negative integer representing age in years" * }) * ) + * Schema.resolveAnnotations(Age)?.title // => "Age" * ``` * * @see {@link annotateEncoded} to annotate the encoded side instead. @@ -531,7 +644,6 @@ export function revealBottom( export function annotate(annotations: Annotations.Bottom) { return (self: S) => self.annotate(annotations) } - /** * Adds metadata annotations to the **encoded** side of a schema without * changing its runtime behavior. This is the encoded-side counterpart of @@ -544,7 +656,7 @@ export function annotate(annotations: Annotations.Bottom(annotations: Annotations.Bottom "my title" * ``` * * @see {@link annotate} to annotate the type side instead. @@ -565,7 +676,6 @@ export function annotate(annotations: Annotations.Bottom(annotations: Annotations.Bottom) { return (self: S): S["Rebuild"] => flip(flip(self).annotate(annotations)) } - /** * Adds key-level annotations to a schema field. This is the pipeable * (curried) counterpart of the `.annotateKey` method. @@ -580,7 +690,7 @@ export function annotateEncoded(annotations: Annotations.Bottom(annotations: Annotations.Bottom "Username is required" * ``` * * @category annotations @@ -601,7 +712,6 @@ export function annotateKey(annotations: Annotations.Key {} - /** * Lightweight structural constraint for APIs that accept schema values but only * read their data and type-level views. @@ -682,7 +791,6 @@ export interface Constraint { readonly "~encoded.optionality": Optionality readonly "~encoded.mutability": Mutability } - /** * Lightweight structural constraint for APIs that need codec type views but do * not need the full schema protocol. @@ -705,7 +813,6 @@ export interface ConstraintCodec extends ConstraintCodec {} - /** * Lightweight structural constraint for APIs that need encoder type views but * do not need the full schema protocol. @@ -743,7 +849,6 @@ export interface ConstraintDecoder extends ConstraintCode * @since 4.0.0 */ export interface ConstraintEncoder extends ConstraintCodec {} - /** * Lightweight structural constraint for APIs that need schema views and the * rebuilt schema type, but do not call the full schema protocol. @@ -760,7 +865,6 @@ export interface ConstraintEncoder extends ConstraintCode export interface ConstraintRebuildable extends Constraint { readonly "Rebuild": Constraint } - /** * Namespace of type-level helpers for {@link Schema}. * @@ -772,7 +876,7 @@ export declare namespace Schema { * * **Example** (Extracting the decoded type) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const Person = Schema.Struct({ name: Schema.String, age: Schema.Number }) @@ -783,9 +887,11 @@ export declare namespace Schema { * @category utility types * @since 3.10.0 */ - export type Type = S extends { readonly "Type": infer T } ? T : never + type Type = S extends { + readonly "Type": infer T + } ? T : + never } - /** * A typed view of a schema that tracks only the decoded (output) type `T`. * @@ -801,13 +907,13 @@ export declare namespace Schema { * * **Example** (Accepting any schema decoding to `string`) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * declare function print(schema: Schema.Schema): void + * const accept = (_schema: Schema.Schema): void => {} * - * print(Schema.String) // ok - * print(Schema.NonEmptyString) // ok + * accept(Schema.String) + * accept(Schema.NonEmptyString) * ``` * * @see {@link Codec} — also tracks Encoded, DecodingServices, EncodingServices @@ -820,7 +926,6 @@ export interface Schema extends Top { readonly "Type": T readonly "Rebuild": Schema } - /** * Namespace of type-level helpers for {@link Codec}. * @@ -832,7 +937,7 @@ export declare namespace Codec { * * **Example** (Extracting the encoded type) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.NumberFromString @@ -843,14 +948,16 @@ export declare namespace Codec { * @category utility types * @since 3.10.0 */ - export type Encoded = S extends { readonly "Encoded": infer E } ? E : never - + type Encoded = S extends { + readonly "Encoded": infer E + } ? E : + never /** * Extracts the Effect services required during *decoding* from a schema. * * **Example** (Checking decoding service requirements) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String @@ -861,14 +968,16 @@ export declare namespace Codec { * @category utility types * @since 4.0.0 */ - export type DecodingServices = S extends { readonly "DecodingServices": infer R } ? R : never - + type DecodingServices = S extends { + readonly "DecodingServices": infer R + } ? R : + never /** * Extracts the Effect services required during *encoding* from a schema. * * **Example** (Checking encoding service requirements) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.String @@ -879,34 +988,11 @@ export declare namespace Codec { * @category utility types * @since 4.0.0 */ - export type EncodingServices = S extends { readonly "EncodingServices": infer R } ? R : never -} - -/** - * A schema that additionally supports optic (lens/prism) operations. - * - * **Details** - * - * `Optic` extends {@link Schema}`` with an `Iso` type that - * describes the isomorphic counterpart used by the optic layer. Crucially, - * decoding and encoding require *no* Effect services (`DecodingServices` and - * `EncodingServices` are both `never`), which means the optic can operate - * purely without an Effect runtime. - * - * Most primitive schemas (e.g. `Schema.String`, `Schema.Number`) implement - * `Optic` automatically. You normally interact with this interface through - * {@link Optic_} utilities rather than constructing it directly. - * - * @category models - * @since 4.0.0 - */ -export interface Optic extends Schema { - readonly "Iso": Iso - readonly "DecodingServices": never - readonly "EncodingServices": never - readonly "Rebuild": Optic + type EncodingServices = S extends { + readonly "EncodingServices": infer R + } ? R : + never } - /** * A schema that tracks the decoded type `T`, the encoded type `E`, and the * Effect services required during decoding (`RD`) and encoding (`RE`). @@ -918,18 +1004,19 @@ export interface Optic extends Schema { * Most concrete schemas produced by this module implement `Codec`. * * For APIs that only need one direction, prefer the narrower views: - * - {@link ConstraintDecoder}`` — decode-only - * - {@link ConstraintEncoder}`` — encode-only + * - {@link Decoder}`` — decode-only + * - {@link Encoder}`` — encode-only * - {@link Schema}`` — type-only (no encoded representation) * * **Example** (Accepting a codec that decodes to `number` from `string`) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * - * declare function serialize(codec: Schema.Codec): string + * const serialize = (codec: Schema.Codec, value: T): string => + * Schema.encodeSync(codec)(value) * - * serialize(Schema.NumberFromString) // ok — decodes number, encoded as string + * serialize(Schema.NumberFromString, 42) // => "42" * ``` * * @see {@link Codec.Encoded} — extract the encoded type @@ -946,7 +1033,50 @@ export interface Codec extends readonly "EncodingServices": RE readonly "Rebuild": Codec } - +/** + * A schema that tracks the decoded type `T` and the Effect services required + * during decoding (`RD`). + * + * **When to use** + * + * Use when you need to preserve a schema's decoded type and decoding service + * requirements, but do not need to constrain its encoded representation or + * encoding services. + * + * @see {@link Codec} for preserving both decoded and encoded type information. + * @see {@link Encoder} for the encode-only view. + * + * @category models + * @since 4.0.0 + */ +export interface Decoder extends Schema { + readonly "Encoded": unknown + readonly "DecodingServices": RD + readonly "EncodingServices": unknown + readonly "Rebuild": Decoder +} +/** + * A schema that tracks the encoded type `E` and the Effect services required + * during encoding (`RE`). + * + * **When to use** + * + * Use when you need to preserve a schema's encoded type and encoding service + * requirements, but do not need to constrain its decoded representation or + * decoding services. + * + * @see {@link Codec} for preserving both decoded and encoded type information. + * @see {@link Decoder} for the decode-only view. + * + * @category models + * @since 4.0.0 + */ +export interface Encoder extends Schema { + readonly "Encoded": E + readonly "DecodingServices": unknown + readonly "EncodingServices": RE + readonly "Rebuild": Encoder +} /** * Returns a codec widened to the full {@link Codec} interface, prompting * TypeScript to infer all four type parameters (`T`, `E`, `RD`, `RE`). @@ -959,7 +1089,7 @@ export interface Codec extends * * **Example** (Recovering encoded type from a schema variable) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema: Schema.Schema = Schema.NumberFromString @@ -975,133 +1105,225 @@ export interface Codec extends export function revealCodec(codec: Codec) { return codec } - -export { - /** - * Error thrown (or returned as the error channel value) when schema decoding - * or encoding fails. - * - * **Details** - * - * The `issue` field contains a structured {@link SchemaIssue.Issue} tree describing - * every validation failure, including the path to the problematic value, - * expected types, and actual values received. `message` renders the issue tree - * as a human-readable string. - * - * Use {@link isSchemaError} to narrow an unknown value to `SchemaError`. - * - * **Example** (Catching a SchemaError) - * - * ```ts - * import { Schema } from "effect" - * - * try { - * Schema.decodeUnknownSync(Schema.Number)("not a number") - * } catch (err) { - * if (Schema.isSchemaError(err)) { - * console.log(err.message) - * // Expected number, actual "not a number" - * } - * } - * ``` - * - * @category errors - * @since 4.0.0 - */ - SchemaError -} - /** - * Returns `true` if `u` is a {@link SchemaError}. + * A schema that additionally supports optic (lens/prism) operations. * - * **Example** (Narrowing Schema errors in a catch block) + * **Details** * - * ```ts - * import { Schema } from "effect" + * `Optic` extends {@link Schema}`` with an `Iso` type that + * describes the isomorphic counterpart used by the optic layer. Crucially, + * decoding and encoding require *no* Effect services (`DecodingServices` and + * `EncodingServices` are both `never`), which means the optic can operate + * purely without an Effect runtime. * - * try { - * Schema.decodeUnknownSync(Schema.Number)("oops") - * } catch (err) { - * if (Schema.isSchemaError(err)) { - * console.log(err._tag) // "SchemaError" - * } - * } - * ``` + * Most primitive schemas (e.g. `Schema.String`, `Schema.Number`) implement + * `Optic` automatically. You normally interact with this interface through + * {@link Optic_} utilities rather than constructing it directly. * - * @category guards + * @category models * @since 4.0.0 */ -export function isSchemaError(u: unknown): u is SchemaError { - return Predicate.hasProperty(u, InternalSchema.SchemaErrorTypeId) -} - -function makeStandardResult(exit: Exit_.Exit>): StandardSchemaV1.Result { - return Exit_.isSuccess(exit) ? exit.value : { - issues: [{ message: Cause_.pretty(exit.cause) }] - } +export interface Optic extends Schema { + readonly "Iso": Iso + readonly "DecodingServices": never + readonly "EncodingServices": never + readonly "Rebuild": Optic } - /** - * Returns a "Standard Schema" object conforming to the [Standard Schema - * v1](https://standardschema.dev/) specification. + * Error thrown or returned when schema decoding or encoding fails. * * **Details** * - * This function creates a schema whose `validate` method attempts to decode and - * validate the provided input synchronously. If the underlying `Schema` - * includes any asynchronous components (e.g., asynchronous message resolutions - * or checks), then validation will necessarily return a `Promise` instead. + * The `issue` field contains a structured {@link SchemaIssue.Issue} tree describing + * every validation failure, including the path to the problematic value and + * the expected type or constraint. The `message` field renders the issue tree + * with the default formatter. * - * **Example** (Creating a standard schema from a regular schema) - * - * ```ts - * import { Schema } from "effect" + * **Gotchas** * - * // Define custom hook functions for error formatting - * const leafHook = (issue: any) => { - * switch (issue._tag) { - * case "InvalidType": - * return "Expected different type" - * case "InvalidValue": - * return "Invalid value provided" - * case "MissingKey": - * return "Required property missing" - * case "UnexpectedKey": - * return "Unexpected property found" - * case "Forbidden": - * return "Operation not allowed" - * case "OneOf": - * return "Multiple valid options available" - * default: - * return "Validation error" - * } - * } + * Parsing with `reportInput: true` adds an enumerable `input` field to + * value-bearing issues. Built-in messages may include reported input, and + * custom annotations or messages are not sanitized. * - * // Create a standard schema from a regular schema - * const PersonSchema = Schema.Struct({ - * name: Schema.NonEmptyString, - * age: Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 150 })) - * }) + * **Example** (Inspecting a SchemaError) * - * const standardSchema = Schema.toStandardSchemaV1(PersonSchema, { - * leafHook - * }) + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" * - * // The standard schema can be used with any Standard Schema v1 compatible library - * const validResult = standardSchema["~standard"].validate({ - * name: "Alice", - * age: 30 - * }) - * console.log(validResult) // { value: { name: "Alice", age: 30 } } + * const result = Schema.decodeUnknownResult(Schema.Number)("not a number") + * const message = Result.isFailure(result) ? result.failure.message : "" + * message // => "Expected number" + * ``` * + * @see {@link isSchemaError} for narrowing unknown values + * @category errors + * @since 4.0.0 + */ +export class SchemaError extends Data.TaggedError("SchemaError")<{ + readonly issue: SchemaIssue.Issue +}> { + readonly [SchemaErrorTypeId]: typeof SchemaErrorTypeId = SchemaErrorTypeId + constructor(issue: SchemaIssue.Issue) { + const stackTraceLimit = getStackTraceLimit() + setStackTraceLimit(0) + try { + super({ issue }) + } finally { + setStackTraceLimit(stackTraceLimit) + } + } + override get message() { + return SchemaIssue.defaultFormatter(this.issue) + } + override toString() { + return `SchemaError(${this.message})` + } +} +/** + * Returns `true` if `u` is a {@link SchemaError}. + * + * **When to use** + * + * Use when you need to narrow an unknown value to `SchemaError`. + * + * **Example** (Narrowing Schema errors) + * + * ```ts import.meta.vitest + * import { Result, Schema } from "effect" + * + * const result = Result.try(() => Schema.decodeUnknownSync(Schema.Number)("oops")) + * const error: unknown = Result.isFailure(result) ? result.failure : undefined + * Schema.isSchemaError(error) // => true + * ``` + * + * @category guards + * @since 4.0.0 + */ +export function isSchemaError(u: unknown): u is SchemaError { + return isSchemaErrorInternal(u) +} + +function fromIssueEffect( + self: Effect.Effect +): Effect.Effect { + if (effectIsExit(self)) { + return fromIssueExit(self as Exit_.Exit) + } + return Effect.catchCause( + self, + (cause) => Effect.failCauseSync(() => Cause_.map(cause, (issue) => new SchemaError(issue))) + ) +} + +function fromIssueExit(exit: Exit_.Exit): Exit_.Exit { + return Exit_.isSuccess(exit) + ? exit as unknown as Exit_.Exit + : Exit_.failCause(Cause_.map(exit.cause, (issue) => new SchemaError(issue))) +} + +function getSchemaErrorOrThrow( + cause: Cause_.Cause, + message: string +): SchemaError { + let schemaError: SchemaError | undefined + for (const reason of cause.reasons) { + if (!Cause_.isFailReason(reason) || !isSchemaError(reason.error)) { + throw new globalThis.Error(message, { cause }) + } + schemaError ??= reason.error + } + if (schemaError === undefined) { + throw new globalThis.Error(message, { cause }) + } + return schemaError +} + +function runSchemaErrorPromise( + self: Effect.Effect +): Promise { + return Effect.runPromiseExit(self).then((exit) => { + if (Exit_.isSuccess(exit)) { + return exit.value + } + throw getSchemaErrorOrThrow(exit.cause, "Promise adapter can only reject schema errors") + }) +} + +function runSchemaErrorSync( + self: Effect.Effect +): A { + const exit = Effect.runSyncExit(self) + if (Exit_.isSuccess(exit)) { + return exit.value + } + throw getSchemaErrorOrThrow(exit.cause, "Sync adapter can only throw schema errors") +} +/** + * Returns a "Standard Schema" object conforming to the [Standard Schema + * v1](https://standardschema.dev/) specification. + * + * **Details** + * + * This function creates a schema whose `validate` method attempts to decode and + * validate the provided input synchronously. If the underlying `Schema` + * includes any asynchronous components (e.g., asynchronous message resolutions + * or checks), then validation will necessarily return a `Promise` instead. + * + * **Example** (Creating a standard schema from a regular schema) + * + * ```ts import.meta.vitest + * import { Schema } from "effect" + * + * // Define custom hook functions for error formatting + * const leafHook = (issue: any) => { + * switch (issue._tag) { + * case "InvalidType": + * return "Expected different type" + * case "InvalidValue": + * return "Invalid value provided" + * case "MissingKey": + * return "Required property missing" + * case "UnexpectedKey": + * return "Unexpected property found" + * case "Forbidden": + * return "Operation not allowed" + * case "OneOf": + * return "Multiple valid options available" + * default: + * return "Validation error" + * } + * } + * + * // Create a standard schema from a regular schema + * const PersonSchema = Schema.Struct({ + * name: Schema.NonEmptyString, + * age: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 150 })) + * }) + * + * const standardSchema = Schema.toStandardSchemaV1(PersonSchema, { + * leafHook + * }) + * + * // The standard schema can be used with any Standard Schema v1 compatible library + * const validResult = standardSchema["~standard"].validate({ + * name: "Alice", + * age: 30 + * }) * const invalidResult = standardSchema["~standard"].validate({ * name: "", * age: 200 * }) - * console.log(invalidResult) // { issues: [{ path: ["name"], message: "..." }, { path: ["age"], message: "..." }] } + * + * if (validResult instanceof Promise || invalidResult instanceof Promise) { + * throw new Error("Expected synchronous validation") + * } + * if ("value" in validResult) { + * validResult.value // => { name: "Alice", age: 30 } + * } + * invalidResult.issues?.map((issue) => issue.path) // => [["name"], ["age"]] * ``` * - * @category Standard Schema + * @category converting * @since 4.0.0 */ export function toStandardSchemaV1>( @@ -1112,67 +1334,8 @@ export function toStandardSchemaV1>( readonly parseOptions?: SchemaAST.ParseOptions | undefined } ): StandardSchemaV1 & S { - const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(self) as ( - input: unknown, - options?: SchemaAST.ParseOptions - ) => Effect.Effect - const parseOptions: SchemaAST.ParseOptions = { errors: "all", ...options?.parseOptions } - const formatter = SchemaIssue.makeFormatterStandardSchemaV1(options) - const validate: StandardSchemaV1["~standard"]["validate"] = (value: unknown) => { - const scheduler = new Scheduler.MixedScheduler() - const fiber = Effect.runFork( - Effect.match(decodeUnknownEffect(value, parseOptions), { - onFailure: formatter, - onSuccess: (value): StandardSchemaV1.Result => ({ value }) - }), - { scheduler } - ) - fiber.currentDispatcher?.flush() - const exit = fiber.pollUnsafe() - if (exit) { - return makeStandardResult(exit) - } - return new Promise((resolve) => { - fiber.addObserver((exit) => { - resolve(makeStandardResult(exit)) - }) - }) - } - if ("~standard" in self) { - const out = self as any - if ("validate" in out["~standard"]) return out - Object.assign(out["~standard"], { validate }) - return out - } else { - return Object.assign(self, { - "~standard": { - version: 1, - vendor: "effect", - validate - } as const - }) - } -} - -function toBaseStandardJSONSchemaV1(self: Constraint, target: StandardJSONSchemaV1.Target): JsonSchema.JsonSchema { - const doc2020_12 = toJsonSchemaDocument(self) - if (target === "draft-2020-12") { - const schema = doc2020_12.schema - if (Object.keys(doc2020_12.definitions).length > 0) { - schema.$defs = doc2020_12.definitions - } - return schema - } else if (target === "draft-07") { - const doc07 = JsonSchema.toDocumentDraft07(doc2020_12) - const schema = doc07.schema - if (Object.keys(doc07.definitions).length > 0) { - schema.definitions = doc07.definitions - } - return schema - } - throw new globalThis.Error(`Unsupported target: ${target}`) + return InternalStandardSchema.toStandardSchemaV1(self, options) } - /** * Converts a schema to an experimental Standard JSON Schema V1 representation. * @@ -1180,36 +1343,14 @@ function toBaseStandardJSONSchemaV1(self: Constraint, target: StandardJSONSchema * * https://github.com/standard-schema/standard-schema/pull/134 * - * @category Standard Schema + * @category converting * @since 4.0.0 */ export function toStandardJSONSchemaV1( self: S ): StandardJSONSchemaV1 & S { - const jsonSchema: StandardJSONSchemaV1.Props["jsonSchema"] = { - input(options) { - return toBaseStandardJSONSchemaV1(self, options.target) - }, - output(options) { - return toBaseStandardJSONSchemaV1(toType(self), options.target) - } - } - if ("~standard" in self) { - const out = self as any - if ("jsonSchema" in out["~standard"]) return out - Object.assign(out["~standard"], { jsonSchema }) - return out - } else { - return Object.assign(self, { - "~standard": { - version: 1, - vendor: "effect", - jsonSchema - } as const - }) - } + return InternalStandardSchema.toStandardJSONSchemaV1(self) } - /** * Creates a type guard function that checks if a value conforms to a given * schema. @@ -1228,27 +1369,26 @@ export function toStandardJSONSchemaV1( * * **Example** (Defining a basic type guard) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const isString = Schema.is(Schema.String) * - * console.log(isString("hello")) // true - * console.log(isString(42)) // false + * isString("hello") // => true + * isString(42) // => false * * // Type narrowing in action * const value: unknown = "hello" * if (isString(value)) { * // value is now typed as string - * console.log(value.toUpperCase()) // "HELLO" + * value.toUpperCase() // => "HELLO" * } * ``` * * @category guards * @since 3.10.0 */ -export const is = SchemaParser.is - +export const is: typeof SchemaParser.is = SchemaParser.is /** * Creates an assertion function that throws an error if the input does not match * the schema. @@ -1262,6 +1402,9 @@ export const is = SchemaParser.is * * The input is narrowed if the assertion succeeds. If schema validation fails, * the assertion throws an `Error` whose cause is `SchemaIssue.Issue`. + * Schema validation failures use the generic message `"Schema validation failed"`. + * Format the `cause` explicitly with `SchemaIssue.makeFormatterDefault()` when + * human-readable details are needed. * * **Gotchas** * @@ -1271,21 +1414,23 @@ export const is = SchemaParser.is * * **Example** (Asserting and narrowing an input) * - * ```ts - * import { Schema } from "effect" + * ```ts import.meta.vitest + * import { Schema, SchemaIssue } from "effect" * * const input: unknown = "hello" * * // This will pass silently (no return value) and narrow input to string * Schema.asserts(Schema.String, input) - * console.log(input.toUpperCase()) + * input.toUpperCase() // => "HELLO" * * // This will throw an error * try { * const invalid: unknown = 123 * Schema.asserts(Schema.String, invalid) * } catch (error) { - * console.log("Non-string assertion failed as expected") + * if (error instanceof Error) { + * SchemaIssue.isIssue(error.cause) // => true + * } * } * ``` * @@ -1294,7 +1439,6 @@ export const is = SchemaParser.is */ export const asserts: (schema: S, input: I) => asserts input is I & S["Type"] = SchemaParser.asserts - /** * Decodes an `unknown` input against a schema, returning an `Effect` that * succeeds with the decoded value or fails with a {@link SchemaError}. @@ -1322,10 +1466,9 @@ export function decodeUnknownEffect(schema: S, options?: S input: unknown, options?: SchemaAST.ParseOptions ): Effect.Effect => { - return InternalSchema.mapSchemaIssueEffect(parser(input, options)) + return fromIssueEffect(parser(input, options)) } } - /** * Decodes a typed input (the schema's `Encoded` type) against a schema, * returning an `Effect` that succeeds with the decoded value or fails with a @@ -1354,45 +1497,6 @@ export const decodeEffect: ( input: S["Encoded"], options?: SchemaAST.ParseOptions ) => Effect.Effect = decodeUnknownEffect - -function getSchemaErrorOrThrow( - cause: Cause_.Cause, - message: string -): SchemaError { - let schemaError: SchemaError | undefined - for (const reason of cause.reasons) { - if (!Cause_.isFailReason(reason) || !isSchemaError(reason.error)) { - throw new globalThis.Error(message, { cause }) - } - schemaError ??= reason.error - } - if (schemaError === undefined) { - throw new globalThis.Error(message, { cause }) - } - return schemaError -} - -function runSchemaErrorPromise( - self: Effect.Effect -): Promise { - return Effect.runPromiseExit(self).then((exit) => { - if (Exit_.isSuccess(exit)) { - return exit.value - } - throw getSchemaErrorOrThrow(exit.cause, "Promise adapter can only reject schema errors") - }) -} - -function runSchemaErrorSync( - self: Effect.Effect -): A { - const exit = Effect.runSyncExit(self) - if (Exit_.isSuccess(exit)) { - return exit.value - } - throw getSchemaErrorOrThrow(exit.cause, "Sync adapter can only throw schema errors") -} - /** * Decodes an `unknown` input against a schema synchronously, returning an * `Exit` that is either a `Success` with the decoded value or a `Failure`. @@ -1426,10 +1530,9 @@ function runSchemaErrorSync( export function decodeUnknownExit>(schema: S, options?: SchemaAST.ParseOptions) { const parser = SchemaParser.decodeUnknownExit(schema, options) return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit => { - return InternalSchema.mapSchemaIssueExit(parser(input, options)) + return fromIssueExit(parser(input, options)) } } - /** * Decodes a typed input (the schema's `Encoded` type) against a schema * synchronously, returning an `Exit` that is either a `Success` with the decoded @@ -1464,7 +1567,6 @@ export const decodeExit: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Exit_.Exit = decodeUnknownExit - /** * Decodes an `unknown` input against a schema, returning an `Option` that is * `Some` with the decoded value on success or `None` for schema mismatches. @@ -1495,7 +1597,6 @@ export const decodeUnknownOption: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: unknown, options?: SchemaAST.ParseOptions) => Option_.Option = SchemaParser.decodeUnknownOption - /** * Decodes a typed input (the schema's `Encoded` type) against a schema, * returning an `Option` that is `Some` with the decoded value on success or @@ -1525,7 +1626,6 @@ export const decodeOption: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Option_.Option = SchemaParser.decodeOption - /** * Decodes an `unknown` input against a schema, returning a `Result` that * succeeds with the decoded value or fails with a {@link SchemaError} for schema @@ -1561,7 +1661,6 @@ export function decodeUnknownResult>(schema return Result_.mapError(parser(input, options), (issue) => new SchemaError(issue)) } } - /** * Decodes a typed input (the schema's `Encoded` type) against a schema, * returning a `Result` that succeeds with the decoded value or fails with a @@ -1595,7 +1694,6 @@ export const decodeResult: >( options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Result_.Result = decodeUnknownResult - /** * Decodes an `unknown` input against a schema, returning a `Promise` that * resolves with the decoded value or rejects with a {@link SchemaError} for @@ -1632,7 +1730,6 @@ export function decodeUnknownPromise>( return runSchemaErrorPromise(parser(input, options)) } } - /** * Decodes a typed input (the schema's `Encoded` type) against a schema, * returning a `Promise` that resolves with the decoded value or rejects with a @@ -1664,7 +1761,6 @@ export const decodePromise: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => Promise = decodeUnknownPromise - /** * Decodes an `unknown` input against a schema synchronously, returning the * decoded value or throwing a {@link SchemaError} for schema mismatches. @@ -1689,19 +1785,12 @@ export const decodePromise: >( * * **Example** (Decoding with a transformation schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const NumberFromString = Schema.NumberFromString * - * console.log(Schema.decodeUnknownSync(NumberFromString)("42")) - * // Output: 42 - * - * Schema.decodeUnknownSync(NumberFromString)("not a number") - * // throws SchemaError: NumberFromString - * // └─ Encoded side transformation failure - * // └─ NumberFromString - * // └─ Expected a numeric string, actual "not a number" + * Schema.decodeUnknownSync(NumberFromString)("42") // => 42 * ``` * * @see {@link SchemaParser.decodeUnknownSync} for the adapter that throws an `Error` whose cause is `SchemaIssue.Issue` @@ -1715,7 +1804,6 @@ export function decodeUnknownSync>(schema: return runSchemaErrorSync(parser(input, options)) } } - /** * Decodes a typed input (the schema's `Encoded` type) against a schema * synchronously, returning the decoded value or throwing a {@link SchemaError} @@ -1746,7 +1834,6 @@ export const decodeSync: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Encoded"], options?: SchemaAST.ParseOptions) => S["Type"] = decodeUnknownSync - /** * Encodes an `unknown` input against a schema, returning an `Effect` that * succeeds with the encoded value or fails with a {@link SchemaError}. @@ -1765,13 +1852,12 @@ export const decodeSync: >( * * **Example** (Encoding a value to a string) * - * ```ts + * ```ts import.meta.vitest * import { Effect, Schema } from "effect" * * const NumberFromString = Schema.NumberFromString * - * Effect.runPromise(Schema.encodeUnknownEffect(NumberFromString)(42)).then(console.log) - * // Output: "42" + * await Effect.runPromise(Schema.encodeUnknownEffect(NumberFromString)(42)) // => "42" * ``` * * @see {@link SchemaParser.encodeUnknownEffect} for the adapter that fails with `SchemaIssue.Issue` directly @@ -1785,10 +1871,9 @@ export function encodeUnknownEffect(schema: S, options?: S input: unknown, options?: SchemaAST.ParseOptions ): Effect.Effect => { - return InternalSchema.mapSchemaIssueEffect(parser(input, options)) + return fromIssueEffect(parser(input, options)) } } - /** * Encodes a typed input (the schema's `Type`) against a schema, returning an * `Effect` that succeeds with the encoded value or fails with a @@ -1817,7 +1902,6 @@ export const encodeEffect: ( input: S["Type"], options?: SchemaAST.ParseOptions ) => Effect.Effect = encodeUnknownEffect - /** * Encodes an `unknown` input against a schema synchronously, returning an * `Exit` that is either a `Success` with the encoded value or a `Failure`. @@ -1850,10 +1934,9 @@ export const encodeEffect: ( export function encodeUnknownExit>(schema: S, options?: SchemaAST.ParseOptions) { const parser = SchemaParser.encodeUnknownExit(schema, options) return (input: unknown, options?: SchemaAST.ParseOptions): Exit_.Exit => { - return InternalSchema.mapSchemaIssueExit(parser(input, options)) + return fromIssueExit(parser(input, options)) } } - /** * Encodes a typed input (the schema's `Type`) against a schema synchronously, * returning an `Exit` that is either a `Success` with the encoded value or a @@ -1888,7 +1971,6 @@ export const encodeExit: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Exit_.Exit = encodeUnknownExit - /** * Encodes an `unknown` input against a schema, returning an `Option` that is * `Some` with the encoded value on success or `None` for schema mismatches. @@ -1920,7 +2002,6 @@ export const encodeUnknownOption: >( options?: SchemaAST.ParseOptions ) => (input: unknown, options?: SchemaAST.ParseOptions) => Option_.Option = SchemaParser.encodeUnknownOption - /** * Encodes a typed input (the schema's `Type`) against a schema, returning an * `Option` that is `Some` with the encoded value on success or `None` for schema @@ -1950,7 +2031,6 @@ export const encodeOption: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Option_.Option = SchemaParser.encodeOption - /** * Encodes an `unknown` input against a schema, returning a `Result` that * succeeds with the encoded value or fails with a {@link SchemaError} for schema @@ -1985,7 +2065,6 @@ export function encodeUnknownResult>(schema return Result_.mapError(parser(input, options), (issue) => new SchemaError(issue)) } } - /** * Encodes a typed input (the schema's `Type`) against a schema, returning a * `Result` that succeeds with the encoded value or fails with a @@ -2019,7 +2098,6 @@ export const encodeResult: >( options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Result_.Result = encodeUnknownResult - /** * Encodes an `unknown` input against a schema, returning a `Promise` that * resolves with the encoded value or rejects with a {@link SchemaError} for @@ -2055,7 +2133,6 @@ export function encodeUnknownPromise>( return runSchemaErrorPromise(parser(input, options)) } } - /** * Encodes a typed input (the schema's `Type`) against a schema, returning a * `Promise` that resolves with the encoded value or rejects with a @@ -2087,7 +2164,6 @@ export const encodePromise: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => Promise = encodeUnknownPromise - /** * Encodes an `unknown` input against a schema synchronously, throwing a * {@link SchemaError} for schema mismatches. @@ -2120,7 +2196,6 @@ export function encodeUnknownSync>(schema: return runSchemaErrorSync(parser(input, options) as Effect.Effect) } } - /** * Encodes a typed input (the schema's `Type`) against a schema synchronously, * throwing a {@link SchemaError} for schema mismatches. @@ -2149,7 +2224,6 @@ export const encodeSync: >( schema: S, options?: SchemaAST.ParseOptions ) => (input: S["Type"], options?: SchemaAST.ParseOptions) => S["Encoded"] = encodeUnknownSync - /** * Creates a schema from an AST (Abstract Syntax Tree) node. * @@ -2167,35 +2241,7 @@ export const encodeSync: >( * @category constructors * @since 3.10.0 */ -export const make: (ast: S["ast"], options?: object) => S = InternalSchema.make - -/** - * Transforms a schema into a class that can be extended with `extends`. The - * resulting class inherits the full schema API (e.g. `annotate`) and can define - * static methods that reference `this`. - * - * **Example** (Wrapping a primitive schema) - * - * ```ts - * import { Schema } from "effect" - * - * class MyString extends Schema.asClass(Schema.String) { - * static readonly decodeUnknownSync = Schema.decodeUnknownSync(this) - * } - * - * console.log(MyString.decodeUnknownSync("a")) - * // "a" - * ``` - * - * @category constructors - * @since 4.0.0 - */ -export function asClass(schema: S): S & { new(_: never): {} } { - // oxlint-disable-next-line @typescript-eslint/no-extraneous-class - class Class {} - return Object.setPrototypeOf(Class, schema) -} - +export const make: (ast: S["ast"], options?: object) => S = InternalMake.make /** * Checks whether a value is a `Schema`. * @@ -2205,7 +2251,6 @@ export function asClass(schema: S): S & { new(_: never): {} } { export function isSchema(u: unknown): u is Top { return Predicate.hasProperty(u, TypeId) && u[TypeId] === TypeId } - /** * Type-level representation returned by {@link optionalKey}. * @@ -2233,12 +2278,10 @@ export interface optionalKey extends readonly "Iso": S["Iso"] readonly schema: S } - interface optionalKeyLambda extends Lambda { (self: S): optionalKey readonly "~lambda.out": this["~lambda.in"] extends Constraint ? optionalKey : never } - /** * Creates an exact optional key schema for struct fields. Unlike `optional`, * this creates exact optional properties (not `| undefined`) that can be @@ -2246,7 +2289,7 @@ interface optionalKeyLambda extends Lambda { * * **Example** (Creating a struct with optional key) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Struct({ @@ -2261,16 +2304,14 @@ interface optionalKeyLambda extends Lambda { * @category combinators * @since 4.0.0 */ -export const optionalKey = Struct_.lambda((schema) => +export const optionalKey: optionalKeyLambda = Struct_.lambda((schema) => make(SchemaAST.optionalKey(schema.ast), { schema }) ) - interface requiredKeyLambda extends Lambda { (self: optionalKey): S readonly "~lambda.out": this["~lambda.in"] extends optionalKey ? this["~lambda.in"]["schema"] : "Error: schema not eligible for requiredKey" } - /** * Reverses `optionalKey` and returns the inner required schema. * @@ -2282,8 +2323,7 @@ interface requiredKeyLambda extends Lambda { * @category combinators * @since 4.0.0 */ -export const requiredKey = Struct_.lambda((self) => self.schema) - +export const requiredKey: requiredKeyLambda = Struct_.lambda((self) => self.schema) /** * Type-level representation returned by {@link optional}. * @@ -2293,12 +2333,10 @@ export const requiredKey = Struct_.lambda((self) => self.sche export interface optional extends optionalKey> { readonly "Rebuild": optional } - interface optionalLambda extends Lambda { (self: S): optional readonly "~lambda.out": this["~lambda.in"] extends Constraint ? optional : never } - /** * Marks a struct field as optional, allowing the key to be absent or * `undefined`. @@ -2313,7 +2351,7 @@ interface optionalLambda extends Lambda { * * **Example** (Defining an optional field accepting undefined) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Struct({ @@ -2328,14 +2366,15 @@ interface optionalLambda extends Lambda { * @category combinators * @since 3.10.0 */ -export const optional = Struct_.lambda((self) => optionalKey(UndefinedOr(self))) - +export const optional: optionalLambda = Struct_.lambda((self) => { + const schema = UndefinedOr(self) + return make(SchemaAST.optional(self.ast), { schema }) +}) interface requiredLambda extends Lambda { (self: optional): S readonly "~lambda.out": this["~lambda.in"] extends optional ? this["~lambda.in"]["schema"]["members"][0] : "Error: schema not eligible for required" } - /** * Reverses `optional` and returns the inner schema. * @@ -2351,8 +2390,7 @@ interface requiredLambda extends Lambda { * @category combinators * @since 3.10.0 */ -export const required = Struct_.lambda((self) => self.schema.members[0]) - +export const required: requiredLambda = Struct_.lambda((self) => self.schema.members[0]) /** * Type-level representation returned by {@link mutableKey}. * @@ -2380,12 +2418,10 @@ export interface mutableKey extends readonly "Iso": S["Iso"] readonly schema: S } - interface mutableKeyLambda extends Lambda { (self: S): mutableKey readonly "~lambda.out": this["~lambda.in"] extends Constraint ? mutableKey : never } - /** * Makes a struct field mutable (removes the `readonly` modifier on the property). * Use {@link readonlyKey} to reverse. @@ -2393,16 +2429,14 @@ interface mutableKeyLambda extends Lambda { * @category combinators * @since 4.0.0 */ -export const mutableKey = Struct_.lambda((schema) => +export const mutableKey: mutableKeyLambda = Struct_.lambda((schema) => make(SchemaAST.mutableKey(schema.ast), { schema }) ) - interface readonlyKeyLambda extends Lambda { (self: mutableKey): S readonly "~lambda.out": this["~lambda.in"] extends mutableKey ? this["~lambda.in"]["schema"] : "Error: schema not eligible for readonlyKey" } - /** * Reverses `mutableKey` and returns the inner readonly schema. * @@ -2414,8 +2448,7 @@ interface readonlyKeyLambda extends Lambda { * @category combinators * @since 4.0.0 */ -export const readonlyKey = Struct_.lambda((self) => self.schema) - +export const readonlyKey: readonlyKeyLambda = Struct_.lambda((self) => self.schema) /** * Type-level representation returned by {@link toType}. * @@ -2443,12 +2476,10 @@ export interface toType extends readonly "Iso": S["Iso"] readonly schema: S } - interface toTypeLambda extends Lambda { (self: S): toType readonly "~lambda.out": this["~lambda.in"] extends Constraint ? toType : never } - /** * Extracts the type-side schema: sets `Encoded` to equal the decoded `Type`, * discarding the encoding transformation path. @@ -2456,8 +2487,9 @@ interface toTypeLambda extends Lambda { * @category transforming * @since 4.0.0 */ -export const toType = Struct_.lambda((schema) => make(SchemaAST.toType(schema.ast), { schema })) - +export const toType: toTypeLambda = Struct_.lambda((schema) => + make(SchemaAST.toType(schema.ast), { schema }) +) /** * Type-level representation returned by {@link toEncoded}. * @@ -2485,12 +2517,10 @@ export interface toEncoded extends readonly "Iso": S["Encoded"] readonly schema: S } - interface toEncodedLambda extends Lambda { (self: S): toEncoded readonly "~lambda.out": this["~lambda.in"] extends Constraint ? toEncoded : never } - /** * Extracts the encoded-side schema: sets `Type` to equal the `Encoded`, * discarding the decoding transformation path. @@ -2498,10 +2528,10 @@ interface toEncodedLambda extends Lambda { * @category transforming * @since 4.0.0 */ -export const toEncoded = Struct_.lambda((schema) => make(SchemaAST.toEncoded(schema.ast), { schema })) - +export const toEncoded: toEncodedLambda = Struct_.lambda((schema) => + make(SchemaAST.toEncoded(schema.ast), { schema }) +) const FlipTypeId = "~effect/Schema/flip" - /** * Type-level representation returned by {@link flip}. * @@ -2534,7 +2564,6 @@ export interface flip extends function isFlip$(schema: Top): schema is flip { return Predicate.hasProperty(schema, FlipTypeId) && schema[FlipTypeId] === FlipTypeId } - /** * Swaps the decoded and encoded sides of a schema. * @@ -2548,12 +2577,12 @@ function isFlip$(schema: Top): schema is flip { * * **Example** (Flipping a number-from-string schema) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * // NumberFromString: decodes string → number * const flipped = Schema.flip(Schema.NumberFromString) - * // flipped: decodes number → string + * Schema.decodeSync(flipped)(42) // => "42" * ``` * * @category transforming @@ -2566,7 +2595,6 @@ export function flip(schema: S): flip { } return make(SchemaAST.flip(schema.ast), { [FlipTypeId]: FlipTypeId, schema }) } - /** * Type-level representation returned by {@link Literal}. * @@ -2579,17 +2607,17 @@ export interface Literal readonly literal: L transform(to: L2): decodeTo, Literal> } - /** * Creates a schema for a single literal value (string, number, bigint, boolean, or null). * * **Example** (Defining a string literal) * - * ```ts + * ```ts import.meta.vitest * import { Schema } from "effect" * * const schema = Schema.Literal("hello") * // Type: Schema.Literal<"hello"> + * Schema.decodeSync(schema)("hello") // => "hello" * ``` * * @see {@link Literals} for a schema that represents a union of literals. @@ -2610,7 +2638,6 @@ export function Literal(literal: L): Literal - - type AppendType< - Template extends string, - Next - > = Next extends LiteralPart ? `${Template}${Next}` - : Next extends { readonly Encoded: infer E extends LiteralPart } ? `${Template}${E}` - : never - + type Parts = ReadonlyArray + type AppendType